feat: support login
- migrate old API into typescript but not finished (only webLogin works now) - seperate the logger of backend due to the shitty behavior of Flask (change logging level)
This commit is contained in:
@@ -1,12 +1,14 @@
|
|||||||
import sys
|
import sys
|
||||||
import logging
|
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
from typing import cast
|
from typing import cast
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import server
|
import server
|
||||||
import config
|
import config
|
||||||
import utils
|
import utils
|
||||||
import database
|
import database
|
||||||
|
import logger
|
||||||
|
from logger import LOGGER, LoggerLevel
|
||||||
|
|
||||||
|
|
||||||
def GetUsernamePassword() -> tuple[str, str]:
|
def GetUsernamePassword() -> tuple[str, str]:
|
||||||
@@ -26,15 +28,10 @@ def GetUsernamePassword() -> tuple[str, str]:
|
|||||||
|
|
||||||
return (username, password)
|
return (username, password)
|
||||||
|
|
||||||
|
|
||||||
def SetLoggingStyle(level: int) -> None:
|
|
||||||
logging.basicConfig(format="[%(levelname)s] %(message)s", level=level)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Set as INFO level in default first,
|
# Set as INFO level in default first,
|
||||||
# and we will change it once we load the configuration file.
|
# and we will change it once we load the configuration file.
|
||||||
SetLoggingStyle(logging.INFO)
|
logger.set_level(LoggerLevel.INFO)
|
||||||
|
|
||||||
# Receive arguments
|
# Receive arguments
|
||||||
parser = ArgumentParser(
|
parser = ArgumentParser(
|
||||||
@@ -60,21 +57,21 @@ if __name__ == "__main__":
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
# Show splash
|
# Show splash
|
||||||
logging.info("Coconut-leaf")
|
LOGGER.info("Coconut-leaf")
|
||||||
logging.info("A light, self-host and multi-account calendar system")
|
LOGGER.info("A light, self-host and multi-account calendar system")
|
||||||
logging.info("Project: https://github.com/yyc12345/coconut-leaf")
|
LOGGER.info("Project: https://github.com/yyc12345/coconut-leaf")
|
||||||
logging.info("===================")
|
LOGGER.info("===================")
|
||||||
|
|
||||||
# Load config file
|
# Load config file
|
||||||
try:
|
try:
|
||||||
config.setup_config(cast(Path, args.config))
|
config.setup_config(cast(Path, args.config))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.critical(f"Error loading config file: {e}")
|
LOGGER.critical(f"Error loading config file: {e}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Change logging level again according to whether enable debug mode
|
# Change logging level again according to whether enable debug mode
|
||||||
logging_level = logging.DEBUG if config.get_config().others.debug else logging.INFO
|
logging_level = LoggerLevel.DEBUG if config.get_config().others.debug else LoggerLevel.INFO
|
||||||
SetLoggingStyle(logging_level)
|
logger.set_level(logging_level)
|
||||||
|
|
||||||
# Initialize the calendar system if needed
|
# Initialize the calendar system if needed
|
||||||
if cast(bool, args.init):
|
if cast(bool, args.init):
|
||||||
@@ -83,5 +80,5 @@ if __name__ == "__main__":
|
|||||||
calendar.init(*gotten_data)
|
calendar.init(*gotten_data)
|
||||||
calendar.close()
|
calendar.close()
|
||||||
|
|
||||||
logging.info("Staring server...")
|
LOGGER.info("Staring server...")
|
||||||
server.run()
|
server.run()
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
import config
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import utils
|
|
||||||
import threading
|
import threading
|
||||||
import logging
|
|
||||||
import dt
|
|
||||||
from typing import cast
|
from typing import cast
|
||||||
from pathlib import Path
|
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."""
|
||||||
|
|
||||||
def SafeDatabaseOperation(func):
|
def SafeDatabaseOperation(func):
|
||||||
def wrapper(self: 'CalendarDatabase', *args, **kwargs):
|
def wrapper(self: 'CalendarDatabase', *args, **kwargs):
|
||||||
@@ -19,18 +31,18 @@ def SafeDatabaseOperation(func):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.cursor = None
|
self.cursor = None
|
||||||
if cfg.others.debug:
|
if cfg.others.debug:
|
||||||
logging.exception(e)
|
LOGGER.exception(e)
|
||||||
return (False, str(e), None)
|
return ResponseBody(False, str(e), None)
|
||||||
|
|
||||||
# do real data work
|
# do real data work
|
||||||
try:
|
try:
|
||||||
currentTime = utils.GetCurrentTimestamp()
|
currentTime = utils.GetCurrentTimestamp()
|
||||||
if currentTime - self.latestClean > cfg.others.auto_token_clean_duration:
|
if currentTime - self.latestClean > cfg.others.auto_token_clean_duration:
|
||||||
self.latestClean = currentTime
|
self.latestClean = currentTime
|
||||||
logging.info('Cleaning outdated token...')
|
LOGGER.info('Cleaning outdated token...')
|
||||||
self.tokenOper_clean()
|
self.tokenOper_clean()
|
||||||
|
|
||||||
result = (True, '', func(self, *args, **kwargs))
|
result = ResponseBody(True, '', func(self, *args, **kwargs))
|
||||||
self.cursor.close()
|
self.cursor.close()
|
||||||
self.cursor = None
|
self.cursor = None
|
||||||
self.db.commit()
|
self.db.commit()
|
||||||
@@ -40,8 +52,8 @@ def SafeDatabaseOperation(func):
|
|||||||
self.cursor = None
|
self.cursor = None
|
||||||
self.db.rollback()
|
self.db.rollback()
|
||||||
if cfg.others.debug:
|
if cfg.others.debug:
|
||||||
logging.exception(e)
|
LOGGER.exception(e)
|
||||||
return (False, str(e), None)
|
return ResponseBody(False, str(e), None)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
@@ -182,7 +194,12 @@ class CalendarDatabase:
|
|||||||
|
|
||||||
@SafeDatabaseOperation
|
@SafeDatabaseOperation
|
||||||
def common_webLogin(self, username, password, clientUa, clientIp):
|
def common_webLogin(self, username, password, clientUa, clientIp):
|
||||||
self.cursor.execute('SELECT [name] FROM user WHERE [name] = ? AND [password] = ?;', (username, utils.ComputePasswordHash(password)))
|
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))
|
||||||
|
|
||||||
if len(self.cursor.fetchall()) != 0:
|
if len(self.cursor.fetchall()) != 0:
|
||||||
token = utils.GenerateToken(username)
|
token = utils.GenerateToken(username)
|
||||||
@@ -553,8 +570,8 @@ class CalendarDatabase:
|
|||||||
argumentsList.append(_username)
|
argumentsList.append(_username)
|
||||||
self.cursor.execute('UPDATE user SET {} WHERE [name] = ?;'.format(', '.join(sqlList)),
|
self.cursor.execute('UPDATE user SET {} WHERE [name] = ?;'.format(', '.join(sqlList)),
|
||||||
tuple(argumentsList))
|
tuple(argumentsList))
|
||||||
logging.debug(cache)
|
LOGGER.debug(cache)
|
||||||
logging.debug(tuple(argumentsList))
|
LOGGER.debug(tuple(argumentsList))
|
||||||
if self.cursor.rowcount != 1:
|
if self.cursor.rowcount != 1:
|
||||||
raise Exception('Fail to update due to no matched rows or too much rows.')
|
raise Exception('Fail to update due to no matched rows or too much rows.')
|
||||||
return True
|
return True
|
||||||
|
|||||||
42
backend/logger.py
Normal file
42
backend/logger.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import logging
|
||||||
|
import enum
|
||||||
|
|
||||||
|
|
||||||
|
def _build_logger() -> tuple[logging.Logger, logging.Handler]:
|
||||||
|
# Create a new logger which is independent with Flask
|
||||||
|
logger = logging.getLogger("my_console_logger")
|
||||||
|
# Avoid message was propagated to root logger or captured by Flask logger.
|
||||||
|
logger.propagate = False
|
||||||
|
# Set initial level.
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
|
# Create StreamHandler to output into stderr.
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setLevel(logging.DEBUG)
|
||||||
|
# Set format for it.
|
||||||
|
formatter = logging.Formatter("[%(levelname)s] %(message)s")
|
||||||
|
console_handler.setFormatter(formatter)
|
||||||
|
# Add handler
|
||||||
|
logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
return (logger, console_handler)
|
||||||
|
|
||||||
|
|
||||||
|
(LOGGER, CONSOLE_HANDLER) = _build_logger()
|
||||||
|
|
||||||
|
|
||||||
|
class LoggerLevel(enum.IntEnum):
|
||||||
|
DEBUG = enum.auto()
|
||||||
|
INFO = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
|
def set_level(level: LoggerLevel) -> None:
|
||||||
|
logging_level: int = logging.INFO
|
||||||
|
match level:
|
||||||
|
case LoggerLevel.DEBUG:
|
||||||
|
logging_level = logging.DEBUG
|
||||||
|
case LoggerLevel.INFO:
|
||||||
|
logging_level = logging.INFO
|
||||||
|
|
||||||
|
LOGGER.setLevel(logging_level)
|
||||||
|
CONSOLE_HANDLER.setLevel(logging_level)
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask import request
|
from flask import request
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
import config
|
import config
|
||||||
import database
|
import database
|
||||||
import utils
|
import utils
|
||||||
|
from logger import LOGGER
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
calendar_db = database.CalendarDatabase()
|
calendar_db = database.CalendarDatabase()
|
||||||
@@ -14,7 +18,7 @@ calendar_db = database.CalendarDatabase()
|
|||||||
@app.route('/common/salt', methods=['POST'])
|
@app.route('/common/salt', methods=['POST'])
|
||||||
def api_common_saltHandle():
|
def api_common_saltHandle():
|
||||||
return SmartDbCaller(calendar_db.common_salt,
|
return SmartDbCaller(calendar_db.common_salt,
|
||||||
(('username', str, False), ),
|
(FormField('username', str, False), ),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/common/login', methods=['POST'])
|
@app.route('/common/login', methods=['POST'])
|
||||||
@@ -27,10 +31,10 @@ def api_common_loginHandle():
|
|||||||
clientIp = request.remote_addr
|
clientIp = request.remote_addr
|
||||||
|
|
||||||
return SmartDbCaller(calendar_db.common_login,
|
return SmartDbCaller(calendar_db.common_login,
|
||||||
(('username', str, False),
|
(FormField('username', str, False),
|
||||||
('password', str, False),
|
FormField('password', str, False),
|
||||||
('clientUa', str, False),
|
FormField('clientUa', str, False),
|
||||||
('clientIp', str, False)),
|
FormField('clientIp', str, False)),
|
||||||
{
|
{
|
||||||
'clientUa': clientUa,
|
'clientUa': clientUa,
|
||||||
'clientIp': clientIp
|
'clientIp': clientIp
|
||||||
@@ -44,12 +48,12 @@ def api_common_webLoginHandle():
|
|||||||
clientIp = request.headers.getlist("X-Forwarded-For")[0]
|
clientIp = request.headers.getlist("X-Forwarded-For")[0]
|
||||||
else:
|
else:
|
||||||
clientIp = request.remote_addr
|
clientIp = request.remote_addr
|
||||||
|
|
||||||
return SmartDbCaller(calendar_db.common_webLogin,
|
return SmartDbCaller(calendar_db.common_webLogin,
|
||||||
(('username', str, False),
|
(FormField('username', str, False),
|
||||||
('password', str, False),
|
FormField('password', str, False),
|
||||||
('clientUa', str, False),
|
FormField('clientUa', str, False),
|
||||||
('clientIp', str, False)),
|
FormField('clientIp', str, False)),
|
||||||
{
|
{
|
||||||
'clientUa': clientUa,
|
'clientUa': clientUa,
|
||||||
'clientIp': clientIp
|
'clientIp': clientIp
|
||||||
@@ -58,13 +62,13 @@ def api_common_webLoginHandle():
|
|||||||
@app.route('/common/logout', methods=['POST'])
|
@app.route('/common/logout', methods=['POST'])
|
||||||
def api_common_logoutHandle():
|
def api_common_logoutHandle():
|
||||||
return SmartDbCaller(calendar_db.common_logout,
|
return SmartDbCaller(calendar_db.common_logout,
|
||||||
(('token', str, False), ),
|
(FormField('token', str, False), ),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/common/tokenValid', methods=['POST'])
|
@app.route('/common/tokenValid', methods=['POST'])
|
||||||
def api_common_tokenValidHandle():
|
def api_common_tokenValidHandle():
|
||||||
return SmartDbCaller(calendar_db.common_tokenValid,
|
return SmartDbCaller(calendar_db.common_tokenValid,
|
||||||
(('token', str, False), ),
|
(FormField('token', str, False), ),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
@@ -74,60 +78,60 @@ def api_common_tokenValidHandle():
|
|||||||
@app.route('/calendar/getFull', methods=['POST'])
|
@app.route('/calendar/getFull', methods=['POST'])
|
||||||
def api_calendar_getFullHandle():
|
def api_calendar_getFullHandle():
|
||||||
return SmartDbCaller(calendar_db.calendar_getFull,
|
return SmartDbCaller(calendar_db.calendar_getFull,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('startDateTime', int, False),
|
FormField('startDateTime', int, False),
|
||||||
('endDateTime', int, False)),
|
FormField('endDateTime', int, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/calendar/getList', methods=['POST'])
|
@app.route('/calendar/getList', methods=['POST'])
|
||||||
def api_calendar_getListHandle():
|
def api_calendar_getListHandle():
|
||||||
return SmartDbCaller(calendar_db.calendar_getList,
|
return SmartDbCaller(calendar_db.calendar_getList,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('startDateTime', int, False),
|
FormField('startDateTime', int, False),
|
||||||
('endDateTime', int, False)),
|
FormField('endDateTime', int, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/calendar/getDetail', methods=['POST'])
|
@app.route('/calendar/getDetail', methods=['POST'])
|
||||||
def api_calendar_getDetailHandle():
|
def api_calendar_getDetailHandle():
|
||||||
return SmartDbCaller(calendar_db.calendar_getDetail,
|
return SmartDbCaller(calendar_db.calendar_getDetail,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('uuid', str, False)),
|
FormField('uuid', str, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/calendar/update', methods=['POST'])
|
@app.route('/calendar/update', methods=['POST'])
|
||||||
def api_calendar_updateHandle():
|
def api_calendar_updateHandle():
|
||||||
return SmartDbCaller(calendar_db.calendar_update,
|
return SmartDbCaller(calendar_db.calendar_update,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('uuid', str, False),
|
FormField('uuid', str, False),
|
||||||
('belongTo', str, True),
|
FormField('belongTo', str, True),
|
||||||
('title', str, True),
|
FormField('title', str, True),
|
||||||
('description', str, True),
|
FormField('description', str, True),
|
||||||
('eventDateTimeStart', int, True),
|
FormField('eventDateTimeStart', int, True),
|
||||||
('eventDateTimeEnd', int, True),
|
FormField('eventDateTimeEnd', int, True),
|
||||||
('loopRules', str, True),
|
FormField('loopRules', str, True),
|
||||||
('timezoneOffset', int, True),
|
FormField('timezoneOffset', int, True),
|
||||||
('lastChange', str, False)),
|
FormField('lastChange', str, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/calendar/add', methods=['POST'])
|
@app.route('/calendar/add', methods=['POST'])
|
||||||
def api_calendar_addHandle():
|
def api_calendar_addHandle():
|
||||||
return SmartDbCaller(calendar_db.calendar_add,
|
return SmartDbCaller(calendar_db.calendar_add,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('belongTo', str, False),
|
FormField('belongTo', str, False),
|
||||||
('title', str, False),
|
FormField('title', str, False),
|
||||||
('description', str, False),
|
FormField('description', str, False),
|
||||||
('eventDateTimeStart', int, False),
|
FormField('eventDateTimeStart', int, False),
|
||||||
('eventDateTimeEnd', int, False),
|
FormField('eventDateTimeEnd', int, False),
|
||||||
('loopRules', str, False),
|
FormField('loopRules', str, False),
|
||||||
('timezoneOffset', int, False)),
|
FormField('timezoneOffset', int, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/calendar/delete', methods=['POST'])
|
@app.route('/calendar/delete', methods=['POST'])
|
||||||
def api_calendar_deleteHandle():
|
def api_calendar_deleteHandle():
|
||||||
return SmartDbCaller(calendar_db.calendar_delete,
|
return SmartDbCaller(calendar_db.calendar_delete,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('uuid', str, False),
|
FormField('uuid', str, False),
|
||||||
('lastChange', str, False)),
|
FormField('lastChange', str, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
@@ -137,77 +141,77 @@ def api_calendar_deleteHandle():
|
|||||||
@app.route('/collection/getFullOwn', methods=['POST'])
|
@app.route('/collection/getFullOwn', methods=['POST'])
|
||||||
def api_collection_getFullOwnHandle():
|
def api_collection_getFullOwnHandle():
|
||||||
return SmartDbCaller(calendar_db.collection_getFullOwn,
|
return SmartDbCaller(calendar_db.collection_getFullOwn,
|
||||||
(('token', str, False), ),
|
(FormField('token', str, False), ),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/collection/getListOwn', methods=['POST'])
|
@app.route('/collection/getListOwn', methods=['POST'])
|
||||||
def api_collection_getListOwnHandle():
|
def api_collection_getListOwnHandle():
|
||||||
return SmartDbCaller(calendar_db.collection_getListOwn,
|
return SmartDbCaller(calendar_db.collection_getListOwn,
|
||||||
(('token', str, False), ),
|
(FormField('token', str, False), ),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/collection/getDetailOwn', methods=['POST'])
|
@app.route('/collection/getDetailOwn', methods=['POST'])
|
||||||
def api_collection_getDetailOwnHandle():
|
def api_collection_getDetailOwnHandle():
|
||||||
return SmartDbCaller(calendar_db.collection_getDetailOwn,
|
return SmartDbCaller(calendar_db.collection_getDetailOwn,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('uuid', str, False)),
|
FormField('uuid', str, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/collection/addOwn', methods=['POST'])
|
@app.route('/collection/addOwn', methods=['POST'])
|
||||||
def api_collection_addOwnHandle():
|
def api_collection_addOwnHandle():
|
||||||
return SmartDbCaller(calendar_db.collection_addOwn,
|
return SmartDbCaller(calendar_db.collection_addOwn,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('name', str, False)),
|
FormField('name', str, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/collection/updateOwn', methods=['POST'])
|
@app.route('/collection/updateOwn', methods=['POST'])
|
||||||
def api_collection_updateOwnHandle():
|
def api_collection_updateOwnHandle():
|
||||||
return SmartDbCaller(calendar_db.collection_updateOwn,
|
return SmartDbCaller(calendar_db.collection_updateOwn,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('uuid', str, False),
|
FormField('uuid', str, False),
|
||||||
('name', str, False),
|
FormField('name', str, False),
|
||||||
('lastChange', str, False)),
|
FormField('lastChange', str, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/collection/deleteOwn', methods=['POST'])
|
@app.route('/collection/deleteOwn', methods=['POST'])
|
||||||
def api_collection_deleteOwnHandle():
|
def api_collection_deleteOwnHandle():
|
||||||
return SmartDbCaller(calendar_db.collection_deleteOwn,
|
return SmartDbCaller(calendar_db.collection_deleteOwn,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('uuid', str, False),
|
FormField('uuid', str, False),
|
||||||
('lastChange', str, False)),
|
FormField('lastChange', str, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/collection/getSharing', methods=['POST'])
|
@app.route('/collection/getSharing', methods=['POST'])
|
||||||
def api_collection_getSharingHandle():
|
def api_collection_getSharingHandle():
|
||||||
return SmartDbCaller(calendar_db.collection_getSharing,
|
return SmartDbCaller(calendar_db.collection_getSharing,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('uuid', str, False)),
|
FormField('uuid', str, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/collection/deleteSharing', methods=['POST'])
|
@app.route('/collection/deleteSharing', methods=['POST'])
|
||||||
def api_collection_deleteSharingHandle():
|
def api_collection_deleteSharingHandle():
|
||||||
return SmartDbCaller(calendar_db.collection_deleteSharing,
|
return SmartDbCaller(calendar_db.collection_deleteSharing,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('uuid', str, False),
|
FormField('uuid', str, False),
|
||||||
('target', str, False),
|
FormField('target', str, False),
|
||||||
('lastChange', str, False)),
|
FormField('lastChange', str, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/collection/addSharing', methods=['POST'])
|
@app.route('/collection/addSharing', methods=['POST'])
|
||||||
def api_collection_addSharingHandle():
|
def api_collection_addSharingHandle():
|
||||||
return SmartDbCaller(calendar_db.collection_addSharing,
|
return SmartDbCaller(calendar_db.collection_addSharing,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('uuid', str, False),
|
FormField('uuid', str, False),
|
||||||
('target', str, False),
|
FormField('target', str, False),
|
||||||
('lastChange', str, False)),
|
FormField('lastChange', str, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/collection/getShared', methods=['POST'])
|
@app.route('/collection/getShared', methods=['POST'])
|
||||||
def api_collection_getSharedHandle():
|
def api_collection_getSharedHandle():
|
||||||
return SmartDbCaller(calendar_db.collection_getShared,
|
return SmartDbCaller(calendar_db.collection_getShared,
|
||||||
(('token', str, False), ),
|
(FormField('token', str, False), ),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
@@ -217,43 +221,43 @@ def api_collection_getSharedHandle():
|
|||||||
@app.route('/todo/getFull', methods=['POST'])
|
@app.route('/todo/getFull', methods=['POST'])
|
||||||
def api_todo_getFullHandle():
|
def api_todo_getFullHandle():
|
||||||
return SmartDbCaller(calendar_db.todo_getFull,
|
return SmartDbCaller(calendar_db.todo_getFull,
|
||||||
(('token', str, False), ),
|
(FormField('token', str, False), ),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/todo/getList', methods=['POST'])
|
@app.route('/todo/getList', methods=['POST'])
|
||||||
def api_todo_getListHandle():
|
def api_todo_getListHandle():
|
||||||
return SmartDbCaller(calendar_db.todo_getList,
|
return SmartDbCaller(calendar_db.todo_getList,
|
||||||
(('token', str, False), ),
|
(FormField('token', str, False), ),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/todo/getDetail', methods=['POST'])
|
@app.route('/todo/getDetail', methods=['POST'])
|
||||||
def api_todo_getDetailHandle():
|
def api_todo_getDetailHandle():
|
||||||
return SmartDbCaller(calendar_db.todo_getDetail,
|
return SmartDbCaller(calendar_db.todo_getDetail,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('uuid', str, False)),
|
FormField('uuid', str, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/todo/add', methods=['POST'])
|
@app.route('/todo/add', methods=['POST'])
|
||||||
def api_todo_addHandle():
|
def api_todo_addHandle():
|
||||||
return SmartDbCaller(calendar_db.todo_add,
|
return SmartDbCaller(calendar_db.todo_add,
|
||||||
(('token', str, False), ),
|
(FormField('token', str, False), ),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/todo/update', methods=['POST'])
|
@app.route('/todo/update', methods=['POST'])
|
||||||
def api_todo_updateHandle():
|
def api_todo_updateHandle():
|
||||||
return SmartDbCaller(calendar_db.todo_update,
|
return SmartDbCaller(calendar_db.todo_update,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('uuid', str, False),
|
FormField('uuid', str, False),
|
||||||
('data', str, False),
|
FormField('data', str, False),
|
||||||
('lastChange', str, False)),
|
FormField('lastChange', str, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/todo/delete', methods=['POST'])
|
@app.route('/todo/delete', methods=['POST'])
|
||||||
def api_todo_deleteHandle():
|
def api_todo_deleteHandle():
|
||||||
return SmartDbCaller(calendar_db.todo_delete,
|
return SmartDbCaller(calendar_db.todo_delete,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('uuid', str, False),
|
FormField('uuid', str, False),
|
||||||
('lastChange', str, False)),
|
FormField('lastChange', str, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
@@ -263,30 +267,30 @@ def api_todo_deleteHandle():
|
|||||||
@app.route('/admin/get', methods=['POST'])
|
@app.route('/admin/get', methods=['POST'])
|
||||||
def api_admin_getHandle():
|
def api_admin_getHandle():
|
||||||
return SmartDbCaller(calendar_db.admin_get,
|
return SmartDbCaller(calendar_db.admin_get,
|
||||||
(('token', str, False), ),
|
(FormField('token', str, False), ),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/admin/add', methods=['POST'])
|
@app.route('/admin/add', methods=['POST'])
|
||||||
def api_admin_addHandle():
|
def api_admin_addHandle():
|
||||||
return SmartDbCaller(calendar_db.admin_add,
|
return SmartDbCaller(calendar_db.admin_add,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('username', str, False)),
|
FormField('username', str, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/admin/update', methods=['POST'])
|
@app.route('/admin/update', methods=['POST'])
|
||||||
def api_admin_updateHandle():
|
def api_admin_updateHandle():
|
||||||
return SmartDbCaller(calendar_db.admin_update,
|
return SmartDbCaller(calendar_db.admin_update,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('username', str, False),
|
FormField('username', str, False),
|
||||||
('password', str, True),
|
FormField('password', str, True),
|
||||||
('isAdmin', utils.Str2Bool, True)),
|
FormField('isAdmin', utils.Str2Bool, True)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/admin/delete', methods=['POST'])
|
@app.route('/admin/delete', methods=['POST'])
|
||||||
def api_admin_deleteHandle():
|
def api_admin_deleteHandle():
|
||||||
return SmartDbCaller(calendar_db.admin_delete,
|
return SmartDbCaller(calendar_db.admin_delete,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('username', str, False)),
|
FormField('username', str, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
@@ -296,27 +300,27 @@ def api_admin_deleteHandle():
|
|||||||
@app.route('/profile/isAdmin', methods=['POST'])
|
@app.route('/profile/isAdmin', methods=['POST'])
|
||||||
def api_profile_isAdminHandle():
|
def api_profile_isAdminHandle():
|
||||||
return SmartDbCaller(calendar_db.profile_isAdmin,
|
return SmartDbCaller(calendar_db.profile_isAdmin,
|
||||||
(('token', str, False), ),
|
(FormField('token', str, False), ),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/profile/changePassword', methods=['POST'])
|
@app.route('/profile/changePassword', methods=['POST'])
|
||||||
def api_profile_changePasswordHandle():
|
def api_profile_changePasswordHandle():
|
||||||
return SmartDbCaller(calendar_db.profile_changePassword,
|
return SmartDbCaller(calendar_db.profile_changePassword,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('password', str, False)),
|
FormField('password', str, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/profile/getToken', methods=['POST'])
|
@app.route('/profile/getToken', methods=['POST'])
|
||||||
def api_profile_getTokenHandle():
|
def api_profile_getTokenHandle():
|
||||||
return SmartDbCaller(calendar_db.profile_getToken,
|
return SmartDbCaller(calendar_db.profile_getToken,
|
||||||
(('token', str, False), ),
|
(FormField('token', str, False), ),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@app.route('/profile/deleteToken', methods=['POST'])
|
@app.route('/profile/deleteToken', methods=['POST'])
|
||||||
def api_profile_deleteTokenHandle():
|
def api_profile_deleteTokenHandle():
|
||||||
return SmartDbCaller(calendar_db.profile_deleteToken,
|
return SmartDbCaller(calendar_db.profile_deleteToken,
|
||||||
(('token', str, False),
|
(FormField('token', str, False),
|
||||||
('deleteToken', str, False)),
|
FormField('deleteToken', str, False)),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
@@ -325,41 +329,64 @@ def api_profile_deleteTokenHandle():
|
|||||||
|
|
||||||
# region: Misc Functions
|
# region: Misc Functions
|
||||||
|
|
||||||
def SmartDbCaller(dbMethod, paramTuple, extraDict):
|
@dataclass(frozen=True)
|
||||||
result = (False, 'Invalid parameter', None)
|
class FormField:
|
||||||
optCount = 0
|
name: str
|
||||||
paramList = []
|
"""The name of form field."""
|
||||||
optParamDict = {}
|
ty: Callable[[str], Any]
|
||||||
# for each item,
|
"""The type of form field."""
|
||||||
# item[0] is field name.
|
is_optional: bool
|
||||||
# item[1] is type.
|
"""True if this form field is optional, otherwise false."""
|
||||||
# item[2] is whether it is optional field
|
|
||||||
realForm = request.form.to_dict()
|
def SmartDbCaller(db_method: Callable, fields: tuple[FormField, ...], padding_form: dict[str, Any] | None) -> dict[str, Any]:
|
||||||
if extraDict is not None:
|
result = ResponseBody(False, 'Invalid parameter', None)
|
||||||
realForm.update(extraDict)
|
opt_param_counter = 0
|
||||||
for item in paramTuple:
|
param_list: list[Any] = []
|
||||||
cache = item[1](realForm.get(item[0], None))
|
opt_param_dict: dict[str, Any] = {}
|
||||||
if item[2]:
|
|
||||||
# optional param
|
|
||||||
if cache is not None:
|
|
||||||
optParamDict[item[0]] = cache
|
|
||||||
optCount += 1
|
|
||||||
else:
|
|
||||||
if cache is None:
|
|
||||||
break
|
|
||||||
paramList.append(cache)
|
|
||||||
else:
|
|
||||||
# at least one opt param
|
|
||||||
if optCount == 0 or len(optParamDict) != 0:
|
|
||||||
result = dbMethod(*paramList, **optParamDict)
|
|
||||||
|
|
||||||
|
real_form = request.form.to_dict()
|
||||||
|
LOGGER.debug(f'Form: {real_form}')
|
||||||
|
if padding_form is not None:
|
||||||
|
real_form.update(padding_form)
|
||||||
|
|
||||||
|
for field in fields:
|
||||||
|
value = real_form.get(field.name, None)
|
||||||
|
if value is not None:
|
||||||
|
value = field.ty(value)
|
||||||
|
|
||||||
|
if field.is_optional:
|
||||||
|
# optional param
|
||||||
|
if value is not None:
|
||||||
|
opt_param_dict[field.name] = value
|
||||||
|
opt_param_counter += 1
|
||||||
|
else:
|
||||||
|
# required param
|
||||||
|
if value is None:
|
||||||
|
break
|
||||||
|
param_list.append(value)
|
||||||
|
|
||||||
|
# at least one opt param
|
||||||
|
LOGGER.debug(f'All Optional Parameter: {opt_param_counter}')
|
||||||
|
LOGGER.debug(f'Optional Parameter Count: {len(opt_param_dict)}')
|
||||||
|
if opt_param_counter == 0 or len(opt_param_dict) != 0:
|
||||||
|
result: ResponseBody = db_method(*param_list, **opt_param_dict)
|
||||||
|
|
||||||
return ConstructResponseBody(result)
|
return ConstructResponseBody(result)
|
||||||
|
|
||||||
def ConstructResponseBody(returnedTuple):
|
@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."""
|
||||||
|
|
||||||
|
def ConstructResponseBody(body: ResponseBody) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
'success': returnedTuple[0],
|
'success': body.success,
|
||||||
'error': returnedTuple[1],
|
'error': body.error,
|
||||||
'data': returnedTuple[2]
|
'data': body.data
|
||||||
}
|
}
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"@fortawesome/fontawesome-svg-core": "^7.2.0",
|
"@fortawesome/fontawesome-svg-core": "^7.2.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "^7.2.0",
|
"@fortawesome/free-solid-svg-icons": "^7.2.0",
|
||||||
"@fortawesome/vue-fontawesome": "^3.2.0",
|
"@fortawesome/vue-fontawesome": "^3.2.0",
|
||||||
|
"axios": "1.14.0",
|
||||||
"bulma": "0.9.1",
|
"bulma": "0.9.1",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
"pinia-plugin-persistedstate": "^4.7.1",
|
"pinia-plugin-persistedstate": "^4.7.1",
|
||||||
|
|||||||
191
frontend/pnpm-lock.yaml
generated
191
frontend/pnpm-lock.yaml
generated
@@ -17,6 +17,9 @@ importers:
|
|||||||
'@fortawesome/vue-fontawesome':
|
'@fortawesome/vue-fontawesome':
|
||||||
specifier: ^3.2.0
|
specifier: ^3.2.0
|
||||||
version: 3.2.0(@fortawesome/fontawesome-svg-core@7.2.0)(vue@3.5.33(typescript@6.0.3))
|
version: 3.2.0(@fortawesome/fontawesome-svg-core@7.2.0)(vue@3.5.33(typescript@6.0.3))
|
||||||
|
axios:
|
||||||
|
specifier: 1.14.0
|
||||||
|
version: 1.14.0
|
||||||
bulma:
|
bulma:
|
||||||
specifier: 0.9.1
|
specifier: 0.9.1
|
||||||
version: 0.9.1
|
version: 0.9.1
|
||||||
@@ -876,6 +879,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==}
|
resolution: {integrity: sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==}
|
||||||
engines: {node: '>=20.19.0'}
|
engines: {node: '>=20.19.0'}
|
||||||
|
|
||||||
|
asynckit@0.4.0:
|
||||||
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
|
|
||||||
|
axios@1.14.0:
|
||||||
|
resolution: {integrity: sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==}
|
||||||
|
|
||||||
balanced-match@4.0.4:
|
balanced-match@4.0.4:
|
||||||
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
|
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
|
||||||
engines: {node: 18 || 20 || >=22}
|
engines: {node: 18 || 20 || >=22}
|
||||||
@@ -911,6 +920,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
|
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
call-bind-apply-helpers@1.0.2:
|
||||||
|
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001791:
|
caniuse-lite@1.0.30001791:
|
||||||
resolution: {integrity: sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==}
|
resolution: {integrity: sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==}
|
||||||
|
|
||||||
@@ -922,6 +935,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
|
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
|
||||||
engines: {node: '>= 20.19.0'}
|
engines: {node: '>= 20.19.0'}
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
confbox@0.1.8:
|
confbox@0.1.8:
|
||||||
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
||||||
|
|
||||||
@@ -974,10 +991,18 @@ packages:
|
|||||||
defu@6.1.7:
|
defu@6.1.7:
|
||||||
resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==}
|
resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==}
|
||||||
|
|
||||||
|
delayed-stream@1.0.0:
|
||||||
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
detect-libc@2.1.2:
|
detect-libc@2.1.2:
|
||||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
dunder-proto@1.0.1:
|
||||||
|
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
electron-to-chromium@1.5.344:
|
electron-to-chromium@1.5.344:
|
||||||
resolution: {integrity: sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==}
|
resolution: {integrity: sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==}
|
||||||
|
|
||||||
@@ -988,6 +1013,22 @@ packages:
|
|||||||
error-stack-parser-es@1.0.5:
|
error-stack-parser-es@1.0.5:
|
||||||
resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==}
|
resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==}
|
||||||
|
|
||||||
|
es-define-property@1.0.1:
|
||||||
|
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-errors@1.3.0:
|
||||||
|
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-object-atoms@1.1.1:
|
||||||
|
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-set-tostringtag@2.1.0:
|
||||||
|
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
escalade@3.2.0:
|
escalade@3.2.0:
|
||||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -1107,15 +1148,39 @@ packages:
|
|||||||
flatted@3.4.2:
|
flatted@3.4.2:
|
||||||
resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
|
resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
|
||||||
|
|
||||||
|
follow-redirects@1.16.0:
|
||||||
|
resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
|
||||||
|
engines: {node: '>=4.0'}
|
||||||
|
peerDependencies:
|
||||||
|
debug: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
debug:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
form-data@4.0.5:
|
||||||
|
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
|
function-bind@1.1.2:
|
||||||
|
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||||
|
|
||||||
gensync@1.0.0-beta.2:
|
gensync@1.0.0-beta.2:
|
||||||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
get-intrinsic@1.3.0:
|
||||||
|
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
get-proto@1.0.1:
|
||||||
|
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
glob-parent@5.1.2:
|
||||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@@ -1124,6 +1189,22 @@ packages:
|
|||||||
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
||||||
engines: {node: '>=10.13.0'}
|
engines: {node: '>=10.13.0'}
|
||||||
|
|
||||||
|
gopd@1.2.0:
|
||||||
|
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
has-symbols@1.1.0:
|
||||||
|
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
has-tostringtag@1.0.2:
|
||||||
|
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
hasown@2.0.3:
|
||||||
|
resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
hookable@5.5.3:
|
hookable@5.5.3:
|
||||||
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||||
|
|
||||||
@@ -1314,6 +1395,10 @@ packages:
|
|||||||
magic-string@0.30.21:
|
magic-string@0.30.21:
|
||||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||||
|
|
||||||
|
math-intrinsics@1.1.0:
|
||||||
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
memorystream@0.3.1:
|
memorystream@0.3.1:
|
||||||
resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
|
resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
|
||||||
engines: {node: '>= 0.10.0'}
|
engines: {node: '>= 0.10.0'}
|
||||||
@@ -1326,6 +1411,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||||
engines: {node: '>=8.6'}
|
engines: {node: '>=8.6'}
|
||||||
|
|
||||||
|
mime-db@1.52.0:
|
||||||
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
minimatch@10.2.5:
|
minimatch@10.2.5:
|
||||||
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
|
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
|
||||||
engines: {node: 18 || 20 || >=22}
|
engines: {node: 18 || 20 || >=22}
|
||||||
@@ -1478,6 +1571,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
proxy-from-env@2.1.0:
|
||||||
|
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
punycode@2.3.1:
|
punycode@2.3.1:
|
||||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -2572,6 +2669,16 @@ snapshots:
|
|||||||
'@babel/parser': 7.29.2
|
'@babel/parser': 7.29.2
|
||||||
ast-kit: 2.2.0
|
ast-kit: 2.2.0
|
||||||
|
|
||||||
|
asynckit@0.4.0: {}
|
||||||
|
|
||||||
|
axios@1.14.0:
|
||||||
|
dependencies:
|
||||||
|
follow-redirects: 1.16.0
|
||||||
|
form-data: 4.0.5
|
||||||
|
proxy-from-env: 2.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
|
||||||
balanced-match@4.0.4: {}
|
balanced-match@4.0.4: {}
|
||||||
|
|
||||||
baseline-browser-mapping@2.10.23: {}
|
baseline-browser-mapping@2.10.23: {}
|
||||||
@@ -2602,6 +2709,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
run-applescript: 7.1.0
|
run-applescript: 7.1.0
|
||||||
|
|
||||||
|
call-bind-apply-helpers@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
function-bind: 1.1.2
|
||||||
|
|
||||||
caniuse-lite@1.0.30001791: {}
|
caniuse-lite@1.0.30001791: {}
|
||||||
|
|
||||||
chokidar@4.0.3:
|
chokidar@4.0.3:
|
||||||
@@ -2613,6 +2725,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
readdirp: 5.0.0
|
readdirp: 5.0.0
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
dependencies:
|
||||||
|
delayed-stream: 1.0.0
|
||||||
|
|
||||||
confbox@0.1.8: {}
|
confbox@0.1.8: {}
|
||||||
|
|
||||||
confbox@0.2.4: {}
|
confbox@0.2.4: {}
|
||||||
@@ -2650,14 +2766,37 @@ snapshots:
|
|||||||
|
|
||||||
defu@6.1.7: {}
|
defu@6.1.7: {}
|
||||||
|
|
||||||
|
delayed-stream@1.0.0: {}
|
||||||
|
|
||||||
detect-libc@2.1.2: {}
|
detect-libc@2.1.2: {}
|
||||||
|
|
||||||
|
dunder-proto@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
es-errors: 1.3.0
|
||||||
|
gopd: 1.2.0
|
||||||
|
|
||||||
electron-to-chromium@1.5.344: {}
|
electron-to-chromium@1.5.344: {}
|
||||||
|
|
||||||
entities@7.0.1: {}
|
entities@7.0.1: {}
|
||||||
|
|
||||||
error-stack-parser-es@1.0.5: {}
|
error-stack-parser-es@1.0.5: {}
|
||||||
|
|
||||||
|
es-define-property@1.0.1: {}
|
||||||
|
|
||||||
|
es-errors@1.3.0: {}
|
||||||
|
|
||||||
|
es-object-atoms@1.1.1:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
|
||||||
|
es-set-tostringtag@2.1.0:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
get-intrinsic: 1.3.0
|
||||||
|
has-tostringtag: 1.0.2
|
||||||
|
hasown: 2.0.3
|
||||||
|
|
||||||
escalade@3.2.0: {}
|
escalade@3.2.0: {}
|
||||||
|
|
||||||
escape-string-regexp@4.0.0: {}
|
escape-string-regexp@4.0.0: {}
|
||||||
@@ -2792,11 +2931,41 @@ snapshots:
|
|||||||
|
|
||||||
flatted@3.4.2: {}
|
flatted@3.4.2: {}
|
||||||
|
|
||||||
|
follow-redirects@1.16.0: {}
|
||||||
|
|
||||||
|
form-data@4.0.5:
|
||||||
|
dependencies:
|
||||||
|
asynckit: 0.4.0
|
||||||
|
combined-stream: 1.0.8
|
||||||
|
es-set-tostringtag: 2.1.0
|
||||||
|
hasown: 2.0.3
|
||||||
|
mime-types: 2.1.35
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
function-bind@1.1.2: {}
|
||||||
|
|
||||||
gensync@1.0.0-beta.2: {}
|
gensync@1.0.0-beta.2: {}
|
||||||
|
|
||||||
|
get-intrinsic@1.3.0:
|
||||||
|
dependencies:
|
||||||
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
es-define-property: 1.0.1
|
||||||
|
es-errors: 1.3.0
|
||||||
|
es-object-atoms: 1.1.1
|
||||||
|
function-bind: 1.1.2
|
||||||
|
get-proto: 1.0.1
|
||||||
|
gopd: 1.2.0
|
||||||
|
has-symbols: 1.1.0
|
||||||
|
hasown: 2.0.3
|
||||||
|
math-intrinsics: 1.1.0
|
||||||
|
|
||||||
|
get-proto@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
dunder-proto: 1.0.1
|
||||||
|
es-object-atoms: 1.1.1
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
glob-parent@5.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
@@ -2805,6 +2974,18 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
|
|
||||||
|
gopd@1.2.0: {}
|
||||||
|
|
||||||
|
has-symbols@1.1.0: {}
|
||||||
|
|
||||||
|
has-tostringtag@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
has-symbols: 1.1.0
|
||||||
|
|
||||||
|
hasown@2.0.3:
|
||||||
|
dependencies:
|
||||||
|
function-bind: 1.1.2
|
||||||
|
|
||||||
hookable@5.5.3: {}
|
hookable@5.5.3: {}
|
||||||
|
|
||||||
ignore@5.3.2: {}
|
ignore@5.3.2: {}
|
||||||
@@ -2940,6 +3121,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
memorystream@0.3.1: {}
|
memorystream@0.3.1: {}
|
||||||
|
|
||||||
merge2@1.4.1: {}
|
merge2@1.4.1: {}
|
||||||
@@ -2949,6 +3132,12 @@ snapshots:
|
|||||||
braces: 3.0.3
|
braces: 3.0.3
|
||||||
picomatch: 2.3.2
|
picomatch: 2.3.2
|
||||||
|
|
||||||
|
mime-db@1.52.0: {}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
dependencies:
|
||||||
|
mime-db: 1.52.0
|
||||||
|
|
||||||
minimatch@10.2.5:
|
minimatch@10.2.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 5.0.5
|
brace-expansion: 5.0.5
|
||||||
@@ -3100,6 +3289,8 @@ snapshots:
|
|||||||
|
|
||||||
prelude-ls@1.2.1: {}
|
prelude-ls@1.2.1: {}
|
||||||
|
|
||||||
|
proxy-from-env@2.1.0: {}
|
||||||
|
|
||||||
punycode@2.3.1: {}
|
punycode@2.3.1: {}
|
||||||
|
|
||||||
quansync@0.2.11: {}
|
quansync@0.2.11: {}
|
||||||
|
|||||||
75
frontend/src/api/admin.ts
Normal file
75
frontend/src/api/admin.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import { apiWrapper, boolApiWrapper } from './index'
|
||||||
|
|
||||||
|
// User interface
|
||||||
|
interface User {
|
||||||
|
username: string
|
||||||
|
isAdmin: boolean
|
||||||
|
// Add other user-related fields as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all users (admin only)
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @returns Array of users or null if operation failed
|
||||||
|
*/
|
||||||
|
export async function get(token: string): Promise<User[] | undefined> {
|
||||||
|
return apiWrapper('/api/admin/get', { token });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add new user (admin only)
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param username - Username
|
||||||
|
* @returns Created user or null if operation failed
|
||||||
|
*/
|
||||||
|
export async function add(token: string, username: string): Promise<User | undefined> {
|
||||||
|
return apiWrapper('/api/admin/add', { token, username });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update user (admin only)
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param username - Username
|
||||||
|
* @param params - Update parameters (partial)
|
||||||
|
* @returns true if update successful, false otherwise
|
||||||
|
*/
|
||||||
|
export async function update(
|
||||||
|
token: string,
|
||||||
|
username: string,
|
||||||
|
params: {
|
||||||
|
password?: string
|
||||||
|
isAdmin?: boolean
|
||||||
|
}
|
||||||
|
): Promise<boolean> {
|
||||||
|
const data: any = {
|
||||||
|
token,
|
||||||
|
username
|
||||||
|
};
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
if (typeof params.password !== 'undefined') {
|
||||||
|
data.password = params.password;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if (typeof params.isAdmin !== 'undefined') {
|
||||||
|
data.isAdmin = params.isAdmin;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no update parameters provided, return true
|
||||||
|
if (count === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return boolApiWrapper('/api/admin/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete user (admin only)
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param username - Username
|
||||||
|
* @returns true if deletion successful, false otherwise
|
||||||
|
*/
|
||||||
|
export async function deleteItem(token: string, username: string): Promise<boolean> {
|
||||||
|
return boolApiWrapper('/api/admin/delete', { token, username });
|
||||||
|
}
|
||||||
141
frontend/src/api/calendar.ts
Normal file
141
frontend/src/api/calendar.ts
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import { apiWrapper, boolApiWrapper } from './index'
|
||||||
|
|
||||||
|
// Calendar event interface
|
||||||
|
interface CalendarEvent {
|
||||||
|
uuid: string
|
||||||
|
belongTo: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
eventDateTimeStart: string
|
||||||
|
eventDateTimeEnd: string
|
||||||
|
loopRules: string
|
||||||
|
timezoneOffset: number
|
||||||
|
lastChange: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description object interface
|
||||||
|
interface DescriptionObject {
|
||||||
|
description: string
|
||||||
|
color: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize calendar description
|
||||||
|
* @param description - Description text
|
||||||
|
* @param color - Color value
|
||||||
|
* @returns JSON string
|
||||||
|
*/
|
||||||
|
export function serializeDescription(description: string, color: string): string {
|
||||||
|
const sobj: DescriptionObject = {
|
||||||
|
description,
|
||||||
|
color
|
||||||
|
}
|
||||||
|
return JSON.stringify(sobj)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize calendar description
|
||||||
|
* @param str - JSON string
|
||||||
|
* @returns Description object
|
||||||
|
*/
|
||||||
|
export function deserializeDescription(str: string): DescriptionObject {
|
||||||
|
try {
|
||||||
|
return JSON.parse(str) as DescriptionObject
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
description: "",
|
||||||
|
color: "#000000" // DefaultColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get full calendar events within date range
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param startDateTime - Start datetime
|
||||||
|
* @param endDateTime - End datetime
|
||||||
|
* @returns Array of calendar events or null if operation failed
|
||||||
|
*/
|
||||||
|
export async function getFull(token: string, startDateTime: string, endDateTime: string): Promise<CalendarEvent[] | undefined> {
|
||||||
|
return apiWrapper('/api/calendar/getFull', { token, startDateTime, endDateTime });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get calendar event detail by UUID
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param uuid - Event UUID
|
||||||
|
* @returns Calendar event or null if operation failed
|
||||||
|
*/
|
||||||
|
export async function getDetail(token: string, uuid: string): Promise<CalendarEvent | undefined> {
|
||||||
|
return apiWrapper('/api/calendar/getDetail', { token, uuid });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update calendar event
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param uuid - Event UUID
|
||||||
|
* @param params - Update parameters (partial)
|
||||||
|
* @returns Updated calendar event or null if operation failed
|
||||||
|
*/
|
||||||
|
export async function update(
|
||||||
|
token: string,
|
||||||
|
uuid: string,
|
||||||
|
params: {
|
||||||
|
belongTo?: string
|
||||||
|
title?: string
|
||||||
|
description?: string
|
||||||
|
eventDateTimeStart?: string
|
||||||
|
eventDateTimeEnd?: string
|
||||||
|
loopRules?: string
|
||||||
|
timezoneOffset?: number
|
||||||
|
lastChange: string
|
||||||
|
}
|
||||||
|
): Promise<CalendarEvent | undefined> {
|
||||||
|
const data: any = {
|
||||||
|
token,
|
||||||
|
uuid,
|
||||||
|
lastChange: params.lastChange
|
||||||
|
};
|
||||||
|
|
||||||
|
if (params.belongTo !== undefined) data.belongTo = params.belongTo;
|
||||||
|
if (params.title !== undefined) data.title = params.title;
|
||||||
|
if (params.description !== undefined) data.description = params.description;
|
||||||
|
if (params.eventDateTimeStart !== undefined) data.eventDateTimeStart = params.eventDateTimeStart;
|
||||||
|
if (params.eventDateTimeEnd !== undefined) data.eventDateTimeEnd = params.eventDateTimeEnd;
|
||||||
|
if (params.loopRules !== undefined) data.loopRules = params.loopRules;
|
||||||
|
if (params.timezoneOffset !== undefined) data.timezoneOffset = params.timezoneOffset;
|
||||||
|
|
||||||
|
return apiWrapper('/api/calendar/update', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add new calendar event
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param params - Event parameters
|
||||||
|
* @returns Created calendar event or null if operation failed
|
||||||
|
*/
|
||||||
|
export async function add(
|
||||||
|
token: string,
|
||||||
|
params: {
|
||||||
|
belongTo: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
eventDateTimeStart: string
|
||||||
|
eventDateTimeEnd: string
|
||||||
|
loopRules: string
|
||||||
|
timezoneOffset: number
|
||||||
|
}
|
||||||
|
): Promise<CalendarEvent | undefined> {
|
||||||
|
return apiWrapper('/api/calendar/add', { token, ...params });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete calendar event
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param uuid - Event UUID
|
||||||
|
* @param lastChange - Last change timestamp
|
||||||
|
* @returns true if deletion successful, false otherwise
|
||||||
|
*/
|
||||||
|
export async function deleteEvent(token: string, uuid: string, lastChange: string): Promise<boolean> {
|
||||||
|
return boolApiWrapper('/api/calendar/delete', { token, uuid, lastChange });
|
||||||
|
}
|
||||||
126
frontend/src/api/collection.ts
Normal file
126
frontend/src/api/collection.ts
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import { apiWrapper, boolApiWrapper } from './index'
|
||||||
|
|
||||||
|
type Uuid = string;
|
||||||
|
|
||||||
|
// Collection item interface
|
||||||
|
interface CollectionItem {
|
||||||
|
uuid: Uuid
|
||||||
|
name: string
|
||||||
|
lastChange: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sharing info interface
|
||||||
|
interface SharingInfo {
|
||||||
|
target: string
|
||||||
|
// Add other sharing-related fields as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all owned collections
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @returns Array of collection items or null if operation failed
|
||||||
|
*/
|
||||||
|
export async function getFullOwn(token: string): Promise<CollectionItem[] | undefined> {
|
||||||
|
return apiWrapper('/api/collection/getFullOwn', { token });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get owned collection detail by UUID
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param uuid - Collection UUID
|
||||||
|
* @returns Collection item or null if operation failed
|
||||||
|
*/
|
||||||
|
export async function getDetailOwn(token: string, uuid: Uuid): Promise<CollectionItem | undefined> {
|
||||||
|
return apiWrapper('/api/collection/getDetailOwn', { token, uuid });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add new owned collection
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param name - Collection name
|
||||||
|
* @returns Created collection item or null if operation failed
|
||||||
|
*/
|
||||||
|
export async function addOwn(token: string, name: string): Promise<Uuid | undefined> {
|
||||||
|
return apiWrapper('/api/collection/addOwn', { token, name });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update owned collection
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param uuid - Collection UUID
|
||||||
|
* @param name - New name
|
||||||
|
* @param lastChange - Last change timestamp
|
||||||
|
* @returns Updated collection item or null if operation failed
|
||||||
|
*/
|
||||||
|
export async function updateOwn(
|
||||||
|
token: string,
|
||||||
|
uuid: Uuid,
|
||||||
|
name: string,
|
||||||
|
lastChange: string
|
||||||
|
): Promise<CollectionItem | undefined> {
|
||||||
|
return apiWrapper('/api/collection/updateOwn', { token, uuid, name, lastChange });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete owned collection
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param uuid - Collection UUID
|
||||||
|
* @param lastChange - Last change timestamp
|
||||||
|
* @returns true if deletion successful, false otherwise
|
||||||
|
*/
|
||||||
|
export async function deleteOwn(token: string, uuid: Uuid, lastChange: string): Promise<boolean> {
|
||||||
|
return boolApiWrapper('/api/collection/deleteOwn', { token, uuid, lastChange });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sharing information for a collection
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param uuid - Collection UUID
|
||||||
|
* @returns Array of sharing info or null if operation failed
|
||||||
|
*/
|
||||||
|
export async function getSharing(token: string, uuid: Uuid): Promise<SharingInfo[] | undefined> {
|
||||||
|
return apiWrapper('/api/collection/getSharing', { token, uuid });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete sharing for a collection
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param uuid - Collection UUID
|
||||||
|
* @param target - Target user
|
||||||
|
* @param lastChange - Last change timestamp
|
||||||
|
* @returns Result data or null if operation failed
|
||||||
|
*/
|
||||||
|
export async function deleteSharing(
|
||||||
|
token: string,
|
||||||
|
uuid: Uuid,
|
||||||
|
target: string,
|
||||||
|
lastChange: string
|
||||||
|
): Promise<any | undefined> {
|
||||||
|
return apiWrapper('/api/collection/deleteSharing', { token, uuid, target, lastChange });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add sharing for a collection
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param uuid - Collection UUID
|
||||||
|
* @param target - Target user
|
||||||
|
* @param lastChange - Last change timestamp
|
||||||
|
* @returns Result data or null if operation failed
|
||||||
|
*/
|
||||||
|
export async function addSharing(
|
||||||
|
token: string,
|
||||||
|
uuid: Uuid,
|
||||||
|
target: string,
|
||||||
|
lastChange: string
|
||||||
|
): Promise<any | undefined> {
|
||||||
|
return apiWrapper('/api/collection/addSharing', { token, uuid, target, lastChange });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all shared collections
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @returns Array of collection items or null if operation failed
|
||||||
|
*/
|
||||||
|
export async function getShared(token: string): Promise<CollectionItem[] | undefined> {
|
||||||
|
return apiWrapper('/api/collection/getShared', { token });
|
||||||
|
}
|
||||||
43
frontend/src/api/common.ts
Normal file
43
frontend/src/api/common.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { apiWrapper, boolApiWrapper } from './index'
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * Login with salt
|
||||||
|
// * @param username - Username
|
||||||
|
// * @param password - Password
|
||||||
|
// * @returns Token if login successful, undefined otherwise
|
||||||
|
// */
|
||||||
|
// export async function login(username: string, password: string): Promise<string | undefined> {
|
||||||
|
// const salt: string | undefined = await apiWrapper('/api/common/salt', { username });
|
||||||
|
// if (typeof salt === 'undefined') return undefined;
|
||||||
|
|
||||||
|
// const computedPassword = computePasswordWithSalt(password, salt);
|
||||||
|
// return apiWrapper('/api/common/login', { username, password: computedPassword });
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Web login
|
||||||
|
* @param username - Username
|
||||||
|
* @param password - Password
|
||||||
|
* @returns Token if login successful, undefined otherwise
|
||||||
|
*/
|
||||||
|
export async function webLogin(username: string, password: string): Promise<string | undefined> {
|
||||||
|
return apiWrapper('/api/common/webLogin', { username, password });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @returns true if logout successful, false otherwise if logout failed
|
||||||
|
*/
|
||||||
|
export async function logout(token: string): Promise<boolean> {
|
||||||
|
return boolApiWrapper('/api/common/logout', { token });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate token
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @returns true if token is valid, false otherwise
|
||||||
|
*/
|
||||||
|
export async function tokenValid(token: string): Promise<boolean> {
|
||||||
|
return boolApiWrapper('/api/common/tokenValid', { token });
|
||||||
|
}
|
||||||
57
frontend/src/api/index.ts
Normal file
57
frontend/src/api/index.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// Response interface
|
||||||
|
interface ApiResponse<T = any> {
|
||||||
|
success: boolean,
|
||||||
|
error: string,
|
||||||
|
data: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function apiWrapper<T>(url: string, data: Record<string, any>): Promise<T | undefined> {
|
||||||
|
try {
|
||||||
|
// 自动编码为 key=value&key2=value2 格式
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
Object.entries(data).forEach(([key, value]) => {
|
||||||
|
params.append(key, String(value));
|
||||||
|
});
|
||||||
|
// 发起请求
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
mode: "cors",
|
||||||
|
cache: "no-cache",
|
||||||
|
credentials: "same-origin",
|
||||||
|
headers: {
|
||||||
|
// 明确指定内容类型
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
redirect: "follow",
|
||||||
|
referrerPolicy: "no-referrer",
|
||||||
|
body: params.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// 检查 HTTP 状态码 (fetch 只有在网络故障时才会 reject,HTTP 404/500 不会)
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`HTTP failed: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 JSON body
|
||||||
|
// 注意:response.json() 返回的是一个 Promise,所以需要 await
|
||||||
|
const payload = await response.json() as ApiResponse<T>;
|
||||||
|
|
||||||
|
// 检查API返回结果
|
||||||
|
if (payload.success) {
|
||||||
|
return payload.data;
|
||||||
|
} else {
|
||||||
|
console.error(`API failed: ${payload.error}`);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// 统一错误处理
|
||||||
|
console.error(`Fetch failed: ${error}`);
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function boolApiWrapper<U>(url: string, data: Record<string, any>): Promise<boolean> {
|
||||||
|
const rv = await apiWrapper<null>(url, data);
|
||||||
|
return rv !== undefined;
|
||||||
|
}
|
||||||
|
|
||||||
45
frontend/src/api/profile.ts
Normal file
45
frontend/src/api/profile.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { apiWrapper, boolApiWrapper } from './index'
|
||||||
|
|
||||||
|
// Token info interface
|
||||||
|
interface TokenInfo {
|
||||||
|
token: string
|
||||||
|
// Add other token-related fields as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if current user is admin
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @returns true if user is admin, false otherwise
|
||||||
|
*/
|
||||||
|
export async function isAdmin(token: string): Promise<boolean> {
|
||||||
|
return boolApiWrapper('/api/profile/isAdmin', { token });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change user password
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param password - New password
|
||||||
|
* @returns true if change successful, false otherwise
|
||||||
|
*/
|
||||||
|
export async function changePassword(token: string, password: string): Promise<boolean> {
|
||||||
|
return boolApiWrapper('/api/profile/changePassword', { token, password });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user tokens
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @returns Array of token info or undefined if operation failed
|
||||||
|
*/
|
||||||
|
export async function getToken(token: string): Promise<TokenInfo[] | undefined> {
|
||||||
|
return apiWrapper('/api/profile/getToken', { token });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a token
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param deleteToken - Token to delete
|
||||||
|
* @returns true if deletion successful, false otherwise
|
||||||
|
*/
|
||||||
|
export async function deleteToken(token: string, deleteToken: string): Promise<boolean> {
|
||||||
|
return boolApiWrapper('/api/profile/deleteToken', { token, deleteToken });
|
||||||
|
}
|
||||||
56
frontend/src/api/todo.ts
Normal file
56
frontend/src/api/todo.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import { apiWrapper, boolApiWrapper } from './index'
|
||||||
|
|
||||||
|
// Todo item interface
|
||||||
|
interface TodoItem {
|
||||||
|
uuid: string
|
||||||
|
data: any
|
||||||
|
lastChange: string
|
||||||
|
// Add other todo-related fields as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all todos
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @returns Array of todo items or null if operation failed
|
||||||
|
*/
|
||||||
|
export async function getFull(token: string): Promise<TodoItem[] | undefined> {
|
||||||
|
return apiWrapper('/api/todo/getFull', { token });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add new todo
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @returns Created todo item or null if operation failed
|
||||||
|
*/
|
||||||
|
export async function add(token: string): Promise<TodoItem | undefined> {
|
||||||
|
return apiWrapper('/api/todo/add', { token });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update todo
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param uuid - Todo UUID
|
||||||
|
* @param data - Todo data
|
||||||
|
* @param lastChange - Last change timestamp
|
||||||
|
* @returns Updated todo item or null if operation failed
|
||||||
|
*/
|
||||||
|
export async function update(
|
||||||
|
token: string,
|
||||||
|
uuid: string,
|
||||||
|
data: any,
|
||||||
|
lastChange: string
|
||||||
|
): Promise<TodoItem | undefined> {
|
||||||
|
return apiWrapper('/api/todo/update', { token, uuid, data, lastChange });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete todo
|
||||||
|
* @param token - Authentication token
|
||||||
|
* @param uuid - Todo UUID
|
||||||
|
* @param lastChange - Last change timestamp
|
||||||
|
* @returns true if deletion successful, false otherwise
|
||||||
|
*/
|
||||||
|
export async function deleteTodo(token: string, uuid: string, lastChange: string): Promise<boolean> {
|
||||||
|
return boolApiWrapper('/api/todo/delete', { token, uuid, lastChange });
|
||||||
|
}
|
||||||
@@ -48,4 +48,8 @@ router.beforeEach((to, from) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const goToHome = () => {
|
||||||
|
router.push({ name: 'Home' })
|
||||||
|
}
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|||||||
@@ -1,11 +1,45 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import MessageBox from '@/components/MessageBox.vue';
|
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
|
import MessageBox from '@/components/MessageBox.vue';
|
||||||
|
import { useTokenStore } from '@/stores/token';
|
||||||
|
import { webLogin as apiCommonWebLogin } from '@/api/common';
|
||||||
|
import { goToHome } from '@/router';
|
||||||
|
|
||||||
|
const isLoggingIn = ref<boolean>(false);
|
||||||
|
const username = ref<string>("");
|
||||||
|
const password = ref<string>("");
|
||||||
|
|
||||||
const messagebox = ref<InstanceType<typeof MessageBox> | null>(null);
|
const messagebox = ref<InstanceType<typeof MessageBox> | null>(null);
|
||||||
|
|
||||||
const login = () => {
|
const login = async () => {
|
||||||
messagebox.value?.show("Fail to login. Please check your username or password.")
|
// disable UI first
|
||||||
|
isLoggingIn.value = true;
|
||||||
|
|
||||||
|
// // try get salt
|
||||||
|
// if (ccn_api_common_salt(username)) {
|
||||||
|
// // continue login
|
||||||
|
// if (ccn_api_common_login(username, password)) {
|
||||||
|
// // ok, logged
|
||||||
|
// // jump into home page again
|
||||||
|
// window.location.href = '/web/home';
|
||||||
|
// } else ccn_messagebox_Show($.i18n.prop("ccn-i18n-js-fail-login"));
|
||||||
|
// } else ccn_messagebox_Show($.i18n.prop("ccn-i18n-js-fail-login"));
|
||||||
|
|
||||||
|
const token = await apiCommonWebLogin(username.value, password.value);
|
||||||
|
if (typeof token !== 'undefined') {
|
||||||
|
// OK. We have logged in.
|
||||||
|
// Update token storage
|
||||||
|
const tokenStore = useTokenStore();
|
||||||
|
tokenStore.login(token);
|
||||||
|
// Go to home page.
|
||||||
|
goToHome();
|
||||||
|
} else {
|
||||||
|
// Show login error.
|
||||||
|
messagebox.value?.show("Fail to login. Please check your username or password.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable all UI
|
||||||
|
isLoggingIn.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
@@ -16,7 +50,7 @@ const login = () => {
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">User Name</label>
|
<label class="label">User Name</label>
|
||||||
<div class="control has-icons-left has-icons-right">
|
<div class="control has-icons-left has-icons-right">
|
||||||
<input id="ccn-login-form-username" class="input" type="text">
|
<input v-model="username" :disabled="isLoggingIn" class="input" type="text">
|
||||||
<span class="icon is-small is-left">
|
<span class="icon is-small is-left">
|
||||||
<font-awesome-icon icon="fas fa-user"></font-awesome-icon>
|
<font-awesome-icon icon="fas fa-user"></font-awesome-icon>
|
||||||
</span>
|
</span>
|
||||||
@@ -25,7 +59,7 @@ const login = () => {
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">Password</label>
|
<label class="label">Password</label>
|
||||||
<p class="control has-icons-left">
|
<p class="control has-icons-left">
|
||||||
<input id="ccn-login-form-password" class="input" type="password">
|
<input v-model="password" :disabled="isLoggingIn" class="input" type="password">
|
||||||
<span class="icon is-small is-left">
|
<span class="icon is-small is-left">
|
||||||
<font-awesome-icon icon="fas fa-lock"></font-awesome-icon>
|
<font-awesome-icon icon="fas fa-lock"></font-awesome-icon>
|
||||||
</span>
|
</span>
|
||||||
@@ -33,7 +67,7 @@ const login = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button class="button is-primary" @click="login">Login</button>
|
<button class="button is-primary" :disabled="isLoggingIn" @click="login">Login</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user