vesta-web 1.1.2__py3-none-any.whl → 1.1.3__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.
vesta/http/baseServer.py CHANGED
@@ -1,3 +1,5 @@
1
+ """Vesta HTTP Server - Base server implementation with routing and request handling."""
2
+
1
3
  import fastwsgi
2
4
  import inspect
3
5
 
@@ -6,40 +8,58 @@ import time
6
8
  import json
7
9
  import hashlib
8
10
  import base64
9
-
10
11
  import re
11
- import urllib
12
-
13
- #requests modules
14
- import multipart as mp
12
+ import urllib.parse
13
+ import traceback
14
+ from pathlib import Path
15
+ from typing import Dict, Any, Optional, Callable
15
16
  from io import BytesIO
16
17
 
18
+ import multipart as mp
17
19
  from configparser import ConfigParser
18
20
 
19
21
  from vesta.http import response
20
22
  from vesta.http import error
21
23
  from vesta.http import redirect
22
24
  from vesta.db import db_service as db
25
+
23
26
  Response = response.Response
24
27
  HTTPRedirect = redirect.HTTPRedirect
25
28
  HTTPError = error.HTTPError
26
29
 
30
+ # Compile regex patterns once for performance
27
31
  RE_URL = re.compile(r"[\&]")
28
32
  RE_PARAM = re.compile(r"[\=]")
29
33
 
30
- routes = {}
31
-
32
34
  from colorama import Fore, Style
33
35
  from colorama import init as colorama_init
34
36
  colorama_init()
35
37
 
38
+ routes: Dict[str, Dict[str, Any]] = {}
39
+
36
40
  class BaseServer:
37
- features = {}
41
+ """Base HTTP server with routing, request parsing, and file handling capabilities."""
42
+
43
+ def __init__(self, path: str, configFile: str, noStart: bool = False):
44
+ """
45
+ Initialize the Vesta server.
46
+
47
+ Args:
48
+ path: Base path for the server
49
+ configFile: Path to configuration file
50
+ noStart: If True, don't start the server (useful for testing)
51
+
52
+ Raises:
53
+ ValueError: If path is invalid
54
+ FileNotFoundError: If config file doesn't exist
55
+ """
56
+ self.log("Starting Vesta server...")
38
57
 
39
- def __init__(self, path, configFile, noStart=False):
40
- print(Fore.GREEN,"[INFO] starting Vesta server...")
41
58
  self.path = path
42
59
 
60
+ # Instance-specific features
61
+ self.features: Dict[str, Any] = {}
62
+
43
63
  self.importConf(configFile)
44
64
 
45
65
  if noStart:
@@ -47,19 +67,25 @@ class BaseServer:
47
67
 
48
68
  self.start()
49
69
 
70
+
50
71
  #----------------------------HTTP SERVER------------------------------------
51
- def expose(func):
52
- def wrapper(self, *args, **kwargs):
72
+ def expose(func: Callable) -> Callable:
73
+ """
74
+ Decorator to expose a method as an HTTP route.
53
75
 
54
- res = func(self, *args, **kwargs)
55
- # print("[DEBUG] res : ", res)
76
+ Args:
77
+ func: Function to expose as a route
56
78
 
79
+ Returns:
80
+ Wrapped function
81
+ """
82
+ def wrapper(self, *args, **kwargs):
83
+ res = func(self, *args, **kwargs)
57
84
  self.response.ok()
58
85
  if res:
59
-
60
86
  return res.encode()
61
87
  else:
62
- return "".encode()
88
+ return b""
63
89
 
64
90
  name = func.__name__
65
91
  if func.__name__ == "index":
@@ -69,19 +95,42 @@ class BaseServer:
69
95
  else:
70
96
  name = "/" + func.__name__
71
97
 
72
- routes[name] = {"params": inspect.signature(func).parameters, "target": wrapper}
98
+ routes[name] = {
99
+ "params": inspect.signature(func).parameters,
100
+ "target": wrapper
101
+ }
73
102
  return wrapper
74
103
 
