a3462214b9af3ff48d76d29de52b88ae19008bbb
[ndcode_site.git] / _config / site.jst
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')
9
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
18     }
19
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
22     async start() {
23       await super.start()
24
25       assert(this.database === undefined)
26       this.database = await this.resources.ref(
27         'database',
28         async () => {
29           let database = new logjson.Database()
30           await database.open(this.root + '/database.logjson')
31           return database
32         }
33       )
34
35       //assert(this.nodemailer_cache === undefined)
36       //this.nodemailer_cache = await this.resources.ref(
37       //  'nodemailer_cache',
38       //  async () => new NodeMailerCache(true)
39       //)
40
41       assert(this.zettair_cache === undefined)
42       this.zettair_cache = await this.resources.ref(
43         'zettair_cache',
44         async () => new ZettairCache(true)
45       )
46     }
47
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
52     async stop() {
53       await super.stop()
54
55       assert(this.database !== undefined)
56       await this.resources.unref('database')
57
58       //assert(this.nodemailer_cache !== undefined)
59       //await this.resources.unref('nodemailer_cache')
60
61       assert(this.zettair_cache !== undefined)
62       await this.resources.unref('zettair_cache')
63     }
64
65     // called once per second, responsible for cache cleaning and flushing
66     async kick() {
67       await super.kick()
68
69       assert(this.database !== undefined)
70       await this.database.kick()
71
72       let new_database_date = new XDate().toUTCString('yyyyMMdd')
73       if (new_database_date !== this.database_date) {
74         console.log(
75           'rotate database',
76           this.database_date,
77           '->',
78           new_database_date
79         )
80         await this.database.rotate(
81           `${this.root}/database.logjson.${this.database_date}`
82         )
83         this.database_date = new_database_date
84       }
85
86       //assert(this.nodemailer_cache !== undefined)
87       //this.nodemailer_cache.kick()
88
89       assert(this.zettair_cache !== undefined)
90       this.zettair_cache.kick()
91     }
92
93     //// retrieves a particular email account (as a nodemailer transport)
94     //get_nodemailer(pathname) {
95     //  return /*await*/ this.nodemailer_cache.get(this.root + pathname)
96     //}
97
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)
101     }
102
103     // customize template serving to initialize env.now, env.session_key first
104     async serve_jst(env, pathname, ...args) {
105       let template
106       try {
107         template = await this.jst_cache.get(pathname)
108       }
109       catch (err) {
110         if (!(err instanceof Error) || err.code !== 'ENOENT')
111           throw err
112         return false
113       }
114       env.site = this
115
116       // added part
117       env.now = XDate.now()
118       let transaction = await this.database.Transaction()
119       try {
120         let root = await transaction.get({})
121         let sessions = await root.get('sessions', {})
122
123         let cookies = cookie.parse(env.request.headers.cookie || '')
124         let session, expires = new XDate(env.now)
125         if (
126           Object.prototype.hasOwnProperty.call(cookies, 'session_key') &&
127             (
128               session = await sessions.get(
129                 env.session_key = cookies.session_key
130               )
131             ) !== undefined &&
132             env.now < await session.get('expires', 0)
133         )
134           // if session key is already in database, the requester supports
135           // cookies, and each access extends the session expiry by 1 month
136           expires.addMonths(1)
137         else {
138           // first request for session, maybe a bot, retain for only 1 day
139           expires.addDays(1)
140
141           do {
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)
146         }
147
148         await session.set('expires', expires.getTime())
149         env.response.setHeader(
150           'Set-Cookie',
151           `session_key=${env.session_key}; expires=${expires.toUTCString()}; path=/; httponly;`
152         )
153
154         await transaction.commit()
155       }
156       catch (error) {
157         transaction.rollback()
158         throw error
159       }
160
161       await template(env, ...args)
162       return true
163     }
164
165     // customize the file search/serving algorithm for this particular website
166     async respond(env) {
167       if (
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'
172       ) {
173         throw new jst_server.Problem(
174           'Not found',
175           `Banned file \"${env.parsed_url.pathname}\"`,
176           404
177         )
178       }
179       return /*await*/ super.respond(env)
180     }
181   }
182
183   return new CustomSite(resources, root, {}, prev_site)
184 }