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()