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

709 lines
25 KiB
Python
Raw Permalink 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 Callable, ParamSpec, TypeVar, Generic
import dt
import utils
import config
from logger import LOGGER
T = TypeVar('T')
P = ParamSpec('P')
R = TypeVar('R')
@dataclass(frozen=True)
class ResponseBody(Generic[T]):
"""The generic response body for API return."""
success: bool
"""True if this operation is successful, otherwise false."""
error: str
"""The error message provided when operation failed."""
data: T | None
"""The payload provided when operation successed."""
2021-01-19 22:20:11 +08:00
class DbException(Exception):
"""Error occurs when manipulating with database."""
pass
def SafeDatabaseOperation(inner: Callable[P, R]) -> Callable[P, ResponseBody[R]]:
def wrapper(*args, **kwargs) -> ResponseBody[R]:
# extract self from args
self: 'CalendarDatabase' = args[0]
# get config
cfg = config.get_config()
2021-01-19 22:20:11 +08:00
with self.mutex:
# try to fetching database and allocate database cursor
2021-01-19 22:20:11 +08:00
try:
db = self._get_db()
self._allocate_cursor()
2021-01-24 14:38:08 +08:00
except Exception as e:
self._free_cursor()
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, '', inner(*args, **kwargs))
self._free_cursor()
db.commit()
2021-01-19 22:20:11 +08:00
return result
2021-01-24 14:38:08 +08:00
except Exception as e:
self._free_cursor()
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 | None
cursor: sqlite3.Cursor | None
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.db is not None):
raise DbException('Database is already opened')
2021-01-16 22:15:10 +08:00
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 DbException('Not implemented database')
case _:
raise DbException('Unknow database type')
2021-01-16 22:15:10 +08:00
def init(self, username: str, password: str):
if (self.db is not None):
raise DbException('Database is already 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 DbException('Not implemented database')
case _:
raise DbException('Unknow database type')
2021-01-16 22:15:10 +08:00
self.open()
db = self._get_db()
self._allocate_cursor()
cursor = self._get_cursor()
# execute script for creating tables
with open(sql_file, 'r', encoding='utf-8') as fsql:
2021-01-16 22:15:10 +08:00
cursor.executescript(fsql.read())
# add default user in user table
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
))
self._free_cursor()
# commit to database
db.commit()
2021-01-16 22:15:10 +08:00
def close(self):
if (self.db is None):
LOGGER.warning('Try to close null database.')
else:
self._free_cursor()
self.db.close()
self.db = None
def _get_db(self) -> sqlite3.Connection:
if (self.db is None):
raise DbException('There is no opened database')
else:
return self.db
2021-01-16 22:15:10 +08:00
def _allocate_cursor(self) -> None:
if (self.cursor is not None):
raise DbException('There is already opened database cursor')
else:
self.cursor = self._get_db().cursor()
2021-01-16 22:15:10 +08:00
def _get_cursor(self) -> sqlite3.Cursor:
if (self.cursor is None):
raise DbException('There is no opened database cursor')
else:
return self.cursor
def _free_cursor(self) -> None:
if (self.cursor is None):
LOGGER.warning('Try to free null databse cursor.')
else:
self.cursor.close()
self.cursor = None
2021-01-16 22:15:10 +08:00
2021-01-24 14:38:08 +08:00
# ======================= token related internal operation
def tokenOper_clean(self):
# remove outdated token
cursor = self._get_cursor()
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):
cursor = self._get_cursor()
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):
cursor = self._get_cursor()
cursor.execute('SELECT [is_admin] FROM user WHERE [name] = ?;',(username, ))
cache = 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):
cursor = self._get_cursor()
cursor.execute('SELECT [user] FROM token WHERE [token] = ? AND [token_expire_on] > ?;',(
2021-01-19 22:20:11 +08:00
token,
utils.GetCurrentTimestamp()
))
result = cursor.fetchone()[0]
2021-01-25 20:42:06 +08:00
# 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):
cursor = self._get_cursor()
2021-01-19 22:20:11 +08:00
salt = utils.GenerateSalt()
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):
cursor = self._get_cursor()
cursor.execute('SELECT [password], [salt] FROM user WHERE [name] = ?;', (username, ))
(gotten_salt, gotten_password) = cursor.fetchone()
2021-01-19 22:20:11 +08:00
if password == utils.ComputePasswordHashWithSalt(gotten_password, gotten_salt):
2021-01-20 22:57:41 +08:00
token = utils.GenerateToken(username)
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
))
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
raise DbException('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):
cursor = self._get_cursor()
LOGGER.debug(f'WebLogin Username: {username}')
LOGGER.debug(f'WebLogin Password: {password}')
passwordHash = utils.ComputePasswordHash(password)
LOGGER.debug(f'WebLogin Password Hash: {passwordHash}')
cursor.execute('SELECT [name] FROM user WHERE [name] = ? AND [password] = ?;', (username, passwordHash))
2021-01-20 22:57:41 +08:00
if len(cursor.fetchall()) != 0:
2021-01-19 22:20:11 +08:00
token = utils.GenerateToken(username)
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
raise DbException('Login authentication failed')
2021-01-19 22:20:11 +08:00
@SafeDatabaseOperation
def common_logout(self, token):
cursor = self._get_cursor()
2021-01-24 14:38:08 +08:00
self.tokenOper_check_valid(token)
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):
cursor = self._get_cursor()
2021-02-02 12:11:13 +08:00
username = self.tokenOper_get_username(token)
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 cursor.fetchall()
2021-02-02 12:11:13 +08:00
@SafeDatabaseOperation
def calendar_getList(self, token, startDateTime, endDateTime):
cursor = self._get_cursor()
2021-02-02 12:11:13 +08:00
username = self.tokenOper_get_username(token)
cursor.execute('SELECT calendar.uuid 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 tuple(map(lambda x: x[0], cursor.fetchall()))
2021-02-02 12:11:13 +08:00
@SafeDatabaseOperation
def calendar_getDetail(self, token, uuid):
cursor = self._get_cursor()
2021-02-02 12:11:13 +08:00
self.tokenOper_check_valid(token)
cursor.execute('SELECT * FROM calendar WHERE [uuid] = ?;', (uuid, ))
return cursor.fetchone()
2021-02-02 12:11:13 +08:00
@SafeDatabaseOperation
def calendar_update(self, token, uuid, lastChange, **optArgs):
cursor = self._get_cursor()
2021-02-02 12:11:13 +08:00
self.tokenOper_check_valid(token)
2021-01-19 22:20:11 +08:00
2021-02-02 12:11:13 +08:00
# get prev data
cursor.execute('SELECT * FROM calendar WHERE [uuid] = ? AND [last_change] = ?;', (uuid, lastChange))
analyseData = list(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)
cursor.execute('UPDATE calendar SET {} WHERE [uuid] = ?;'.format(', '.join(sqlList)),
2021-02-02 12:11:13 +08:00
tuple(argumentsList))
if cursor.rowcount != 1:
raise DbException('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):
cursor = self._get_cursor()
2021-02-02 12:11:13 +08:00
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
cursor.execute('INSERT INTO calendar VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);',
2021-02-02 12:11:13 +08:00
(newuuid,
belongTo,
title,
description,
lastupdate,
eventDateTimeStart,
eventDateTimeEnd,
timezoneOffset,
loopRules,
loopDateTimeStart,
loopDateTimeEnd))
return newuuid
@SafeDatabaseOperation
def calendar_delete(self, token, uuid, lastChange):
cursor = self._get_cursor()
2021-02-02 12:11:13 +08:00
self.tokenOper_check_valid(token)
cursor.execute('DELETE FROM calendar WHERE [uuid] = ? AND [last_change] = ?;', (uuid, lastChange))
if cursor.rowcount != 1:
raise DbException('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):
cursor = self._get_cursor()
2021-02-02 20:19:53 +08:00
username = self.tokenOper_get_username(token)
cursor.execute('SELECT [uuid], [name], [last_change] FROM collection WHERE [user] = ?;', (username, ))
return cursor.fetchall()
2021-02-02 20:19:53 +08:00
@SafeDatabaseOperation
def collection_getListOwn(self, token):
cursor = self._get_cursor()
2021-02-02 20:19:53 +08:00
username = self.tokenOper_get_username(token)
cursor.execute('SELECT [uuid] FROM collection WHERE [user] = ?;', (username, ))
return tuple(map(lambda x: x[0], cursor.fetchall()))
2021-02-02 20:19:53 +08:00
@SafeDatabaseOperation
def collection_getDetailOwn(self, token, uuid):
cursor = self._get_cursor()
2021-02-02 20:19:53 +08:00
username = self.tokenOper_get_username(token)
cursor.execute('SELECT [uuid], [name], [last_change] FROM collection WHERE [user] = ? AND [uuid] = ?;', (username, uuid))
return cursor.fetchone()
2021-02-02 20:19:53 +08:00
@SafeDatabaseOperation
def collection_addOwn(self, token, newname):
cursor = self._get_cursor()
2021-02-02 20:19:53 +08:00
username = self.tokenOper_get_username(token)
newuuid = utils.GenerateUUID()
lastupdate = utils.GenerateUUID()
cursor.execute('INSERT INTO collection VALUES (?, ?, ?, ?);',
2021-02-02 20:19:53 +08:00
(newuuid, newname, username, lastupdate))
return newuuid
@SafeDatabaseOperation
def collection_updateOwn(self, token, uuid, newname, lastChange):
cursor = self._get_cursor()
2021-02-02 20:19:53 +08:00
self.tokenOper_check_valid(token)
lastupdate = utils.GenerateUUID()
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 cursor.rowcount != 1:
raise DbException('Fail to update due to no matched rows or too much rows.')
2021-02-02 20:19:53 +08:00
return lastupdate
@SafeDatabaseOperation
def collection_deleteOwn(self, token, uuid, lastChange):
cursor = self._get_cursor()
2021-02-02 20:19:53 +08:00
self.tokenOper_check_valid(token)
2021-01-19 22:20:11 +08:00
cursor.execute('DELETE FROM collection WHERE [uuid] = ? AND [last_change] = ?;', (
2021-02-02 20:19:53 +08:00
uuid,
lastChange
))
if cursor.rowcount != 1:
raise DbException('Fail to delete due to no matched rows or too much rows.')
2021-02-02 20:19:53 +08:00
return True
@SafeDatabaseOperation
def collection_getSharing(self, token, uuid):
cursor = self._get_cursor()
2021-02-02 20:19:53 +08:00
self.tokenOper_check_valid(token)
cursor.execute('SELECT [target] FROM share WHERE [uuid] = ?;', (uuid, ))
return tuple(map(lambda x: x[0], cursor.fetchall()))
2021-02-02 20:19:53 +08:00
@SafeDatabaseOperation
def collection_deleteSharing(self, token, uuid, target, lastChange):
cursor = self._get_cursor()
2021-02-02 20:19:53 +08:00
self.tokenOper_check_valid(token)
lastupdate = utils.GenerateUUID()
cursor.execute('UPDATE collection SET [last_change] = ?, WHERE [uuid] = ? AND [last_change] = ?;', (lastupdate, uuid, lastChange))
if cursor.rowcount != 1:
raise DbException('Fail to delete due to no matched rows or too much rows.')
2021-02-02 20:19:53 +08:00
cursor.execute('DELETE FROM share WHERE [uuid] = ? AND [target] = ?;', (uuid, target))
if cursor.rowcount != 1:
raise DbException('Fail to delete due to no matched rows or too much rows.')
2021-02-02 20:19:53 +08:00
return lastupdate
@SafeDatabaseOperation
def collection_addSharing(self, token, uuid, target, lastChange):
cursor = self._get_cursor()
2021-02-02 20:19:53 +08:00
self.tokenOper_check_valid(token)
lastupdate = utils.GenerateUUID()
cursor.execute('UPDATE collection SET [last_change] = ? WHERE [uuid] = ? AND [last_change] = ?;', (lastupdate, uuid, lastChange))
if cursor.rowcount != 1:
raise DbException('Fail to delete due to no matched rows or too much rows.')
2021-02-02 20:19:53 +08:00
cursor.execute('SELECT * FROM share WHERE [uuid] = ? AND [target] = ?;', (uuid, target))
if len(cursor.fetchall()) != 0:
raise DbException('Fail to insert duplicated item.')
cursor.execute('INSERT INTO share VALUES (?, ?);', (uuid, target))
2021-02-02 20:19:53 +08:00
return lastupdate
@SafeDatabaseOperation
def collection_getShared(self, token):
cursor = self._get_cursor()
2021-02-02 20:19:53 +08:00
username = self.tokenOper_get_username(token)
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, ))
return 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):
cursor = self._get_cursor()
2021-01-24 14:38:08 +08:00
username = self.tokenOper_get_username(token)
cursor.execute('SELECT * FROM todo WHERE [belong_to] = ?;', (username, ))
return cursor.fetchall()
2021-01-19 22:20:11 +08:00
2021-01-20 22:57:41 +08:00
@SafeDatabaseOperation
def todo_getList(self, token):
cursor = self._get_cursor()
2021-01-24 14:38:08 +08:00
username = self.tokenOper_get_username(token)
cursor.execute('SELECT [uuid] FROM todo WHERE [belong_to] = ?;', (username, ))
return tuple(map(lambda x: x[0], cursor.fetchall()))
2021-01-20 22:57:41 +08:00
@SafeDatabaseOperation
def todo_getDetail(self, token, uuid):
cursor = self._get_cursor()
2021-01-24 14:38:08 +08:00
username = self.tokenOper_get_username(token)
cursor.execute('SELECT * FROM todo WHERE [belong_to] = ? AND [uuid] = ?;', (username, uuid))
return cursor.fetchone()
2021-01-20 22:57:41 +08:00
@SafeDatabaseOperation
def todo_add(self, token):
cursor = self._get_cursor()
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
)
cursor.execute('INSERT INTO todo VALUES (?, ?, ?, ?);', returnedData)
2021-01-23 18:37:12 +08:00
return returnedData
2021-01-20 22:57:41 +08:00
@SafeDatabaseOperation
def todo_update(self, token, uuid, data, lastChange):
cursor = self._get_cursor()
2021-01-20 22:57:41 +08:00
# 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()
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
))
if cursor.rowcount != 1:
raise DbException('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):
cursor = self._get_cursor()
2021-01-20 22:57:41 +08:00
# 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
cursor.execute('DELETE FROM todo WHERE [uuid] = ? AND [last_change] = ?;', (uuid, lastChange))
if cursor.rowcount != 1:
raise DbException('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):
cursor = self._get_cursor()
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):
raise DbException('Permission denied.')
2021-01-19 22:20:11 +08:00
cursor.execute('SELECT [name], [is_admin] FROM user;')
return tuple(map(lambda x: (x[0], x[1] == 1), cursor.fetchall()))
2021-02-02 20:19:53 +08:00
@SafeDatabaseOperation
def admin_add(self, token, newname):
cursor = self._get_cursor()
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):
raise DbException('Permission denied.')
2021-02-02 20:19:53 +08:00
newpassword = utils.ComputePasswordHash(utils.GenerateUUID())
cursor.execute('INSERT INTO user VALUES (?, ?, ?, ?);', (
2021-02-02 20:19:53 +08:00
newname,
newpassword,
0,
utils.GenerateSalt()
))
return (newname, False)
@SafeDatabaseOperation
2021-02-03 16:08:40 +08:00
def admin_update(self, token, _username, **optArgs):
cursor = self._get_cursor()
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):
raise DbException('Permission denied.')
2021-02-02 20:19:53 +08:00
# 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)
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))
if cursor.rowcount != 1:
raise DbException('Fail to update due to no matched rows or too much rows.')
2021-02-02 20:19:53 +08:00
return True
@SafeDatabaseOperation
def admin_delete(self, token, username):
cursor = self._get_cursor()
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):
raise DbException('Permission denied.')
2021-02-02 20:19:53 +08:00
# delete
cursor.execute('DELETE FROM user WHERE [name] = ?;', (username, ))
if cursor.rowcount != 1:
raise DbException('Fail to delete due to no matched rows or too much rows.')
2021-02-02 20:19:53 +08:00
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):
cursor = self._get_cursor()
2021-03-08 21:56:03 +08:00
username = self.tokenOper_get_username(token)
return self.tokenOper_is_admin(username)
@SafeDatabaseOperation
def profile_changePassword(self, token, newpassword):
cursor = self._get_cursor()
2021-03-08 21:56:03 +08:00
username = self.tokenOper_get_username(token)
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):
cursor = self._get_cursor()
2021-03-08 21:56:03 +08:00
username = self.tokenOper_get_username(token)
cursor.execute('SELECT * FROM token WHERE [user] = ?;', (
2021-03-08 21:56:03 +08:00
username,
))
return cursor.fetchall()
2021-03-08 21:56:03 +08:00
@SafeDatabaseOperation
def profile_deleteToken(self, token, deleteToken):
cursor = self._get_cursor()
2021-03-08 21:56:03 +08:00
_username = self.tokenOper_get_username(token)
# delete
cursor.execute('DELETE FROM token WHERE [user] = ? AND [token] = ?;', (
2021-03-08 21:56:03 +08:00
_username,
deleteToken
))
if cursor.rowcount != 1:
raise DbException('Fail to delete due to no matched rows or too much rows.')
2021-03-08 21:56:03 +08:00
return True