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 • 30.45 kiB
Python script, ASCII text executable
        
            
1
import os
2
import random
3
import subprocess
4
from functools import wraps
5
6
import cairosvg
7
import flask
8
from flask_sqlalchemy import SQLAlchemy
9
import git
10
import mimetypes
11
import magic
12
from flask_bcrypt import Bcrypt
13
from markupsafe import escape, Markup
14
from flask_migrate import Migrate
15
from datetime import datetime
16
from enum import Enum
17
import shutil
18
from PIL import Image
19
from cairosvg import svg2png
20
import platform
21
22
import config
23
24
app = flask.Flask(__name__)
25
26
from flask_httpauth import HTTPBasicAuth
27
28
auth = HTTPBasicAuth()
29
30
app.config["SQLALCHEMY_DATABASE_URI"] = config.DB_URI
31
app.config["SECRET_KEY"] = config.DB_PASSWORD
32
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
33
db = SQLAlchemy(app)
34
bcrypt = Bcrypt(app)
35
migrate = Migrate(app, db)
36
37
38
def gitCommand(repo, data, *args):
39
if not os.path.isdir(repo):
40
raise FileNotFoundError("Repo not found")
41
env = os.environ.copy()
42
43
command = ["git", *args]
44
45
proc = subprocess.Popen(" ".join(command), cwd=repo, env=env, shell=True, stdout=subprocess.PIPE,
46
stdin=subprocess.PIPE)
47
print(command)
48
49
if data:
50
proc.stdin.write(data)
51
52
out, err = proc.communicate()
53
return out
54
55
56
def onlyChars(string, chars):
57
for i in string:
58
if i not in chars:
59
return False
60
return True
61
62
63
with app.app_context():
64
class RepoAccess(db.Model):
65
id = db.Column(db.Integer, primary_key=True)
66
userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
67
repoRoute = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)
68
accessLevel = db.Column(db.SmallInteger(), nullable=False) # 0 read-only, 1 read-write, 2 admin
69
70
user = db.relationship("User", back_populates="repoAccess")
71
repo = db.relationship("Repo", back_populates="repoAccess")
72
73
__table_args__ = (db.UniqueConstraint("userUsername", "repoRoute", name="_user_repo_uc"),)
74
75
def __init__(self, user, repo, level):
76
self.userUsername = user.username
77
self.repoRoute = repo.route
78
self.accessLevel = level
79
80
81
class User(db.Model):
82
username = db.Column(db.String(32), unique=True, nullable=False, primary_key=True)
83
displayName = db.Column(db.Unicode(128), unique=False, nullable=True)
84
bio = db.Column(db.Unicode(512), unique=False, nullable=True)
85
passwordHashed = db.Column(db.String(60), nullable=False)
86
email = db.Column(db.String(254), nullable=True)
87
company = db.Column(db.Unicode(64), nullable=True)
88
companyURL = db.Column(db.String(256), nullable=True)
89
URL = db.Column(db.String(256), nullable=True)
90
showMail = db.Column(db.Boolean, default=False, nullable=False)
91
location = db.Column(db.Unicode(64), nullable=True)
92
creationDate = db.Column(db.DateTime, default=datetime.utcnow)
93
94
repositories = db.relationship("Repo", back_populates="owner")
95
repoAccess = db.relationship("RepoAccess", back_populates="user")
96
97
commits = db.relationship("Commit", back_populates="owner")
98
posts = db.relationship("Post", back_populates="owner")
99
100
def __init__(self, username, password, email=None, displayName=None):
101
self.username = username
102
self.passwordHashed = bcrypt.generate_password_hash(password, config.HASHING_ROUNDS).decode("utf-8")
103
self.email = email
104
self.displayName = displayName
105
106
# Create the user's directory
107
if not os.path.exists(os.path.join(config.REPOS_PATH, username)):
108
os.makedirs(os.path.join(config.REPOS_PATH, username))
109
if not os.path.exists(os.path.join(config.USERDATA_PATH, username)):
110
os.makedirs(os.path.join(config.USERDATA_PATH, username))
111
112
avatarName = random.choice(os.listdir(config.DEFAULT_AVATARS_PATH))
113
if os.path.join(config.DEFAULT_AVATARS_PATH, avatarName).endswith(".svg"):
114
cairosvg.svg2png(url=os.path.join(config.DEFAULT_AVATARS_PATH, avatarName),
115
write_to="/tmp/roundabout-avatar.png")
116
avatar = Image.open("/tmp/roundabout-avatar.png")
117
else:
118
avatar = Image.open(os.path.join(config.DEFAULT_AVATARS_PATH, avatarName))
119
avatar.thumbnail(config.AVATAR_SIZE)
120
avatar.save(os.path.join(config.USERDATA_PATH, username, "avatar.png"))
121
122
123
class Repo(db.Model):
124
route = db.Column(db.String(98), unique=True, nullable=False, primary_key=True)
125
ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
126
name = db.Column(db.String(64), nullable=False)
127
owner = db.relationship("User", back_populates="repositories")
128
visibility = db.Column(db.SmallInteger(), nullable=False)
129
info = db.Column(db.Unicode(512), nullable=True)
130
URL = db.Column(db.String(256), nullable=True)
131
creationDate = db.Column(db.DateTime, default=datetime.utcnow)
132
133
defaultBranch = db.Column(db.String(64), nullable=True, default="")
134
135
commits = db.relationship("Commit", back_populates="repo")
136
posts = db.relationship("Post", back_populates="repo")
137
repoAccess = db.relationship("RepoAccess", back_populates="repo")
138
139
lastPostID = db.Column(db.Integer, nullable=False, default=0)
140
141
def __init__(self, owner, name, visibility):
142
self.route = f"/{owner.username}/{name}"
143
self.name = name
144
self.ownerName = owner.username
145
self.owner = owner
146
self.visibility = visibility
147
148
# Add the owner as an admin
149
repoAccess = RepoAccess(owner, self, 2)
150
db.session.add(repoAccess)
151
152
153
class Commit(db.Model):
154
identifier = db.Column(db.String(227), unique=True, nullable=False, primary_key=True)
155
sha = db.Column(db.String(128), nullable=False)
156
repoName = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)
157
ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
158
ownerIdentity = db.Column(db.String(321))
159
receiveDate = db.Column(db.DateTime, default=datetime.now)
160
authorDate = db.Column(db.DateTime)
161
message = db.Column(db.UnicodeText)
162
repo = db.relationship("Repo", back_populates="commits")
163
owner = db.relationship("User", back_populates="commits")
164
165
def __init__(self, sha, owner, repo, date, message, ownerIdentity):
166
self.identifier = f"/{owner.username}/{repo.name}/{sha}"
167
self.sha = sha
168
self.repoName = repo.route
169
self.repo = repo
170
self.ownerName = owner.username
171
self.owner = owner
172
self.authorDate = datetime.fromtimestamp(int(date))
173
self.message = message
174
self.ownerIdentity = ownerIdentity
175
176
177
class Post(db.Model):
178
identifier = db.Column(db.String(109), unique=True, nullable=False, primary_key=True)
179
number = db.Column(db.String(109), nullable=False)
180
repoName = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)
181
ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
182
183
parentID = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=True)
184
state = db.Column(db.SmallInteger, nullable=True, default=1)
185
186
date = db.Column(db.DateTime, default=datetime.now)
187
subject = db.Column(db.Unicode(384))
188
message = db.Column(db.UnicodeText)
189
repo = db.relationship("Repo", back_populates="posts")
190
owner = db.relationship("User", back_populates="posts")
191
parent = db.relationship("Post", back_populates="children", remote_side="Post.identifier")
192
children = db.relationship("Post", back_populates="parent", remote_side="Post.parentID")
193
194
def __init__(self, owner, repo, parent, subject, message):
195
self.identifier = f"/{owner.username}/{repo.name}/{repo.lastPostID}"
196
self.number = repo.lastPostID
197
self.repoName = repo.route
198
self.repo = repo
199
self.ownerName = owner.username
200
self.owner = owner
201
self.subject = subject
202
self.message = message
203
self.parent = parent
204
repo.lastPostID += 1
205
206
207
def getPermissionLevel(loggedIn, username, repository):
208
user = User.query.filter_by(username=loggedIn).first()
209
repo = Repo.query.filter_by(route=f"/{username}/{repository}").first()
210
211
if user and repo:
212
permission = RepoAccess.query.filter_by(user=user, repo=repo).first()
213
if permission:
214
return permission.accessLevel
215
216
return None
217
218
219
def getVisibility(username, repository):
220
repo = Repo.query.filter_by(route=f"/{username}/{repository}").first()
221
222
if repo:
223
return repo.visibility
224
225
return None
226
227
228
import gitHTTP
229
import jinjaUtils
230
231
232
def humanSize(value, decimals=2, scale=1024,
233
units=("B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB")):
234
for unit in units:
235
if value < scale:
236
break
237
value /= scale
238
if int(value) == value:
239
# do not return decimals, if the value is already round
240
return int(value), unit
241
return round(value * 10 ** decimals) / 10 ** decimals, unit
242
243
244
def guessMIME(path):
245
if os.path.isdir(path):
246
mimetype = "inode/directory"
247
elif magic.from_file(path, mime=True):
248
mimetype = magic.from_file(path, mime=True)
249
else:
250
mimetype = "application/octet-stream"
251
return mimetype
252
253
254
def convertToHTML(path):
255
with open(path, "r") as f:
256
contents = f.read()
257
return contents
258
259
260
@app.context_processor
261
def default():
262
username = flask.session.get("username")
263
264
return {"loggedInUser": username}
265
266
267
@app.route("/")
268
def main():
269
return flask.render_template("home.html")
270
271
272
@app.route("/about/")
273
def about():
274
return flask.render_template("about.html", platform=platform)
275
276
277
@app.route("/settings/", methods=["GET", "POST"])
278
def settings():
279
if flask.request.method == "GET":
280
if not flask.session.get("username"):
281
flask.abort(401)
282
user = User.query.filter_by(username=flask.session.get("username")).first()
283
284
return flask.render_template("user-settings.html", user=user)
285
else:
286
user = User.query.filter_by(username=flask.session.get("username")).first()
287
288
user.displayName = flask.request.form["displayname"]
289
user.URL = flask.request.form["url"]
290
user.company = flask.request.form["company"]
291
user.companyURL = flask.request.form["companyurl"]
292
user.location = flask.request.form["location"]
293
user.showMail = flask.request.form.get("showmail", user.showMail)
294
295
db.session.commit()
296
297
flask.flash(Markup("<iconify-icon icon='mdi:check'></iconify-icon>Settings saved"), category="success")
298
return flask.redirect(f"/{flask.session.get('username')}", code=303)
299
300
301
@app.route("/accounts/", methods=["GET", "POST"])
302
def login():
303
if flask.request.method == "GET":
304
return flask.render_template("login.html")
305
else:
306
if "login" in flask.request.form:
307
username = flask.request.form["username"]
308
password = flask.request.form["password"]
309
310
user = User.query.filter_by(username=username).first()
311
312
if user and bcrypt.check_password_hash(user.passwordHashed, password):
313
flask.session["username"] = user.username
314
flask.flash(
315
Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged in as {username}"),
316
category="success")
317
return flask.redirect("/", code=303)
318
elif not user:
319
flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>User not found"),
320
category="alert")
321
return flask.render_template("login.html")
322
else:
323
flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>Invalid password"),
324
category="error")
325
return flask.render_template("login.html")
326
if "signup" in flask.request.form:
327
username = flask.request.form["username"]
328
password = flask.request.form["password"]
329
password2 = flask.request.form["password2"]
330
email = flask.request.form.get("email")
331
email2 = flask.request.form.get("email2") # repeat email is a honeypot
332
name = flask.request.form.get("name")
333
334
if not onlyChars(username, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"):
335
flask.flash(Markup(
336
"<iconify-icon icon='mdi:account-error'></iconify-icon>Usernames may only contain Latin alphabet, numbers, '-' and '_'"),
337
category="error")
338
return flask.render_template("login.html")
339
340
if username in config.RESERVED_NAMES:
341
flask.flash(
342
Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>Sorry, {username} is a system path"),
343
category="error")
344
return flask.render_template("login.html")
345
346
userCheck = User.query.filter_by(username=username).first()
347
if userCheck:
348
flask.flash(
349
Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>The username {username} is taken"),
350
category="error")
351
return flask.render_template("login.html")
352
353
if password2 != password:
354
flask.flash(Markup("<iconify-icon icon='mdi:key-alert'></iconify-icon>Make sure the passwords match"),
355
category="error")
356
return flask.render_template("login.html")
357
358
user = User(username, password, email, name)
359
db.session.add(user)
360
db.session.commit()
361
flask.session["username"] = user.username
362
flask.flash(Markup(
363
f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully created and logged in as {username}"),
364
category="success")
365
return flask.redirect("/", code=303)
366
367
368
@app.route("/newrepo/", methods=["GET", "POST"])
369
def newRepo():
370
if flask.request.method == "GET":
371
return flask.render_template("new-repo.html")
372
else:
373
name = flask.request.form["name"]
374
visibility = int(flask.request.form["visibility"])
375
376
if not onlyChars(name, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"):
377
flask.flash(Markup(
378
"<iconify-icon icon='mdi:error'></iconify-icon>Repository names may only contain Latin alphabet, numbers, '-' and '_'"),
379
category="error")
380
return flask.render_template("new-repo.html")
381
382
user = User.query.filter_by(username=flask.session.get("username")).first()
383
384
repo = Repo(user, name, visibility)
385
db.session.add(repo)
386
db.session.commit()
387
388
if not os.path.exists(os.path.join(config.REPOS_PATH, repo.route)):
389
subprocess.run(["git", "init", repo.name],
390
cwd=os.path.join(config.REPOS_PATH, flask.session.get("username")))
391
392
flask.flash(Markup(f"<iconify-icon icon='mdi:folder'></iconify-icon>Successfully created repository {name}"),
393
category="success")
394
return flask.redirect(repo.route, code=303)
395
396
397
@app.route("/logout")
398
def logout():
399
flask.session.clear()
400
flask.flash(Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged out"), category="info")
401
return flask.redirect("/", code=303)
402
403
404
@app.route("/<username>/")
405
def userProfile(username):
406
user = User.query.filter_by(username=username).first()
407
repos = Repo.query.filter_by(ownerName=username, visibility=2)
408
return flask.render_template("user-profile.html", user=user, repos=repos)
409
410
411
@app.route("/<username>/<repository>/")
412
def repositoryIndex(username, repository):
413
return flask.redirect("./tree", code=302)
414
415
416
@app.route("/<username>/<repository>/raw/<branch>/<path:subpath>")
417
def repositoryRaw(username, repository, branch, subpath):
418
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("user"), username,
419
repository) is not None):
420
flask.abort(403)
421
422
serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
423
424
app.logger.info(f"Loading {serverRepoLocation}")
425
426
if not os.path.exists(serverRepoLocation):
427
app.logger.error(f"Cannot load {serverRepoLocation}")
428
return flask.render_template("not-found.html"), 404
429
430
repo = git.Repo(serverRepoLocation)
431
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
432
if not repoData.defaultBranch:
433
if repo.heads:
434
repoData.defaultBranch = repo.heads[0].name
435
else:
436
return flask.render_template("empty.html",
437
remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
438
if not branch:
439
branch = repoData.defaultBranch
440
return flask.redirect(f"./{branch}", code=302)
441
442
if branch.startswith("tag:"):
443
ref = f"tags/{branch[4:]}"
444
elif branch.startswith("~"):
445
ref = branch[1:]
446
else:
447
ref = f"heads/{branch}"
448
449
ref = ref.replace("~", "/") # encode slashes for URL support
450
451
try:
452
repo.git.checkout("-f", ref)
453
except git.exc.GitCommandError:
454
return flask.render_template("not-found.html"), 404
455
456
return flask.send_from_directory(config.REPOS_PATH, os.path.join(username, repository, subpath))
457
458
459
@app.route("/info/<username>/avatar")
460
def userAvatar(username):
461
serverUserdataLocation = os.path.join(config.USERDATA_PATH, username)
462
463
if not os.path.exists(serverUserdataLocation):
464
return flask.render_template("not-found.html"), 404
465
466
return flask.send_from_directory(serverUserdataLocation, "avatar.png")
467
468
469
@app.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""})
470
@app.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""})
471
@app.route("/<username>/<repository>/tree/<branch>/<path:subpath>")
472
def repositoryTree(username, repository, branch, subpath):
473
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
474
repository) is not None):
475
flask.abort(403)
476
477
serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
478
479
app.logger.info(f"Loading {serverRepoLocation}")
480
481
if not os.path.exists(serverRepoLocation):
482
app.logger.error(f"Cannot load {serverRepoLocation}")
483
return flask.render_template("not-found.html"), 404
484
485
repo = git.Repo(serverRepoLocation)
486
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
487
if not repoData.defaultBranch:
488
if repo.heads:
489
repoData.defaultBranch = repo.heads[0].name
490
else:
491
return flask.render_template("empty.html",
492
remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
493
if not branch:
494
branch = repoData.defaultBranch
495
return flask.redirect(f"./{branch}", code=302)
496
497
if branch.startswith("tag:"):
498
ref = f"tags/{branch[4:]}"
499
elif branch.startswith("~"):
500
ref = branch[1:]
501
else:
502
ref = f"heads/{branch}"
503
504
ref = ref.replace("~", "/") # encode slashes for URL support
505
506
try:
507
repo.git.checkout("-f", ref)
508
except git.exc.GitCommandError:
509
return flask.render_template("not-found.html"), 404
510
511
branches = repo.heads
512
513
allRefs = []
514
for ref in repo.heads:
515
allRefs.append((ref, "head"))
516
for ref in repo.tags:
517
allRefs.append((ref, "tag"))
518
519
if os.path.isdir(os.path.join(serverRepoLocation, subpath)):
520
files = []
521
blobs = []
522
523
for entry in os.listdir(os.path.join(serverRepoLocation, subpath)):
524
if not os.path.basename(entry) == ".git":
525
files.append(os.path.join(subpath, entry))
526
527
infos = []
528
529
for file in files:
530
path = os.path.join(serverRepoLocation, file)
531
mimetype = guessMIME(path)
532
533
text = gitCommand(serverRepoLocation, None, "log", "--format='%H\n'", file).decode()
534
535
sha = text.split("\n")[0]
536
identifier = f"/{username}/{repository}/{sha}"
537
lastCommit = Commit.query.filter_by(identifier=identifier).first()
538
539
info = {
540
"name": os.path.basename(file),
541
"serverPath": path,
542
"relativePath": file,
543
"link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file),
544
"size": humanSize(os.path.getsize(path)),
545
"mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}",
546
"commit": lastCommit,
547
"shaSize": 7,
548
}
549
550
specialIcon = config.matchIcon(os.path.basename(file))
551
if specialIcon:
552
info["icon"] = specialIcon
553
elif os.path.isdir(path):
554
info["icon"] = config.folderIcon
555
elif mimetypes.guess_type(path)[0] in config.fileIcons:
556
info["icon"] = config.fileIcons[mimetypes.guess_type(path)[0]]
557
else:
558
info["icon"] = config.unknownIcon
559
560
if os.path.isdir(path):
561
infos.insert(0, info)
562
else:
563
infos.append(info)
564
565
return flask.render_template(
566
"repo-tree.html",
567
username=username,
568
repository=repository,
569
files=infos,
570
subpath=os.path.join("/", subpath),
571
branches=allRefs,
572
current=branch
573
)
574
else:
575
path = os.path.join(serverRepoLocation, subpath)
576
577
if not os.path.exists(path):
578
return flask.render_template("not-found.html"), 404
579
580
mimetype = guessMIME(path)
581
mode = mimetype.split("/", 1)[0]
582
size = humanSize(os.path.getsize(path))
583
584
specialIcon = config.matchIcon(os.path.basename(path))
585
if specialIcon:
586
icon = specialIcon
587
elif os.path.isdir(path):
588
icon = config.folderIcon
589
elif mimetypes.guess_type(path)[0] in config.fileIcons:
590
icon = config.fileIcons[mimetypes.guess_type(path)[0]]
591
else:
592
icon = config.unknownIcon
593
594
contents = None
595
if mode == "text":
596
contents = convertToHTML(path)
597
598
return flask.render_template(
599
"repo-file.html",
600
username=username,
601
repository=repository,
602
file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath),
603
branches=allRefs,
604
current=branch,
605
mode=mode,
606
mimetype=mimetype,
607
detailedtype=magic.from_file(path),
608
size=size,
609
icon=icon,
610
subpath=os.path.join("/", subpath),
611
basename=os.path.basename(path),
612
contents=contents
613
)
614
615
616
@app.route("/<username>/<repository>/forum/")
617
def repositoryForum(username, repository):
618
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
619
repository) is not None):
620
flask.abort(403)
621
622
serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
623
624
app.logger.info(f"Loading {serverRepoLocation}")
625
626
if not os.path.exists(serverRepoLocation):
627
app.logger.error(f"Cannot load {serverRepoLocation}")
628
return flask.render_template("not-found.html"), 404
629
630
repo = git.Repo(serverRepoLocation)
631
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
632
user = User.query.filter_by(username=username).first()
633
relationships = RepoAccess.query.filter_by(repo=repoData)
634
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
635
636
return flask.render_template("repo-forum.html", username=username, repository=repository, repoData=repoData, relationships=relationships, repo=repo, userRelationship=userRelationship, Post=Post)
637
638
639
@app.route("/<username>/<repository>/forum/<postID>")
640
def repositoryForumThread(username, repository, postID):
641
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
642
repository) is not None):
643
flask.abort(403)
644
645
serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
646
647
app.logger.info(f"Loading {serverRepoLocation}")
648
649
if not os.path.exists(serverRepoLocation):
650
app.logger.error(f"Cannot load {serverRepoLocation}")
651
return flask.render_template("not-found.html"), 404
652
653
repo = git.Repo(serverRepoLocation)
654
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
655
user = User.query.filter_by(username=username).first()
656
relationships = RepoAccess.query.filter_by(repo=repoData)
657
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
658
659
return flask.render_template("repo-forum-thread.html", username=username, repository=repository, repoData=repoData, relationships=relationships, repo=repo, userRelationship=userRelationship, Post=Post, postID=postID)
660
661
662
@app.route("/<username>/<repository>/users/", methods=["GET", "POST"])
663
def repositoryUsers(username, repository):
664
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
665
repository) is not None):
666
flask.abort(403)
667
668
serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
669
670
app.logger.info(f"Loading {serverRepoLocation}")
671
672
if not os.path.exists(serverRepoLocation):
673
app.logger.error(f"Cannot load {serverRepoLocation}")
674
return flask.render_template("not-found.html"), 404
675
676
repo = git.Repo(serverRepoLocation)
677
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
678
user = User.query.filter_by(username=flask.session.get("username")).first()
679
relationships = RepoAccess.query.filter_by(repo=repoData)
680
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
681
682
if flask.request.method == "GET":
683
return flask.render_template("repo-users.html", username=username, repository=repository, repoData=repoData, relationships=relationships, repo=repo, userRelationship=userRelationship)
684
else:
685
if getPermissionLevel(flask.session.get("username"), username, repository) != 2:
686
flask.abort(401)
687
688
if flask.request.form.get("new-username"):
689
# Create new relationship
690
newUser = User.query.filter_by(username=flask.request.form.get("new-username")).first()
691
relationship = RepoAccess(newUser, repoData, flask.request.form.get("new-level"))
692
db.session.add(relationship)
693
db.session.commit()
694
if flask.request.form.get("update-username"):
695
# Create new relationship
696
updatedUser = User.query.filter_by(username=flask.request.form.get("update-username")).first()
697
relationship = RepoAccess.query.filter_by(repo=repoData, user=updatedUser).first()
698
if flask.request.form.get("update-level") == -1:
699
relationship.delete()
700
else:
701
relationship.accessLevel = flask.request.form.get("update-level")
702
db.session.commit()
703
704
return flask.redirect(app.url_for("repositoryUsers", username=username, repository=repository))
705
706
707
@app.route("/<username>/<repository>/branches/")
708
def repositoryBranches(username, repository):
709
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
710
repository) is not None):
711
flask.abort(403)
712
713
serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
714
715
app.logger.info(f"Loading {serverRepoLocation}")
716
717
if not os.path.exists(serverRepoLocation):
718
app.logger.error(f"Cannot load {serverRepoLocation}")
719
return flask.render_template("not-found.html"), 404
720
721
repo = git.Repo(serverRepoLocation)
722
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
723
724
return flask.render_template("repo-branches.html", username=username, repository=repository, repoData=repoData, repo=repo)
725
726
727
@app.route("/<username>/<repository>/log/")
728
def repositoryLog(username, repository):
729
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
730
repository) is not None):
731
flask.abort(403)
732
733
serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
734
735
app.logger.info(f"Loading {serverRepoLocation}")
736
737
if not os.path.exists(serverRepoLocation):
738
app.logger.error(f"Cannot load {serverRepoLocation}")
739
return flask.render_template("not-found.html"), 404
740
741
repo = git.Repo(serverRepoLocation)
742
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
743
commits = Commit.query.filter_by(repo=repoData)
744
745
return flask.render_template("repo-log.html", username=username, repository=repository, repoData=repoData, repo=repo, commits=commits)
746
747
748
@app.route("/<username>/<repository>/settings/")
749
def repositorySettings(username, repository):
750
if getPermissionLevel(flask.session.get("username"), username, repository) != 2:
751
flask.abort(401)
752
753
return flask.render_template("repo-settings.html", username=username, repository=repository)
754
755
756
@app.errorhandler(404)
757
def e404(error):
758
return flask.render_template("not-found.html"), 404
759
760
761
@app.errorhandler(401)
762
def e401(error):
763
return flask.render_template("unauthorised.html"), 401
764
765
766
@app.errorhandler(403)
767
def e403(error):
768
return flask.render_template("forbidden.html"), 403
769
770
771
@app.errorhandler(418)
772
def e418(error):
773
return flask.render_template("teapot.html"), 418
774
775
776
@app.errorhandler(405)
777
def e405(error):
778
return flask.render_template("method-not-allowed.html"), 405
779
780
781
if __name__ == "__main__":
782
app.run(debug=True, port=8080, host="0.0.0.0")
783