2021-01-16 22:15:10 +08:00
|
|
|
import config
|
|
|
|
|
import sqlite3
|
|
|
|
|
import json
|
2021-01-19 22:20:11 +08:00
|
|
|
import utils
|
|
|
|
|
import threading
|
2021-01-24 14:38:08 +08:00
|
|
|
import logging
|
2021-01-19 22:20:11 +08:00
|
|
|
|
|
|
|
|
def SafeDatabaseOperation(func):
|
|
|
|
|
def wrapper(self, *args, **kwargs):
|
|
|
|
|
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
|
2021-01-24 14:38:08 +08:00
|
|
|
if config.CustomConfig['debug']:
|
|
|
|
|
logging.exception(e)
|
|
|
|
|
return (False, str(e), None)
|
2021-01-19 22:20:11 +08:00
|
|
|
|
|
|
|
|
# do real data work
|
|
|
|
|
try:
|
2021-01-24 14:38:08 +08:00
|
|
|
if self.isFirstRun:
|
|
|
|
|
self.isFirstRun = False
|
|
|
|
|
print('Cleaning outdated token...')
|
|
|
|
|
self.tokenOper_clean()
|
|
|
|
|
|
|
|
|
|
result = (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()
|
2021-01-24 14:38:08 +08:00
|
|
|
if config.CustomConfig['debug']:
|
|
|
|
|
logging.exception(e)
|
|
|
|
|
return (False, str(e), None)
|
2021-01-19 22:20:11 +08:00
|
|
|
|
|
|
|
|
return wrapper
|
2021-01-16 22:15:10 +08:00
|
|
|
|
|
|
|
|
class CalendarDatabase(object):
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.db = None
|
2021-01-19 22:20:11 +08:00
|
|
|
self.cursor = None
|
|
|
|
|
self.mutex = threading.Lock()
|
2021-01-24 14:38:08 +08:00
|
|
|
self.isFirstRun = True
|
2021-01-16 22:15:10 +08:00
|
|
|
|
|
|
|
|
def open(self):
|
|
|
|
|
if (self.is_database_valid()):
|
|
|
|
|
raise Exception('Databade is opened')
|
|
|
|
|
|
|
|
|
|
if config.CustomConfig['database-type'] == 'sqlite':
|
|
|
|
|
self.db = sqlite3.connect(config.CustomConfig['database-config']['url'])
|
|
|
|
|
elif config.CustomConfig['database-type'] == 'mysql':
|
|
|
|
|
raise Exception('Not implemented database')
|
|
|
|
|
else:
|
|
|
|
|
raise Exception('Unknow database type')
|
|
|
|
|
|
|
|
|
|
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
|
2021-01-16 22:15:10 +08:00
|
|
|
self.open()
|
2021-01-19 22:20:11 +08:00
|
|
|
cursor = self.db.cursor()
|
2021-01-16 22:15:10 +08:00
|
|
|
with open('sql/sqlite.sql', 'r', encoding='utf-8') as fsql:
|
|
|
|
|
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
|
|
|
|
|
self.cursor.execute('DELETE FROM token WHERE [ccn_tokenExpireOn] <= ?',(utils.GetCurrentTimestamp(), ))
|
|
|
|
|
|
|
|
|
|
def tokenOper_postpone_expireOn(self, token):
|
|
|
|
|
self.cursor.execute('UPDATE token SET [ccn_tokenExpireOn] = ? WHERE [ccn_token] = ?;', (
|
|
|
|
|
utils.GetTokenExpireOn(),
|
|
|
|
|
token
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
def tokenOper_check_valid(self, token):
|
|
|
|
|
self.tokenOper_get_username(token)
|
|
|
|
|
|
|
|
|
|
def tokenOper_is_admin(self, username):
|
|
|
|
|
self.cursor.execute('SELECT [ccn_isAdmin] FROM user WHERE [ccn_name] = ?;',(username, ))
|
|
|
|
|
cache = self.cursor.fetchone()[0]
|
|
|
|
|
return True if cache == 1 else False
|
|
|
|
|
|
|
|
|
|
def tokenOper_get_username(self, token):
|
|
|
|
|
self.cursor.execute('SELECT [ccn_user] FROM token WHERE [ccn_token] = ? AND [ccn_tokenExpireOn] > ?;',(
|
2021-01-19 22:20:11 +08:00
|
|
|
token,
|
|
|
|
|
utils.GetCurrentTimestamp()
|
|
|
|
|
))
|
|
|
|
|
return self.cursor.fetchone()[0]
|
2021-01-24 14:38:08 +08:00
|
|
|
|
2021-01-19 22:20:11 +08:00
|
|
|
# =============================== # =============================== operation function
|
|
|
|
|
# =============================== common
|
|
|
|
|
@SafeDatabaseOperation
|
|
|
|
|
def common_salt(self, username):
|
|
|
|
|
salt = utils.GenerateSalt()
|
|
|
|
|
self.cursor.execute('UPDATE user SET [ccn_salt] = ? WHERE [ccn_name] = ?;', (
|
|
|
|
|
salt,
|
|
|
|
|
username
|
|
|
|
|
))
|
|
|
|
|
return salt
|
|
|
|
|
|
|
|
|
|
@SafeDatabaseOperation
|
|
|
|
|
def common_login(self, username, password):
|
|
|
|
|
self.cursor.execute('SELECT [ccn_password], [ccn_salt] FROM user WHERE [ccn_name] = ?;', (username, ))
|
|
|
|
|
(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)
|
2021-01-24 14:38:08 +08:00
|
|
|
self.cursor.execute('UPDATE user SET [ccn_salt] = ? WHERE [ccn_name] = ?;', (
|
2021-01-20 22:57:41 +08:00
|
|
|
utils.GenerateSalt(), # regenerate a new slat to prevent re-login try
|
|
|
|
|
username
|
|
|
|
|
))
|
2021-01-24 14:38:08 +08:00
|
|
|
self.cursor.execute('INSERT INTO token VALUES (?, ?, ?);', (
|
|
|
|
|
username,
|
|
|
|
|
token,
|
|
|
|
|
utils.GetTokenExpireOn(), # add 2 day from now
|
|
|
|
|
))
|
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
|
|
|
|
|
def common_webLogin(self, username, password):
|
|
|
|
|
self.cursor.execute('SELECT [ccn_name] FROM user WHERE [ccn_name] = ? AND [ccn_password] = ?;', (username, utils.ComputePasswordHash(password)))
|
|
|
|
|
|
|
|
|
|
if len(self.cursor.fetchall()) != 0:
|
2021-01-19 22:20:11 +08:00
|
|
|
token = utils.GenerateToken(username)
|
2021-01-24 14:38:08 +08:00
|
|
|
self.cursor.execute('INSERT INTO token VALUES (?, ?, ?);', (
|
|
|
|
|
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-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)
|
|
|
|
|
self.cursor.execute('DELETE FROM token WHERE [ccn_token] = ?;', (token, ))
|
2021-01-23 18:37:12 +08:00
|
|
|
return None
|
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-23 18:37:12 +08:00
|
|
|
return None
|
2021-01-19 22:20:11 +08:00
|
|
|
|
|
|
|
|
@SafeDatabaseOperation
|
|
|
|
|
def common_isAdmin(self, token):
|
2021-01-24 14:38:08 +08:00
|
|
|
username = self.tokenOper_get_username(token)
|
|
|
|
|
return self.tokenOper_is_admin(username)
|
2021-01-19 22:20:11 +08:00
|
|
|
|
|
|
|
|
@SafeDatabaseOperation
|
|
|
|
|
def common_changePassword(self, token, newpassword):
|
2021-01-24 14:38:08 +08:00
|
|
|
username = self.tokenOper_get_username(token)
|
2021-01-19 22:20:11 +08:00
|
|
|
self.cursor.execute('UPDATE user SET [ccn_password] = ? WHERE [ccn_name] = ?;', (
|
|
|
|
|
newpassword,
|
|
|
|
|
username
|
|
|
|
|
))
|
2021-01-20 22:57:41 +08:00
|
|
|
return True
|
2021-01-19 22:20:11 +08:00
|
|
|
|
|
|
|
|
# =============================== calendar
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================== collection
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================== 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)
|
2021-01-20 22:57:41 +08:00
|
|
|
self.cursor.execute('SELECT * FROM todo WHERE [ccn_belongTo] = ?;', (username, ))
|
|
|
|
|
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)
|
2021-01-20 22:57:41 +08:00
|
|
|
self.cursor.execute('SELECT [ccn_uuid] FROM todo WHERE [ccn_belongTo] = ?;', (username, ))
|
|
|
|
|
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)
|
2021-01-20 22:57:41 +08:00
|
|
|
self.cursor.execute('SELECT * FROM todo WHERE [ccn_belongTo] = ? AND [ccn_uuid] = ?;', (username, uuid))
|
|
|
|
|
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
|
|
|
# check sync conflict
|
|
|
|
|
self.cursor.execute('SELECT [ccn_uuid] FROM todo WHERE [ccn_uuid] = ? AND [ccn_lastChange] = ?;', (
|
|
|
|
|
uuid,
|
|
|
|
|
lastChange
|
|
|
|
|
))
|
|
|
|
|
if len(self.cursor.fetchall()) == 0:
|
2021-01-23 18:37:12 +08:00
|
|
|
raise Exception()
|
2021-01-20 22:57:41 +08:00
|
|
|
|
|
|
|
|
# update
|
2021-01-23 18:37:12 +08:00
|
|
|
newLastChange = utils.GenerateUUID()
|
|
|
|
|
self.cursor.execute('UPDATE todo SET [ccn_data] = ?, [ccn_lastChange] = ? WHERE [ccn_uuid] = ?;', (
|
2021-01-20 22:57:41 +08:00
|
|
|
data,
|
2021-01-23 18:37:12 +08:00
|
|
|
newLastChange,
|
2021-01-20 22:57:41 +08:00
|
|
|
uuid
|
|
|
|
|
))
|
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-20 22:57:41 +08:00
|
|
|
# check sync conflict
|
|
|
|
|
self.cursor.execute('SELECT [ccn_uuid] FROM todo WHERE [ccn_uuid] = ? AND [ccn_lastChange] = ?;', (
|
|
|
|
|
uuid,
|
|
|
|
|
lastChange
|
|
|
|
|
))
|
|
|
|
|
if len(self.cursor.fetchall()) == 0:
|
2021-01-23 18:37:12 +08:00
|
|
|
raise Exception()
|
2021-01-19 22:20:11 +08:00
|
|
|
|
2021-01-20 22:57:41 +08:00
|
|
|
# delete
|
|
|
|
|
self.cursor.execute('DELETE FROM todo WHERE [ccn_uuid] = ?;', (uuid, ))
|
2021-01-23 18:37:12 +08:00
|
|
|
return None
|
2021-01-19 22:20:11 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# =============================== admin
|
|
|
|
|
|
2021-01-16 22:15:10 +08:00
|
|
|
|