Wire real WeChat app config, prefer activity cover over posters, and add admin cover cropping for the home banner. Co-authored-by: Cursor <cursoragent@cursor.com>
134 lines
3.9 KiB
JavaScript
134 lines
3.9 KiB
JavaScript
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 || '',
|
|
// Activity cover wins over linked posters so admin “封面图片” updates show immediately.
|
|
poster: activity.cover || (poster && poster.image) || '',
|
|
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
|
|
}
|