refactor: refactor for modern layout
- split frontend and backend. - update backend with modern Python dev strategies.
This commit is contained in:
1 parent
8e72e75a15
commit
a0e3385670
65 files changed
+474
-134
No files matched your search
+3
-16
@@ -1,16 +1,3 @@
|
|||||||
# ignore sqlite db
|
## ======== Personal ========
|
||||||
*.db
|
# Ignore VSCode
|
||||||
|
.vscode/
|
||||||
# ignore my debug setting
|
|
||||||
*.cfg
|
|
||||||
|
|
||||||
# ignore py cache
|
|
||||||
src/__pycache__
|
|
||||||
|
|
||||||
# ignore any image first
|
|
||||||
*.png
|
|
||||||
*.jpg
|
|
||||||
*.gif
|
|
||||||
|
|
||||||
# elimate vscode
|
|
||||||
.vscode
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
## ======== Personal ========
|
||||||
|
# Database file
|
||||||
|
*.db
|
||||||
|
|
||||||
|
# Ignore setting file
|
||||||
|
coconut-leaf.toml
|
||||||
|
|
||||||
|
## ======== Python ========
|
||||||
|
# Python-generated files
|
||||||
|
__pycache__/
|
||||||
|
*.py[oc]
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
wheels/
|
||||||
|
*.egg-info
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
3.11
|
||||||
File renamed without changes.
@@ -0,0 +1,76 @@
|
|||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
from argparse import ArgumentParser
|
||||||
|
from typing import cast
|
||||||
|
from pathlib import Path
|
||||||
|
import server
|
||||||
|
import config
|
||||||
|
import utils
|
||||||
|
import database
|
||||||
|
|
||||||
|
|
||||||
|
def GetUsernamePassword():
|
||||||
|
print("What is the first username of this calendar system?")
|
||||||
|
cache = input()
|
||||||
|
while not utils.IsValidUsername(cache):
|
||||||
|
print("Sorry, invalid data. Please try again.")
|
||||||
|
cache = input()
|
||||||
|
username = cache
|
||||||
|
|
||||||
|
print("Input this user password:")
|
||||||
|
cache = input()
|
||||||
|
while not utils.IsValidPassword(cache):
|
||||||
|
print("Sorry, invalid data. Please try again.")
|
||||||
|
cache = input()
|
||||||
|
password = cache
|
||||||
|
|
||||||
|
return (username, password)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("Coconut-leaf")
|
||||||
|
print("A self-host, multi-account calendar system.")
|
||||||
|
print("Project: https://github.com/yyc12345/coconut-leaf")
|
||||||
|
print("===================")
|
||||||
|
|
||||||
|
# Receive arguments
|
||||||
|
parser = ArgumentParser(description="Coconut-leaf")
|
||||||
|
parser.add_argument(
|
||||||
|
"-c",
|
||||||
|
"--config",
|
||||||
|
required=True,
|
||||||
|
type=Path,
|
||||||
|
action="store",
|
||||||
|
metavar="CONFIG_TOML",
|
||||||
|
dest="config",
|
||||||
|
help="The configuration file for coconut-leaf",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-i",
|
||||||
|
"--init",
|
||||||
|
action="store_true",
|
||||||
|
dest="init",
|
||||||
|
help="Set for initialize the calendar system",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Load config file
|
||||||
|
try:
|
||||||
|
config.setup_config(cast(Path, args.config))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error loading config file: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Setup logging level
|
||||||
|
logging_level = logging.DEBUG if config.get_config().others.debug else logging.INFO
|
||||||
|
logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging_level)
|
||||||
|
|
||||||
|
# Initialize the calendar system if needed
|
||||||
|
if cast(bool, args.init):
|
||||||
|
gotten_data = GetUsernamePassword()
|
||||||
|
calendar = database.CalendarDatabase()
|
||||||
|
calendar.init(*gotten_data)
|
||||||
|
calendar.close()
|
||||||
|
|
||||||
|
logging.info("Staring server...")
|
||||||
|
server.run()
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
[database]
|
||||||
|
driver = "sqlite"
|
||||||
|
|
||||||
|
[database.config]
|
||||||
|
path = "coconut-leaf.db"
|
||||||
|
|
||||||
|
# [database]
|
||||||
|
# driver = "mysql"
|
||||||
|
#
|
||||||
|
# [database.config]
|
||||||
|
# host = "localhost"
|
||||||
|
# port = 3306
|
||||||
|
# user = "root"
|
||||||
|
# password = "password"
|
||||||
|
# database = "coconut_leaf"
|
||||||
|
|
||||||
|
[web]
|
||||||
|
port = 8888
|
||||||
|
|
||||||
|
[others]
|
||||||
|
auto-token-clean-duration = 86400
|
||||||
|
debug = true
|
||||||
File renamed without changes.
@@ -0,0 +1,130 @@
|
|||||||
|
import tomllib
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import StrEnum
|
||||||
|
from typing import Optional
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseDriver(StrEnum):
|
||||||
|
SQLITE = "sqlite"
|
||||||
|
MYSQL = "mysql"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_raw(raw: dict):
|
||||||
|
return DatabaseDriver(raw["driver"])
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class SqliteDatabaseConfig:
|
||||||
|
path: str
|
||||||
|
"""Database path"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_raw(raw: dict):
|
||||||
|
return SqliteDatabaseConfig(raw["path"])
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class MysqlDatabaseConfig:
|
||||||
|
host: str
|
||||||
|
"""Database host"""
|
||||||
|
port: int
|
||||||
|
"""Database port"""
|
||||||
|
user: str
|
||||||
|
"""Database user"""
|
||||||
|
password: str
|
||||||
|
"""Database password"""
|
||||||
|
database: str
|
||||||
|
"""Database name"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_raw(raw: dict):
|
||||||
|
return MysqlDatabaseConfig(
|
||||||
|
raw["host"], raw["port"], raw["user"], raw["password"], raw["database"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class DatabaseConfig:
|
||||||
|
driver: DatabaseDriver
|
||||||
|
"""Database driver"""
|
||||||
|
config: SqliteDatabaseConfig | MysqlDatabaseConfig
|
||||||
|
"""Database config"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_raw(raw: dict):
|
||||||
|
if raw["driver"] == DatabaseDriver.SQLITE:
|
||||||
|
return DatabaseConfig(
|
||||||
|
DatabaseDriver.SQLITE, SqliteDatabaseConfig.from_raw(raw["config"])
|
||||||
|
)
|
||||||
|
elif raw["driver"] == DatabaseDriver.MYSQL:
|
||||||
|
return DatabaseConfig(
|
||||||
|
DatabaseDriver.MYSQL, MysqlDatabaseConfig.from_raw(raw["config"])
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid database driver")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class WebConfig:
|
||||||
|
port: int
|
||||||
|
"""Web server port"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_raw(raw: dict):
|
||||||
|
return WebConfig(raw["port"])
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class OthersConfig:
|
||||||
|
debug: bool
|
||||||
|
"""Whether enable debug mode"""
|
||||||
|
auto_token_clean_duration: int
|
||||||
|
"""Auto token clean duration"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_raw(raw: dict):
|
||||||
|
return OthersConfig(raw["debug"], raw["auto-token-clean-duration"])
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Config:
|
||||||
|
database: DatabaseConfig
|
||||||
|
web: WebConfig
|
||||||
|
others: OthersConfig
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_raw(raw: dict):
|
||||||
|
return Config(
|
||||||
|
database=DatabaseConfig.from_raw(raw["database"]),
|
||||||
|
web=WebConfig.from_raw(raw["web"]),
|
||||||
|
others=OthersConfig.from_raw(raw["others"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_CONFIG: Optional[Config] = None
|
||||||
|
|
||||||
|
|
||||||
|
def setup_config(p: Path) -> None:
|
||||||
|
"""
|
||||||
|
Setup config by given path.
|
||||||
|
|
||||||
|
Raise exception if config file is invalid.
|
||||||
|
"""
|
||||||
|
with open(p, "rb") as f:
|
||||||
|
raw = tomllib.load(f)
|
||||||
|
|
||||||
|
global _CONFIG
|
||||||
|
_CONFIG = Config.from_raw(raw)
|
||||||
|
|
||||||
|
|
||||||
|
def get_config() -> Config:
|
||||||
|
"""
|
||||||
|
Get config instance.
|
||||||
|
|
||||||
|
Raises RuntimeError if config is not loaded.
|
||||||
|
"""
|
||||||
|
if _CONFIG is None:
|
||||||
|
raise RuntimeError("Config is not loaded. Call setup_config() first.")
|
||||||
|
else:
|
||||||
|
return _CONFIG
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
import config
|
import config
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import json
|
|
||||||
import utils
|
import utils
|
||||||
import threading
|
import threading
|
||||||
import logging
|
import logging
|
||||||
import dt
|
import dt
|
||||||
|
from typing import cast
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
def SafeDatabaseOperation(func):
|
def SafeDatabaseOperation(func):
|
||||||
def wrapper(self, *args, **kwargs):
|
def wrapper(self: 'CalendarDatabase', *args, **kwargs):
|
||||||
|
cfg = config.get_config()
|
||||||
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
# check database and acquire cursor
|
# check database and acquire cursor
|
||||||
try:
|
try:
|
||||||
@@ -15,16 +18,16 @@ def SafeDatabaseOperation(func):
|
|||||||
self.cursor = self.db.cursor()
|
self.cursor = self.db.cursor()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.cursor = None
|
self.cursor = None
|
||||||
if config.CustomConfig['debug']:
|
if cfg.others.debug:
|
||||||
logging.exception(e)
|
logging.exception(e)
|
||||||
return (False, str(e), None)
|
return (False, str(e), None)
|
||||||
|
|
||||||
# do real data work
|
# do real data work
|
||||||
try:
|
try:
|
||||||
currentTime = utils.GetCurrentTimestamp()
|
currentTime = utils.GetCurrentTimestamp()
|
||||||
if currentTime - self.latestClean > config.CustomConfig['auto-token-clean-duration']:
|
if currentTime - self.latestClean > cfg.others.auto_token_clean_duration:
|
||||||
self.latestClean = currentTime
|
self.latestClean = currentTime
|
||||||
print('Cleaning outdated token...')
|
logging.info('Cleaning outdated token...')
|
||||||
self.tokenOper_clean()
|
self.tokenOper_clean()
|
||||||
|
|
||||||
result = (True, '', func(self, *args, **kwargs))
|
result = (True, '', func(self, *args, **kwargs))
|
||||||
@@ -36,13 +39,19 @@ def SafeDatabaseOperation(func):
|
|||||||
self.cursor.close()
|
self.cursor.close()
|
||||||
self.cursor = None
|
self.cursor = None
|
||||||
self.db.rollback()
|
self.db.rollback()
|
||||||
if config.CustomConfig['debug']:
|
if cfg.others.debug:
|
||||||
logging.exception(e)
|
logging.exception(e)
|
||||||
return (False, str(e), None)
|
return (False, str(e), None)
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
class CalendarDatabase(object):
|
class CalendarDatabase:
|
||||||
|
|
||||||
|
db: sqlite3.Connection
|
||||||
|
cursor: sqlite3.Cursor
|
||||||
|
mutex: threading.Lock
|
||||||
|
latestClean: int
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.db = None
|
self.db = None
|
||||||
self.cursor = None
|
self.cursor = None
|
||||||
@@ -53,13 +62,15 @@ class CalendarDatabase(object):
|
|||||||
if (self.is_database_valid()):
|
if (self.is_database_valid()):
|
||||||
raise Exception('Databade is opened')
|
raise Exception('Databade is opened')
|
||||||
|
|
||||||
if config.CustomConfig['database-type'] == 'sqlite':
|
cfg = config.get_config()
|
||||||
self.db = sqlite3.connect(config.CustomConfig['database-config']['url'], check_same_thread = False)
|
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 encoding = "UTF-8";')
|
||||||
self.db.execute('PRAGMA foreign_keys = ON;')
|
self.db.execute('PRAGMA foreign_keys = ON;')
|
||||||
elif config.CustomConfig['database-type'] == 'mysql':
|
case config.DatabaseDriver.MYSQL:
|
||||||
raise Exception('Not implemented database')
|
raise Exception('Not implemented database')
|
||||||
else:
|
case _:
|
||||||
raise Exception('Unknow database type')
|
raise Exception('Unknow database type')
|
||||||
|
|
||||||
def init(self, username, password):
|
def init(self, username, password):
|
||||||
@@ -67,9 +78,20 @@ class CalendarDatabase(object):
|
|||||||
raise Exception('Database is opened')
|
raise Exception('Database is opened')
|
||||||
|
|
||||||
# establish tables
|
# establish tables
|
||||||
|
cfg = config.get_config()
|
||||||
|
backend_path = Path(__file__).resolve().parent
|
||||||
|
backend_sql_path = backend_path / 'sql'
|
||||||
|
match cfg.database.driver:
|
||||||
|
case config.DatabaseDriver.SQLITE:
|
||||||
|
sql_file = backend_sql_path / 'sqlite.sql'
|
||||||
|
case config.DatabaseDriver.MYSQL:
|
||||||
|
raise Exception('Not implemented database')
|
||||||
|
case _:
|
||||||
|
raise Exception('Unknow database type')
|
||||||
|
|
||||||
self.open()
|
self.open()
|
||||||
cursor = self.db.cursor()
|
cursor = self.db.cursor()
|
||||||
with open('sql/sqlite.sql', 'r', encoding='utf-8') as fsql:
|
with open(sql_file, 'r', encoding='utf-8') as fsql:
|
||||||
cursor.executescript(fsql.read())
|
cursor.executescript(fsql.read())
|
||||||
|
|
||||||
# finish init
|
# finish init
|
||||||
@@ -272,11 +294,11 @@ class CalendarDatabase(object):
|
|||||||
sqlList.append('[ccn_loopDateTimeStart] = ?')
|
sqlList.append('[ccn_loopDateTimeStart] = ?')
|
||||||
argumentsList.append(analyseData[5])
|
argumentsList.append(analyseData[5])
|
||||||
sqlList.append('[ccn_loopDateTimeEnd] = ?')
|
sqlList.append('[ccn_loopDateTimeEnd] = ?')
|
||||||
argumentsList.append(dt.ResolveLoopStr(
|
argumentsList.append(str(dt.ResolveLoopStr(
|
||||||
analyseData[8],
|
analyseData[8],
|
||||||
analyseData[5],
|
analyseData[5],
|
||||||
analyseData[7]
|
analyseData[7]
|
||||||
))
|
)))
|
||||||
|
|
||||||
# execute
|
# execute
|
||||||
argumentsList.append(uuid)
|
argumentsList.append(uuid)
|
||||||
@@ -531,8 +553,8 @@ class CalendarDatabase(object):
|
|||||||
argumentsList.append(_username)
|
argumentsList.append(_username)
|
||||||
self.cursor.execute('UPDATE user SET {} WHERE [ccn_name] = ?;'.format(', '.join(sqlList)),
|
self.cursor.execute('UPDATE user SET {} WHERE [ccn_name] = ?;'.format(', '.join(sqlList)),
|
||||||
tuple(argumentsList))
|
tuple(argumentsList))
|
||||||
print(cache)
|
logging.debug(cache)
|
||||||
print(tuple(argumentsList))
|
logging.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
|
||||||
+27
-20
@@ -1,6 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import time
|
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
|
import typing
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
@@ -13,7 +14,9 @@ MAX_TIMESTAMP = int(MAX_DATETIME.timestamp() / 60)
|
|||||||
DAY1_SPAN = 60 * 24
|
DAY1_SPAN = 60 * 24
|
||||||
DAY7_SPAN = 7 * DAY1_SPAN
|
DAY7_SPAN = 7 * DAY1_SPAN
|
||||||
|
|
||||||
def ResolveLoopStr(strl, starttime, tzoffset):
|
LoopHandle = typing.Callable[[re.Match, int, int, int], int]
|
||||||
|
|
||||||
|
def ResolveLoopStr(strl: str, starttime: int, tzoffset: int) -> int:
|
||||||
# check no loop
|
# check no loop
|
||||||
if strl == '':
|
if strl == '':
|
||||||
return starttime
|
return starttime
|
||||||
@@ -40,7 +43,7 @@ def ResolveLoopStr(strl, starttime, tzoffset):
|
|||||||
raise Exception('Invalid loopRules')
|
raise Exception('Invalid loopRules')
|
||||||
|
|
||||||
|
|
||||||
def LoopHandle_Year(searchResult, starttime, times, tzoffset):
|
def LoopHandle_Year(searchResult: re.Match, starttime: int, times: int, tzoffset: int) -> int:
|
||||||
clientDate = datetime.datetime.fromtimestamp(starttime * 60, UTCTimezone(tzoffset))
|
clientDate = datetime.datetime.fromtimestamp(starttime * 60, UTCTimezone(tzoffset))
|
||||||
isStrict = searchResult.group(1) == 'S'
|
isStrict = searchResult.group(1) == 'S'
|
||||||
yearSpan = int(searchResult.group(2))
|
yearSpan = int(searchResult.group(2))
|
||||||
@@ -52,7 +55,7 @@ def LoopHandle_Year(searchResult, starttime, times, tzoffset):
|
|||||||
if clientMonth == 2 and clientDay == 29:
|
if clientMonth == 2 and clientDay == 29:
|
||||||
if isStrict:
|
if isStrict:
|
||||||
realSpan = utils.LCM(yearSpan, 4)
|
realSpan = utils.LCM(yearSpan, 4)
|
||||||
print(realSpan)
|
logging.debug(realSpan)
|
||||||
valCache = starttime
|
valCache = starttime
|
||||||
while valCache < MAX_TIMESTAMP and times > 0:
|
while valCache < MAX_TIMESTAMP and times > 0:
|
||||||
newYear += realSpan
|
newYear += realSpan
|
||||||
@@ -71,7 +74,7 @@ def LoopHandle_Year(searchResult, starttime, times, tzoffset):
|
|||||||
val = starttime + DAY1_SPAN * (DaysCount(newYear, newMonth, newDay) - DaysCount(clientYear, clientMonth, clientDay))
|
val = starttime + DAY1_SPAN * (DaysCount(newYear, newMonth, newDay) - DaysCount(clientYear, clientMonth, clientDay))
|
||||||
return val if val < MAX_TIMESTAMP else MAX_TIMESTAMP
|
return val if val < MAX_TIMESTAMP else MAX_TIMESTAMP
|
||||||
|
|
||||||
def LoopHandle_Month(searchResult, starttime, times, tzoffset):
|
def LoopHandle_Month(searchResult: re.Match, starttime: int, times: int, tzoffset: int) -> int:
|
||||||
isStrict = searchResult.group(1) == 'S'
|
isStrict = searchResult.group(1) == 'S'
|
||||||
loopType = searchResult.group(2)
|
loopType = searchResult.group(2)
|
||||||
monthSpan = int(searchResult.group(3))
|
monthSpan = int(searchResult.group(3))
|
||||||
@@ -144,7 +147,7 @@ def LoopHandle_Month(searchResult, starttime, times, tzoffset):
|
|||||||
val = starttime + DAY1_SPAN * (DaysCount(newYear, newMonth, newDay) - DaysCount(clientYear, clientMonth, clientDay))
|
val = starttime + DAY1_SPAN * (DaysCount(newYear, newMonth, newDay) - DaysCount(clientYear, clientMonth, clientDay))
|
||||||
return val if val < MAX_TIMESTAMP else MAX_TIMESTAMP
|
return val if val < MAX_TIMESTAMP else MAX_TIMESTAMP
|
||||||
|
|
||||||
def LoopHandle_Week(searchResult, starttime, times, tzoffset):
|
def LoopHandle_Week(searchResult: re.Match, starttime: int, times: int, tzoffset: int) -> int:
|
||||||
weekOccupied = tuple(map(lambda x: x == 'T', searchResult.group(1)))
|
weekOccupied = tuple(map(lambda x: x == 'T', searchResult.group(1)))
|
||||||
weekEventCount = reduce(lambda x, y: x + (1 if y else 0), weekOccupied, 0)
|
weekEventCount = reduce(lambda x, y: x + (1 if y else 0), weekOccupied, 0)
|
||||||
if weekEventCount == 0:
|
if weekEventCount == 0:
|
||||||
@@ -170,25 +173,25 @@ def LoopHandle_Week(searchResult, starttime, times, tzoffset):
|
|||||||
val -= 1
|
val -= 1
|
||||||
return val if val < MAX_TIMESTAMP else MAX_TIMESTAMP
|
return val if val < MAX_TIMESTAMP else MAX_TIMESTAMP
|
||||||
|
|
||||||
def LoopHandle_Day(searchResult, starttime, times, tzoffset):
|
def LoopHandle_Day(searchResult: re.Match, starttime: int, times: int, tzoffset: int) -> int:
|
||||||
val = starttime + DAY1_SPAN * times * int(searchResult.group(1))
|
val = starttime + DAY1_SPAN * times * int(searchResult.group(1))
|
||||||
val -= 1
|
val -= 1
|
||||||
return val if val < MAX_TIMESTAMP else MAX_TIMESTAMP
|
return val if val < MAX_TIMESTAMP else MAX_TIMESTAMP
|
||||||
|
|
||||||
precompiledLoopRules = (
|
precompiledLoopRules: tuple[tuple[re.Pattern, LoopHandle], ...] = (
|
||||||
(re.compile(r'^Y([SR]{1})([1-9]\d*)$'), LoopHandle_Year),
|
(re.compile(r'^Y([SR]{1})([1-9]\d*)$'), LoopHandle_Year),
|
||||||
(re.compile(r'^M([SR]{1})([ABCD]{1})([1-9]\d*)$'), LoopHandle_Month),
|
(re.compile(r'^M([SR]{1})([ABCD]{1})([1-9]\d*)$'), LoopHandle_Month),
|
||||||
(re.compile(r'^W([TF]{7})([1-9]\d*)$'), LoopHandle_Week),
|
(re.compile(r'^W([TF]{7})([1-9]\d*)$'), LoopHandle_Week),
|
||||||
(re.compile(r'^D([1-9]\d*)$'), LoopHandle_Day)
|
(re.compile(r'^D([1-9]\d*)$'), LoopHandle_Day)
|
||||||
)
|
)
|
||||||
|
|
||||||
precompiledLoopStopRules = {
|
precompiledLoopStopRules: dict[str, re.Pattern] = {
|
||||||
'infinity': re.compile(r'^F$'),
|
'infinity': re.compile(r'^F$'),
|
||||||
'datetime': re.compile(r'^D([1-9]\d*|0)$'),
|
'datetime': re.compile(r'^D([1-9]\d*|0)$'),
|
||||||
'times': re.compile(r'^T([1-9]\d*)$')
|
'times': re.compile(r'^T([1-9]\d*)$')
|
||||||
}
|
}
|
||||||
|
|
||||||
def LeapYearCountEx(endYear, includeThis = False, baseYear = 1, includeBase = True):
|
def LeapYearCountEx(endYear: int, includeThis: bool = False, baseYear: int = 1, includeBase: bool = True):
|
||||||
if not includeThis:
|
if not includeThis:
|
||||||
endYear -= 1
|
endYear -= 1
|
||||||
if includeBase:
|
if includeBase:
|
||||||
@@ -204,10 +207,10 @@ def LeapYearCountEx(endYear, includeThis = False, baseYear = 1, includeBase = Tr
|
|||||||
|
|
||||||
return (endly - basely)
|
return (endly - basely)
|
||||||
|
|
||||||
def LeapYearCount(year):
|
def LeapYearCount(year: int):
|
||||||
return LeapYearCountEx(year, False, 1, True)
|
return LeapYearCountEx(year, False, 1, True)
|
||||||
|
|
||||||
def IsLeapYear(year):
|
def IsLeapYear(year: int):
|
||||||
isLeap = False
|
isLeap = False
|
||||||
if year % 4 == 0:
|
if year % 4 == 0:
|
||||||
isLeap = True
|
isLeap = True
|
||||||
@@ -217,7 +220,7 @@ def IsLeapYear(year):
|
|||||||
isLeap = True
|
isLeap = True
|
||||||
return isLeap
|
return isLeap
|
||||||
|
|
||||||
def DaysCount(year, month, day):
|
def DaysCount(year: int, month: int, day: int):
|
||||||
ly = LeapYearCountEx(year, False, 1, True)
|
ly = LeapYearCountEx(year, False, 1, True)
|
||||||
days = 365 * (year - 1)
|
days = 365 * (year - 1)
|
||||||
days += ly
|
days += ly
|
||||||
@@ -231,7 +234,7 @@ def DaysCount(year, month, day):
|
|||||||
days += day - 1
|
days += day - 1
|
||||||
return days
|
return days
|
||||||
|
|
||||||
def DayOfWeek(year, month, day):
|
def DayOfWeek(year: int, month: int, day: int):
|
||||||
# as we know, 1/1/1900 is Monday.
|
# as we know, 1/1/1900 is Monday.
|
||||||
# via this method, we can got 1/1/1 is Monday
|
# via this method, we can got 1/1/1 is Monday
|
||||||
# compute day span
|
# compute day span
|
||||||
@@ -240,7 +243,7 @@ def DayOfWeek(year, month, day):
|
|||||||
# return day of week (from 0 - 6, corresponding with python)
|
# return day of week (from 0 - 6, corresponding with python)
|
||||||
return days % 7
|
return days % 7
|
||||||
|
|
||||||
def GetDayInMonth(year, month, day):
|
def GetDayInMonth(year: int, month: int, day: int):
|
||||||
days = MonthDayCount[month - 1] + (1 if (month == 2 and IsLeapYear(year)) else 0)
|
days = MonthDayCount[month - 1] + (1 if (month == 2 and IsLeapYear(year)) else 0)
|
||||||
firstDayOfWeek = DayOfWeek(year, month, 1)
|
firstDayOfWeek = DayOfWeek(year, month, 1)
|
||||||
dayOfWeek = (firstDayOfWeek + day - 1) % 7
|
dayOfWeek = (firstDayOfWeek + day - 1) % 7
|
||||||
@@ -253,7 +256,7 @@ def GetDayInMonth(year, month, day):
|
|||||||
|
|
||||||
return (dayForwards, dayBackwards, weeksForward, dayOfWeek, weeksBackwards, dayOfWeek)
|
return (dayForwards, dayBackwards, weeksForward, dayOfWeek, weeksBackwards, dayOfWeek)
|
||||||
|
|
||||||
def GetMonthWeekStatistics(year, month):
|
def GetMonthWeekStatistics(year: int, month: int):
|
||||||
days = MonthDayCount[month - 1] + (1 if (month == 2 and IsLeapYear(year)) else 0)
|
days = MonthDayCount[month - 1] + (1 if (month == 2 and IsLeapYear(year)) else 0)
|
||||||
firstDayOfWeek = DayOfWeek(year, month, 1)
|
firstDayOfWeek = DayOfWeek(year, month, 1)
|
||||||
lastDayOfWeek = (firstDayOfWeek + days - 1) % 7
|
lastDayOfWeek = (firstDayOfWeek + days - 1) % 7
|
||||||
@@ -269,14 +272,18 @@ def GetMonthWeekStatistics(year, month):
|
|||||||
return tuple(result)
|
return tuple(result)
|
||||||
|
|
||||||
class UTCTimezone(datetime.tzinfo):
|
class UTCTimezone(datetime.tzinfo):
|
||||||
def __init__(self, offset = 0):
|
|
||||||
self._offset = offset
|
__offset: int
|
||||||
|
|
||||||
|
def __init__(self, offset: int = 0):
|
||||||
|
self.__offset = offset
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
def utcoffset(self, dt):
|
||||||
return datetime.timedelta(minutes=self._offset)
|
return datetime.timedelta(minutes=self.__offset)
|
||||||
|
|
||||||
def tzname(self, dt):
|
def tzname(self, dt):
|
||||||
return 'UTC {}'.format(self._offset)
|
return 'UTC {}'.format(self.__offset)
|
||||||
|
|
||||||
def dst(self, dt):
|
def dst(self, dt):
|
||||||
return datetime.timedelta(0)
|
return datetime.timedelta(0)
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
[project]
|
||||||
|
name = "coleaf-backend"
|
||||||
|
version = "1.1.0"
|
||||||
|
description = "The backend of coconut-leaf."
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
dependencies = [
|
||||||
|
"flask==2.2.3",
|
||||||
|
]
|
||||||
|
[tool.uv]
|
||||||
|
constraint-dependencies = [
|
||||||
|
"Werkzeug==2.2.2",
|
||||||
|
"MarkupSafe==2.1.5"
|
||||||
|
]
|
||||||
@@ -1,39 +1,45 @@
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask import g
|
# from flask import g
|
||||||
from flask import render_template
|
from flask import render_template
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
from flask import request
|
from flask import request
|
||||||
from flask import abort
|
# from flask import abort
|
||||||
from flask import redirect
|
from flask import redirect
|
||||||
|
|
||||||
from functools import reduce
|
# from functools import reduce
|
||||||
import json
|
# import json
|
||||||
import os
|
# import os
|
||||||
|
|
||||||
import config
|
import config
|
||||||
import database
|
import database
|
||||||
import utils
|
import utils
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
app = Flask(__name__)
|
_FRONTEND_PATH = Path(__file__).resolve().parent.parent / "frontend"
|
||||||
|
app = Flask(
|
||||||
|
__name__,
|
||||||
|
static_folder=_FRONTEND_PATH / "static",
|
||||||
|
template_folder=_FRONTEND_PATH / "templates",
|
||||||
|
)
|
||||||
calendar_db = database.CalendarDatabase()
|
calendar_db = database.CalendarDatabase()
|
||||||
|
|
||||||
# render_static_resources = None
|
# render_static_resources = None
|
||||||
|
|
||||||
# =============================================database
|
# =============================================database
|
||||||
'''
|
|
||||||
def get_database():
|
|
||||||
db = getattr(g, '_database', None)
|
|
||||||
if db is None:
|
|
||||||
db = database.CalendarDatabase()
|
|
||||||
db.open()
|
|
||||||
return db
|
|
||||||
|
|
||||||
@app.teardown_appcontext
|
# def get_database():
|
||||||
def close_database(exception):
|
# db = getattr(g, '_database', None)
|
||||||
db = getattr(g, '_database', None)
|
# if db is None:
|
||||||
if db is not None:
|
# db = database.CalendarDatabase()
|
||||||
db.close()
|
# db.open()
|
||||||
'''
|
# return db
|
||||||
|
|
||||||
|
# @app.teardown_appcontext
|
||||||
|
# def close_database(exception):
|
||||||
|
# db = getattr(g, '_database', None)
|
||||||
|
# if db is not None:
|
||||||
|
# db.close()
|
||||||
|
|
||||||
|
|
||||||
# ============================================= static page route
|
# ============================================= static page route
|
||||||
|
|
||||||
@@ -216,7 +222,7 @@ def api_collection_getFullOwnHandle():
|
|||||||
|
|
||||||
@app.route('/api/collection/getListOwn', methods=['POST'])
|
@app.route('/api/collection/getListOwn', methods=['POST'])
|
||||||
def api_collection_getListOwnHandle():
|
def api_collection_getListOwnHandle():
|
||||||
return SmartDbCaller(calendar_db.collection_getListlOwn,
|
return SmartDbCaller(calendar_db.collection_getListOwn,
|
||||||
(('token', str, False), ),
|
(('token', str, False), ),
|
||||||
None)
|
None)
|
||||||
|
|
||||||
@@ -446,6 +452,6 @@ def ConstructResponseBody(returnedTuple):
|
|||||||
|
|
||||||
def run():
|
def run():
|
||||||
calendar_db.open()
|
calendar_db.open()
|
||||||
app.run(port=config.CustomConfig['web']['port'])
|
app.run(port=config.get_config().web.port)
|
||||||
calendar_db.close()
|
calendar_db.close()
|
||||||
|
|
||||||
Whitespace-only changes.
File renamed without changes.
File renamed without changes.
Generated
+117
@@ -0,0 +1,117 @@
|
|||||||
|
version = 1
|
||||||
|
revision = 2
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
|
||||||
|
[manifest]
|
||||||
|
constraints = [
|
||||||
|
{ name = "markupsafe", specifier = "==2.1.5" },
|
||||||
|
{ name = "werkzeug", specifier = "==2.2.2" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "click"
|
||||||
|
version = "8.3.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/bb/63/f9e1ea081ce35720d8b92acde70daaedace594dc93b693c869e0d5910718/click-8.3.3.tar.gz", hash = "sha256:398329ad4837b2ff7cbe1dd166a4c0f8900c3ca3a218de04466f38f6497f18a2", size = 328061, upload-time = "2026-04-22T15:11:27.506Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ae/44/c1221527f6a71a01ec6fbad7fa78f1d50dfa02217385cf0fa3eec7087d59/click-8.3.3-py3-none-any.whl", hash = "sha256:a2bf429bb3033c89fa4936ffb35d5cb471e3719e1f3c8a7c3fff0b8314305613", size = 110502, upload-time = "2026-04-22T15:11:25.044Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coleaf-backend"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = { virtual = "." }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "flask" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
requires-dist = [{ name = "flask", specifier = "==2.2.3" }]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorama"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flask"
|
||||||
|
version = "2.2.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "click" },
|
||||||
|
{ name = "itsdangerous" },
|
||||||
|
{ name = "jinja2" },
|
||||||
|
{ name = "werkzeug" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/e8/5c/ff9047989bd995b1098d14b03013f160225db2282925b517bb4a967752ee/Flask-2.2.3.tar.gz", hash = "sha256:7eb373984bf1c770023fce9db164ed0c3353cd0b53f130f4693da0ca756a2e6d", size = 697599, upload-time = "2023-02-15T22:43:57.265Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/95/9c/a3542594ce4973786236a1b7b702b8ca81dbf40ea270f0f96284f0c27348/Flask-2.2.3-py3-none-any.whl", hash = "sha256:c0bec9477df1cb867e5a67c9e1ab758de9cb4a3e52dd70681f59fa40a62b3f2d", size = 101839, upload-time = "2023-02-15T22:43:55.501Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itsdangerous"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410, upload-time = "2024-04-16T21:28:15.614Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234, upload-time = "2024-04-16T21:28:14.499Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jinja2"
|
||||||
|
version = "3.1.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "markupsafe" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markupsafe"
|
||||||
|
version = "2.1.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/87/5b/aae44c6655f3801e81aa3eef09dbbf012431987ba564d7231722f68df02d/MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b", size = 19384, upload-time = "2024-02-02T16:31:22.863Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/11/e7/291e55127bb2ae67c64d66cef01432b5933859dfb7d6949daa721b89d0b3/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f", size = 18219, upload-time = "2024-02-02T16:30:19.988Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/cb/aed7a284c00dfa7c0682d14df85ad4955a350a21d2e3b06d8240497359bf/MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2", size = 14098, upload-time = "2024-02-02T16:30:21.063Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1c/cf/35fe557e53709e93feb65575c93927942087e9b97213eabc3fe9d5b25a55/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced", size = 29014, upload-time = "2024-02-02T16:30:22.926Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/97/18/c30da5e7a0e7f4603abfc6780574131221d9148f323752c2755d48abad30/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5", size = 28220, upload-time = "2024-02-02T16:30:24.76Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/40/2e73e7d532d030b1e41180807a80d564eda53babaf04d65e15c1cf897e40/MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c", size = 27756, upload-time = "2024-02-02T16:30:25.877Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/18/46/5dca760547e8c59c5311b332f70605d24c99d1303dd9a6e1fc3ed0d73561/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f", size = 33988, upload-time = "2024-02-02T16:30:26.935Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6d/c5/27febe918ac36397919cd4a67d5579cbbfa8da027fa1238af6285bb368ea/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a", size = 32718, upload-time = "2024-02-02T16:30:28.111Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/81/56e567126a2c2bc2684d6391332e357589a96a76cb9f8e5052d85cb0ead8/MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f", size = 33317, upload-time = "2024-02-02T16:30:29.214Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/00/0b/23f4b2470accb53285c613a3ab9ec19dc944eaf53592cb6d9e2af8aa24cc/MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906", size = 16670, upload-time = "2024-02-02T16:30:30.915Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b7/a2/c78a06a9ec6d04b3445a949615c4c7ed86a0b2eb68e44e7541b9d57067cc/MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617", size = 17224, upload-time = "2024-02-02T16:30:32.09Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/53/bd/583bf3e4c8d6a321938c13f49d44024dbe5ed63e0a7ba127e454a66da974/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1", size = 18215, upload-time = "2024-02-02T16:30:33.081Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/d6/e7cd795fc710292c3af3a06d80868ce4b02bfbbf370b7cee11d282815a2a/MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4", size = 14069, upload-time = "2024-02-02T16:30:34.148Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/51/b5/5d8ec796e2a08fc814a2c7d2584b55f889a55cf17dd1a90f2beb70744e5c/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee", size = 29452, upload-time = "2024-02-02T16:30:35.149Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0a/0d/2454f072fae3b5a137c119abf15465d1771319dfe9e4acbb31722a0fff91/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5", size = 28462, upload-time = "2024-02-02T16:30:36.166Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/75/fd6cb2e68780f72d47e6671840ca517bda5ef663d30ada7616b0462ad1e3/MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b", size = 27869, upload-time = "2024-02-02T16:30:37.834Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/b0/81/147c477391c2750e8fc7705829f7351cf1cd3be64406edcf900dc633feb2/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a", size = 33906, upload-time = "2024-02-02T16:30:39.366Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8b/ff/9a52b71839d7a256b563e85d11050e307121000dcebc97df120176b3ad93/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f", size = 32296, upload-time = "2024-02-02T16:30:40.413Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/88/07/2dc76aa51b481eb96a4c3198894f38b480490e834479611a4053fbf08623/MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169", size = 33038, upload-time = "2024-02-02T16:30:42.243Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/96/0c/620c1fb3661858c0e37eb3cbffd8c6f732a67cd97296f725789679801b31/MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad", size = 16572, upload-time = "2024-02-02T16:30:43.326Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3f/14/c3554d512d5f9100a95e737502f4a2323a1959f6d0d01e0d0997b35f7b10/MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb", size = 17127, upload-time = "2024-02-02T16:30:44.418Z" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "werkzeug"
|
||||||
|
version = "2.2.2"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "markupsafe" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/f8/c1/1c8e539f040acd80f844c69a5ef8e2fccdf8b442dabb969e497b55d544e1/Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f", size = 844378, upload-time = "2022-08-08T21:44:15.376Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c8/27/be6ddbcf60115305205de79c29004a0c6bc53cec814f733467b1bb89386d/Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5", size = 232700, upload-time = "2022-08-08T21:44:13.251Z" },
|
||||||
|
]
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,54 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
import getopt
|
|
||||||
import server
|
|
||||||
import config
|
|
||||||
import utils
|
|
||||||
import database
|
|
||||||
|
|
||||||
def GetUsernamePassword():
|
|
||||||
print('What is the first username of this calendar system?')
|
|
||||||
cache = input()
|
|
||||||
while(not utils.IsValidUsername(cache)):
|
|
||||||
print('Sorry, invalid data. Please try again.')
|
|
||||||
cache = input()
|
|
||||||
username = cache
|
|
||||||
|
|
||||||
print("Input this user password:")
|
|
||||||
cache = input()
|
|
||||||
while(not utils.IsValidPassword(cache)):
|
|
||||||
print('Sorry, invalid data. Please try again.')
|
|
||||||
cache = input()
|
|
||||||
password = cache
|
|
||||||
|
|
||||||
return (username, password)
|
|
||||||
|
|
||||||
print('Coconut-leaf')
|
|
||||||
print('A self-host, multi-account calendar system.')
|
|
||||||
print('Project: https://github.com/yyc12345/coconut-leaf')
|
|
||||||
print('===================')
|
|
||||||
|
|
||||||
# process args
|
|
||||||
# preset init value
|
|
||||||
need_init = False
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "hi")
|
|
||||||
except getopt.GetoptError:
|
|
||||||
print('Wrong arguments!')
|
|
||||||
print('python coconut-leaf.py [-i] [-h]')
|
|
||||||
sys.exit(1)
|
|
||||||
for opt, arg in opts:
|
|
||||||
if opt == '-h':
|
|
||||||
print('python coconut-leaf.py [-i]')
|
|
||||||
sys.exit(0)
|
|
||||||
elif opt == '-i':
|
|
||||||
need_init = True
|
|
||||||
|
|
||||||
if need_init:
|
|
||||||
gotten_data = GetUsernamePassword()
|
|
||||||
calendar = database.CalendarDatabase()
|
|
||||||
calendar.init(*gotten_data)
|
|
||||||
calendar.close()
|
|
||||||
|
|
||||||
print('Staring server...')
|
|
||||||
server.run()
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import json
|
|
||||||
|
|
||||||
CustomConfig = None
|
|
||||||
|
|
||||||
# read cfg
|
|
||||||
with open('config.cfg', 'r', encoding='utf-8') as f:
|
|
||||||
CustomConfig = json.load(f)
|
|
||||||
Reference in new issue
Block a user