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 • 38.59 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 RepoFavourite(db.Model):
82
id = db.Column(db.Integer, primary_key=True)
83
userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
84
repoRoute = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)
85
86
user = db.relationship("User", back_populates="favourites")
87
repo = db.relationship("Repo", back_populates="favourites")
88
89
__table_args__ = (db.UniqueConstraint("userUsername", "repoRoute", name="_user_repo_uc"),)
90
91
def __init__(self, user, repo):
92
self.userUsername = user.username
93
self.repoRoute = repo.route
94
95
96
class PostVote(db.Model):
97
id = db.Column(db.Integer, primary_key=True)
98
userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
99
postIdentifier = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=False)
100
voteScore = db.Column(db.SmallInteger(), nullable=False)
101
102
user = db.relationship("User", back_populates="votes")
103
post = db.relationship("Post", back_populates="votes")
104
105
__table_args__ = (db.UniqueConstraint("userUsername", "postIdentifier", name="_user_post_uc"),)
106
107
def __init__(self, user, post, score):
108
self.userUsername = user.username
109
self.postIdentifier = post.identifier
110
self.voteScore = score
111
112
113
class User(db.Model):
114
username = db.Column(db.String(32), unique=True, nullable=False, primary_key=True)
115
displayName = db.Column(db.Unicode(128), unique=False, nullable=True)
116
bio = db.Column(db.Unicode(512), unique=False, nullable=True)
117
passwordHashed = db.Column(db.String(60), nullable=False)
118
email = db.Column(db.String(254), nullable=True)
119
company = db.Column(db.Unicode(64), nullable=True)
120
companyURL = db.Column(db.String(256), nullable=True)
121
URL = db.Column(db.String(256), nullable=True)
122
showMail = db.Column(db.Boolean, default=False, nullable=False)
123
location = db.Column(db.Unicode(64), nullable=True)
124
creationDate = db.Column(db.DateTime, default=datetime.utcnow)
125
126
repositories = db.relationship("Repo", back_populates="owner")
127
repoAccess = db.relationship("RepoAccess", back_populates="user")
128
votes = db.relationship("PostVote", back_populates="user")
129
favourites = db.relationship("RepoFavourite", back_populates="user")
130
131
commits = db.relationship("Commit", back_populates="owner")
132
posts = db.relationship("Post", back_populates="owner")
133
134
def __init__(self, username, password, email=None, displayName=None):
135
self.username = username
136
self.passwordHashed = bcrypt.generate_password_hash(password, config.HASHING_ROUNDS).decode("utf-8")
137
self.email = email
138
self.displayName = displayName
139
140
# Create the user's directory
141
if not os.path.exists(os.path.join(config.REPOS_PATH, username)):
142
os.makedirs(os.path.join(config.REPOS_PATH, username))
143
if not os.path.exists(os.path.join(config.USERDATA_PATH, username)):
144
os.makedirs(os.path.join(config.USERDATA_PATH, username))
145
146
avatarName = random.choice(os.listdir(config.DEFAULT_AVATARS_PATH))
147
if os.path.join(config.DEFAULT_AVATARS_PATH, avatarName).endswith(".svg"):
148
cairosvg.svg2png(url=os.path.join(config.DEFAULT_AVATARS_PATH, avatarName),
149
write_to="/tmp/roundabout-avatar.png")
150
avatar = Image.open("/tmp/roundabout-avatar.png")
151
else:
152
avatar = Image.open(os.path.join(config.DEFAULT_AVATARS_PATH, avatarName))
153
avatar.thumbnail(config.AVATAR_SIZE)
154
avatar.save(os.path.join(config.USERDATA_PATH, username, "avatar.png"))
155
156
157
class Repo(db.Model):
158
route = db.Column(db.String(98), unique=True, nullable=False, primary_key=True)
159
ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
160
name = db.Column(db.String(64), nullable=False)
161
owner = db.relationship("User", back_populates="repositories")
162
visibility = db.Column(db.SmallInteger(), nullable=False)
163
info = db.Column(db.Unicode(512), nullable=True)
164
URL = db.Column(db.String(256), nullable=True)
165
creationDate = db.Column(db.DateTime, default=datetime.utcnow)
166
167
defaultBranch = db.Column(db.String(64), nullable=True, default="")
168
169
commits = db.relationship("Commit", back_populates="repo")
170
posts = db.relationship("Post", back_populates="repo")
171
repoAccess = db.relationship("RepoAccess", back_populates="repo")
172
favourites = db.relationship("RepoFavourite", back_populates="repo")
173
174
lastPostID = db.Column(db.Integer, nullable=False, default=0)
175
176
def __init__(self, owner, name, visibility):
177
self.route = f"/{owner.username}/{name}"
178
self.name = name
179
self.ownerName = owner.username
180
self.owner = owner
181
self.visibility = visibility
182
183
# Add the owner as an admin
184
repoAccess = RepoAccess(owner, self, 2)
185
db.session.add(repoAccess)
186
187
188
class Commit(db.Model):
189
identifier = db.Column(db.String(227), unique=True, nullable=False, primary_key=True)
190
sha = db.Column(db.String(128), nullable=False)
191
repoName = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)
192
ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
193
ownerIdentity = db.Column(db.String(321))
194
receiveDate = db.Column(db.DateTime, default=datetime.now)
195
authorDate = db.Column(db.DateTime)
196
message = db.Column(db.UnicodeText)
197
repo = db.relationship("Repo", back_populates="commits")
198
owner = db.relationship("User", back_populates="commits")
199
200
def __init__(self, sha, owner, repo, date, message, ownerIdentity):
201
self.identifier = f"{repo.route}/{sha}"
202
self.sha = sha
203
self.repoName = repo.route
204
self.repo = repo
205
self.ownerName = owner.username
206
self.owner = owner
207
self.authorDate = datetime.fromtimestamp(int(date))
208
self.message = message
209
self.ownerIdentity = ownerIdentity
210
211
212
class Post(db.Model):
213
identifier = db.Column(db.String(109), unique=True, nullable=False, primary_key=True)
214
number = db.Column(db.Integer, nullable=False)
215
repoName = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False)
216
ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False)
217
votes = db.relationship("PostVote", back_populates="post")
218
voteSum = db.Column(db.Integer, nullable=False, default=0)
219
220
parentID = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=True)
221
state = db.Column(db.SmallInteger, nullable=True, default=1)
222
223
date = db.Column(db.DateTime, default=datetime.now)
224
lastUpdated = db.Column(db.DateTime, default=datetime.now)
225
subject = db.Column(db.Unicode(384))
226
message = db.Column(db.UnicodeText)
227
repo = db.relationship("Repo", back_populates="posts")
228
owner = db.relationship("User", back_populates="posts")
229
parent = db.relationship("Post", back_populates="children", remote_side="Post.identifier")
230
children = db.relationship("Post", back_populates="parent", remote_side="Post.parentID")
231
232
def __init__(self, owner, repo, parent, subject, message):
233
self.identifier = f"{repo.route}/{repo.lastPostID}"
234
self.number = repo.lastPostID
235
self.repoName = repo.route
236
self.repo = repo
237
self.ownerName = owner.username
238
self.owner = owner
239
self.subject = subject
240
self.message = message
241
self.parent = parent
242
repo.lastPostID += 1
243
244
def updateDate(self):
245
self.lastUpdated = datetime.now()
246
with db.session.no_autoflush:
247
if self.parent is not None:
248
self.parent.updateDate()
249
250
251
def getPermissionLevel(loggedIn, username, repository):
252
user = User.query.filter_by(username=loggedIn).first()
253
repo = Repo.query.filter_by(route=f"/{username}/{repository}").first()
254
255
if user and repo:
256
permission = RepoAccess.query.filter_by(user=user, repo=repo).first()
257
if permission:
258
return permission.accessLevel
259
260
return None
261
262
263
def getVisibility(username, repository):
264
repo = Repo.query.filter_by(route=f"/{username}/{repository}").first()
265
266
if repo:
267
return repo.visibility
268
269
return None
270
271
272
import gitHTTP
273
import jinjaUtils
274
275
276
def humanSize(value, decimals=2, scale=1024,
277
units=("B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB")):
278
for unit in units:
279
if value < scale:
280
break
281
value /= scale
282
if int(value) == value:
283
# do not return decimals, if the value is already round
284
return int(value), unit
285
return round(value * 10 ** decimals) / 10 ** decimals, unit
286
287
288
def guessMIME(path):
289
if os.path.isdir(path):
290
mimetype = "inode/directory"
291
elif magic.from_file(path, mime=True):
292
mimetype = magic.from_file(path, mime=True)
293
else:
294
mimetype = "application/octet-stream"
295
return mimetype
296
297
298
def convertToHTML(path):
299
with open(path, "r") as f:
300
contents = f.read()
301
return contents
302
303
304
@app.context_processor
305
def default():
306
username = flask.session.get("username")
307
308
return {"loggedInUser": username}
309
310
311
@app.route("/")
312
def main():
313
return flask.render_template("home.html")
314
315
316
@app.route("/about/")
317
def about():
318
return flask.render_template("about.html", platform=platform)
319
320
321
@app.route("/settings/", methods=["GET", "POST"])
322
def settings():
323
if flask.request.method == "GET":
324
if not flask.session.get("username"):
325
flask.abort(401)
326
user = User.query.filter_by(username=flask.session.get("username")).first()
327
328
return flask.render_template("user-settings.html", user=user)
329
else:
330
user = User.query.filter_by(username=flask.session.get("username")).first()
331
332
user.displayName = flask.request.form["displayname"]
333
user.URL = flask.request.form["url"]
334
user.company = flask.request.form["company"]
335
user.companyURL = flask.request.form["companyurl"]
336
user.location = flask.request.form["location"]
337
user.showMail = flask.request.form.get("showmail", user.showMail)
338
339
db.session.commit()
340
341
flask.flash(Markup("<iconify-icon icon='mdi:check'></iconify-icon>Settings saved"), category="success")
342
return flask.redirect(f"/{flask.session.get('username')}", code=303)
343
344
345
@app.route("/accounts/", methods=["GET", "POST"])
346
def login():
347
if flask.request.method == "GET":
348
return flask.render_template("login.html")
349
else:
350
if "login" in flask.request.form:
351
username = flask.request.form["username"]
352
password = flask.request.form["password"]
353
354
user = User.query.filter_by(username=username).first()
355
356
if user and bcrypt.check_password_hash(user.passwordHashed, password):
357
flask.session["username"] = user.username
358
flask.flash(
359
Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged in as {username}"),
360
category="success")
361
return flask.redirect("/", code=303)
362
elif not user:
363
flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>User not found"),
364
category="alert")
365
return flask.render_template("login.html")
366
else:
367
flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>Invalid password"),
368
category="error")
369
return flask.render_template("login.html")
370
if "signup" in flask.request.form:
371
username = flask.request.form["username"]
372
password = flask.request.form["password"]
373
password2 = flask.request.form["password2"]
374
email = flask.request.form.get("email")
375
email2 = flask.request.form.get("email2") # repeat email is a honeypot
376
name = flask.request.form.get("name")
377
378
if not onlyChars(username, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"):
379
flask.flash(Markup(
380
"<iconify-icon icon='mdi:account-error'></iconify-icon>Usernames may only contain Latin alphabet, numbers, '-' and '_'"),
381
category="error")
382
return flask.render_template("login.html")
383
384
if username in config.RESERVED_NAMES:
385
flask.flash(
386
Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>Sorry, {username} is a system path"),
387
category="error")
388
return flask.render_template("login.html")
389
390
userCheck = User.query.filter_by(username=username).first()
391
if userCheck:
392
flask.flash(
393
Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>The username {username} is taken"),
394
category="error")
395
return flask.render_template("login.html")
396
397
if password2 != password:
398
flask.flash(Markup("<iconify-icon icon='mdi:key-alert'></iconify-icon>Make sure the passwords match"),
399
category="error")
400
return flask.render_template("login.html")
401
402
user = User(username, password, email, name)
403
db.session.add(user)
404
db.session.commit()
405
flask.session["username"] = user.username
406
flask.flash(Markup(
407
f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully created and logged in as {username}"),
408
category="success")
409
return flask.redirect("/", code=303)
410
411
412
@app.route("/newrepo/", methods=["GET", "POST"])
413
def newRepo():
414
if flask.request.method == "GET":
415
return flask.render_template("new-repo.html")
416
else:
417
name = flask.request.form["name"]
418
visibility = int(flask.request.form["visibility"])
419
420
if not onlyChars(name, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"):
421
flask.flash(Markup(
422
"<iconify-icon icon='mdi:error'></iconify-icon>Repository names may only contain Latin alphabet, numbers, '-' and '_'"),
423
category="error")
424
return flask.render_template("new-repo.html")
425
426
user = User.query.filter_by(username=flask.session.get("username")).first()
427
428
repo = Repo(user, name, visibility)
429
db.session.add(repo)
430
db.session.commit()
431
432
if not os.path.exists(os.path.join(config.REPOS_PATH, repo.route)):
433
subprocess.run(["git", "init", repo.name],
434
cwd=os.path.join(config.REPOS_PATH, flask.session.get("username")))
435
436
flask.flash(Markup(f"<iconify-icon icon='mdi:folder'></iconify-icon>Successfully created repository {name}"),
437
category="success")
438
return flask.redirect(repo.route, code=303)
439
440
441
@app.route("/logout")
442
def logout():
443
flask.session.clear()
444
flask.flash(Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged out"), category="info")
445
return flask.redirect("/", code=303)
446
447
448
@app.route("/<username>/")
449
def userProfile(username):
450
user = User.query.filter_by(username=username).first()
451
repos = Repo.query.filter_by(ownerName=username, visibility=2)
452
return flask.render_template("user-profile.html", user=user, repos=repos)
453
454
455
@app.route("/<username>/<repository>/")
456
def repositoryIndex(username, repository):
457
return flask.redirect("./tree", code=302)
458
459
460
@app.route("/<username>/<repository>/raw/<branch>/<path:subpath>")
461
def repositoryRaw(username, repository, branch, subpath):
462
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("user"), username,
463
repository) is not None):
464
flask.abort(403)
465
466
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
467
468
app.logger.info(f"Loading {serverRepoLocation}")
469
470
if not os.path.exists(serverRepoLocation):
471
app.logger.error(f"Cannot load {serverRepoLocation}")
472
return flask.render_template("not-found.html"), 404
473
474
repo = git.Repo(serverRepoLocation)
475
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
476
if not repoData.defaultBranch:
477
if repo.heads:
478
repoData.defaultBranch = repo.heads[0].name
479
else:
480
return flask.render_template("empty.html",
481
remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
482
if not branch:
483
branch = repoData.defaultBranch
484
return flask.redirect(f"./{branch}", code=302)
485
486
if branch.startswith("tag:"):
487
ref = f"tags/{branch[4:]}"
488
elif branch.startswith("~"):
489
ref = branch[1:]
490
else:
491
ref = f"heads/{branch}"
492
493
ref = ref.replace("~", "/") # encode slashes for URL support
494
495
try:
496
repo.git.checkout("-f", ref)
497
except git.exc.GitCommandError:
498
return flask.render_template("not-found.html"), 404
499
500
return flask.send_from_directory(config.REPOS_PATH, os.path.join(username, repository, subpath))
501
502
503
@app.route("/info/<username>/avatar")
504
def userAvatar(username):
505
serverUserdataLocation = os.path.join(config.USERDATA_PATH, username)
506
507
if not os.path.exists(serverUserdataLocation):
508
return flask.render_template("not-found.html"), 404
509
510
return flask.send_from_directory(serverUserdataLocation, "avatar.png")
511
512
513
@app.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""})
514
@app.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""})
515
@app.route("/<username>/<repository>/tree/<branch>/<path:subpath>")
516
def repositoryTree(username, repository, branch, subpath):
517
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
518
repository) is not None):
519
flask.abort(403)
520
521
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
522
523
app.logger.info(f"Loading {serverRepoLocation}")
524
525
if not os.path.exists(serverRepoLocation):
526
app.logger.error(f"Cannot load {serverRepoLocation}")
527
return flask.render_template("not-found.html"), 404
528
529
repo = git.Repo(serverRepoLocation)
530
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
531
if not repoData.defaultBranch:
532
if repo.heads:
533
repoData.defaultBranch = repo.heads[0].name
534
else:
535
return flask.render_template("empty.html",
536
remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
537
if not branch:
538
branch = repoData.defaultBranch
539
return flask.redirect(f"./{branch}", code=302)
540
541
if branch.startswith("tag:"):
542
ref = f"tags/{branch[4:]}"
543
elif branch.startswith("~"):
544
ref = branch[1:]
545
else:
546
ref = f"heads/{branch}"
547
548
ref = ref.replace("~", "/") # encode slashes for URL support
549
550
try:
551
repo.git.checkout("-f", ref)
552
except git.exc.GitCommandError:
553
return flask.render_template("not-found.html"), 404
554
555
branches = repo.heads
556
557
allRefs = []
558
for ref in repo.heads:
559
allRefs.append((ref, "head"))
560
for ref in repo.tags:
561
allRefs.append((ref, "tag"))
562
563
if os.path.isdir(os.path.join(serverRepoLocation, subpath)):
564
files = []
565
blobs = []
566
567
for entry in os.listdir(os.path.join(serverRepoLocation, subpath)):
568
if not os.path.basename(entry) == ".git":
569
files.append(os.path.join(subpath, entry))
570
571
infos = []
572
573
for file in files:
574
path = os.path.join(serverRepoLocation, file)
575
mimetype = guessMIME(path)
576
577
text = gitCommand(serverRepoLocation, None, "log", "--format='%H\n'", file).decode()
578
579
sha = text.split("\n")[0]
580
identifier = f"/{username}/{repository}/{sha}"
581
lastCommit = Commit.query.filter_by(identifier=identifier).first()
582
583
info = {
584
"name": os.path.basename(file),
585
"serverPath": path,
586
"relativePath": file,
587
"link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file),
588
"size": humanSize(os.path.getsize(path)),
589
"mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}",
590
"commit": lastCommit,
591
"shaSize": 7,
592
}
593
594
specialIcon = config.matchIcon(os.path.basename(file))
595
if specialIcon:
596
info["icon"] = specialIcon
597
elif os.path.isdir(path):
598
info["icon"] = config.folderIcon
599
elif mimetypes.guess_type(path)[0] in config.fileIcons:
600
info["icon"] = config.fileIcons[mimetypes.guess_type(path)[0]]
601
else:
602
info["icon"] = config.unknownIcon
603
604
if os.path.isdir(path):
605
infos.insert(0, info)
606
else:
607
infos.append(info)
608
609
return flask.render_template(
610
"repo-tree.html",
611
username=username,
612
repository=repository,
613
files=infos,
614
subpath=os.path.join("/", subpath),
615
branches=allRefs,
616
current=branch,
617
remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"
618
)
619
else:
620
path = os.path.join(serverRepoLocation, subpath)
621
622
if not os.path.exists(path):
623
return flask.render_template("not-found.html"), 404
624
625
mimetype = guessMIME(path)
626
mode = mimetype.split("/", 1)[0]
627
size = humanSize(os.path.getsize(path))
628
629
specialIcon = config.matchIcon(os.path.basename(path))
630
if specialIcon:
631
icon = specialIcon
632
elif os.path.isdir(path):
633
icon = config.folderIcon
634
elif mimetypes.guess_type(path)[0] in config.fileIcons:
635
icon = config.fileIcons[mimetypes.guess_type(path)[0]]
636
else:
637
icon = config.unknownIcon
638
639
contents = None
640
if mode == "text":
641
contents = convertToHTML(path)
642
643
return flask.render_template(
644
"repo-file.html",
645
username=username,
646
repository=repository,
647
file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath),
648
branches=allRefs,
649
current=branch,
650
mode=mode,
651
mimetype=mimetype,
652
detailedtype=magic.from_file(path),
653
size=size,
654
icon=icon,
655
subpath=os.path.join("/", subpath),
656
basename=os.path.basename(path),
657
contents=contents,
658
remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"
659
)
660
661
662
@app.route("/<username>/<repository>/forum/")
663
def repositoryForum(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, 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
return flask.render_template(
683
"repo-forum.html",
684
username=username,
685
repository=repository,
686
repoData=repoData,
687
relationships=relationships,
688
repo=repo,
689
userRelationship=userRelationship,
690
Post=Post,
691
remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"
692
)
693
694
695
@app.route("/<username>/<repository>/forum/new", methods=["POST"])
696
def repositoryForumAdd(username, repository):
697
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
698
repository) is not None):
699
flask.abort(403)
700
701
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
702
703
app.logger.info(f"Loading {serverRepoLocation}")
704
705
if not os.path.exists(serverRepoLocation):
706
app.logger.error(f"Cannot load {serverRepoLocation}")
707
return flask.render_template("not-found.html"), 404
708
709
repo = git.Repo(serverRepoLocation)
710
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
711
user = User.query.filter_by(username=flask.session.get("username")).first()
712
relationships = RepoAccess.query.filter_by(repo=repoData)
713
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
714
715
post = Post(user, repoData, None, flask.request.form["subject"], flask.request.form["message"])
716
717
db.session.add(post)
718
db.session.commit()
719
720
return flask.redirect(flask.url_for("repositoryForumThread", username=username, repository=repository, postID=post.number), code=303)
721
722
723
@app.route("/<username>/<repository>/forum/<int:postID>")
724
def repositoryForumThread(username, repository, postID):
725
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
726
repository) is not None):
727
flask.abort(403)
728
729
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
730
731
app.logger.info(f"Loading {serverRepoLocation}")
732
733
if not os.path.exists(serverRepoLocation):
734
app.logger.error(f"Cannot load {serverRepoLocation}")
735
return flask.render_template("not-found.html"), 404
736
737
repo = git.Repo(serverRepoLocation)
738
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
739
user = User.query.filter_by(username=flask.session.get("username")).first()
740
relationships = RepoAccess.query.filter_by(repo=repoData)
741
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
742
743
return flask.render_template(
744
"repo-forum-thread.html",
745
username=username,
746
repository=repository,
747
repoData=repoData,
748
relationships=relationships,
749
repo=repo,
750
userRelationship=userRelationship,
751
Post=Post,
752
postID=postID,
753
maxPostNesting=4,
754
remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"
755
)
756
757
758
@app.route("/<username>/<repository>/forum/<int:postID>/reply", methods=["POST"])
759
def repositoryForumReply(username, repository, postID):
760
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
761
repository) is not None):
762
flask.abort(403)
763
764
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
765
766
app.logger.info(f"Loading {serverRepoLocation}")
767
768
if not os.path.exists(serverRepoLocation):
769
app.logger.error(f"Cannot load {serverRepoLocation}")
770
return flask.render_template("not-found.html"), 404
771
772
repo = git.Repo(serverRepoLocation)
773
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
774
user = User.query.filter_by(username=flask.session.get("username")).first()
775
relationships = RepoAccess.query.filter_by(repo=repoData)
776
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
777
if not user:
778
flask.abort(401)
779
780
parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first()
781
post = Post(user, repoData, parent, flask.request.form["subject"], flask.request.form["message"])
782
783
db.session.add(post)
784
post.updateDate()
785
db.session.commit()
786
787
return flask.redirect(flask.url_for("repositoryForumThread", username=username, repository=repository, postID=postID), code=303)
788
789
790
@app.route("/<username>/<repository>/forum/<int:postID>/voteup", defaults={"score": 1})
791
@app.route("/<username>/<repository>/forum/<int:postID>/votedown", defaults={"score": -1})
792
@app.route("/<username>/<repository>/forum/<int:postID>/votes", defaults={"score": 0})
793
def repositoryForumVote(username, repository, postID, score):
794
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
795
repository) is not None):
796
flask.abort(403)
797
798
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
799
800
app.logger.info(f"Loading {serverRepoLocation}")
801
802
if not os.path.exists(serverRepoLocation):
803
app.logger.error(f"Cannot load {serverRepoLocation}")
804
return flask.render_template("not-found.html"), 404
805
806
repo = git.Repo(serverRepoLocation)
807
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
808
user = User.query.filter_by(username=flask.session.get("username")).first()
809
relationships = RepoAccess.query.filter_by(repo=repoData)
810
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
811
if not user:
812
flask.abort(401)
813
814
post = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first()
815
816
if score:
817
oldRelationship = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first()
818
if oldRelationship:
819
if score == oldRelationship.voteScore:
820
db.session.delete(oldRelationship)
821
post.voteSum -= oldRelationship.voteScore
822
else:
823
post.voteSum -= oldRelationship.voteScore
824
post.voteSum += score
825
oldRelationship.voteScore = score
826
else:
827
relationship = PostVote(user, post, score)
828
post.voteSum += score
829
db.session.add(relationship)
830
831
db.session.commit()
832
833
userVote = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first()
834
response = flask.make_response(str(post.voteSum) + " " + str(userVote.voteScore if userVote else 0))
835
response.content_type = "text/plain"
836
837
return response
838
839
840
@app.route("/<username>/<repository>/users/", methods=["GET", "POST"])
841
def repositoryUsers(username, repository):
842
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
843
repository) is not None):
844
flask.abort(403)
845
846
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
847
848
app.logger.info(f"Loading {serverRepoLocation}")
849
850
if not os.path.exists(serverRepoLocation):
851
app.logger.error(f"Cannot load {serverRepoLocation}")
852
return flask.render_template("not-found.html"), 404
853
854
repo = git.Repo(serverRepoLocation)
855
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
856
user = User.query.filter_by(username=flask.session.get("username")).first()
857
relationships = RepoAccess.query.filter_by(repo=repoData)
858
userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
859
860
if flask.request.method == "GET":
861
return flask.render_template(
862
"repo-users.html",
863
username=username,
864
repository=repository,
865
repoData=repoData,
866
relationships=relationships,
867
repo=repo,
868
userRelationship=userRelationship,
869
remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"
870
)
871
else:
872
if getPermissionLevel(flask.session.get("username"), username, repository) != 2:
873
flask.abort(401)
874
875
if flask.request.form.get("new-username"):
876
# Create new relationship
877
newUser = User.query.filter_by(username=flask.request.form.get("new-username")).first()
878
relationship = RepoAccess(newUser, repoData, flask.request.form.get("new-level"))
879
db.session.add(relationship)
880
db.session.commit()
881
if flask.request.form.get("update-username"):
882
# Create new relationship
883
updatedUser = User.query.filter_by(username=flask.request.form.get("update-username")).first()
884
relationship = RepoAccess.query.filter_by(repo=repoData, user=updatedUser).first()
885
if flask.request.form.get("update-level") == -1:
886
relationship.delete()
887
else:
888
relationship.accessLevel = flask.request.form.get("update-level")
889
db.session.commit()
890
891
return flask.redirect(app.url_for("repositoryUsers", username=username, repository=repository))
892
893
894
@app.route("/<username>/<repository>/branches/")
895
def repositoryBranches(username, repository):
896
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
897
repository) is not None):
898
flask.abort(403)
899
900
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
901
902
app.logger.info(f"Loading {serverRepoLocation}")
903
904
if not os.path.exists(serverRepoLocation):
905
app.logger.error(f"Cannot load {serverRepoLocation}")
906
return flask.render_template("not-found.html"), 404
907
908
repo = git.Repo(serverRepoLocation)
909
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
910
911
return flask.render_template(
912
"repo-branches.html",
913
username=username,
914
repository=repository,
915
repoData=repoData,
916
repo=repo,
917
remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"
918
)
919
920
921
@app.route("/<username>/<repository>/log/")
922
def repositoryLog(username, repository):
923
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
924
repository) is not None):
925
flask.abort(403)
926
927
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
928
929
app.logger.info(f"Loading {serverRepoLocation}")
930
931
if not os.path.exists(serverRepoLocation):
932
app.logger.error(f"Cannot load {serverRepoLocation}")
933
return flask.render_template("not-found.html"), 404
934
935
repo = git.Repo(serverRepoLocation)
936
repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
937
commits = Commit.query.filter_by(repo=repoData)
938
939
return flask.render_template(
940
"repo-log.html",
941
username=username,
942
repository=repository,
943
repoData=repoData,
944
repo=repo,
945
commits=commits,
946
remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"
947
)
948
949
950
@app.route("/<username>/<repository>/settings/")
951
def repositorySettings(username, repository):
952
if getPermissionLevel(flask.session.get("username"), username, repository) != 2:
953
flask.abort(401)
954
955
return flask.render_template("repo-settings.html", username=username, repository=repository)
956
957
958
@app.errorhandler(404)
959
def e404(error):
960
return flask.render_template("not-found.html"), 404
961
962
963
@app.errorhandler(401)
964
def e401(error):
965
return flask.render_template("unauthorised.html"), 401
966
967
968
@app.errorhandler(403)
969
def e403(error):
970
return flask.render_template("forbidden.html"), 403
971
972
973
@app.errorhandler(418)
974
def e418(error):
975
return flask.render_template("teapot.html"), 418
976
977
978
@app.errorhandler(405)
979
def e405(error):
980
return flask.render_template("method-not-allowed.html"), 405
981
982
983
if __name__ == "__main__":
984
app.run(debug=True, port=8080, host="0.0.0.0")
985