tkserver 1.7.8 → 1.7.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,6 +19,7 @@ tkserver
19
19
  | `MONGODB_URI` | MongoDB 数据库连接字符串,不传则使用 lokijs | `null` |
20
20
  | `MONGO_URL` | MongoDB 数据库连接字符串,不传则使用 lokijs | `null` |
21
21
  | `TWIKOO_DATA` | lokijs 数据库存储路径 | `./data` |
22
+ | `TWIKOO_HOST` | 自定义监听的主机名或IP地址(例如 0.0.0.0 或 127.0.0.1),设置该值则会忽略 TWIKOO_LOCALHOST_ONLY,默认值为 null 但实际行为会回退到 `::` | `null` |
22
23
  | `TWIKOO_PORT` | 端口号 | `8080` |
23
24
  | `TWIKOO_THROTTLE` | IP 请求限流,当同一 IP 短时间内请求次数超过阈值将对该 IP 返回错误 | `250` |
24
25
  | `TWIKOO_LOCALHOST_ONLY` | 为`true`时只监听本地请求,使得 nginx 等服务器反代之后不暴露原始端口 | `null` |
package/index.js CHANGED
@@ -39,7 +39,8 @@ const {
39
39
  checkCapCaptcha,
40
40
  getConfig,
41
41
  getConfigForAdmin,
42
- validate
42
+ validate,
43
+ checkCommentOwnership
43
44
  } = require('twikoo-func/utils')
