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