const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() const _ = db.command async function safeGet(collection, where, limit = 200) { try { return await db.collection(collection).where(where).limit(limit).get() } catch (e) { return { data: [] } } } function formatTimeText(ts) { const diff = Date.now() - ts const mins = Math.floor(diff / 60000) if (mins < 1) return '刚刚' if (mins < 60) return `${mins} 分钟前` const hours = Math.floor(mins / 60) if (hours < 24) return `${hours} 小时前` const days = Math.floor(hours / 24) if (days === 1) return '昨天' if (days < 7) return `${days} 天前` const d = new Date(ts) return `${d.getMonth() + 1}月${d.getDate()}日` } function isCloudFileId(value) { return typeof value === 'string' && value.startsWith('cloud://') } function extractMediaUrl(media) { if (!media) return '' if (typeof media === 'string') return media return media.url || media.fileId || media.fileID || '' } async function resolveTempUrlMap(urls) { const fileIds = [...new Set(urls.filter(isCloudFileId))] if (!fileIds.length) return new Map() try { const result = await cloud.getTempFileURL({ fileList: fileIds }) const fileList = Array.isArray(result.fileList) ? result.fileList : [] return new Map(fileList.map(file => [ file.fileID || file.fileId, file.tempFileURL || file.tempFileUrl || '' ]).filter(([fileId, tempUrl]) => fileId && tempUrl)) } catch (e) { return new Map() } } function readableUrl(url, tempUrlMap) { if (!url) return '' if (!isCloudFileId(url)) return url return tempUrlMap.get(url) || '' } // List a user's own posts. Defaults to the caller; pass authorId to view // another user (only public posts are returned for others). exports.main = async event => { const { OPENID } = cloud.getWXContext() if (!OPENID) throw new Error('no openid in wx context') const pageSize = Math.max(1, Math.min(Number(event.pageSize || 20), 50)) let authorOpenid = OPENID let viewingSelf = true if (event.authorId) { const u = await db.collection('users').doc(event.authorId).get().catch(() => null) if (!u || !u.data) return { list: [] } authorOpenid = u.data.openid viewingSelf = authorOpenid === OPENID } const where = { authorOpenid } if (!viewingSelf) where.visibility = 'public' const cursor = Number(event.cursor || 0) if (Number.isFinite(cursor) && cursor > 0) where.createdAt = _.lt(cursor) const res = await db.collection('posts') .where(where) .orderBy('createdAt', 'desc') .limit(pageSize + 1) .get() if (!res.data.length) return { list: [], nextCursor: '' } const page = res.data.slice(0, pageSize) const nextCursor = res.data.length > pageSize && page[page.length - 1] ? String(page[page.length - 1].createdAt || '') : '' const authorRes = await db.collection('users').where({ openid: authorOpenid }).limit(1).get().catch(() => null) const authorUser = authorRes && authorRes.data ? authorRes.data[0] : null // Real presence: author has a recent, visible location (same signal as 附近). const ONLINE_WINDOW_MS = 5 * 60 * 1000 const locRes = await safeGet('locations', { openid: authorOpenid }, 1) const authorLoc = locRes.data[0] const authorOnline = Boolean( authorLoc && authorLoc.visible && authorLoc.updatedAt && Date.now() - authorLoc.updatedAt < ONLINE_WINDOW_MS ) const postIds = page.map(p => p._id) const likedRes = await safeGet('postLikes', { openid: OPENID, postId: _.in(postIds) }) const likedSet = new Set(likedRes.data.map(l => l.postId)) const assetUrls = [ authorUser?.avatarUrl || '', ...page.flatMap(post => Array.isArray(post.media) ? post.media.map(extractMediaUrl) : []) ] const tempUrlMap = await resolveTempUrlMap(assetUrls) const list = page.map(post => ({ _id: post._id, author: { id: post.authorId || '', name: post.authorSnapshot?.name || '用户', avatarKey: authorUser?.avatarKey || post.authorSnapshot?.avatarKey || 'gradient-avatar-1', avatarUrl: readableUrl(authorUser?.avatarUrl || post.authorSnapshot?.avatarUrl || '', tempUrlMap), online: authorOnline }, pet: post.petSnapshot || { name: '', breed: '' }, body: post.content || '', topics: post.topics || [], visibility: post.visibility || 'public', media: Array.isArray(post.media) ? post.media .map(extractMediaUrl) .map(url => readableUrl(url, tempUrlMap)) .filter(Boolean) : [], mediaTone: post.mediaTone || 'pet-1', sticker: post.sticker || undefined, locationText: post.locationText || '', timeText: formatTimeText(post.createdAt || Date.now()), counts: { likes: post.counts?.likes || 0, comments: post.counts?.comments || 0 }, likedByMe: likedSet.has(post._id) })) return { list, nextCursor } }