75
- def saveFile(self, content, name="",ext=None, category=None):
104
+
105
+ def saveFile(self, content: str, name: str = "", ext: Optional[str] = None,
106
+ category: Optional[str] = None) -> str:
107
+ """
108
+ Save a base64-encoded file to the attachments' directory.
109
+
110
+ Args:
111
+ content: Base64-encoded file content with data URI prefix
112
+ name: Optional filename (generated from hash if not provided)
113
+ ext: Optional file extension override
114
+ category: Optional subdirectory category
115
+
116
+ Returns:
117
+ Relative path to saved file
118
+
119
+ Raises:
120
+ ValueError: If content is invalid or file size exceeds limit
121
+ IOError: If file cannot be saved
122
+ """
76
123
  content = content.split(",")
77
124
  extension = content[0].split("/")[1].split(";")[0]
78
125
  content = base64.b64decode(content[1])
79
126
 
80
- if not name :
127
+ if not name:
81
128
  hash_object = hashlib.sha256(content)
82
129
  hex_dig = hash_object.hexdigest()
83
130
 
84
131
  name = hex_dig
132
+ else:
133
+ name = self._sanitize_filename(name)
85
134
 
86
135
  prefix = self.path + "/static/attachments/"
87
136
  if category:
@@ -93,17 +142,65 @@ class BaseServer:
93
142
  f.write(content)
94
143
  return name
95
144
 
96
- def parseCookies(self, cookieStr):
145
+
146
+ def _sanitize_filename(self, filename: str) -> str:
147
+ """
148
+ Sanitize filename to prevent path traversal and other security issues.
149
+
150
+ Args:
151
+ filename: Filename to sanitize
152
+
153
+ Returns:
154
+ Sanitized filename
155
+ """
156
+ # Remove path separators and dangerous characters
157
+ filename = filename.replace('/', '').replace('\\', '').replace('..', '')
158
+ # Keep only alphanumeric, dash, underscore
159
+ filename = re.sub(r'[^a-zA-Z0-9_-]', '', filename)
160
+ if not filename:
161
+ raise ValueError("Invalid filename after sanitization")
162
+ return filename
163
+
164
+
165
+ def parseCookies(self, cookieStr: Optional[str]) -> Dict[str, str]:
166
+ """
167
+ Parse HTTP cookie header into a dictionary.
168
+
169
+ Args:
170
+ cookieStr: Raw cookie header string
171
+
172
+ Returns:
173
+ Dictionary of cookie name-value pairs
174
+ """
97
175
  if not cookieStr:
98
- return
176
+ return {}
177
+
99
178
  cookies = {}
100
- for cookie in cookieStr.split(';'):
101
- key, value = cookie.split('=')
102
- cookies[key.strip()] = value
179
+ try:
180
+ for cookie in cookieStr.split(';'):
181
+ if '=' in cookie:
182
+ key, value = cookie.split('=', 1)
183
+ cookies[key.strip()] = value.strip()
184
+ except Exception as e:
185
+ self.logWarning(f"Error parsing cookies: {e}")
186
+
103
187
  return cookies
104
188
 
105
189
 
106
- def parseRequest(self,environ):
190
+
191
+ def parseRequest(self, environ: Dict[str, Any]) -> Dict[str, Any]:
192
+ """
193
+ Parse HTTP request into a dictionary of parameters.
194
+
195
+ Args:
196
+ environ: WSGI environ dictionary
197
+
198
+ Returns:
199
+ Dictionary of request parameters
200
+
201
+ Raises:
202
+ ValueError: If content length exceeds maximum
203
+ """
107
204
  self.response.cookies = self.parseCookies(environ.get('HTTP_COOKIE'))
108
205
 
109
206
  if environ.get('CONTENT_TYPE'):
@@ -112,30 +209,55 @@ class BaseServer:
112
209
  content_type = ["text/html"]
113
210
 
114
211
  args = {}
212
+
213
+ # Parse query string
115
214
  if environ.get('QUERY_STRING'):
116
215
  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_plus(query[i][1], encoding='utf-8')
216
+ for param in query:
217
+ parts = re.split(RE_PARAM, param)
218
+ if len(parts) == 2:
219
+ args[parts[0]] = urllib.parse.unquote_plus(parts[1], encoding='utf-8')
220
+
221
+ content_length = int(environ.get('CONTENT_LENGTH', 0))
222
+ # Parse multipart form data
120
223
  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]
