1 let assert = require('assert')
2 let cookie = require('cookie')
3 let crypto = require('crypto')
4 let logjson = (await import('@ndcode/logjson')).default
5 //let NodeMailerCache = require('@ndcode/nodemailer_cache')
6 let XDate = require('xdate')
7 let ZettairCache = require('@ndcode/zettair_cache')
9 return async (resources, root, prev_site) => {
10 class CustomSite extends _jst_server.Site {
11 constructor(resources, root, options, prev_site) {
12 super(resources, root, options, prev_site)
13 this.database = undefined
14 this.database_date = new XDate().toUTCString('yyyyMMdd')
15 this.nodemailer_cache = undefined
16 this.zettair_cache = undefined
19 // called when the server starts or the site.jst file is modified
20 // in latter case it will carry over the previously created resource objects
22 await _jst_server.Site.prototype.start.call(this)
24 assert(this.database === undefined)
25 this.database = await this.resources.ref(
28 let database = new logjson.Database()
29 await database.open(this.root + '/database.logjson')
34 //assert(this.nodemailer_cache === undefined)
35 //this.nodemailer_cache = await this.resources.ref(
36 // 'nodemailer_cache',
37 // async () => new NodeMailerCache(true)
40 assert(this.zettair_cache === undefined)
41 this.zettair_cache = await this.resources.ref(
43 async () => new ZettairCache(true)
47 // called when the server starts or the site.jst file is modified
48 // in latter case the start() method of the new CustomSite object is called
49 // first and then the stop() method of the old CustomSite object, so that the
50 // reference counting can keep the resource objects alive during changeover
52 await _jst_server.Site.prototype.stop.call(this)
54 assert(this.database !== undefined)
55 await this.resources.unref('database')
57 //assert(this.nodemailer_cache !== undefined)
58 //await this.resources.unref('nodemailer_cache')
60 assert(this.zettair_cache !== undefined)
61 await this.resources.unref('zettair_cache')
64 // called once per second, responsible for cache cleaning and flushing
66 await _jst_server.Site.prototype.kick.call(this)
68 assert(this.database !== undefined)
69 await this.database.kick()
71 let new_database_date = new XDate().toUTCString('yyyyMMdd')
72 if (new_database_date !== this.database_date) {
79 await this.database.rotate(
80 `${this.root}/database.logjson.${this.database_date}`
82 this.database_date = new_database_date
85 //assert(this.nodemailer_cache !== undefined)
86 //this.nodemailer_cache.kick()
88 assert(this.zettair_cache !== undefined)
89 this.zettair_cache.kick()
92 //// retrieves a particular email account (as a nodemailer transport)
93 //get_nodemailer(pathname) {
94 // return /*await*/ this.nodemailer_cache.get(this.root + pathname)
97 // retrieves a particular search index (node.js wrapper of a zettair object)
98 get_zettair(pathname) {
99 return /*await*/ this.zettair_cache.get(this.root + pathname)
102 // customize template serving to initialize env.now, env.session_key first
103 async serve_jst(env, pathname, ...args) {
106 template = await this.jst_cache.get(pathname)
109 if (!(err instanceof Error) || err.code !== 'ENOENT')
116 env.now = XDate.now()
117 let transaction = await this.database.Transaction()
119 let root = await transaction.get({})
120 let sessions = await root.get('sessions', {})
122 let cookies = cookie.parse(env.request.headers.cookie || '')
123 let session, expires = new XDate(env.now)
125 Object.prototype.hasOwnProperty.call(cookies, 'session_key') &&
127 session = await sessions.get(
128 env.session_key = cookies.session_key
131 env.now < await session.get('expires', 0)
133 // if session key is already in database, the requester supports
134 // cookies, and each access extends the session expiry by 1 month
137 // first request for session, maybe a bot, retain for only 1 day
141 env.session_key = crypto.randomBytes(16).toString('hex')
142 } while (sessions.has(env.session_key))
143 session = transaction.LazyObject()
144 sessions.set(env.session_key, session)
147 await session.set('expires', expires.getTime())
148 env.response.setHeader(
150 `session_key=${env.session_key}; expires=${expires.toUTCString()}; path=/; httponly;`
153 await transaction.commit()
156 transaction.rollback()
160 await template(env, ...args)
164 // customize the file search/serving algorithm for this particular website
167 env.parsed_url.pathname === '/node_modules' ||
168 env.parsed_url.pathname.slice(0, 14) === '/node_modules/' ||
169 env.parsed_url.pathname === '/package.json' ||
170 env.parsed_url.pathname === '/package-lock.json'
172 this.die(env, `banned file ${env.parsed_url.pathname}`)
175 return /*await*/ _jst_server.Site.prototype.respond.call(this, env)
179 return new CustomSite(