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