1
0
Files
coconut-leaf/backend/database.py

628 lines
23 KiB
Python
Raw Normal View History

2021-01-16 22:15:10 +08:00
import sqlite3
2021-01-19 22:20:11 +08:00
import threading
from typing import cast
from pathlib import Path
from dataclasses import dataclass
from typing import Any
import dt
import utils
import config
from logger import LOGGER
@dataclass(frozen=True)
class ResponseBody:
success: bool
"""True if this operation is successful, otherwise false."""
error: str
"""The error message provided when operation failed."""
data: Any
"""The payload provided when operation successed."""
2021-01-19 22:20:11 +08:00
def SafeDatabaseOperation(func):
def wrapper(self: 'CalendarDatabase', *args, **kwargs):
cfg = config.get_config()
2021-01-19 22:20:11 +08:00
with self.mutex:
# check database and acquire cursor
try:
self.check_database()
self.cursor = self.db.cursor()
2021-01-24 14:38:08 +08:00
except Exception as e:
2021-01-19 22:20:11 +08:00
self.cursor = None
if cfg.others.debug:
LOGGER.exception(e)
return ResponseBody(False, str(e), None)
2021-01-19 22:20:11 +08:00
# do real data work
try:
2021-01-25 20:42:06 +08:00
currentTime = utils.GetCurrentTimestamp()
if currentTime - self.latestClean > cfg.others.auto_token_clean_duration:
2021-01-25 20:42:06 +08:00
self.latestClean = currentTime
LOGGER.info('Cleaning outdated token...')
2021-01-24 14:38:08 +08:00
self.tokenOper_clean()
result = ResponseBody(True, '', func(self, *args, **kwargs))
2021-01-19 22:20:11 +08:00
self.cursor.close()
self.cursor = None
self.db.commit()
return result
2021-01-24 14:38:08 +08:00
except Exception as e:
2021-01-19 22:20:11 +08:00
self.cursor.close()
self.cursor = None
self.db.rollback()
if cfg.others.debug:
LOGGER.exception(e)
return ResponseBody(False, str(e), None)
2021-01-19 22:20:11 +08:00
return wrapper
2021-01-16 22:15:10 +08:00
class CalendarDatabase:
db: sqlite3.Connection
cursor: sqlite3.Cursor
mutex: threading.Lock
latestClean: int
2021-01-16 22:15:10 +08:00
def __init__(self):
self.db = None
2021-01-19 22:20:11 +08:00
self.cursor = None
self.mutex = threading.Lock()
2021-01-25 20:42:06 +08:00
self.latestClean = 0
2021-01-16 22:15:10 +08:00
def open(self):
if (self.is_database_valid()):
raise Exception('Databade is opened')
cfg = config.get_config()
match cfg.database.driver:
case config.DatabaseDriver.SQLITE:
self.db = sqlite3.connect(cast(config.SqliteDatabaseConfig, cfg.database.config).path, check_same_thread = False)
self.db.execute('PRAGMA encoding = "UTF-8";')
self.db.execute('PRAGMA foreign_keys = ON;')
case config.DatabaseDriver.MYSQL:
raise Exception('Not implemented database')
case _:
raise Exception('Unknow database type')
2021-01-16 22:15:10 +08:00
def init(self, username, password):
if (self.is_database_valid()):
2021-01-24 14:38:08 +08:00
raise Exception('Database is opened')
2021-01-16 22:15:10 +08:00
2021-01-19 22:20:11 +08:00
# establish tables
cfg = config.get_config()
backend_path = Path(__file__).resolve().parent
backend_sql_path = backend_path / 'sql'
match cfg.database.driver:
case config.DatabaseDriver.SQLITE:
sql_file = backend_sql_path / 'sqlite.sql'
case config.DatabaseDriver.MYSQL:
raise Exception('Not implemented database')
case _:
raise Exception('Unknow database type')
2021-01-16 22:15:10 +08:00
self.open()
2021-01-19 22:20:11 +08:00
cursor = self.db.cursor()
with open(sql_file, 'r', encoding='utf-8') as fsql:
2021-01-16 22:15:10 +08:00
cursor.executescript(fsql.read())
2021-01-19 22:20:11 +08:00
# finish init
2021-01-24 14:38:08 +08:00
cursor.execute('INSERT INTO user VALUES (?, ?, ?, ?);', (
2021-01-19 22:20:11 +08:00
username,
utils.ComputePasswordHash(password),
1,
2021-01-24 14:38:08 +08:00
utils.GenerateSalt()
2021-01-19 22:20:11 +08:00
))
cursor.close()
self.db.commit()
2021-01-16 22:15:10 +08:00
def close(self):
self.check_database()
self.db.close()
self.db = None
def check_database(self):
if (not self.is_database_valid()):
raise Exception('Databade is None')
def is_database_valid(self):
return not (self.db == None)
2021-01-24 14:38:08 +08:00
# ======================= token related internal operation
def tokenOper_clean(self):
# remove outdated token
2026-05-12 19:25:31 +08:00
self.cursor.execute('DELETE FROM token WHERE [token_expire_on] <= ?',(utils.GetCurrentTimestamp(), ))
2021-01-24 14:38:08 +08:00
def tokenOper_postpone_expireOn(self, token):
2026-05-12 19:25:31 +08:00
self.cursor.execute('UPDATE token SET [token_expire_on] = ? WHERE [token] = ?;', (
2021-01-24 14:38:08 +08:00
utils.GetTokenExpireOn(),
token
))
def tokenOper_check_valid(self, token):
self.tokenOper_get_username(token)
def tokenOper_is_admin(self, username):
2026-05-12 19:25:31 +08:00
self.cursor.execute('SELECT [is_admin] FROM user WHERE [name] = ?;',(username, ))
2021-01-24 14:38:08 +08:00
cache = self.cursor.fetchone()[0]
2021-02-03 16:08:40 +08:00
return cache == 1
2021-01-24 14:38:08 +08:00
def tokenOper_get_username(self, token):
2026-05-12 19:25:31 +08:00
self.cursor.execute('SELECT [user] FROM token WHERE [token] = ? AND [token_expire_on] > ?;',(
2021-01-19 22:20:11 +08:00
token,
utils.GetCurrentTimestamp()
))
2021-01-25 20:42:06 +08:00
result = self.cursor.fetchone()[0]
# need postpone expire on time
self.tokenOper_postpone_expireOn(token)
return result
2021-01-24 14:38:08 +08:00
2021-01-19 22:20:11 +08:00
# =============================== # =============================== operation function
# =============================== common
2021-01-29 11:10:25 +08:00
2021-01-19 22:20:11 +08:00
@SafeDatabaseOperation
def common_salt(self, username):
salt = utils.GenerateSalt()
2026-05-12 19:25:31 +08:00
self.cursor.execute('UPDATE user SET [salt] = ? WHERE [name] = ?;', (
2021-01-19 22:20:11 +08:00
salt,
username
))
return salt
@SafeDatabaseOperation
2021-03-08 21:56:03 +08:00
def common_login(self, username, password, clientUa, clientIp):
2026-05-12 19:25:31 +08:00
self.cursor.execute('SELECT [password], [salt] FROM user WHERE [name] = ?;', (username, ))
2021-01-19 22:20:11 +08:00
(gotten_salt, gotten_password) = self.cursor.fetchone()
if password == utils.ComputePasswordHashWithSalt(gotten_password, gotten_salt):
2021-01-20 22:57:41 +08:00
token = utils.GenerateToken(username)
2026-05-12 19:25:31 +08:00
self.cursor.execute('UPDATE user SET [salt] = ? WHERE [name] = ?;', (
2021-01-20 22:57:41 +08:00
utils.GenerateSalt(), # regenerate a new slat to prevent re-login try
username
))
2021-03-08 21:56:03 +08:00
self.cursor.execute('INSERT INTO token VALUES (?, ?, ?, ?, ?);', (
2021-01-24 14:38:08 +08:00
username,
token,
utils.GetTokenExpireOn(), # add 2 day from now
2021-03-08 21:56:03 +08:00
clientUa,
clientIp,
2021-01-24 14:38:08 +08:00
))
2021-01-20 22:57:41 +08:00
return token
else:
2021-01-23 18:37:12 +08:00
# throw a exception to indicate fail to login
2021-01-24 14:38:08 +08:00
raise Exception('Login authentication failed')
2021-01-20 22:57:41 +08:00
@SafeDatabaseOperation
2021-03-08 21:56:03 +08:00
def common_webLogin(self, username, password, clientUa, clientIp):
LOGGER.debug(f'WebLogin Username: {username}')
LOGGER.debug(f'WebLogin Password: {password}')
passwordHash = utils.ComputePasswordHash(password)
LOGGER.debug(f'WebLogin Password Hash: {passwordHash}')
self.cursor.execute('SELECT [name] FROM user WHERE [name] = ? AND [password] = ?;', (username, passwordHash))
2021-01-20 22:57:41 +08:00
if len(self.cursor.fetchall()) != 0:
2021-01-19 22:20:11 +08:00
token = utils.GenerateToken(username)
2021-03-08 21:56:03 +08:00
self.cursor.execute('INSERT INTO token VALUES (?, ?, ?, ?, ?);', (
2021-01-24 14:38:08 +08:00
username,
2021-01-19 22:20:11 +08:00
token,
2021-01-24 14:38:08 +08:00
utils.GetTokenExpireOn(), # add 2 day from now
2021-03-08 21:56:03 +08:00
clientUa,
clientIp,
2021-01-19 22:20:11 +08:00
))
return token
else:
2021-01-23 18:37:12 +08:00
# throw a exception to indicate fail to login
2021-01-24 14:38:08 +08:00
raise Exception('Login authentication failed')
2021-01-19 22:20:11 +08:00
@SafeDatabaseOperation
def common_logout(self, token):
2021-01-24 14:38:08 +08:00
self.tokenOper_check_valid(token)
2026-05-12 19:25:31 +08:00
self.cursor.execute('DELETE FROM token WHERE [token] = ?;', (token, ))
2021-01-31 13:50:20 +08:00
return True
2021-01-19 22:20:11 +08:00
@SafeDatabaseOperation
def common_tokenValid(self, token):
2021-01-24 14:38:08 +08:00
self.tokenOper_check_valid(token)
2021-01-31 13:50:20 +08:00
return True
2021-01-19 22:20:11 +08:00
# =============================== calendar
2021-02-02 12:11:13 +08:00
@SafeDatabaseOperation
def calendar_getFull(self, token, startDateTime, endDateTime):
username = self.tokenOper_get_username(token)
self.cursor.execute('SELECT calendar.* FROM calendar INNER JOIN collection \
2026-05-12 19:25:31 +08:00
ON collection.uuid = calendar.belong_to \
WHERE (collection.user = ? AND calendar.loop_date_time_end >= ? AND calendar.loop_date_time_start - (calendar.event_date_time_end - calendar.event_date_time_start) <= ?);',
2021-02-02 12:11:13 +08:00
(username, startDateTime, endDateTime))
return self.cursor.fetchall()
@SafeDatabaseOperation
def calendar_getList(self, token, startDateTime, endDateTime):
username = self.tokenOper_get_username(token)
2026-05-12 19:25:31 +08:00
self.cursor.execute('SELECT calendar.uuid FROM calendar INNER JOIN collection \
ON collection.uuid = calendar.belong_to \
WHERE (collection.user = ? AND calendar.loop_date_time_end >= ? AND calendar.loop_date_time_start - (calendar.event_date_time_end - calendar.event_date_time_start) <= ?);',
2021-02-02 12:11:13 +08:00
(username, startDateTime, endDateTime))
return tuple(map(lambda x: x[0], self.cursor.fetchall()))
@SafeDatabaseOperation
def calendar_getDetail(self, token, uuid):
self.tokenOper_check_valid(token)
2026-05-12 19:25:31 +08:00
self.cursor.execute('SELECT * FROM calendar WHERE [uuid] = ?;', (uuid, ))
2021-02-02 12:11:13 +08:00
return self.cursor.fetchone()
@SafeDatabaseOperation
def calendar_update(self, token, uuid, lastChange, **optArgs):
self.tokenOper_check_valid(token)
2021-01-19 22:20:11 +08:00
2021-02-02 12:11:13 +08:00
# get prev data
2026-05-12 19:25:31 +08:00
self.cursor.execute('SELECT * FROM calendar WHERE [uuid] = ? AND [last_change] = ?;', (uuid, lastChange))
2021-02-09 17:10:05 +08:00
analyseData = list(self.cursor.fetchone())
2021-02-02 12:11:13 +08:00
# construct update data
lastupdate = utils.GenerateUUID()
sqlList = [
2026-05-12 19:25:31 +08:00
'[last_change] = ?',
2021-02-02 12:11:13 +08:00
]
argumentsList = [
lastupdate,
]
# analyse opt arg
reAnalyseLoop = False
2021-02-03 16:08:40 +08:00
cache = optArgs.get('belongTo', None)
2021-02-02 12:11:13 +08:00
if cache is not None:
2026-05-12 19:25:31 +08:00
sqlList.append('[belong_to] = ?')
2021-02-02 12:11:13 +08:00
argumentsList.append(cache)
2021-02-03 16:08:40 +08:00
cache = optArgs.get('title', None)
2021-02-02 12:11:13 +08:00
if cache is not None:
2026-05-12 19:25:31 +08:00
sqlList.append('[title] = ?')
2021-02-02 12:11:13 +08:00
argumentsList.append(cache)
2021-02-03 16:08:40 +08:00
cache = optArgs.get('description', None)
2021-02-02 12:11:13 +08:00
if cache is not None:
2026-05-12 19:25:31 +08:00
sqlList.append('[description] = ?')
2021-02-02 12:11:13 +08:00
argumentsList.append(cache)
2021-02-03 16:08:40 +08:00
cache = optArgs.get('eventDateTimeStart', None)
2021-02-02 12:11:13 +08:00
if cache is not None:
2026-05-12 19:25:31 +08:00
sqlList.append('[event_date_time_start] = ?')
2021-02-02 12:11:13 +08:00
argumentsList.append(cache)
reAnalyseLoop = True
analyseData[5] = cache
2021-02-03 16:08:40 +08:00
cache = optArgs.get('eventDateTimeEnd', None)
2021-02-02 12:11:13 +08:00
if cache is not None:
2026-05-12 19:25:31 +08:00
sqlList.append('[event_date_time_end] = ?')
2021-02-02 12:11:13 +08:00
argumentsList.append(cache)
2021-02-03 16:08:40 +08:00
cache = optArgs.get('loopRules', None)
2021-02-02 12:11:13 +08:00
if cache is not None:
2026-05-12 19:25:31 +08:00
sqlList.append('[loop_rules] = ?')
2021-02-02 12:11:13 +08:00
argumentsList.append(cache)
reAnalyseLoop = True
analyseData[8] = cache
2021-02-03 16:08:40 +08:00
cache = optArgs.get('timezoneOffset', None)
2021-02-02 12:11:13 +08:00
if cache is not None:
2026-05-12 19:25:31 +08:00
sqlList.append('[timezone_offset] = ?')
2021-02-02 12:11:13 +08:00
argumentsList.append(cache)
reAnalyseLoop = True
analyseData[7] = cache
if reAnalyseLoop:
2021-02-07 13:44:16 +08:00
# re-compute loop data and upload it into list
2026-05-12 19:25:31 +08:00
sqlList.append('[loop_date_time_start] = ?')
2021-02-07 13:44:16 +08:00
argumentsList.append(analyseData[5])
2026-05-12 19:25:31 +08:00
sqlList.append('[loop_date_time_end] = ?')
argumentsList.append(str(dt.ResolveLoopStr(
2021-02-07 13:44:16 +08:00
analyseData[8],
analyseData[5],
analyseData[7]
)))
2021-02-02 12:11:13 +08:00
# execute
argumentsList.append(uuid)
2026-05-12 19:25:31 +08:00
self.cursor.execute('UPDATE calendar SET {} WHERE [uuid] = ?;'.format(', '.join(sqlList)),
2021-02-02 12:11:13 +08:00
tuple(argumentsList))
2021-02-02 20:19:53 +08:00
if self.cursor.rowcount != 1:
raise Exception('Fail to update due to no matched rows or too much rows.')
2021-02-02 12:11:13 +08:00
return lastupdate
@SafeDatabaseOperation
def calendar_add(self, token, belongTo, title, description, eventDateTimeStart, eventDateTimeEnd, loopRules, timezoneOffset):
self.tokenOper_check_valid(token)
newuuid = utils.GenerateUUID()
lastupdate = utils.GenerateUUID()
2021-02-07 13:44:16 +08:00
# analyse loopRules and output following 2 fileds.
2021-02-02 12:11:13 +08:00
loopDateTimeStart = eventDateTimeStart
2021-02-07 13:44:16 +08:00
loopDateTimeEnd = dt.ResolveLoopStr(loopRules, eventDateTimeStart, timezoneOffset)
2021-02-02 12:11:13 +08:00
self.cursor.execute('INSERT INTO calendar VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);',
(newuuid,
belongTo,
title,
description,
lastupdate,
eventDateTimeStart,
eventDateTimeEnd,
timezoneOffset,
loopRules,
loopDateTimeStart,
loopDateTimeEnd))
return newuuid
@SafeDatabaseOperation
def calendar_delete(self, token, uuid, lastChange):
self.tokenOper_check_valid(token)
2026-05-12 19:25:31 +08:00
self.cursor.execute('DELETE FROM calendar WHERE [uuid] = ? AND [last_change] = ?;', (uuid, lastChange))
2021-02-02 20:19:53 +08:00
if self.cursor.rowcount != 1:
raise Exception('Fail to delete due to no matched rows or too much rows.')
2021-02-02 12:11:13 +08:00
return True
2021-01-19 22:20:11 +08:00
# =============================== collection
2021-02-02 20:19:53 +08:00
@SafeDatabaseOperation
def collection_getFullOwn(self, token):
username = self.tokenOper_get_username(token)
2026-05-12 19:25:31 +08:00
self.cursor.execute('SELECT [uuid], [name], [last_change] FROM collection WHERE [user] = ?;', (username, ))
2021-02-02 20:19:53 +08:00
return self.cursor.fetchall()
@SafeDatabaseOperation
def collection_getListOwn(self, token):
username = self.tokenOper_get_username(token)
2026-05-12 19:25:31 +08:00
self.cursor.execute('SELECT [uuid] FROM collection WHERE [user] = ?;', (username, ))
2021-02-02 20:19:53 +08:00
return tuple(map(lambda x: x[0], self.cursor.fetchall()))
@SafeDatabaseOperation
def collection_getDetailOwn(self, token, uuid):
username = self.tokenOper_get_username(token)
2026-05-12 19:25:31 +08:00
self.cursor.execute('SELECT [uuid], [name], [last_change] FROM collection WHERE [user] = ? AND [uuid] = ?;', (username, uuid))
2021-02-02 20:19:53 +08:00
return self.cursor.fetchone()
@SafeDatabaseOperation
def collection_addOwn(self, token, newname):
username = self.tokenOper_get_username(token)
newuuid = utils.GenerateUUID()
lastupdate = utils.GenerateUUID()
self.cursor.execute('INSERT INTO collection VALUES (?, ?, ?, ?);',
(newuuid, newname, username, lastupdate))
return newuuid
@SafeDatabaseOperation
def collection_updateOwn(self, token, uuid, newname, lastChange):
self.tokenOper_check_valid(token)
lastupdate = utils.GenerateUUID()
2026-05-12 19:25:31 +08:00
self.cursor.execute('UPDATE collection SET [name] = ?, [last_change] = ? WHERE [uuid] = ? AND [last_change] = ?;', (
2021-02-02 20:19:53 +08:00
newname,
lastupdate,
uuid,
lastChange
))
if self.cursor.rowcount != 1:
raise Exception('Fail to update due to no matched rows or too much rows.')
return lastupdate
@SafeDatabaseOperation
def collection_deleteOwn(self, token, uuid, lastChange):
self.tokenOper_check_valid(token)
2021-01-19 22:20:11 +08:00
2026-05-12 19:25:31 +08:00
self.cursor.execute('DELETE FROM collection WHERE [uuid] = ? AND [last_change] = ?;', (
2021-02-02 20:19:53 +08:00
uuid,
lastChange
))
if self.cursor.rowcount != 1:
raise Exception('Fail to delete due to no matched rows or too much rows.')
return True
@SafeDatabaseOperation
def collection_getSharing(self, token, uuid):
self.tokenOper_check_valid(token)
2026-05-12 19:25:31 +08:00
self.cursor.execute('SELECT [target] FROM share WHERE [uuid] = ?;', (uuid, ))
2021-02-02 20:19:53 +08:00
return tuple(map(lambda x: x[0], self.cursor.fetchall()))
@SafeDatabaseOperation
def collection_deleteSharing(self, token, uuid, target, lastChange):
self.tokenOper_check_valid(token)
lastupdate = utils.GenerateUUID()
2026-05-12 19:25:31 +08:00
self.cursor.execute('UPDATE collection SET [last_change] = ?, WHERE [uuid] = ? AND [last_change] = ?;', (lastupdate, uuid, lastChange))
2021-02-02 20:19:53 +08:00
if self.cursor.rowcount != 1:
raise Exception('Fail to delete due to no matched rows or too much rows.')
2026-05-12 19:25:31 +08:00
self.cursor.execute('DELETE FROM share WHERE [uuid] = ? AND [target] = ?;', (uuid, target))
2021-02-02 20:19:53 +08:00
if self.cursor.rowcount != 1:
raise Exception('Fail to delete due to no matched rows or too much rows.')
return lastupdate
@SafeDatabaseOperation
def collection_addSharing(self, token, uuid, target, lastChange):
self.tokenOper_check_valid(token)
lastupdate = utils.GenerateUUID()
2026-05-12 19:25:31 +08:00
self.cursor.execute('UPDATE collection SET [last_change] = ? WHERE [uuid] = ? AND [last_change] = ?;', (lastupdate, uuid, lastChange))
2021-02-02 20:19:53 +08:00
if self.cursor.rowcount != 1:
raise Exception('Fail to delete due to no matched rows or too much rows.')
2026-05-12 19:25:31 +08:00
self.cursor.execute('SELECT * FROM share WHERE [uuid] = ? AND [target] = ?;', (uuid, target))
2021-02-02 20:19:53 +08:00
if len(self.cursor.fetchall()) != 0:
raise Exception('Fail to insert duplicated item.')
self.cursor.execute('INSERT INTO share VALUES (?, ?);', (uuid, target))
return lastupdate
@SafeDatabaseOperation
def collection_getShared(self, token):
username = self.tokenOper_get_username(token)
2026-05-12 19:25:31 +08:00
self.cursor.execute('SELECT collection.uuid, collection.name, collection.user \
2021-02-02 20:19:53 +08:00
FROM share INNER JOIN collection \
2026-05-12 19:25:31 +08:00
ON share.uuid = collection.uuid \
WHERE share.target = ?;', (username, ))
2021-02-02 20:19:53 +08:00
return self.cursor.fetchall()
2021-01-19 22:20:11 +08:00
# =============================== todo
2021-01-20 22:57:41 +08:00
@SafeDatabaseOperation
def todo_getFull(self, token):
2021-01-24 14:38:08 +08:00
username = self.tokenOper_get_username(token)
2026-05-12 19:25:31 +08:00
self.cursor.execute('SELECT * FROM todo WHERE [belong_to] = ?;', (username, ))
2021-01-20 22:57:41 +08:00
return self.cursor.fetchall()
2021-01-19 22:20:11 +08:00
2021-01-20 22:57:41 +08:00
@SafeDatabaseOperation
def todo_getList(self, token):
2021-01-24 14:38:08 +08:00
username = self.tokenOper_get_username(token)
2026-05-12 19:25:31 +08:00
self.cursor.execute('SELECT [uuid] FROM todo WHERE [belong_to] = ?;', (username, ))
2021-01-20 22:57:41 +08:00
return tuple(map(lambda x: x[0], self.cursor.fetchall()))
@SafeDatabaseOperation
def todo_getDetail(self, token, uuid):
2021-01-24 14:38:08 +08:00
username = self.tokenOper_get_username(token)
2026-05-12 19:25:31 +08:00
self.cursor.execute('SELECT * FROM todo WHERE [belong_to] = ? AND [uuid] = ?;', (username, uuid))
2021-01-20 22:57:41 +08:00
return self.cursor.fetchone()
@SafeDatabaseOperation
def todo_add(self, token):
2021-01-24 14:38:08 +08:00
username = self.tokenOper_get_username(token)
2021-01-20 22:57:41 +08:00
newuuid = utils.GenerateUUID()
lastupdate = utils.GenerateUUID()
2021-01-23 18:37:12 +08:00
returnedData = (
2021-01-20 22:57:41 +08:00
newuuid,
username,
'',
lastupdate,
2021-01-23 18:37:12 +08:00
)
self.cursor.execute('INSERT INTO todo VALUES (?, ?, ?, ?);', returnedData)
return returnedData
2021-01-20 22:57:41 +08:00
@SafeDatabaseOperation
def todo_update(self, token, uuid, data, lastChange):
# check valid token
2021-01-24 14:38:08 +08:00
self.tokenOper_check_valid(token)
2021-01-20 22:57:41 +08:00
# update
2021-01-23 18:37:12 +08:00
newLastChange = utils.GenerateUUID()
2026-05-12 19:25:31 +08:00
self.cursor.execute('UPDATE todo SET [data] = ?, [last_change] = ? WHERE [uuid] = ? AND [last_change] = ?;', (
2021-01-20 22:57:41 +08:00
data,
2021-01-23 18:37:12 +08:00
newLastChange,
2021-02-02 20:19:53 +08:00
uuid,
lastChange
2021-01-20 22:57:41 +08:00
))
2021-02-02 20:19:53 +08:00
if self.cursor.rowcount != 1:
raise Exception('Fail to update due to no matched rows or too much rows.')
2021-01-23 18:37:12 +08:00
return newLastChange
2021-01-20 22:57:41 +08:00
@SafeDatabaseOperation
def todo_delete(self, token, uuid, lastChange):
# check valid token
2021-01-24 14:38:08 +08:00
self.tokenOper_check_valid(token)
2021-01-19 22:20:11 +08:00
2021-01-20 22:57:41 +08:00
# delete
2026-05-12 19:25:31 +08:00
self.cursor.execute('DELETE FROM todo WHERE [uuid] = ? AND [last_change] = ?;', (uuid, lastChange))
2021-02-02 20:19:53 +08:00
if self.cursor.rowcount != 1:
raise Exception('Fail to delete due to no matched rows or too much rows.')
2021-01-31 13:50:20 +08:00
return True
2021-01-19 22:20:11 +08:00
# =============================== admin
2021-02-02 20:19:53 +08:00
@SafeDatabaseOperation
def admin_get(self, token):
username = self.tokenOper_get_username(token)
2021-02-03 16:08:40 +08:00
if not self.tokenOper_is_admin(username):
2021-02-02 20:19:53 +08:00
raise Exception('Permission denied.')
2021-01-19 22:20:11 +08:00
2026-05-12 19:25:31 +08:00
self.cursor.execute('SELECT [name], [is_admin] FROM user;')
2021-02-02 20:19:53 +08:00
return tuple(map(lambda x: (x[0], x[1] == 1), self.cursor.fetchall()))
@SafeDatabaseOperation
def admin_add(self, token, newname):
username = self.tokenOper_get_username(token)
2021-02-03 16:08:40 +08:00
if not self.tokenOper_is_admin(username):
2021-02-02 20:19:53 +08:00
raise Exception('Permission denied.')
newpassword = utils.ComputePasswordHash(utils.GenerateUUID())
self.cursor.execute('INSERT INTO user VALUES (?, ?, ?, ?);', (
newname,
newpassword,
0,
utils.GenerateSalt()
))
return (newname, False)
@SafeDatabaseOperation
2021-02-03 16:08:40 +08:00
def admin_update(self, token, _username, **optArgs):
2021-02-02 20:19:53 +08:00
username = self.tokenOper_get_username(token)
2021-02-03 16:08:40 +08:00
if not self.tokenOper_is_admin(username):
2021-02-02 20:19:53 +08:00
raise Exception('Permission denied.')
# construct data
sqlList = []
argumentsList = []
# analyse opt arg
2021-02-03 16:08:40 +08:00
cache = optArgs.get('password', None)
2021-02-02 20:19:53 +08:00
if cache is not None:
2026-05-12 19:25:31 +08:00
sqlList.append('[password] = ?')
2021-02-02 20:19:53 +08:00
argumentsList.append(utils.ComputePasswordHash(cache))
2021-02-03 16:08:40 +08:00
cache = optArgs.get('isAdmin', None)
2021-02-02 20:19:53 +08:00
if cache is not None:
2026-05-12 19:25:31 +08:00
sqlList.append('[is_admin] = ?')
2021-02-02 20:19:53 +08:00
argumentsList.append(1 if cache else 0)
# execute
2021-02-03 16:08:40 +08:00
argumentsList.append(_username)
2026-05-12 19:25:31 +08:00
self.cursor.execute('UPDATE user SET {} WHERE [name] = ?;'.format(', '.join(sqlList)),
2021-02-02 20:19:53 +08:00
tuple(argumentsList))
LOGGER.debug(cache)
LOGGER.debug(tuple(argumentsList))
2021-02-02 20:19:53 +08:00
if self.cursor.rowcount != 1:
raise Exception('Fail to update due to no matched rows or too much rows.')
return True
@SafeDatabaseOperation
def admin_delete(self, token, username):
_username = self.tokenOper_get_username(token)
2021-02-03 16:08:40 +08:00
if not self.tokenOper_is_admin(_username):
2021-02-02 20:19:53 +08:00
raise Exception('Permission denied.')
# delete
2026-05-12 19:25:31 +08:00
self.cursor.execute('DELETE FROM user WHERE [name] = ?;', (username, ))
2021-02-02 20:19:53 +08:00
if self.cursor.rowcount != 1:
raise Exception('Fail to delete due to no matched rows or too much rows.')
return True
2021-01-16 22:15:10 +08:00
2021-03-08 21:56:03 +08:00
# =============================== profile
@SafeDatabaseOperation
def profile_isAdmin(self, token):
username = self.tokenOper_get_username(token)
return self.tokenOper_is_admin(username)
@SafeDatabaseOperation
def profile_changePassword(self, token, newpassword):
username = self.tokenOper_get_username(token)
2026-05-12 19:25:31 +08:00
self.cursor.execute('UPDATE user SET [password] = ? WHERE [name] = ?;', (
2021-03-08 21:56:03 +08:00
utils.ComputePasswordHash(newpassword),
username
))
return True
@SafeDatabaseOperation
def profile_getToken(self, token):
username = self.tokenOper_get_username(token)
2026-05-12 19:25:31 +08:00
self.cursor.execute('SELECT * FROM token WHERE [user] = ?;', (
2021-03-08 21:56:03 +08:00
username,
))
return self.cursor.fetchall()
@SafeDatabaseOperation
def profile_deleteToken(self, token, deleteToken):
_username = self.tokenOper_get_username(token)
# delete
2026-05-12 19:25:31 +08:00
self.cursor.execute('DELETE FROM token WHERE [user] = ? AND [token] = ?;', (
2021-03-08 21:56:03 +08:00
_username,
deleteToken
))
if self.cursor.rowcount != 1:
raise Exception('Fail to delete due to no matched rows or too much rows.')
return True