44
45
  const {
45
46
  jsonParse,
@@ -68,6 +69,7 @@ const TWIKOO_REQ_TIMES_CLEAR_TIME = parseInt(process.env.TWIKOO_REQ_TIMES_CLEAR_
68
69
  let db = null
69
70
  let config
70
71
  let requestTimes = {}
72
+ let requestTimesTimer = null
71
73
 
72
74
  connectToDatabase()
73
75
 
@@ -103,6 +105,9 @@ module.exports = async (request, response) => {
103
105
  case 'COMMENT_DELETE_FOR_ADMIN':
104
106
  res = await commentDeleteForAdmin(event)
105
107
  break
108
+ case 'COMMENT_DELETE_FOR_USER':
109
+ res = await commentDeleteForUser(event)
110
+ break
106
111
  case 'COMMENT_IMPORT_FOR_ADMIN':
107
112
  res = await commentImportForAdmin(event)
108
113
  break
@@ -474,6 +479,24 @@ async function commentDeleteForAdmin (event) {
474
479
  return res
475
480
  }
476
481
 
482
+ // 用户删除自己的评论
483
+ async function commentDeleteForUser (event) {
484
+ const res = {}
485
+ try {
486
+ const uid = event.accessToken
487
+ await checkCommentOwnership(event.id, uid, (id) => {
488
+ return db.getCollection('comment').findOne({ _id: id })
489
+ })
490
+ db.getCollection('comment').findAndRemove({ _id: event.id })
491
+ res.code = RES_CODE.SUCCESS
492
+ res.deleted = 1
493
+ } catch (e) {
494
+ res.code = RES_CODE.FAIL
495
+ res.message = e.message
496
+ }
497
+ return res
498
+ }
499
+
477
500
  // 管理员导入评论
478
501
  async function commentImportForAdmin (event) {
479
502
  const res = {}
@@ -1043,8 +1066,36 @@ function getIp (request) {
1043
1066
  return getUserIP(request)
1044
1067
  }
1045
1068
 
1069
+ async function closeDatabase () {
1070
+ if (!db) return
1071
+ try {
1072
+ await new Promise((resolve, reject) => {
1073
+ db.saveDatabase((err) => {
1074
+ if (err) {
1075
+ reject(err)
1076
+ } else {
1077
+ resolve()
1078
+ }
1079
+ })
1080
+ })
1081
+ } finally {
1082
+ db.close()
1083
+ db = null
1084
+ }
1085
+ }
1086
+
1087
+ async function shutdown () {
1088
+ if (requestTimesTimer) {
1089
+ clearInterval(requestTimesTimer)
1090
+ requestTimesTimer = null
1091
+ }
1092
+ await closeDatabase()
1093
+ }
1094
+
1046
1095
  function clearRequestTimes () {
1047
1096
  requestTimes = {}
1048
1097
  }
1049
1098
 
1050
- setInterval(clearRequestTimes, TWIKOO_REQ_TIMES_CLEAR_TIME)
1099
+ requestTimesTimer = setInterval(clearRequestTimes, TWIKOO_REQ_TIMES_CLEAR_TIME)
1100
+
1101
+ module.exports.shutdown = shutdown
package/mongo.js CHANGED
@@ -36,7 +36,8 @@ const {
36
36
  checkCapCaptcha,
37
37
  getConfig,
38
38
  getConfigForAdmin,
39
- validate
39
+ validate,
40
+ checkCommentOwnership
40
41
  } = require('twikoo-func/utils')
41
42
  const {
42
43
  jsonParse,
@@ -65,6 +66,8 @@ const TWIKOO_REQ_TIMES_CLEAR_TIME = parseInt(process.env.TWIKOO_REQ_TIMES_CLEAR_
65
66
  let db = null
66
67
  let config
67
68
  let requestTimes = {}
69
+ let client = null
70
+ let requestTimesTimer = null
68
71
 
69
72
  module.exports = async (request, response) => {
70
73
  let accessToken
@@ -99,6 +102,9 @@ module.exports = async (request, response) => {
99
102
  case 'COMMENT_DELETE_FOR_ADMIN':
100
103
  res = await commentDeleteForAdmin(event)
101
104
  break
105
+ case 'COMMENT_DELETE_FOR_USER':
106
+ res = await commentDeleteForUser(event)
107
+ break
102
108
  case 'COMMENT_IMPORT_FOR_ADMIN':
103
109
  res = await commentImportForAdmin(event)
104
110
  break
@@ -221,7 +227,7 @@ async function connectToDatabase (uri) {
221
227
  if (!uri) throw new Error('未设置环境变量 MONGODB_URI | MONGO_URL')
222
228
  // If no connection is cached, create a new one
223
229
  logger.info('Connecting to database...')
224
- const client = await MongoClient.connect(uri, {})
230
+ client = await MongoClient.connect(uri, {})
225
231
  // Select the database through the connection,
226
232
  // using the database path of the connection string
227
233
  const dbName = (new URL(uri)).pathname.substring(1) || 'twikoo'
@@ -460,6 +466,24 @@ async function commentDeleteForAdmin (event) {
460
466
  return res
461
467
  }
462
468
 
469
+ // 用户删除自己的评论
470
+ async function commentDeleteForUser (event) {
471
+ const res = {}
472
+ try {
473
+ const uid = event.accessToken
474
+ await checkCommentOwnership(event.id, uid, async (id) => {
475
+ return db.collection('comment').findOne({ _id: id })
476
+ })
477
+ const data = await db.collection('comment').deleteOne({ _id: event.id })
478
+ res.code = RES_CODE.SUCCESS
479
+ res.deleted = data.deletedCount
480
+ } catch (e) {
481
+ res.code = RES_CODE.FAIL
482
+ res.message = e.message
483
+ }
484
+ return res
485
+ }
486
+
463
487
  // 管理员导入评论
464
488
  async function commentImportForAdmin (event) {
465
489
  const res = {}
@@ -1015,8 +1039,22 @@ function getIp (request) {
1015
1039
  return getUserIP(request)
1016
1040
  }
1017
1041
 
1042
+ async function shutdown () {
1043
+ if (requestTimesTimer) {
1044
+ clearInterval(requestTimesTimer)
1045
+ requestTimesTimer = null
1046
+ }
1047
+ if (client) {
1048
+ await client.close()
1049
+ client = null
1050
+ db = null
1051
+ }
1052
+ }
1053
+
1018
1054
  function clearRequestTimes () {
1019
1055
  requestTimes = {}
1020
1056
  }
1021
1057
 
1022
- setInterval(clearRequestTimes, TWIKOO_REQ_TIMES_CLEAR_TIME)
1058
+ requestTimesTimer = setInterval(clearRequestTimes, TWIKOO_REQ_TIMES_CLEAR_TIME)
1059
+
1060
+ module.exports.shutdown = shutdown
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tkserver",
3
- "version": "1.7.8",
3
+ "version": "1.7.10",
4
4
  "description": "A simple comment system.",
5
5
  "keywords": [
6
6
  "twikoo",
@@ -31,7 +31,7 @@
31
31
  "get-user-ip": "^1.0.1",
32
32
  "lokijs": "^1.5.12",
33
33
  "mongodb": "^6.3.0",
34
- "twikoo-func": "1.7.8",
34
+ "twikoo-func": "1.7.10",
35
35
  "uuid": "^8.3.2"
36
36
  }
37
37
  }
package/server.js CHANGED
@@ -6,8 +6,20 @@ const logger = require('twikoo-func/utils/logger')
6
6
  const dbUrl = process.env.MONGODB_URI || process.env.MONGO_URL || null
7
7
  const twikoo = dbUrl ? require('./mongo') : require('./index')
8
8
  const server = http.createServer()
9
+ const sockets = new Set()
10
+ const TWIKOO_SHUTDOWN_TIMEOUT = parseInt(process.env.TWIKOO_SHUTDOWN_TIMEOUT) || 5000
11
+ let isShuttingDown = false
12
+ let shuttingDownPromise = null
9
13
 
10
14
  server.on('request', async function (request, response) {
15
+ if (isShuttingDown) {
16
+ response.writeHead(503, {
17
+ Connection: 'close',
18
+ 'Content-Type': 'application/json'
19
+ })
20
+ response.end(JSON.stringify({ code: 503, message: 'Twikoo server is shutting down' }))
21
+ return
22
+ }
11
23
  try {
12
24
  const buffers = []
13
25
  for await (const chunk of request) {
@@ -31,10 +43,83 @@ server.on('request', async function (request, response) {
31
43
  return await twikoo(request, response)
32
44
  })
33
45
 
46
+ server.on('connection', (socket) => {
47
+ sockets.add(socket)
48
+ socket.on('close', () => sockets.delete(socket))
49
+ })
50
+
34
51
  const port = parseInt(process.env.TWIKOO_PORT) || 8080
35
- const host = process.env.TWIKOO_LOCALHOST_ONLY === 'true' ? 'localhost' : '::'
52
+ const host = process.env.TWIKOO_HOST || (process.env.TWIKOO_LOCALHOST_ONLY === 'true' ? 'localhost' : '::')
36
53
 
37
54
  server.listen(port, host, function () {
38
55
  logger.info(`Twikoo is using ${dbUrl ? 'mongo' : 'loki'} database`)
39
56
  logger.info(`Twikoo function started on host ${host} port ${port}`)
40
57
  })
58
+
59
+ function closeServer () {
60
+ return new Promise((resolve, reject) => {
61
+ server.close((err) => {
62
+ if (err) {
63
+ reject(err)
64
+ } else {
65
+ resolve()
66
+ }
67
+ })
68
+ })
69
+ }
70
+
71
+ function destroySockets () {
72
+ for (const socket of sockets) {
73
+ socket.destroy()
74
+ }
75
+ }
76
+
77
+ async function shutdownWithTimeout () {
78
+ let timeoutId
79
+ const closeTask = closeServer()
80
+ const timeoutTask = new Promise((resolve) => {
81
+ timeoutId = setTimeout(() => {
82
+ logger.warn(`Twikoo server shutdown timed out after ${TWIKOO_SHUTDOWN_TIMEOUT}ms, destroying open connections`)
83
+ destroySockets()
84
+ resolve()
85
+ }, TWIKOO_SHUTDOWN_TIMEOUT)
86
+ })
87
+ await Promise.race([closeTask, timeoutTask])
88
+ clearTimeout(timeoutId)
89
+ await closeTask
90
+ }
91
+
92
+ async function runShutdown (signal) {
93
+ isShuttingDown = true
94
+ logger.info(`Received ${signal}, shutting down Twikoo server...`)
95
+ try {
96
+ await shutdownWithTimeout()
97
+ } catch (e) {
98
+ logger.error('Twikoo HTTP server shutdown failed:', e)
99
+ } finally {
100
+ if (typeof twikoo.shutdown === 'function') {
101
+ await twikoo.shutdown()
102
+ }
103
+ }
104
+ logger.info('Twikoo server stopped')
105
+ }
106
+
107
+ async function shutdown (signal) {
108
+ if (!shuttingDownPromise) {
109
+ shuttingDownPromise = runShutdown(signal)
110
+ }
111
+ return await shuttingDownPromise
112
+ }
113
+
114
+ async function handleSignal (signal) {
115
+ try {
116
+ await shutdown(signal)
117
+ process.exitCode = 0
118
+ } catch (e) {
119
+ logger.error('Twikoo server shutdown failed:', e)
120
+ process.exitCode = 1
121
+ }
122
+ }
123
+
124
+ process.on('SIGTERM', () => handleSignal('SIGTERM'))
125
+ process.on('SIGINT', () => handleSignal('SIGINT'))