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:
@@ -17,6 +17,7 @@
|
||||
"@fortawesome/fontawesome-svg-core": "^7.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^7.2.0",
|
||||
"@fortawesome/vue-fontawesome": "^3.2.0",
|
||||
"axios": "1.14.0",
|
||||
"bulma": "0.9.1",
|
||||
"pinia": "^3.0.4",
|
||||
"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':
|
||||
specifier: ^3.2.0
|
||||
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:
|
||||
specifier: 0.9.1
|
||||
version: 0.9.1
|
||||
@@ -876,6 +879,12 @@ packages:
|
||||
resolution: {integrity: sha512-cbdCP0PGOBq0ASG+sjnKIoYkWMKhhz+F/h9pRexUdX2Hd38+WOlBkRKlqkGOSm0YQpcFMQBJeK4WspUAkwsEdg==}
|
||||
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:
|
||||
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
@@ -911,6 +920,10 @@ packages:
|
||||
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
|
||||
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:
|
||||
resolution: {integrity: sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==}
|
||||
|
||||
@@ -922,6 +935,10 @@ packages:
|
||||
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
|
||||
engines: {node: '>= 20.19.0'}
|
||||
|
||||
combined-stream@1.0.8:
|
||||
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
confbox@0.1.8:
|
||||
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
||||
|
||||
@@ -974,10 +991,18 @@ packages:
|
||||
defu@6.1.7:
|
||||
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:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
dunder-proto@1.0.1:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
electron-to-chromium@1.5.344:
|
||||
resolution: {integrity: sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==}
|
||||
|
||||
@@ -988,6 +1013,22 @@ packages:
|
||||
error-stack-parser-es@1.0.5:
|
||||
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:
|
||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -1107,15 +1148,39 @@ packages:
|
||||
flatted@3.4.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
function-bind@1.1.2:
|
||||
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||
|
||||
gensync@1.0.0-beta.2:
|
||||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||
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:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -1124,6 +1189,22 @@ packages:
|
||||
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
||||
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:
|
||||
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||
|
||||
@@ -1314,6 +1395,10 @@ packages:
|
||||
magic-string@0.30.21:
|
||||
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:
|
||||
resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
@@ -1326,6 +1411,14 @@ packages:
|
||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||
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:
|
||||
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
|
||||
engines: {node: 18 || 20 || >=22}
|
||||
@@ -1478,6 +1571,10 @@ packages:
|
||||
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||
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:
|
||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -2572,6 +2669,16 @@ snapshots:
|
||||
'@babel/parser': 7.29.2
|
||||
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: {}
|
||||
|
||||
baseline-browser-mapping@2.10.23: {}
|
||||
@@ -2602,6 +2709,11 @@ snapshots:
|
||||
dependencies:
|
||||
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: {}
|
||||
|
||||
chokidar@4.0.3:
|
||||
@@ -2613,6 +2725,10 @@ snapshots:
|
||||
dependencies:
|
||||
readdirp: 5.0.0
|
||||
|
||||
combined-stream@1.0.8:
|
||||
dependencies:
|
||||
delayed-stream: 1.0.0
|
||||
|
||||
confbox@0.1.8: {}
|
||||
|
||||
confbox@0.2.4: {}
|
||||
@@ -2650,14 +2766,37 @@ snapshots:
|
||||
|
||||
defu@6.1.7: {}
|
||||
|
||||
delayed-stream@1.0.0: {}
|
||||
|
||||
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: {}
|
||||
|
||||
entities@7.0.1: {}
|
||||
|
||||
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: {}
|
||||
|
||||
escape-string-regexp@4.0.0: {}
|
||||
@@ -2792,11 +2931,41 @@ snapshots:
|
||||
|
||||
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:
|
||||
optional: true
|
||||
|
||||
function-bind@1.1.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:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
@@ -2805,6 +2974,18 @@ snapshots:
|
||||
dependencies:
|
||||
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: {}
|
||||
|
||||
ignore@5.3.2: {}
|
||||
@@ -2940,6 +3121,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
memorystream@0.3.1: {}
|
||||
|
||||
merge2@1.4.1: {}
|
||||
@@ -2949,6 +3132,12 @@ snapshots:
|
||||
braces: 3.0.3
|
||||
picomatch: 2.3.2
|
||||
|
||||
mime-db@1.52.0: {}
|
||||
|
||||
mime-types@2.1.35:
|
||||
dependencies:
|
||||
mime-db: 1.52.0
|
||||
|
||||
minimatch@10.2.5:
|
||||
dependencies:
|
||||
brace-expansion: 5.0.5
|
||||
@@ -3100,6 +3289,8 @@ snapshots:
|
||||
|
||||
prelude-ls@1.2.1: {}
|
||||
|
||||
proxy-from-env@2.1.0: {}
|
||||
|
||||
punycode@2.3.1: {}
|
||||
|
||||
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
|
||||
|
||||
@@ -1,11 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import MessageBox from '@/components/MessageBox.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 login = () => {
|
||||
messagebox.value?.show("Fail to login. Please check your username or password.")
|
||||
const login = async () => {
|
||||
// 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>
|
||||
@@ -16,7 +50,7 @@ const login = () => {
|
||||
<div class="field">
|
||||
<label class="label">User Name</label>
|
||||
<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">
|
||||
<font-awesome-icon icon="fas fa-user"></font-awesome-icon>
|
||||
</span>
|
||||
@@ -25,7 +59,7 @@ const login = () => {
|
||||
<div class="field">
|
||||
<label class="label">Password</label>
|
||||
<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">
|
||||
<font-awesome-icon icon="fas fa-lock"></font-awesome-icon>
|
||||
</span>
|
||||
@@ -33,7 +67,7 @@ const login = () => {
|
||||
</div>
|
||||
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user