224
+ try:
225
+ body = environ['wsgi.input'].read(content_length)
226
+ sep = content_type[1].split("=")[1]
227
+ parser = mp.MultipartParser(BytesIO(body), sep.encode('utf-8'))
228
+ for part in parser.parts():
229
+ args[part.name] = part.value
230
+ except Exception as e:
231
+ self.logError(f"Error parsing multipart data: {e}")
232
+ raise ValueError("Invalid multipart data")
233
+
234
+ # Parse JSON
235
+ elif content_type[0] == "application/json" and content_length > 0:
236
+ try:
237
+ body = environ['wsgi.input'].read(content_length)
238
+ data = json.loads(body)
239
+ if isinstance(data, dict):
240
+ args.update(data)
241
+ except json.JSONDecodeError as e:
242
+ self.logError(f"Error parsing JSON: {e}")
243
+ raise ValueError("Invalid JSON data")
133
244
 
134
245
  return args
135
246
 
136
247
 
137
- def tryDefault(self, environ, target):
138
- print(Fore.WHITE,"[INFO] Vesta - using default route")
248
+
249
+ def tryDefault(self, environ: Dict[str, Any], target: str) -> bytes:
250
+ """
251
+ Try to handle request with default route.
252
+
253
+ Args:
254
+ environ: WSGI environ dictionary
255
+ target: Request target path
256
+
257
+ Returns:
258
+ Response bytes
259
+ """
260
+ self.logInfo("Vesta - using default route")
139
261
 
140
262
  args = self.parseRequest(environ)
141
263
  args["target"] = target
@@ -147,9 +269,19 @@ class BaseServer:
147
269
  return self.handleUnexpected(e)
148
270
 
149
271
 
150
- def onrequest(self, environ, start_response):
272
+ def onrequest(self, environ: Dict[str, Any], start_response: Callable) -> bytes:
273
+ """
274
+ Handle incoming HTTP request.
275
+
276
+ Args:
277
+ environ: WSGI environ dictionary
278
+ start_response: WSGI start_response callable
279
+
280
+ Returns:
281
+ Response bytes
282
+ """
151
283
  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')))
284
+ self.log(f"Vesta - request received: '{environ['PATH_INFO']}' with {environ.get('QUERY_STRING')}")
153
285
  target = environ['PATH_INFO']
154
286
 
155
287
  if routes.get(target):
@@ -162,7 +294,7 @@ class BaseServer:
162
294
  except (HTTPError, HTTPRedirect):
163
295
  return self.response.encode()
164
296
  except Exception as e:
165
- return self.handleUnexpected(e)
297
+ return self.handleUnexpected(e)
166
298
  else:
167
299
  if routes.get("default"):
168
300
  return self.tryDefault(environ, target)
@@ -170,21 +302,26 @@ class BaseServer:
170
302
  self.response.ok()
171
303
  return self.response.encode()
172
304
 
173
- def handleUnexpected(self, e):
174
- print(Fore.RED,"[ERROR] Vesta - UNEXPECTED ERROR :", e)
305
+ def handleUnexpected(self, e: Exception) -> bytes:
306
+ """
307
+ Handle unexpected errors during request processing.
308
+
309
+ Args:
310
+ e: Exception that occurred
311
+
312
+ Returns:
313
+ Error response bytes
314
+ """
315
+ self.logError(f"Vesta - UNEXPECTED ERROR: {e}", exc_info=True)
175
316
  self.response.code = 500
176
317
  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
318
+
319
+ # Only expose detailed errors in debug mode
320
+ if self.config.getboolean("server", "DEBUG"):
321
+ self.response.content = str(e) + "\n\n" + traceback.format_exc()
322
+ else:
323
+ self.response.content = "Internal Server Error"
324
+
188
325
  return self.response.encode()
189
326
 
190
327
  def onStart(self):
@@ -192,66 +329,160 @@ class BaseServer:
192
329
 
193
330
  #--------------------------GENERAL USE METHODS------------------------------
194
331
 
195
- def importConf(self, configFile):
332
+ def importConf(self, configFile: str):
333
+ """
334
+ Import configuration from file.
335
+
336
+ Args:
337
+ configFile: Path to configuration file
338
+
339
+ Raises:
340
+ FileNotFoundError: If config file doesn't exist
341
+ """
196
342
  self.config = ConfigParser()
