mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-26 21:27:48 -05:00
User leaving a group now clears their suggestions, invitations they sent & invitations the group received
This commit is contained in:
13
app/db/seed/factories/SQGroupLikeFactory.ts
Normal file
13
app/db/seed/factories/SQGroupLikeFactory.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as SQGroupRepository from "~/features/sendouq/SQGroupRepository.server";
|
||||
import { defineFactory } from "../core/defineFactory";
|
||||
|
||||
type InsertArgs = Parameters<typeof SQGroupRepository.insertLike>[0];
|
||||
|
||||
/** One group challenging another, sent by a member of the liker group. */
|
||||
export const { create } = defineFactory({
|
||||
insert: async (args: InsertArgs) => {
|
||||
await SQGroupRepository.insertLike(args);
|
||||
|
||||
return args;
|
||||
},
|
||||
});
|
||||
13
app/db/seed/factories/SQGroupSuggestionFactory.ts
Normal file
13
app/db/seed/factories/SQGroupSuggestionFactory.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as SQGroupRepository from "~/features/sendouq/SQGroupRepository.server";
|
||||
import { defineFactory } from "../core/defineFactory";
|
||||
|
||||
type InsertArgs = Parameters<typeof SQGroupRepository.insertSuggestion>[0];
|
||||
|
||||
/** A group flagged as worth a look by one of the suggester group's own members. */
|
||||
export const { create } = defineFactory({
|
||||
insert: async (args: InsertArgs) => {
|
||||
await SQGroupRepository.insertSuggestion(args);
|
||||
|
||||
return args;
|
||||
},
|
||||
});
|
||||
@@ -3,11 +3,14 @@ import { describe, expect, test } from "vitest";
|
||||
import { backdate } from "~/db/seed/core/backdate";
|
||||
import * as GroupMatchContinueVoteFactory from "~/db/seed/factories/GroupMatchContinueVoteFactory";
|
||||
import * as SQGroupFactory from "~/db/seed/factories/SQGroupFactory";
|
||||
import * as SQGroupLikeFactory from "~/db/seed/factories/SQGroupLikeFactory";
|
||||
import * as SQGroupSuggestionFactory from "~/db/seed/factories/SQGroupSuggestionFactory";
|
||||
import * as SQMatchFactory from "~/db/seed/factories/SQMatchFactory";
|
||||
import * as TeamFactory from "~/db/seed/factories/TeamFactory";
|
||||
import * as UserFactory from "~/db/seed/factories/UserFactory";
|
||||
import { db } from "~/db/sql";
|
||||
import * as GroupMatchContinueVoteRepository from "~/features/sendouq-match/GroupMatchContinueVoteRepository.server";
|
||||
import invariant from "~/utils/invariant";
|
||||
import { FULL_GROUP_SIZE } from "./q-constants";
|
||||
import * as SQGroupRepository from "./SQGroupRepository.server";
|
||||
|
||||
@@ -392,4 +395,102 @@ describe("leaveGroup", () => {
|
||||
expect(groupRow).toBeUndefined();
|
||||
expect(await allChatRooms()).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("clears the challenges the group received", async () => {
|
||||
const { ownGroup, challengerGroup } = await setupGroupWithChallenges();
|
||||
|
||||
await SQGroupRepository.leaveGroup(ownGroup.leaverId);
|
||||
|
||||
const likes = await SQGroupRepository.findAllLikesByGroupId(ownGroup.id);
|
||||
expect(likes.received).toHaveLength(0);
|
||||
expect(
|
||||
await SQGroupRepository.findAllLikesByGroupId(challengerGroup.id),
|
||||
).toMatchObject({ given: [] });
|
||||
});
|
||||
|
||||
test("clears the challenges the leaver sent but not those of the members who stay", async () => {
|
||||
const { ownGroup, leaverTargetGroup, stayerTargetGroup } =
|
||||
await setupGroupWithChallenges();
|
||||
|
||||
await SQGroupRepository.leaveGroup(ownGroup.leaverId);
|
||||
|
||||
const likes = await SQGroupRepository.findAllLikesByGroupId(ownGroup.id);
|
||||
expect(likes.given.map((like) => like.groupId)).toEqual([
|
||||
stayerTargetGroup.id,
|
||||
]);
|
||||
expect(
|
||||
await SQGroupRepository.findAllLikesByGroupId(leaverTargetGroup.id),
|
||||
).toMatchObject({ received: [] });
|
||||
});
|
||||
|
||||
test("clears the suggestions the leaver made but not those of the members who stay", async () => {
|
||||
const { ownGroup, stayerTargetGroup } = await setupGroupWithChallenges();
|
||||
|
||||
await SQGroupRepository.leaveGroup(ownGroup.leaverId);
|
||||
|
||||
const suggestions = await SQGroupRepository.findAllSuggestionsByGroupId(
|
||||
ownGroup.id,
|
||||
);
|
||||
expect(suggestions.map((suggestion) => suggestion.groupId)).toEqual([
|
||||
stayerTargetGroup.id,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* A group of two whose first member is about to leave, with a challenge received, one sent by
|
||||
* each of its members and a suggestion made by each of them.
|
||||
*/
|
||||
const setupGroupWithChallenges = async () => {
|
||||
const [leaver, stayer] = await UserFactory.createMany(2);
|
||||
invariant(leaver && stayer, "Expected two users");
|
||||
|
||||
const ownGroup = await SQGroupFactory.create({
|
||||
memberUserIds: [leaver.id, stayer.id],
|
||||
});
|
||||
|
||||
const [challengerGroup, leaverTargetGroup, stayerTargetGroup] =
|
||||
await createSoloGroups(3);
|
||||
|
||||
await SQGroupLikeFactory.create({
|
||||
likerGroupId: challengerGroup.id,
|
||||
targetGroupId: ownGroup.id,
|
||||
createdByUserId: challengerGroup.memberUserIds[0]!,
|
||||
});
|
||||
await SQGroupLikeFactory.create({
|
||||
likerGroupId: ownGroup.id,
|
||||
targetGroupId: leaverTargetGroup.id,
|
||||
createdByUserId: leaver.id,
|
||||
});
|
||||
await SQGroupLikeFactory.create({
|
||||
likerGroupId: ownGroup.id,
|
||||
targetGroupId: stayerTargetGroup.id,
|
||||
createdByUserId: stayer.id,
|
||||
});
|
||||
|
||||
await SQGroupSuggestionFactory.create({
|
||||
suggesterGroupId: ownGroup.id,
|
||||
targetGroupId: challengerGroup.id,
|
||||
createdByUserId: leaver.id,
|
||||
});
|
||||
await SQGroupSuggestionFactory.create({
|
||||
suggesterGroupId: ownGroup.id,
|
||||
targetGroupId: stayerTargetGroup.id,
|
||||
createdByUserId: stayer.id,
|
||||
});
|
||||
|
||||
return {
|
||||
ownGroup: { ...ownGroup, leaverId: leaver.id },
|
||||
challengerGroup,
|
||||
leaverTargetGroup,
|
||||
stayerTargetGroup,
|
||||
};
|
||||
};
|
||||
|
||||
const createSoloGroups = async (count: number) => {
|
||||
const users = await UserFactory.createMany(count);
|
||||
|
||||
return Promise.all(
|
||||
users.map((user) => SQGroupFactory.create({ memberUserIds: [user.id] })),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -325,6 +325,31 @@ export async function deleteLikesAndSuggestionsByGroupId(
|
||||
await deleteSuggestionsByGroupId(groupId, trx);
|
||||
}
|
||||
|
||||
/** Clears what the departing member is responsible for: every challenge the group received (the roster the other group challenged is gone) plus the challenges and suggestions that member made themselves. */
|
||||
async function deleteLikesAndSuggestionsOnLeave(
|
||||
{ groupId, userId }: { groupId: number; userId: number },
|
||||
trx: Transaction<DB>,
|
||||
) {
|
||||
await trx
|
||||
.deleteFrom("GroupLike")
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
eb("GroupLike.targetGroupId", "=", groupId),
|
||||
eb.and([
|
||||
eb("GroupLike.likerGroupId", "=", groupId),
|
||||
eb("GroupLike.createdByUserId", "=", userId),
|
||||
]),
|
||||
]),
|
||||
)
|
||||
.execute();
|
||||
|
||||
await trx
|
||||
.deleteFrom("GroupSuggestion")
|
||||
.where("GroupSuggestion.suggesterGroupId", "=", groupId)
|
||||
.where("GroupSuggestion.createdByUserId", "=", userId)
|
||||
.execute();
|
||||
}
|
||||
|
||||
export function morphGroups({
|
||||
survivingGroupId,
|
||||
otherGroupId,
|
||||
@@ -836,7 +861,7 @@ export function deleteAllLikesByGroupId(groupId: number) {
|
||||
return db.transaction().execute((trx) => deleteLikesByGroupId(groupId, trx));
|
||||
}
|
||||
|
||||
/** Removes the user from their group (deleting it if they were last). A ready check the group was in is called off; returns the ids of the groups that were in it. */
|
||||
/** Removes the user from their group (deleting it if they were last). A ready check the group was in is called off; returns the ids of the groups that were in it. Challenges the group received and challenges/suggestions the leaver made are cleared. */
|
||||
export function leaveGroup(userId: number) {
|
||||
return db.transaction().execute(async (trx) => {
|
||||
const userGroup = await trx
|
||||
@@ -908,6 +933,11 @@ export function leaveGroup(userId: number) {
|
||||
throw new SendouQError("Can't leave group when already in a match");
|
||||
}
|
||||
|
||||
await deleteLikesAndSuggestionsOnLeave(
|
||||
{ groupId: userGroup.id, userId },
|
||||
trx,
|
||||
);
|
||||
|
||||
await syncTeamId(userGroup.id, trx);
|
||||
|
||||
return { abortedReadyCheckGroupIds };
|
||||
|
||||
5
changelog/2026-09-06-sendouq-leave-clears-challenges.md
Normal file
5
changelog/2026-09-06-sendouq-leave-clears-challenges.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
navItem: sendouq
|
||||
type: feature
|
||||
---
|
||||
User leaving a group now clears their suggestions, invitations they sent & invitations the group received
|
||||
Reference in New Issue
Block a user