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