v0.0.4
This commit is contained in:
19
cloudfunctions/conversationRead/index.js
Normal file
19
cloudfunctions/conversationRead/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
const _ = db.command
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
if (!event.conversationId) throw new Error('conversationId is required')
|
||||
|
||||
await db.collection('conversations').doc(event.conversationId).update({
|
||||
data: {
|
||||
unreadOpenids: _.pull(OPENID),
|
||||
[`unreadCounts.${OPENID}`]: 0
|
||||
}
|
||||
})
|
||||
|
||||
return { unreadCount: 0 }
|
||||
}
|
||||
9
cloudfunctions/conversationRead/package.json
Normal file
9
cloudfunctions/conversationRead/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "conversationRead",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
13
cloudfunctions/draftGet/index.js
Normal file
13
cloudfunctions/draftGet/index.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
|
||||
exports.main = async () => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
if (!OPENID) throw new Error('no openid in wx context')
|
||||
const res = await db.collection('drafts').where({ openid: OPENID }).limit(1).get()
|
||||
if (!res.data.length) return { draft: null }
|
||||
const record = res.data[0]
|
||||
return { draft: record.draft || null, updatedAt: record.updatedAt }
|
||||
}
|
||||
8
cloudfunctions/draftGet/package.json
Normal file
8
cloudfunctions/draftGet/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "draftGet",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
24
cloudfunctions/draftSave/index.js
Normal file
24
cloudfunctions/draftSave/index.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
const now = Date.now()
|
||||
const data = {
|
||||
openid: OPENID,
|
||||
draft: event.draft || {},
|
||||
updatedAt: now
|
||||
}
|
||||
const existed = await db.collection('drafts').where({ openid: OPENID }).limit(1).get()
|
||||
|
||||
if (existed.data.length) {
|
||||
await db.collection('drafts').doc(existed.data[0]._id).update({ data })
|
||||
return { draftId: existed.data[0]._id, updatedAt: now }
|
||||
}
|
||||
|
||||
const result = await db.collection('drafts').add({ data })
|
||||
return { draftId: result._id, updatedAt: now }
|
||||
}
|
||||
|
||||
9
cloudfunctions/draftSave/package.json
Normal file
9
cloudfunctions/draftSave/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "draftSave",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
105
cloudfunctions/feedList/index.js
Normal file
105
cloudfunctions/feedList/index.js
Normal file
@@ -0,0 +1,105 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
const _ = db.command
|
||||
|
||||
// Query a collection that may not exist yet (created lazily on first write).
|
||||
// Returns an empty result instead of throwing "collection not exists".
|
||||
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()}日`
|
||||
}
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
const pageSize = Math.min(event.pageSize || 20, 50)
|
||||
const query = { visibility: 'public' }
|
||||
|
||||
if (event.topic) query.topics = event.topic
|
||||
|
||||
// Follow channel: only posts from users I follow (plus my own).
|
||||
if (event.channel === 'follow') {
|
||||
if (!OPENID) return { list: [], nextCursor: '' }
|
||||
const meRes = await db.collection('users').where({ openid: OPENID }).limit(1).get()
|
||||
if (!meRes.data.length) return { list: [], nextCursor: '' }
|
||||
const meId = meRes.data[0]._id
|
||||
const followRes = await safeGet('follows', { followerId: meId }, 500)
|
||||
const authorOpenids = followRes.data.map(f => f.followeeOpenid).filter(Boolean)
|
||||
authorOpenids.push(OPENID)
|
||||
query.authorOpenid = _.in(authorOpenids)
|
||||
query.visibility = _.in(['public', 'friends'])
|
||||
}
|
||||
|
||||
if (event.cursor) {
|
||||
query.createdAt = _.lt(Number(event.cursor))
|
||||
}
|
||||
|
||||
const res = await db.collection('posts')
|
||||
.where(query)
|
||||
.orderBy('createdAt', 'desc')
|
||||
.limit(pageSize)
|
||||
.get()
|
||||
|
||||
if (!res.data.length) return { list: [], nextCursor: '' }
|
||||
|
||||
const postIds = res.data.map(p => p._id)
|
||||
|
||||
// Batch-check liked/favorited status — skip if no valid OPENID (non-WeChat context)
|
||||
let likedSet = new Set()
|
||||
let favSet = new Set()
|
||||
if (OPENID) {
|
||||
const [likedRes, favRes] = await Promise.all([
|
||||
safeGet('postLikes', { openid: OPENID, postId: _.in(postIds) }),
|
||||
safeGet('postFavorites', { openid: OPENID, postId: _.in(postIds) })
|
||||
])
|
||||
likedSet = new Set(likedRes.data.map(l => l.postId))
|
||||
favSet = new Set(favRes.data.map(f => f.postId))
|
||||
}
|
||||
|
||||
const list = res.data.map(post => ({
|
||||
_id: post._id,
|
||||
author: {
|
||||
id: post.authorId || '',
|
||||
name: post.authorSnapshot?.name || '用户',
|
||||
avatarKey: post.authorSnapshot?.avatarKey || 'gradient-avatar-1'
|
||||
},
|
||||
pet: post.petSnapshot || { name: '', breed: '' },
|
||||
body: post.content || '',
|
||||
topics: post.topics || [],
|
||||
media: Array.isArray(post.media)
|
||||
? post.media.map(m => (typeof m === 'string' ? m : m && (m.url || m.fileId))).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,
|
||||
favorites: post.counts?.favorites || 0
|
||||
},
|
||||
likedByMe: likedSet.has(post._id),
|
||||
favoritedByMe: favSet.has(post._id)
|
||||
}))
|
||||
|
||||
const last = res.data[res.data.length - 1]
|
||||
return { list, nextCursor: last ? String(last.createdAt) : '' }
|
||||
}
|
||||
9
cloudfunctions/feedList/package.json
Normal file
9
cloudfunctions/feedList/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "feedList",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
63
cloudfunctions/followList/index.js
Normal file
63
cloudfunctions/followList/index.js
Normal file
@@ -0,0 +1,63 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
const _ = db.command
|
||||
|
||||
async function fetchUsers(ids) {
|
||||
if (!ids.length) return []
|
||||
const out = []
|
||||
// _.in supports up to ~100 values; chunk to be safe
|
||||
for (let i = 0; i < ids.length; i += 100) {
|
||||
const chunk = ids.slice(i, i + 100)
|
||||
const res = await db.collection('users').where({ _id: _.in(chunk) }).limit(100).get()
|
||||
out.push(...res.data)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
if (!OPENID) throw new Error('no openid in wx context')
|
||||
const meRes = await db.collection('users').where({ openid: OPENID }).limit(1).get()
|
||||
if (!meRes.data.length) throw new Error('user not found')
|
||||
const meId = meRes.data[0]._id
|
||||
const type = event.type || 'friends'
|
||||
|
||||
const safeFollows = where =>
|
||||
db.collection('follows').where(where).limit(500).get().catch(() => ({ data: [] }))
|
||||
const [followingRes, followerRes] = await Promise.all([
|
||||
safeFollows({ followerId: meId }),
|
||||
safeFollows({ followeeId: meId })
|
||||
])
|
||||
const followingIds = followingRes.data.map(f => f.followeeId)
|
||||
const followerIds = followerRes.data.map(f => f.followerId)
|
||||
const followingSet = new Set(followingIds)
|
||||
const followerSet = new Set(followerIds)
|
||||
const friendIds = followingIds.filter(id => followerSet.has(id))
|
||||
|
||||
let ids = friendIds
|
||||
if (type === 'following') ids = followingIds
|
||||
else if (type === 'followers') ids = followerIds
|
||||
|
||||
const users = await fetchUsers(ids)
|
||||
const list = users.map(u => ({
|
||||
id: u._id,
|
||||
name: u.nickname || '用户',
|
||||
handle: u.handle || '',
|
||||
avatarKey: u.avatarKey || 'gradient-avatar-1',
|
||||
avatarUrl: u.avatarUrl || '',
|
||||
bio: u.bio || '',
|
||||
isFollowing: followingSet.has(u._id),
|
||||
isFriend: followingSet.has(u._id) && followerSet.has(u._id)
|
||||
}))
|
||||
|
||||
return {
|
||||
list,
|
||||
counts: {
|
||||
following: followingIds.length,
|
||||
followers: followerIds.length,
|
||||
friends: friendIds.length
|
||||
}
|
||||
}
|
||||
}
|
||||
8
cloudfunctions/followList/package.json
Normal file
8
cloudfunctions/followList/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "followList",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
47
cloudfunctions/followToggle/index.js
Normal file
47
cloudfunctions/followToggle/index.js
Normal file
@@ -0,0 +1,47 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
if (!OPENID) throw new Error('no openid in wx context')
|
||||
const { targetUserId, follow } = event
|
||||
if (!targetUserId) throw new Error('targetUserId is required')
|
||||
|
||||
const meRes = await db.collection('users').where({ openid: OPENID }).limit(1).get()
|
||||
if (!meRes.data.length) throw new Error('user not found')
|
||||
const meId = meRes.data[0]._id
|
||||
if (meId === targetUserId) throw new Error('cannot follow yourself')
|
||||
|
||||
const targetRes = await db.collection('users').doc(targetUserId).get().catch(() => null)
|
||||
const target = targetRes && targetRes.data
|
||||
if (!target) throw new Error('target user not found')
|
||||
|
||||
const existing = await db.collection('follows')
|
||||
.where({ followerId: meId, followeeId: targetUserId })
|
||||
.limit(1)
|
||||
.get()
|
||||
|
||||
if (follow && !existing.data.length) {
|
||||
await db.collection('follows').add({
|
||||
data: {
|
||||
followerId: meId,
|
||||
followerOpenid: OPENID,
|
||||
followeeId: targetUserId,
|
||||
followeeOpenid: target.openid,
|
||||
createdAt: Date.now()
|
||||
}
|
||||
})
|
||||
} else if (!follow && existing.data.length) {
|
||||
await db.collection('follows').doc(existing.data[0]._id).remove()
|
||||
}
|
||||
|
||||
// A friendship (汪友) is a mutual follow.
|
||||
const back = await db.collection('follows')
|
||||
.where({ followerId: targetUserId, followeeId: meId })
|
||||
.limit(1)
|
||||
.get()
|
||||
|
||||
return { following: Boolean(follow), mutual: Boolean(follow) && back.data.length > 0 }
|
||||
}
|
||||
8
cloudfunctions/followToggle/package.json
Normal file
8
cloudfunctions/followToggle/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "followToggle",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
21
cloudfunctions/locationUpdate/index.js
Normal file
21
cloudfunctions/locationUpdate/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
const now = Date.now()
|
||||
const data = {
|
||||
openid: OPENID,
|
||||
location: event.location || null,
|
||||
visible: Boolean(event.visible),
|
||||
updatedAt: now
|
||||
}
|
||||
const existed = await db.collection('locations').where({ openid: OPENID }).limit(1).get()
|
||||
if (existed.data.length) await db.collection('locations').doc(existed.data[0]._id).update({ data })
|
||||
else await db.collection('locations').add({ data })
|
||||
|
||||
return { ok: true }
|
||||
}
|
||||
|
||||
9
cloudfunctions/locationUpdate/package.json
Normal file
9
cloudfunctions/locationUpdate/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "locationUpdate",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
59
cloudfunctions/login/index.js
Normal file
59
cloudfunctions/login/index.js
Normal file
@@ -0,0 +1,59 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
|
||||
// Generate a unique handle from openid
|
||||
function makeHandle(openid) {
|
||||
const suffix = openid.slice(-6).toLowerCase()
|
||||
return `@user_${suffix}`
|
||||
}
|
||||
|
||||
// Default avatar pool to randomise new user avatars
|
||||
const AVATAR_KEYS = [
|
||||
'gradient-avatar-1', 'gradient-avatar-2', 'gradient-avatar-3',
|
||||
'gradient-avatar-4', 'gradient-avatar-5', 'gradient-avatar-6'
|
||||
]
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
if (!OPENID) throw new Error('no openid in wx context')
|
||||
|
||||
const now = Date.now()
|
||||
const existed = await db.collection('users').where({ openid: OPENID }).limit(1).get()
|
||||
|
||||
if (existed.data.length) {
|
||||
const user = existed.data[0]
|
||||
await db.collection('users').doc(user._id).update({
|
||||
data: { lastLoginAt: now }
|
||||
})
|
||||
return {
|
||||
openid: OPENID,
|
||||
user: { ...user, lastLoginAt: now },
|
||||
isNew: false
|
||||
}
|
||||
}
|
||||
|
||||
// New user — create with sensible defaults
|
||||
const avatarKey = AVATAR_KEYS[Math.floor(Math.random() * AVATAR_KEYS.length)]
|
||||
const user = {
|
||||
openid: OPENID,
|
||||
nickname: event.userInfo?.nickname || '宠物达人',
|
||||
handle: makeHandle(OPENID),
|
||||
avatarKey,
|
||||
avatarUrl: '',
|
||||
profileCompleted: false,
|
||||
location: '',
|
||||
yearsWithPets: 0,
|
||||
level: 1,
|
||||
bio: '',
|
||||
verified: false,
|
||||
stats: { posts: 0, following: 0, followers: 0, favorites: 0 },
|
||||
preferences: { notifications: true, nearbyVisible: true },
|
||||
createdAt: now,
|
||||
lastLoginAt: now
|
||||
}
|
||||
|
||||
const result = await db.collection('users').add({ data: user })
|
||||
return { openid: OPENID, user: { _id: result._id, ...user }, isNew: true }
|
||||
}
|
||||
9
cloudfunctions/login/package.json
Normal file
9
cloudfunctions/login/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "login",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
46
cloudfunctions/matchLike/index.js
Normal file
46
cloudfunctions/matchLike/index.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
const _ = db.command
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
const { petId } = event
|
||||
if (!petId) throw new Error('petId is required')
|
||||
|
||||
// Get current user and their pets
|
||||
const userRes = await db.collection('users').where({ openid: OPENID }).limit(1).get()
|
||||
if (!userRes.data.length) throw new Error('user not found')
|
||||
const userId = userRes.data[0]._id
|
||||
|
||||
// Record this like (idempotent)
|
||||
const existed = await db.collection('matches').where({ fromOpenid: OPENID, petId }).limit(1).get()
|
||||
if (!existed.data.length) {
|
||||
await db.collection('matches').add({ data: { fromOpenid: OPENID, userId, petId, createdAt: Date.now() } })
|
||||
}
|
||||
|
||||
// Check mutual match: does the target pet's owner like any of current user's pets?
|
||||
let matched = false
|
||||
try {
|
||||
const targetPetRes = await db.collection('pets').doc(petId).get()
|
||||
const targetOwnerId = targetPetRes.data?.ownerId
|
||||
if (targetOwnerId && targetOwnerId !== userId) {
|
||||
const targetOwnerRes = await db.collection('users').doc(targetOwnerId).get()
|
||||
const targetOpenid = targetOwnerRes.data?.openid
|
||||
if (targetOpenid) {
|
||||
const myPetsRes = await db.collection('pets').where({ ownerId: userId }).limit(20).get()
|
||||
const myPetIds = myPetsRes.data.map(p => p._id)
|
||||
if (myPetIds.length) {
|
||||
const reverseRes = await db.collection('matches')
|
||||
.where({ fromOpenid: targetOpenid, petId: _.in(myPetIds) })
|
||||
.limit(1)
|
||||
.get()
|
||||
matched = reverseRes.data.length > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
return { matched }
|
||||
}
|
||||
9
cloudfunctions/matchLike/package.json
Normal file
9
cloudfunctions/matchLike/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "matchLike",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
101
cloudfunctions/messageList/index.js
Normal file
101
cloudfunctions/messageList/index.js
Normal file
@@ -0,0 +1,101 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
const _ = db.command
|
||||
|
||||
function formatTimeText(ts) {
|
||||
if (!ts) return ''
|
||||
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 '昨天'
|
||||
const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||
if (days < 7) return weekdays[new Date(ts).getDay()]
|
||||
const d = new Date(ts)
|
||||
return `${d.getMonth() + 1}/${d.getDate()}`
|
||||
}
|
||||
|
||||
// Query a collection that may not exist yet (created lazily on first write).
|
||||
async function safeGet(collection, where, limit = 200) {
|
||||
try {
|
||||
return await db.collection(collection).where(where).limit(limit).get()
|
||||
} catch (e) {
|
||||
return { data: [] }
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchUsers(ids) {
|
||||
if (!ids.length) return []
|
||||
const out = []
|
||||
for (let i = 0; i < ids.length; i += 100) {
|
||||
const chunk = ids.slice(i, i + 100)
|
||||
const res = await db.collection('users').where({ _id: _.in(chunk) }).limit(100).get()
|
||||
out.push(...res.data)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
if (!OPENID) throw new Error('no openid in wx context')
|
||||
const tab = event.tab || 'all'
|
||||
const query = { memberOpenids: OPENID }
|
||||
if (tab === 'groups') query.type = 'group'
|
||||
if (tab === 'unread') query.unreadOpenids = OPENID
|
||||
|
||||
const meRes = await db.collection('users').where({ openid: OPENID }).limit(1).get()
|
||||
const meId = meRes.data[0] && meRes.data[0]._id
|
||||
|
||||
// Active row = my 汪友 (mutual follows)
|
||||
let friendUsers = []
|
||||
if (meId) {
|
||||
const [followingRes, followerRes] = await Promise.all([
|
||||
safeGet('follows', { followerId: meId }, 500),
|
||||
safeGet('follows', { followeeId: meId }, 500)
|
||||
])
|
||||
const followerSet = new Set(followerRes.data.map(f => f.followerId))
|
||||
const friendIds = followingRes.data.map(f => f.followeeId).filter(id => followerSet.has(id))
|
||||
friendUsers = await fetchUsers(friendIds)
|
||||
}
|
||||
|
||||
let convRes
|
||||
try {
|
||||
convRes = await db.collection('conversations').where(query).orderBy('updatedAt', 'desc').limit(50).get()
|
||||
} catch (e) {
|
||||
convRes = { data: [] }
|
||||
}
|
||||
|
||||
const conversations = convRes.data.map(c => {
|
||||
const unreadOpenids = c.unreadOpenids || []
|
||||
const unreadCounts = c.unreadCounts || {}
|
||||
const unreadCount = unreadCounts[OPENID] || (unreadOpenids.includes(OPENID) ? 1 : 0)
|
||||
return {
|
||||
_id: c._id,
|
||||
type: c.type || 'single',
|
||||
title: c.title || '未知用户',
|
||||
petName: c.petName || undefined,
|
||||
avatarKey: c.avatarKey || 'gradient-avatar-1',
|
||||
preview: c.lastMessage?.content || c.preview || '',
|
||||
timeText: formatTimeText(c.updatedAt || c.createdAt),
|
||||
unreadCount,
|
||||
pinned: Boolean(c.pinned),
|
||||
muted: Boolean(c.muted),
|
||||
online: Boolean(c.online)
|
||||
}
|
||||
})
|
||||
|
||||
const activeUsers = friendUsers.map(u => ({
|
||||
id: u._id,
|
||||
name: u.nickname || '汪友',
|
||||
avatarKey: u.avatarKey || 'gradient-avatar-1',
|
||||
avatarUrl: u.avatarUrl || '',
|
||||
live: Boolean(u.online)
|
||||
}))
|
||||
|
||||
return { activeUsers, conversations }
|
||||
}
|
||||
9
cloudfunctions/messageList/package.json
Normal file
9
cloudfunctions/messageList/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "messageList",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
88
cloudfunctions/messageSend/index.js
Normal file
88
cloudfunctions/messageSend/index.js
Normal file
@@ -0,0 +1,88 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
const _ = db.command
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
const content = String(event.content || '').trim()
|
||||
if (!content) throw new Error('content is required')
|
||||
|
||||
const now = Date.now()
|
||||
let convId = event.conversationId
|
||||
|
||||
// Create a 1:1 conversation if not provided
|
||||
if (!convId) {
|
||||
let { toOpenid } = event
|
||||
// Allow opening by user id without exposing openids to the client.
|
||||
if (!toOpenid && event.toUserId) {
|
||||
const toUserById = await db.collection('users').doc(event.toUserId).get().catch(() => null)
|
||||
if (toUserById && toUserById.data) toOpenid = toUserById.data.openid
|
||||
}
|
||||
if (!toOpenid) throw new Error('conversationId or toOpenid/toUserId is required')
|
||||
|
||||
// Look for existing conversation between these two users
|
||||
const existing = await db.collection('conversations')
|
||||
.where({ type: 'single', memberOpenids: _.all([OPENID, toOpenid]) })
|
||||
.limit(1)
|
||||
.get()
|
||||
|
||||
if (existing.data.length) {
|
||||
convId = existing.data[0]._id
|
||||
} else {
|
||||
const toUserRes = await db.collection('users').where({ openid: toOpenid }).limit(1).get()
|
||||
const toUser = toUserRes.data[0] || { nickname: '用户', avatarKey: 'gradient-avatar-1' }
|
||||
const newConv = await db.collection('conversations').add({
|
||||
data: {
|
||||
type: 'single',
|
||||
memberOpenids: [OPENID, toOpenid],
|
||||
unreadOpenids: [toOpenid],
|
||||
unreadCounts: { [toOpenid]: 1 },
|
||||
title: toUser.nickname,
|
||||
avatarKey: toUser.avatarKey,
|
||||
lastMessage: { content: content.slice(0, 50), createdAt: now },
|
||||
pinned: false,
|
||||
muted: false,
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
}
|
||||
})
|
||||
convId = newConv._id
|
||||
}
|
||||
}
|
||||
|
||||
// Add message to messages collection
|
||||
const msgResult = await db.collection('messages').add({
|
||||
data: {
|
||||
conversationId: convId,
|
||||
fromOpenid: OPENID,
|
||||
content: content.slice(0, 1000),
|
||||
type: event.msgType || 'text',
|
||||
createdAt: now
|
||||
}
|
||||
})
|
||||
|
||||
// Update conversation: last message + unread counts for all recipients
|
||||
const convRes = await db.collection('conversations').doc(convId).get()
|
||||
const members = convRes.data.memberOpenids || []
|
||||
const recipients = members.filter(id => id !== OPENID)
|
||||
|
||||
const updateData = {
|
||||
lastMessage: { content: content.slice(0, 50), createdAt: now },
|
||||
updatedAt: now
|
||||
}
|
||||
// Add recipients to unreadOpenids and increment their unread counts
|
||||
recipients.forEach(r => {
|
||||
updateData[`unreadCounts.${r}`] = _.inc(1)
|
||||
})
|
||||
|
||||
// Rebuild unreadOpenids to include all recipients (remove duplicates)
|
||||
const existingUnread = convRes.data.unreadOpenids || []
|
||||
const newUnread = [...new Set([...existingUnread, ...recipients])]
|
||||
updateData.unreadOpenids = newUnread
|
||||
|
||||
await db.collection('conversations').doc(convId).update({ data: updateData })
|
||||
|
||||
return { messageId: msgResult._id, conversationId: convId }
|
||||
}
|
||||
8
cloudfunctions/messageSend/package.json
Normal file
8
cloudfunctions/messageSend/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "messageSend",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
82
cloudfunctions/messageThread/index.js
Normal file
82
cloudfunctions/messageThread/index.js
Normal file
@@ -0,0 +1,82 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
const _ = db.command
|
||||
|
||||
function timeText(ts) {
|
||||
if (!ts) return ''
|
||||
const d = new Date(ts)
|
||||
const hh = String(d.getHours()).padStart(2, '0')
|
||||
const mm = String(d.getMinutes()).padStart(2, '0')
|
||||
const sameDay = new Date().toDateString() === d.toDateString()
|
||||
if (sameDay) return `${hh}:${mm}`
|
||||
return `${d.getMonth() + 1}/${d.getDate()} ${hh}:${mm}`
|
||||
}
|
||||
|
||||
async function userToPeer(u) {
|
||||
return { id: u._id, name: u.nickname || '用户', avatarKey: u.avatarKey || 'gradient-avatar-1', avatarUrl: u.avatarUrl || '' }
|
||||
}
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
if (!OPENID) throw new Error('no openid in wx context')
|
||||
|
||||
let convId = event.conversationId || ''
|
||||
let peer = null
|
||||
|
||||
// Open by target user — resolve peer and find an existing conversation.
|
||||
if (!convId && event.targetUserId) {
|
||||
const tRes = await db.collection('users').doc(event.targetUserId).get().catch(() => null)
|
||||
const t = tRes && tRes.data
|
||||
if (t) {
|
||||
peer = await userToPeer(t)
|
||||
const ex = await db.collection('conversations')
|
||||
.where({ type: 'single', memberOpenids: _.all([OPENID, t.openid]) })
|
||||
.limit(1)
|
||||
.get()
|
||||
.catch(() => ({ data: [] }))
|
||||
if (ex.data.length) convId = ex.data[0]._id
|
||||
}
|
||||
}
|
||||
|
||||
// Open by conversation — resolve the other member as peer.
|
||||
if (convId && !peer) {
|
||||
const c = await db.collection('conversations').doc(convId).get().catch(() => null)
|
||||
if (c && c.data) {
|
||||
const otherOpenid = (c.data.memberOpenids || []).find(o => o !== OPENID)
|
||||
if (otherOpenid) {
|
||||
const oRes = await db.collection('users').where({ openid: otherOpenid }).limit(1).get()
|
||||
if (oRes.data[0]) peer = await userToPeer(oRes.data[0])
|
||||
}
|
||||
if (!peer) {
|
||||
peer = { id: '', name: c.data.title || '对话', avatarKey: c.data.avatarKey || 'gradient-avatar-1', avatarUrl: '' }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let messages = []
|
||||
if (convId) {
|
||||
const res = await db.collection('messages')
|
||||
.where({ conversationId: convId })
|
||||
.orderBy('createdAt', 'desc')
|
||||
.limit(50)
|
||||
.get()
|
||||
.catch(() => ({ data: [] }))
|
||||
messages = res.data.reverse().map(m => ({
|
||||
_id: m._id,
|
||||
fromMe: m.fromOpenid === OPENID,
|
||||
content: m.content,
|
||||
type: m.type || 'text',
|
||||
createdAt: m.createdAt,
|
||||
timeText: timeText(m.createdAt)
|
||||
}))
|
||||
|
||||
// Mark this conversation read for me.
|
||||
await db.collection('conversations').doc(convId).update({
|
||||
data: { unreadOpenids: _.pull(OPENID), [`unreadCounts.${OPENID}`]: 0 }
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
return { conversationId: convId, peer, messages }
|
||||
}
|
||||
8
cloudfunctions/messageThread/package.json
Normal file
8
cloudfunctions/messageThread/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "messageThread",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
70
cloudfunctions/nearbyPets/index.js
Normal file
70
cloudfunctions/nearbyPets/index.js
Normal file
@@ -0,0 +1,70 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
|
||||
function hashId(id) {
|
||||
let h = 5381
|
||||
for (let i = 0; i < id.length; i++) {
|
||||
h = ((h << 5) + h) ^ id.charCodeAt(i)
|
||||
h = h >>> 0 // keep as unsigned 32-bit
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// Generate a stable map pin position from pet id (fallback abstract layer)
|
||||
function stablePin(id) {
|
||||
const h = hashId(id)
|
||||
const left = 10 + (h % 70)
|
||||
const top = 15 + ((h >> 8) % 55)
|
||||
return { left, top }
|
||||
}
|
||||
|
||||
// Scatter a pet deterministically within roughly ±0.9km of the user, so on a
|
||||
// real map markers cluster nearby (stable per pet id across requests).
|
||||
function syntheticCoord(id, userLoc) {
|
||||
const h = hashId(id)
|
||||
const dLat = ((h % 1600) - 800) / 100000 // ±0.008° ≈ ±0.9km
|
||||
const dLng = (((h >> 8) % 1600) - 800) / 100000
|
||||
return { latitude: userLoc.latitude + dLat, longitude: userLoc.longitude + dLng }
|
||||
}
|
||||
|
||||
// Rough distance text from coordinate delta (fallback when no real distance)
|
||||
function distanceText(petLoc, userLoc) {
|
||||
if (!petLoc || !userLoc) return null
|
||||
const R = 6371000
|
||||
const dLat = ((petLoc.latitude - userLoc.latitude) * Math.PI) / 180
|
||||
const dLon = ((petLoc.longitude - userLoc.longitude) * Math.PI) / 180
|
||||
const a =
|
||||
Math.sin(dLat / 2) ** 2 +
|
||||
Math.cos((userLoc.latitude * Math.PI) / 180) *
|
||||
Math.cos((petLoc.latitude * Math.PI) / 180) *
|
||||
Math.sin(dLon / 2) ** 2
|
||||
const meters = Math.round(R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)))
|
||||
if (meters < 1000) return `${meters}m`
|
||||
return `${(meters / 1000).toFixed(1)}km`
|
||||
}
|
||||
|
||||
exports.main = async event => {
|
||||
const filter = event.filters?.type || 'all'
|
||||
const userLoc = event.location || null
|
||||
const query = {}
|
||||
if (filter === 'dog' || filter === 'cat') query.species = filter
|
||||
if (filter === 'online') query.online = true
|
||||
|
||||
const res = await db.collection('pets').where(query).limit(50).get()
|
||||
|
||||
const list = res.data.map(pet => {
|
||||
const realCoord = pet.location && typeof pet.location.latitude === 'number' ? pet.location : null
|
||||
const coord = realCoord || (userLoc ? syntheticCoord(pet._id, userLoc) : null)
|
||||
return {
|
||||
...pet,
|
||||
latitude: coord ? coord.latitude : undefined,
|
||||
longitude: coord ? coord.longitude : undefined,
|
||||
distanceText: distanceText(coord, userLoc) || pet.distanceText || null,
|
||||
pin: stablePin(pet._id)
|
||||
}
|
||||
})
|
||||
|
||||
return { list }
|
||||
}
|
||||
9
cloudfunctions/nearbyPets/package.json
Normal file
9
cloudfunctions/nearbyPets/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "nearbyPets",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
26
cloudfunctions/petDelete/index.js
Normal file
26
cloudfunctions/petDelete/index.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
if (!OPENID) throw new Error('no openid in wx context')
|
||||
if (!event.petId) throw new Error('petId is required')
|
||||
|
||||
const users = await db.collection('users').where({ openid: OPENID }).limit(1).get()
|
||||
if (!users.data.length) throw new Error('user not found')
|
||||
const ownerId = users.data[0]._id
|
||||
|
||||
let pet
|
||||
try {
|
||||
pet = await db.collection('pets').doc(event.petId).get()
|
||||
} catch (_) {
|
||||
return { ok: true } // already gone
|
||||
}
|
||||
if (!pet.data) return { ok: true }
|
||||
if (pet.data.ownerId !== ownerId) throw new Error('not allowed')
|
||||
|
||||
await db.collection('pets').doc(event.petId).remove()
|
||||
return { ok: true }
|
||||
}
|
||||
8
cloudfunctions/petDelete/package.json
Normal file
8
cloudfunctions/petDelete/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "petDelete",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
17
cloudfunctions/petList/index.js
Normal file
17
cloudfunctions/petList/index.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
let ownerId = event.ownerId
|
||||
if (!ownerId) {
|
||||
const user = await db.collection('users').where({ openid: OPENID }).limit(1).get()
|
||||
ownerId = user.data[0]?._id
|
||||
}
|
||||
|
||||
const res = ownerId ? await db.collection('pets').where({ ownerId }).limit(20).get() : { data: [] }
|
||||
return { list: res.data }
|
||||
}
|
||||
|
||||
9
cloudfunctions/petList/package.json
Normal file
9
cloudfunctions/petList/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "petList",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
27
cloudfunctions/petSave/index.js
Normal file
27
cloudfunctions/petSave/index.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
const users = await db.collection('users').where({ openid: OPENID }).limit(1).get()
|
||||
if (!users.data.length) throw new Error('user not found')
|
||||
|
||||
const pet = {
|
||||
...(event.pet || {}),
|
||||
ownerId: users.data[0]._id,
|
||||
updatedAt: Date.now()
|
||||
}
|
||||
delete pet._id
|
||||
|
||||
if (event.pet?._id) {
|
||||
await db.collection('pets').doc(event.pet._id).update({ data: pet })
|
||||
return { petId: event.pet._id }
|
||||
}
|
||||
|
||||
pet.createdAt = Date.now()
|
||||
const result = await db.collection('pets').add({ data: pet })
|
||||
return { petId: result._id }
|
||||
}
|
||||
|
||||
9
cloudfunctions/petSave/package.json
Normal file
9
cloudfunctions/petSave/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "petSave",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
65
cloudfunctions/postCreate/index.js
Normal file
65
cloudfunctions/postCreate/index.js
Normal file
@@ -0,0 +1,65 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
const _ = db.command
|
||||
|
||||
const MEDIA_TONES = ['pet-1', 'pet-2', 'pet-3']
|
||||
const PET_TONES = { dog: 'gold', cat: 'purple', other: 'green' }
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
const now = Date.now()
|
||||
const content = String(event.content || '').trim()
|
||||
if (!content) throw new Error('content is required')
|
||||
|
||||
// Get author info first (required for snapshot)
|
||||
const userRes = await db.collection('users').where({ openid: OPENID }).limit(1).get()
|
||||
if (!userRes.data.length) throw new Error('user not found')
|
||||
const user = userRes.data[0]
|
||||
|
||||
// Get pet snapshot if petId provided
|
||||
let petSnapshot = null
|
||||
if (event.petId) {
|
||||
try {
|
||||
const petRes = await db.collection('pets').doc(event.petId).get()
|
||||
if (petRes.data) {
|
||||
const p = petRes.data
|
||||
petSnapshot = { name: p.name, breed: p.breed, tone: PET_TONES[p.species] || 'green' }
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// Derive mediaTone from media count so similar posts look varied
|
||||
const mediaCount = Array.isArray(event.media) ? event.media.length : 0
|
||||
const mediaTone = event.mediaTone || MEDIA_TONES[(mediaCount + now) % 3]
|
||||
|
||||
const post = {
|
||||
authorOpenid: OPENID,
|
||||
authorId: user._id,
|
||||
authorSnapshot: { name: user.nickname, avatarKey: user.avatarKey },
|
||||
petId: event.petId || '',
|
||||
petSnapshot,
|
||||
content: content.slice(0, 2000),
|
||||
topics: Array.isArray(event.topics) ? event.topics : [],
|
||||
media: Array.isArray(event.media) ? event.media : [],
|
||||
mediaTone,
|
||||
sticker: event.sticker || null,
|
||||
locationText: event.locationText || '',
|
||||
location: (typeof event.latitude === 'number' && typeof event.longitude === 'number')
|
||||
? { latitude: event.latitude, longitude: event.longitude }
|
||||
: null,
|
||||
visibility: event.visibility || 'public',
|
||||
counts: { likes: 0, comments: 0, shares: 0, favorites: 0 },
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
}
|
||||
|
||||
const result = await db.collection('posts').add({ data: post })
|
||||
|
||||
await db.collection('users').doc(user._id).update({
|
||||
data: { 'stats.posts': _.inc(1) }
|
||||
})
|
||||
|
||||
return { postId: result._id }
|
||||
}
|
||||
9
cloudfunctions/postCreate/package.json
Normal file
9
cloudfunctions/postCreate/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "postCreate",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
32
cloudfunctions/postFavorite/index.js
Normal file
32
cloudfunctions/postFavorite/index.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
const _ = db.command
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
const { postId, favorited } = event
|
||||
if (!postId) throw new Error('postId is required')
|
||||
|
||||
const existed = await db.collection('postFavorites').where({ postId, openid: OPENID }).limit(1).get()
|
||||
let delta = 0
|
||||
|
||||
if (favorited && !existed.data.length) {
|
||||
await db.collection('postFavorites').add({ data: { postId, openid: OPENID, createdAt: Date.now() } })
|
||||
delta = 1
|
||||
}
|
||||
|
||||
if (!favorited && existed.data.length) {
|
||||
await db.collection('postFavorites').doc(existed.data[0]._id).remove()
|
||||
delta = -1
|
||||
}
|
||||
|
||||
if (delta) {
|
||||
await db.collection('posts').doc(postId).update({ data: { 'counts.favorites': _.inc(delta) } })
|
||||
}
|
||||
|
||||
const post = await db.collection('posts').doc(postId).get()
|
||||
return { favorited: Boolean(favorited), favorites: post.data.counts?.favorites || 0 }
|
||||
}
|
||||
|
||||
9
cloudfunctions/postFavorite/package.json
Normal file
9
cloudfunctions/postFavorite/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "postFavorite",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
32
cloudfunctions/postLike/index.js
Normal file
32
cloudfunctions/postLike/index.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
const _ = db.command
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
const { postId, liked } = event
|
||||
if (!postId) throw new Error('postId is required')
|
||||
|
||||
const existed = await db.collection('postLikes').where({ postId, openid: OPENID }).limit(1).get()
|
||||
let delta = 0
|
||||
|
||||
if (liked && !existed.data.length) {
|
||||
await db.collection('postLikes').add({ data: { postId, openid: OPENID, createdAt: Date.now() } })
|
||||
delta = 1
|
||||
}
|
||||
|
||||
if (!liked && existed.data.length) {
|
||||
await db.collection('postLikes').doc(existed.data[0]._id).remove()
|
||||
delta = -1
|
||||
}
|
||||
|
||||
if (delta) {
|
||||
await db.collection('posts').doc(postId).update({ data: { 'counts.likes': _.inc(delta) } })
|
||||
}
|
||||
|
||||
const post = await db.collection('posts').doc(postId).get()
|
||||
return { liked: Boolean(liked), likes: post.data.counts?.likes || 0 }
|
||||
}
|
||||
|
||||
9
cloudfunctions/postLike/package.json
Normal file
9
cloudfunctions/postLike/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "postLike",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
40
cloudfunctions/profileGet/index.js
Normal file
40
cloudfunctions/profileGet/index.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
|
||||
// Count a collection safely — returns 0 if the collection does not exist yet.
|
||||
async function safeCount(collection, where) {
|
||||
try {
|
||||
const res = await db.collection(collection).where(where).count()
|
||||
return res.total || 0
|
||||
} catch (e) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Derive live stats from the source collections so the displayed numbers are
|
||||
// always accurate instead of relying on a stored field that can drift.
|
||||
async function computeStats(user) {
|
||||
const [posts, favorites, following, followers] = await Promise.all([
|
||||
safeCount('posts', { authorId: user._id }),
|
||||
safeCount('postFavorites', { openid: user.openid }),
|
||||
safeCount('follows', { followerId: user._id }),
|
||||
safeCount('follows', { followeeId: user._id })
|
||||
])
|
||||
return { posts, following, followers, favorites }
|
||||
}
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
if (!event.userId && !OPENID) throw new Error('no openid in wx context')
|
||||
const query = event.userId ? { _id: event.userId } : { openid: OPENID }
|
||||
const users = await db.collection('users').where(query).limit(1).get()
|
||||
const user = users.data[0] || null
|
||||
if (!user) return { user: null, pets: [] }
|
||||
|
||||
const pets = await db.collection('pets').where({ ownerId: user._id }).limit(20).get()
|
||||
user.stats = await computeStats(user)
|
||||
|
||||
return { user, pets: pets.data }
|
||||
}
|
||||
9
cloudfunctions/profileGet/package.json
Normal file
9
cloudfunctions/profileGet/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "profileGet",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
28
cloudfunctions/profileUpdate/index.js
Normal file
28
cloudfunctions/profileUpdate/index.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
if (!OPENID) throw new Error('no openid in wx context')
|
||||
const patch = event.patch || {}
|
||||
delete patch.openid
|
||||
delete patch._id
|
||||
|
||||
const users = await db.collection('users').where({ openid: OPENID }).limit(1).get()
|
||||
if (!users.data.length) throw new Error('user not found')
|
||||
|
||||
// Setting a real avatar means the user has completed WeChat profile authorization
|
||||
if (patch.avatarUrl) patch.profileCompleted = true
|
||||
|
||||
await db.collection('users').doc(users.data[0]._id).update({
|
||||
data: {
|
||||
...patch,
|
||||
updatedAt: Date.now()
|
||||
}
|
||||
})
|
||||
const user = await db.collection('users').doc(users.data[0]._id).get()
|
||||
return { user: user.data }
|
||||
}
|
||||
|
||||
9
cloudfunctions/profileUpdate/package.json
Normal file
9
cloudfunctions/profileUpdate/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "profileUpdate",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
|
||||
46
cloudfunctions/userList/index.js
Normal file
46
cloudfunctions/userList/index.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const cloud = require('wx-server-sdk')
|
||||
|
||||
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
|
||||
const db = cloud.database()
|
||||
const _ = db.command
|
||||
|
||||
// Discover other users to follow. Optional keyword filters by nickname.
|
||||
exports.main = async event => {
|
||||
const { OPENID } = cloud.getWXContext()
|
||||
if (!OPENID) throw new Error('no openid in wx context')
|
||||
const meRes = await db.collection('users').where({ openid: OPENID }).limit(1).get()
|
||||
if (!meRes.data.length) throw new Error('user not found')
|
||||
const meId = meRes.data[0]._id
|
||||
const keyword = String(event.keyword || '').trim()
|
||||
|
||||
const where = { _id: _.neq(meId) }
|
||||
if (keyword) where.nickname = db.RegExp({ regexp: keyword, options: 'i' })
|
||||
|
||||
const usersRes = await db.collection('users')
|
||||
.where(where)
|
||||
.orderBy('lastLoginAt', 'desc')
|
||||
.limit(30)
|
||||
.get()
|
||||
|
||||
const safeFollows = where2 =>
|
||||
db.collection('follows').where(where2).limit(500).get().catch(() => ({ data: [] }))
|
||||
const [followingRes, followerRes] = await Promise.all([
|
||||
safeFollows({ followerId: meId }),
|
||||
safeFollows({ followeeId: meId })
|
||||
])
|
||||
const followingSet = new Set(followingRes.data.map(f => f.followeeId))
|
||||
const followerSet = new Set(followerRes.data.map(f => f.followerId))
|
||||
|
||||
const list = usersRes.data.map(u => ({
|
||||
id: u._id,
|
||||
name: u.nickname || '用户',
|
||||
handle: u.handle || '',
|
||||
avatarKey: u.avatarKey || 'gradient-avatar-1',
|
||||
avatarUrl: u.avatarUrl || '',
|
||||
bio: u.bio || '',
|
||||
isFollowing: followingSet.has(u._id),
|
||||
isFriend: followingSet.has(u._id) && followerSet.has(u._id)
|
||||
}))
|
||||
|
||||
return { list }
|
||||
}
|
||||
8
cloudfunctions/userList/package.json
Normal file
8
cloudfunctions/userList/package.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "userList",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"wx-server-sdk": "latest"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user