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