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