By using this site, you agree to have cookies stored on your device, strictly for functional purposes, such as storing your session and preferences.

Dismiss

 app.py

View raw Download
text/x-script.python • 43.24 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
parent=Post.query.filter_by(repo=repo_data, number=post_id).first(),
638
)
639
640
641
@repositories.route("/<username>/<repository>/forum/<int:post_id>/reply", methods=["POST"])
642
def repository_forum_reply(username, repository, post_id):
643
if not (get_visibility(username, repository) or get_permission_level(flask.session.get("username"), username,
644
repository) is not None):
645
flask.abort(403)
646
647
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
648
649
app.logger.info(f"Loading {server_repo_location}")
650
651
if not os.path.exists(server_repo_location):
652
app.logger.error(f"Cannot load {server_repo_location}")
653
return flask.render_template("not-found.html"), 404
654
655
repo = git.Repo(server_repo_location)
656
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
657
user = User.query.filter_by(username=flask.session.get("username")).first()
658
relationships = RepoAccess.query.filter_by(repo=repo_data)
659
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
660
if not user:
661
flask.abort(401)
662
663
parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
664
post = Post(user, repo_data, parent, flask.request.form["subject"], flask.request.form["message"])
665
666
db.session.add(post)
667
post.update_date()
668
db.session.commit()
669
670
return flask.redirect(
671
flask.url_for(".repository_forum_thread", username=username, repository=repository, post_id=post_id),
672
code=303)
673
674
675
@repositories.route("/<username>/<repository>/forum/<int:post_id>/voteup", defaults={"score": 1})
676
@repositories.route("/<username>/<repository>/forum/<int:post_id>/votedown", defaults={"score": -1})
677
@repositories.route("/<username>/<repository>/forum/<int:post_id>/votes", defaults={"score": 0})
678
def repository_forum_vote(username, repository, post_id, score):
679
if not (get_visibility(username, repository) or get_permission_level(flask.session.get("username"), username,
680
repository) is not None):
681
flask.abort(403)
682
683
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
684
685
app.logger.info(f"Loading {server_repo_location}")
686
687
if not os.path.exists(server_repo_location):
688
app.logger.error(f"Cannot load {server_repo_location}")
689
return flask.render_template("not-found.html"), 404
690
691
repo = git.Repo(server_repo_location)
692
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
693
user = User.query.filter_by(username=flask.session.get("username")).first()
694
relationships = RepoAccess.query.filter_by(repo=repo_data)
695
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
696
if not user:
697
flask.abort(401)
698
699
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{post_id}").first()
700
701
if score:
702
old_relationship = PostVote.query.filter_by(user_username=user.username,
703
post_identifier=post.identifier).first()
704
if old_relationship:
705
if score == old_relationship.vote_score:
706
db.session.delete(old_relationship)
707
post.vote_sum -= old_relationship.vote_score
708
else:
709
post.vote_sum -= old_relationship.vote_score
710
post.vote_sum += score
711
old_relationship.vote_score = score
712
else:
713
relationship = PostVote(user, post, score)
714
post.vote_sum += score
715
db.session.add(relationship)
716
717
db.session.commit()
718
719
user_vote = PostVote.query.filter_by(user_username=user.username, post_identifier=post.identifier).first()
720
response = flask.make_response(str(post.vote_sum) + " " + str(user_vote.vote_score if user_vote else 0))
721
response.content_type = "text/plain"
722
723
return response
724
725
726
@repositories.route("/<username>/<repository>/favourite")
727
def repository_favourite(username, repository):
728
if not (get_visibility(username, repository) or get_permission_level(flask.session.get("username"), username,
729
repository) is not None):
730
flask.abort(403)
731
732
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
733
734
app.logger.info(f"Loading {server_repo_location}")
735
736
if not os.path.exists(server_repo_location):
737
app.logger.error(f"Cannot load {server_repo_location}")
738
return flask.render_template("not-found.html"), 404
739
740
repo = git.Repo(server_repo_location)
741
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
742
user = User.query.filter_by(username=flask.session.get("username")).first()
743
relationships = RepoAccess.query.filter_by(repo=repo_data)
744
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
745
if not user:
746
flask.abort(401)
747
748
old_relationship = RepoFavourite.query.filter_by(user_username=user.username, repo_route=repo_data.route).first()
749
if old_relationship:
750
db.session.delete(old_relationship)
751
else:
752
relationship = RepoFavourite(user, repo_data)
753
db.session.add(relationship)
754
755
db.session.commit()
756
757
return flask.redirect(flask.url_for("favourites"), code=303)
758
759
760
@repositories.route("/<username>/<repository>/users/", methods=["GET", "POST"])
761
def repository_users(username, repository):
762
if not (get_visibility(username, repository) or get_permission_level(flask.session.get("username"), username,
763
repository) is not None):
764
flask.abort(403)
765
766
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
767
768
app.logger.info(f"Loading {server_repo_location}")
769
770
if not os.path.exists(server_repo_location):
771
app.logger.error(f"Cannot load {server_repo_location}")
772
return flask.render_template("not-found.html"), 404
773
774
repo = git.Repo(server_repo_location)
775
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
776
user = User.query.filter_by(username=flask.session.get("username")).first()
777
relationships = RepoAccess.query.filter_by(repo=repo_data)
778
user_relationship = RepoAccess.query.filter_by(repo=repo_data, user=user).first()
779
780
if flask.request.method == "GET":
781
return flask.render_template(
782
"repo-users.html",
783
username=username,
784
repository=repository,
785
repo_data=repo_data,
786
relationships=relationships,
787
repo=repo,
788
user_relationship=user_relationship,
789
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
790
is_favourite=get_favourite(flask.session.get("username"), username, repository)
791
)
792
else:
793
if get_permission_level(flask.session.get("username"), username, repository) != 2:
794
flask.abort(401)
795
796
if flask.request.form.get("new-username"):
797
# Create new relationship
798
new_user = User.query.filter_by(username=flask.request.form.get("new-username")).first()
799
relationship = RepoAccess(new_user, repo_data, flask.request.form.get("new-level"))
800
db.session.add(relationship)
801
db.session.commit()
802
if flask.request.form.get("update-username"):
803
# Create new relationship
804
updated_user = User.query.filter_by(username=flask.request.form.get("update-username")).first()
805
relationship = RepoAccess.query.filter_by(repo=repo_data, user=updated_user).first()
806
if flask.request.form.get("update-level") == -1:
807
relationship.delete()
808
else:
809
relationship.access_level = flask.request.form.get("update-level")
810
db.session.commit()
811
812
return flask.redirect(app.url_for(".repository_users", username=username, repository=repository))
813
814
815
@repositories.route("/<username>/<repository>/branches/")
816
def repository_branches(username, repository):
817
if not (get_visibility(username, repository) or get_permission_level(flask.session.get("username"), username,
818
repository) is not None):
819
flask.abort(403)
820
821
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
822
823
app.logger.info(f"Loading {server_repo_location}")
824
825
if not os.path.exists(server_repo_location):
826
app.logger.error(f"Cannot load {server_repo_location}")
827
return flask.render_template("not-found.html"), 404
828
829
repo = git.Repo(server_repo_location)
830
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
831
832
return flask.render_template(
833
"repo-branches.html",
834
username=username,
835
repository=repository,
836
repo_data=repo_data,
837
repo=repo,
838
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
839
is_favourite=get_favourite(flask.session.get("username"), username, repository)
840
)
841
842
843
@repositories.route("/<username>/<repository>/log/", defaults={"branch": None})
844
@repositories.route("/<username>/<repository>/log/<branch>/")
845
def repository_log(username, repository, branch):
846
if not (get_visibility(username, repository) or get_permission_level(flask.session.get("username"), username,
847
repository) is not None):
848
flask.abort(403)
849
850
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
851
852
app.logger.info(f"Loading {server_repo_location}")
853
854
if not os.path.exists(server_repo_location):
855
app.logger.error(f"Cannot load {server_repo_location}")
856
return flask.render_template("not-found.html"), 404
857
858
repo = git.Repo(server_repo_location)
859
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
860
if not repo_data.default_branch:
861
if repo.heads:
862
repo_data.default_branch = repo.heads[0].name
863
else:
864
return flask.render_template("empty.html",
865
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
866
if not branch:
867
branch = repo_data.default_branch
868
return flask.redirect(f"./{branch}", code=302)
869
870
if branch.startswith("tag:"):
871
ref = f"tags/{branch[4:]}"
872
elif branch.startswith("~"):
873
ref = branch[1:]
874
else:
875
ref = f"heads/{branch}"
876
877
ref = ref.replace("~", "/") # encode slashes for URL support
878
879
try:
880
repo.git.checkout("-f", ref)
881
except git.exc.GitCommandError:
882
return flask.render_template("not-found.html"), 404
883
884
branches = repo.heads
885
886
all_refs = []
887
for ref in repo.heads:
888
all_refs.append((ref, "head"))
889
for ref in repo.tags:
890
all_refs.append((ref, "tag"))
891
892
commit_list = [f"/{username}/{repository}/{sha}" for sha in
893
git_command(server_repo_location, None, "log", "--format='%H'").decode().split("\n")]
894
895
commits = Commit.query.filter(Commit.identifier.in_(commit_list))
896
897
return flask.render_template(
898
"repo-log.html",
899
username=username,
900
repository=repository,
901
branches=all_refs,
902
current=branch,
903
repo_data=repo_data,
904
repo=repo,
905
commits=commits,
906
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
907
is_favourite=get_favourite(flask.session.get("username"), username, repository)
908
)
909
910
911
@repositories.route("/<username>/<repository>/prs/", methods=["GET", "POST"])
912
def repository_prs(username, repository):
913
if not (get_visibility(username, repository) or get_permission_level(flask.session.get("username"), username,
914
repository) is not None):
915
flask.abort(403)
916
917
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
918
919
app.logger.info(f"Loading {server_repo_location}")
920
921
if not os.path.exists(server_repo_location):
922
app.logger.error(f"Cannot load {server_repo_location}")
923
return flask.render_template("not-found.html"), 404
924
925
if flask.request.method == "GET":
926
repo = git.Repo(server_repo_location)
927
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
928
user = User.query.filter_by(username=flask.session.get("username")).first()
929
930
return flask.render_template(
931
"repo-prs.html",
932
username=username,
933
repository=repository,
934
repo_data=repo_data,
935
repo=repo,
936
PullRequest=PullRequest,
937
remote=f"http{'s' if config.suggest_https else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
938
is_favourite=get_favourite(flask.session.get("username"), username, repository),
939
default_branch=repo_data.default_branch,
940
branches=repo.branches
941
)
942
943
else:
944
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
945
head = flask.request.form.get("head")
946
head_route = flask.request.form.get("headroute")
947
base = flask.request.form.get("base")
948
949
if not head and base and head_route:
950
return flask.redirect(".", 400)
951
952
head_repo = git.Repo(os.path.join(config.REPOS_PATH, head_route.lstrip("/")))
953
base_repo = git.Repo(server_repo_location)
954
print(head_repo)
955
956
if head not in head_repo.branches or base not in base_repo.branches:
957
flask.flash(Markup(
958
"<iconify-icon icon='mdi:error'></iconify-icon>Bad branch name"),
959
category="error")
960
return flask.redirect(".", 303)
961
962
head_data = db.session.get(Repo, head_route)
963
if not head_data.visibility:
964
flask.flash(Markup(
965
"<iconify-icon icon='mdi:error'></iconify-icon>Head can't be restricted"),
966
category="error")
967
return flask.redirect(".", 303)
968
969
pull_request = PullRequest(repo_data, head, head_data, base, db.session.get(User, flask.session["username"]))
970
971
db.session.add(pull_request)
972
db.session.commit()
973
974
return flask.redirect(".", 303)
975
976
977
@repositories.route("/<username>/<repository>/prs/merge", methods=["POST"])
978
def repository_prs_merge(username, repository):
979
if not get_permission_level(flask.session.get("username"), username, repository):
980
flask.abort(401)
981
982
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
983
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
984
repo = git.Repo(server_repo_location)
985
id = flask.request.form.get("id")
986
987
pull_request = db.session.get(PullRequest, id)
988
989
if pull_request:
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
simulate=True
996
)
997
task_result = worker.AsyncResult(result.id)
998
999
return flask.redirect(f"/task/{result.id}?pr-id={id}", 303)
1000
# db.session.delete(pull_request)
1001
# db.session.commit()
1002
else:
1003
flask.abort(400)
1004
1005
1006
@repositories.route("/<username>/<repository>/prs/<int:id>/merge")
1007
def repository_prs_merge_stage_two(username, repository, id):
1008
if not get_permission_level(flask.session.get("username"), username, repository):
1009
flask.abort(401)
1010
1011
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1012
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1013
repo = git.Repo(server_repo_location)
1014
1015
pull_request = db.session.get(PullRequest, id)
1016
1017
if pull_request:
1018
result = celery_tasks.merge_heads.delay(
1019
pull_request.head_route,
1020
pull_request.head_branch,
1021
pull_request.base_route,
1022
pull_request.base_branch,
1023
simulate=False
1024
)
1025
task_result = worker.AsyncResult(result.id)
1026
1027
return flask.redirect(f"/task/{result.id}?pr-id={id}", 303)
1028
# db.session.delete(pull_request)
1029
# db.session.commit()
1030
else:
1031
flask.abort(400)
1032
1033
1034
@app.route("/task/<task_id>")
1035
def task_monitor(task_id):
1036
task_result = worker.AsyncResult(task_id)
1037
print(task_result.status)
1038
1039
return flask.render_template("task-monitor.html", result=task_result)
1040
1041
1042
@repositories.route("/<username>/<repository>/prs/delete", methods=["POST"])
1043
def repository_prs_delete(username, repository):
1044
if not get_permission_level(flask.session.get("username"), username, repository):
1045
flask.abort(401)
1046
1047
server_repo_location = os.path.join(config.REPOS_PATH, username, repository)
1048
repo_data = Repo.query.filter_by(route=f"/{username}/{repository}").first()
1049
repo = git.Repo(server_repo_location)
1050
id = flask.request.form.get("id")
1051
1052
pull_request = db.session.get(PullRequest, id)
1053
1054
if pull_request:
1055
db.session.delete(pull_request)
1056
db.session.commit()
1057
1058
return flask.redirect(".", 303)
1059
1060
1061
@repositories.route("/<username>/<repository>/settings/")
1062
def repository_settings(username, repository):
1063
if get_permission_level(flask.session.get("username"), username, repository) != 2:
1064
flask.abort(401)
1065
1066
return flask.render_template("repo-settings.html", username=username, repository=repository)
1067
1068
1069
@app.errorhandler(404)
1070
def e404(error):
1071
return flask.render_template("not-found.html"), 404
1072
1073
1074
@app.errorhandler(401)
1075
def e401(error):
1076
return flask.render_template("unauthorised.html"), 401
1077
1078
1079
@app.errorhandler(403)
1080
def e403(error):
1081
return flask.render_template("forbidden.html"), 403
1082
1083
1084
@app.errorhandler(418)
1085
def e418(error):
1086
return flask.render_template("teapot.html"), 418
1087
1088
1089
@app.errorhandler(405)
1090
def e405(error):
1091
return flask.render_template("method-not-allowed.html"), 405
1092
1093
1094
if __name__ == "__main__":
1095
app.run(debug=True, port=8080, host="0.0.0.0")
1096
1097
app.register_blueprint(repositories)
1098