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