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