163 lines
6.1 KiB
JavaScript
163 lines
6.1 KiB
JavaScript
/**
|
||
* 关系与匹配服务(API_INVENTORY 行 48-55, 60-63)
|
||
*
|
||
* 封装:Like/Pass/Block/取消/解除 + Mutual 列表 + Unmatch + 成功牵手双确认。
|
||
* 关键不变量(AGENTS.md):
|
||
* - 第 4 条:Block 即时禁止互动——前端调用 block 后必须立即更新本地会话与列表。
|
||
* - 第 5 条:红娘不可替用户 Like——本层仅供当前用户调用,不接受 matchmakerId 参数。
|
||
* - 第 6 条:成功牵手必须双确认——success/request 与 success/confirm 分两次调用。
|
||
* 单向意向不泄露:A Like B 但未 Mutual,本层不提供"谁喜欢我"的可识别列表,
|
||
* 仅返回计数 / 是否被 liked 的布尔,详情走 /relations/likers(仅返回匿名化字段)。
|
||
*/
|
||
const api = require('../utils/api');
|
||
|
||
/**
|
||
* 表达喜欢(API_INVENTORY 行 48)
|
||
* @param {string} userId
|
||
* @returns {Promise<import('../types').LikeResult>}
|
||
*/
|
||
function like(userId) {
|
||
const idempotencyKey = `like:${userId}:${Date.now()}`;
|
||
return api.post(`/relations/${encodeURIComponent(userId)}/like`, {}, undefined, idempotencyKey);
|
||
}
|
||
|
||
/**
|
||
* 取消喜欢(API_INVENTORY 行 49)
|
||
* 注意:Mutual 后不可直接取消(后端会返回 MATCH_REQUIRED),前端不应阻断调用。
|
||
* @param {string} userId
|
||
* @returns {Promise<{ relationStatus: import('../types').RelationStatus }>}
|
||
*/
|
||
function unlike(userId) {
|
||
return api.delete(`/relations/${encodeURIComponent(userId)}/like`, {}, undefined, `unlike:${userId}:${Date.now()}`);
|
||
}
|
||
|
||
/**
|
||
* 表示不合适(API_INVENTORY 行 50)
|
||
* Pass 是单向意向,不会通知对方(AGENTS 第 6 条)。
|
||
* @param {string} userId
|
||
* @returns {Promise<{ relationStatus: 'passed' }>}
|
||
*/
|
||
function pass(userId) {
|
||
const idempotencyKey = `pass:${userId}:${Date.now()}`;
|
||
return api.post(`/relations/${encodeURIComponent(userId)}/pass`, {}, undefined, idempotencyKey);
|
||
}
|
||
|
||
/**
|
||
* 拉黑用户(API_INVENTORY 行 51,AGENTS 第 4 条:Block 优先级最高)
|
||
* 前端立即停止与该用户的所有互动,从推荐/搜索/会话列表移除。
|
||
* @param {string} userId
|
||
* @param {{ reason?: string }} [payload]
|
||
* @returns {Promise<{ relationStatus: 'blocked' }>}
|
||
*/
|
||
function block(userId, payload = {}) {
|
||
const idempotencyKey = `block:${userId}:${Date.now()}`;
|
||
return api.post(`/relations/${encodeURIComponent(userId)}/block`, payload, undefined, idempotencyKey);
|
||
}
|
||
|
||
/**
|
||
* 解除拉黑(API_INVENTORY 行 52)
|
||
* @param {string} userId
|
||
* @returns {Promise<{ relationStatus: 'neutral' }>}
|
||
*/
|
||
function unblock(userId) {
|
||
return api.delete(`/relations/${encodeURIComponent(userId)}/block`, {}, undefined, `unblock:${userId}:${Date.now()}`);
|
||
}
|
||
|
||
/**
|
||
* 获取互相喜欢列表(API_INVENTORY 行 53,即 Mutual Matches)
|
||
* @param {{ cursor?: string, pageSize?: number }} params
|
||
* @returns {Promise<{ items: Array<import('../types').Conversation>, nextCursor: string|null }>}
|
||
*/
|
||
function getMutualList(params = {}) {
|
||
return api.get('/relations/mutual', params);
|
||
}
|
||
|
||
/**
|
||
* 获取我喜欢的人列表(API_INVENTORY 行 54)
|
||
* @param {{ cursor?: string, pageSize?: number }} params
|
||
* @returns {Promise<{ items: Array<import('../types').User & { likedAt: number }>, nextCursor: string|null }>}
|
||
*/
|
||
function getLikedByMe(params = {}) {
|
||
return api.get('/relations/liked', params);
|
||
}
|
||
|
||
/**
|
||
* 获取喜欢我的人列表(API_INVENTORY 行 55)
|
||
* 仅返回脱敏字段(昵称/年龄/地区/是否已 Mutual),不泄露对方明示身份。
|
||
* @param {{ cursor?: string, pageSize?: number }} params
|
||
* @returns {Promise<{ items: Array<import('../types').User>, nextCursor: string|null, totalCount: number }>}
|
||
*/
|
||
function getLikers(params = {}) {
|
||
return api.get('/relations/likers', params);
|
||
}
|
||
|
||
/**
|
||
* 获取黑名单列表(REST §86 章 GET /me/blacklist)
|
||
* 拉黑/解除拉黑统一走 Relationship 接口(block/unblock),本接口仅查询。
|
||
* 返回字段已脱敏(不返回对方手机号/身份证等敏感信息)。
|
||
* @param {{ cursor?: string, pageSize?: number }} params
|
||
* @returns {Promise<{ items: Array<{ userId: string, userName: string, avatarUrl?: string, blockedAt: number, reason?: string }>, nextCursor: string|null }>}
|
||
*/
|
||
function getBlockedList(params = {}) {
|
||
return api.get('/me/blacklist', params);
|
||
}
|
||
|
||
/**
|
||
* 获取我的匹配列表(API_INVENTORY 行 60)
|
||
* 与 /relations/mutual 区别:matches 包含牵手状态、服务跟进字段,会话列表专用。
|
||
* @param {{ cursor?: string, pageSize?: number }} params
|
||
* @returns {Promise<{ items: Array<import('../types').Conversation>, nextCursor: string|null }>}
|
||
*/
|
||
function getMatches(params = {}) {
|
||
return api.get('/matches', params);
|
||
}
|
||
|
||
/**
|
||
* 解除匹配(API_INVENTORY 行 61)
|
||
* 解除后双方会话立即停用,AGENTS 第 4 条影响范围仅次于 Block。
|
||
* @param {string} matchId
|
||
* @returns {Promise<{ relationStatus: 'unmatched' }>}
|
||
*/
|
||
function unmatch(matchId) {
|
||
const idempotencyKey = `unmatch:${matchId}:${Date.now()}`;
|
||
return api.post(`/matches/${encodeURIComponent(matchId)}/unmatch`, {}, undefined, idempotencyKey);
|
||
}
|
||
|
||
/**
|
||
* 发起成功牵手请求(API_INVENTORY 行 62,AGENTS 第 6 条:成功牵手必须双确认)
|
||
* 仅发起方调用一次,需对方在有效期内 confirm。
|
||
* @param {string} matchId
|
||
* @returns {Promise<{ successRequestId: string, expiresAt: number, status: 'pending_confirm' }>}
|
||
*/
|
||
function requestSuccess(matchId) {
|
||
const idempotencyKey = `success-req:${matchId}:${Date.now()}`;
|
||
return api.post(`/matches/${encodeURIComponent(matchId)}/success/request`, {}, undefined, idempotencyKey);
|
||
}
|
||
|
||
/**
|
||
* 确认成功牵手(API_INVENTORY 行 63)
|
||
* 仅被请求方调用,确认后双方关系置为 matched_successfully。
|
||
* @param {string} matchId
|
||
* @returns {Promise<{ relationshipStatus: 'matched_successfully', confirmedAt: number }>}
|
||
*/
|
||
function confirmSuccess(matchId) {
|
||
const idempotencyKey = `success-confirm:${matchId}:${Date.now()}`;
|
||
return api.post(`/matches/${encodeURIComponent(matchId)}/success/confirm`, {}, undefined, idempotencyKey);
|
||
}
|
||
|
||
module.exports = {
|
||
like,
|
||
unlike,
|
||
pass,
|
||
block,
|
||
unblock,
|
||
getMutualList,
|
||
getLikedByMe,
|
||
getLikers,
|
||
getBlockedList,
|
||
getMatches,
|
||
unmatch,
|
||
requestSuccess,
|
||
confirmSuccess,
|
||
};
|