mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-08 14:41:28 -07:00
moving some more things to domains
This commit is contained in:
@@ -263,3 +263,52 @@ export async function removeFriend(db: D1Database, a: number, b: number): Promis
|
||||
.bind(a, b)
|
||||
.run()
|
||||
}
|
||||
|
||||
/** A per-player relationship flag — each is stored on the player's own side of the row. */
|
||||
export type RelationshipFlag = 'favorited' | 'ignored' | 'muted'
|
||||
|
||||
/**
|
||||
* Set one of `playerId`'s per-side flags (favorited/ignored/muted) on their
|
||||
* relationship with `otherId`. These flags are stored per player, so the write
|
||||
* targets the caller's OWN side of the row — `requester_*` when the caller
|
||||
* initiated the pair, `target_*` otherwise. When the pair has no relationship yet
|
||||
* (you can ignore/mute someone you aren't friends with) a fresh `None` row is
|
||||
* created with the caller as requester. Returns the relationship from `playerId`'s
|
||||
* point of view. The `flag`/side names are a fixed union, so interpolating them
|
||||
* into the SQL is safe (same pattern as the room interaction toggles).
|
||||
*/
|
||||
export async function setRelationshipFlag(
|
||||
db: D1Database,
|
||||
playerId: number,
|
||||
otherId: number,
|
||||
flag: RelationshipFlag,
|
||||
value: boolean
|
||||
): Promise<RelationshipResponse> {
|
||||
const existing = await findPair(db, playerId, otherId)
|
||||
const v = value ? 1 : 0
|
||||
if (!existing) {
|
||||
// New row: the caller is the requester, so the flag lives on the requester side.
|
||||
await db
|
||||
.prepare(
|
||||
`INSERT INTO relationship (requester_id, target_id, relationship_type, requester_${flag})
|
||||
VALUES (?1, ?2, ?3, ?4)`
|
||||
)
|
||||
.bind(playerId, otherId, RelationshipType.None, v)
|
||||
.run()
|
||||
} else {
|
||||
// Update whichever side the caller is on, leaving the other player's flag alone.
|
||||
const side = existing.requester_id === playerId ? 'requester' : 'target'
|
||||
await db
|
||||
.prepare(
|
||||
`UPDATE relationship SET ${side}_${flag} = ?3
|
||||
WHERE (requester_id = ?1 AND target_id = ?2) OR (requester_id = ?2 AND target_id = ?1)`
|
||||
)
|
||||
.bind(playerId, otherId, v)
|
||||
.run()
|
||||
}
|
||||
|
||||
const updated = await findPair(db, playerId, otherId)
|
||||
return updated
|
||||
? toResponse(updated, playerId)
|
||||
: { PlayerID: otherId, RelationshipType: RelationshipType.None, Favorited: 0, Ignored: 0, Muted: 0 }
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
getRelationshipsForPlayer,
|
||||
removeFriend,
|
||||
sendFriendRequest,
|
||||
setRelationshipFlag,
|
||||
} from '../relationships-db'
|
||||
import { authedId, unauthorized } from '../http'
|
||||
|
||||
@@ -88,5 +89,25 @@ export const socialRoutes = new Hono<App>({ strict: false })
|
||||
return c.json(await addFriend(c.env.DB, id, target))
|
||||
})
|
||||
|
||||
// Ignore / mute another player (target arrives as `PlayerId` in the POST body).
|
||||
// These set a per-player flag on the *caller's* side of the relationship row,
|
||||
// creating a bare (None) row when the pair aren't otherwise related — so you can
|
||||
// ignore/mute someone you've never friended. Auth-gated. Returns the resulting
|
||||
// relationship from the caller's point of view.
|
||||
.on(['GET', 'POST'], '/api/relationships/v1/ignore', async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return unauthorized(c)
|
||||
const target = await targetPlayerId(c)
|
||||
if (target === null || target === id) return c.json({ error: 'invalid player id' }, 400)
|
||||
return c.json(await setRelationshipFlag(c.env.DB, id, target, 'ignored', true))
|
||||
})
|
||||
.on(['GET', 'POST'], '/api/relationships/v1/mute', async (c) => {
|
||||
const id = await authedId(c)
|
||||
if (id === null) return unauthorized(c)
|
||||
const target = await targetPlayerId(c)
|
||||
if (target === null || target === id) return c.json({ error: 'invalid player id' }, 400)
|
||||
return c.json(await setRelationshipFlag(c.env.DB, id, target, 'muted', true))
|
||||
})
|
||||
|
||||
.get('/api/messages/v2/get', (c) => c.json([]))
|
||||
.get('/api/messages/v1/favoriteFriendOnlineStatus', (c) => c.json([]))
|
||||
|
||||
@@ -649,6 +649,8 @@ describe('relationships', () => {
|
||||
'/api/relationships/v2/acceptfriendrequest',
|
||||
'/api/relationships/v2/removefriend',
|
||||
'/api/relationships/v2/addfriend',
|
||||
'/api/relationships/v1/ignore',
|
||||
'/api/relationships/v1/mute',
|
||||
]) {
|
||||
const res = await exports.default.fetch(`${ORIGIN}${path}?id=1`)
|
||||
expect(res.status).toBe(401)
|
||||
@@ -693,4 +695,46 @@ describe('relationships', () => {
|
||||
test('a self-targeted request is rejected', async () => {
|
||||
expect((await mutate('/api/relationships/v2/sendfriendrequest', '530', 530)).status).toBe(400)
|
||||
})
|
||||
|
||||
test('v1 ignore/mute set the caller’s own side of the relationship', async () => {
|
||||
type FullRel = { PlayerID: number; RelationshipType: number; Ignored: number; Muted: number }
|
||||
// POST the real client shape: form body `PlayerId=<id>`.
|
||||
const flag = async (path: string, sub: string, playerId: number) =>
|
||||
(await (
|
||||
await exports.default.fetch(`${ORIGIN}${path}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...(await bearer(sub)),
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
},
|
||||
body: `PlayerId=${playerId}`,
|
||||
})
|
||||
).json()) as FullRel
|
||||
|
||||
// 700 ignores 701 with no prior relationship → a bare None row, the caller's side flagged.
|
||||
expect(await flag('/api/relationships/v1/ignore', '700', 701)).toMatchObject({
|
||||
PlayerID: 701,
|
||||
RelationshipType: 0,
|
||||
Ignored: 1,
|
||||
Muted: 0,
|
||||
})
|
||||
// 700 then mutes 701 → same row, mute added, the earlier ignore preserved.
|
||||
expect(await flag('/api/relationships/v1/mute', '700', 701)).toMatchObject({
|
||||
PlayerID: 701,
|
||||
Ignored: 1,
|
||||
Muted: 1,
|
||||
})
|
||||
|
||||
// The tricky case: the caller is the row's TARGET. 710 sends 711 a request
|
||||
// (710 = requester); 711 ignoring 710 must flag the target side, not the requester's.
|
||||
await mutate('/api/relationships/v2/sendfriendrequest', '710', 711)
|
||||
expect(await flag('/api/relationships/v1/ignore', '711', 710)).toMatchObject({
|
||||
PlayerID: 710,
|
||||
RelationshipType: 2, // 711 sees 710's request as Received
|
||||
Ignored: 1,
|
||||
})
|
||||
// 710's own side is untouched — the requester never ignored anyone.
|
||||
const view710 = (await relationships('710')) as unknown as FullRel[]
|
||||
expect(view710).toEqual([expect.objectContaining({ PlayerID: 711, RelationshipType: 1, Ignored: 0 })])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user