110 lines
2.8 KiB
JavaScript
110 lines
2.8 KiB
JavaScript
const fs = require('fs')
|
|||
|
|
const path = require('path')
|
||
|
|
const passwords = require('./passwords')
|
||
|
|
|
||
|
|
const file = process.env.DB_FILE
|
||
|
|
? path.resolve(process.env.DB_FILE)
|
||
|
|
: path.join(__dirname, '../data/db.json')
|
||
|
|
|
||
|
|
const SEED = {
|
||
|
|
users: [],
|
||
|
|
admins: [{ username: 'admin', password: '123456', token: '' }],
|
||
|
|
adminAllowlist: [],
|
||
|
|
activities: [
|
||
|
|
{
|
||
|
|
id: '93',
|
||
|
|
title: '2026年卓米第93期卓米会',
|
||
|
|
brandName: '卓米会 ZHUOMI',
|
||
|
|
brandDesc: '专注于品牌增长与企业服务的共创平台,连接资源、方法与同行者。',
|
||
|
|
cover: '',
|
||
|
|
video: '',
|
||
|
|
startAt: '2026-06-18 14:00',
|
||
|
|
address: '上海市浦东新区世纪大道 100 号 卓米中心 3F',
|
||
|
|
latitude: 31.235929,
|
||
|
|
longitude: 121.506321,
|
||
|
|
status: 'current',
|
||
|
|
isCurrent: true,
|
||
|
|
pinned: false,
|
||
|
|
pinnedAt: '',
|
||
|
|
theme: 0,
|
||
|
|
createdAt: '2026-06-01T00:00:00.000Z',
|
||
|
|
updatedAt: '2026-06-01T00:00:00.000Z'
|
||
|
|
}
|
||
|
|
],
|
||
|
|
posters: [],
|
||
|
|
signups: [],
|
||
|
|
checkinCodes: []
|
||
|
|
}
|
||
|
|
|
||
|
|
const COLLECTIONS = ['users', 'admins', 'adminAllowlist', 'activities', 'posters', 'signups', 'checkinCodes']
|
||
|
|
|
||
|
|
let cache = null
|
||
|
|
|
||
|
|
// Returns true when the stored shape changed and must be persisted.
|
||
|
|
function normalize(data) {
|
||
|
|
let dirty = false
|
||
|
|
COLLECTIONS.forEach((key) => {
|
||
|
|
if (!Array.isArray(data[key])) {
|
||
|
|
data[key] = []
|
||
|
|
dirty = true
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
// The allowlist is the only source of truth for mini-program admins.
|
||
|
|
data.users.forEach((user) => {
|
||
|
|
// Client temp paths (http://tmp/..., wxfile://...) were saved by older builds and never resolve.
|
||
|
|
if (user.avatarUrl && !user.avatarUrl.startsWith('/static/uploads/')) {
|
||
|
|
user.avatarUrl = ''
|
||
|
|
dirty = true
|
||
|
|
}
|
||
|
|
if (!('role' in user)) return
|
||
|
|
if (user.role === 'admin' && !data.adminAllowlist.includes(user.openid)) {
|
||
|
|
data.adminAllowlist.push(user.openid)
|
||
|
|
}
|
||
|
|
delete user.role
|
||
|
|
dirty = true
|
||
|
|
})
|
||
|
|
|
||
|
|
data.admins.forEach((admin) => {
|
||
|
|
if (!passwords.isHashed(admin.password)) {
|
||
|
|
admin.password = passwords.hash(admin.password || '')
|
||
|
|
dirty = true
|
||
|
|
}
|
||
|
|
})
|
||
|
|
return dirty
|
||
|
|
}
|
||
|
|
|
||
|
|
function persist(data) {
|
||
|
|
fs.mkdirSync(path.dirname(file), { recursive: true })
|
||
|
|
const tmp = `${file}.${process.pid}.tmp`
|
||
|
|
fs.writeFileSync(tmp, JSON.stringify(data, null, 2))
|
||
|
|
fs.renameSync(tmp, file)
|
||
|
|
}
|
||
|
|
|
||
|
|
function load() {
|
||
|
|
if (cache) return cache
|
||
|
|
const data = fs.existsSync(file)
|
||
|
|
? JSON.parse(fs.readFileSync(file, 'utf8'))
|
||
|
|
: JSON.parse(JSON.stringify(SEED))
|
||
|
|
if (normalize(data) || !fs.existsSync(file)) persist(data)
|
||
|
|
cache = data
|
||
|
|
return cache
|
||
|
|
}
|
||
|
|
|
||
|
|
function read() {
|
||
|
|
return load()
|
||
|
|
}
|
||
|
|
|
||
|
|
function update(mutator) {
|
||
|
|
const data = load()
|
||
|
|
const result = mutator(data)
|
||
|
|
persist(data)
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
function reset() {
|
||
|
|
cache = null
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = { read, update, reset, file, SEED }
|