reyserver 1.1.93__py3-none-any.whl
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.
- reyserver/__init__.py +26 -0
- reyserver/rall.py +21 -0
- reyserver/rauth.py +471 -0
- reyserver/rbase.py +74 -0
- reyserver/rbind.py +335 -0
- reyserver/rcache.py +116 -0
- reyserver/rclient.py +267 -0
- reyserver/rfile.py +316 -0
- reyserver/rpublic.py +62 -0
- reyserver/rredirect.py +48 -0
- reyserver/rserver.py +432 -0
- reyserver/rtest.py +80 -0
- reyserver-1.1.93.dist-info/METADATA +33 -0
- reyserver-1.1.93.dist-info/RECORD +16 -0
- reyserver-1.1.93.dist-info/WHEEL +4 -0
- reyserver-1.1.93.dist-info/licenses/LICENSE +7 -0
reyserver/rfile.py
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
@Time : 2025-10-06
|
|
6
|
+
@Author : Rey
|
|
7
|
+
@Contact : reyxbo@163.com
|
|
8
|
+
@Explain : File methods. Can create database used "self.build_db" function.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from fastapi import APIRouter
|
|
13
|
+
from fastapi.responses import FileResponse
|
|
14
|
+
from reydb import rorm, DatabaseEngine, DatabaseEngineAsync
|
|
15
|
+
from reykit.ros import get_md5
|
|
16
|
+
|
|
17
|
+
from .rbase import exit_api
|
|
18
|
+
from .rbind import Bind
|
|
19
|
+
from .rcache import wrap_cache
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
__all__ = (
|
|
23
|
+
'DatabaseORMTableInfo',
|
|
24
|
+
'DatabaseORMTableData',
|
|
25
|
+
'build_db_file',
|
|
26
|
+
'router_file'
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class DatabaseORMTableInfo(rorm.Table):
|
|
31
|
+
"""
|
|
32
|
+
Database "info" table ORM model.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
__name__ = 'info'
|
|
36
|
+
__comment__ = 'File information table.'
|
|
37
|
+
create_time: rorm.Datetime = rorm.Field(field_default=':time', not_null=True, index_n=True, comment='Record create time.')
|
|
38
|
+
file_id: int = rorm.Field(key_auto=True, comment='File ID.')
|
|
39
|
+
md5: str = rorm.Field(rorm.types.CHAR(32), not_null=True, index_n=True, comment='File MD5.')
|
|
40
|
+
name: str = rorm.Field(rorm.types.VARCHAR(260), index_n=True, comment='File name.')
|
|
41
|
+
note: str = rorm.Field(rorm.types.VARCHAR(500), comment='File note.')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class DatabaseORMTableData(rorm.Table):
|
|
45
|
+
"""
|
|
46
|
+
Database "data" table ORM model.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
__name__ = 'data'
|
|
50
|
+
__comment__ = 'File data table.'
|
|
51
|
+
md5: str = rorm.Field(rorm.types.CHAR(32), key=True, comment='File MD5.')
|
|
52
|
+
size: int = rorm.Field(not_null=True, comment='File bytes size.')
|
|
53
|
+
path: str = rorm.Field(rorm.types.VARCHAR(4095), not_null=True, comment='File disk storage relative path.')
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def build_db_file(engine: DatabaseEngine | DatabaseEngineAsync) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Check and build "file" database tables.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
db : Database engine instance.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
# Set parameter.
|
|
66
|
+
|
|
67
|
+
## Table.
|
|
68
|
+
tables = [DatabaseORMTableInfo, DatabaseORMTableData]
|
|
69
|
+
|
|
70
|
+
## View.
|
|
71
|
+
views = [
|
|
72
|
+
{
|
|
73
|
+
'table': 'data_info',
|
|
74
|
+
'select': (
|
|
75
|
+
'SELECT "b"."last_time", "a"."md5", "a"."size", "b"."names", "b"."notes"\n'
|
|
76
|
+
'FROM "data" AS "a"\n'
|
|
77
|
+
'LEFT JOIN (\n'
|
|
78
|
+
' SELECT\n'
|
|
79
|
+
' "md5",\n'
|
|
80
|
+
' STRING_AGG(DISTINCT "name", \' | \') AS "names",\n'
|
|
81
|
+
' STRING_AGG(DISTINCT "note", \' | \') AS "notes",\n'
|
|
82
|
+
' MAX("create_time") as "last_time"\n'
|
|
83
|
+
' FROM (\n'
|
|
84
|
+
' SELECT "create_time", "md5", "name", "note"\n'
|
|
85
|
+
' FROM "info"\n'
|
|
86
|
+
' ORDER BY "create_time" DESC\n'
|
|
87
|
+
' ) AS "INFO"\n'
|
|
88
|
+
' GROUP BY "md5"\n'
|
|
89
|
+
' ORDER BY "last_time" DESC\n'
|
|
90
|
+
') AS "b"\n'
|
|
91
|
+
'ON "a"."md5" = "b"."md5"\n'
|
|
92
|
+
'ORDER BY "last_time" DESC'
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
## View stats.
|
|
98
|
+
views_stats = [
|
|
99
|
+
{
|
|
100
|
+
'table': 'stats',
|
|
101
|
+
'items': [
|
|
102
|
+
{
|
|
103
|
+
'name': 'count',
|
|
104
|
+
'select': (
|
|
105
|
+
'SELECT COUNT(1)\n'
|
|
106
|
+
'FROM "info"'
|
|
107
|
+
),
|
|
108
|
+
'comment': 'File information count.'
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
'name': 'past_day_count',
|
|
112
|
+
'select': (
|
|
113
|
+
'SELECT COUNT(1)\n'
|
|
114
|
+
'FROM "info"\n'
|
|
115
|
+
'WHERE DATE_PART(\'day\', NOW() - "create_time") = 0'
|
|
116
|
+
),
|
|
117
|
+
'comment': 'File information count in the past day.'
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
'name': 'past_week_count',
|
|
121
|
+
'select': (
|
|
122
|
+
'SELECT COUNT(1)\n'
|
|
123
|
+
'FROM "info"\n'
|
|
124
|
+
'WHERE DATE_PART(\'day\', NOW() - "create_time") <= 6'
|
|
125
|
+
),
|
|
126
|
+
'comment': 'File information count in the past week.'
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
'name': 'past_month_count',
|
|
130
|
+
'select': (
|
|
131
|
+
'SELECT COUNT(1)\n'
|
|
132
|
+
'FROM "info"\n'
|
|
133
|
+
'WHERE DATE_PART(\'day\', NOW() - "create_time") <= 29'
|
|
134
|
+
),
|
|
135
|
+
'comment': 'File information count in the past month.'
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
'name': 'data_count',
|
|
139
|
+
'select': (
|
|
140
|
+
'SELECT COUNT(1)\n'
|
|
141
|
+
'FROM "data"'
|
|
142
|
+
),
|
|
143
|
+
'comment': 'File data unique count.'
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
'name': 'total_size',
|
|
147
|
+
'select': (
|
|
148
|
+
'SELECT TO_CHAR(SUM("size"), \'FM999,999,999,999,999\')\n'
|
|
149
|
+
'FROM "data"'
|
|
150
|
+
),
|
|
151
|
+
'comment': 'File total byte size.'
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
'name': 'avg_size',
|
|
155
|
+
'select': (
|
|
156
|
+
'SELECT TO_CHAR(ROUND(AVG("size")), \'FM999,999,999,999,999\')\n'
|
|
157
|
+
'FROM "data"'
|
|
158
|
+
),
|
|
159
|
+
'comment': 'File average byte size.'
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
'name': 'max_size',
|
|
163
|
+
'select': (
|
|
164
|
+
'SELECT TO_CHAR(MAX("size"), \'FM999,999,999,999,999\')\n'
|
|
165
|
+
'FROM "data"'
|
|
166
|
+
),
|
|
167
|
+
'comment': 'File maximum byte size.'
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
'name': 'last_time',
|
|
171
|
+
'select': (
|
|
172
|
+
'SELECT MAX("create_time")\n'
|
|
173
|
+
'FROM "info"'
|
|
174
|
+
),
|
|
175
|
+
'comment': 'File last record create time.'
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
# Build.
|
|
182
|
+
engine.sync_engine.build.build(tables=tables, views=views, views_stats=views_stats, skip=True)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
router_file = APIRouter()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@router_file.get('/files/{file_id}')
|
|
189
|
+
@wrap_cache
|
|
190
|
+
async def get_file_info(
|
|
191
|
+
file_id: int = Bind.i.path,
|
|
192
|
+
sess: Bind.Sess = Bind.sess.file
|
|
193
|
+
) -> DatabaseORMTableInfo:
|
|
194
|
+
"""
|
|
195
|
+
Get file information.
|
|
196
|
+
|
|
197
|
+
Parameters
|
|
198
|
+
----------
|
|
199
|
+
file_id : File ID.
|
|
200
|
+
|
|
201
|
+
Returns
|
|
202
|
+
-------
|
|
203
|
+
File information.
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
# Get.
|
|
207
|
+
table_info = await sess.get(DatabaseORMTableInfo, file_id)
|
|
208
|
+
|
|
209
|
+
# Check.
|
|
210
|
+
if table_info is None:
|
|
211
|
+
exit_api(404)
|
|
212
|
+
|
|
213
|
+
return table_info
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@router_file.post('/files')
|
|
217
|
+
async def upload_file(
|
|
218
|
+
file: Bind.File = Bind.i.forms,
|
|
219
|
+
name: str = Bind.i.forms_n,
|
|
220
|
+
note: str = Bind.i.forms_n,
|
|
221
|
+
sess: Bind.Sess = Bind.sess.file,
|
|
222
|
+
server: Bind.Server = Bind.server
|
|
223
|
+
) -> DatabaseORMTableInfo:
|
|
224
|
+
"""
|
|
225
|
+
Upload file.
|
|
226
|
+
|
|
227
|
+
Parameters
|
|
228
|
+
----------
|
|
229
|
+
file : File instance.
|
|
230
|
+
name : File name.
|
|
231
|
+
note : File note.
|
|
232
|
+
|
|
233
|
+
Returns
|
|
234
|
+
-------
|
|
235
|
+
File information.
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
# Parameter.
|
|
239
|
+
file_store = server.api_file_store
|
|
240
|
+
file_bytes = await file.read()
|
|
241
|
+
file_md5 = get_md5(file_bytes)
|
|
242
|
+
file_size = len(file_bytes)
|
|
243
|
+
|
|
244
|
+
# Upload.
|
|
245
|
+
file_path = file_store.index(file_md5)
|
|
246
|
+
|
|
247
|
+
## Data.
|
|
248
|
+
if file_path is None:
|
|
249
|
+
file_path = file_store.store(file_bytes)
|
|
250
|
+
file_relpath = file_store.get_relpath(file_path)
|
|
251
|
+
table_data = DatabaseORMTableData(
|
|
252
|
+
md5=file_md5,
|
|
253
|
+
size=file_size,
|
|
254
|
+
path=file_relpath
|
|
255
|
+
)
|
|
256
|
+
await sess.add(table_data)
|
|
257
|
+
|
|
258
|
+
## Information.
|
|
259
|
+
table_info = DatabaseORMTableInfo(
|
|
260
|
+
md5=file_md5,
|
|
261
|
+
name=name,
|
|
262
|
+
note=note
|
|
263
|
+
)
|
|
264
|
+
await sess.add(table_info)
|
|
265
|
+
|
|
266
|
+
# Get ID.
|
|
267
|
+
await sess.flush()
|
|
268
|
+
|
|
269
|
+
return table_info
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@router_file.get('/files/{file_id}/download')
|
|
273
|
+
async def download_file(
|
|
274
|
+
file_id: int = Bind.i.path,
|
|
275
|
+
conn: Bind.Conn = Bind.conn.file,
|
|
276
|
+
server: Bind.Server = Bind.server
|
|
277
|
+
) -> FileResponse:
|
|
278
|
+
"""
|
|
279
|
+
Download file.
|
|
280
|
+
|
|
281
|
+
Parameters
|
|
282
|
+
----------
|
|
283
|
+
file_id : File ID.
|
|
284
|
+
|
|
285
|
+
Returns
|
|
286
|
+
-------
|
|
287
|
+
File data.
|
|
288
|
+
"""
|
|
289
|
+
|
|
290
|
+
# Parameter.
|
|
291
|
+
file_store = server.api_file_store
|
|
292
|
+
|
|
293
|
+
# Search.
|
|
294
|
+
sql = (
|
|
295
|
+
'SELECT "name", (\n'
|
|
296
|
+
' SELECT "path"\n'
|
|
297
|
+
' FROM "data"\n'
|
|
298
|
+
' WHERE "md5" = "info"."md5"\n'
|
|
299
|
+
' LIMIT 1\n'
|
|
300
|
+
') AS "path"\n'
|
|
301
|
+
'FROM "info"\n'
|
|
302
|
+
'WHERE "file_id" = :file_id\n'
|
|
303
|
+
'LIMIT 1'
|
|
304
|
+
)
|
|
305
|
+
result = await conn.execute(sql, file_id=file_id)
|
|
306
|
+
|
|
307
|
+
# Check.
|
|
308
|
+
if result.empty:
|
|
309
|
+
exit_api(404)
|
|
310
|
+
|
|
311
|
+
# Response.
|
|
312
|
+
file_name, file_relpath = result.first()
|
|
313
|
+
file_abspath = file_store.get_abspath(file_relpath)
|
|
314
|
+
response = FileResponse(file_abspath, filename=file_name)
|
|
315
|
+
|
|
316
|
+
return response
|
reyserver/rpublic.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
@Time : 2025-10-21
|
|
6
|
+
@Author : Rey
|
|
7
|
+
@Contact : reyxbo@163.com
|
|
8
|
+
@Explain : Public methods.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from fastapi import APIRouter
|
|
13
|
+
from fastapi.responses import HTMLResponse, FileResponse
|
|
14
|
+
from reykit.ros import File, Folder
|
|
15
|
+
|
|
16
|
+
from .rbind import Bind
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = (
|
|
20
|
+
'router_public',
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
router_public = APIRouter()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@router_public.get('/')
|
|
28
|
+
def home(server: Bind.Server = Bind.server) -> HTMLResponse:
|
|
29
|
+
"""
|
|
30
|
+
Home page.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
Home page HTML content.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Parameter.
|
|
38
|
+
public_dir = server.api_public_dir
|
|
39
|
+
file_path = Folder(public_dir) + 'index.html'
|
|
40
|
+
file = File(file_path)
|
|
41
|
+
|
|
42
|
+
# Response.
|
|
43
|
+
response = HTMLResponse(file.str)
|
|
44
|
+
|
|
45
|
+
return response
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@router_public.get('/public/{path:path}')
|
|
49
|
+
def download_public_file(path: str = Bind.i.path) -> FileResponse:
|
|
50
|
+
"""
|
|
51
|
+
Download public file.
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
path : Relative path of based on public directory.
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
File.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
...
|
reyserver/rredirect.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# !/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
@Time : 2025-10-21
|
|
6
|
+
@Author : Rey
|
|
7
|
+
@Contact : reyxbo@163.com
|
|
8
|
+
@Explain : Redirect methods.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
from fastapi import APIRouter
|
|
13
|
+
from fastapi.responses import RedirectResponse
|
|
14
|
+
from reykit.rnet import join_url
|
|
15
|
+
|
|
16
|
+
from .rbind import Bind
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
__all__ = (
|
|
20
|
+
'router_redirect',
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
router_redirect = APIRouter()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@router_redirect.get('/{path:path}')
|
|
28
|
+
def redirect_all(
|
|
29
|
+
path: str = Bind.i.path,
|
|
30
|
+
server: Bind.Server = Bind.server
|
|
31
|
+
) -> RedirectResponse:
|
|
32
|
+
"""
|
|
33
|
+
Redirect all requests to the target server.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
path : Resource path.
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
Redirect response.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
# Response.
|
|
45
|
+
url = join_url(server.api_redirect_server_url, path)
|
|
46
|
+
response = RedirectResponse(url, 308)
|
|
47
|
+
|
|
48
|
+
return response
|