better code (split files)
Massimo Melina committed
Jan 1, 2022 at 17:10 UTC
c316b0f9f14695e75f0672d6552a0d473d1666cf
4 files changed
+128
-130
src/apis.ts
+2
-122
@@ -1,15 +1,9 @@
1
import Koa from 'koa'
2
-import { vfs, VfsNode, walkNode } from './vfs'
3
-import { stat } from 'fs/promises'
4
-import _ from 'lodash'
5
-import { getCurrentUsername, updateAccount, verifyLogin } from './perm'
6
-import createSSE from './sse'
7
-import { basename } from 'path'
2
3
type ApiHandler = (params:any, ctx:Koa.Context) => any
10
-type ApiHandlers = Record<string, ApiHandler>
4
+export type ApiHandlers = Record<string, ApiHandler>
5
12
-export function apiMw(apis: ApiHandlers) : Koa.Middleware {
6
+export function apiMiddleware(apis: ApiHandlers) : Koa.Middleware {
7
return async (ctx, next) => {
8
const params = ctx.method === 'POST' ? ctx.request.body : ctx.request.query
9
console.debug('API', ctx.method, ctx.path, { ...params })
@@ -32,117 +26,3 @@ export function apiMw(apis: ApiHandlers) : Koa.Middleware {
26
}
27
}
28
35
-interface DirEntry { n:string, s?:number, m?:Date, c?:Date }
36
-
37
-export const frontEndApis: ApiHandlers = {
38
-
39
- async file_list({ path, offset, limit, search, omit, sse }, ctx) {
40
- let node = await vfs.urlToNode(path || '/', ctx)
41
- if (!node)
42
- return
43
- if (search?.includes('..'))
44
- return ctx.throw(400)
45
- if (node.default)
46
- return { redirect: path }
47
- offset = Number(offset)
48
- limit = Number(limit)
49
- const re = new RegExp(_.escapeRegExp(search),'i')
50
- const match = (s?:string) => !s || !search || re.test(s)
51
- const walker = walkNode(node, ctx, search ? Infinity : 0)
52
- const sseSrv = sse ? createSSE(ctx) : null
53
- const res = produceEntries()
54
- return !sseSrv && { list: await res }
55
-
56
- async function produceEntries() {
57
- const list = []
58
- const h = sseSrv && setInterval(()=> console.log('WALKING'), 500)
59
- for await (const sub of walker) {
60
- if (sseSrv?.stopped || ctx.aborted) break
61
- const filename = basename(sub.name||'')
62
- if (!match(filename))
63
- continue
64
- const entry = await nodeToDirEntry(sub)
65
- if (!entry)
66
- continue
67
- if (offset) {
68
- --offset
69
- continue
70
- }
71
- if (omit) {
72
- if (omit !== 'c')
73
- ctx.throw(400, 'omit')
74
- if (!entry.m)
75
- entry.m = entry.c
76
- delete entry.c
77
- }
78
- if (sseSrv)
79
- sseSrv.send({ entry })
80
- else
81
- list.push(entry)
82
- if (limit && !--limit)
83
- break
84
- }
85
- if (h) clearInterval(h)
86
- sseSrv?.close()
87
- return list
88
- }
89
- },
90
-
91
- async login({ user, password }, ctx) {
92
- if (!user)
93
- return ctx.status = 400
94
- if (!password)
95
- return ctx.status = 400
96
- if (!await verifyLogin(user, password))
97
- return ctx.status = 401
98
- if (ctx.session)
99
- ctx.session.user = user
100
- return true
101
- },
102
-
103
- async logout({}, ctx) {
104
- if (ctx.session)
105
- ctx.session.user = undefined
106
- ctx.status = 200
107
- return true
108
- },
109
-
110
- async refresh_session({}, ctx) {
111
- return { user: ctx.session?.user }
112
- },
113
-
114
- async change_pwd({ newPassword }, ctx) {
115
- await updateAccount(await getCurrentUsername(ctx), account => {
116
- account.password = newPassword
117
- })
118
- return true
119
- }
120
-}
121
-
122
-async function nodeToDirEntry(node: VfsNode): Promise<DirEntry | null> {
123
- try {
124
- let { name, source, default:def } = node
125
- if (source?.includes('//'))
126
- return { n: name || source }
127
- if (source) {
128
- if (!name)
129
- name = basename(source)
130
- if (def)
131
- return { n: name }
132
- const st = await stat(source)
133
- const folder = st.isDirectory()
134
- const { ctime, mtime } = st
135
- return {
136
- n: name + (folder ? '/' : ''),
137
- c: ctime,
138
- m: Math.abs(+mtime-+ctime) < 1000 ? undefined : mtime,
139
- s: folder ? undefined : st.size,
140
- }
141
- }
142
- return name ? { n: name + '/' } : null
143
- }
144
- catch (err:any) {
145
- console.error(String(err))
146
- return null
147
- }
148
-}
src/frontEndApis.ts
new
+120
@@ -0,0 +1,120 @@
1
+import { vfs, VfsNode, walkNode } from './vfs'
2
+import _ from 'lodash'
3
+import createSSE from './sse'
4
+import { basename } from 'path'
5
+import { getCurrentUsername, updateAccount, verifyLogin } from './perm'
6
+import { stat } from 'fs/promises'
7
+import { ApiHandlers } from './apis'
8
+
9
+export const frontEndApis: ApiHandlers = {
10
+
11
+ async file_list({ path, offset, limit, search, omit, sse }, ctx) {
12
+ let node = await vfs.urlToNode(path || '/', ctx)
13
+ if (!node)
14
+ return
15
+ if (search?.includes('..'))
16
+ return ctx.throw(400)
17
+ if (node.default)
18
+ return { redirect: path }
19
+ offset = Number(offset)
20
+ limit = Number(limit)
21
+ const re = new RegExp(_.escapeRegExp(search),'i')
22
+ const match = (s?:string) => !s || !search || re.test(s)
23
+ const walker = walkNode(node, ctx, search ? Infinity : 0)
24
+ const sseSrv = sse ? createSSE(ctx) : null
25
+ const res = produceEntries()
26
+ return !sseSrv && { list: await res }
27
+
28
+ async function produceEntries() {
29
+ const list = []
30
+ for await (const sub of walker) {
31
+ if (sseSrv?.stopped || ctx.aborted) break
32
+ const filename = basename(sub.name||'')
33
+ if (!match(filename))
34
+ continue
35
+ const entry = await nodeToDirEntry(sub)
36
+ if (!entry)
37
+ continue
38
+ if (offset) {
39
+ --offset
40
+ continue
41
+ }
42
+ if (omit) {
43
+ if (omit !== 'c')
44
+ ctx.throw(400, 'omit')
45
+ if (!entry.m)
46
+ entry.m = entry.c
47
+ delete entry.c
48
+ }
49
+ if (sseSrv)
50
+ sseSrv.send({ entry })
51
+ else
52
+ list.push(entry)
53
+ if (limit && !--limit)
54
+ break
55
+ }
56
+ sseSrv?.close()
57
+ return list
58
+ }
59
+ },
60
+
61
+ async login({ user, password }, ctx) {
62
+ if (!user)
63
+ return ctx.status = 400
64
+ if (!password)
65
+ return ctx.status = 400
66
+ if (!await verifyLogin(user, password))
67
+ return ctx.status = 401
68
+ if (ctx.session)
69
+ ctx.session.user = user
70
+ return true
71
+ },
72
+
73
+ async logout({}, ctx) {
74
+ if (ctx.session)
75
+ ctx.session.user = undefined
76
+ ctx.status = 200
77
+ return true
78
+ },
79
+
80
+ async refresh_session({}, ctx) {
81
+ return { user: ctx.session?.user }
82
+ },
83
+
84
+ async change_pwd({ newPassword }, ctx) {
85
+ await updateAccount(await getCurrentUsername(ctx), account => {
86
+ account.password = newPassword
87
+ })
88
+ return true
89
+ }
90
+}
91
+
92
+interface DirEntry { n:string, s?:number, m?:Date, c?:Date }
93
+
94
+async function nodeToDirEntry(node: VfsNode): Promise<DirEntry | null> {
95
+ try {
96
+ let { name, source, default:def } = node
97
+ if (source?.includes('//'))
98
+ return { n: name || source }
99
+ if (source) {
100
+ if (!name)
101
+ name = basename(source)
102
+ if (def)
103
+ return { n: name }
104
+ const st = await stat(source)
105
+ const folder = st.isDirectory()
106
+ const { ctime, mtime } = st
107
+ return {
108
+ n: name + (folder ? '/' : ''),
109
+ c: ctime,
110
+ m: Math.abs(+mtime-+ctime) < 1000 ? undefined : mtime,
111
+ s: folder ? undefined : st.size,
112
+ }
113
+ }
114
+ return name ? { n: name + '/' } : null
115
+ }
116
+ catch (err:any) {
117
+ console.error(String(err))
118
+ return null
119
+ }
120
+}
src/index.ts
+3
-2
@@ -1,7 +1,7 @@
1
import Koa from 'koa'
2
import mount from 'koa-mount'
3
import bodyParser from 'koa-bodyparser'
4
-import { apiMw, frontEndApis } from './apis'
4
+import { apiMiddleware } from './apis'
5
import { serveFrontend } from './serveFrontend'
6
import { API_URI, argv, DEV, FRONTEND_URI } from './const'
7
import { serveFile } from './serveFile'
@@ -13,6 +13,7 @@ import { Server } from 'http'
13
import { subscribe } from './config'
14
import session from 'koa-session'
15
import { zipStreamFromFolder } from './zip'
16
+import { frontEndApis } from './frontEndApis'
17
18
const BUILD_TIMESTAMP = ""
19
@@ -34,7 +35,7 @@ app.use(async (ctx, next) => {
35
// serve apis
36
app.use(mount(API_URI, new Koa()
37
.use(bodyParser())
37
- .use(apiMw(frontEndApis))
38
+ .use(apiMiddleware(frontEndApis))
39
.use(compress({
40
threshold: 2048,
41
gzip: { flush: require('zlib').constants.Z_SYNC_FLUSH },
src/perm.ts
+3
-6
@@ -58,12 +58,9 @@ export async function updateAccount(username: string, changer:Changer) {
58
saveAccountsAsap()
59
}
60
61
-const saveAccountsAsap = _.debounce(saveAccounts)
62
-
63
-function saveAccounts() {
64
- return fs.writeFile(PATH, yaml.stringify({ accounts })).catch(err =>
65
- console.error('Failed at saving accounts file, please ensure it is writable.', String(err)))
66
-}
61
+const saveAccountsAsap = _.debounce(() =>
62
+ fs.writeFile(PATH, yaml.stringify({ accounts })).catch(err =>
63
+ console.error('Failed at saving accounts file, please ensure it is writable.', String(err))))
64
65
let doing = false
66
load().then()