343
+ config_path = self.path + configFile
344
+
197
345
  try:
198
- self.config.read(self.path + configFile)
199
- print(Fore.GREEN,"[INFO] Vesta - config at " + self.path + configFile + " loaded")
346
+ self.config.read(config_path)
347
+ print(Fore.GREEN,"[INFO] Vesta - config at " + config_path + " loaded")
200
348
  except Exception:
201
349
  print(Fore.RED,"[ERROR] Vesta - Please create a config file")
202
350
 
203
351
  def start(self):
352
+ """Start the HTTP server."""
204
353
  self.fileCache = {}
205
354
 
206
355
  if self.features.get("errors"):
207
356
  for code, page in self.features["errors"].items():
208
357
  Response.ERROR_PAGES[code] = self.path + page
209
358
 
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
-
359
+ if self.features.get("orm"):
360
+ try:
361
+ self.db = db.DB(
362
+ user=self.config.get('DB', 'DB_USER'),
363
+ password=self.config.get('DB', 'DB_PASSWORD'),
364
+ host=self.config.get('DB', 'DB_HOST'),
365
+ port=int(self.config.get('DB', 'DB_PORT')),
366
+ db=self.config.get('DB', 'DB_NAME')
367
+ )
368
+ except Exception as e:
369
+ self.logError(f"Failed to initialize database: {e}")
370
+ raise
215
371
 
216
372
  self.onStart()
217
373
 
218
374
  fastwsgi.server.nowait = 1
219
375
  fastwsgi.server.hook_sigint = 1
220
376
 
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')))
377
+ self.logInfo(f"Vesta - server running on PID: {os.getpid()} and port {self.config.get('server', 'PORT')}")
378
+ fastwsgi.server.init(
379
+ app=self.onrequest,
380
+ host=self.config.get('server', 'IP'),
381
+ port=int(self.config.get('server', 'PORT'))
382
+ )
383
+
224
384
  while True:
225
385
  code = fastwsgi.server.run()
226
386
  if code != 0:
227
387
  break
228
388
  time.sleep(0)
389
+
229
390
  self.close()
230
391
 
231
392
  def close(self):
232
- print(Fore.GREEN,"[INFO] SIGTERM/SIGINT received")
393
+ """Shutdown the server gracefully."""
394
+
395
+ self.logInfo("SIGTERM/SIGINT received")
396
+
397
+ # Close database connection if it exists
398
+ if hasattr(self, 'db'):
399
+ try:
400
+ self.db.close()
401
+ except Exception as e:
402
+ self.logError(f"Error closing database: {e}")
403
+
233
404
  fastwsgi.server.close()
234
- print(Fore.GREEN,"[INFO] SERVER STOPPED")
235
- exit()
405
+ self.logInfo("SERVER STOPPED")
406
+
407
+
408
+ def file(self, path: str, responseFile: bool = True) -> str:
409
+ """
410
+ Read a file with caching support.
411
+
412
+ Args:
413
+ path: Path to file (relative to server path or absolute)
414
+ responseFile: If True, set response type to HTML
415
+
416
+ Returns:
417
+ File content as string
236
418
 
237
- def file(self, path, responseFile=True):
238
- if responseFile:
239
- self.response.type = "html"
240
- self.response.headers = [('Content-Type', 'text/html')]
419
+ Raises:
420
+ ValueError: If path traversal is detected
421
+ """
241
422
  file = self.fileCache.get(path)
242
423
  if file:
243
424
  return file
244
425
  else:
426
+ # Validate path to prevent traversal
427
+ file_path = Path(path)
428
+ if not file_path.is_absolute():
429
+ file_path = self.path / file_path
430
+
431
+ file_path = file_path.resolve()
432
+
433
+ # Ensure path is within server directory
434
+ if not str(file_path).startswith(str(self.path.resolve())):
435
+ raise ValueError("Invalid file path: path traversal attempt detected")
436
+
437
+ if not file_path.exists():
438
+ raise FileNotFoundError(f"File not found: {path}")
439
+
245
440
  file = open(path)
246
441
  content = file.read()
247
442
  file.close()
248
- self.fileCache[path] = content
249
443
  return content
250
444
 
445
+
251
446
  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")
