mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-07-30 15:53:59 -05:00
More consistent date columns
This commit is contained in:
parent
4b48a45109
commit
4cf2003e3c
|
|
@ -63,7 +63,7 @@ export function EventsList({
|
|||
|
||||
const groupedEvents = events.reduce<Record<string, typeof events>>(
|
||||
(acc, event) => {
|
||||
const key = getDayKey(event.startTime);
|
||||
const key = getDayKey(event.startsAt);
|
||||
if (!acc[key]) {
|
||||
acc[key] = [];
|
||||
}
|
||||
|
|
@ -79,7 +79,7 @@ export function EventsList({
|
|||
<>
|
||||
{dayKeys.map((dayKey) => {
|
||||
const dayEvents = groupedEvents[dayKey];
|
||||
const firstDate = new Date(dayEvents[0].startTime * 1000);
|
||||
const firstDate = new Date(dayEvents[0].startsAt * 1000);
|
||||
|
||||
return (
|
||||
<div key={dayKey}>
|
||||
|
|
@ -89,7 +89,7 @@ export function EventsList({
|
|||
key={`${event.type}-${event.id}`}
|
||||
to={event.url}
|
||||
imageUrl={event.logoUrl ?? undefined}
|
||||
subtitle={timeFormatter.format(event.startTime)}
|
||||
subtitle={timeFormatter.format(event.startsAt)}
|
||||
onClick={onClick}
|
||||
>
|
||||
{event.scrimStatus === "booked"
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ function TournamentItem({ item }: { item: TournamentSearchItem }) {
|
|||
<div className={searchSelectStyles.itemTextsContainer}>
|
||||
<span>{item.name}</span>
|
||||
<LocaleTime
|
||||
date={item.startTime}
|
||||
date={item.startsAt}
|
||||
options={{
|
||||
day: "numeric",
|
||||
month: "numeric",
|
||||
|
|
|
|||
|
|
@ -509,7 +509,7 @@ function ResultItem({ result }: { result: SearchResult }) {
|
|||
<div className={styles.resultTexts}>
|
||||
<span className={styles.resultName}>{result.name}</span>
|
||||
<LocaleTime
|
||||
date={result.startTime}
|
||||
date={result.startsAt}
|
||||
options={{ day: "numeric", month: "long", year: "numeric" }}
|
||||
className={styles.resultSecondary}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ export function Layout({
|
|||
imageUrl={event.logoUrl ?? undefined}
|
||||
subtitle={
|
||||
isHydrated ? (
|
||||
formatRelativeDate(event.startTime)
|
||||
formatRelativeDate(event.startsAt)
|
||||
) : (
|
||||
<span className="invisible">Placeholder</span>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ function finalizedBracket() {
|
|||
// CalendarEventDate — recent start time (within 7-day spoiler window)
|
||||
sql
|
||||
.prepare(
|
||||
`insert into "CalendarEventDate" ("eventId", "startTime")
|
||||
`insert into "CalendarEventDate" ("eventId", "startsAt")
|
||||
values ($eventId, $startTime)`,
|
||||
)
|
||||
.run({
|
||||
|
|
@ -564,7 +564,7 @@ function abDivisionsTournament() {
|
|||
|
||||
sql
|
||||
.prepare(
|
||||
`insert into "CalendarEventDate" ("eventId", "startTime")
|
||||
`insert into "CalendarEventDate" ("eventId", "startsAt")
|
||||
values ($eventId, $startTime)`,
|
||||
)
|
||||
.run({
|
||||
|
|
@ -705,7 +705,7 @@ function fixAdminId() {
|
|||
function makeAdminPatron() {
|
||||
sql
|
||||
.prepare(
|
||||
`update "User" set "patronTier" = 2, "patronSince" = 1674663454 where id = 1`,
|
||||
`update "User" set "patronTier" = 2, "patronStartedAt" = 1674663454 where id = 1`,
|
||||
)
|
||||
.run();
|
||||
}
|
||||
|
|
@ -1117,7 +1117,7 @@ async function lastMonthsVoting() {
|
|||
year,
|
||||
score: 1,
|
||||
tier: idToPlusTier(id),
|
||||
validAfter: dateToDatabaseTimestamp(fiveMinutesAgo),
|
||||
becomesValidAt: dateToDatabaseTimestamp(fiveMinutesAgo),
|
||||
votedId: id,
|
||||
});
|
||||
}
|
||||
|
|
@ -1129,7 +1129,7 @@ async function lastMonthsVoting() {
|
|||
year,
|
||||
score: -1,
|
||||
tier: idToPlusTier(id),
|
||||
validAfter: dateToDatabaseTimestamp(fiveMinutesAgo),
|
||||
becomesValidAt: dateToDatabaseTimestamp(fiveMinutesAgo),
|
||||
votedId: id,
|
||||
});
|
||||
}
|
||||
|
|
@ -1267,19 +1267,19 @@ function patrons() {
|
|||
) as number[];
|
||||
|
||||
const givePatronStm = sql.prepare(
|
||||
`update user set "patronTier" = $patronTier, "patronSince" = $patronSince where id = $id`,
|
||||
`update user set "patronTier" = $patronTier, "patronStartedAt" = $patronStartedAt where id = $id`,
|
||||
);
|
||||
for (const id of userIds) {
|
||||
givePatronStm.run({
|
||||
id,
|
||||
patronSince: dateToDatabaseTimestamp(faker.date.past()),
|
||||
patronStartedAt: dateToDatabaseTimestamp(faker.date.past()),
|
||||
patronTier: faker.helpers.arrayElement([1, 1, 2, 2, 2, 3, 3, 4]),
|
||||
});
|
||||
}
|
||||
|
||||
givePatronStm.run({
|
||||
id: ADMIN_ID,
|
||||
patronSince: dateToDatabaseTimestamp(faker.date.past()),
|
||||
patronStartedAt: dateToDatabaseTimestamp(faker.date.past()),
|
||||
patronTier: 2,
|
||||
});
|
||||
|
||||
|
|
@ -1372,7 +1372,7 @@ function calendarEvents() {
|
|||
`
|
||||
insert into "CalendarEventDate" (
|
||||
"eventId",
|
||||
"startTime"
|
||||
"startsAt"
|
||||
) values (
|
||||
$eventId,
|
||||
$startTime
|
||||
|
|
@ -1392,7 +1392,7 @@ function calendarEvents() {
|
|||
`
|
||||
insert into "CalendarEventDate" (
|
||||
"eventId",
|
||||
"startTime"
|
||||
"startsAt"
|
||||
) values (
|
||||
$eventId,
|
||||
$startTime
|
||||
|
|
@ -1441,7 +1441,7 @@ async function calendarEventResults() {
|
|||
`select "CalendarEvent"."id"
|
||||
from "CalendarEvent"
|
||||
join "CalendarEventDate" on "CalendarEventDate"."eventId" = "CalendarEvent"."id"
|
||||
where "CalendarEventDate"."startTime" < $startTime`,
|
||||
where "CalendarEventDate"."startsAt" < $startTime`,
|
||||
)
|
||||
.all({ startTime: dateToDatabaseTimestamp(new Date()) }) as any[]
|
||||
).map((r) => r.id),
|
||||
|
|
@ -1729,7 +1729,7 @@ function calendarEventWithToTools(
|
|||
`
|
||||
insert into "CalendarEventDate" (
|
||||
"eventId",
|
||||
"startTime"
|
||||
"startsAt"
|
||||
) values (
|
||||
$eventId,
|
||||
$startTime
|
||||
|
|
@ -3306,8 +3306,8 @@ async function scrimPosts() {
|
|||
// scrim with admin on the ALPHA side and N-ZAP on the BRAVO side.
|
||||
const adminVsNzapAt = date(true);
|
||||
const adminVsNzapPostId = await ScrimPostRepository.insert({
|
||||
at: adminVsNzapAt,
|
||||
rangeEnd: null,
|
||||
startsAt: adminVsNzapAt,
|
||||
rangeEndsAt: null,
|
||||
isScheduledForFuture: true,
|
||||
teamId: null,
|
||||
text: null,
|
||||
|
|
@ -3329,8 +3329,8 @@ async function scrimPosts() {
|
|||
const atTime = date();
|
||||
const hasRangeEnd = Math.random() > 0.5;
|
||||
await ScrimPostRepository.insert({
|
||||
at: atTime,
|
||||
rangeEnd: hasRangeEnd
|
||||
startsAt: atTime,
|
||||
rangeEndsAt: hasRangeEnd
|
||||
? dateToDatabaseTimestamp(
|
||||
add(databaseTimestampToDate(atTime), {
|
||||
hours: faker.helpers.rangeToNumber({ min: 1, max: 3 }),
|
||||
|
|
@ -3355,7 +3355,7 @@ async function scrimPosts() {
|
|||
|
||||
const adminPostAtTime = date(true); // admin's scrim is always at least 1 hour in the future
|
||||
const adminPostId = await ScrimPostRepository.insert({
|
||||
at: adminPostAtTime,
|
||||
startsAt: adminPostAtTime,
|
||||
isScheduledForFuture: true,
|
||||
text:
|
||||
faker.number.float(1) > 0.5
|
||||
|
|
@ -3777,7 +3777,7 @@ function splatoonRotations() {
|
|||
sql
|
||||
.prepare(
|
||||
`
|
||||
insert into "SplatoonRotation" ("type", "mode", "stageId1", "stageId2", "startTime", "endTime")
|
||||
insert into "SplatoonRotation" ("type", "mode", "stageId1", "stageId2", "startsAt", "endsAt")
|
||||
values ($type, $mode, $stageId1, $stageId2, $startTime, $endTime)
|
||||
`,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ export interface CalendarEventBadge {
|
|||
export interface CalendarEventDate {
|
||||
eventId: number;
|
||||
id: GeneratedAlways<number>;
|
||||
startTime: number;
|
||||
startsAt: number;
|
||||
}
|
||||
|
||||
export interface CalendarEventResultPlayer {
|
||||
|
|
@ -329,7 +329,7 @@ export interface LFGPost {
|
|||
plusTierVisibility: number | null;
|
||||
languages: string | null;
|
||||
updatedAt: Generated<number>;
|
||||
createdAt: GeneratedAlways<number>;
|
||||
createdAt: Generated<number>;
|
||||
}
|
||||
|
||||
export interface MapPoolMap {
|
||||
|
|
@ -362,7 +362,7 @@ export interface PlayerResult {
|
|||
|
||||
export interface PlusSuggestion {
|
||||
authorId: number;
|
||||
createdAt: GeneratedAlways<number>;
|
||||
createdAt: Generated<number>;
|
||||
id: GeneratedAlways<number>;
|
||||
month: number;
|
||||
suggestedId: number;
|
||||
|
|
@ -382,7 +382,8 @@ export interface PlusVote {
|
|||
month: number;
|
||||
score: number;
|
||||
tier: number;
|
||||
validAfter: number;
|
||||
/** When the vote stops being secret and starts counting towards the results i.e. the end of the voting range. */
|
||||
becomesValidAt: number;
|
||||
votedId: number;
|
||||
year: number;
|
||||
}
|
||||
|
|
@ -417,6 +418,7 @@ export interface Skill {
|
|||
season: number;
|
||||
tournamentId: number | null;
|
||||
userId: number | null;
|
||||
/** Can be null because we did not always save this. */
|
||||
createdAt: number | null;
|
||||
}
|
||||
|
||||
|
|
@ -521,7 +523,7 @@ export interface TournamentMatchPickBanEvent {
|
|||
matchId: number;
|
||||
authorId: number | null;
|
||||
number: number;
|
||||
createdAt: GeneratedAlways<number>;
|
||||
createdAt: Generated<number>;
|
||||
}
|
||||
|
||||
export interface TournamentMatchGameResult {
|
||||
|
|
@ -585,8 +587,7 @@ export interface TournamentStage {
|
|||
settings: JSONColumnType<StageSettings>;
|
||||
tournamentId: number;
|
||||
type: (typeof TOURNAMENT_STAGE_TYPES)[number];
|
||||
// not Generated<> because SQLite doesn't allow altering tables to add columns with default values :(
|
||||
createdAt: number | null;
|
||||
createdAt: Generated<number>;
|
||||
}
|
||||
|
||||
/** Tournament sub post, shown in a list of subs available for teams to pick from. */
|
||||
|
|
@ -681,7 +682,7 @@ export interface TournamentAuditLog {
|
|||
/** References {@link TournamentTeamHistory.id} so the team name stays resolvable after the team is hard-deleted. */
|
||||
tournamentTeamHistoryId: number | null;
|
||||
metadata: JSONColumnTypeNullable<TournamentAuditLogMetadata>;
|
||||
createdAt: number;
|
||||
createdAt: Generated<number>;
|
||||
}
|
||||
|
||||
export interface TournamentOrganization {
|
||||
|
|
@ -769,7 +770,8 @@ export interface UnvalidatedVideo {
|
|||
title: string;
|
||||
type: string;
|
||||
validatedAt: number | null;
|
||||
youtubeDate: number;
|
||||
/** When the video was published on YouTube. Day precision only, stored as noon UTC of that day. */
|
||||
youtubePublishedAt: number;
|
||||
youtubeId: string;
|
||||
}
|
||||
|
||||
|
|
@ -806,9 +808,9 @@ export interface User {
|
|||
languages: string | null;
|
||||
motionSens: number | null;
|
||||
pronouns: JSONColumnTypeNullable<Pronouns>;
|
||||
patronSince: number | null;
|
||||
patronStartedAt: number | null;
|
||||
patronTier: number | null;
|
||||
patronTill: number | null;
|
||||
patronExpiresAt: number | null;
|
||||
showDiscordUniqueName: Generated<DBBoolean>;
|
||||
stickSens: number | null;
|
||||
twitch: string | null;
|
||||
|
|
@ -886,7 +888,7 @@ export interface UserFriendCode {
|
|||
friendCode: string;
|
||||
userId: number;
|
||||
submitterUserId: number;
|
||||
createdAt: GeneratedAlways<number>;
|
||||
createdAt: Generated<number>;
|
||||
}
|
||||
|
||||
export interface UserWidget {
|
||||
|
|
@ -899,7 +901,7 @@ export interface ApiToken {
|
|||
userId: number;
|
||||
token: string;
|
||||
type: Generated<ApiTokenType>;
|
||||
createdAt: GeneratedAlways<number>;
|
||||
createdAt: Generated<number>;
|
||||
}
|
||||
|
||||
export interface LiveStream {
|
||||
|
|
@ -922,7 +924,7 @@ export interface ExternalStream {
|
|||
name: string;
|
||||
url: string;
|
||||
avatarImgId: number | null;
|
||||
startTime: number;
|
||||
startsAt: number;
|
||||
createdAt: Generated<number>;
|
||||
}
|
||||
|
||||
|
|
@ -943,7 +945,7 @@ export interface BanLog {
|
|||
banned: number | null;
|
||||
bannedReason: string | null;
|
||||
bannedByUserId: number;
|
||||
createdAt: GeneratedAlways<number>;
|
||||
createdAt: Generated<number>;
|
||||
}
|
||||
|
||||
export interface ModNote {
|
||||
|
|
@ -951,7 +953,7 @@ export interface ModNote {
|
|||
userId: number;
|
||||
authorId: number;
|
||||
text: string;
|
||||
createdAt: GeneratedAlways<number>;
|
||||
createdAt: Generated<number>;
|
||||
isDeleted: Generated<DBBoolean>;
|
||||
}
|
||||
|
||||
|
|
@ -974,7 +976,8 @@ export interface Video {
|
|||
type: "SCRIM" | "TOURNAMENT" | "MATCHMAKING" | "CAST" | "SENDOUQ";
|
||||
/** Never `null` in practice, the view filters unvalidated rows out. */
|
||||
validatedAt: number | null;
|
||||
youtubeDate: number;
|
||||
/** When the video was published on YouTube. Day precision only, stored as noon UTC of that day. */
|
||||
youtubePublishedAt: number;
|
||||
youtubeId: string;
|
||||
}
|
||||
|
||||
|
|
@ -1014,9 +1017,9 @@ export interface XRankPlacement {
|
|||
export interface ScrimPost {
|
||||
id: GeneratedAlways<number>;
|
||||
/** When is the scrim scheduled to happen */
|
||||
at: number;
|
||||
/** Optional end of time range indicating team accepts scrims starting between at and rangeEnd */
|
||||
rangeEnd: number | null;
|
||||
startsAt: number;
|
||||
/** Optional end of time range indicating team accepts scrims starting between startsAt and rangeEndsAt */
|
||||
rangeEndsAt: number | null;
|
||||
/** Highest LUTI div accepted */
|
||||
maxDiv: number | null;
|
||||
/** Lowest LUTI div accepted */
|
||||
|
|
@ -1043,7 +1046,7 @@ export interface ScrimPost {
|
|||
maps: "SZ" | "ALL" | "RANKED" | null;
|
||||
/** If set, specifies the maps of a tournament to play */
|
||||
mapsTournamentId: number | null;
|
||||
createdAt: GeneratedAlways<number>;
|
||||
createdAt: Generated<number>;
|
||||
updatedAt: Generated<number>;
|
||||
}
|
||||
|
||||
|
|
@ -1080,10 +1083,10 @@ export interface ScrimPostRequest {
|
|||
scrimPostId: number;
|
||||
teamId: number | null;
|
||||
message: string | null;
|
||||
/** Specific time selected by requester (required when post has rangeEnd) */
|
||||
at: number | null;
|
||||
/** Specific time selected by requester (required when post has rangeEndsAt) */
|
||||
startsAt: number | null;
|
||||
isAccepted: Generated<DBBoolean>;
|
||||
createdAt: GeneratedAlways<number>;
|
||||
createdAt: Generated<number>;
|
||||
}
|
||||
|
||||
export interface ScrimPostRequestUser {
|
||||
|
|
@ -1097,7 +1100,7 @@ export interface Association {
|
|||
id: GeneratedAlways<number>;
|
||||
name: string;
|
||||
inviteCode: string;
|
||||
createdAt: GeneratedAlways<number>;
|
||||
createdAt: Generated<number>;
|
||||
}
|
||||
|
||||
export interface AssociationMember {
|
||||
|
|
@ -1111,7 +1114,7 @@ export interface Notification {
|
|||
type: NotificationValue["type"];
|
||||
meta: JSONColumnTypeNullable<Record<string, number | string>>;
|
||||
pictureUrl: string | null;
|
||||
createdAt: GeneratedAlways<number>;
|
||||
createdAt: Generated<number>;
|
||||
}
|
||||
|
||||
export interface NotificationUser {
|
||||
|
|
@ -1133,8 +1136,8 @@ export interface SplatoonRotation {
|
|||
mode: string;
|
||||
stageId1: number;
|
||||
stageId2: number;
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
startsAt: number;
|
||||
endsAt: number;
|
||||
}
|
||||
|
||||
export type Tables = { [P in keyof DB]: Selectable<DB[P]> };
|
||||
|
|
|
|||
|
|
@ -291,15 +291,15 @@ export async function linkUserAndPlayer({
|
|||
export function forcePatron(args: {
|
||||
id: number;
|
||||
patronTier: Tables["User"]["patronTier"];
|
||||
patronSince: Date;
|
||||
patronTill: Date;
|
||||
patronStartedAt: Date;
|
||||
patronExpiresAt: Date;
|
||||
}) {
|
||||
return db
|
||||
.updateTable("User")
|
||||
.set({
|
||||
patronTier: args.patronTier,
|
||||
patronSince: dateToDatabaseTimestamp(args.patronSince),
|
||||
patronTill: dateToDatabaseTimestamp(args.patronTill),
|
||||
patronStartedAt: dateToDatabaseTimestamp(args.patronStartedAt),
|
||||
patronExpiresAt: dateToDatabaseTimestamp(args.patronExpiresAt),
|
||||
})
|
||||
.where("User.id", "=", args.id)
|
||||
.execute();
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const RETENTION_SECONDS = 24 * 60 * 60;
|
|||
export function insert(
|
||||
args: Pick<
|
||||
TablesInsertable["ExternalStream"],
|
||||
"name" | "url" | "avatarImgId" | "startTime"
|
||||
"name" | "url" | "avatarImgId" | "startsAt"
|
||||
>,
|
||||
) {
|
||||
return db.insertInto("ExternalStream").values(args).execute();
|
||||
|
|
@ -36,12 +36,12 @@ export function all() {
|
|||
"ExternalStream.id",
|
||||
"ExternalStream.name",
|
||||
"ExternalStream.url",
|
||||
"ExternalStream.startTime",
|
||||
"ExternalStream.startsAt",
|
||||
concatUserSubmittedImagePrefix(eb.ref("UserSubmittedImage.url")).as(
|
||||
"avatarUrl",
|
||||
),
|
||||
])
|
||||
.orderBy("ExternalStream.startTime", "asc")
|
||||
.orderBy("ExternalStream.startsAt", "asc")
|
||||
.execute();
|
||||
}
|
||||
|
||||
|
|
@ -58,13 +58,13 @@ export function forSidebar() {
|
|||
"ExternalStream.id",
|
||||
"ExternalStream.name",
|
||||
"ExternalStream.url",
|
||||
"ExternalStream.startTime",
|
||||
"ExternalStream.startsAt",
|
||||
concatUserSubmittedImagePrefix(eb.ref("UserSubmittedImage.url")).as(
|
||||
"avatarUrl",
|
||||
),
|
||||
])
|
||||
.where(
|
||||
"ExternalStream.startTime",
|
||||
"ExternalStream.startsAt",
|
||||
">=",
|
||||
databaseTimestampNow() - SIDEBAR_VISIBLE_SECONDS,
|
||||
)
|
||||
|
|
@ -75,6 +75,6 @@ export function forSidebar() {
|
|||
export function deleteOld() {
|
||||
return db
|
||||
.deleteFrom("ExternalStream")
|
||||
.where("startTime", "<", databaseTimestampNow() - RETENTION_SECONDS)
|
||||
.where("startsAt", "<", databaseTimestampNow() - RETENTION_SECONDS)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,9 +74,9 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||
|
||||
await AdminRepository.forcePatron({
|
||||
id: data.user,
|
||||
patronSince: new Date(),
|
||||
patronStartedAt: new Date(),
|
||||
patronTier: data.patronTier,
|
||||
patronTill: new Date(data.patronTill),
|
||||
patronExpiresAt: new Date(data.patronExpiresAt),
|
||||
});
|
||||
|
||||
message = "Patron status updated";
|
||||
|
|
@ -207,7 +207,7 @@ export const adminActionSchema = z.union([
|
|||
_action: _action("FORCE_PATRON"),
|
||||
user: z.preprocess(actualNumber, z.number().positive()),
|
||||
patronTier: z.preprocess(actualNumber, z.number()),
|
||||
patronTill: z.string(),
|
||||
patronExpiresAt: z.string(),
|
||||
}),
|
||||
z.object({
|
||||
_action: _action("VIDEO_ADDER"),
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||
name: data.name,
|
||||
url: data.url,
|
||||
avatarImgId: data.avatar,
|
||||
startTime: dateToDatabaseTimestamp(data.startTime),
|
||||
startsAt: dateToDatabaseTimestamp(data.startTime),
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ function ExternalStreamList() {
|
|||
</Link>
|
||||
<span className="text-xs text-lighter">
|
||||
<LocaleTime
|
||||
date={stream.startTime}
|
||||
date={stream.startsAt}
|
||||
inline
|
||||
options={{
|
||||
month: "numeric",
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const voteArgs = ({
|
|||
authorId,
|
||||
month,
|
||||
tier: 1,
|
||||
validAfter: dateToDatabaseTimestamp(new Date("2021-12-11T00:00:00.000Z")),
|
||||
becomesValidAt: dateToDatabaseTimestamp(new Date("2021-12-11T00:00:00.000Z")),
|
||||
year,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -370,7 +370,7 @@ function ForcePatron() {
|
|||
|
||||
<div className="flex-same-size">
|
||||
<label>Patron till</label>
|
||||
<input name="patronTill" type="date" />
|
||||
<input name="patronExpiresAt" type="date" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="stack horizontal md">
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ function adjustSeedDatesToCurrent(variation: SeedVariation) {
|
|||
REG_OPEN_TOURNAMENT_IDS.includes(tournamentId);
|
||||
|
||||
sql
|
||||
.prepare(`UPDATE "CalendarEventDate" SET startTime = ? WHERE eventId = ?`)
|
||||
.prepare(`UPDATE "CalendarEventDate" SET startsAt = ? WHERE eventId = ?`)
|
||||
.run(isRegOpen ? halfAnHourFromNow : oneHourAgo, id);
|
||||
}
|
||||
|
||||
|
|
@ -103,17 +103,17 @@ function adjustSeedDatesToCurrent(variation: SeedVariation) {
|
|||
const scrimTimeOffset = now - SEED_REFERENCE_TIMESTAMP;
|
||||
sql
|
||||
.prepare(
|
||||
`UPDATE "ScrimPost" SET "at" = "at" + ?, "createdAt" = "createdAt" + ?`,
|
||||
`UPDATE "ScrimPost" SET "startsAt" = "startsAt" + ?, "createdAt" = "createdAt" + ?`,
|
||||
)
|
||||
.run(scrimTimeOffset, scrimTimeOffset);
|
||||
sql
|
||||
.prepare(
|
||||
`UPDATE "ScrimPost" SET "rangeEnd" = "rangeEnd" + ? WHERE "rangeEnd" IS NOT NULL`,
|
||||
`UPDATE "ScrimPost" SET "rangeEndsAt" = "rangeEndsAt" + ? WHERE "rangeEndsAt" IS NOT NULL`,
|
||||
)
|
||||
.run(scrimTimeOffset);
|
||||
sql
|
||||
.prepare(
|
||||
`UPDATE "ScrimPostRequest" SET "at" = "at" + ? WHERE "at" IS NOT NULL`,
|
||||
`UPDATE "ScrimPostRequest" SET "startsAt" = "startsAt" + ? WHERE "startsAt" IS NOT NULL`,
|
||||
)
|
||||
.run(scrimTimeOffset);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
|
|||
|
||||
const result: GetCalendarWeekResponse = events.map((event) => ({
|
||||
name: event.name,
|
||||
startTime: databaseTimestampToDate(event.startTime).toISOString(),
|
||||
startTime: databaseTimestampToDate(event.startsAt).toISOString(),
|
||||
tournamentId: event.tournamentId,
|
||||
tournamentUrl: event.tournamentId
|
||||
? `https://sendou.ink/to/${event.tournamentId}/brackets`
|
||||
|
|
@ -48,19 +48,15 @@ function fetchEventsOfWeek(args: { week: number; year: number }) {
|
|||
.select([
|
||||
"Tournament.id as tournamentId",
|
||||
"CalendarEvent.name",
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
])
|
||||
.where(
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
">=",
|
||||
dateToDatabaseTimestamp(startTime),
|
||||
)
|
||||
.where(
|
||||
"CalendarEventDate.startTime",
|
||||
"<=",
|
||||
dateToDatabaseTimestamp(endTime),
|
||||
)
|
||||
.where("CalendarEventDate.startsAt", "<=", dateToDatabaseTimestamp(endTime))
|
||||
.where("CalendarEvent.hidden", "=", 0)
|
||||
.orderBy("CalendarEventDate.startTime", "asc")
|
||||
.orderBy("CalendarEventDate.startsAt", "asc")
|
||||
.execute();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
|
|||
.select(({ eb, exists, selectFrom }) => [
|
||||
"CalendarEvent.name",
|
||||
"CalendarEvent.organizationId",
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
"Tournament.settings",
|
||||
exists(
|
||||
selectFrom("TournamentResult")
|
||||
|
|
@ -61,7 +61,7 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
|
|||
|
||||
const result: GetTournamentResponse = {
|
||||
name: tournament.name,
|
||||
startTime: databaseTimestampToDate(tournament.startTime).toISOString(),
|
||||
startTime: databaseTimestampToDate(tournament.startsAt).toISOString(),
|
||||
url: `https://sendou.ink/to/${id}/brackets`,
|
||||
logoUrl: tournament.logoUrl,
|
||||
teams: {
|
||||
|
|
|
|||
|
|
@ -46,8 +46,8 @@ describe("Permission logic consistency between allApiTokens and checkUserHasApiA
|
|||
await AdminRepository.forcePatron({
|
||||
id: 1,
|
||||
patronTier: 2,
|
||||
patronSince: new Date(),
|
||||
patronTill: add(new Date(), { months: 3 }),
|
||||
patronStartedAt: new Date(),
|
||||
patronExpiresAt: add(new Date(), { months: 3 }),
|
||||
});
|
||||
|
||||
await ApiRepository.generateToken(1, "read");
|
||||
|
|
@ -64,8 +64,8 @@ describe("Permission logic consistency between allApiTokens and checkUserHasApiA
|
|||
await AdminRepository.forcePatron({
|
||||
id: 1,
|
||||
patronTier: 1,
|
||||
patronSince: new Date(),
|
||||
patronTill: add(new Date(), { months: 3 }),
|
||||
patronStartedAt: new Date(),
|
||||
patronExpiresAt: add(new Date(), { months: 3 }),
|
||||
});
|
||||
|
||||
await ApiRepository.generateToken(1, "read");
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ const withTeamsCount = (
|
|||
.where((eb) =>
|
||||
eb.or([
|
||||
eb("TournamentTeamCheckIn.checkedInAt", "is not", null),
|
||||
eb("CalendarEventDate.startTime", ">", databaseTimestampNow()),
|
||||
eb("CalendarEventDate.startsAt", ">", databaseTimestampNow()),
|
||||
]),
|
||||
)
|
||||
.select(({ fn }) => [fn.countAll<number>().as("teamsCount")]);
|
||||
|
|
@ -176,10 +176,10 @@ function findAllBetweenTwoTimestampsQuery({
|
|||
"Tournament.tier",
|
||||
"CalendarEvent.name",
|
||||
"CalendarEvent.tags",
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
// events get grouped to their closest :00 or :30 so for example users can't make their event start at :59 to make it show at the top
|
||||
sql<number>`(("CalendarEventDate"."startTime" + 900) / 1800) * 1800`.as(
|
||||
"normalizedStartTime",
|
||||
sql<number>`(("CalendarEventDate"."startsAt" + 900) / 1800) * 1800`.as(
|
||||
"normalizedStartsAt",
|
||||
),
|
||||
withOrganization(eb).as("organization"),
|
||||
withTeamsCount(eb).as("teamsCount"),
|
||||
|
|
@ -205,15 +205,11 @@ function findAllBetweenTwoTimestampsQuery({
|
|||
])
|
||||
.where("CalendarEvent.hidden", "=", 0)
|
||||
.where(
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
">=",
|
||||
dateToDatabaseTimestamp(startTime),
|
||||
)
|
||||
.where(
|
||||
"CalendarEventDate.startTime",
|
||||
"<=",
|
||||
dateToDatabaseTimestamp(endTime),
|
||||
)
|
||||
.where("CalendarEventDate.startsAt", "<=", dateToDatabaseTimestamp(endTime))
|
||||
.$narrowType<{ teamsCount: NotNull }>()
|
||||
.execute();
|
||||
}
|
||||
|
|
@ -224,14 +220,14 @@ function findAllBetweenTwoTimestampsMapped(
|
|||
at: number;
|
||||
events: Array<CalendarEvent>;
|
||||
}> {
|
||||
const mapped: Array<CalendarEvent & { startTime: number }> = rows.map(
|
||||
const mapped: Array<CalendarEvent & { startsAt: number }> = rows.map(
|
||||
(row) => {
|
||||
const tags = row.tags
|
||||
? (row.tags.split(",") as CalendarEvent["tags"])
|
||||
: [];
|
||||
|
||||
const isPastEvent =
|
||||
databaseTimestampToDate(row.startTime) < sub(new Date(), { days: 1 });
|
||||
databaseTimestampToDate(row.startsAt) < sub(new Date(), { days: 1 });
|
||||
const tentativeTier =
|
||||
row.tier === null &&
|
||||
row.organizationId !== null &&
|
||||
|
|
@ -241,7 +237,7 @@ function findAllBetweenTwoTimestampsMapped(
|
|||
: null;
|
||||
|
||||
return {
|
||||
at: databaseTimestampToJavascriptTimestamp(row.startTime),
|
||||
at: databaseTimestampToJavascriptTimestamp(row.startsAt),
|
||||
type: "calendar",
|
||||
id: row.eventId,
|
||||
url: row.tournamentId
|
||||
|
|
@ -265,11 +261,11 @@ function findAllBetweenTwoTimestampsMapped(
|
|||
: null,
|
||||
badges: row.badges,
|
||||
logoUrl: row.logoUrl,
|
||||
startTime: row.normalizedStartTime,
|
||||
startsAt: row.normalizedStartsAt,
|
||||
isRanked: row.tournamentSettings
|
||||
? tournamentIsRanked({
|
||||
isSetAsRanked: row.tournamentSettings.isRanked,
|
||||
startTime: databaseTimestampToDate(row.startTime),
|
||||
startsAt: databaseTimestampToDate(row.startsAt),
|
||||
minMembersPerTeam: row.tournamentSettings.minMembersPerTeam ?? 4,
|
||||
isTest: row.tournamentSettings.isTest ?? false,
|
||||
})
|
||||
|
|
@ -280,7 +276,7 @@ function findAllBetweenTwoTimestampsMapped(
|
|||
},
|
||||
);
|
||||
|
||||
const grouped = R.groupBy(mapped, (row) => row.startTime);
|
||||
const grouped = R.groupBy(mapped, (row) => row.startsAt);
|
||||
const dates = Object.keys(grouped)
|
||||
.map((dbTimestamp) => ({
|
||||
at: databaseTimestampToDate(Number(dbTimestamp)).getTime(),
|
||||
|
|
@ -327,7 +323,7 @@ export async function findById(
|
|||
"CalendarEvent.avatarImgId",
|
||||
"Tournament.mapPickingStyle",
|
||||
"User.id as authorId",
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
"CalendarEventDate.eventId",
|
||||
"User.username",
|
||||
"User.discordId",
|
||||
|
|
@ -338,7 +334,7 @@ export async function findById(
|
|||
),
|
||||
])
|
||||
.where("CalendarEvent.id", "=", id)
|
||||
.orderBy("CalendarEventDate.startTime", "asc")
|
||||
.orderBy("CalendarEventDate.startsAt", "asc")
|
||||
.execute();
|
||||
|
||||
if (!firstRow) return null;
|
||||
|
|
@ -346,8 +342,8 @@ export async function findById(
|
|||
return {
|
||||
...firstRow,
|
||||
tags: tagsArray(firstRow),
|
||||
startTimes: [firstRow, ...rest].map((row) => row.startTime),
|
||||
startTime: undefined,
|
||||
startTimes: [firstRow, ...rest].map((row) => row.startsAt),
|
||||
startsAt: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -363,7 +359,7 @@ export async function findRecentTournamentsByAuthorId(authorId: number) {
|
|||
.select([
|
||||
"CalendarEvent.id",
|
||||
"CalendarEvent.name",
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
])
|
||||
.where("CalendarEvent.authorId", "=", authorId)
|
||||
.orderBy("CalendarEvent.id", "desc")
|
||||
|
|
@ -425,7 +421,7 @@ type CreateArgs = Pick<
|
|||
| "bracketUrl"
|
||||
| "organizationId"
|
||||
> & {
|
||||
startTimes: Array<Tables["CalendarEventDate"]["startTime"]>;
|
||||
startTimes: Array<Tables["CalendarEventDate"]["startsAt"]>;
|
||||
badges: Array<Tables["CalendarEventBadge"]["badgeId"]>;
|
||||
mapPoolMaps?: Array<Pick<Tables["MapPoolMap"], "mode" | "stageId">>;
|
||||
isFullTournament: boolean;
|
||||
|
|
@ -750,7 +746,7 @@ function createDatesInTrx({
|
|||
}) {
|
||||
return trx
|
||||
.insertInto("CalendarEventDate")
|
||||
.values(startTimes.map((startTime) => ({ startTime, eventId })))
|
||||
.values(startTimes.map((startsAt) => ({ startsAt, eventId })))
|
||||
.execute();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export interface CalendarEvent extends CommonEvent {
|
|||
|
||||
export interface ShowcaseCalendarEvent extends CommonEvent {
|
||||
type: "showcase";
|
||||
startTime: number;
|
||||
startsAt: number;
|
||||
/** Id of the organization the event belongs to, if any */
|
||||
organizationId: number | null;
|
||||
/** Tournament is hidden from the public (test tournament) */
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export function TournamentCard({
|
|||
const isHostedOnSendouInk = typeof tournament.isRanked === "boolean";
|
||||
|
||||
const startDate = isShowcase
|
||||
? databaseTimestampToDate(tournament.startTime)
|
||||
? databaseTimestampToDate(tournament.startsAt)
|
||||
: null;
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -29,19 +29,19 @@ export const loader = async () => {
|
|||
|
||||
const registered = tournamentsData.participatingFor
|
||||
.map(tournamentToSidebarEvent)
|
||||
.sort((a, b) => a.startTime - b.startTime);
|
||||
.sort((a, b) => a.startsAt - b.startsAt);
|
||||
|
||||
const hosting = tournamentsData.organizingFor
|
||||
.map(tournamentToSidebarEvent)
|
||||
.sort((a, b) => a.startTime - b.startTime);
|
||||
.sort((a, b) => a.startsAt - b.startsAt);
|
||||
|
||||
const scrims = scrimsData
|
||||
.map(scrimToSidebarEvent)
|
||||
.sort((a, b) => a.startTime - b.startTime);
|
||||
.sort((a, b) => a.startsAt - b.startsAt);
|
||||
|
||||
const saved = savedTournaments
|
||||
.map(tournamentToSidebarEvent)
|
||||
.sort((a, b) => a.startTime - b.startTime);
|
||||
.sort((a, b) => a.startsAt - b.startsAt);
|
||||
|
||||
const userOrganizationIds = new Set(userOrganizations.map((org) => org.id));
|
||||
const organization = upcomingTournaments
|
||||
|
|
@ -52,7 +52,7 @@ export const loader = async () => {
|
|||
userOrganizationIds.has(tournament.organizationId),
|
||||
)
|
||||
.map(tournamentToSidebarEvent)
|
||||
.sort((a, b) => a.startTime - b.startTime);
|
||||
.sort((a, b) => a.startsAt - b.startsAt);
|
||||
|
||||
return { registered, hosting, scrims, saved, organization };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -142,7 +142,7 @@ function useDefaultValues() {
|
|||
|
||||
const regClosesAt: RegClosesAtOption = tournamentCtx?.settings.regClosesAt
|
||||
? datesToRegClosesAt({
|
||||
startTime: databaseTimestampToDate(tournamentCtx.startTime),
|
||||
startTime: databaseTimestampToDate(tournamentCtx.startsAt),
|
||||
regClosesAt: databaseTimestampToDate(
|
||||
tournamentCtx.settings.regClosesAt,
|
||||
),
|
||||
|
|
@ -243,7 +243,7 @@ function TemplateTournamentForm() {
|
|||
<option value="">Select a template</option>
|
||||
{recentTournaments.map((event) => (
|
||||
<option key={event.id} value={event.id}>
|
||||
{event.name} ({formatter.format(event.startTime) ?? ""})
|
||||
{event.name} ({formatter.format(event.startsAt) ?? ""})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export function getLiveTournamentStreams(): SidebarStream[] {
|
|||
imageUrl: tournament.ctx.logoUrl,
|
||||
url: tournamentStreamsPage(tournament.ctx.id),
|
||||
subtitle: deriveCurrentRound(tournament),
|
||||
startsAt: dateToDatabaseTimestamp(tournament.ctx.startTime),
|
||||
startsAt: dateToDatabaseTimestamp(tournament.ctx.startsAt),
|
||||
tier: tournament.ctx.tier,
|
||||
membersPerTeam: tournament.minMembersPerTeam,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ function withLfgJoins<QB extends SelectQueryBuilder<any, any, any>>(qb: QB) {
|
|||
...commonUserSelect(eb),
|
||||
"CalendarEvent.name as tournamentName",
|
||||
"TournamentTeam.tournamentId",
|
||||
"CalendarEventDate.startTime as tournamentStartTime",
|
||||
"CalendarEventDate.startsAt as tournamentStartTime",
|
||||
sql<
|
||||
number | null
|
||||
>`(SELECT COUNT(*) FROM "TournamentTeamMember" "ttm" WHERE "ttm"."tournamentTeamId" = "TournamentTeam"."id")`.as(
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export function SplatoonRotations() {
|
|||
const nowUnixLive = useNowUnix(data.now);
|
||||
|
||||
const allInThePast = data.rotations.every(
|
||||
(rotation) => rotation.endTime <= nowUnixLive,
|
||||
(rotation) => rotation.endsAt <= nowUnixLive,
|
||||
);
|
||||
if (allInThePast) return null;
|
||||
|
||||
|
|
@ -63,8 +63,8 @@ export function SplatoonRotations() {
|
|||
if (activeFilter !== "ALL" && rotation.mode !== activeFilter) continue;
|
||||
|
||||
const isCurrent =
|
||||
rotation.startTime <= nowUnixLive && rotation.endTime > nowUnixLive;
|
||||
const isNext = rotation.startTime > nowUnixLive;
|
||||
rotation.startsAt <= nowUnixLive && rotation.endsAt > nowUnixLive;
|
||||
const isNext = rotation.startsAt > nowUnixLive;
|
||||
|
||||
if (!isCurrent && !isNext) continue;
|
||||
|
||||
|
|
@ -173,8 +173,8 @@ function RotationCard({
|
|||
const progress = current
|
||||
? rotationProgress(
|
||||
now,
|
||||
databaseTimestampToDate(current.startTime),
|
||||
databaseTimestampToDate(current.endTime),
|
||||
databaseTimestampToDate(current.startsAt),
|
||||
databaseTimestampToDate(current.endsAt),
|
||||
)
|
||||
: null;
|
||||
const displayRotation = current ?? next;
|
||||
|
|
@ -195,7 +195,7 @@ function RotationCard({
|
|||
style={{ width: `${progress * 100}%` }}
|
||||
/>
|
||||
<span className={styles.rotationCardProgressText}>
|
||||
{formatDistanceToNow(current.endTime)}
|
||||
{formatDistanceToNow(current.endsAt)}
|
||||
</span>
|
||||
</div>
|
||||
) : null}
|
||||
|
|
@ -208,7 +208,7 @@ function RotationCard({
|
|||
>
|
||||
<span className={styles.rotationCardProgressText}>
|
||||
<NextLabel
|
||||
startTime={databaseTimestampToDate(next.startTime)}
|
||||
startTime={databaseTimestampToDate(next.startsAt)}
|
||||
now={now}
|
||||
/>
|
||||
</span>
|
||||
|
|
@ -233,11 +233,11 @@ function RotationCard({
|
|||
<div className={styles.rotationCardNext}>
|
||||
{shownNext ? (
|
||||
<div className={styles.rotationCardNextInfo}>
|
||||
{current && shownNext.startTime === current.endTime ? (
|
||||
{current && shownNext.startsAt === current.endsAt ? (
|
||||
t("front:rotations.nextLabel")
|
||||
) : (
|
||||
<NextLabel
|
||||
startTime={databaseTimestampToDate(shownNext.startTime)}
|
||||
startTime={databaseTimestampToDate(shownNext.startsAt)}
|
||||
now={now}
|
||||
compact
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ function deleteExtraResults(tournaments: ShowcaseCalendarEvent[]) {
|
|||
(tournament) =>
|
||||
tournament.firstPlacers.length === 0 &&
|
||||
!tournament.isFinalized &&
|
||||
tournament.startTime > threeDaysAgo,
|
||||
tournament.startsAt > threeDaysAgo,
|
||||
);
|
||||
|
||||
const rankedResults = tournaments
|
||||
|
|
@ -214,7 +214,7 @@ function deleteExtraResults(tournaments: ShowcaseCalendarEvent[]) {
|
|||
|
||||
return {
|
||||
results: [...rankedResultsToKeep, ...nonRankedResultsToKeep].sort(
|
||||
(a, b) => b.startTime - a.startTime,
|
||||
(a, b) => b.startsAt - a.startsAt,
|
||||
),
|
||||
upcoming: nonResults,
|
||||
};
|
||||
|
|
@ -225,8 +225,8 @@ function resolveShowcaseTournaments(
|
|||
): ShowcaseCalendarEvent[] {
|
||||
const happeningDuringNextWeek = tournaments.filter(
|
||||
(tournament) =>
|
||||
tournament.startTime > databaseTimestampSixHoursAgo() &&
|
||||
tournament.startTime < databaseTimestampWeekFromNow(),
|
||||
tournament.startsAt > databaseTimestampSixHoursAgo() &&
|
||||
tournament.startsAt < databaseTimestampWeekFromNow(),
|
||||
);
|
||||
const sorted = happeningDuringNextWeek.sort(
|
||||
(a, b) => b.teamsCount - a.teamsCount,
|
||||
|
|
@ -238,7 +238,7 @@ function resolveShowcaseTournaments(
|
|||
.filter((tournament) => !tournament.isRanked)
|
||||
.slice(0, 6 - ranked.length);
|
||||
|
||||
return [...ranked, ...nonRanked].sort((a, b) => a.startTime - b.startTime);
|
||||
return [...ranked, ...nonRanked].sort((a, b) => a.startsAt - b.startsAt);
|
||||
}
|
||||
|
||||
async function tournamentsToParticipationInfoMap(
|
||||
|
|
@ -302,7 +302,7 @@ function mapTournamentFromDB(
|
|||
authorId: tournament.authorId,
|
||||
organizationId: tournament.organizationId,
|
||||
name: tournament.name,
|
||||
startTime: tournament.startTime,
|
||||
startsAt: tournament.startsAt,
|
||||
teamsCount: tournament.teamsCount,
|
||||
logoUrl: tournament.logoUrl,
|
||||
organization: tournament.organization
|
||||
|
|
@ -313,7 +313,7 @@ function mapTournamentFromDB(
|
|||
: null,
|
||||
isRanked: tournamentIsRanked({
|
||||
isSetAsRanked: tournament.settings.isRanked,
|
||||
startTime: databaseTimestampToDate(tournament.startTime),
|
||||
startsAt: databaseTimestampToDate(tournament.startsAt),
|
||||
minMembersPerTeam: tournament.settings.minMembersPerTeam ?? 4,
|
||||
isTest: tournament.settings.isTest ?? false,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ export async function seasonProgressionByUserId({
|
|||
)
|
||||
.select(({ fn }) => [
|
||||
fn.max("Skill.ordinal").as("ordinal"),
|
||||
sql<string>`date(coalesce("Skill"."createdAt", "GroupMatch"."createdAt", "CalendarEventDate"."startTime"), 'unixepoch')`.as(
|
||||
sql<string>`date(coalesce("Skill"."createdAt", "GroupMatch"."createdAt", "CalendarEventDate"."startsAt"), 'unixepoch')`.as(
|
||||
"date",
|
||||
),
|
||||
])
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export async function allPlusTiersFromLatestVoting() {
|
|||
const latestVoting = await db
|
||||
.selectFrom("PlusVote")
|
||||
.select(["PlusVote.year", "PlusVote.month"])
|
||||
.where("PlusVote.validAfter", "<", sql<number>`strftime('%s', 'now')`)
|
||||
.where("PlusVote.becomesValidAt", "<", sql<number>`strftime('%s', 'now')`)
|
||||
.orderBy("PlusVote.year", "desc")
|
||||
.orderBy("PlusVote.month", "desc")
|
||||
.limit(1)
|
||||
|
|
@ -201,7 +201,13 @@ export async function hasVoted(args: {
|
|||
|
||||
export type UpsertManyPlusVotesArgs = Pick<
|
||||
TablesInsertable["PlusVote"],
|
||||
"month" | "year" | "tier" | "authorId" | "votedId" | "score" | "validAfter"
|
||||
| "month"
|
||||
| "year"
|
||||
| "tier"
|
||||
| "authorId"
|
||||
| "votedId"
|
||||
| "score"
|
||||
| "becomesValidAt"
|
||||
>[];
|
||||
export function upsertMany(votes: UpsertManyPlusVotesArgs) {
|
||||
const firstVote = votes[0];
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export const action: ActionFunction = async ({ request }) => {
|
|||
month,
|
||||
year,
|
||||
tier: user.plusTier!, // no clue why i couldn't make narrowing the type down above work
|
||||
validAfter: dateToDatabaseTimestamp(votingRange.endDate),
|
||||
becomesValidAt: dateToDatabaseTimestamp(votingRange.endDate),
|
||||
})),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -16,17 +16,17 @@ const WINDOW = {
|
|||
};
|
||||
|
||||
function insertPost({
|
||||
at,
|
||||
rangeEnd = null,
|
||||
startsAt,
|
||||
rangeEndsAt = null,
|
||||
users,
|
||||
}: {
|
||||
at: Date;
|
||||
rangeEnd?: Date | null;
|
||||
startsAt: Date;
|
||||
rangeEndsAt?: Date | null;
|
||||
users: Array<{ userId: number; isOwner: 0 | 1 }>;
|
||||
}) {
|
||||
return ScrimPostRepository.insert({
|
||||
at: dbTs(at),
|
||||
rangeEnd: rangeEnd ? dbTs(rangeEnd) : null,
|
||||
startsAt: dbTs(startsAt),
|
||||
rangeEndsAt: rangeEndsAt ? dbTs(rangeEndsAt) : null,
|
||||
maxDiv: null,
|
||||
minDiv: null,
|
||||
teamId: null,
|
||||
|
|
@ -51,7 +51,7 @@ describe("findPendingOverlapsForUsers", () => {
|
|||
|
||||
test("returns a specific-time pending post in window with its member ids", async () => {
|
||||
const postId = await insertPost({
|
||||
at: BOOKED_AT,
|
||||
startsAt: BOOKED_AT,
|
||||
users: [
|
||||
{ userId: 1, isOwner: 1 },
|
||||
{ userId: 2, isOwner: 0 },
|
||||
|
|
@ -71,8 +71,8 @@ describe("findPendingOverlapsForUsers", () => {
|
|||
|
||||
test("returns a ranged post whose interval overlaps the window even if its start is outside", async () => {
|
||||
const postId = await insertPost({
|
||||
at: sub(BOOKED_AT, { hours: 2 }),
|
||||
rangeEnd: BOOKED_AT,
|
||||
startsAt: sub(BOOKED_AT, { hours: 2 }),
|
||||
rangeEndsAt: BOOKED_AT,
|
||||
users: [{ userId: 1, isOwner: 1 }],
|
||||
});
|
||||
|
||||
|
|
@ -87,8 +87,8 @@ describe("findPendingOverlapsForUsers", () => {
|
|||
|
||||
test("does not return a ranged post whose interval does not overlap the window", async () => {
|
||||
await insertPost({
|
||||
at: sub(BOOKED_AT, { hours: 5 }),
|
||||
rangeEnd: sub(BOOKED_AT, { hours: 3 }),
|
||||
startsAt: sub(BOOKED_AT, { hours: 5 }),
|
||||
rangeEndsAt: sub(BOOKED_AT, { hours: 3 }),
|
||||
users: [{ userId: 1, isOwner: 1 }],
|
||||
});
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ describe("findPendingOverlapsForUsers", () => {
|
|||
|
||||
test("excludes the just-booked post even when it overlaps", async () => {
|
||||
const postId = await insertPost({
|
||||
at: BOOKED_AT,
|
||||
startsAt: BOOKED_AT,
|
||||
users: [{ userId: 1, isOwner: 1 }],
|
||||
});
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ describe("findPendingOverlapsForUsers", () => {
|
|||
|
||||
test("does not return posts that involve none of the given users", async () => {
|
||||
await insertPost({
|
||||
at: BOOKED_AT,
|
||||
startsAt: BOOKED_AT,
|
||||
users: [{ userId: 3, isOwner: 1 }],
|
||||
});
|
||||
|
||||
|
|
@ -133,14 +133,14 @@ describe("findPendingOverlapsForUsers", () => {
|
|||
|
||||
test("excludes already-accepted (booked) posts", async () => {
|
||||
const postId = await insertPost({
|
||||
at: BOOKED_AT,
|
||||
startsAt: BOOKED_AT,
|
||||
users: [{ userId: 1, isOwner: 1 }],
|
||||
});
|
||||
await ScrimPostRepository.insertRequest({
|
||||
scrimPostId: postId,
|
||||
teamId: null,
|
||||
message: null,
|
||||
at: dbTs(BOOKED_AT),
|
||||
startsAt: dbTs(BOOKED_AT),
|
||||
users: [{ userId: 3, isOwner: 1 }],
|
||||
});
|
||||
const post = await ScrimPostRepository.findById(postId);
|
||||
|
|
@ -159,14 +159,14 @@ describe("findPendingOverlapsForUsers", () => {
|
|||
|
||||
test("returns pending request ids whose effective time falls in the window", async () => {
|
||||
const postId = await insertPost({
|
||||
at: add(BOOKED_AT, { hours: 3 }),
|
||||
startsAt: add(BOOKED_AT, { hours: 3 }),
|
||||
users: [{ userId: 3, isOwner: 1 }],
|
||||
});
|
||||
await ScrimPostRepository.insertRequest({
|
||||
scrimPostId: postId,
|
||||
teamId: null,
|
||||
message: null,
|
||||
at: dbTs(BOOKED_AT),
|
||||
startsAt: dbTs(BOOKED_AT),
|
||||
users: [{ userId: 1, isOwner: 1 }],
|
||||
});
|
||||
const post = await ScrimPostRepository.findById(postId);
|
||||
|
|
@ -185,14 +185,14 @@ describe("findPendingOverlapsForUsers", () => {
|
|||
|
||||
test("does not return pending requests whose effective time is outside the window", async () => {
|
||||
const postId = await insertPost({
|
||||
at: add(BOOKED_AT, { hours: 3 }),
|
||||
startsAt: add(BOOKED_AT, { hours: 3 }),
|
||||
users: [{ userId: 3, isOwner: 1 }],
|
||||
});
|
||||
await ScrimPostRepository.insertRequest({
|
||||
scrimPostId: postId,
|
||||
teamId: null,
|
||||
message: null,
|
||||
at: dbTs(add(BOOKED_AT, { hours: 3 })),
|
||||
startsAt: dbTs(add(BOOKED_AT, { hours: 3 })),
|
||||
users: [{ userId: 1, isOwner: 1 }],
|
||||
});
|
||||
|
||||
|
|
@ -218,14 +218,14 @@ describe("findUserScrims", () => {
|
|||
|
||||
test("passed-over requester does not see the scrim booked between the post and another team", async () => {
|
||||
const postId = await insertPost({
|
||||
at: BOOKED_AT,
|
||||
startsAt: BOOKED_AT,
|
||||
users: [{ userId: 3, isOwner: 1 }],
|
||||
});
|
||||
await ScrimPostRepository.insertRequest({
|
||||
scrimPostId: postId,
|
||||
teamId: null,
|
||||
message: null,
|
||||
at: null,
|
||||
startsAt: null,
|
||||
users: [
|
||||
{ userId: 1, isOwner: 1 },
|
||||
{ userId: 2, isOwner: 0 },
|
||||
|
|
@ -235,7 +235,7 @@ describe("findUserScrims", () => {
|
|||
scrimPostId: postId,
|
||||
teamId: null,
|
||||
message: null,
|
||||
at: null,
|
||||
startsAt: null,
|
||||
users: [
|
||||
{ userId: 4, isOwner: 1 },
|
||||
{ userId: 5, isOwner: 0 },
|
||||
|
|
@ -256,21 +256,21 @@ describe("findUserScrims", () => {
|
|||
|
||||
test("post owner sees the accepted request's side as the opponent", async () => {
|
||||
const postId = await insertPost({
|
||||
at: BOOKED_AT,
|
||||
startsAt: BOOKED_AT,
|
||||
users: [{ userId: 3, isOwner: 1 }],
|
||||
});
|
||||
await ScrimPostRepository.insertRequest({
|
||||
scrimPostId: postId,
|
||||
teamId: null,
|
||||
message: null,
|
||||
at: null,
|
||||
startsAt: null,
|
||||
users: [{ userId: 1, isOwner: 1 }],
|
||||
});
|
||||
await ScrimPostRepository.insertRequest({
|
||||
scrimPostId: postId,
|
||||
teamId: null,
|
||||
message: null,
|
||||
at: null,
|
||||
startsAt: null,
|
||||
users: [{ userId: 4, isOwner: 1 }],
|
||||
});
|
||||
|
||||
|
|
@ -309,13 +309,13 @@ describe("insertRequest", () => {
|
|||
scrimPostId,
|
||||
teamId,
|
||||
message: null,
|
||||
at: null,
|
||||
startsAt: null,
|
||||
users: [{ userId, isOwner: 1 }],
|
||||
});
|
||||
|
||||
test("throws if the team already has a request for the post", async () => {
|
||||
const postId = await insertPost({
|
||||
at: BOOKED_AT,
|
||||
startsAt: BOOKED_AT,
|
||||
users: [{ userId: 1, isOwner: 1 }],
|
||||
});
|
||||
const team = await TeamRepository.create({
|
||||
|
|
@ -340,11 +340,11 @@ describe("insertRequest", () => {
|
|||
|
||||
test("allows the team to request another post", async () => {
|
||||
const postId = await insertPost({
|
||||
at: BOOKED_AT,
|
||||
startsAt: BOOKED_AT,
|
||||
users: [{ userId: 1, isOwner: 1 }],
|
||||
});
|
||||
const otherPostId = await insertPost({
|
||||
at: BOOKED_AT,
|
||||
startsAt: BOOKED_AT,
|
||||
users: [{ userId: 4, isOwner: 1 }],
|
||||
});
|
||||
const team = await TeamRepository.create({
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ import { getPostRequestCensor, parseLutiDiv } from "./scrims-utils";
|
|||
|
||||
type InsertArgs = Pick<
|
||||
TablesInsertable["ScrimPost"],
|
||||
| "at"
|
||||
| "rangeEnd"
|
||||
| "startsAt"
|
||||
| "rangeEndsAt"
|
||||
| "maxDiv"
|
||||
| "minDiv"
|
||||
| "teamId"
|
||||
|
|
@ -49,8 +49,8 @@ export function insert(args: InsertArgs) {
|
|||
const newPost = await trx
|
||||
.insertInto("ScrimPost")
|
||||
.values({
|
||||
at: args.at,
|
||||
rangeEnd: args.rangeEnd,
|
||||
startsAt: args.startsAt,
|
||||
rangeEndsAt: args.rangeEndsAt,
|
||||
maxDiv: args.maxDiv,
|
||||
minDiv: args.minDiv,
|
||||
teamId: args.teamId,
|
||||
|
|
@ -76,7 +76,7 @@ export function insert(args: InsertArgs) {
|
|||
|
||||
type InsertRequestArgs = Pick<
|
||||
Insertable<Tables["ScrimPostRequest"]>,
|
||||
"scrimPostId" | "teamId" | "message" | "at"
|
||||
"scrimPostId" | "teamId" | "message" | "startsAt"
|
||||
> & {
|
||||
users: Array<
|
||||
Pick<Insertable<Tables["ScrimPostRequestUser"]>, "userId" | "isOwner">
|
||||
|
|
@ -113,7 +113,7 @@ export function insertRequest(args: InsertRequestArgs) {
|
|||
scrimPostId: args.scrimPostId,
|
||||
teamId: args.teamId,
|
||||
message: args.message,
|
||||
at: args.at,
|
||||
startsAt: args.startsAt,
|
||||
})
|
||||
.returning("id")
|
||||
.executeTakeFirstOrThrow();
|
||||
|
|
@ -146,8 +146,8 @@ const baseFindQuery = db
|
|||
)
|
||||
.select((eb) => [
|
||||
"ScrimPost.id",
|
||||
"ScrimPost.at",
|
||||
"ScrimPost.rangeEnd",
|
||||
"ScrimPost.startsAt",
|
||||
"ScrimPost.rangeEndsAt",
|
||||
"ScrimPost.createdAt",
|
||||
"ScrimPost.visibility",
|
||||
"ScrimPost.maxDiv",
|
||||
|
|
@ -197,7 +197,7 @@ const baseFindQuery = db
|
|||
"ScrimPostRequest.isAccepted",
|
||||
"ScrimPostRequest.createdAt",
|
||||
"ScrimPostRequest.message",
|
||||
"ScrimPostRequest.at",
|
||||
"ScrimPostRequest.startsAt",
|
||||
jsonBuildObject({
|
||||
name: innerEb.ref("Team.name"),
|
||||
customUrl: innerEb.ref("Team.customUrl"),
|
||||
|
|
@ -229,8 +229,8 @@ function findMany() {
|
|||
const min = sub(new Date(), { hours: 3 });
|
||||
|
||||
return baseFindQuery
|
||||
.orderBy("at", "asc")
|
||||
.where("ScrimPost.at", ">=", dateToDatabaseTimestamp(min))
|
||||
.orderBy("startsAt", "asc")
|
||||
.where("ScrimPost.startsAt", ">=", dateToDatabaseTimestamp(min))
|
||||
.execute();
|
||||
}
|
||||
|
||||
|
|
@ -277,8 +277,8 @@ const mapDBRowToScrimPost = (
|
|||
|
||||
const result = {
|
||||
id: row.id,
|
||||
at: row.at,
|
||||
rangeEnd: row.rangeEnd,
|
||||
startsAt: row.startsAt,
|
||||
rangeEndsAt: row.rangeEndsAt,
|
||||
createdAt: row.createdAt,
|
||||
visibility: row.visibility,
|
||||
text: row.text,
|
||||
|
|
@ -309,7 +309,7 @@ const mapDBRowToScrimPost = (
|
|||
isAccepted: Boolean(request.isAccepted),
|
||||
createdAt: request.createdAt,
|
||||
message: request.message,
|
||||
at: request.at,
|
||||
startsAt: request.startsAt,
|
||||
team: request.team.name
|
||||
? {
|
||||
name: request.team.name,
|
||||
|
|
@ -347,8 +347,8 @@ const mapDBRowToScrimPost = (
|
|||
|
||||
return {
|
||||
...result,
|
||||
at: Scrim.getStartTime(result),
|
||||
rangeEnd: null,
|
||||
startsAt: Scrim.getStartTime(result),
|
||||
rangeEndsAt: null,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -447,8 +447,8 @@ export async function findAcceptedScrimsBetweenTwoTimestamps({
|
|||
excludeRecentlyCreated: Date;
|
||||
}) {
|
||||
const rows = await baseFindQuery
|
||||
.where("ScrimPost.at", ">=", dateToDatabaseTimestamp(startTime))
|
||||
.where("ScrimPost.at", "<", dateToDatabaseTimestamp(endTime))
|
||||
.where("ScrimPost.startsAt", ">=", dateToDatabaseTimestamp(startTime))
|
||||
.where("ScrimPost.startsAt", "<", dateToDatabaseTimestamp(endTime))
|
||||
.where("ScrimPost.canceledAt", "is", null)
|
||||
.where(
|
||||
"ScrimPost.createdAt",
|
||||
|
|
@ -481,7 +481,7 @@ export async function findPendingOverlapsForUsers({
|
|||
endTime: number;
|
||||
excludePostId: number;
|
||||
}): Promise<{
|
||||
posts: Array<{ id: number; at: number; memberIds: number[] }>;
|
||||
posts: Array<{ id: number; startsAt: number; memberIds: number[] }>;
|
||||
requestIds: number[];
|
||||
}> {
|
||||
if (userIds.length === 0) {
|
||||
|
|
@ -492,7 +492,7 @@ export async function findPendingOverlapsForUsers({
|
|||
|
||||
const rows = await baseFindQuery
|
||||
.where("ScrimPost.canceledAt", "is", null)
|
||||
.where("ScrimPost.at", ">=", now)
|
||||
.where("ScrimPost.startsAt", ">=", now)
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
eb.exists(
|
||||
|
|
@ -520,7 +520,8 @@ export async function findPendingOverlapsForUsers({
|
|||
|
||||
const userIdSet = new Set(userIds);
|
||||
|
||||
const posts: Array<{ id: number; at: number; memberIds: number[] }> = [];
|
||||
const posts: Array<{ id: number; startsAt: number; memberIds: number[] }> =
|
||||
[];
|
||||
const requestIds: number[] = [];
|
||||
|
||||
for (const post of rows
|
||||
|
|
@ -530,18 +531,19 @@ export async function findPendingOverlapsForUsers({
|
|||
|
||||
const postInvolvesUser = post.users.some((u) => userIdSet.has(u.id));
|
||||
const postIntervalOverlaps =
|
||||
post.at <= endTime && (post.rangeEnd ?? post.at) >= startTime;
|
||||
post.startsAt <= endTime &&
|
||||
(post.rangeEndsAt ?? post.startsAt) >= startTime;
|
||||
if (postInvolvesUser && postIntervalOverlaps) {
|
||||
posts.push({
|
||||
id: post.id,
|
||||
at: post.at,
|
||||
startsAt: post.startsAt,
|
||||
memberIds: post.users.map((u) => u.id),
|
||||
});
|
||||
}
|
||||
|
||||
for (const request of post.requests) {
|
||||
if (request.isAccepted) continue;
|
||||
const effectiveAt = request.at ?? post.at;
|
||||
const effectiveAt = request.startsAt ?? post.startsAt;
|
||||
const requestInvolvesUser = request.users.some((u) =>
|
||||
userIdSet.has(u.id),
|
||||
);
|
||||
|
|
@ -560,7 +562,7 @@ export async function findPendingOverlapsForUsers({
|
|||
|
||||
export type SidebarScrim = {
|
||||
id: number;
|
||||
at: number;
|
||||
startsAt: number;
|
||||
opponentName: string | null;
|
||||
opponentAvatarUrl: string | null;
|
||||
status: "booked" | "looking" | "requestPending";
|
||||
|
|
@ -571,7 +573,7 @@ export async function findUserScrims(userId: number): Promise<SidebarScrim[]> {
|
|||
|
||||
const rows = await baseFindQuery
|
||||
.where("ScrimPost.canceledAt", "is", null)
|
||||
.where("ScrimPost.at", ">=", now)
|
||||
.where("ScrimPost.startsAt", ">=", now)
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
eb.exists(
|
||||
|
|
@ -595,7 +597,7 @@ export async function findUserScrims(userId: number): Promise<SidebarScrim[]> {
|
|||
),
|
||||
]),
|
||||
)
|
||||
.orderBy("ScrimPost.at", "asc")
|
||||
.orderBy("ScrimPost.startsAt", "asc")
|
||||
.execute();
|
||||
|
||||
return rows
|
||||
|
|
@ -610,7 +612,7 @@ export async function findUserScrims(userId: number): Promise<SidebarScrim[]> {
|
|||
if (!isAccepted) {
|
||||
return {
|
||||
id: post.id,
|
||||
at: post.at,
|
||||
startsAt: post.startsAt,
|
||||
opponentName: null,
|
||||
opponentAvatarUrl: null,
|
||||
status: userIsInPost
|
||||
|
|
@ -627,7 +629,7 @@ export async function findUserScrims(userId: number): Promise<SidebarScrim[]> {
|
|||
|
||||
return {
|
||||
id: post.id,
|
||||
at: post.at,
|
||||
startsAt: post.startsAt,
|
||||
opponentName: opponentTeam?.name ?? null,
|
||||
opponentAvatarUrl:
|
||||
opponentTeam?.avatarUrl ?? opponentOwner?.discordAvatar ?? null,
|
||||
|
|
|
|||
|
|
@ -57,8 +57,8 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||
const resolvedDivs = data.divs ? resolveDivs(data.divs) : null;
|
||||
|
||||
await ScrimPostRepository.insert({
|
||||
at: dateToDatabaseTimestamp(data.at),
|
||||
rangeEnd: rangeEndDate ? dateToDatabaseTimestamp(rangeEndDate) : null,
|
||||
startsAt: dateToDatabaseTimestamp(data.at),
|
||||
rangeEndsAt: rangeEndDate ? dateToDatabaseTimestamp(rangeEndDate) : null,
|
||||
maxDiv: resolvedDivs?.[0] ? serializeLutiDiv(resolvedDivs[0]) : null,
|
||||
minDiv: resolvedDivs?.[1] ? serializeLutiDiv(resolvedDivs[1]) : null,
|
||||
text: data.postText,
|
||||
|
|
|
|||
|
|
@ -74,17 +74,17 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||
errorToastIfFalsy(canSeePost, "Post not found");
|
||||
}
|
||||
|
||||
if (post.rangeEnd && !data.at) {
|
||||
if (post.rangeEndsAt && !data.at) {
|
||||
return actionError<typeof newRequestSchema>({
|
||||
msg: "Please select a time for the scrim",
|
||||
field: "at",
|
||||
});
|
||||
}
|
||||
|
||||
if (post.rangeEnd && data.at) {
|
||||
if (post.rangeEndsAt && data.at) {
|
||||
const validTimeOptions = generateTimeOptions(
|
||||
databaseTimestampToDate(post.at),
|
||||
databaseTimestampToDate(post.rangeEnd),
|
||||
databaseTimestampToDate(post.startsAt),
|
||||
databaseTimestampToDate(post.rangeEndsAt),
|
||||
);
|
||||
const requestTime = data.at.getTime();
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||
scrimPostId: data.scrimPostId,
|
||||
teamId: data.from.mode === "TEAM" ? data.from.teamId : null,
|
||||
message: data.message,
|
||||
at: data.at ? dateToDatabaseTimestamp(data.at) : null,
|
||||
startsAt: data.at ? dateToDatabaseTimestamp(data.at) : null,
|
||||
users: (
|
||||
await usersListForPost({ authorId: user.id, from: data.from })
|
||||
).map((userId) => ({
|
||||
|
|
@ -154,15 +154,18 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||
ChatSystemMessage.setMetadata({
|
||||
chatCode: fullPost.chatCode,
|
||||
header: datePlaceholder(
|
||||
databaseTimestampToDate(request.at ?? post.at),
|
||||
databaseTimestampToDate(request.startsAt ?? post.startsAt),
|
||||
),
|
||||
subtitle: "Scrim",
|
||||
url: scrimPage(post.id),
|
||||
imageUrl: `${navIconUrl("scrims")}.avif`,
|
||||
participantUserIds: Scrim.participantIdsListFromAccepted(fullPost),
|
||||
expiresAt: add(databaseTimestampToDate(request.at ?? post.at), {
|
||||
hours: 3,
|
||||
}),
|
||||
expiresAt: add(
|
||||
databaseTimestampToDate(request.startsAt ?? post.startsAt),
|
||||
{
|
||||
hours: 3,
|
||||
},
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -218,7 +221,7 @@ export const action = async ({ request }: ActionFunctionArgs) => {
|
|||
defaultSeenUserIds: [user.id],
|
||||
notification: {
|
||||
type: "SCRIM_AUTO_DELETED",
|
||||
meta: { at: removed.at },
|
||||
meta: { at: removed.startsAt },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,8 +57,8 @@ export function ScrimPostCard({
|
|||
const isPickup = !post.team?.name;
|
||||
const teamName = post.team?.name ?? owner.username;
|
||||
|
||||
const flexTimeDisplay = post.rangeEnd
|
||||
? formatFlexTimeDisplay(post.at, post.rangeEnd)
|
||||
const flexTimeDisplay = post.rangeEndsAt
|
||||
? formatFlexTimeDisplay(post.startsAt, post.rangeEndsAt)
|
||||
: null;
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -109,7 +109,7 @@ export function ScrimPostCard({
|
|||
<ScrimInfoItem label="Start">
|
||||
<ScrimStartTimeDisplay
|
||||
isScheduledForFuture={post.isScheduledForFuture}
|
||||
startTimestamp={post.at}
|
||||
startTimestamp={post.startsAt}
|
||||
createdAtTimestamp={post.createdAt}
|
||||
canceled={post.canceled}
|
||||
/>
|
||||
|
|
@ -486,13 +486,13 @@ function ScrimActionButtons({
|
|||
<div className="text-lighter">{userRequest.message}</div>
|
||||
</div>
|
||||
) : null}
|
||||
{userRequest.at ? (
|
||||
{userRequest.startsAt ? (
|
||||
<div>
|
||||
<div className="text-sm font-semi-bold mb-1">
|
||||
{t("scrims:requestModal.at.label")}
|
||||
</div>
|
||||
<LocaleTime
|
||||
date={userRequest.at}
|
||||
date={userRequest.startsAt}
|
||||
options={{
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
|
|
@ -576,8 +576,8 @@ export function ScrimRequestCard({
|
|||
const isPickup = !request.team?.name;
|
||||
const teamName = request.team?.name ?? owner.username;
|
||||
|
||||
const confirmedTime = request.at
|
||||
? databaseTimestampToDate(request.at)
|
||||
const confirmedTime = request.startsAt
|
||||
? databaseTimestampToDate(request.startsAt)
|
||||
: databaseTimestampToDate(postStartTime);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ function ScrimMatchBannerTopRow() {
|
|||
|
||||
const acceptedRequest = data.post.requests.find((r) => r.isAccepted);
|
||||
const scheduledAt = databaseTimestampToDate(
|
||||
acceptedRequest?.at ?? data.post.at,
|
||||
acceptedRequest?.startsAt ?? data.post.startsAt,
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export function ScrimMatchHeader() {
|
|||
const canCancel =
|
||||
allowedToCancel &&
|
||||
!isCanceled &&
|
||||
databaseTimestampToDate(data.post.at) > new Date();
|
||||
databaseTimestampToDate(data.post.startsAt) > new Date();
|
||||
|
||||
const acceptedRequest = data.post.requests.find((r) => r.isAccepted);
|
||||
const viewerSide = data.mapByMap.viewerSide;
|
||||
|
|
|
|||
|
|
@ -29,10 +29,10 @@ export function ScrimRequestModal({
|
|||
minute: "numeric",
|
||||
});
|
||||
|
||||
const timeOptions = post.rangeEnd
|
||||
const timeOptions = post.rangeEndsAt
|
||||
? generateTimeOptions(
|
||||
databaseTimestampToDate(post.at),
|
||||
databaseTimestampToDate(post.rangeEnd),
|
||||
databaseTimestampToDate(post.startsAt),
|
||||
databaseTimestampToDate(post.rangeEndsAt),
|
||||
).map((timestamp) => ({
|
||||
value: String(timestamp),
|
||||
label: timeFormatter.format(new Date(timestamp)) ?? "",
|
||||
|
|
@ -55,7 +55,7 @@ export function ScrimRequestModal({
|
|||
) as unknown as number[],
|
||||
},
|
||||
message: "",
|
||||
at: post.rangeEnd && timeOptions[0] ? timeOptions[0].value : null,
|
||||
at: post.rangeEndsAt && timeOptions[0] ? timeOptions[0].value : null,
|
||||
}}
|
||||
>
|
||||
{({ FormField }) => (
|
||||
|
|
@ -74,7 +74,7 @@ export function ScrimRequestModal({
|
|||
<WithFormField usersTeams={data.teams} {...props} />
|
||||
)}
|
||||
</FormField>
|
||||
{post.rangeEnd ? (
|
||||
{post.rangeEndsAt ? (
|
||||
<FormField name="at" options={timeOptions} />
|
||||
) : null}
|
||||
<FormField name="message" />
|
||||
|
|
|
|||
|
|
@ -108,14 +108,14 @@ describe("sideDisplayName", () => {
|
|||
|
||||
describe("applyFilters", () => {
|
||||
function createPostForFilters(
|
||||
at: Date,
|
||||
rangeEnd?: Date,
|
||||
startsAt: Date,
|
||||
rangeEndsAt?: Date,
|
||||
divs?: { min: string; max: string },
|
||||
): ScrimPost {
|
||||
return {
|
||||
id: 1,
|
||||
at: dateToDatabaseTimestamp(at),
|
||||
rangeEnd: rangeEnd ? dateToDatabaseTimestamp(rangeEnd) : null,
|
||||
startsAt: dateToDatabaseTimestamp(startsAt),
|
||||
rangeEndsAt: rangeEndsAt ? dateToDatabaseTimestamp(rangeEndsAt) : null,
|
||||
divs: divs ? { min: divs.min as any, max: divs.max as any } : null,
|
||||
users: [],
|
||||
requests: [],
|
||||
|
|
|
|||
|
|
@ -43,12 +43,12 @@ export function participantIdsListFromAccepted(post: ScrimPost) {
|
|||
|
||||
/**
|
||||
* Returns the actual start time of the scrim.
|
||||
* When the post has a time range (rangeEnd is set), returns the accepted request's specific time if available.
|
||||
* When the post has a time range (rangeEndsAt is set), returns the accepted request's specific time if available.
|
||||
* Otherwise returns the post's start time.
|
||||
*/
|
||||
export function getStartTime(post: ScrimPost): number {
|
||||
const acceptedRequest = post.requests.find((r) => r.isAccepted);
|
||||
return acceptedRequest?.at ?? post.at;
|
||||
return acceptedRequest?.startsAt ?? post.startsAt;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -91,14 +91,14 @@ export function applyFilters(post: ScrimPost, filters: ScrimFilters): boolean {
|
|||
}
|
||||
}
|
||||
|
||||
const timeFilters = isWeekend(databaseTimestampToDate(post.at))
|
||||
const timeFilters = isWeekend(databaseTimestampToDate(post.startsAt))
|
||||
? filters.weekendTimes
|
||||
: filters.weekdayTimes;
|
||||
|
||||
if (timeFilters) {
|
||||
const startDate = databaseTimestampToDate(post.at);
|
||||
const endDate = post.rangeEnd
|
||||
? databaseTimestampToDate(post.rangeEnd)
|
||||
const startDate = databaseTimestampToDate(post.startsAt);
|
||||
const endDate = post.rangeEndsAt
|
||||
? databaseTimestampToDate(post.rangeEndsAt)
|
||||
: startDate;
|
||||
|
||||
const startTimeString = format(startDate, "HH:mm");
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ function ScrimsDaySeparatedCards({
|
|||
autoScrollToPostId: number | null;
|
||||
}) {
|
||||
const postsByDay = R.groupBy(posts, (post) =>
|
||||
format(databaseTimestampToDate(post.at), "yyyy-MM-dd"),
|
||||
format(databaseTimestampToDate(post.startsAt), "yyyy-MM-dd"),
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
@ -251,7 +251,7 @@ function ScrimsDaySection({
|
|||
<div className="stack xxs">
|
||||
<h2 className="text-sm">
|
||||
<LocaleTime
|
||||
date={posts[0].at}
|
||||
date={posts[0].startsAt}
|
||||
options={{
|
||||
day: "numeric",
|
||||
month: "numeric",
|
||||
|
|
@ -368,7 +368,7 @@ function ScrimsDaySeparatedOwnedCards({ posts }: { posts: ScrimPost[] }) {
|
|||
const user = useUser();
|
||||
|
||||
const postsByDay = R.groupBy(posts, (post) =>
|
||||
format(databaseTimestampToDate(post.at), "yyyy-MM-dd"),
|
||||
format(databaseTimestampToDate(post.startsAt), "yyyy-MM-dd"),
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
@ -380,7 +380,7 @@ function ScrimsDaySeparatedOwnedCards({ posts }: { posts: ScrimPost[] }) {
|
|||
<div key={day} className="stack md">
|
||||
<h2 className="text-sm">
|
||||
<LocaleTime
|
||||
date={posts![0].at}
|
||||
date={posts![0].startsAt}
|
||||
options={{
|
||||
day: "numeric",
|
||||
month: "numeric",
|
||||
|
|
@ -410,7 +410,7 @@ function ScrimsDaySeparatedOwnedCards({ posts }: { posts: ScrimPost[] }) {
|
|||
<ScrimRequestCard
|
||||
key={request.id}
|
||||
request={request}
|
||||
postStartTime={post.at}
|
||||
postStartTime={post.startsAt}
|
||||
canAccept={Boolean(
|
||||
user &&
|
||||
post.permissions.MANAGE_REQUESTS.includes(
|
||||
|
|
@ -438,7 +438,7 @@ function ScrimsDaySeparatedOwnedCards({ posts }: { posts: ScrimPost[] }) {
|
|||
|
||||
function ScrimsDaySeparatedBookedCards({ posts }: { posts: ScrimPost[] }) {
|
||||
const postsByDay = R.groupBy(posts, (post) =>
|
||||
format(databaseTimestampToDate(post.at), "yyyy-MM-dd"),
|
||||
format(databaseTimestampToDate(post.startsAt), "yyyy-MM-dd"),
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
@ -450,7 +450,7 @@ function ScrimsDaySeparatedBookedCards({ posts }: { posts: ScrimPost[] }) {
|
|||
<div key={day} className="stack md">
|
||||
<h2 className="text-sm">
|
||||
<LocaleTime
|
||||
date={posts![0].at}
|
||||
date={posts![0].startsAt}
|
||||
options={{
|
||||
day: "numeric",
|
||||
month: "numeric",
|
||||
|
|
@ -470,7 +470,7 @@ function ScrimsDaySeparatedBookedCards({ posts }: { posts: ScrimPost[] }) {
|
|||
{acceptedRequest ? (
|
||||
<ScrimRequestCard
|
||||
request={acceptedRequest}
|
||||
postStartTime={post.at}
|
||||
postStartTime={post.startsAt}
|
||||
canAccept={false}
|
||||
showFooter={false}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ export type ScrimSide = "ALPHA" | "BRAVO";
|
|||
|
||||
export interface ScrimPost {
|
||||
id: number;
|
||||
at: number;
|
||||
rangeEnd: number | null;
|
||||
startsAt: number;
|
||||
rangeEndsAt: number | null;
|
||||
createdAt: number;
|
||||
visibility: AssociationVisibility | null;
|
||||
text: string | null;
|
||||
|
|
@ -53,7 +53,7 @@ export interface ScrimPostRequest {
|
|||
users: Array<ScrimPostUser>;
|
||||
team: ScrimPostTeam | null;
|
||||
message: string | null;
|
||||
at: number | null;
|
||||
startsAt: number | null;
|
||||
permissions: {
|
||||
CANCEL: number[];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -98,7 +98,7 @@ async function searchByType({
|
|||
id: t.id,
|
||||
name: t.name,
|
||||
logoUrl: t.logoUrl,
|
||||
startTime: t.startTime,
|
||||
startsAt: t.startsAt,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ const tournamentResultsSubQuery = (
|
|||
"TournamentResult.setResults",
|
||||
"TournamentResult.tournamentId",
|
||||
"TournamentResult.tournamentTeamId",
|
||||
"CalendarEventDate.startTime as tournamentStartTime",
|
||||
"CalendarEventDate.startsAt as tournamentStartTime",
|
||||
"CalendarEvent.name as tournamentName",
|
||||
tournamentLogoWithDefault(eb).as("logoUrl"),
|
||||
])
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export type SidebarEvent = {
|
|||
name: string;
|
||||
url: string;
|
||||
logoUrl: string | null;
|
||||
startTime: number;
|
||||
startsAt: number;
|
||||
type: "tournament" | "scrim";
|
||||
scrimStatus?: "booked" | "looking" | "requestPending";
|
||||
};
|
||||
|
|
@ -116,7 +116,7 @@ export async function resolveSidebarData(userId: number | null) {
|
|||
const scrimEvents: SidebarEvent[] = scrimsData.map(scrimToSidebarEvent);
|
||||
|
||||
const events = [...tournamentEvents, ...savedEvents, ...scrimEvents]
|
||||
.sort((a, b) => a.startTime - b.startTime)
|
||||
.sort((a, b) => a.startsAt - b.startsAt)
|
||||
.slice(0, MAX_EVENTS_VISIBLE);
|
||||
|
||||
const friends = resolveFriends(friendsWithActivity);
|
||||
|
|
@ -170,7 +170,7 @@ async function combinedStreams(): Promise<SidebarStream[]> {
|
|||
imageUrl: externalStream.avatarUrl ?? BLANK_IMAGE_URL,
|
||||
url: externalStream.url,
|
||||
subtitle: "",
|
||||
startsAt: externalStream.startTime,
|
||||
startsAt: externalStream.startsAt,
|
||||
tier: null,
|
||||
},
|
||||
score: StreamRanking.EXTERNAL_STREAM_SCORE,
|
||||
|
|
@ -245,8 +245,8 @@ async function combinedStreams(): Promise<SidebarStream[]> {
|
|||
for (const event of upcomingTournaments) {
|
||||
const effectiveTier = event.tier ?? event.tentativeTier;
|
||||
if (effectiveTier === null) continue;
|
||||
if (event.startTime < nowTimestamp) continue;
|
||||
if (event.startTime > threeDaysFromNow) continue;
|
||||
if (event.startsAt < nowTimestamp) continue;
|
||||
if (event.startsAt > threeDaysFromNow) continue;
|
||||
if (event.hidden) continue;
|
||||
|
||||
const membersPerTeam = event.minMembersPerTeam ?? 4;
|
||||
|
|
@ -258,7 +258,7 @@ async function combinedStreams(): Promise<SidebarStream[]> {
|
|||
imageUrl: event.logoUrl ?? BLANK_IMAGE_URL,
|
||||
url: event.url,
|
||||
subtitle: "",
|
||||
startsAt: event.startTime,
|
||||
startsAt: event.startsAt,
|
||||
tier: (event.tier as TournamentTierNumber) ?? null,
|
||||
membersPerTeam,
|
||||
tentativeTier: event.tentativeTier ?? undefined,
|
||||
|
|
@ -397,7 +397,7 @@ export function tournamentToSidebarEvent(
|
|||
name: t.name,
|
||||
url: t.url,
|
||||
logoUrl: t.logoUrl,
|
||||
startTime: t.startTime,
|
||||
startsAt: t.startsAt,
|
||||
type: "tournament" as const,
|
||||
};
|
||||
}
|
||||
|
|
@ -415,7 +415,7 @@ export function scrimToSidebarEvent(s: SidebarScrim): SidebarEvent {
|
|||
? `${href("/scrims")}?pendingRequestPostId=${s.id}`
|
||||
: href("/scrims"),
|
||||
logoUrl: s.opponentAvatarUrl ?? SCRIMS_ICON_URL,
|
||||
startTime: s.at,
|
||||
startsAt: s.startsAt,
|
||||
type: "scrim" as const,
|
||||
scrimStatus: s.status,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -36,9 +36,9 @@ function queryAll() {
|
|||
"SplatoonRotation.mode",
|
||||
"SplatoonRotation.stageId1",
|
||||
"SplatoonRotation.stageId2",
|
||||
"SplatoonRotation.startTime",
|
||||
"SplatoonRotation.endTime",
|
||||
"SplatoonRotation.startsAt",
|
||||
"SplatoonRotation.endsAt",
|
||||
])
|
||||
.orderBy("SplatoonRotation.startTime", "asc")
|
||||
.orderBy("SplatoonRotation.startsAt", "asc")
|
||||
.execute();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,8 +106,8 @@ export async function fetchRotations(): Promise<
|
|||
mode,
|
||||
stageId1,
|
||||
stageId2,
|
||||
startTime: Math.floor(new Date(node.startTime).getTime() / 1000),
|
||||
endTime: Math.floor(new Date(node.endTime).getTime() / 1000),
|
||||
startsAt: Math.floor(new Date(node.startTime).getTime() / 1000),
|
||||
endsAt: Math.floor(new Date(node.endTime).getTime() / 1000),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -127,8 +127,8 @@ export async function fetchRotations(): Promise<
|
|||
mode,
|
||||
stageId1,
|
||||
stageId2,
|
||||
startTime: Math.floor(new Date(node.startTime).getTime() / 1000),
|
||||
endTime: Math.floor(new Date(node.endTime).getTime() / 1000),
|
||||
startsAt: Math.floor(new Date(node.startTime).getTime() / 1000),
|
||||
endsAt: Math.floor(new Date(node.endTime).getTime() / 1000),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -261,7 +261,7 @@ export async function findResultsById(teamId: number) {
|
|||
"results.participantCount",
|
||||
"results.tournamentTeamId",
|
||||
"CalendarEvent.name as tournamentName",
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
"Tournament.tier",
|
||||
tournamentLogoOrNull(eb).as("logoUrl"),
|
||||
jsonArrayFrom(
|
||||
|
|
@ -285,7 +285,7 @@ export async function findResultsById(teamId: number) {
|
|||
.select((eb) => commonUserSelect(eb)),
|
||||
).as("participants"),
|
||||
])
|
||||
.orderBy("CalendarEventDate.startTime", "desc")
|
||||
.orderBy("CalendarEventDate.startsAt", "desc")
|
||||
.execute();
|
||||
|
||||
const members = await allMembersById(teamId);
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export function TeamResultsTable({ results }: TeamResultsTableProps) {
|
|||
</td>
|
||||
<td className="whitespace-nowrap">
|
||||
<LocaleTime
|
||||
date={result.startTime}
|
||||
date={result.startsAt}
|
||||
options={{
|
||||
day: "numeric",
|
||||
month: "numeric",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ describe("subsOfResult()", () => {
|
|||
it("returns empty array if all participants are current members", () => {
|
||||
const result = {
|
||||
participants: [{ id: 1 }, { id: 2 }],
|
||||
startTime: 1000,
|
||||
startsAt: 1000,
|
||||
};
|
||||
const members = [
|
||||
{ userId: 1, createdAt: 500, leftAt: null },
|
||||
|
|
@ -18,7 +18,7 @@ describe("subsOfResult()", () => {
|
|||
it("returns participant not in members as sub", () => {
|
||||
const result = {
|
||||
participants: [{ id: 1 }, { id: 2 }],
|
||||
startTime: 1000,
|
||||
startsAt: 1000,
|
||||
};
|
||||
const members = [{ userId: 1, createdAt: 500, leftAt: null }];
|
||||
const subs = subsOfResult(result, members);
|
||||
|
|
@ -28,7 +28,7 @@ describe("subsOfResult()", () => {
|
|||
it("returns participant as sub if they left before result startTime", () => {
|
||||
const result = {
|
||||
participants: [{ id: 1 }, { id: 2 }],
|
||||
startTime: 1000,
|
||||
startsAt: 1000,
|
||||
};
|
||||
const members = [
|
||||
{ userId: 1, createdAt: 500, leftAt: 900 },
|
||||
|
|
@ -41,7 +41,7 @@ describe("subsOfResult()", () => {
|
|||
it("does not return participant as sub if they were a member during result", () => {
|
||||
const result = {
|
||||
participants: [{ id: 1 }, { id: 2 }],
|
||||
startTime: 1000,
|
||||
startsAt: 1000,
|
||||
};
|
||||
const members = [
|
||||
{ userId: 1, createdAt: 500, leftAt: 2000 },
|
||||
|
|
@ -54,7 +54,7 @@ describe("subsOfResult()", () => {
|
|||
it("returns multiple subs correctly", () => {
|
||||
const result = {
|
||||
participants: [{ id: 1 }, { id: 2 }, { id: 3 }],
|
||||
startTime: 1000,
|
||||
startsAt: 1000,
|
||||
};
|
||||
const members = [
|
||||
{ userId: 1, createdAt: 500, leftAt: 900 },
|
||||
|
|
@ -67,7 +67,7 @@ describe("subsOfResult()", () => {
|
|||
it("returns empty array if no participants", () => {
|
||||
const result = {
|
||||
participants: [],
|
||||
startTime: 1000,
|
||||
startsAt: 1000,
|
||||
};
|
||||
const members = [{ userId: 1, createdAt: 500, leftAt: null }];
|
||||
const subs = subsOfResult(result, members);
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ export function getMemberRoleType(member: {
|
|||
* - They are not a past member who was part of the team during the result's start time.
|
||||
*/
|
||||
export function subsOfResult<T extends { id: number }>(
|
||||
result: { participants: Array<T>; startTime: number },
|
||||
result: { participants: Array<T>; startsAt: number },
|
||||
members: Array<Pick<Tables["TeamMember"], "userId" | "createdAt" | "leftAt">>,
|
||||
) {
|
||||
const currentMembers = members.filter((member) => !member.leftAt);
|
||||
|
|
@ -113,9 +113,9 @@ export function subsOfResult<T extends { id: number }>(
|
|||
pastMembers.some(
|
||||
(member) =>
|
||||
member.userId === cur.id &&
|
||||
member.createdAt < result.startTime &&
|
||||
member.createdAt < result.startsAt &&
|
||||
member.leftAt &&
|
||||
member.leftAt > result.startTime,
|
||||
member.leftAt > result.startsAt,
|
||||
)
|
||||
) {
|
||||
return acc;
|
||||
|
|
|
|||
|
|
@ -162,7 +162,6 @@ export function insertBracket(args: {
|
|||
type: stageInput.type,
|
||||
settings: JSON.stringify(stageInput.settings),
|
||||
number: kyselySql<number>`(select coalesce(max("number"), 0) + 1 from "TournamentStage" where "tournamentId" = ${args.tournamentId})`,
|
||||
createdAt: databaseTimestampNow(),
|
||||
})
|
||||
.returning(["id"])
|
||||
.executeTakeFirstOrThrow();
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ export function findTournamentsNeedingVodSync() {
|
|||
.where(({ or, and, eb }) =>
|
||||
or([
|
||||
and([
|
||||
eb("CalendarEventDate.startTime", ">", oneDayAgo),
|
||||
eb("CalendarEventDate.startsAt", ">", oneDayAgo),
|
||||
eb("Tournament.vodsSyncCount", "=", 0),
|
||||
]),
|
||||
and([
|
||||
|
|
@ -122,7 +122,7 @@ export function deleteObsolete() {
|
|||
"CalendarEvent.id",
|
||||
)
|
||||
.select("TournamentMatch.id")
|
||||
.where("CalendarEventDate.startTime", "<", cutoff),
|
||||
.where("CalendarEventDate.startsAt", "<", cutoff),
|
||||
)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -218,7 +218,7 @@ function resolveFinalizationSeason(tournament: Tournament) {
|
|||
// league divisions might be running for many weeks
|
||||
const attributionDate = tournament.isLeagueDivision
|
||||
? new Date()
|
||||
: tournament.ctx.startTime;
|
||||
: tournament.ctx.startsAt;
|
||||
const season = Seasons.current(attributionDate);
|
||||
if (!season) return undefined;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export function useBracketSpoilerCensor() {
|
|||
|
||||
const withinSpoilerWindow =
|
||||
tournament.ctx.isFinalized &&
|
||||
differenceInDays(new Date(), tournament.ctx.startTime) <
|
||||
differenceInDays(new Date(), tournament.ctx.startsAt) <
|
||||
TOURNAMENT.VOD_VISIBILITY_DAYS;
|
||||
|
||||
const censored = withinSpoilerWindow && isCensored(tournament.ctx.id);
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ function mostRecentStartTime(tournament: Tournament) {
|
|||
.filter((b) => b.startTime)
|
||||
.map((b) => databaseTimestampToDate(b.startTime!));
|
||||
|
||||
const allStartTimes = [tournament.ctx.startTime, ...bracketStartTimes];
|
||||
const allStartTimes = [tournament.ctx.startsAt, ...bracketStartTimes];
|
||||
|
||||
return allStartTimes
|
||||
.filter((t) => t <= new Date())
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export class Tournament {
|
|||
? // after the start the teams who did not check-in are irrelevant
|
||||
teamsInSeedOrder.filter((team) => team.checkIns.length > 0)
|
||||
: teamsInSeedOrder,
|
||||
startTime: databaseTimestampToDate(ctx.startTime),
|
||||
startsAt: databaseTimestampToDate(ctx.startsAt),
|
||||
};
|
||||
|
||||
this.initBrackets(data);
|
||||
|
|
@ -477,7 +477,7 @@ export class Tournament {
|
|||
get ranked() {
|
||||
return tournamentIsRanked({
|
||||
isSetAsRanked: this.ctx.settings.isRanked,
|
||||
startTime: this.ctx.startTime,
|
||||
startsAt: this.ctx.startsAt,
|
||||
minMembersPerTeam: this.minMembersPerTeam,
|
||||
isTest: this.isTest,
|
||||
});
|
||||
|
|
@ -726,7 +726,7 @@ export class Tournament {
|
|||
return (
|
||||
!this.ctx.settings.regClosesAt ||
|
||||
this.ctx.settings.regClosesAt ===
|
||||
dateToDatabaseTimestamp(this.ctx.startTime) ||
|
||||
dateToDatabaseTimestamp(this.ctx.startsAt) ||
|
||||
this.registrationOpen
|
||||
);
|
||||
}
|
||||
|
|
@ -753,7 +753,7 @@ export class Tournament {
|
|||
|
||||
/** Has the regular check-in (check-in for the whole tournament) ended? */
|
||||
get regularCheckInHasEnded() {
|
||||
return this.ctx.startTime < new Date();
|
||||
return this.ctx.startsAt < new Date();
|
||||
}
|
||||
|
||||
/** Has the regular check-in (check-in for the whole tournament) started? Note it is also considered started if it has ended. */
|
||||
|
|
@ -763,21 +763,21 @@ export class Tournament {
|
|||
|
||||
/** Date when the regular check-in is scheduled to start. */
|
||||
get regularCheckInStartsAt() {
|
||||
const result = new Date(this.ctx.startTime);
|
||||
const result = new Date(this.ctx.startsAt);
|
||||
result.setMinutes(result.getMinutes() - 60);
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Date when the regular check-in is scheduled to start. */
|
||||
get regularCheckInEndsAt() {
|
||||
return this.ctx.startTime;
|
||||
return this.ctx.startsAt;
|
||||
}
|
||||
|
||||
/** Date when the tournament registration is scheduled to end. This can be set by the organizer. */
|
||||
get registrationClosesAt() {
|
||||
return this.ctx.settings.regClosesAt
|
||||
? databaseTimestampToDate(this.ctx.settings.regClosesAt)
|
||||
: this.ctx.startTime;
|
||||
: this.ctx.startsAt;
|
||||
}
|
||||
|
||||
/** Is the tournament registration open at this time? */
|
||||
|
|
@ -789,11 +789,11 @@ export class Tournament {
|
|||
|
||||
/** Can participants submit/undo their own weapon reports right now?
|
||||
* Always open while the tournament is running; once finalized it stays open only for tournaments
|
||||
* whose startTime is inside the current-season-plus-adjacent-off-season window. */
|
||||
* whose start time is inside the current-season-plus-adjacent-off-season window. */
|
||||
get weaponReportingOpen() {
|
||||
if (!this.ctx.isFinalized) return true;
|
||||
return tournamentInWeaponReportingWindow({
|
||||
tournamentStartTime: this.ctx.startTime,
|
||||
tournamentStartTime: this.ctx.startsAt,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6248,7 +6248,7 @@ export const LOW_INK_DECEMBER_2024 = (): TournamentData => ({
|
|||
mapPickingStyle: "TO",
|
||||
hasRules: true,
|
||||
name: "Low Ink December 2024",
|
||||
startTime: 1734199200,
|
||||
startsAt: 1734199200,
|
||||
organization: {
|
||||
id: 3,
|
||||
name: "Inkling Performance Labs",
|
||||
|
|
|
|||
|
|
@ -1752,7 +1752,7 @@ export const SWIM_OR_SINK_167 = (
|
|||
mapPickingStyle: "TO",
|
||||
hasRules: true,
|
||||
name: "Swim or Sink 167",
|
||||
startTime: 1730941200,
|
||||
startsAt: 1730941200,
|
||||
organization: {
|
||||
id: 3,
|
||||
name: "Inkling Performance Labs",
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ export const ZONES_WEEKLY_38 = (): TournamentData => ({
|
|||
parentTournamentId: null,
|
||||
parentTournamentName: null,
|
||||
name: "Zones Weekly 38",
|
||||
startTime: 1734685200,
|
||||
startsAt: 1734685200,
|
||||
isFinalized: 0,
|
||||
organization: null,
|
||||
logoUrl: "tournament-logo-hfX5gzVyrt5QCV8fiQA4n-1716906622859.webp",
|
||||
|
|
|
|||
|
|
@ -1250,7 +1250,7 @@ export const PADDLING_POOL_257 = () =>
|
|||
name: "Paddling Pool 257",
|
||||
hasRules: false,
|
||||
logoUrl: "/test.avif",
|
||||
startTime: 1709748000,
|
||||
startsAt: 1709748000,
|
||||
author: {
|
||||
id: 860,
|
||||
username: "공주 Alice",
|
||||
|
|
@ -8033,7 +8033,7 @@ export const PADDLING_POOL_255 = () =>
|
|||
name: "Paddling Pool 255",
|
||||
hasRules: false,
|
||||
logoUrl: "/test.avif",
|
||||
startTime: 1708538400,
|
||||
startsAt: 1708538400,
|
||||
author: {
|
||||
id: 860,
|
||||
username: "공주 Alice",
|
||||
|
|
@ -15193,7 +15193,7 @@ export const IN_THE_ZONE_32 = ({
|
|||
name: "In The Zone 32",
|
||||
hasRules: false,
|
||||
logoUrl: "/test.avif",
|
||||
startTime: 1707588000,
|
||||
startsAt: 1707588000,
|
||||
author: {
|
||||
id: 274,
|
||||
username: "Sendou",
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export const testTournament = ({
|
|||
hasRules: false,
|
||||
logoUrl: "/test.avif",
|
||||
discordUrl: null,
|
||||
startTime: 1705858842,
|
||||
startsAt: 1705858842,
|
||||
isFinalized: 0,
|
||||
name: "test",
|
||||
castTwitchAccounts: [],
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ async function standingsWithSetParticipation(tournament: Tournament) {
|
|||
);
|
||||
invariant(results.length > 0, "No results found");
|
||||
|
||||
const season = Seasons.current(tournament.ctx.startTime)?.nth;
|
||||
const season = Seasons.current(tournament.ctx.startsAt)?.nth;
|
||||
|
||||
const seedingSkillCountsFor = tournament.skillCountsFor;
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
|||
id: tournamentId,
|
||||
name: tournament.ctx.name,
|
||||
logoUrl: tournament.ctx.logoUrl,
|
||||
startTime: tournament.ctx.startTime,
|
||||
startTime: tournament.ctx.startsAt,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -211,7 +211,7 @@ export const action = async ({ request, params }: ActionFunctionArgs) => {
|
|||
id: tournamentId,
|
||||
name: tournament.ctx.name,
|
||||
logoUrl: tournament.ctx.logoUrl,
|
||||
startTime: tournament.ctx.startTime,
|
||||
startTime: tournament.ctx.startsAt,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -767,7 +767,7 @@ export const action: ActionFunction = async ({ params, request }) => {
|
|||
tournamentMatchId: matchId,
|
||||
mapIndex: data.mapIndex,
|
||||
weaponSplId: data.weaponSplId,
|
||||
createdAt: dateToDatabaseTimestamp(tournament.ctx.startTime),
|
||||
createdAt: dateToDatabaseTimestamp(tournament.ctx.startsAt),
|
||||
});
|
||||
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
|
|||
? true
|
||||
: !chatAccessible({
|
||||
expiresAfterDays: tournament.isLeagueDivision ? 30 : 7,
|
||||
comparedTo: tournament.ctx.startTime,
|
||||
comparedTo: tournament.ctx.startsAt,
|
||||
});
|
||||
|
||||
const visibleChatCode =
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ const findEventsBaseQuery = (organizationId: number) =>
|
|||
"CalendarEvent.id as eventId",
|
||||
"CalendarEvent.name",
|
||||
"CalendarEvent.tournamentId",
|
||||
eb.fn.min("CalendarEventDate.startTime").as("startTime"),
|
||||
eb.fn.min("CalendarEventDate.startsAt").as("startsAt"),
|
||||
tournamentLogoWithDefault(eb).as("logoUrl"),
|
||||
jsonArrayFrom(
|
||||
eb
|
||||
|
|
@ -347,16 +347,16 @@ export async function findEventsByMonth({
|
|||
|
||||
const events = await findEventsBaseQuery(organizationId)
|
||||
.where(
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
">=",
|
||||
dateToDatabaseTimestamp(firstDayOfTheMonth),
|
||||
)
|
||||
.where(
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
"<=",
|
||||
dateToDatabaseTimestamp(lastDayOfTheMonth),
|
||||
)
|
||||
.orderBy("CalendarEventDate.startTime", "asc")
|
||||
.orderBy("CalendarEventDate.startsAt", "asc")
|
||||
.execute();
|
||||
|
||||
return events.map(mapEvent);
|
||||
|
|
@ -387,7 +387,7 @@ const findSeriesEventsBaseQuery = ({
|
|||
),
|
||||
),
|
||||
)
|
||||
.orderBy("CalendarEventDate.startTime", "desc");
|
||||
.orderBy("CalendarEventDate.startsAt", "desc");
|
||||
|
||||
export async function findPaginatedEventsBySeries({
|
||||
organizationId,
|
||||
|
|
@ -458,8 +458,8 @@ export async function countActiveParticipants({
|
|||
)
|
||||
.select(({ fn }) => fn.count<number>("tmgrp.userId").distinct().as("count"))
|
||||
.where("ce.organizationId", "=", organizationId)
|
||||
.where("ced.startTime", ">=", startTime)
|
||||
.where("ced.startTime", "<", endTime)
|
||||
.where("ced.startsAt", ">=", startTime)
|
||||
.where("ced.startsAt", "<", endTime)
|
||||
.where("ttci.checkedInAt", "is not", null)
|
||||
.where("ttci.isCheckOut", "=", 0)
|
||||
.executeTakeFirst();
|
||||
|
|
@ -580,12 +580,12 @@ export function update({
|
|||
"Tournament.id as tournamentId",
|
||||
"CalendarEvent.name",
|
||||
"Tournament.tier",
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
])
|
||||
.where("Tournament.isFinalized", "=", 1)
|
||||
.where("CalendarEvent.organizationId", "=", id)
|
||||
.where("CalendarEvent.hidden", "=", 0)
|
||||
.orderBy("CalendarEventDate.startTime", "asc")
|
||||
.orderBy("CalendarEventDate.startsAt", "asc")
|
||||
.execute();
|
||||
|
||||
for (const s of insertedSeries) {
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export function EventCalendar({
|
|||
))}
|
||||
{dates.map((date, i) => {
|
||||
const daysEvents = events.filter((event) => {
|
||||
const startTimeDate = databaseTimestampToDate(event.startTime);
|
||||
const startTimeDate = databaseTimestampToDate(event.startsAt);
|
||||
|
||||
return (
|
||||
isHydrated &&
|
||||
|
|
|
|||
|
|
@ -137,6 +137,6 @@ async function seriesStuff({
|
|||
: null,
|
||||
eventsCount: events.length,
|
||||
logoUrl: events[0].logoUrl,
|
||||
established: events.at(-1)!.startTime,
|
||||
established: events.at(-1)!.startsAt,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -485,11 +485,11 @@ function EventsList({
|
|||
const events = filteredByMonth
|
||||
? data.events.filter(
|
||||
(event) =>
|
||||
databaseTimestampToDate(event.startTime).getMonth() === data.month,
|
||||
databaseTimestampToDate(event.startsAt).getMonth() === data.month,
|
||||
)
|
||||
: data.events;
|
||||
const pastEvents = events.filter((event) => event.startTime < now);
|
||||
const upcomingEvents = events.filter((event) => event.startTime >= now);
|
||||
const pastEvents = events.filter((event) => event.startsAt < now);
|
||||
const upcomingEvents = events.filter((event) => event.startsAt >= now);
|
||||
|
||||
return (
|
||||
<div className="w-full stack xs">
|
||||
|
|
@ -540,7 +540,7 @@ function EventInfo({
|
|||
<div>
|
||||
<div>{event.name}</div>
|
||||
<LocaleTime
|
||||
date={event.startTime}
|
||||
date={event.startsAt}
|
||||
options={{
|
||||
day: "numeric",
|
||||
month: "numeric",
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export async function seedOrgEventWithParticipants({
|
|||
|
||||
await db
|
||||
.insertInto("CalendarEventDate")
|
||||
.values({ eventId: event.id, startTime })
|
||||
.values({ eventId: event.id, startsAt: startTime })
|
||||
.execute();
|
||||
|
||||
const team = await db
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { db } from "~/db/sql";
|
|||
import type { DB, Tables } from "~/db/tables";
|
||||
import type { TournamentAuditLogMetadata } from "~/db/tables-json";
|
||||
import { actorId } from "~/features/auth/core/user.server";
|
||||
import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import { commonUserSelect } from "~/utils/kysely.server";
|
||||
|
||||
export const AUDIT_LOG_PAGE_SIZE = 30;
|
||||
|
|
@ -56,7 +56,6 @@ export async function insert(trx: Transaction<DB>, args: InsertArgs) {
|
|||
subjectUserId: args.subjectUserId ?? null,
|
||||
tournamentTeamHistoryId,
|
||||
metadata: args.metadata ? JSON.stringify(args.metadata) : null,
|
||||
createdAt: databaseTimestampNow(),
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ export async function findById(id: number) {
|
|||
.as("parentTournamentName"),
|
||||
"Tournament.tier",
|
||||
"CalendarEvent.name",
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
"Tournament.isFinalized",
|
||||
jsonObjectFrom(
|
||||
eb
|
||||
|
|
@ -549,7 +549,7 @@ export function forShowcase() {
|
|||
"CalendarEvent.authorId",
|
||||
"CalendarEvent.name",
|
||||
"CalendarEvent.organizationId",
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
"CalendarEvent.hidden",
|
||||
eb
|
||||
.selectFrom("TournamentTeam")
|
||||
|
|
@ -567,7 +567,7 @@ export function forShowcase() {
|
|||
.where((eb) =>
|
||||
eb.or([
|
||||
eb("TournamentTeamCheckIn.checkedInAt", "is not", null),
|
||||
eb("CalendarEventDate.startTime", ">", databaseTimestampNow()),
|
||||
eb("CalendarEventDate.startsAt", ">", databaseTimestampNow()),
|
||||
]),
|
||||
)
|
||||
.select(({ fn }) => [
|
||||
|
|
@ -639,8 +639,8 @@ export function forShowcase() {
|
|||
.select(({ fn }) => [fn.countAll<number>().as("count")])
|
||||
.as("vodCount"),
|
||||
])
|
||||
.where("CalendarEventDate.startTime", ">", databaseTimestampWeekAgo())
|
||||
.orderBy("CalendarEventDate.startTime", "asc")
|
||||
.where("CalendarEventDate.startsAt", ">", databaseTimestampWeekAgo())
|
||||
.orderBy("CalendarEventDate.startsAt", "asc")
|
||||
.$narrowType<{ teamsCount: NotNull }>()
|
||||
.execute();
|
||||
}
|
||||
|
|
@ -670,15 +670,11 @@ export function findAllBetweenTwoTimestamps({
|
|||
.innerJoin("Tournament", "CalendarEvent.tournamentId", "Tournament.id")
|
||||
.select(["Tournament.id as tournamentId"])
|
||||
.where(
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
">",
|
||||
dateToDatabaseTimestamp(startTime),
|
||||
)
|
||||
.where(
|
||||
"CalendarEventDate.startTime",
|
||||
"<=",
|
||||
dateToDatabaseTimestamp(endTime),
|
||||
)
|
||||
.where("CalendarEventDate.startsAt", "<=", dateToDatabaseTimestamp(endTime))
|
||||
.where("CalendarEvent.hidden", "=", 0)
|
||||
.execute();
|
||||
}
|
||||
|
|
@ -1304,17 +1300,17 @@ export async function searchByName({
|
|||
.select((eb) => [
|
||||
"Tournament.id",
|
||||
"CalendarEvent.name",
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
tournamentLogoWithDefault(eb).as("logoUrl"),
|
||||
])
|
||||
.where("CalendarEvent.name", "like", `%${query}%`)
|
||||
.where("CalendarEvent.hidden", "=", 0)
|
||||
.orderBy("CalendarEventDate.startTime", "desc")
|
||||
.orderBy("CalendarEventDate.startsAt", "desc")
|
||||
.limit(limit);
|
||||
|
||||
if (minStartTime) {
|
||||
sqlQuery = sqlQuery.where(
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
">=",
|
||||
dateToDatabaseTimestamp(minStartTime),
|
||||
);
|
||||
|
|
@ -1322,7 +1318,7 @@ export async function searchByName({
|
|||
|
||||
if (maxStartTime) {
|
||||
sqlQuery = sqlQuery.where(
|
||||
"CalendarEventDate.startTime",
|
||||
"CalendarEventDate.startsAt",
|
||||
"<=",
|
||||
dateToDatabaseTimestamp(maxStartTime),
|
||||
);
|
||||
|
|
@ -1398,8 +1394,8 @@ export async function findRunningTournamentIds() {
|
|||
)
|
||||
.select("Tournament.id")
|
||||
.where("Tournament.isFinalized", "=", 0)
|
||||
.where("CalendarEventDate.startTime", "<", dateToDatabaseTimestamp(now))
|
||||
.where("CalendarEventDate.startTime", ">", dateToDatabaseTimestamp(cutoff))
|
||||
.where("CalendarEventDate.startsAt", "<", dateToDatabaseTimestamp(now))
|
||||
.where("CalendarEventDate.startsAt", ">", dateToDatabaseTimestamp(cutoff))
|
||||
.where((eb) =>
|
||||
eb.exists(
|
||||
eb
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ const regOpenTournamentTeamsByJoinedUserId = (userId: number) =>
|
|||
.where(
|
||||
sql`coalesce(
|
||||
"Tournament"."settings" ->> 'regClosesAt',
|
||||
"CalendarEventDate"."startTime"
|
||||
"CalendarEventDate"."startsAt"
|
||||
)`,
|
||||
">",
|
||||
databaseTimestampNow(),
|
||||
|
|
@ -375,7 +375,7 @@ async function registrationClosedNow(
|
|||
.select(
|
||||
sql<number>`coalesce(
|
||||
"Tournament"."settings" ->> 'regClosesAt',
|
||||
min("CalendarEventDate"."startTime")
|
||||
min("CalendarEventDate"."startsAt")
|
||||
)`.as("regClosesAt"),
|
||||
)
|
||||
.where("Tournament.id", "=", tournamentId)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export function TournamentHeader({ tournament }: { tournament: Tournament }) {
|
|||
|
||||
const startTimes = R.uniqueBy(
|
||||
[
|
||||
tournament.ctx.startTime,
|
||||
tournament.ctx.startsAt,
|
||||
...tournament.ctx.settings.bracketProgression
|
||||
.filter((b) => b.startTime)
|
||||
.map((b) => databaseTimestampToDate(b.startTime!)),
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
|
|||
|
||||
const friendCodeVisibilityDays = tournament.ctx.parentTournamentId ? 120 : 30;
|
||||
const tournamentStartedRecently = isAfter(
|
||||
databaseTimestampToDate(tournament.ctx.startTime),
|
||||
databaseTimestampToDate(tournament.ctx.startsAt),
|
||||
subDays(new Date(), friendCodeVisibilityDays),
|
||||
);
|
||||
const isTournamentAdmin =
|
||||
|
|
@ -73,7 +73,7 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
|
|||
const showVods =
|
||||
tournament.ctx.isFinalized &&
|
||||
isAfter(
|
||||
databaseTimestampToDate(tournament.ctx.startTime),
|
||||
databaseTimestampToDate(tournament.ctx.startsAt),
|
||||
subDays(new Date(), TOURNAMENT.VOD_VISIBILITY_DAYS),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ function useFacts(
|
|||
|
||||
const showsEstimatedTier = !tournament.ctx.tier && !tournament.hasStarted;
|
||||
|
||||
const rankedSeason = Seasons.current(tournament.ctx.startTime);
|
||||
const rankedSeason = Seasons.current(tournament.ctx.startsAt);
|
||||
|
||||
return [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -300,12 +300,12 @@ function RegistrationProgress({
|
|||
|
||||
const regClosesBeforeStart =
|
||||
tournament.registrationClosesAt.getTime() !==
|
||||
tournament.ctx.startTime.getTime();
|
||||
tournament.ctx.startsAt.getTime();
|
||||
|
||||
const registrationClosesAtString =
|
||||
registrationClosesFormatter.format(
|
||||
tournament.isLeagueSignup
|
||||
? tournament.ctx.startTime
|
||||
? tournament.ctx.startsAt
|
||||
: tournament.registrationClosesAt,
|
||||
) ?? "";
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export default function TournamentResultsPage() {
|
|||
const { isCensored, reveal } = useSpoilerFree();
|
||||
|
||||
const withinSpoilerWindow =
|
||||
differenceInDays(new Date(), tournament.ctx.startTime) <
|
||||
differenceInDays(new Date(), tournament.ctx.startsAt) <
|
||||
TOURNAMENT.VOD_VISIBILITY_DAYS;
|
||||
const censored = withinSpoilerWindow && isCensored(tournament.ctx.id);
|
||||
|
||||
|
|
|
|||
|
|
@ -139,18 +139,18 @@ export function validateCounterPickMapPool(
|
|||
|
||||
export function tournamentIsRanked({
|
||||
isSetAsRanked,
|
||||
startTime,
|
||||
startsAt,
|
||||
minMembersPerTeam,
|
||||
isTest,
|
||||
}: {
|
||||
isSetAsRanked?: boolean;
|
||||
startTime: Date;
|
||||
startsAt: Date;
|
||||
minMembersPerTeam: number;
|
||||
isTest: boolean;
|
||||
}) {
|
||||
if (isTest) return false;
|
||||
|
||||
const seasonIsActive = Boolean(Seasons.current(startTime));
|
||||
const seasonIsActive = Boolean(Seasons.current(startsAt));
|
||||
if (!seasonIsActive) return false;
|
||||
|
||||
// 1v1, 2v2 and 3v3 are always considered "gimmicky"
|
||||
|
|
@ -160,7 +160,7 @@ export function tournamentIsRanked({
|
|||
}
|
||||
|
||||
/**
|
||||
* Whether a tournament's startTime falls inside the active weapon-reporting window
|
||||
* Whether a tournament's start time falls inside the active weapon-reporting window
|
||||
* for late (post-finalization) reporting.
|
||||
*
|
||||
* - In-season: window is `(previousSeason.ends, now]` — current season plus the off-season immediately before it.
|
||||
|
|
|
|||
|
|
@ -472,7 +472,7 @@ export function findAllPatrons() {
|
|||
.select(["User.id", "User.discordId", "User.username", "User.patronTier"])
|
||||
.where("User.patronTier", "is not", null)
|
||||
.orderBy("User.patronTier", "desc")
|
||||
.orderBy("User.patronSince", "asc")
|
||||
.orderBy("User.patronStartedAt", "asc")
|
||||
.execute();
|
||||
}
|
||||
|
||||
|
|
@ -507,9 +507,9 @@ export async function findChatUsersByUserIds(userIds: number[]) {
|
|||
const withMaxEventStartTime = (eb: ExpressionBuilder<DB, "CalendarEvent">) => {
|
||||
return eb
|
||||
.selectFrom("CalendarEventDate")
|
||||
.select(({ fn }) => [fn.max("CalendarEventDate.startTime").as("startTime")])
|
||||
.select(({ fn }) => [fn.max("CalendarEventDate.startsAt").as("startsAt")])
|
||||
.whereRef("CalendarEventDate.eventId", "=", "CalendarEvent.id")
|
||||
.as("startTime");
|
||||
.as("startsAt");
|
||||
};
|
||||
|
||||
const baseCalendarEventResultsQuery = (userId: number) =>
|
||||
|
|
@ -669,8 +669,8 @@ export function findResultsByUserId(
|
|||
|
||||
let query = calendarEventResultsQuery
|
||||
.unionAll(tournamentResultsQuery)
|
||||
.orderBy("startTime", "desc")
|
||||
.$narrowType<{ startTime: NotNull }>();
|
||||
.orderBy("startsAt", "desc")
|
||||
.$narrowType<{ startsAt: NotNull }>();
|
||||
|
||||
if (limit !== undefined) {
|
||||
query = query.limit(limit);
|
||||
|
|
@ -973,14 +973,14 @@ export async function inGameNameByUserId(userId: number) {
|
|||
)?.inGameName;
|
||||
}
|
||||
|
||||
export async function patronSinceByUserId(userId: number) {
|
||||
export async function patronStartedAtByUserId(userId: number) {
|
||||
return (
|
||||
await db
|
||||
.selectFrom("User")
|
||||
.select("User.patronSince")
|
||||
.select("User.patronStartedAt")
|
||||
.where("id", "=", userId)
|
||||
.executeTakeFirst()
|
||||
)?.patronSince;
|
||||
)?.patronStartedAt;
|
||||
}
|
||||
|
||||
export async function joinOrderByUserId(userId: number) {
|
||||
|
|
@ -1248,7 +1248,7 @@ export function updateOwnBuildSorting(buildSorting: BuildSort[] | null) {
|
|||
}
|
||||
|
||||
export type UpdatePatronDataArgs = Array<
|
||||
Pick<Tables["User"], "discordId" | "patronTier" | "patronSince">
|
||||
Pick<Tables["User"], "discordId" | "patronTier" | "patronStartedAt">
|
||||
>;
|
||||
export function updatePatronData(users: UpdatePatronDataArgs) {
|
||||
return db.transaction().execute(async (trx) => {
|
||||
|
|
@ -1256,13 +1256,13 @@ export function updatePatronData(users: UpdatePatronDataArgs) {
|
|||
.updateTable("User")
|
||||
.set({
|
||||
patronTier: null,
|
||||
patronSince: null,
|
||||
patronTill: null,
|
||||
patronStartedAt: null,
|
||||
patronExpiresAt: null,
|
||||
})
|
||||
.where((eb) =>
|
||||
eb.or([
|
||||
eb("patronTill", "<", dateToDatabaseTimestamp(new Date())),
|
||||
eb("patronTill", "is", null),
|
||||
eb("patronExpiresAt", "<", dateToDatabaseTimestamp(new Date())),
|
||||
eb("patronExpiresAt", "is", null),
|
||||
]),
|
||||
)
|
||||
.execute();
|
||||
|
|
@ -1272,8 +1272,8 @@ export function updatePatronData(users: UpdatePatronDataArgs) {
|
|||
.updateTable("User")
|
||||
.set({
|
||||
patronTier: user.patronTier,
|
||||
patronSince: user.patronSince,
|
||||
patronTill: null,
|
||||
patronStartedAt: user.patronStartedAt,
|
||||
patronExpiresAt: null,
|
||||
})
|
||||
.where("User.discordId", "=", user.discordId)
|
||||
.execute();
|
||||
|
|
|
|||
|
|
@ -154,8 +154,8 @@ describe("UserRepository", () => {
|
|||
await AdminRepository.forcePatron({
|
||||
id,
|
||||
patronTier: 1,
|
||||
patronSince: now,
|
||||
patronTill: oneYearFromNow,
|
||||
patronStartedAt: now,
|
||||
patronExpiresAt: oneYearFromNow,
|
||||
});
|
||||
|
||||
const user = await UserRepository.findLeanById(id);
|
||||
|
|
@ -178,8 +178,8 @@ describe("UserRepository", () => {
|
|||
await AdminRepository.forcePatron({
|
||||
id,
|
||||
patronTier: 2,
|
||||
patronSince: now,
|
||||
patronTill: oneYearFromNow,
|
||||
patronStartedAt: now,
|
||||
patronExpiresAt: oneYearFromNow,
|
||||
});
|
||||
|
||||
const user = await UserRepository.findLeanById(id);
|
||||
|
|
@ -305,8 +305,8 @@ describe("UserRepository", () => {
|
|||
await AdminRepository.forcePatron({
|
||||
id,
|
||||
patronTier: 2,
|
||||
patronSince: now,
|
||||
patronTill: oneYearFromNow,
|
||||
patronStartedAt: now,
|
||||
patronExpiresAt: oneYearFromNow,
|
||||
});
|
||||
|
||||
await AdminRepository.makeArtistByUserId(id);
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ export function UserResultsTable({
|
|||
</td>
|
||||
<td className="whitespace-nowrap">
|
||||
<LocaleTime
|
||||
date={result.startTime}
|
||||
date={result.startsAt}
|
||||
options={{
|
||||
day: "numeric",
|
||||
month: "numeric",
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export function Widget({
|
|||
user: Pick<Tables["User"], "id" | "discordId" | "customUrl">;
|
||||
}) {
|
||||
const { t } = useTranslation(["user", "badges", "team", "org", "lfg"]);
|
||||
const { formatter: patronSinceFormatter } = useDateTimeFormat({
|
||||
const { formatter: patronStartedAtFormatter } = useDateTimeFormat({
|
||||
day: "numeric",
|
||||
month: "numeric",
|
||||
year: "numeric",
|
||||
|
|
@ -184,7 +184,9 @@ export function Widget({
|
|||
case "patron-since":
|
||||
if (!widget.data) return null;
|
||||
return (
|
||||
<BigValue value={patronSinceFormatter.format(widget.data) ?? ""} />
|
||||
<BigValue
|
||||
value={patronStartedAtFormatter.format(widget.data) ?? ""}
|
||||
/>
|
||||
);
|
||||
case "join-date":
|
||||
if (!widget.data) return null;
|
||||
|
|
@ -418,7 +420,7 @@ function HighlightedResults({
|
|||
) : null}
|
||||
</div>
|
||||
<LocaleTime
|
||||
date={result.startTime}
|
||||
date={result.startsAt}
|
||||
options={{
|
||||
day: "numeric",
|
||||
month: "numeric",
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ export const WIDGET_LOADERS = {
|
|||
};
|
||||
},
|
||||
"patron-since": async (userId: number) => {
|
||||
return UserRepository.patronSinceByUserId(userId);
|
||||
return UserRepository.patronStartedAtByUserId(userId);
|
||||
},
|
||||
"join-date": async (userId: number) => {
|
||||
return UserRepository.joinOrderByUserId(userId);
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ export async function findVods({
|
|||
}
|
||||
const result = await query
|
||||
.groupBy("Video.id")
|
||||
.orderBy("Video.youtubeDate", "desc")
|
||||
.orderBy("Video.youtubePublishedAt", "desc")
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
.execute();
|
||||
|
|
@ -163,7 +163,7 @@ export async function findVodById(id: Tables["Video"]["id"]) {
|
|||
.select([
|
||||
"id",
|
||||
"title",
|
||||
"youtubeDate",
|
||||
"youtubePublishedAt",
|
||||
"youtubeId",
|
||||
"type",
|
||||
"submitterUserId",
|
||||
|
|
@ -259,7 +259,7 @@ export async function insert(
|
|||
const video = {
|
||||
title: args.title,
|
||||
type: args.type,
|
||||
youtubeDate: dayMonthYearToDatabaseTimestamp(args.date),
|
||||
youtubePublishedAt: dayMonthYearToDatabaseTimestamp(args.date),
|
||||
eventId: args.eventId ?? null,
|
||||
youtubeId,
|
||||
submitterUserId: args.submitterUserId,
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ export default function VodPage() {
|
|||
<div className="stack horizontal sm items-center">
|
||||
<PovUser pov={data.vod.pov} />
|
||||
<LocaleTime
|
||||
date={data.vod.youtubeDate}
|
||||
date={data.vod.youtubePublishedAt}
|
||||
options={{
|
||||
day: "numeric",
|
||||
month: "numeric",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export interface Vod {
|
|||
| string;
|
||||
title: Tables["Video"]["title"];
|
||||
type: Tables["Video"]["type"];
|
||||
youtubeDate: Tables["Video"]["youtubeDate"];
|
||||
youtubePublishedAt: Tables["Video"]["youtubePublishedAt"];
|
||||
youtubeId: Tables["Video"]["youtubeId"];
|
||||
matches: Array<VodMatch>;
|
||||
submitterUserId: Tables["Video"]["submitterUserId"];
|
||||
|
|
@ -28,7 +28,7 @@ export type VodMatch = Pick<
|
|||
weapons: Array<MainWeaponId>;
|
||||
};
|
||||
|
||||
export type ListVod = Omit<Vod, "youtubeDate" | "matches"> & {
|
||||
export type ListVod = Omit<Vod, "youtubePublishedAt" | "matches"> & {
|
||||
weapons: Array<MainWeaponId>;
|
||||
type: Tables["Video"]["type"];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ describe("generateYoutubeTimestamps", () => {
|
|||
});
|
||||
|
||||
describe("vodToVideoBeingAdded", () => {
|
||||
// youtubeDate is stored as noon UTC of the chosen day (see
|
||||
// youtubePublishedAt is stored as noon UTC of the chosen day (see
|
||||
// dayMonthYearToDatabaseTimestamp), so reading the day/month/year back out
|
||||
// must also use UTC. Force a timezone east of UTC+12 where local time has
|
||||
// already rolled over to the next day, making the bug deterministic.
|
||||
|
|
@ -242,7 +242,7 @@ describe("vodToVideoBeingAdded", () => {
|
|||
title: "Test VOD",
|
||||
type: "TOURNAMENT",
|
||||
youtubeId: "dQw4w9WgXcQ",
|
||||
youtubeDate: dayMonthYearToDatabaseTimestamp(date),
|
||||
youtubePublishedAt: dayMonthYearToDatabaseTimestamp(date),
|
||||
submitterUserId: 1,
|
||||
matches: [],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { HOURS_MINUTES_SECONDS_REGEX } from "./vods-schemas";
|
|||
import type { VideoBeingAdded, Vod } from "./vods-types";
|
||||
|
||||
export function vodToVideoBeingAdded(vod: Vod): VideoBeingAdded {
|
||||
const dateObj = databaseTimestampToDate(vod.youtubeDate);
|
||||
const dateObj = databaseTimestampToDate(vod.youtubePublishedAt);
|
||||
|
||||
return {
|
||||
title: vod.title,
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export async function updatePatreonData(): Promise<void> {
|
|||
).map((discordId) => ({
|
||||
discordId,
|
||||
patronTier: 4,
|
||||
patronSince: dateToDatabaseTimestamp(new Date()),
|
||||
patronStartedAt: dateToDatabaseTimestamp(new Date()),
|
||||
})),
|
||||
];
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ function parsePatronData({
|
|||
|
||||
patronsWithIds.push({
|
||||
patreonId: patron.relationships.user.data.id,
|
||||
patronSince: dateToDatabaseTimestamp(
|
||||
patronStartedAt: dateToDatabaseTimestamp(
|
||||
new Date(patron.attributes.pledge_relationship_start ?? Date.now()),
|
||||
),
|
||||
patronTier: idToTierNumber(tier),
|
||||
|
|
@ -162,7 +162,7 @@ function parsePatronData({
|
|||
}
|
||||
|
||||
result.patrons.push({
|
||||
patronSince: patronData.patronSince,
|
||||
patronStartedAt: patronData.patronStartedAt,
|
||||
discordId,
|
||||
patronTier: patronData.patronTier,
|
||||
});
|
||||
|
|
|
|||
BIN
db-test.sqlite3
BIN
db-test.sqlite3
Binary file not shown.
|
|
@ -25,14 +25,18 @@ Counter-examples that look like booleans but aren't: `User.banned` (`1` = permab
|
|||
## Generated vs. GeneratedAlways
|
||||
|
||||
- `Generated<T>` — column has a DB default, so it's optional on insert but you may still pass a value (`createdAt`, every boolean flag).
|
||||
- `GeneratedAlways<T>` — never insertable or updatable: primary keys, computed columns, `createdAt` columns whose value should never be supplied by app code.
|
||||
- `GeneratedAlways<T>` — never insertable or updatable: primary keys and computed columns (`User.username`, the `UserSearch` FTS columns).
|
||||
- Plain `T` — required on every insert.
|
||||
|
||||
Pick by what the DB allows, not by what app code happens to do today: if the column has a default it is `Generated<T>`, even when nothing currently overrides it.
|
||||
|
||||
## Timestamps
|
||||
|
||||
Unix seconds as `number`, named `*At`. Never ISO strings, never milliseconds.
|
||||
|
||||
Columns with `default (strftime('%s', 'now'))` are `Generated<number>`; use `GeneratedAlways<number>` when app code should never set them. Nullable `*At` columns double as markers (`deletedAt`, `validatedAt`, `leftAt`) — document that meaning in JSDoc.
|
||||
Name every instant with an `*At` suffix, including future ones: `startsAt`, `endsAt`, `expiresAt`, `becomesValidAt`. Not `startTime`, `youtubeDate` or a bare `at`. Domain objects and repository return shapes that carry a column's value keep the column's name; form fields, query range bounds and external API payloads are free to use their own names.
|
||||
|
||||
Every `createdAt` gets `default (strftime('%s', 'now'))` and is typed `Generated<number>`. The two nullable ones (`User`, `Skill`) predate the column existing and have no backfillable value — JSDoc says so on each. Other nullable `*At` columns double as markers (`deletedAt`, `validatedAt`, `leftAt`) — document that meaning in JSDoc too.
|
||||
|
||||
Convert with `~/utils/dates`: `dateToDatabaseTimestamp`, `databaseTimestampToDate`, `databaseTimestampNow`.
|
||||
|
||||
|
|
@ -63,7 +67,7 @@ Non-obvious columns get a JSDoc comment: what `null` means, what a magic number
|
|||
- SQLite can't add `not null` to an existing column. To tighten one: add `"<col>_new" integer not null default 0`, `update ... set "<col>_new" = coalesce("<col>", 0)`, drop the old column, rename the new one. See `migrations/162-boolean-columns-not-null.js`.
|
||||
- `drop column` fails if the column is indexed, part of a constraint, or referenced by a trigger, view, generated column or partial index. Check `sqlite_master` first.
|
||||
- Only when that isn't enough, rebuild the table: `pragma foreign_keys = OFF`, create `X_new`, copy, drop, rename, recreate every index, then `pragma foreign_key_check` inside the transaction. See `migrations/161-tournament-match-nullable-opponents.js`.
|
||||
- New columns can't be added with a non-constant default, so `createdAt` added later ends up nullable (see `TournamentStage.createdAt`).
|
||||
- New columns can't be added with a non-constant default, so a `createdAt` added later starts out nullable. Backfill it and rebuild the table in the same migration rather than leaving the nullability behind — see the `TournamentStage` rebuild in `migrations/162-boolean-columns-not-null.js`.
|
||||
|
||||
## After changing the schema
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user