vesta-web 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. vesta/__init__.py +486 -0
  2. vesta/db/UNIAUTH.sql +58 -0
  3. vesta/db/db_service.py +253 -0
  4. vesta/emptyProject/.gitignore +16 -0
  5. vesta/emptyProject/.gitlab-ci.yml +12 -0
  6. vesta/emptyProject/CONTRIBUTING.md +45 -0
  7. vesta/emptyProject/LICENSE.md +3 -0
  8. vesta/emptyProject/README.md +44 -0
  9. vesta/emptyProject/crons/exemple.py +13 -0
  10. vesta/emptyProject/db/schema.sql +0 -0
  11. vesta/emptyProject/install.sh +17 -0
  12. vesta/emptyProject/mailing/mailReset.html +1 -0
  13. vesta/emptyProject/mailing/mailVerif.html +1 -0
  14. vesta/emptyProject/misc/nginx_local +15 -0
  15. vesta/emptyProject/misc/nginx_prod +29 -0
  16. vesta/emptyProject/misc/nginx_prod_ws +29 -0
  17. vesta/emptyProject/misc/vesta.service +11 -0
  18. vesta/emptyProject/requirements.txt +0 -0
  19. vesta/emptyProject/server_static.ini +6 -0
  20. vesta/emptyProject/server_static.py +15 -0
  21. vesta/emptyProject/server_static_orm.ini +13 -0
  22. vesta/emptyProject/server_static_orm.py +15 -0
  23. vesta/emptyProject/server_vesta.ini +27 -0
  24. vesta/emptyProject/server_vesta.py +17 -0
  25. vesta/emptyProject/server_vesta_ws.ini +31 -0
  26. vesta/emptyProject/server_vesta_ws.py +56 -0
  27. vesta/emptyProject/static/home/auth.css +101 -0
  28. vesta/emptyProject/static/home/auth.html +38 -0
  29. vesta/emptyProject/static/home/auth.js +159 -0
  30. vesta/emptyProject/static/home/reset.html +33 -0
  31. vesta/emptyProject/static/home/verif.html +33 -0
  32. vesta/emptyProject/static/main.html +14 -0
  33. vesta/emptyProject/static/main.mjs +9 -0
  34. vesta/emptyProject/static/mobileUiManifest.mjs +3 -0
  35. vesta/emptyProject/static/style.css +0 -0
  36. vesta/emptyProject/static/translations/en.mjs +2 -0
  37. vesta/emptyProject/static/translations/fr.mjs +2 -0
  38. vesta/emptyProject/static/translations/translation.mjs +51 -0
  39. vesta/emptyProject/static/ws/onMessage.mjs +21 -0
  40. vesta/emptyProject/tests/example/foo.py +7 -0
  41. vesta/http/baseServer.py +257 -0
  42. vesta/http/error.py +5 -0
  43. vesta/http/redirect.py +5 -0
  44. vesta/http/response.py +85 -0
  45. vesta/mailing/mailing_service.py +126 -0
  46. vesta/scripts/initDB.py +52 -0
  47. vesta/scripts/install.py +76 -0
  48. vesta/scripts/testsRun.py +83 -0
  49. vesta/scripts/utils.py +84 -0
  50. vesta/scripts/vesta.py +225 -0
  51. vesta_web-1.1.0.dist-info/METADATA +55 -0
  52. vesta_web-1.1.0.dist-info/RECORD +55 -0
  53. vesta_web-1.1.0.dist-info/WHEEL +4 -0
  54. vesta_web-1.1.0.dist-info/entry_points.txt +2 -0
  55. vesta_web-1.1.0.dist-info/licenses/LICENSE.md +5 -0
