Add 卓米会 mini program and admin server.
Initial commit of the WeChat mini program, Express backend, and admin console. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
const { uid, str, has } = require('./http')
|
||||
const { isAdmin } = require('./auth')
|
||||
|
||||
const STATUSES = ['current', 'past', 'draft']
|
||||
const DEFAULT_BRAND = '卓米会 ZHUOMI'
|
||||
|
||||
function publicActivity(activity, posters) {
|
||||
const poster = (posters || [])
|
||||
.filter((item) => item.activityId === activity.id && item.enabled && item.image)
|
||||
.sort((a, b) => a.sort - b.sort)[0]
|
||||
return {
|
||||
id: activity.id,
|
||||
title: activity.title,
|
||||
brandName: activity.brandName || DEFAULT_BRAND,
|
||||
brandDesc: activity.brandDesc || '',
|
||||
startAt: activity.startAt || '',
|
||||
address: activity.address || '',
|
||||
latitude: activity.latitude,
|
||||
longitude: activity.longitude,
|
||||
status: activity.status,
|
||||
isCurrent: !!activity.isCurrent,
|
||||
pinned: !!activity.pinned,
|
||||
pinnedAt: activity.pinnedAt || '',
|
||||
theme: activity.theme || 0,
|
||||
cover: activity.cover || '',
|
||||
video: activity.video || '',
|
||||
poster: (poster && poster.image) || activity.cover || '',
|
||||
createdAt: activity.createdAt
|
||||
}
|
||||
}
|
||||
|
||||
function currentActivity(data) {
|
||||
return (
|
||||
data.activities.find((item) => item.isCurrent && item.status !== 'draft') ||
|
||||
data.activities.find((item) => item.status === 'current') ||
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
function byPinnedThenNewest(a, b) {
|
||||
const ap = a.pinned ? 1 : 0
|
||||
const bp = b.pinned ? 1 : 0
|
||||
if (ap !== bp) return bp - ap
|
||||
if (ap) return String(b.pinnedAt || '').localeCompare(String(a.pinnedAt || ''))
|
||||
return String(b.createdAt).localeCompare(String(a.createdAt))
|
||||
}
|
||||
|
||||
function pastActivities(data) {
|
||||
return data.activities.filter((item) => item.status === 'past').sort(byPinnedThenNewest)
|
||||
}
|
||||
|
||||
function nextPastTheme(data) {
|
||||
return data.activities.filter((item) => item.status === 'past').length % 2
|
||||
}
|
||||
|
||||
function setPinned(activity, pinned) {
|
||||
const now = new Date().toISOString()
|
||||
activity.pinned = !!pinned
|
||||
activity.pinnedAt = pinned ? now : ''
|
||||
activity.updatedAt = now
|
||||
return activity
|
||||
}
|
||||
|
||||
const PROFILE_FIELDS = ['nickName', 'avatarUrl', 'name', 'phone', 'company', 'position']
|
||||
|
||||
function publicProfile(user, data) {
|
||||
const profile = { openid: user.openid }
|
||||
PROFILE_FIELDS.forEach((key) => {
|
||||
profile[key] = user[key] || ''
|
||||
})
|
||||
profile.isAdmin = isAdmin(user.openid, data)
|
||||
return profile
|
||||
}
|
||||
|
||||
function coordinate(value) {
|
||||
if (value === '' || value == null) return null
|
||||
const num = Number(value)
|
||||
return Number.isFinite(num) ? num : null
|
||||
}
|
||||
|
||||
function activityPayload(body, prev, data) {
|
||||
const now = new Date().toISOString()
|
||||
const status = STATUSES.includes(body.status) ? body.status : 'draft'
|
||||
const keep = (key, fallback) => (has(body, key) ? str(body[key]) : prev ? prev[key] || fallback : fallback)
|
||||
|
||||
let pinned = prev ? !!prev.pinned : false
|
||||
let pinnedAt = prev ? prev.pinnedAt || '' : ''
|
||||
if (status !== 'past') {
|
||||
pinned = false
|
||||
pinnedAt = ''
|
||||
} else if (has(body, 'pinned')) {
|
||||
const requested = body.pinned === true || body.pinned === 'on' || body.pinned === 'true'
|
||||
if (requested !== pinned) pinnedAt = requested ? now : ''
|
||||
pinned = requested
|
||||
}
|
||||
|
||||
let theme = Number(body.theme)
|
||||
if (!has(body, 'theme') || !Number.isFinite(theme)) {
|
||||
theme = prev ? prev.theme || 0 : status === 'past' ? nextPastTheme(data) : 0
|
||||
}
|
||||
|
||||
return {
|
||||
id: prev ? prev.id : uid('a'),
|
||||
title: str(body.title, 60),
|
||||
brandName: str(body.brandName) || DEFAULT_BRAND,
|
||||
brandDesc: str(body.brandDesc, 300),
|
||||
cover: keep('cover', ''),
|
||||
video: keep('video', ''),
|
||||
startAt: str(body.startAt, 32),
|
||||
address: str(body.address, 120),
|
||||
latitude: coordinate(body.latitude),
|
||||
longitude: coordinate(body.longitude),
|
||||
status,
|
||||
isCurrent: !!body.isCurrent,
|
||||
pinned,
|
||||
pinnedAt,
|
||||
theme,
|
||||
createdAt: prev ? prev.createdAt : now,
|
||||
updatedAt: now
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
publicActivity,
|
||||
currentActivity,
|
||||
pastActivities,
|
||||
nextPastTheme,
|
||||
setPinned,
|
||||
publicProfile,
|
||||
activityPayload,
|
||||
DEFAULT_BRAND
|
||||
}
|
||||
Reference in New Issue
Block a user