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 • 29.11 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
repoName = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)
180
ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
181
182
parentID = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=True)
183
184
date = db.Column(db.DateTime, default=datetime.now)
185
subject = db.Column(db.Unicode(384))
186
message = db.Column(db.UnicodeText)
187
repo = db.relationship("Repo", back_populates="posts")
188
owner = db.relationship("User", back_populates="posts")
189
parent = db.relationship("Post", back_populates="children", remote_side="Post.identifier")
190
children = db.relationship("Post", back_populates="parent", remote_side="Post.parentID")
191
192
def __init__(self, owner, repo, parent, subject, message):
193
self.identifier = f"/{owner.username}/{repo.name}/{repo.lastPostID}"
194
self.repoName = repo.route
195
self.repo = repo
196
self.ownerName = owner.username
197
self.owner = owner
198
self.subject = subject
199
self.message = message
200
self.parent = parent
201
repo.lastPostID += 1
202
203
204
def getPermissionLevel(loggedIn, username, repository):
205
user = User.query.filter_by(username=loggedIn).first()
206
repo = Repo.query.filter_by(route=f"/{username}/{repository}").first()
207
208
if user and repo:
209
permission = RepoAccess.query.filter_by(user=user, repo=repo).first()
210
if permission:
211
return permission.accessLevel
212
213
return None
214
215
216
def getVisibility(username, repository):
217
repo = Repo.query.filter_by(route=f"/{username}/{repository}").first()
218
219
if repo:
220
return repo.visibility
221
222
return None
223
224
225
import gitHTTP
226
import jinjaUtils
227
228
229
def humanSize(value, decimals=2, scale=1024,
230
units=("B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB")):
231
for unit in units:
232
if value < scale:
233
break
234
value /= scale
235
if int(value) == value:
236
# do not return decimals, if the value is already round
237
return int(value), unit
238
return round(value * 10 ** decimals) / 10 ** decimals, unit
239
240
241
def guessMIME(path):
242
if os.path.isdir(path):
243
mimetype = "inode/directory"
244
elif magic.from_file(path, mime=True):
245
mimetype = magic.from_file(path, mime=True)
246
else:
247
mimetype = "application/octet-stream"
248
return mimetype
249
250
251
def convertToHTML(path):
252
with open(path, "r") as f:
253
contents = f.read()
254
return contents
255
256
257
@app.context_processor
258
def default():
259
username = flask.session.get("username")
260
261
return {"loggedInUser": username}
262
263
264
@app.route("/")
265
def main():
266
return flask.render_template("home.html")
267
268
269
@app.route("/about/")
270
def about():
271
return flask.render_template("about.html", platform=platform)
272
273
274
@app.route("/settings/", methods=["GET", "POST"])
275
def settings():
276
if flask.request.method == "GET":
277
if not flask.session.get("username"):
278
flask.abort(401)
279
user = User.query.filter_by(username=flask.session.get("username")).first()
280
281
return flask.render_template("user-settings.html", user=user)
282
else:
283
user = User.query.filter_by(username=flask.session.get("username")).first()
284
285
user.displayName = flask.request.form["displayname"]
286
user.URL = flask.request.form["url"]
287
user.company = flask.request.form["company"]
288
user.companyURL = flask.request.form["companyurl"]
289
user.location = flask.request.form["location"]
290
user.showMail = flask.request.form.get("showmail", user.showMail)
291
292
db.session.commit()
293
294
flask.flash(Markup("<iconify-icon icon='mdi:check'></iconify-icon>Settings saved"), category="success")
295
return flask.redirect(f"/{flask.session.get('username')}", code=303)
296
297
298
@app.route("/accounts/", methods=["GET", "POST"])
299
def login():
300
if flask.request.method == "GET":
301
return flask.render_template("login.html")
302
else:
303
if "login" in flask.request.form:
304
username = flask.request.form["username"]
305
password = flask.request.form["password"]
306
307
user = User.query.filter_by(username=username).first()
308
309
if user and bcrypt.check_password_hash(user.passwordHashed, password):
310
flask.session["username"] = user.username
311
flask.flash(
312
Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged in as {username}"),
313
category="success")
314
return flask.redirect("/", code=303)
315
elif not user:
316
flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>User not found"),
317
category="alert")
318
return flask.render_template("login.html")
319
else:
320
flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>Invalid password"),
321
category="error")
322
return flask.render_template("login.html")
323
if "signup" in flask.request.form:
324
username = flask.request.form["username"]
325
password = flask.request.form["password"]
326
password2 = flask.request.form["password2"]
327
email = flask.request.form.get("email")
328
email2 = flask.request.form.get("email2") # repeat email is a honeypot
329
name = flask.request.form.get("name")
330
331
if not onlyChars(username, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"):
332
flask.flash(Markup(
333
"<iconify-icon icon='mdi:account-error'></iconify-icon>Usernames may only contain Latin alphabet, numbers, '-' and '_'"),
334
category="error")
335
return flask.render_template("login.html")
336
337
if username in config.RESERVED_NAMES:
338
flask.flash(
339
Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>Sorry, {username} is a system path"),
340
category="error")
341
return flask.render_template("login.html")
342
343
userCheck = User.query.filter_by(username=username).first()
344
if userCheck:
345
flask.flash(
346
Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>The username {username} is taken"),
347
category="error")
348
return flask.render_template("login.html")
349
350
if password2 != password:
351
flask.flash(Markup("<iconify-icon icon='mdi:key-alert'></iconify-icon>Make sure the passwords match"),
352
category="error")
353
return flask.render_template("login.html")
354
355
user = User(username, password, email, name)
356
db.session.add(user)
357
db.session.commit()
358
flask.session["username"] = user.username
359
flask.flash(Markup(
360
f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully created and logged in as {username}"),
361
category="success")
362
return flask.redirect("/", code=303)
363
364
365
@app.route("/newrepo/", methods=["GET", "POST"])
366
def newRepo():
367
if flask.request.method == "GET":
368
return flask.render_template("new-repo.html")
369
else:
370
name = flask.request.form["name"]
371
visibility = int(flask.request.form["visibility"])
372
373
if not onlyChars(name, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"):
374
flask.flash(Markup(
375
"<iconify-icon icon='mdi:error'></iconify-icon>Repository names may only contain Latin alphabet, numbers, '-' and '_'"),
376
category="error")
377
return flask.render_template("new-repo.html")
378
379
user = User.query.filter_by(username=flask.session.get("username")).first()
380
381
repo = Repo(user, name, visibility)
382
db.session.add(repo)
383
db.session.commit()
384
385
if not os.path.exists(os.path.join(config.REPOS_PATH, repo.route)):
386
subprocess.run(["git", "init", repo.name],
387
cwd=os.path.join(config.REPOS_PATH, flask.session.get("username")))
388
389
flask.flash(Markup(f"<iconify-icon icon='mdi:folder'></iconify-icon>Successfully created repository {name}"),
390
category="success")
391
return flask.redirect(repo.route, code=303)
392
393
394
@app.route("/logout")
395
def logout():
396
flask.session.clear()
397
flask.flash(Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged out"), category="info")
398
return flask.redirect("/", code=303)
399
400
401
@app.route("/<username>/")
402
def userProfile(username):
403
user = User.query.filter_by(username=username).first()
404
repos = Repo.query.filter_by(ownerName=username, visibility=2)
405
return flask.render_template("user-profile.html", user=user, repos=repos)
406
407
408
@app.route("/<username>/<repository>/")
409
def repositoryIndex(username, repository):
410
return flask.redirect("./tree", code=302)
411
412
413
@app.route("/<username>/<repository>/raw/<branch>/<path:subpath>")
414
def repositoryRaw(username, repository, branch, subpath):
415
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("user"), username,
416
repository) is not None):
417
flask.abort(403)
418
419
serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
420
421
app.logger.info(f"Loading {serverRepoLocation}")
422
423
if not os.path.exists(serverRepoLocation):
424
app.logger.error(f"Cannot load {serverRepoLocation}")
425
return flask.render_template("not-found.html"), 404
426
427
repo = git.Repo(serverRepoLocation)
428
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
429
if not repoData.defaultBranch:
430
if repo.heads:
431
repoData.defaultBranch = repo.heads[0].name
432
else:
433
return flask.render_template("empty.html",
434
remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
435
if not branch:
436
branch = repoData.defaultBranch
437
return flask.redirect(f"./{branch}", code=302)
438
439
if branch.startswith("tag:"):
440
ref = f"tags/{branch[4:]}"
441
elif branch.startswith("~"):
442
ref = branch[1:]
443
else:
444
ref = f"heads/{branch}"
445
446
ref = ref.replace("~", "/") # encode slashes for URL support
447
448
try:
449
repo.git.checkout("-f", ref)
450
except git.exc.GitCommandError:
451
return flask.render_template("not-found.html"), 404
452
453
return flask.send_from_directory(config.REPOS_PATH, os.path.join(username, repository, subpath))
454
455
456
@app.route("/info/<username>/avatar")
457
def userAvatar(username):
458
serverUserdataLocation = os.path.join(config.USERDATA_PATH, username)
459
460
if not os.path.exists(serverUserdataLocation):
461
return flask.render_template("not-found.html"), 404
462
463
return flask.send_from_directory(serverUserdataLocation, "avatar.png")
464
465
466
@app.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""})
467
@app.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""})
468
@app.route("/<username>/<repository>/tree/<branch>/<path:subpath>")
469
def repositoryTree(username, repository, branch, subpath):
470
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
471
repository) is not None):
472
flask.abort(403)
473
474
serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
475
476
app.logger.info(f"Loading {serverRepoLocation}")
477
478
if not os.path.exists(serverRepoLocation):
479
app.logger.error(f"Cannot load {serverRepoLocation}")
480
return flask.render_template("not-found.html"), 404
481
482
repo = git.Repo(serverRepoLocation)
483
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
484
if not repoData.defaultBranch:
485
if repo.heads:
486
repoData.defaultBranch = repo.heads[0].name
487
else:
488
return flask.render_template("empty.html",
489
remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
490
if not branch:
491
branch = repoData.defaultBranch
492
return flask.redirect(f"./{branch}", code=302)
493
494
if branch.startswith("tag:"):
495
ref = f"tags/{branch[4:]}"
496
elif branch.startswith("~"):
497
ref = branch[1:]
498
else:
499
ref = f"heads/{branch}"
500
501
ref = ref.replace("~", "/") # encode slashes for URL support
502
503
try:
504
repo.git.checkout("-f", ref)
505
except git.exc.GitCommandError:
506
return flask.render_template("not-found.html"), 404
507
508
branches = repo.heads
509
510
allRefs = []
511
for ref in repo.heads:
512
allRefs.append((ref, "head"))
513
for ref in repo.tags:
514
allRefs.append((ref, "tag"))
515
516
if os.path.isdir(os.path.join(serverRepoLocation, subpath)):
517
files = []
518
blobs = []
519
520
for entry in os.listdir(os.path.join(serverRepoLocation, subpath)):
521
if not os.path.basename(entry) == ".git":
522
files.append(os.path.join(subpath, entry))
523
524
infos = []
525
526
for file in files:
527
path = os.path.join(serverRepoLocation, file)
528
mimetype = guessMIME(path)
529
530
text = gitCommand(serverRepoLocation, None, "log", "--format='%H\n'", file).decode()
531
532
sha = text.split("\n")[0]
533
identifier = f"/{username}/{repository}/{sha}"
534
lastCommit = Commit.query.filter_by(identifier=identifier).first()
535
536
info = {
537
"name": os.path.basename(file),
538
"serverPath": path,
539
"relativePath": file,
540
"link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file),
541
"size": humanSize(os.path.getsize(path)),
542
"mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}",
543
"commit": lastCommit,
544
"shaSize": 7,
545
}
546
547
specialIcon = config.matchIcon(os.path.basename(file))
548
if specialIcon:
549
info["icon"] = specialIcon
550
elif os.path.isdir(path):
551
info["icon"] = config.folderIcon
552
elif mimetypes.guess_type(path)[0] in config.fileIcons:
553
info["icon"] = config.fileIcons[mimetypes.guess_type(path)[0]]
554
else:
555
info["icon"] = config.unknownIcon
556
557
if os.path.isdir(path):
558
infos.insert(0, info)
559
else:
560
infos.append(info)
561
562
return flask.render_template(
563
"repo-tree.html",
564
username=username,
565
repository=repository,
566
files=infos,
567
subpath=os.path.join("/", subpath),
568
branches=allRefs,
569
current=branch
570
)
571
else:
572
path = os.path.join(serverRepoLocation, subpath)
573
574
if not os.path.exists(path):
575
return flask.render_template("not-found.html"), 404
576
577
mimetype = guessMIME(path)
578
mode = mimetype.split("/", 1)[0]
579
size = humanSize(os.path.getsize(path))
580
581
specialIcon = config.matchIcon(os.path.basename(path))
582
if specialIcon:
583
icon = specialIcon
584
elif os.path.isdir(path):
585
icon = config.folderIcon
586
elif mimetypes.guess_type(path)[0] in config.fileIcons:
587
icon = config.fileIcons[mimetypes.guess_type(path)[0]]
588
else:
589
icon = config.unknownIcon
590
591
contents = None
592
if mode == "text":
593
contents = convertToHTML(path)
594
595
return flask.render_template(
596
"repo-file.html",
597
username=username,
598
repository=repository,
599
file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath),
600
branches=allRefs,
601
current=branch,
602
mode=mode,
603
mimetype=mimetype,
604
detailedtype=magic.from_file(path),
605
size=size,
606
icon=icon,
607
subpath=os.path.join("/", subpath),
608
basename=os.path.basename(path),
609
contents=contents
610
)
611
612
613
@app.route("/<username>/<repository>/forum/")
614
def repositoryForum(username, repository):
615
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
616
repository) is not None):
617
flask.abort(403)
618
619
serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
620
621
app.logger.info(f"Loading {serverRepoLocation}")
622
623
if not os.path.exists(serverRepoLocation):
624
app.logger.error(f"Cannot load {serverRepoLocation}")
625
return flask.render_template("not-found.html"), 404
626
627
repo = git.Repo(serverRepoLocation)
628
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
629
user = User.query.filter_by(username=username).first()
630
relationships = RepoAccess.query.filter_by(repo=repoData)
631
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
632
633
return flask.render_template("repo-forum.html", username=username, repository=repository, repoData=repoData, relationships=relationships, repo=repo, userRelationship=userRelationship, Post=Post)
634
635
636
@app.route("/<username>/<repository>/users/", methods=["GET", "POST"])
637
def repositoryUsers(username, repository):
638
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
639
repository) is not None):
640
flask.abort(403)
641
642
serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
643
644
app.logger.info(f"Loading {serverRepoLocation}")
645
646
if not os.path.exists(serverRepoLocation):
647
app.logger.error(f"Cannot load {serverRepoLocation}")
648
return flask.render_template("not-found.html"), 404
649
650
repo = git.Repo(serverRepoLocation)
651
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
652
user = User.query.filter_by(username=flask.session.get("username")).first()
653
relationships = RepoAccess.query.filter_by(repo=repoData)
654
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
655
656
if flask.request.method == "GET":
657
return flask.render_template("repo-users.html", username=username, repository=repository, repoData=repoData, relationships=relationships, repo=repo, userRelationship=userRelationship)
658
else:
659
if getPermissionLevel(flask.session.get("username"), username, repository) != 2:
660
flask.abort(401)
661
662
if flask.request.form.get("new-username"):
663
# Create new relationship
664
newUser = User.query.filter_by(username=flask.request.form.get("new-username")).first()
665
relationship = RepoAccess(newUser, repoData, flask.request.form.get("new-level"))
666
db.session.add(relationship)
667
db.session.commit()
668
if flask.request.form.get("update-username"):
669
# Create new relationship
670
updatedUser = User.query.filter_by(username=flask.request.form.get("update-username")).first()
671
relationship = RepoAccess.query.filter_by(repo=repoData, user=updatedUser).first()
672
if flask.request.form.get("update-level") == -1:
673
relationship.delete()
674
else:
675
relationship.accessLevel = flask.request.form.get("update-level")
676
db.session.commit()
677
678
return flask.redirect(app.url_for("repositoryUsers", username=username, repository=repository))
679
680
681
@app.route("/<username>/<repository>/branches/")
682
def repositoryBranches(username, repository):
683
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
684
repository) is not None):
685
flask.abort(403)
686
687
serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
688
689
app.logger.info(f"Loading {serverRepoLocation}")
690
691
if not os.path.exists(serverRepoLocation):
692
app.logger.error(f"Cannot load {serverRepoLocation}")
693
return flask.render_template("not-found.html"), 404
694
695
repo = git.Repo(serverRepoLocation)
696
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
697
698
return flask.render_template("repo-branches.html", username=username, repository=repository, repoData=repoData, repo=repo)
699
700
701
@app.route("/<username>/<repository>/log/")
702
def repositoryLog(username, repository):
703
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
704
repository) is not None):
705
flask.abort(403)
706
707
serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
708
709
app.logger.info(f"Loading {serverRepoLocation}")
710
711
if not os.path.exists(serverRepoLocation):
712
app.logger.error(f"Cannot load {serverRepoLocation}")
713
return flask.render_template("not-found.html"), 404
714
715
repo = git.Repo(serverRepoLocation)
716
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
717
commits = Commit.query.filter_by(repo=repoData)
718
719
return flask.render_template("repo-log.html", username=username, repository=repository, repoData=repoData, repo=repo, commits=commits)
720
721
722
@app.route("/<username>/<repository>/settings/")
723
def repositorySettings(username, repository):
724
if getPermissionLevel(flask.session.get("username"), username, repository) != 2:
725
flask.abort(401)
726
727
return flask.render_template("repo-settings.html", username=username, repository=repository)
728
729
730
@app.errorhandler(404)
731
def e404(error):
732
return flask.render_template("not-found.html"), 404
733
734
735
@app.errorhandler(401)
736
def e401(error):
737
return flask.render_template("unauthorised.html"), 401
738
739
740
@app.errorhandler(403)
741
def e403(error):
742
return flask.render_template("forbidden.html"), 403
743
744
745
@app.errorhandler(418)
746
def e418(error):
747
return flask.render_template("teapot.html"), 418
748
749
750
@app.errorhandler(405)
751
def e405(error):
752
return flask.render_template("method-not-allowed.html"), 405
753
754
755
if __name__ == "__main__":
756
app.run(debug=True, port=8080, host="0.0.0.0")
757