@@ -0,0 +1,257 @@
1
+ import fastwsgi
2
+ import inspect
3
+
4
+ import os
5
+ import time
6
+ import json
7
+ import hashlib
8
+ import base64
9
+
10
+ import re
11
+ import urllib
12
+
13
+ #requests modules
14
+ import multipart as mp
15
+ from io import BytesIO
16
+
17
+ from configparser import ConfigParser
18
+
19
+ from vesta.http import response
20
+ from vesta.http import error
21
+ from vesta.http import redirect
22
+ from vesta.db import db_service as db
23
+ Response = response.Response
24
+ HTTPRedirect = redirect.HTTPRedirect
25
+ HTTPError = error.HTTPError
26
+
27
+ RE_URL = re.compile(r"[\&]")
28
+ RE_PARAM = re.compile(r"[\=]")
29
+
30
+ routes = {}
31
+
32
+ from colorama import Fore, Style
33
+ from colorama import init as colorama_init
34
+ colorama_init()
35
+
36
+ class BaseServer:
37
+ features = {}
38
+
39
+ def __init__(self, path, configFile, noStart=False):
40
+ print(Fore.GREEN,"[INFO] starting Vesta server...")
41
+ self.path = path
42
+
43
+ self.importConf(configFile)
44
+
45
+ if noStart:
46
+ return
47
+
48
+ self.start()
49
+
50
+ #----------------------------HTTP SERVER------------------------------------
51
+ def expose(func):
52
+ def wrapper(self, *args, **kwargs):
53
+
54
+ res = func(self, *args, **kwargs)
55
+ # print("[DEBUG] res : ", res)
56
+
57
+ self.response.ok()
58
+ if res:
59
+
60
+ return res.encode()
61
+ else:
62
+ return "".encode()
63
+
64
+ name = func.__name__
65
+ if func.__name__ == "index":
66
+ name = "/"
67
+ elif func.__name__ == "default":
68
+ name = "default"
69
+ else:
70
+ name = "/" + func.__name__
71
+
72
+ routes[name] = {"params": inspect.signature(func).parameters, "target": wrapper}
73
+ return wrapper
74
+
75
+ def saveFile(self, content, name="",ext=None, category=None):
76
+ content = content.split(",")
77
+ extension = content[0].split("/")[1].split(";")[0]
78
+ content = base64.b64decode(content[1])
79
+
80
+ if not name :
81
+ hash_object = hashlib.sha256(content)
82
+ hex_dig = hash_object.hexdigest()
83
+
84
+ name = hex_dig
85
+
86
+ prefix = self.path + "/static/attachments/"
87
+ if category:
88
+ name = category + "/" + name
89
+ if ext :
90
+ name = name + "." + ext
91
+
92
+ with open(prefix+name, 'wb') as f:
93
+ f.write(content)
94
+ return name
95
+
96
+ def parseCookies(self, cookieStr):
97
+ if not cookieStr:
98
+ return
99
+ cookies = {}
100
+ for cookie in cookieStr.split(';'):
101
+ key, value = cookie.split('=')
102
+ cookies[key.strip()] = value
103
+ return cookies
104
+
105
+
106
+ def parseRequest(self,environ):
107
+ self.response.cookies = self.parseCookies(environ.get('HTTP_COOKIE'))
108
+
109
+ if environ.get('CONTENT_TYPE'):
110
+ content_type = environ.get('CONTENT_TYPE').strip().split(";")
111
+ else:
112
+ content_type = ["text/html"]
113
+
114
+ args = {}
115
+ if environ.get('QUERY_STRING'):
116
+ query = re.split(RE_URL, environ['QUERY_STRING'])
117
+ for i in range(0, len(query)):
118
+ query[i] = re.split(RE_PARAM, query[i])
119
+ args[query[i][0]] = urllib.parse.unquote(query[i][1])
120
+ if content_type[0] == "multipart/form-data":
121
+ length = int(environ.get('CONTENT_LENGTH'))
122
+ body = environ['wsgi.input'].read(length)
123
+ sep = content_type[1].split("=")[1]
124
+ body = mp.MultipartParser(BytesIO(body), sep.encode('utf-8'))
125
+ for part in body.parts():
126
+ args[part.name] = part.value
127
+ if content_type[0] == "application/json":
128
+ length = int(environ.get('CONTENT_LENGTH'))
129
+ body = environ['wsgi.input'].read(length)
130
+ body = json.loads(body)
131
+ for key in body:
132
+ args[key] = body[key]
133
+
134
+ return args
135
+
136
+
137
+ def tryDefault(self, environ, target):
138
+ print(Fore.WHITE,"[INFO] Vesta - using default route")
139
+
140
+ args = self.parseRequest(environ)
141
+ args["target"] = target
142
+ try:
143
+ return routes["default"]["target"](self, **args)
144
+ except (HTTPError, HTTPRedirect):
145
+ return self.response.encode()
146
+ except Exception as e:
147
+ return self.handleUnexpected(e)
148
+
149
+
150
+ def onrequest(self, environ, start_response):
151
+ self.response = Response(start_response=start_response)
152
+ print(Fore.WHITE,"[INFO] Vesta - request received :'", str(environ['PATH_INFO']) + "'" + " with "+ str(environ.get('QUERY_STRING')))
153
+ target = environ['PATH_INFO']
154
+
155
+ if routes.get(target):
156
+ args = self.parseRequest(environ)
157
+
158
+ try:
159
+ if len(args) == 0:
160
+ return routes[target]["target"](self)
161
+ return routes[target]["target"](self, **args)
162
+ except (HTTPError, HTTPRedirect):
163
+ return self.response.encode()
164
+ except Exception as e:
165
+ return self.handleUnexpected(e)
166
+ else:
167
+ if routes.get("default"):
168
+ return self.tryDefault(environ, target)
169
+ self.response.code = 404
170
+ self.response.ok()
171
+ return self.response.encode()
172
+
173
+ def handleUnexpected(self, e):
174
+ print(Fore.RED,"[ERROR] Vesta - UNEXPECTED ERROR :", e)
175
+ self.response.code = 500
176
+ self.response.ok()
177
+ self.response.content = str(e)
178
+ tb = e.__traceback__
179
+ while tb is not None:
180
+ frame = tb.tb_frame
181
+ lineno = tb.tb_lineno
182
+ filename = frame.f_code.co_filename
183
+ funcname = frame.f_code.co_name
184
+ print(f' File "{filename}", line {lineno}, in {funcname}')
185
+ if self.features.get("debug"):
186
+ self.response.content += f' File "{filename}", line {lineno}, in {funcname}'
187
+ tb = tb.tb_next
188
+ return self.response.encode()
189
+
190
+ def onStart(self):
191
+ pass
192
+
193
+ #--------------------------GENERAL USE METHODS------------------------------
194
+
195
+ def importConf(self, configFile):
196
+ self.config = ConfigParser()
197
+ try:
198
+ self.config.read(self.path + configFile)
199
+ print(Fore.GREEN,"[INFO] Vesta - config at " + self.path + configFile + " loaded")
200
+ except Exception:
201
+ print(Fore.RED,"[ERROR] Vesta - Please create a config file")
202
+
203
+ def start(self):
204
+ self.fileCache = {}
205
+
206
+ if self.features.get("errors"):
207
+ for code, page in self.features["errors"].items():
208
+ Response.ERROR_PAGES[code] = self.path + page
209
+
210
+ if self.features.get("orm") == True:
211
+ self.db = db.DB(user=self.config.get('DB', 'DB_USER'), password=self.config.get('DB', 'DB_PASSWORD'),
212
+ host=self.config.get('DB', 'DB_HOST'), port=int(self.config.get('DB', 'DB_PORT')),
213
+ db=self.config.get('DB', 'DB_NAME'))
214
+
215
+
216
+ self.onStart()
217
+
218
+ fastwsgi.server.nowait = 1
219
+ fastwsgi.server.hook_sigint = 1
220
+
221
+ print(Fore.GREEN,"[INFO] Vesta - server running on PID:", os.getpid())
222
+ fastwsgi.server.init(app=self.onrequest, host=self.config.get('server', 'IP'),
223
+ port=int(self.config.get('server', 'PORT')))
224
+ while True:
225
+ code = fastwsgi.server.run()
226
+ if code != 0:
227
+ break
228
+ time.sleep(0)
229
+ self.close()
230
+
231
+ def close(self):
232
+ print(Fore.GREEN,"[INFO] SIGTERM/SIGINT received")
233
+ fastwsgi.server.close()
234
+ print(Fore.GREEN,"[INFO] SERVER STOPPED")
235
+ exit()
236
+
237
+ def file(self, path, responseFile=True):
238
+ if responseFile:
239
+ self.response.type = "html"
240
+ self.response.headers = [('Content-Type', 'text/html')]
241
+ file = self.fileCache.get(path)
242
+ if file:
243
+ return file
244
+ else:
245
+ file = open(path)
246
+ content = file.read()
247
+ file.close()
248
+ self.fileCache[path] = content
249
+ return content
250
+
251
+ def start_ORM(self):
252
+ if self.features.get("orm") == True:
253
+ self.db = db.DB(user=self.config.get('DB', 'DB_USER'), password=self.config.get('DB', 'DB_PASSWORD'),
254
+ host=self.config.get('DB', 'DB_HOST'), port=int(self.config.get('DB', 'DB_PORT')),
255
+ db=self.config.get('DB', 'DB_NAME'))
256
+ else:
257
+ raise Exception("ORM not enabled")
vesta/http/error.py ADDED
@@ -0,0 +1,5 @@
1
+ class HTTPError(Exception):
2
+ def __init__(self, response, code=500, message="Unexpected"):
3
+ response.code = code
4
+ response.content = message
5
+ response.ok()
vesta/http/redirect.py ADDED
@@ -0,0 +1,5 @@
1
+ class HTTPRedirect(Exception):
2
+ def __init__(self, response, target):
3
+ response.code = 302
4
+ response.headers.append(('Location', target))
5
+ response.ok()
vesta/http/response.py ADDED
@@ -0,0 +1,85 @@
1
+ import datetime
2
+
3
+ class Response:
4
+ CODES = {200: "200 OK", 400: "400 Bad Request", 403: "403 Forbidden", 404: "404 Not Found", 500: "500 Server Error", 302: "302 Redirect"}
5
+ ERROR_PAGES = {}
6
+
7
+ def __init__(self, start_response, code=200, type="html"):
8
+ self.cookies = {}
9
+ self.type = type
10
+ self.headers = [('Content-Type', 'text/' + type),('Cache-Control', 'no-cache'), ('Server', 'mag v1 Harpie')]
11
+ self.code = code
12
+ self.start_response = start_response
13
+ self.content = ""
14
+
15
+ def ok(self):
16
+ if self.code != 200 and self.code != 302:
17
+ if self.code in self.ERROR_PAGES.keys():
18
+ self.type = "html"
19
+ self.headers = [('Content-Type', 'text/html')]
20
+ file = open(self.ERROR_PAGES[self.code])
21
+ self.content = file.read()
22
+ file.close()
23
+ else:
24
+ self.type = "plain"
25
+ self.headers = [('Content-Type', 'text/plain')]
26
+ self.start_response(self.CODES.get(self.code, "500 UNEXPECTED"), self.headers)
27
+
28
+ def encode(self):
29
+ # print("[INFO] encoding response : ", self.content)
30
+
31
+ if self.type == "plain":
32
+ return (self.CODES[self.code] + " " + self.content).encode()
33
+ return str(self.content).encode()
34
+
35
+ def set_cookie(self, name, value, exp=None, samesite=None, secure=False, httponly=False):
36
+ """Set a response cookie for the client.
37
+ name
38
+ the name of the cookie.
39
+
40
+ exp
41
+ the expiration timeout for the cookie. If 0 or other boolean
42
+ False, no 'expires' param will be set, and the cookie will be a
43
+ "session cookie" which expires when the browser is closed.
44
+
45
+ samesite
46
+ The 'SameSite' attribute of the cookie. If None (the default)
47
+ the cookie 'samesite' value will not be set. If 'Strict' or
48
+ 'Lax', the cookie 'samesite' value will be set to the given value.
49
+
50
+ secure
51
+ if False (the default) the cookie 'secure' value will not
52
+ be set. If True, the cookie 'secure' value will be set (to 1).
53
+
54
+ httponly
55
+ If False (the default) the cookie 'httponly' value will not be set.
56
+ If True, the cookie 'httponly' value will be set (to 1).
57
+
58
+ """
59
+
60
+ # Calculate expiration time
61
+ expires = None
62
+ if exp:
63
+ if exp['unit'] == "days":
64
+ expires = datetime.datetime.now() + datetime.timedelta(days=exp['value'])
65
+ elif exp['unit'] == "minutes":
66
+ expires = datetime.datetime.now() + datetime.timedelta(minutes=exp['value'])
67
+ expires = expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT")
68
+
69
+ # Construct cookie string
70
+ cookie_parts = [f"{name}={value}"]
71
+ if expires:
72
+ cookie_parts.append(f"Expires={expires}")
73
+ if samesite:
74
+ cookie_parts.append(f"SameSite={samesite}")
75
+ if secure:
76
+ cookie_parts.append("Secure")
77
+ if httponly:
78
+ cookie_parts.append("HttpOnly")
79
+ cookie_string = "; ".join(cookie_parts)
80
+
81
+ # Add cookie to headers
82
+ self.headers.append(('Set-Cookie', cookie_string))
83
+
84
+ def del_cookie(self, name):
85
+ self.set_cookie(name, "", exp={"value": 0, "unit": "days"})
@@ -0,0 +1,126 @@
1
+ import smtplib
2
+ from email.mime.multipart import MIMEMultipart
3
+ from email.mime.text import MIMEText
4
+ import email.utils
5
+ import dkim
6
+
7
+
8
+ class Mailing:
9
+ def __init__(self, host, port, address, password, serviceName, path):
10
+ try:
11
+ self.name = serviceName
12
+ self.host = host
13
+ self.port = port
14
+ self.password = password
15
+ self.smtp = smtplib.SMTP(host, port)
16
+ self.smtp.ehlo() # send the extended hello to our server
17
+ self.smtp.starttls()
18
+ self.address = address
19
+ self.smtp.login(address, password)
20
+ self.templates = {}
21
+ self.path = path
22
+ with open(self.path + "/mailing/dkim.txt") as fh:
23
+ self.private = fh.read()
24
+ print("[Vesta - mails] server connected")
25
+ except smtplib.SMTPException as e:
26
+ print(f"Error connecting to mailing server : {e}")
27
+ self.smtp.quit()
28
+
29
+ def restart(self):
30
+ self.smtp = smtplib.SMTP(self.host, self.port)
31
+ self.smtp.ehlo() # send the extended hello to our server
32
+ self.smtp.starttls()
33
+ self.smtp.login(self.address, self.password)
34
+ print("[Vesta - mails] server reconnected")
35
+
36
+ def is_connected(conn):
37
+ try:
38
+ status = conn.noop()[0]
39
+ except: # smtplib.SMTPServerDisconnected
40
+ status = -1
41
+ return True if status == 250 else False
42
+
43
+ def sendMail(self, content):
44
+ if not self.is_connected():
45
+ self.restart()
46
+ try:
47
+ self.smtp.send_message(content)
48
+ print("[Vesta - mails] mail sent")
49
+ except smtplib.SMTPException as e:
50
+ print(f"Error sending a mail : {e}")
51
+
52
+ def sendTemplate(self,template,target,subject,text,values):
53
+ try:
54
+ self.templates[template]
55
+ except:
56
+ f = open(self.path + "/mailing/" + template, "r")
57
+ self.templates[template] = f.read()
58
+ f.close()
59
+
60
+ mail_confirmation = MIMEMultipart('alternative')
61
+ mail_confirmation['Subject'] = subject
62
+ mail_confirmation['From'] = self.name + " <" + self.address + ">"
63
+ mail_confirmation['Message-ID'] = email.utils.make_msgid(domain='carbonlab.dev')
64
+ mail_confirmation['Date'] = email.utils.formatdate()
65
+ part1 = MIMEText(text, 'plain')
66
+ part2 = MIMEText(self.templates[template].format(values), 'html')
67
+ mail_confirmation.attach(part1)
68
+ mail_confirmation.attach(part2)
69
+ mail_confirmation['To'] = target # ATTENTION CE N'EST PAS UNE COPIE CACHEE
70
+ self.signMail(mail_confirmation)
71
+ self.sendMail(mail_confirmation)
72
+
73
+ def signMail(self, mail):
74
+ headers = ["To", "From", "Subject"]
75
+ sig = dkim.sign(
76
+ message=mail.as_bytes(),
77
+ selector="1686741263.carbonlab".encode(),
78
+ domain="carbonlab.dev".encode(),
79
+ privkey=self.private.encode(),
80
+ include_headers=headers, )
81
+ mail["DKIM-Signature"] = sig[len("DKIM-Signature: "):].decode()
82
+
83
+ def sendConfirmation(self, target, OTP):
84
+ try:
85
+ self.template_confirmation
86
+ except:
87
+ f = open(self.path + "/mailing/mailVerif.html", "r")
88
+ self.template_confirmation = f.read()
89
+ f.close()
90
+
91
+ mail_confirmation = MIMEMultipart('alternative')
92
+ mail_confirmation['Subject'] = "🌱 Confirmez votre adresse ! 📨"
93
+ mail_confirmation['From'] = self.name + " <" + self.address + ">"
94
+ mail_confirmation['Message-ID'] = email.utils.make_msgid(domain='carbonlab.dev')
95
+ mail_confirmation['Date'] = email.utils.formatdate()
96
+ text = "Confirm your " + self.name + " account : " + OTP
97
+ part1 = MIMEText(text, 'plain')
98
+ part2 = MIMEText(self.template_confirmation.format(OTP), 'html')
99
+ mail_confirmation.attach(part1)
100
+ mail_confirmation.attach(part2)
101
+ mail_confirmation['To'] = target # ATTENTION CE N'EST PAS UNE COPIE CACHEE
102
+ self.signMail(mail_confirmation)
103
+ self.sendMail(mail_confirmation)
104
+
105
+ def sendOrgInvite(self, target, company):
106
+ try:
107
+ self.template_org_invitation
108
+ except:
109
+ f = open(self.path + "/mailing/mailInvite.html", "r")
110
+ self.template_org_invitation = f.read()
111
+ f.close()
112
+ self.mail_invitation = MIMEMultipart('alternative')
113
+ self.mail_invitation['Subject'] = "🔔 Rejoignez " + company + " 🔔"
114
+ self.mail_invitation['From'] = self.name + " <" + self.address + ">"
115
+
116
+ self.mail_invitation['Message-ID'] = email.utils.make_msgid(domain='carbonlab.dev')
117
+ self.mail_invitation['Date'] = email.utils.formatdate()
118
+ text = "Inscrivez vous sur " + self.name + " pour rejoindre " + company
119
+ part1 = MIMEText(text, 'plain')
120
+ part2 = MIMEText(self.template_org_invitation.format(company,company), 'html')
121
+ self.mail_invitation.attach(part1)
122
+ self.mail_invitation.attach(part2)
123
+ self.mail_invitation['To'] = target # ATTENTION CE N'EST PAS UNE COPIE CACHEE
124
+ self.signMail(self.mail_invitation)
125
+
126
+ self.sendMail(self.mail_invitation)
@@ -0,0 +1,52 @@
1
+ from vesta import Server
2
+ from os.path import abspath, dirname
3
+ import sys
4
+ from psycopg import sql
5
+
6
+ PATH = dirname(abspath(__file__))
7
+
8
+
9
+ class DBInitializer(Server):
10
+ def initUniauth(self):
11
+ if sys.argv[1].upper() == 'Y':
12
+ self.uniauth.initUniauth()
13
+
14
+ def initDB(self):
15
+ self.referenceUniauth()
16
+ self.db.cur.execute(open(self.path + "/db/schema.sql", "r").read())
17
+ self.db.conn.commit()
18
+
19
+ def referenceUniauth(self):
20
+ self.db.cur.execute("CREATE EXTENSION if not exists postgres_fdw;")
21
+ self.db.cur.execute(
22
+ sql.SQL("""
23
+ CREATE SERVER if not exists uniauth
24
+ FOREIGN DATA WRAPPER postgres_fdw
25
+ OPTIONS (host {}, port {}, dbname {});
26
+ """).format(
27
+ sql.Literal(self.config.get('UNIAUTH', 'DB_HOST')),
28
+ sql.Literal(self.config.get('UNIAUTH', 'DB_PORT')),
29
+ sql.Literal(self.config.get('UNIAUTH', 'DB_NAME'))
30
+ ))
31
+
32
+ self.db.cur.execute(
33
+ sql.SQL("""
34
+ CREATE USER MAPPING if not exists FOR CURRENT_USER SERVER uniauth
35
+ OPTIONS (user '%s', password '%s');
36
+ """).format(
37
+ sql.Literal(self.config.get('DB', 'DB_USER')),
38
+ sql.Literal(self.config.get('DB', 'DB_PASSWORD'))
39
+ ))
40
+ self.db.cur.execute(
41
+ """
42
+ CREATE FOREIGN TABLE if not exists account (id bigserial NOT NULL)
43
+ SERVER uniauth
44
+ OPTIONS (schema_name 'public', table_name 'account');
45
+ """)
46
+
47
+
48
+
49
+ initializer = DBInitializer(path=PATH[:-3], configFile="/server.ini", noStart=True)
50
+ initializer.initUniauth()
51
+ initializer.initDB()
52
+ print("DB ready")
@@ -0,0 +1,76 @@
1
+ import sys
2
+ from configparser import ConfigParser
3
+ from os.path import abspath, dirname
4
+ import subprocess
5
+
6
+ PATH = dirname(abspath(__file__))
7
+
8
+
9
+ class Installer:
10
+ def __init__(self, configFile, arg="all"):
11
+ self.importConf(configFile)
12
+ self.uniauth = "N"
13
+ self.name = self.config.get("server", "SERVICE_NAME").replace(" ", "_").upper()
14
+
15
+ if arg == "all" or arg == "a":
16
+ self.installAll()
17
+ elif arg == "db":
18
+ self.installDB()
19
+ elif arg == "uniauth":
20
+ self.installUniauth()
21
+ elif arg == "vesta":
22
+ self.resetVesta()
23
+ elif arg == "reset":
24
+ self.resetDB()
25
+ elif arg == "service":
26
+ self.installService()
27
+ elif arg == "cron":
28
+ self.setupCrons()
29
+
30
+ def ex(self, command):
31
+ subprocess.run(command, shell=True, check=True)
32
+
33
+ def installDB(self):
34
+ print("----DB----")
35
+ self.ex("sudo -u postgres createdb " + self.config.get("DB", "DB_NAME"))
36
+ self.ex("sudo -u postgres createuser " + self.config.get("DB", "DB_USER") + " -s --pwprompt ")
37
+
38
+ def resetVesta(self):
39
+ self.ex("pip uninstall vesta")
40
+ self.ex("pip install git+https://gitlab.com/Louciole/vesta.git/")
41
+
42
+ def installUniauth(self):
43
+ print("----UNIAUTH----")
44
+ while True:
45
+ uniauth = input("Do you want to create a uniauth database? (y/n)")
46
+ if uniauth.upper() == 'Y' or uniauth.upper() == 'N':
47
+ self.uniauth = uniauth.upper()
48
+ break
49
+
50
+ if self.uniauth == 'Y':
51
+ self.ex("sudo -u postgres createdb " + self.config.get("UNIAUTH", "DB_NAME"))
52
+
53
+ def initDB(self):
54
+ self.ex("python3 ./db/initDB.py " + self.uniauth)
55
+
56
+ def resetDB(self):
57
+ self.ex("sudo -u postgres dropdb " + self.config.get("DB", "DB_NAME"))
58
+ self.initDB()
59
+
60
+ def installAll(self):
61
+ self.installNginx()
62
+ self.installDB()
63
+ self.installUniauth()
64
+ self.initDB()
65
+
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+ if len(sys.argv) > 1:
74
+ Installer(PATH + "/server.ini", sys.argv[1])
75
+ else:
76
+ Installer(PATH + "/server.ini")
@@ -0,0 +1,83 @@
1
+ import os
2
+ import sys
3
+ import importlib
4
+ from colorama import Fore, Style, init
5
+
6
+ def run_file(file_path):
7
+ """Runs a single Python test file."""
8
+ # Extract the module name from the file path
9
+ readable_path = file_path.strip(test_dir)
10
+
11
+ # Import the test module
12
+ try:
13
+ module_path = os.path.splitext(file_path[2:])[0]
14
+ module_name = module_path.replace(os.sep, '.')
15
+ test_module = importlib.import_module(module_name)
16
+ except ModuleNotFoundError as e:
17
+ print(f"Failed to import test module '{module_name}': {e}")
18
+ return False
19
+
20
+ # Look for a 'run' function within the module (assuming tests are run from this function)
21
+ if hasattr(test_module, 'run'):
22
+ try:
23
+ result = test_module.run()
24
+ for res in result:
25
+ if res[1] == False:
26
+ print(Fore.RED +f"FAILED: '{res[0]}' @{readable_path}")
27
+ return False
28
+ else:
29
+ print(Fore.GREEN +f"PASSED: '{res[0]}' @{readable_path}")
30
+ return True
31
+ except Exception as e:
32
+ print(f"Error running test file '{readable_path}': {e}")
33
+ else:
34
+ print(f"Test file '{readable_path}' doesn't have a 'run' function.")
35
+
36
+ return False
37
+
38
+
39
+ def run_folder(folder):
40
+ """Runs all Python test files in a directory."""
41
+ counter = (0,0)
42
+
43
+ for root, _, files in os.walk(folder):
44
+ for file in files:
45
+ if file.endswith('.py'):
46
+ file_path = os.path.join(root, file)
47
+ res = run_file(file_path)
48
+ if res == True:
49
+ counter = (counter[0]+1,counter[1]+1)
50
+ elif res == False:
51
+ counter = (counter[0],counter[1]+1)
52
+
53
+ return counter
54
+
55
+ test_dir = "./tests"
56
+
57
+ if __name__ == "__main__":
58
+ init()# Initialize colorama
59
+
60
+ counter = 0
61
+ total = 0
62
+
63
+ cwd = os.getcwd()
64
+ if cwd not in sys.path:
65
+ sys.path.insert(0, cwd)
66
+
67
+ for _, dirs, _ in os.walk(test_dir):
68
+ for folder in dirs:
69
+ if folder == '__pycache__':
70
+ continue
71
+ print("Running folder "+folder)
72
+ count = run_folder(test_dir+'/'+folder)
73
+ counter += count[0]
74
+ total += count[1]
75
+
76
+ if counter == total:
77
+ color = Fore.GREEN
78
+ exCode = 0
79
+ else:
80
+ color = Fore.RED
81
+ exCode = 1
82
+ print(color + f"{counter}/{total} tests passed")
83
+ exit(exCode)