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