lfss 0.2.1__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lfss-0.2.1 → lfss-0.2.3}/PKG-INFO +1 -1
- lfss-0.2.3/docs/Known_issues.md +1 -0
- {lfss-0.2.1 → lfss-0.2.3}/frontend/api.js +27 -1
- lfss-0.2.3/frontend/popup.css +30 -0
- lfss-0.2.3/frontend/popup.js +89 -0
- {lfss-0.2.1 → lfss-0.2.3}/frontend/scripts.js +32 -8
- {lfss-0.2.1 → lfss-0.2.3}/frontend/styles.css +1 -0
- {lfss-0.2.1 → lfss-0.2.3}/lfss/src/database.py +59 -13
- {lfss-0.2.1 → lfss-0.2.3}/lfss/src/error.py +2 -0
- {lfss-0.2.1 → lfss-0.2.3}/lfss/src/server.py +13 -3
- {lfss-0.2.1 → lfss-0.2.3}/pyproject.toml +1 -1
- {lfss-0.2.1 → lfss-0.2.3}/Readme.md +0 -0
- {lfss-0.2.1 → lfss-0.2.3}/docs/Permission.md +0 -0
- {lfss-0.2.1 → lfss-0.2.3}/frontend/index.html +0 -0
- {lfss-0.2.1 → lfss-0.2.3}/frontend/utils.js +0 -0
- {lfss-0.2.1 → lfss-0.2.3}/lfss/cli/panel.py +0 -0
- {lfss-0.2.1 → lfss-0.2.3}/lfss/cli/serve.py +0 -0
- {lfss-0.2.1 → lfss-0.2.3}/lfss/cli/user.py +0 -0
- {lfss-0.2.1 → lfss-0.2.3}/lfss/src/__init__.py +0 -0
- {lfss-0.2.1 → lfss-0.2.3}/lfss/src/config.py +0 -0
- {lfss-0.2.1 → lfss-0.2.3}/lfss/src/log.py +0 -0
- {lfss-0.2.1 → lfss-0.2.3}/lfss/src/utils.py +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
[Safari 中文输入法回车捕获](https://github.com/anse-app/anse/issues/127)
|
@@ -165,7 +165,10 @@ export default class Connector {
|
|
165
165
|
*/
|
166
166
|
async setFilePermission(path, permission){
|
167
167
|
if (path.startsWith('/')){ path = path.slice(1); }
|
168
|
-
const
|
168
|
+
const dst = new URL(this.config.endpoint + '/_api/fmeta');
|
169
|
+
dst.searchParams.append('path', path);
|
170
|
+
dst.searchParams.append('perm', permission);
|
171
|
+
const res = await fetch(dst.toString(), {
|
169
172
|
method: 'POST',
|
170
173
|
headers: {
|
171
174
|
'Authorization': 'Bearer ' + this.config.token
|
@@ -175,4 +178,27 @@ export default class Connector {
|
|
175
178
|
throw new Error(`Failed to set permission, status code: ${res.status}, message: ${await res.json()}`);
|
176
179
|
}
|
177
180
|
}
|
181
|
+
|
182
|
+
/**
|
183
|
+
* @param {string} path - file path(url)
|
184
|
+
* @param {string} newPath - new file path(url)
|
185
|
+
*/
|
186
|
+
async moveFile(path, newPath){
|
187
|
+
if (path.startsWith('/')){ path = path.slice(1); }
|
188
|
+
if (newPath.startsWith('/')){ newPath = newPath.slice(1); }
|
189
|
+
const dst = new URL(this.config.endpoint + '/_api/fmeta');
|
190
|
+
dst.searchParams.append('path', path);
|
191
|
+
dst.searchParams.append('new_path', newPath);
|
192
|
+
const res = await fetch(dst.toString(), {
|
193
|
+
method: 'POST',
|
194
|
+
headers: {
|
195
|
+
'Authorization': 'Bearer ' + this.config.token,
|
196
|
+
'Content-Type': 'application/www-form-urlencoded'
|
197
|
+
},
|
198
|
+
});
|
199
|
+
if (res.status != 200){
|
200
|
+
throw new Error(`Failed to move file, status code: ${res.status}, message: ${await res.json()}`);
|
201
|
+
}
|
202
|
+
}
|
203
|
+
|
178
204
|
}
|
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
div.floating-window.blocker{
|
3
|
+
position: fixed;
|
4
|
+
top: 0;
|
5
|
+
left: 0;
|
6
|
+
width: 100%;
|
7
|
+
height: 100%;
|
8
|
+
background-color: rgba(0,0,0,0.25);
|
9
|
+
z-index: 100;
|
10
|
+
}
|
11
|
+
|
12
|
+
div.floating-window.window{
|
13
|
+
position: fixed;
|
14
|
+
top: 50%;
|
15
|
+
left: 50%;
|
16
|
+
transform: translate(-50%, -50%);
|
17
|
+
background-color: white;
|
18
|
+
box-shadow: 0 0 10px rgba(0,0,0,0.2);
|
19
|
+
border-radius: 0.5rem;
|
20
|
+
z-index: 101;
|
21
|
+
max-width: 80%;
|
22
|
+
max-height: 80%;
|
23
|
+
overflow: auto;
|
24
|
+
text-align: center;
|
25
|
+
|
26
|
+
display: flex;
|
27
|
+
flex-direction: column;
|
28
|
+
justify-content: center;
|
29
|
+
align-items: center;
|
30
|
+
}
|
@@ -0,0 +1,89 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
export function createFloatingWindow(innerHTML = '', {
|
4
|
+
onClose = () => {},
|
5
|
+
width = "auto",
|
6
|
+
height = "auto",
|
7
|
+
padding = "20px",
|
8
|
+
} = {}){
|
9
|
+
const blocker = document.createElement("div");
|
10
|
+
blocker.classList.add("floating-window", "blocker");
|
11
|
+
|
12
|
+
const floatingWindow = document.createElement("div");
|
13
|
+
floatingWindow.classList.add("floating-window", "window");
|
14
|
+
floatingWindow.id = "floatingWindow";
|
15
|
+
floatingWindow.innerHTML = innerHTML;
|
16
|
+
floatingWindow.style.width = width;
|
17
|
+
floatingWindow.style.height = height;
|
18
|
+
floatingWindow.style.padding = padding;
|
19
|
+
|
20
|
+
const container = document.createElement("div");
|
21
|
+
container.classList.add("floating-window", "container");
|
22
|
+
|
23
|
+
document.body.appendChild(blocker);
|
24
|
+
document.body.appendChild(floatingWindow);
|
25
|
+
|
26
|
+
function closeWindow(){
|
27
|
+
onClose();
|
28
|
+
if (blocker.parentNode) document.body.removeChild(blocker);
|
29
|
+
if (floatingWindow.parentNode) document.body.removeChild(floatingWindow);
|
30
|
+
window.removeEventListener("keydown", excapeEvListener);
|
31
|
+
}
|
32
|
+
blocker.onclick = closeWindow;
|
33
|
+
|
34
|
+
const excapeEvListener = (event) => {
|
35
|
+
event.stopPropagation();
|
36
|
+
if (event.key === "Escape") closeWindow();
|
37
|
+
}
|
38
|
+
window.addEventListener("keydown", excapeEvListener);
|
39
|
+
|
40
|
+
return [floatingWindow, closeWindow];
|
41
|
+
}
|
42
|
+
|
43
|
+
/* select can be "last-filename" */
|
44
|
+
export function showFloatingWindowLineInput(onSubmit = (v) => {}, {
|
45
|
+
text = "",
|
46
|
+
placeholder = "Enter text",
|
47
|
+
value = "",
|
48
|
+
select = ""
|
49
|
+
} = {}){
|
50
|
+
const [floatingWindow, closeWindow] = createFloatingWindow(`
|
51
|
+
<div style="margin-bottom: 0.5rem;width: 100%;text-align: left;">${text}</div>
|
52
|
+
<div style="display: flex; flex-direction: row; gap: 0.25rem;">
|
53
|
+
<input type="text" placeholder="${placeholder}" id="floatingWindowInput" value="${value}" style="min-width: 300px;"/>
|
54
|
+
<button id="floatingWindowSubmit">OK</button>
|
55
|
+
</div>
|
56
|
+
`);
|
57
|
+
|
58
|
+
/** @type {HTMLInputElement} */
|
59
|
+
const input = document.getElementById("floatingWindowInput");
|
60
|
+
const submit = document.getElementById("floatingWindowSubmit");
|
61
|
+
|
62
|
+
input.focus();
|
63
|
+
input.addEventListener("keydown", event => {
|
64
|
+
if(event.key === "Enter" && input.value && event.isComposing === false){
|
65
|
+
submit.click();
|
66
|
+
}
|
67
|
+
});
|
68
|
+
|
69
|
+
submit.onclick = () => {
|
70
|
+
onSubmit(input.value);
|
71
|
+
closeWindow();
|
72
|
+
};
|
73
|
+
|
74
|
+
if (select === "last-filename") {
|
75
|
+
const inputVal = input.value;
|
76
|
+
let lastSlash = inputVal.lastIndexOf("/");
|
77
|
+
if (lastSlash === -1) {
|
78
|
+
lastSlash = 0;
|
79
|
+
}
|
80
|
+
const fname = inputVal.slice(lastSlash + 1);
|
81
|
+
let lastDot = fname.lastIndexOf(".");
|
82
|
+
if (lastDot === -1) {
|
83
|
+
lastDot = fname.length;
|
84
|
+
}
|
85
|
+
input.setSelectionRange(lastSlash + 1, lastSlash + lastDot + 1);
|
86
|
+
}
|
87
|
+
|
88
|
+
return [floatingWindow, closeWindow];
|
89
|
+
}
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import Connector from './api.js';
|
2
2
|
import { permMap } from './api.js';
|
3
|
-
import {
|
3
|
+
import { showFloatingWindowLineInput } from './popup.js';
|
4
|
+
import { formatSize, decodePathURI, ensurePathURI, copyToClipboard, getRandomString, cvtGMT2Local, debounce, encodePathURI } from './utils.js';
|
4
5
|
|
5
6
|
const conn = new Connector();
|
6
7
|
let userRecord = null;
|
@@ -133,7 +134,8 @@ uploadButton.addEventListener('click', () => {
|
|
133
134
|
});
|
134
135
|
|
135
136
|
uploadFileNameInput.addEventListener('keydown', (e) => {
|
136
|
-
if (e.key === 'Enter'){
|
137
|
+
if (e.key === 'Enter' && !e.isComposing){
|
138
|
+
e.preventDefault();
|
137
139
|
uploadButton.click();
|
138
140
|
}
|
139
141
|
});
|
@@ -343,19 +345,41 @@ function refreshFileList(){
|
|
343
345
|
const actContainer = document.createElement('div');
|
344
346
|
actContainer.classList.add('action-container');
|
345
347
|
|
348
|
+
const viewButton = document.createElement('a');
|
349
|
+
viewButton.textContent = 'View';
|
350
|
+
viewButton.href = conn.config.endpoint + '/' + file.url + '?token=' + conn.config.token;
|
351
|
+
viewButton.target = '_blank';
|
352
|
+
actContainer.appendChild(viewButton);
|
353
|
+
|
346
354
|
const copyButton = document.createElement('a');
|
347
|
-
copyButton.textContent = '
|
355
|
+
copyButton.textContent = 'Share';
|
348
356
|
copyButton.href = '#';
|
349
357
|
copyButton.addEventListener('click', () => {
|
350
358
|
copyToClipboard(conn.config.endpoint + '/' + file.url);
|
351
359
|
});
|
352
360
|
actContainer.appendChild(copyButton);
|
353
361
|
|
354
|
-
const
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
362
|
+
const moveButton = document.createElement('a');
|
363
|
+
moveButton.textContent = 'Move';
|
364
|
+
moveButton.href = '#';
|
365
|
+
moveButton.addEventListener('click', () => {
|
366
|
+
showFloatingWindowLineInput((dstPath) => {
|
367
|
+
dstPath = encodePathURI(dstPath);
|
368
|
+
if (dstPath.endsWith('/')){
|
369
|
+
dstPath = dstPath.slice(0, -1);
|
370
|
+
}
|
371
|
+
conn.moveFile(file.url, dstPath)
|
372
|
+
.then(() => {
|
373
|
+
refreshFileList();
|
374
|
+
});
|
375
|
+
}, {
|
376
|
+
text: 'Enter the destination path: ',
|
377
|
+
placeholder: 'Destination path',
|
378
|
+
value: decodePathURI(file.url),
|
379
|
+
select: "last-filename"
|
380
|
+
});
|
381
|
+
});
|
382
|
+
actContainer.appendChild(moveButton);
|
359
383
|
|
360
384
|
const downloadBtn = document.createElement('a');
|
361
385
|
downloadBtn.textContent = 'Download';
|
@@ -5,6 +5,7 @@ from abc import ABC, abstractmethod
|
|
5
5
|
import urllib.parse
|
6
6
|
import dataclasses, hashlib, uuid
|
7
7
|
from contextlib import asynccontextmanager
|
8
|
+
from functools import wraps
|
8
9
|
from enum import IntEnum
|
9
10
|
import zipfile, io
|
10
11
|
|
@@ -21,6 +22,18 @@ _g_conn: Optional[aiosqlite.Connection] = None
|
|
21
22
|
def hash_credential(username, password):
|
22
23
|
return hashlib.sha256((username + password).encode()).hexdigest()
|
23
24
|
|
25
|
+
_atomic_lock = Lock()
|
26
|
+
def atomic(func):
|
27
|
+
"""
|
28
|
+
Ensure non-reentrancy.
|
29
|
+
Can be skipped if the function only executes a single SQL statement.
|
30
|
+
"""
|
31
|
+
@wraps(func)
|
32
|
+
async def wrapper(*args, **kwargs):
|
33
|
+
async with _atomic_lock:
|
34
|
+
return await func(*args, **kwargs)
|
35
|
+
return wrapper
|
36
|
+
|
24
37
|
class DBConnBase(ABC):
|
25
38
|
logger = get_logger('database', global_instance=True)
|
26
39
|
|
@@ -113,6 +126,7 @@ class UserConn(DBConnBase):
|
|
113
126
|
if res is None: return None
|
114
127
|
return self.parse_record(res)
|
115
128
|
|
129
|
+
@atomic
|
116
130
|
async def create_user(
|
117
131
|
self, username: str, password: str, is_admin: bool = False,
|
118
132
|
max_storage: int = 1073741824, permission: FileReadPermission = FileReadPermission.UNSET
|
@@ -128,6 +142,7 @@ class UserConn(DBConnBase):
|
|
128
142
|
assert cursor.lastrowid is not None
|
129
143
|
return cursor.lastrowid
|
130
144
|
|
145
|
+
@atomic
|
131
146
|
async def update_user(
|
132
147
|
self, username: str, password: Optional[str] = None, is_admin: Optional[bool] = None,
|
133
148
|
max_storage: Optional[int] = None, permission: Optional[FileReadPermission] = None
|
@@ -149,7 +164,10 @@ class UserConn(DBConnBase):
|
|
149
164
|
if max_storage is None: max_storage = current_record.max_storage
|
150
165
|
if permission is None: permission = current_record.permission
|
151
166
|
|
152
|
-
await self.conn.execute(
|
167
|
+
await self.conn.execute(
|
168
|
+
"UPDATE user SET credential = ?, is_admin = ?, max_storage = ?, permission = ? WHERE username = ?",
|
169
|
+
(credential, is_admin, max_storage, int(permission), username)
|
170
|
+
)
|
153
171
|
self.logger.info(f"User {username} updated")
|
154
172
|
|
155
173
|
async def all(self):
|
@@ -355,6 +373,7 @@ class FileConn(DBConnBase):
|
|
355
373
|
assert res is not None
|
356
374
|
return res[0] or 0
|
357
375
|
|
376
|
+
@atomic
|
358
377
|
async def set_file_record(
|
359
378
|
self, url: str,
|
360
379
|
owner_id: Optional[int] = None,
|
@@ -362,10 +381,10 @@ class FileConn(DBConnBase):
|
|
362
381
|
file_size: Optional[int] = None,
|
363
382
|
permission: Optional[ FileReadPermission ] = None
|
364
383
|
):
|
365
|
-
self.logger.debug(f"Updating fmeta {url}: user_id={owner_id}, file_id={file_id}")
|
366
384
|
|
367
385
|
old = await self.get_file_record(url)
|
368
386
|
if old is not None:
|
387
|
+
self.logger.debug(f"Updating fmeta {url}: permission={permission}, owner_id={owner_id}")
|
369
388
|
# should delete the old blob if file_id is changed
|
370
389
|
assert file_id is None, "Cannot update file id"
|
371
390
|
assert file_size is None, "Cannot update file size"
|
@@ -374,21 +393,37 @@ class FileConn(DBConnBase):
|
|
374
393
|
if permission is None: permission = old.permission
|
375
394
|
await self.conn.execute(
|
376
395
|
"""
|
377
|
-
UPDATE fmeta SET owner_id = ?,
|
396
|
+
UPDATE fmeta SET owner_id = ?, permission = ?,
|
378
397
|
access_time = CURRENT_TIMESTAMP WHERE url = ?
|
379
|
-
""", (owner_id,
|
398
|
+
""", (owner_id, int(permission), url))
|
380
399
|
self.logger.info(f"File {url} updated")
|
381
400
|
else:
|
401
|
+
self.logger.debug(f"Creating fmeta {url}: permission={permission}, owner_id={owner_id}, file_id={file_id}, file_size={file_size}")
|
382
402
|
if permission is None:
|
383
403
|
permission = FileReadPermission.UNSET
|
384
404
|
assert owner_id is not None and file_id is not None and file_size is not None, "Missing required fields"
|
385
|
-
await self.conn.execute(
|
405
|
+
await self.conn.execute(
|
406
|
+
"INSERT INTO fmeta (url, owner_id, file_id, file_size, permission) VALUES (?, ?, ?, ?, ?)",
|
407
|
+
(url, owner_id, file_id, file_size, int(permission))
|
408
|
+
)
|
386
409
|
await self.user_size_inc(owner_id, file_size)
|
387
410
|
self.logger.info(f"File {url} created")
|
388
411
|
|
412
|
+
@atomic
|
413
|
+
async def move_file(self, old_url: str, new_url: str):
|
414
|
+
old = await self.get_file_record(old_url)
|
415
|
+
if old is None:
|
416
|
+
raise FileNotFoundError(f"File {old_url} not found")
|
417
|
+
new_exists = await self.get_file_record(new_url)
|
418
|
+
if new_exists is not None:
|
419
|
+
raise FileExistsError(f"File {new_url} already exists")
|
420
|
+
async with self.conn.execute("UPDATE fmeta SET url = ? WHERE url = ?", (new_url, old_url)):
|
421
|
+
self.logger.info(f"Moved file {old_url} to {new_url}")
|
422
|
+
|
389
423
|
async def log_access(self, url: str):
|
390
424
|
await self.conn.execute("UPDATE fmeta SET access_time = CURRENT_TIMESTAMP WHERE url = ?", (url, ))
|
391
425
|
|
426
|
+
@atomic
|
392
427
|
async def delete_file_record(self, url: str):
|
393
428
|
file_record = await self.get_file_record(url)
|
394
429
|
if file_record is None: return
|
@@ -396,6 +431,7 @@ class FileConn(DBConnBase):
|
|
396
431
|
await self.user_size_dec(file_record.owner_id, file_record.file_size)
|
397
432
|
self.logger.info(f"Deleted fmeta {url}")
|
398
433
|
|
434
|
+
@atomic
|
399
435
|
async def delete_user_file_records(self, owner_id: int):
|
400
436
|
async with self.conn.execute("SELECT * FROM fmeta WHERE owner_id = ?", (owner_id, )) as cursor:
|
401
437
|
res = await cursor.fetchall()
|
@@ -403,6 +439,7 @@ class FileConn(DBConnBase):
|
|
403
439
|
await self.conn.execute("DELETE FROM usize WHERE user_id = ?", (owner_id, ))
|
404
440
|
self.logger.info(f"Deleted {len(res)} files for user {owner_id}") # type: ignore
|
405
441
|
|
442
|
+
@atomic
|
406
443
|
async def delete_path_records(self, path: str):
|
407
444
|
"""Delete all records with url starting with path"""
|
408
445
|
async with self.conn.execute("SELECT * FROM fmeta WHERE url LIKE ?", (path + '%', )) as cursor:
|
@@ -436,18 +473,20 @@ class FileConn(DBConnBase):
|
|
436
473
|
async def delete_file_blobs(self, file_ids: list[str]):
|
437
474
|
await self.conn.execute("DELETE FROM fdata WHERE file_id IN ({})".format(','.join(['?'] * len(file_ids))), file_ids)
|
438
475
|
|
439
|
-
def
|
476
|
+
def validate_url(url: str, is_file = True):
|
440
477
|
ret = not url.startswith('/') and not ('..' in url) and ('/' in url) and not ('//' in url) \
|
441
478
|
and not ' ' in url and not url.startswith('\\') and not url.startswith('_') and not url.startswith('.')
|
442
479
|
|
443
480
|
if not ret:
|
444
|
-
|
481
|
+
raise InvalidPathError(f"Invalid URL: {url}")
|
445
482
|
|
446
483
|
if is_file:
|
447
484
|
ret = ret and not url.endswith('/')
|
448
485
|
else:
|
449
486
|
ret = ret and url.endswith('/')
|
450
|
-
|
487
|
+
|
488
|
+
if not ret:
|
489
|
+
raise InvalidPathError(f"Invalid URL: {url}")
|
451
490
|
|
452
491
|
async def get_user(db: "Database", user: int | str) -> Optional[DBUserRecord]:
|
453
492
|
if isinstance(user, str):
|
@@ -467,6 +506,7 @@ async def transaction(db: "Database"):
|
|
467
506
|
except Exception as e:
|
468
507
|
db.logger.error(f"Error in transaction: {e}")
|
469
508
|
await db.rollback()
|
509
|
+
raise e
|
470
510
|
finally:
|
471
511
|
_transaction_lock.release()
|
472
512
|
|
@@ -496,8 +536,7 @@ class Database:
|
|
496
536
|
await _g_conn.rollback()
|
497
537
|
|
498
538
|
async def save_file(self, u: int | str, url: str, blob: bytes):
|
499
|
-
|
500
|
-
raise ValueError(f"Invalid URL: {url}")
|
539
|
+
validate_url(url)
|
501
540
|
assert isinstance(blob, bytes), "blob must be bytes"
|
502
541
|
|
503
542
|
user = await get_user(self, u)
|
@@ -529,7 +568,7 @@ class Database:
|
|
529
568
|
|
530
569
|
# async def read_file_stream(self, url: str): ...
|
531
570
|
async def read_file(self, url: str) -> bytes:
|
532
|
-
|
571
|
+
validate_url(url)
|
533
572
|
|
534
573
|
r = await self.file.get_file_record(url)
|
535
574
|
if r is None:
|
@@ -546,7 +585,7 @@ class Database:
|
|
546
585
|
return blob
|
547
586
|
|
548
587
|
async def delete_file(self, url: str) -> Optional[FileDBRecord]:
|
549
|
-
|
588
|
+
validate_url(url)
|
550
589
|
|
551
590
|
async with transaction(self):
|
552
591
|
r = await self.file.get_file_record(url)
|
@@ -556,9 +595,16 @@ class Database:
|
|
556
595
|
await self.file.delete_file_blob(f_id)
|
557
596
|
await self.file.delete_file_record(url)
|
558
597
|
return r
|
598
|
+
|
599
|
+
async def move_file(self, old_url: str, new_url: str):
|
600
|
+
validate_url(old_url)
|
601
|
+
validate_url(new_url)
|
602
|
+
|
603
|
+
async with transaction(self):
|
604
|
+
await self.file.move_file(old_url, new_url)
|
559
605
|
|
560
606
|
async def delete_path(self, url: str):
|
561
|
-
|
607
|
+
validate_url(url, is_file=False)
|
562
608
|
|
563
609
|
async with transaction(self):
|
564
610
|
records = await self.file.get_path_records(url)
|
@@ -37,7 +37,10 @@ def handle_exception(fn):
|
|
37
37
|
logger.error(f"Error in {fn.__name__}: {e}")
|
38
38
|
if isinstance(e, HTTPException): raise e
|
39
39
|
elif isinstance(e, StorageExceededError): raise HTTPException(status_code=413, detail=str(e))
|
40
|
-
elif isinstance(e,
|
40
|
+
elif isinstance(e, PermissionError): raise HTTPException(status_code=403, detail=str(e))
|
41
|
+
elif isinstance(e, InvalidPathError): raise HTTPException(status_code=400, detail=str(e))
|
42
|
+
elif isinstance(e, FileNotFoundError): raise HTTPException(status_code=404, detail=str(e))
|
43
|
+
elif isinstance(e, FileExistsError): raise HTTPException(status_code=409, detail=str(e))
|
41
44
|
else: raise HTTPException(status_code=500, detail=str(e))
|
42
45
|
return wrapper
|
43
46
|
|
@@ -63,7 +66,7 @@ async def get_current_user(
|
|
63
66
|
raise HTTPException(status_code=401, detail="Invalid token")
|
64
67
|
return user
|
65
68
|
|
66
|
-
app = FastAPI(docs_url=
|
69
|
+
app = FastAPI(docs_url="/_docs", redoc_url=None, lifespan=lifespan)
|
67
70
|
app.add_middleware(
|
68
71
|
CORSMiddleware,
|
69
72
|
allow_origins=["*"],
|
@@ -255,6 +258,7 @@ async def get_file_meta(path: str, user: DBUserRecord = Depends(get_current_user
|
|
255
258
|
async def update_file_meta(
|
256
259
|
path: str,
|
257
260
|
perm: Optional[int] = None,
|
261
|
+
new_path: Optional[str] = None,
|
258
262
|
user: DBUserRecord = Depends(get_current_user)
|
259
263
|
):
|
260
264
|
if user.id == 0:
|
@@ -271,10 +275,16 @@ async def update_file_meta(
|
|
271
275
|
|
272
276
|
if perm is not None:
|
273
277
|
logger.info(f"Update permission of {path} to {perm}")
|
274
|
-
await conn.file.set_file_record(
|
278
|
+
await handle_exception(conn.file.set_file_record)(
|
275
279
|
url = file_record.url,
|
276
280
|
permission = FileReadPermission(perm)
|
277
281
|
)
|
282
|
+
|
283
|
+
if new_path is not None:
|
284
|
+
new_path = ensure_uri_compnents(new_path)
|
285
|
+
logger.info(f"Update path of {path} to {new_path}")
|
286
|
+
await handle_exception(conn.move_file)(path, new_path)
|
287
|
+
|
278
288
|
return Response(status_code=200, content="OK")
|
279
289
|
|
280
290
|
@router_api.get("/whoami")
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|