447
+ """
448
+ Start ORM connection (for manual initialization).
449
+
450
+ Raises:
451
+ Exception: If ORM is not enabled in features
452
+ """
453
+ if not self.features.get("orm"):
454
+ raise Exception("ORM not enabled in server features")
455
+
456
+ try:
457
+ self.db = db.DB(
458
+ user=self.config.get('DB', 'DB_USER'),
459
+ password=self.config.get('DB', 'DB_PASSWORD'),
460
+ host=self.config.get('DB', 'DB_HOST'),
461
+ port=int(self.config.get('DB', 'DB_PORT')),
462
+ db=self.config.get('DB', 'DB_NAME')
463
+ )
464
+ self.logInfo("Vesta - ORM database connection established")
465
+ except Exception as e:
466
+ self.logError(f"Failed to start ORM: {e}")
467
+ raise
468
+
469
+ def log(self, message):
470
+ print(Fore.WHITE,"[LOG]", message, Style.RESET_ALL)
471
+
472
+ def logInfo(self, message):
473
+ print(Fore.GREEN,"[INFO]", message, Style.RESET_ALL)
474
+
475
+ def logWarning(self, message):
476
+ print(Fore.ORANGE,"[WARNING]", message, Style.RESET_ALL)
477
+
478
+ def logError(self, message: str, exc_info: bool = False):
479
+ """
480
+ Log an error message.
481
+
482
+ Args:
483
+ message: Error message to log
484
+ exc_info: If True, include exception traceback
485
+ """
486
+ print(Fore.RED, "[ERROR]", message, Style.RESET_ALL)
487
+ if exc_info:
488
+ print(Fore.RED, traceback.format_exc(), Style.RESET_ALL)
vesta/scripts/cli.py CHANGED
@@ -171,11 +171,16 @@ def main():
171
171
  # Commande init
172
172
  parser_init = subparsers.add_parser('init', help='Initialize a new Vesta project')
173
173
  parser_init = subparsers.add_parser('install', help='Import dependencies')
174
- parser_db = subparsers.add_parser('db', help='Manage the database')
175
174
  parser_update = subparsers.add_parser('update', help='Update dependencies')
176
175
  parser_test = subparsers.add_parser('test', help='Run tests')
177
176
  parser_add_feature = subparsers.add_parser('add-feature', help='Add a feature to the project')
178
177
 
178
+ parser_db = subparsers.add_parser('db', help='Manage the database')
179
+ db_subparsers = parser_db.add_subparsers(dest='db_command')
180
+ parser_db_create = db_subparsers.add_parser('create', help='Create and initialize the database')
181
+ parser_db_init = db_subparsers.add_parser('init', help='Initialize the database')
182
+ parser_db_reset = db_subparsers.add_parser('reset', help='Reset the database ')
183
+
179
184
  parser_nginx = subparsers.add_parser('nginx', help='Setup/manage nginx configuration')
180
185
  nginx_subparsers = parser_nginx.add_subparsers(dest='nginx_command')
181
186
  parser_nginx_setup = nginx_subparsers.add_parser('setup', help='Install nginx config')
@@ -192,7 +197,19 @@ def main():
192
197
  if args.command == 'init':
193
198
  init_project()
194
199
  elif args.command == 'db':
195
- pass
200
+ installer = Installer(PATH + "/server.ini", PATH)
201
+
202
+ if args.db_command == 'create':
203
+ installer.createDB()
204
+ installer.createUniauth()
205
+ installer.initDB()
206
+ elif args.nginx_command == 'init':
207
+ installer.initDB()
208
+ elif args.nginx_command == 'reset':
209
+ installer.resetDB()
210
+ else:
211
+ parser_db.print_help()
212
+
196
213
  elif args.command == 'update':
197
214
  updateDeps()
198
215
  elif args.command == 'install':
vesta/scripts/utils.py CHANGED
@@ -2,6 +2,10 @@ import subprocess
2
2
  from configparser import ConfigParser
3
3
 
4
4
 
5
+ from vesta import Server
6
+ import sys
7
+ from psycopg import sql
8
+
5
9
  def ex(command):
6
10
  subprocess.run(command, shell=True, check=True)
7
11
 
@@ -9,7 +13,7 @@ class Installer:
9
13
  def __init__(self, configFile, path):
