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