1 let assert = require('assert')
2 let cookie = require('cookie')
3 let crypto = require('crypto')
4 let jst_server = (await import('@ndcode/jst_server')).default
5 let logjson = (await import('@ndcode/logjson')).default
6 //let NodeMailerCache = require('@ndcode/nodemailer_cache')
7 let XDate = require('xdate')
8 let ZettairCache = require('@ndcode/zettair_cache')
10 return async (resources, root, prev_site) => {
11 class CustomSite extends jst_server.Site {
12 constructor(resources, root, options, prev_site) {
13 super(resources, root, options, prev_site)
14 this.database = undefined
15 this.database_date = new XDate().toUTCString('yyyyMMdd')
16 this.nodemailer_cache = undefined
17 this.zettair_cache = undefined
20 // called when the server starts or the site.jst file is modified
21 // in latter case it will carry over the previously created resource objects
25 assert(this.database === undefined)
26 this.database = await this.resources.ref(
27 `database:${this.root}`,
29 let database = new logjson.Database()
30 await database.open(this.root + '/database.logjson')
35 //assert(this.nodemailer_cache === undefined)
36 //this.nodemailer_cache = await this.resources.ref(
37 // 'nodemailer_cache',
38 // async () => new NodeMailerCache(true)
41 assert(this.zettair_cache === undefined)
42 this.zettair_cache = await this.resources.ref(
44 async () => new ZettairCache(true)
48 // called when the server starts or the site.jst file is modified
49 // in latter case the start() method of the new CustomSite object is called
50 // first and then the stop() method of the old CustomSite object, so that the
51 // reference counting can keep the resource objects alive during changeover
55 assert(this.database !== undefined)
56 await this.resources.unref('database')
58 //assert(this.nodemailer_cache !== undefined)
59 //await this.resources.unref('nodemailer_cache')
61 assert(this.zettair_cache !== undefined)
62 await this.resources.unref('zettair_cache')
65 // called once per second, responsible for cache cleaning and flushing
69 assert(this.database !== undefined)
70 await this.database.kick()
72 let new_database_date = new XDate().toUTCString('yyyyMMdd')
73 if (new_database_date !== this.database_date) {
80 await this.database.rotate(
81 `${this.root}/database.logjson.${this.database_date}`
83 this.database_date = new_database_date
86 //assert(this.nodemailer_cache !== undefined)
87 //this.nodemailer_cache.kick()
89 assert(this.zettair_cache !== undefined)
90 this.zettair_cache.kick()
93 //// retrieves a particular email account (as a nodemailer transport)
94 //get_nodemailer(pathname) {
95 // return /*await*/ this.nodemailer_cache.get(this.root + pathname)
98 // retrieves a particular search index (node.js wrapper of a zettair object)
99 get_zettair(pathname) {
100 return /*await*/ this.zettair_cache.get(this.root + pathname)
103 // customize template serving to initialize env.now, env.session_key first
104 async serve_jst(env, pathname, ...args) {
107 template = await this.jst_cache.get(pathname)
110 if (!(err instanceof Error) || err.code !== 'ENOENT')
117 env.now = XDate.now()
118 let transaction = await this.database.Transaction()
120 let root = await transaction.get({})
121 let sessions = await root.get('sessions', {})
123 let cookies = cookie.parse(env.request.headers.cookie || '')
124 let session, expires = new XDate(env.now)
126 Object.prototype.hasOwnProperty.call(cookies, 'session_key') &&
128 session = await sessions.get(
129 env.session_key = cookies.session_key
132 env.now < await session.get('expires', 0)
134 // if session key is already in database, the requester supports
135 // cookies, and each access extends the session expiry by 1 month
138 // first request for session, maybe a bot, retain for only 1 day
142 env.session_key = crypto.randomBytes(16).toString('hex')
143 } while (sessions.has(env.session_key))
144 session = transaction.LazyObject()
145 sessions.set(env.session_key, session)
148 await session.set('expires', expires.getTime())
149 env.response.setHeader(
151 `session_key=${env.session_key}; expires=${expires.toUTCString()}; path=/; httponly;`
154 await transaction.commit()
157 transaction.rollback()
161 await template(env, ...args)
165 // customize the file search/serving algorithm for this particular website
168 env.parsed_url.pathname === '/node_modules' ||
169 env.parsed_url.pathname.slice(0, 14) === '/node_modules/' ||
170 env.parsed_url.pathname === '/package.json' ||
171 env.parsed_url.pathname === '/package-lock.json'
173 throw new jst_server.Problem(
175 `Banned file \"${env.parsed_url.pathname}\"`,
179 return /*await*/ super.respond(env)
183 return new CustomSite(resources, root, {}, prev_site)