10
14
  self.PATH = path
11
15
  self.importConf(configFile)
12
- self.uniauth = "N"
16
+ self.uniauth = False
13
17
  self.name = self.config.get("server", "SERVICE_NAME").replace(" ", "_").lower()
14
18
 
15
19
  def installNginx(self, link=True):
@@ -81,4 +85,72 @@ class Installer:
81
85
  for key in templates:
82
86
  data = data.replace(key, templates[key])
83
87
  with open(file+"_filled", "w+") as f:
84
- f.write(data)
88
+ f.write(data)
89
+
90
+ # -----------------__DB METHODS__----------------- #
91
+ def initDB(self):
92
+ initializer = DBInitializer(path=self.PATH, configFile=self.PATH + "/server.ini", noStart=True)
93
+ if self.uniauth:
94
+ initializer.initUniauth()
95
+ initializer.initDB()
96
+
97
+
98
+ def resetDB(self):
99
+ ex("sudo -u postgres dropdb " + self.config.get("DB", "DB_NAME"))
100
+ self.initDB()
101
+
102
+ def createUniauth(self):
103
+ while True:
104
+ uniauth = input("Do you want to create a uniauth database? (y/n)")
105
+ if uniauth.upper() == 'Y' or uniauth.upper() == 'N':
106
+ self.uniauth = uniauth.upper()
107
+ break
108
+
109
+ if self.uniauth == 'Y':
110
+ ex("sudo -u postgres createdb " + self.config.get("UNIAUTH", "DB_NAME"))
111
+
112
+ def createDB(self):
113
+ ex("sudo -u postgres createdb " + self.config.get("DB", "DB_NAME"))
114
+
115
+ def createUser(self):
116
+ ex("sudo -u postgres createuser " + self.config.get("DB", "DB_USER") + " -s --pwprompt ")
117
+
118
+
119
+ class DBInitializer(Server):
120
+ def initUniauth(self):
121
+ self.uniauth.initUniauth()
122
+
123
+ def initDB(self):
124
+ self.referenceUniauth()
125
+ self.db.cur.execute(open(self.path + "/db/schema.sql", "r").read())
126
+ self.db.conn.commit()
127
+
128
+ def referenceUniauth(self):
129
+ self.db.cur.execute("CREATE EXTENSION if not exists postgres_fdw;")
130
+ self.db.cur.execute(
131
+ sql.SQL("""
132
+ CREATE SERVER if not exists uniauth
133
+ FOREIGN DATA WRAPPER postgres_fdw
134
+ OPTIONS (host %s, port %s, dbname %s);
135
+ """),
136
+ (
137
+ self.config.get('UNIAUTH', 'DB_HOST'),
138
+ self.config.get('UNIAUTH', 'DB_PORT'),
139
+ self.config.get('UNIAUTH', 'DB_NAME')
140
+ ))
141
+
142
+ self.db.cur.execute(
143
+ sql.SQL("""
144
+ CREATE USER MAPPING if not exists FOR CURRENT_USER SERVER uniauth
145
+ OPTIONS (user %s, password %s);
146
+ """),
147
+ (
148
+ self.config.get('DB', 'DB_USER'),
149
+ self.config.get('DB', 'DB_PASSWORD')
150
+ ))
151
+ self.db.cur.execute(
152
+ """
153
+ CREATE FOREIGN TABLE if not exists account (id bigserial NOT NULL)
154
+ SERVER uniauth
155
+ OPTIONS (schema_name 'public', table_name 'account');
156
+ """)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vesta-web
3
- Version: 1.1.2
3
+ Version: 1.1.3
4
4
  Summary: An extensive web framework adding every feature needed for Carbonlab
5
5
  Project-URL: Homepage, https://gitlab.com/Louciole/vesta
6
6
  Project-URL: Issues, https://gitlab.com/Louciole/vesta/-/issues
@@ -23,6 +23,7 @@ Requires-Dist: fastwsgi
23
23
  Requires-Dist: multipart
24
24
  Requires-Dist: psycopg
25
25
  Requires-Dist: pyjwt
26
+ Requires-Dist: setuptools
26
27
  Requires-Dist: websockets
27
28
  Description-Content-Type: text/markdown
28
29
 
