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