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