@@ -45,18 +45,18 @@ vesta/emptyProject/static/translations/fr.mjs,sha256=ouMluPVTgB4Q5vmb7zGE6YGTH4U
45
45
  vesta/emptyProject/static/translations/translation.mjs,sha256=JxJ2peSlYVQK-bUKpfddPLXm0XZiz2yu6A6iWIqpKyM,1422
46
46
  vesta/emptyProject/static/ws/onMessage.mjs,sha256=ow5nwSEdiBcvm-Y2zOUMhnqLp-5xWgo11kHviaTRlTw,658
47
47
  vesta/emptyProject/tests/example/foo.py,sha256=NS9oIXFBOvIyWK1LHwkJm9amJuSMN4cxJwouBrJlh2I,115
48
- vesta/http/baseServer.py,sha256=7jN0y1SpIRWE9ohzT8IlehTbzy3ofqrOPeKA4vKOC1k,8312
48
+ vesta/http/baseServer.py,sha256=To64gSOIvpgEeMz5shQnqzexgVVgJcGOBsEbGA_IN8c,14785
49
49
  vesta/http/error.py,sha256=fWdp-oI2ObJD2mHHuxs1yVJvhON5oHYgYFRLAcUMs-I,180
50
50
  vesta/http/redirect.py,sha256=OiDeOmU-X5Mos8a0BQIeOIJqvgWjDEtaYrM4-x4MXl0,177
51
51
  vesta/http/response.py,sha256=G7cmbrXFNbIbQoqNxNkR06I5VymIwjFSAe3LtVa56Ok,3760
52
52
  vesta/mailing/mailing_service.py,sha256=GBO5Hnspm9Pqwd5kGB0iekZaMoIrfQvrhMUf8tVma7g,5386
53
- vesta/scripts/cli.py,sha256=xfY-dVzPAbJdmgpRJoGGNSlSXDsMyrQPwykAK017YqE,7506
53
+ vesta/scripts/cli.py,sha256=3vKp-imzaAxLHkOwFcNznQ8t9j3St9m0G6RJMpErJIc,8230
54
54
  vesta/scripts/initDB.py,sha256=TKaK4RZM6CycBEsHeGb9Q9PdphkQgaJDnEWhvRnGC9k,1659
55
55
  vesta/scripts/install.py,sha256=GvH_HHx5aU5_54RQ1_2vz4DaLCh42AHfUKy-m0q21vY,2125
56
56
  vesta/scripts/testsRun.py,sha256=bXJImdexKQUDW8CR8F9VIKTrgkd7QfnvHQPENEV4x38,2463
57
- vesta/scripts/utils.py,sha256=MQZ29b4eplF0OR9EimUToOO73CVoV_cTxQeez2F3OoY,3460
58
- vesta_web-1.1.2.dist-info/METADATA,sha256=F1vBrwheV7MlW4UmsKMehblxF1Cleqk1LkNObRJvgU8,1558
59
- vesta_web-1.1.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
60
- vesta_web-1.1.2.dist-info/entry_points.txt,sha256=MHMrWJwtkb4FmNz0CTpxZzwQ3LTqndXBh8YBPDfXlW4,49
61
- vesta_web-1.1.2.dist-info/licenses/LICENSE.md,sha256=zoPFEFUUoSgosmDBK5fGTWGRHHBaSVuuJT2ZQIYXuIk,177
62
- vesta_web-1.1.2.dist-info/RECORD,,
57
+ vesta/scripts/utils.py,sha256=g5QKt1Xeezqf56m4ETLvYBhbZ82eg3qHRPKdjE6mjAQ,5860
58
+ vesta_web-1.1.3.dist-info/METADATA,sha256=7EY_IDwbSTQEmK2TXoIcm6A5sCt9Bojo_dhDR6s_mcc,1584
59
+ vesta_web-1.1.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
60
+ vesta_web-1.1.3.dist-info/entry_points.txt,sha256=MHMrWJwtkb4FmNz0CTpxZzwQ3LTqndXBh8YBPDfXlW4,49
61
+ vesta_web-1.1.3.dist-info/licenses/LICENSE.md,sha256=zoPFEFUUoSgosmDBK5fGTWGRHHBaSVuuJT2ZQIYXuIk,177
62
+ vesta_web-1.1.3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any