mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-23 03:46:28 -05:00
* Initial * Saves preferences * Include TW * mapModePreferencesToModeList * mapPoolFromPreferences initial * Preference to map pool * Adjust seed * q.looking tests * adds about created map preferences to memento in the correct spot (two preferrers) * Failing test about modes * Mode preferences to memento * Remove old Plus Voting code * Fix seeding * find match by id via kysely * View map memento * Fix up map list generation logic * Mode memento info * Future match modes * Add TODO * Migration number * Migrate test DB * Remove old map pool code * createGroupFromPrevious new * Settings styling * VC to settings * Weapon pool * Add TODOs * Progress * Adjust mode exclusion policy * Progress * Progress * Progress * Notes in progress * Note feedback after submit * Textarea styling * Unskip tests * Note sorting failing test * Private note in Q * Ownerpicksmaps later * New bottom section * Mobile layout initial * Add basic match meta * Tabs initial * Sticky tab * Unseen messages in match page * Front page i18n * Settings i18n * Looking 18n * Chat i18n * Progress * Tranfer weapon pools script * Sticky on match page * Match page translations * i18n - tiers page * Preparing page i18n * Icon * Show add note right after report
252 lines
7.1 KiB
TypeScript
252 lines
7.1 KiB
TypeScript
import { sql } from "kysely";
|
|
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/sqlite";
|
|
import { db } from "~/db/sql";
|
|
import type {
|
|
Tables,
|
|
TablesInsertable,
|
|
UserMapModePreferences,
|
|
} from "~/db/tables";
|
|
import { COMMON_USER_FIELDS } from "~/utils/kysely.server";
|
|
import type { LookingGroupWithInviteCode } from "./q-types";
|
|
import { nanoid } from "nanoid";
|
|
import { INVITE_CODE_LENGTH } from "~/constants";
|
|
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
|
|
|
export function mapModePreferencesByGroupId(groupId: number) {
|
|
return db
|
|
.selectFrom("GroupMember")
|
|
.innerJoin("User", "User.id", "GroupMember.userId")
|
|
.select(["User.id as userId", "User.mapModePreferences as preferences"])
|
|
.where("GroupMember.groupId", "=", groupId)
|
|
.where("User.mapModePreferences", "is not", null)
|
|
.execute() as Promise<
|
|
{ userId: number; preferences: UserMapModePreferences }[]
|
|
>;
|
|
}
|
|
|
|
// groups visible for longer to make development easier
|
|
const SECONDS_TILL_STALE =
|
|
process.env.NODE_ENV === "development" ? 1_000_000 : 1_800;
|
|
|
|
export async function findLookingGroups({
|
|
minGroupSize,
|
|
maxGroupSize,
|
|
ownGroupId,
|
|
includeChatCode = false,
|
|
includeMapModePreferences = false,
|
|
loggedInUserId,
|
|
}: {
|
|
minGroupSize?: number;
|
|
maxGroupSize?: number;
|
|
ownGroupId: number;
|
|
includeChatCode?: boolean;
|
|
includeMapModePreferences?: boolean;
|
|
loggedInUserId?: number;
|
|
}): Promise<LookingGroupWithInviteCode[]> {
|
|
const rows = await db
|
|
.selectFrom("Group")
|
|
.leftJoin("GroupMatch", (join) =>
|
|
join.on((eb) =>
|
|
eb.or([
|
|
eb("GroupMatch.alphaGroupId", "=", eb.ref("Group.id")),
|
|
eb("GroupMatch.bravoGroupId", "=", eb.ref("Group.id")),
|
|
]),
|
|
),
|
|
)
|
|
.select((eb) => [
|
|
"Group.id",
|
|
"Group.createdAt",
|
|
"Group.chatCode",
|
|
"Group.inviteCode",
|
|
jsonArrayFrom(
|
|
eb
|
|
.selectFrom("GroupMember")
|
|
.innerJoin("User", "User.id", "GroupMember.userId")
|
|
.leftJoin("PlusTier", "PlusTier.userId", "GroupMember.userId")
|
|
.select((arrayEb) => [
|
|
...COMMON_USER_FIELDS,
|
|
"User.qWeaponPool as weapons",
|
|
"PlusTier.tier as plusTier",
|
|
"GroupMember.note",
|
|
"User.languages",
|
|
"User.vc",
|
|
jsonObjectFrom(
|
|
eb
|
|
.selectFrom("PrivateUserNote")
|
|
.select([
|
|
"PrivateUserNote.sentiment",
|
|
"PrivateUserNote.text",
|
|
"PrivateUserNote.updatedAt",
|
|
])
|
|
.where("authorId", "=", loggedInUserId ?? -1)
|
|
.where("targetId", "=", arrayEb.ref("User.id")),
|
|
).as("privateNote"),
|
|
sql<
|
|
string | null
|
|
>`IIF(COALESCE("User"."patronTier", 0) >= 2, "User"."css" ->> 'chat', null)`.as(
|
|
"chatNameColor",
|
|
),
|
|
])
|
|
.where("GroupMember.groupId", "=", eb.ref("Group.id"))
|
|
.groupBy("GroupMember.userId"),
|
|
).as("members"),
|
|
])
|
|
.$if(includeMapModePreferences, (qb) =>
|
|
qb.select((eb) =>
|
|
jsonArrayFrom(
|
|
eb
|
|
.selectFrom("GroupMember")
|
|
.innerJoin("User", "User.id", "GroupMember.userId")
|
|
.select("User.mapModePreferences")
|
|
.where("GroupMember.groupId", "=", eb.ref("Group.id"))
|
|
.where("User.mapModePreferences", "is not", null),
|
|
).as("mapModePreferences"),
|
|
),
|
|
)
|
|
.where("Group.status", "=", "ACTIVE")
|
|
.where("GroupMatch.id", "is", null)
|
|
.where((eb) =>
|
|
eb.or([
|
|
eb(
|
|
"Group.latestActionAt",
|
|
">",
|
|
sql`(unixepoch() - ${SECONDS_TILL_STALE})`,
|
|
),
|
|
eb("Group.id", "=", ownGroupId),
|
|
]),
|
|
)
|
|
.execute();
|
|
|
|
// TODO: a bit weird we filter chatCode here but not inviteCode and do some logic about filtering
|
|
return rows
|
|
.map((row) => {
|
|
return {
|
|
...row,
|
|
chatCode: includeChatCode ? row.chatCode : undefined,
|
|
mapModePreferences: row.mapModePreferences?.map(
|
|
(c) => c.mapModePreferences,
|
|
) as NonNullable<Tables["User"]["mapModePreferences"]>[],
|
|
members: row.members.map((member) => {
|
|
return {
|
|
...member,
|
|
languages: member.languages ? member.languages.split(",") : [],
|
|
} as LookingGroupWithInviteCode["members"][number];
|
|
}),
|
|
};
|
|
})
|
|
.filter((group) => {
|
|
if (group.id === ownGroupId) return true;
|
|
if (maxGroupSize && group.members.length > maxGroupSize) return false;
|
|
if (minGroupSize && group.members.length < minGroupSize) return false;
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
type CreateGroupArgs = {
|
|
status: Exclude<Tables["Group"]["status"], "INACTIVE">;
|
|
userId: number;
|
|
};
|
|
export function createGroup(args: CreateGroupArgs) {
|
|
return db.transaction().execute(async (trx) => {
|
|
const createdGroup = await trx
|
|
.insertInto("Group")
|
|
.values({
|
|
inviteCode: nanoid(INVITE_CODE_LENGTH),
|
|
chatCode: nanoid(INVITE_CODE_LENGTH),
|
|
status: args.status,
|
|
})
|
|
.returning("id")
|
|
.executeTakeFirstOrThrow();
|
|
|
|
await trx
|
|
.insertInto("GroupMember")
|
|
.values({
|
|
groupId: createdGroup.id,
|
|
userId: args.userId,
|
|
role: "OWNER",
|
|
})
|
|
.execute();
|
|
|
|
return createdGroup;
|
|
});
|
|
}
|
|
|
|
type CreateGroupFromPreviousGroupArgs = {
|
|
previousGroupId: number;
|
|
members: {
|
|
id: number;
|
|
role: Tables["GroupMember"]["role"];
|
|
}[];
|
|
};
|
|
export async function createGroupFromPrevious(
|
|
args: CreateGroupFromPreviousGroupArgs,
|
|
) {
|
|
return db.transaction().execute(async (trx) => {
|
|
const createdGroup = await trx
|
|
.insertInto("Group")
|
|
.columns(["teamId", "chatCode", "inviteCode", "status"])
|
|
.expression((eb) =>
|
|
eb
|
|
.selectFrom("Group")
|
|
.select((eb) => [
|
|
"Group.teamId",
|
|
"Group.chatCode",
|
|
eb.val(nanoid(INVITE_CODE_LENGTH)).as("inviteCode"),
|
|
eb.val("PREPARING").as("status"),
|
|
])
|
|
.where("Group.id", "=", args.previousGroupId),
|
|
)
|
|
.returning("id")
|
|
.executeTakeFirstOrThrow();
|
|
|
|
await trx
|
|
.insertInto("GroupMember")
|
|
.values(
|
|
args.members.map((member) => ({
|
|
groupId: createdGroup.id,
|
|
userId: member.id,
|
|
role: member.role,
|
|
})),
|
|
)
|
|
.execute();
|
|
|
|
return createdGroup;
|
|
});
|
|
}
|
|
|
|
export function upsertPrivateUserNote(
|
|
args: TablesInsertable["PrivateUserNote"],
|
|
) {
|
|
return db
|
|
.insertInto("PrivateUserNote")
|
|
.values({
|
|
authorId: args.authorId,
|
|
targetId: args.targetId,
|
|
sentiment: args.sentiment,
|
|
text: args.text,
|
|
})
|
|
.onConflict((oc) =>
|
|
oc.columns(["authorId", "targetId"]).doUpdateSet({
|
|
sentiment: args.sentiment,
|
|
text: args.text,
|
|
updatedAt: dateToDatabaseTimestamp(new Date()),
|
|
}),
|
|
)
|
|
.execute();
|
|
}
|
|
|
|
export function deletePrivateUserNote({
|
|
authorId,
|
|
targetId,
|
|
}: {
|
|
authorId: number;
|
|
targetId: number;
|
|
}) {
|
|
return db
|
|
.deleteFrom("PrivateUserNote")
|
|
.where("authorId", "=", authorId)
|
|
.where("targetId", "=", targetId)
|
|
.execute();
|
|
}
|