By using this site, you agree to have cookies stored on your device, strictly for functional purposes, such as storing your session and preferences.

Dismiss

 app.py

View raw Download
text/x-script.python • 49.6 kiB
Python script, Unicode text, UTF-8 text executable
        
            
1
__version__ = "0.0.1"
2
3
import os
4
import shutil
5
import random
6
import subprocess
7
import platform
8
import git
9
import mimetypes
10
import magic
11
import flask
12
import cairosvg
13
import celery
14
import shlex
15
from functools import wraps
16
from datetime import datetime
17
from enum import Enum
18
from cairosvg import svg2png
19
from flask_sqlalchemy import SQLAlchemy
20
from flask_bcrypt import Bcrypt
21
from markupsafe import escape, Markup
22
from flask_migrate import Migrate
23
from PIL import Image
24
from flask_httpauth import HTTPBasicAuth
25
import config
26
from flask_babel import Babel, gettext, ngettext, force_locale
27
28
_ = gettext
29
n_ = gettext
30
31
app = flask.Flask(__name__)
32
app.config.from_mapping(
33
CELERY=dict(
34
broker_url=config.REDIS_URI,
35
result_backend=config.REDIS_URI,
36
task_ignore_result=True,
37
),
38
)
39
40
auth = HTTPBasicAuth()
41
42
app.config["SQLALCHEMY_DATABASE_URI"] = config.DB_URI
43
app.config["SECRET_KEY"] = config.DB_PASSWORD
44
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
45
app.config["BABEL_TRANSLATION_DIRECTORIES"] = "i18n"
46
app.config["MAX_CONTENT_LENGTH"] = config.MAX_PAYLOAD_SIZE
47
48
db = SQLAlchemy(app)
49
bcrypt = Bcrypt(app)
50
migrate = Migrate(app, db)
51
52
from models import *
53
from misc_utils import *
54
55
import git_http
56
import jinja_utils
57
import celery_tasks
58
from celery import Celery, Task
59
import celery_integration
60
import pathlib
61
62
babel = Babel(app)
63
64
65
def get_locale():
66
if flask.request.cookies.get("language"):
67
return flask.request.cookies.get("language")
68
return flask.request.accept_languages.best_match(config.available_locales)
69
70
71
babel.init_app(app, locale_selector=get_locale)
72
73
with app.app_context():
74
locale_names = {}
75
for language in config.available_locales:
76
with force_locale(language):
77
# NOTE: Translate this to the language's name in that language, for example in French you would use français
78
locale_names[language] = gettext("English")
79
80
worker = celery_integration.init_celery_app(app)
81
82
repositories = flask.Blueprint("repository", __name__, template_folder="templates/repository/")
83
84
85
@app.context_processor
86
def default():
87
username = flask.session.get("username")
88
89
user_object = User.query.filter_by(username=username).first()
90
91
return {
92
"logged_in_user": username,
93
"user_object": user_object,
94
"Notification": Notification,
95
"unread": UserNotification.query.filter_by(user_username=username).filter(
96
UserNotification.attention_level > 0).count(),
97
"config": config,
98
"Markup": Markup,
99
"locale_names": locale_names,
100
}
101
102
103
@app.route("/")
104
def main():
105
if flask.session.get("username"):
106
return flask.render_template("home.html")
107
else:
108
return flask.render_template("no-home.html")
109
110
111
@app.route("/userstyle")
112
def userstyle():
113
if flask.session.get("username"):
114
return flask.send_from_directory(os.path.join(config.REPOS_PATH, flask.session.get("username"), ".config"), "theme.css")
115
else:
116
return flask.Response("", mimetype="text/css")
117
118
119
@app.route("/about/")
120
def about():
121
return flask.render_template("about.html", platform=platform, version=__version__)
122
123
124
@app.route("/language", methods=["POST"])
125
def set_locale():
126
response = flask.redirect(flask.request.referrer if flask.request.referrer else "/",
127
code=303)
128
if not flask.request.form.get("language"):
129
response.delete_cookie("language")
130
else:
131
response.set_cookie("language", flask.request.form.get("language"))
132
133
return response
134
135
136
@app.route("/cookie-dismiss")
137
def dismiss_banner():
138
response = flask.redirect(flask.request.referrer if flask.request.referrer else "/",
139
code=303)
140
response.set_cookie("cookie-banner", "1")
141
return response
142
143
144
@app.route("/help/")
145
def help_index():
146
return flask.render_template("help.html", faqs=config.faqs)
147
148
149
@app.route("/settings/", methods=["GET", "POST"])
150
def settings():
151
if not flask.session.get("username"):
152
flask.abort(401)
153
if flask.request.method == "GET":
154
user = User.query.filter_by(username=flask.session.get("username")).first()
155
156
return flask.render_template("user-settings.html", user=user)
157
else:
158
user = User.query.filter_by(username=flask.session.get("username")).first()
159
160
user.display_name = flask.request.form["displayname"]
161
user.URL = flask.request.form["url"]
162
user.company = flask.request.form["company"]
163
user.company_URL = flask.request.form["companyurl"]
164
user.email = flask.request.form.get("email") if flask.request.form.get(
165
"email") else None
166
user.location = flask.request.form["location"]
167
user.show_mail = True if flask.request.form.get("showmail") else False
168
user.bio = flask.request.form.get("bio")
169
170
db.session.commit()
171
172
flask.flash(
173
Markup("<iconify-icon icon='mdi:check'></iconify-icon>" + _("Settings saved")),
174
category="success")
175
return flask.redirect(f"/{flask.session.get('username')}", code=303)
176
177
178
@app.route("/favourites/", methods=["GET", "POST"])
179
def favourites():
180
if not flask.session.get("username"):
181
flask.abort(401)
182
if flask.request.method == "GET":
183
relationships = RepoFavourite.query.filter_by(
184
user_username=flask.session.get("username"))
185
186
return flask.render_template("favourites.html", favourites=relationships)
187
188
189
@app.route("/notifications/", methods=["GET", "POST"])
190
def notifications():
191
if not flask.session.get("username"):
192
flask.abort(401)
193
if flask.request.method == "GET":
194
return flask.render_template("notifications.html",
195
notifications=UserNotification.query.filter_by(
196
user_username=flask.session.get("username")))
197
198
199
@app.route("/accounts/", methods=["GET", "POST"])
200
def login():
201
if flask.request.method == "GET":
202
return flask.render_template("login.html")
203
else:
204
if "login" in flask.request.form:
205
username = flask.request.form["username"]
206
password = flask.request.form["password"]
207
208
user = User.query.filter_by(username=username).first()
209
210
if user and bcrypt.check_password_hash(user.password_hashed, password):
211
flask.session["username"] = user.username
212
flask.flash(
213
Markup("<iconify-icon icon='mdi:account'></iconify-icon>" + _(
214
"Successfully logged in as {username}").format(username=username)),
215
category="success")
216
return flask.redirect("/", code=303)
217
elif not user:
218
flask.flash(Markup(
219
"<iconify-icon icon='mdi:account-question'></iconify-icon>" + _(
220
"User not found")),
221
category="alert")
222
return flask.render_template("login.html")
223
else:
224
flask.flash(Markup(
225
"<iconify-icon icon='mdi:account-question'></iconify-icon>" + _(
226
"Invalid password")),
227
category="error")
228
return flask.render_template("login.html")
229
if "signup" in flask.request.form:
230
username = flask.request.form["username"]
231
password = flask.request.form["password"]
232
password2 = flask.request.form["password2"]
233
email = flask.request.form.get("email")
234
email2 = flask.request.form.get("email2") # repeat email is a honeypot
235
name = flask.request.form.get("name")
236
237
if not only_chars(username,
238
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"):
239
flask.flash(Markup(
240
_("Usernames may only contain Latin alphabet, numbers, '-' and '_'")),
241
category="error")
242
return flask.render_template("login.html")
243
244
if username in config.RESERVED_NAMES:
245
flask.flash(
246
Markup(
247
"<iconify-icon icon='mdi:account-error'></iconify-icon>" + _(
248
"Sorry, {username} is a system path").format(
249
username=username)),
250
category="error")
251
return flask.render_template("login.html")
252
253
user_check = User.query.filter_by(username=username).first()
254
if user_check:
255
flask.flash(
256
Markup(
257
"<iconify-icon icon='mdi:account-error'></iconify-icon>" + _(
258
"The username {username} is taken").format(
259
username=username)),
260
category="error")
261
return flask.render_template("login.html")
262
263
if password2 != password:
264
flask.flash(Markup("<iconify-icon icon='mdi:key-alert'></iconify-icon>" + _(
265
"Make sure the passwords match")),
266
category="error")
267
return flask.render_template("login.html")
268
269
user = User(username, password, email, name)
270
db.session.add(user)
271
db.session.commit()
272
flask.session["username"] = user.username
273
flask.flash(Markup(
274
"<iconify-icon icon='mdi:account'></iconify-icon>" + _(
275
"Successfully created and logged in as {username}").format(
276
username=username)),
277
category="success")
278
279
notification = Notification({"type": "welcome"})
280
db.session.add(notification)
281
db.session.commit()
282
283
result = celery_tasks.send_notification.delay(notification.id, [username], 1)
284
285
return flask.redirect("/", code=303)
286
287
288
@app.route("/newrepo/", methods=["GET", "POST"])
289
def new_repo():
290
if not flask.session.get("username"):
291
flask.abort(401)
292
if flask.request.method == "GET":
293
return flask.render_template("new-repo.html")
294
else:
295
name = flask.request.form["name"]
296
visibility = int(flask.request.form["visibility"])
297
298
if not only_chars(name,
299
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"):
300
flask.flash(Markup(
301
"<iconify-icon icon='mdi:error'></iconify-icon>" + _(
302
"Repository names may only contain Latin alphabet, numbers, '-' and '_'")),
303
category="error")
304
return flask.render_template("new-repo.html")
305
306
user = User.query.filter_by(username=flask.session.get("username")).first()
307
308
repo = Repo(user, name, visibility)
309
db.session.add(repo)
310
db.session.commit()
311
312
flask.flash(Markup(_("Successfully created repository {name}").format(name=name)),
313
category="success")
314
return flask.redirect(repo.route, code=303)
315
316
317
@app.route("/logout")
318
def logout():
319
flask.session.clear()
320
flask.flash(Markup(
321
"<iconify-icon icon='mdi:account'></iconify-icon>" + _("Successfully logged out")),
322
category="info")
323
return flask.redirect("/", code=303)
324
325
326
@app.route("/<username>/", methods=["GET", "POST"])
327
def user_profile(username):
328
old_relationship = UserFollow.query.filter_by(
329
follower_username=flask.session.get("username"),
330
followed_username=username).first()
331
if flask.request.method == "GET":
332
user = User.query.filter_by(username=username).first()
333
match flask.request.args.get("action"):
334
case "repositories":
335
repos = Repo.query.filter_by(owner_name=username, visibility=2)
336
return flask.render_template("user-profile-repositories.html", user=user,
337
repos=repos,
338
relationship=old_relationship)
339
case "followers":
340
return flask.render_template("user-profile-followers.html", user=user,
341
relationship=old_relationship)
342
case "follows":
343
return flask.render_template("user-profile-follows.html", user=user,
344
relationship=old_relationship)
345
case _:
346
return flask.render_template("user-profile-overview.html", user=user,
347
relationship=old_relationship)
348
349
elif flask.request.method == "POST":
350
match flask.request.args.get("action"):
351
case "follow":
352
if username == flask.session.get("username"):
353
flask.abort(403)
354
if old_relationship:
355
db.session.delete(old_relationship)
356
else:
357
relationship = UserFollow(
358
flask.session.get("username"),
359
username
360
)
361
db.session.add(relationship)
362
db.session.commit()
363
364
user = db.session.get(User, username)
365
author = db.session.get(User, flask.session.get("username"))
366
notification = Notification({"type": "update", "version": "0.0.0"})
367
db.session.add(notification)
368
db.session.commit()
369
370
result = celery_tasks.send_notification.delay(notification.id, [username],
371
1)
372
373
db.session.commit()
374
return flask.redirect("?", code=303)
375
376
377
@app.route("/<username>/<repository>/")
378
def repository_index(username, repository):
379
return flask.redirect("./tree", code=302)
380
381
382
@app.route("/info/<username>/avatar")
383
def user_avatar(username):
384
serverUserdataLocation = os.path.join(config.USERDATA_PATH, username)
385
386
if not os.path.exists(serverUserdataLocation):
387
return flask.render_template("not-found.html"), 404
388
389
return flask.send_from_directory(serverUserdataLocation, "avatar.png")
390
391
392
@app.route("/<username>/<repository>/raw/<branch>/<path:subpath>")
393
def repository_raw(username, repository, branch, subpath):
394
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
395
if not os.path.exists(server_repo_location):
396
app.logger.error(f"Cannot load {server_repo_location}")
397
flask.abort(404)
398
if not (get_visibility(username, repository) or get_permission_level(
399
flask.session.get("username"), username,
400
repository) is not None):
401
flask.abort(403)
402
403
app.logger.info(f"Loading {server_repo_location}")
404
405
if not os.path.exists(server_repo_location):
406
app.logger.error(f"Cannot load {server_repo_location}")
407
return flask.render_template("not-found.html"), 404
408
409
repo = git.Repo(server_repo_location)
410
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
411
if not repo_data.default_branch:
412
if repo.heads:
413
repo_data.default_branch = repo.heads[0].name
414
else:
415
return flask.render_template("empty.html",
416
remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
417
if not branch:
418
branch = repo_data.default_branch
419
return flask.redirect(f"./{branch}", code=302)
420
421
if branch.startswith("tag:"):
422
ref = f"tags/{branch[4:]}"
423
elif branch.startswith("~"):
424
ref = branch[1:]
425
else:
426
ref = f"heads/{branch}"
427
428
ref = ref.replace("~", "/") # encode slashes for URL support
429
430
try:
431
repo.git.checkout("-f", ref)
432
except git.exc.GitCommandError:
433
return flask.render_template("not-found.html"), 404
434
435
return flask.send_from_directory(config.REPOS_PATH,
436
os.path.join(username, repository, subpath))
437
438
439
@repositories.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""})
440
@repositories.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""})
441
@repositories.route("/<username>/<repository>/tree/<branch>/<path:subpath>")
442
def repository_tree(username, repository, branch, subpath):
443
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
444
if not os.path.exists(server_repo_location):
445
app.logger.error(f"Cannot load {server_repo_location}")
446
flask.abort(404)
447
if not (get_visibility(username, repository) or get_permission_level(
448
flask.session.get("username"), username,
449
repository) is not None):
450
flask.abort(403)
451
452
app.logger.info(f"Loading {server_repo_location}")
453
454
repo = git.Repo(server_repo_location)
455
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
456
if not repo_data.default_branch:
457
if repo.heads:
458
repo_data.default_branch = repo.heads[0].name
459
else:
460
return flask.render_template("empty.html",
461
remote=f"{config.www_protocol}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
462
if not branch:
463
branch = repo_data.default_branch
464
return flask.redirect(f"./{branch}", code=302)
465
466
if branch.startswith("tag:"):
467
ref = f"tags/{branch[4:]}"
468
elif branch.startswith("~"):
469
ref = branch[1:]
470
else:
471
ref = f"heads/{branch}"
472
473
ref = ref.replace("~", "/") # encode slashes for URL support
474
475
try:
476
repo.git.checkout("-f", ref)
477
except git.exc.GitCommandError:
478
return flask.render_template("not-found.html"), 404
479
480
branches = repo.heads
481
482
all_refs = []
483
for ref in repo.heads:
484
all_refs.append((ref, "head"))
485
for ref in repo.tags:
486
all_refs.append((ref, "tag"))
487
488
if os.path.isdir(os.path.join(server_repo_location, subpath)):
489
files = []
490
blobs = []
491
492
for entry in os.listdir(os.path.join(server_repo_location, subpath)):
493
if not os.path.basename(entry) == ".git":
494
files.append(os.path.join(subpath, entry))
495
496
infos = []
497
498
for file in files:
499
path = os.path.join(server_repo_location, file)
500
mimetype = guess_mime(path)
501
502
text = git_command(server_repo_location, None, "log", "--format='%H\n'",
503
shlex.quote(file)).decode()
504
505
sha = text.split("\n")[0]
506
identifier = f"/{username}/{repository}/{sha}"
507
508
last_commit = db.session.get(Commit, identifier)
509
510
info = {
511
"name": os.path.basename(file),
512
"serverPath": path,
513
"relativePath": file,
514
"link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file),
515
"size": human_size(os.path.getsize(path)),
516
"mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}",
517
"commit": last_commit,
518
"shaSize": 7,
519
}
520
521
special_icon = config.match_icon(os.path.basename(file))
522
if special_icon:
523
info["icon"] = special_icon
524
elif os.path.isdir(path):
525
info["icon"] = config.folder_icon
526
elif mimetypes.guess_type(path)[0] in config.file_icons:
527
info["icon"] = config.file_icons[mimetypes.guess_type(path)[0]]
528
else:
529
info["icon"] = config.unknown_icon
530
531
if os.path.isdir(path):
532
infos.insert(0, info)
533
else:
534
infos.append(info)
535
536
return flask.render_template(
537
"repo-tree.html",
538
username=username,
539
repository=repository,
540
files=infos,
541
subpath=os.path.join("/", subpath),
542
branches=all_refs,
543
current=branch,
544
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
545
is_favourite=get_favourite(flask.session.get("username"), username, repository)
546
)
547
else:
548
path = os.path.join(server_repo_location, subpath)
549
550
if not os.path.exists(path):
551
return flask.render_template("not-found.html"), 404
552
553
mimetype = guess_mime(path)
554
mode = mimetype.split("/", 1)[0]
555
size = human_size(os.path.getsize(path))
556
557
special_icon = config.match_icon(os.path.basename(path))
558
if special_icon:
559
icon = special_icon
560
elif os.path.isdir(path):
561
icon = config.folder_icon
562
elif mimetypes.guess_type(path)[0] in config.file_icons:
563
icon = config.file_icons[mimetypes.guess_type(path)[0]]
564
else:
565
icon = config.unknown_icon
566
567
contents = None
568
if mode == "text":
569
contents = convert_to_html(path)
570
571
return flask.render_template(
572
"repo-file.html",
573
username=username,
574
repository=repository,
575
file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath),
576
branches=all_refs,
577
current=branch,
578
mode=mode,
579
mimetype=mimetype,
580
detailedtype=magic.from_file(path),
581
size=size,
582
icon=icon,
583
subpath=os.path.join("/", subpath),
584
extension=pathlib.Path(path).suffix,
585
basename=os.path.basename(path),
586
contents=contents,
587
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
588
is_favourite=get_favourite(flask.session.get("username"), username, repository)
589
)
590
591
592
@repositories.route("/<username>/<repository>/commit/<sha>")
593
def repository_commit(username, repository, sha):
594
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
595
if not os.path.exists(server_repo_location):
596
app.logger.error(f"Cannot load {server_repo_location}")
597
flask.abort(404)
598
if not (get_visibility(username, repository) or get_permission_level(
599
flask.session.get("username"), username,
600
repository) is not None):
601
flask.abort(403)
602
603
app.logger.info(f"Loading {server_repo_location}")
604
605
if not os.path.exists(server_repo_location):
606
app.logger.error(f"Cannot load {server_repo_location}")
607
return flask.render_template("not-found.html"), 404
608
609
repo = git.Repo(server_repo_location)
610
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
611
612
files = git_command(os.path.join(server_repo_location, ".git"), None, "diff-tree", "-r",
613
"--name-only", "--no-commit-id", sha).decode().split("\n")
614
615
return flask.render_template(
616
"repo-commit.html",
617
username=username,
618
repository=repository,
619
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
620
is_favourite=get_favourite(flask.session.get("username"), username, repository),
621
diff={file: git_command(os.path.join(server_repo_location, ".git"), None, "diff",
622
str(sha), str(sha) + "^", file).decode().split("\n") for
623
file in files}
624
)
625
626
627
@repositories.route("/<username>/<repository>/forum/")
628
def repository_forum(username, repository):
629
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
630
if not os.path.exists(server_repo_location):
631
app.logger.error(f"Cannot load {server_repo_location}")
632
flask.abort(404)
633
if not (get_visibility(username, repository) or get_permission_level(
634
flask.session.get("username"), username,
635
repository) is not None):
636
flask.abort(403)
637
638
app.logger.info(f"Loading {server_repo_location}")
639
640
if not os.path.exists(server_repo_location):
641
app.logger.error(f"Cannot load {server_repo_location}")
642
return flask.render_template("not-found.html"), 404
643
644
repo = git.Repo(server_repo_location)
645
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
646
user = User.query.filter_by(username=flask.session.get("username")).first()
647
relationships = RepoAccess.query.filter_by(repo=repo_data)
648
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
649
650
return flask.render_template(
651
"repo-forum.html",
652
username=username,
653
repository=repository,
654
repo_data=repo_data,
655
relationships=relationships,
656
repo=repo,
657
user_relationship=user_relationship,
658
Post=Post,
659
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
660
is_favourite=get_favourite(flask.session.get("username"), username, repository),
661
default_branch=repo_data.default_branch
662
)
663
664
665
@repositories.route("/<username>/<repository>/forum/topic/<int:id>")
666
def repository_forum_topic(username, repository, id):
667
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
668
if not os.path.exists(server_repo_location):
669
app.logger.error(f"Cannot load {server_repo_location}")
670
flask.abort(404)
671
if not (get_visibility(username, repository) or get_permission_level(
672
flask.session.get("username"), username,
673
repository) is not None):
674
flask.abort(403)
675
676
app.logger.info(f"Loading {server_repo_location}")
677
678
if not os.path.exists(server_repo_location):
679
app.logger.error(f"Cannot load {server_repo_location}")
680
return flask.render_template("not-found.html"), 404
681
682
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
683
user = User.query.filter_by(username=flask.session.get("username")).first()
684
relationships = RepoAccess.query.filter_by(repo=repo_data)
685
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
686
687
post = Post.query.filter_by(id=id).first()
688
689
return flask.render_template(
690
"repo-topic.html",
691
username=username,
692
repository=repository,
693
repo_data=repo_data,
694
relationships=relationships,
695
user_relationship=user_relationship,
696
post=post,
697
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
698
is_favourite=get_favourite(flask.session.get("username"), username, repository),
699
default_branch=repo_data.default_branch
700
)
701
702
703
@repositories.route("/<username>/<repository>/forum/new", methods=["POST", "GET"])
704
def repository_forum_new(username, repository):
705
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
706
if not os.path.exists(server_repo_location):
707
app.logger.error(f"Cannot load {server_repo_location}")
708
flask.abort(404)
709
if not (get_visibility(username, repository) or get_permission_level(
710
flask.session.get("username"), username,
711
repository) is not None):
712
flask.abort(403)
713
714
app.logger.info(f"Loading {server_repo_location}")
715
716
if not os.path.exists(server_repo_location):
717
app.logger.error(f"Cannot load {server_repo_location}")
718
return flask.render_template("not-found.html"), 404
719
720
repo = git.Repo(server_repo_location)
721
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
722
user = User.query.filter_by(username=flask.session.get("username")).first()
723
relationships = RepoAccess.query.filter_by(repo=repo_data)
724
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
725
726
post = Post(user, repo_data, None, flask.request.form["subject"],
727
flask.request.form["message"])
728
729
db.session.add(post)
730
db.session.commit()
731
732
return flask.redirect(
733
flask.url_for(".repository_forum_thread", username=username, repository=repository,
734
post_id=post.number),
735
code=303)
736
737
738
@repositories.route("/<username>/<repository>/forum/<int:post_id>")
739
def repository_forum_thread(username, repository, post_id):
740
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
741
if not os.path.exists(server_repo_location):
742
app.logger.error(f"Cannot load {server_repo_location}")
743
flask.abort(404)
744
if not (get_visibility(username, repository) or get_permission_level(
745
flask.session.get("username"), username,
746
repository) is not None):
747
flask.abort(403)
748
749
app.logger.info(f"Loading {server_repo_location}")
750
751
if not os.path.exists(server_repo_location):
752
app.logger.error(f"Cannot load {server_repo_location}")
753
return flask.render_template("not-found.html"), 404
754
755
repo = git.Repo(server_repo_location)
756
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
757
user = User.query.filter_by(username=flask.session.get("username")).first()
758
relationships = RepoAccess.query.filter_by(repo=repo_data)
759
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
760
761
return flask.render_template(
762
"repo-forum-thread.html",
763
username=username,
764
repository=repository,
765
repo_data=repo_data,
766
relationships=relationships,
767
repo=repo,
768
Post=Post,
769
user_relationship=user_relationship,
770
post_id=post_id,
771
max_post_nesting=4,
772
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
773
is_favourite=get_favourite(flask.session.get("username"), username, repository),
774
parent=Post.query.filter_by(repo=repo_data, number=post_id).first(),
775
)
776
777
778
@repositories.route("/<username>/<repository>/forum/<int:post_id>/reply", methods=["POST"])
779
def repository_forum_reply(username, repository, post_id):
780
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
781
if not os.path.exists(server_repo_location):
782
app.logger.error(f"Cannot load {server_repo_location}")
783
flask.abort(404)
784
if not (get_visibility(username, repository) or get_permission_level(
785
flask.session.get("username"), username,
786
repository) is not None):
787
flask.abort(403)
788
789
app.logger.info(f"Loading {server_repo_location}")
790
791
if not os.path.exists(server_repo_location):
792
app.logger.error(f"Cannot load {server_repo_location}")
793
return flask.render_template("not-found.html"), 404
794
795
repo = git.Repo(server_repo_location)
796
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
797
user = User.query.filter_by(username=flask.session.get("username")).first()
798
relationships = RepoAccess.query.filter_by(repo=repo_data)
799
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
800
if not user:
801
flask.abort(401)
802
803
parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
804
post = Post(user, repo_data, parent, flask.request.form["subject"],
805
flask.request.form["message"])
806
807
db.session.add(post)
808
post.update_date()
809
db.session.commit()
810
811
return flask.redirect(
812
flask.url_for(".repository_forum_thread", username=username, repository=repository,
813
post_id=post_id),
814
code=303)
815
816
817
@repositories.route("/<username>/<repository>/forum/<int:post_id>/voteup",
818
defaults={"score": 1})
819
@repositories.route("/<username>/<repository>/forum/<int:post_id>/votedown",
820
defaults={"score": -1})
821
@repositories.route("/<username>/<repository>/forum/<int:post_id>/votes", defaults={"score": 0})
822
def repository_forum_vote(username, repository, post_id, score):
823
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
824
if not os.path.exists(server_repo_location):
825
app.logger.error(f"Cannot load {server_repo_location}")
826
flask.abort(404)
827
if not (get_visibility(username, repository) or get_permission_level(
828
flask.session.get("username"), username,
829
repository) is not None):
830
flask.abort(403)
831
832
app.logger.info(f"Loading {server_repo_location}")
833
834
if not os.path.exists(server_repo_location):
835
app.logger.error(f"Cannot load {server_repo_location}")
836
return flask.render_template("not-found.html"), 404
837
838
repo = git.Repo(server_repo_location)
839
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
840
user = User.query.filter_by(username=flask.session.get("username")).first()
841
relationships = RepoAccess.query.filter_by(repo=repo_data)
842
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
843
if not user:
844
flask.abort(401)
845
846
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
847
848
if score:
849
old_relationship = PostVote.query.filter_by(user_username=user.username,
850
post_identifier=post.identifier).first()
851
if old_relationship:
852
if score == old_relationship.vote_score:
853
db.session.delete(old_relationship)
854
post.vote_sum -= old_relationship.vote_score
855
else:
856
post.vote_sum -= old_relationship.vote_score
857
post.vote_sum += score
858
old_relationship.vote_score = score
859
else:
860
relationship = PostVote(user, post, score)
861
post.vote_sum += score
862
db.session.add(relationship)
863
864
db.session.commit()
865
866
user_vote = PostVote.query.filter_by(user_username=user.username,
867
post_identifier=post.identifier).first()
868
response = flask.make_response(
869
str(post.vote_sum) + " " + str(user_vote.vote_score if user_vote else 0))
870
response.content_type = "text/plain"
871
872
return response
873
874
875
@repositories.route("/<username>/<repository>/favourite")
876
def repository_favourite(username, repository):
877
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
878
if not os.path.exists(server_repo_location):
879
app.logger.error(f"Cannot load {server_repo_location}")
880
flask.abort(404)
881
if not (get_visibility(username, repository) or get_permission_level(
882
flask.session.get("username"), username,
883
repository) is not None):
884
flask.abort(403)
885
886
app.logger.info(f"Loading {server_repo_location}")
887
888
if not os.path.exists(server_repo_location):
889
app.logger.error(f"Cannot load {server_repo_location}")
890
return flask.render_template("not-found.html"), 404
891
892
repo = git.Repo(server_repo_location)
893
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
894
user = User.query.filter_by(username=flask.session.get("username")).first()
895
relationships = RepoAccess.query.filter_by(repo=repo_data)
896
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
897
if not user:
898
flask.abort(401)
899
900
old_relationship = RepoFavourite.query.filter_by(user_username=user.username,
901
repo_route=repo_data.route).first()
902
if old_relationship:
903
db.session.delete(old_relationship)
904
else:
905
relationship = RepoFavourite(user, repo_data)
906
db.session.add(relationship)
907
908
db.session.commit()
909
910
return flask.redirect(flask.url_for("favourites"), code=303)
911
912
913
@repositories.route("/<username>/<repository>/users/", methods=["GET", "POST"])
914
def repository_users(username, repository):
915
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
916
if not os.path.exists(server_repo_location):
917
app.logger.error(f"Cannot load {server_repo_location}")
918
flask.abort(404)
919
if not (get_visibility(username, repository) or get_permission_level(
920
flask.session.get("username"), username,
921
repository) is not None):
922
flask.abort(403)
923
924
app.logger.info(f"Loading {server_repo_location}")
925
926
if not os.path.exists(server_repo_location):
927
app.logger.error(f"Cannot load {server_repo_location}")
928
return flask.render_template("not-found.html"), 404
929
930
repo = git.Repo(server_repo_location)
931
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
932
user = User.query.filter_by(username=flask.session.get("username")).first()
933
relationships = RepoAccess.query.filter_by(repo=repo_data)
934
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
935
936
if flask.request.method == "GET":
937
return flask.render_template(
938
"repo-users.html",
939
username=username,
940
repository=repository,
941
repo_data=repo_data,
942
relationships=relationships,
943
repo=repo,
944
user_relationship=user_relationship,
945
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
946
is_favourite=get_favourite(flask.session.get("username"), username, repository)
947
)
948
else:
949
if get_permission_level(flask.session.get("username"), username, repository) != 2:
950
flask.abort(401)
951
952
if flask.request.form.get("new-username"):
953
# Create new relationship
954
new_user = User.query.filter_by(
955
username=flask.request.form.get("new-username")).first()
956
relationship = RepoAccess(new_user, repo_data, flask.request.form.get("new-level"))
957
db.session.add(relationship)
958
db.session.commit()
959
if flask.request.form.get("update-username"):
960
# Create new relationship
961
updated_user = User.query.filter_by(
962
username=flask.request.form.get("update-username")).first()
963
relationship = RepoAccess.query.filter_by(repo=repo_data, user=updated_user).first()
964
if flask.request.form.get("update-level") == -1:
965
relationship.delete()
966
else:
967
relationship.access_level = flask.request.form.get("update-level")
968
db.session.commit()
969
970
return flask.redirect(
971
app.url_for(".repository_users", username=username, repository=repository))
972
973
974
@repositories.route("/<username>/<repository>/branches/")
975
def repository_branches(username, repository):
976
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
977
if not os.path.exists(server_repo_location):
978
app.logger.error(f"Cannot load {server_repo_location}")
979
flask.abort(404)
980
if not (get_visibility(username, repository) or get_permission_level(
981
flask.session.get("username"), username,
982
repository) is not None):
983
flask.abort(403)
984
985
app.logger.info(f"Loading {server_repo_location}")
986
987
if not os.path.exists(server_repo_location):
988
app.logger.error(f"Cannot load {server_repo_location}")
989
return flask.render_template("not-found.html"), 404
990
991
repo = git.Repo(server_repo_location)
992
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
993
994
return flask.render_template(
995
"repo-branches.html",
996
username=username,
997
repository=repository,
998
repo_data=repo_data,
999
repo=repo,
1000
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1001
is_favourite=get_favourite(flask.session.get("username"), username, repository)
1002
)
1003
1004
1005
@repositories.route("/<username>/<repository>/log/", defaults={"branch": None})
1006
@repositories.route("/<username>/<repository>/log/<branch>/")
1007
def repository_log(username, repository, branch):
1008
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1009
if not os.path.exists(server_repo_location):
1010
app.logger.error(f"Cannot load {server_repo_location}")
1011
flask.abort(404)
1012
if not (get_visibility(username, repository) or get_permission_level(
1013
flask.session.get("username"), username,
1014
repository) is not None):
1015
flask.abort(403)
1016
1017
app.logger.info(f"Loading {server_repo_location}")
1018
1019
if not os.path.exists(server_repo_location):
1020
app.logger.error(f"Cannot load {server_repo_location}")
1021
return flask.render_template("not-found.html"), 404
1022
1023
repo = git.Repo(server_repo_location)
1024
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1025
if not repo_data.default_branch:
1026
if repo.heads:
1027
repo_data.default_branch = repo.heads[0].name
1028
else:
1029
return flask.render_template("empty.html",
1030
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
1031
if not branch:
1032
branch = repo_data.default_branch
1033
return flask.redirect(f"./{branch}", code=302)
1034
1035
if branch.startswith("tag:"):
1036
ref = f"tags/{branch[4:]}"
1037
elif branch.startswith("~"):
1038
ref = branch[1:]
1039
else:
1040
ref = f"heads/{branch}"
1041
1042
ref = ref.replace("~", "/") # encode slashes for URL support
1043
1044
try:
1045
repo.git.checkout("-f", ref)
1046
except git.exc.GitCommandError:
1047
return flask.render_template("not-found.html"), 404
1048
1049
branches = repo.heads
1050
1051
all_refs = []
1052
for ref in repo.heads:
1053
all_refs.append((ref, "head"))
1054
for ref in repo.tags:
1055
all_refs.append((ref, "tag"))
1056
1057
commit_list = [f"/{username}/{repository}/{sha}" for sha in
1058
git_command(server_repo_location, None, "log",
1059
"--format='%H'").decode().split("\n")]
1060
1061
commits = Commit.query.filter(Commit.identifier.in_(commit_list))
1062
1063
return flask.render_template(
1064
"repo-log.html",
1065
username=username,
1066
repository=repository,
1067
branches=all_refs,
1068
current=branch,
1069
repo_data=repo_data,
1070
repo=repo,
1071
commits=commits,
1072
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1073
is_favourite=get_favourite(flask.session.get("username"), username, repository)
1074
)
1075
1076
1077
@repositories.route("/<username>/<repository>/prs/", methods=["GET", "POST"])
1078
def repository_prs(username, repository):
1079
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1080
if not os.path.exists(server_repo_location):
1081
app.logger.error(f"Cannot load {server_repo_location}")
1082
flask.abort(404)
1083
if not (get_visibility(username, repository) or get_permission_level(
1084
flask.session.get("username"), username,
1085
repository) is not None):
1086
flask.abort(403)
1087
1088
app.logger.info(f"Loading {server_repo_location}")
1089
1090
if not os.path.exists(server_repo_location):
1091
app.logger.error(f"Cannot load {server_repo_location}")
1092
return flask.render_template("not-found.html"), 404
1093
1094
if flask.request.method == "GET":
1095
repo = git.Repo(server_repo_location)
1096
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1097
user = User.query.filter_by(username=flask.session.get("username")).first()
1098
1099
return flask.render_template(
1100
"repo-prs.html",
1101
username=username,
1102
repository=repository,
1103
repo_data=repo_data,
1104
repo=repo,
1105
PullRequest=PullRequest,
1106
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
1107
is_favourite=get_favourite(flask.session.get("username"), username, repository),
1108
default_branch=repo_data.default_branch,
1109
branches=repo.branches
1110
)
1111
1112
else:
1113
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1114
head = flask.request.form.get("head")
1115
head_route = flask.request.form.get("headroute")
1116
base = flask.request.form.get("base")
1117
1118
if not head and base and head_route:
1119
return flask.redirect(".", 400)
1120
1121
head_repo = git.Repo(os.path.join(config.REPOS_PATH, head_route.lstrip("/")))
1122
base_repo = git.Repo(server_repo_location)
1123
print(head_repo)
1124
1125
if head not in head_repo.branches or base not in base_repo.branches:
1126
flask.flash(Markup(
1127
"<iconify-icon icon='mdi:error'></iconify-icon>" + _("Bad branch name")),
1128
category="error")
1129
return flask.redirect(".", 303)
1130
1131
head_data = db.session.get(Repo, head_route)
1132
if not head_data.visibility:
1133
flask.flash(Markup(
1134
"<iconify-icon icon='mdi:error'></iconify-icon>" + _(
1135
"Head can't be restricted")),
1136
category="error")
1137
return flask.redirect(".", 303)
1138
1139
pull_request = PullRequest(repo_data, head, head_data, base,
1140
db.session.get(User, flask.session["username"]))
1141
1142
db.session.add(pull_request)
1143
db.session.commit()
1144
1145
return flask.redirect(".", 303)
1146
1147
1148
@repositories.route("/<username>/<repository>/prs/merge", methods=["POST"])
1149
def repository_prs_merge(username, repository):
1150
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1151
if not os.path.exists(server_repo_location):
1152
app.logger.error(f"Cannot load {server_repo_location}")
1153
flask.abort(404)
1154
if not (get_visibility(username, repository) or get_permission_level(
1155
flask.session.get("username"), username,
1156
repository) is not None):
1157
flask.abort(403)
1158
1159
if not get_permission_level(flask.session.get("username"), username, repository):
1160
flask.abort(401)
1161
1162
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1163
repo = git.Repo(server_repo_location)
1164
id = flask.request.form.get("id")
1165
1166
pull_request = db.session.get(PullRequest, id)
1167
1168
if pull_request:
1169
result = celery_tasks.merge_heads.delay(
1170
pull_request.head_route,
1171
pull_request.head_branch,
1172
pull_request.base_route,
1173
pull_request.base_branch,
1174
simulate=True
1175
)
1176
task_result = worker.AsyncResult(result.id)
1177
1178
return flask.redirect(f"/task/{result.id}?pr-id={id}", 303)
1179
# db.session.delete(pull_request)
1180
# db.session.commit()
1181
else:
1182
flask.abort(400)
1183
1184
1185
@repositories.route("/<username>/<repository>/prs/<int:id>/merge")
1186
def repository_prs_merge_stage_two(username, repository, id):
1187
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1188
if not os.path.exists(server_repo_location):
1189
app.logger.error(f"Cannot load {server_repo_location}")
1190
flask.abort(404)
1191
if not (get_visibility(username, repository) or get_permission_level(
1192
flask.session.get("username"), username,
1193
repository) is not None):
1194
flask.abort(403)
1195
1196
if not get_permission_level(flask.session.get("username"), username, repository):
1197
flask.abort(401)
1198
1199
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1200
repo = git.Repo(server_repo_location)
1201
1202
pull_request = db.session.get(PullRequest, id)
1203
1204
if pull_request:
1205
result = celery_tasks.merge_heads.delay(
1206
pull_request.head_route,
1207
pull_request.head_branch,
1208
pull_request.base_route,
1209
pull_request.base_branch,
1210
simulate=False
1211
)
1212
task_result = worker.AsyncResult(result.id)
1213
1214
return flask.redirect(f"/task/{result.id}?pr-id={id}", 303)
1215
# db.session.delete(pull_request)
1216
# db.session.commit()
1217
else:
1218
flask.abort(400)
1219
1220
1221
@app.route("/task/<task_id>")
1222
def task_monitor(task_id):
1223
task_result = worker.AsyncResult(task_id)
1224
print(task_result.status)
1225
1226
return flask.render_template("task-monitor.html", result=task_result)
1227
1228
1229
@repositories.route("/<username>/<repository>/prs/delete", methods=["POST"])
1230
def repository_prs_delete(username, repository):
1231
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1232
if not os.path.exists(server_repo_location):
1233
app.logger.error(f"Cannot load {server_repo_location}")
1234
flask.abort(404)
1235
if not (get_visibility(username, repository) or get_permission_level(
1236
flask.session.get("username"), username,
1237
repository) is not None):
1238
flask.abort(403)
1239
1240
if not get_permission_level(flask.session.get("username"), username, repository):
1241
flask.abort(401)
1242
1243
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1244
repo = git.Repo(server_repo_location)
1245
id = flask.request.form.get("id")
1246
1247
pull_request = db.session.get(PullRequest, id)
1248
1249
if pull_request:
1250
db.session.delete(pull_request)
1251
db.session.commit()
1252
1253
return flask.redirect(".", 303)
1254
1255
1256
@repositories.route("/<username>/<repository>/settings/")
1257
def repository_settings(username, repository):
1258
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1259
flask.abort(401)
1260
1261
return flask.render_template("repo-settings.html", username=username, repository=repository)
1262
1263
1264
@app.errorhandler(404)
1265
def e404(error):
1266
return flask.render_template("not-found.html"), 404
1267
1268
1269
@app.errorhandler(401)
1270
def e401(error):
1271
return flask.render_template("unauthorised.html"), 401
1272
1273
1274
@app.errorhandler(403)
1275
def e403(error):
1276
return flask.render_template("forbidden.html"), 403
1277
1278
1279
@app.errorhandler(418)
1280
def e418(error):
1281
return flask.render_template("teapot.html"), 418
1282
1283
1284
@app.errorhandler(405)
1285
def e405(error):
1286
return flask.render_template("method-not-allowed.html"), 405
1287
1288
1289
if __name__ == "__main__":
1290
app.run(debug=True, port=8080, host="0.0.0.0")
1291
1292
app.register_blueprint(repositories)