mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-09-08 12:16:12 -05:00
Calendar events from DB initial
This commit is contained in:
@@ -2,10 +2,12 @@ import * as users from "./models/users.server";
|
||||
import * as plusSuggestions from "./models/plusSuggestions.server";
|
||||
import * as plusVotes from "./models/plusVotes.server";
|
||||
import * as badges from "./models/badges.server";
|
||||
import * as calendar from "./models/calendar.server";
|
||||
|
||||
export const db = {
|
||||
users,
|
||||
plusSuggestions,
|
||||
plusVotes,
|
||||
badges,
|
||||
calendar,
|
||||
};
|
||||
|
||||
32
app/db/models/calendar.server.ts
Normal file
32
app/db/models/calendar.server.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import { sql } from "../sql";
|
||||
import type { CalendarEvent, CalendarEventDate } from "../types";
|
||||
|
||||
const findAllBetweenTwoTimestampsStm = sql.prepare(`
|
||||
select
|
||||
"CalendarEvent"."name",
|
||||
"CalendarEvent"."discordUrl",
|
||||
"CalendarEvent"."bracketUrl",
|
||||
"CalendarEventDate"."id",
|
||||
"CalendarEventDate"."startTime"
|
||||
from "CalendarEvent"
|
||||
join "CalendarEventDate" on "CalendarEvent"."id" = "CalendarEventDate"."eventId"
|
||||
where "CalendarEventDate"."startTime" between $startTime and $endTime
|
||||
order by "CalendarEventDate"."startTime" asc
|
||||
`);
|
||||
|
||||
export function findAllBetweenTwoTimestamps({
|
||||
startTime,
|
||||
endTime,
|
||||
}: {
|
||||
startTime: Date;
|
||||
endTime: Date;
|
||||
}) {
|
||||
return findAllBetweenTwoTimestampsStm.all({
|
||||
startTime: dateToDatabaseTimestamp(startTime),
|
||||
endTime: dateToDatabaseTimestamp(endTime),
|
||||
}) as Array<
|
||||
Pick<CalendarEvent, "name" | "discordUrl" | "bracketUrl"> &
|
||||
Pick<CalendarEventDate, "id" | "startTime">
|
||||
>;
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import type { UpsertManyPlusVotesArgs } from "./models/plusVotes.server";
|
||||
import { ADMIN_DISCORD_ID } from "~/constants";
|
||||
import shuffle from "just-shuffle";
|
||||
import { dateToDatabaseTimestamp } from "~/utils/dates";
|
||||
import capitalize from "just-capitalize";
|
||||
|
||||
const ADMIN_TEST_AVATAR = "fcfd65a3bea598905abb9ca25296816b";
|
||||
|
||||
@@ -28,6 +29,7 @@ const basicSeeds = [
|
||||
badgesToUsers,
|
||||
badgeManagers,
|
||||
patrons,
|
||||
calendarEvents,
|
||||
];
|
||||
|
||||
export function seed() {
|
||||
@@ -40,6 +42,10 @@ export function seed() {
|
||||
|
||||
function wipeDB() {
|
||||
const tablesToDelete = [
|
||||
"CalendarEventDate",
|
||||
"CalendarEventWinner",
|
||||
"CalendarEventBadge",
|
||||
"CalendarEvent",
|
||||
"User",
|
||||
"PlusVote",
|
||||
"PlusSuggestion",
|
||||
@@ -299,3 +305,87 @@ function patrons() {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function calendarEvents() {
|
||||
const userIds = sql
|
||||
.prepare(`select "id" from "User" order by random()`)
|
||||
.all()
|
||||
.map((u) => u.id);
|
||||
|
||||
for (let id = 1; id <= 100; id++) {
|
||||
sql
|
||||
.prepare(
|
||||
`
|
||||
insert into "CalendarEvent" (
|
||||
"id",
|
||||
"name",
|
||||
"description",
|
||||
"discordUrl",
|
||||
"bracketUrl",
|
||||
"authorId"
|
||||
) values (
|
||||
$id,
|
||||
$name,
|
||||
$description,
|
||||
$discordUrl,
|
||||
$bracketUrl,
|
||||
$authorId
|
||||
)
|
||||
`
|
||||
)
|
||||
.run({
|
||||
id,
|
||||
name: `${capitalize(faker.word.adjective())} ${capitalize(
|
||||
faker.word.noun()
|
||||
)}`,
|
||||
description: faker.lorem.paragraph(),
|
||||
discordUrl: faker.internet.url(),
|
||||
bracketUrl: faker.internet.url(),
|
||||
authorId: userIds.pop(),
|
||||
});
|
||||
|
||||
const twoDayEvent = Math.random() > 0.9;
|
||||
const startTime = faker.date.soon(42);
|
||||
startTime.setMinutes(0, 0, 0);
|
||||
|
||||
sql
|
||||
.prepare(
|
||||
`
|
||||
insert into "CalendarEventDate" (
|
||||
"eventId",
|
||||
"startTime"
|
||||
) values (
|
||||
$eventId,
|
||||
$startTime
|
||||
)
|
||||
`
|
||||
)
|
||||
.run({
|
||||
eventId: id,
|
||||
startTime: dateToDatabaseTimestamp(startTime),
|
||||
});
|
||||
|
||||
if (twoDayEvent) {
|
||||
startTime.setDate(startTime.getDate() + 1);
|
||||
|
||||
sql
|
||||
.prepare(
|
||||
`
|
||||
insert into "CalendarEventDate" (
|
||||
"eventId",
|
||||
"startTime"
|
||||
) values (
|
||||
$eventId,
|
||||
$startTime
|
||||
)
|
||||
`
|
||||
)
|
||||
.run({
|
||||
eventId: id,
|
||||
startTime: dateToDatabaseTimestamp(startTime),
|
||||
});
|
||||
}
|
||||
|
||||
// xxx: 20% of cases have 1-2 badge prizes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ export interface CalendarEvent {
|
||||
}
|
||||
|
||||
export interface CalendarEventDate {
|
||||
id: number;
|
||||
eventId: number;
|
||||
startTime: number;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import type { LoaderArgs } from "@remix-run/node";
|
||||
import { json, type LinksFunction } from "@remix-run/node";
|
||||
import { Link, useLoaderData } from "@remix-run/react";
|
||||
import type { UseDataFunctionReturn } from "@remix-run/react/dist/components";
|
||||
import { addDays, subDays } from "date-fns";
|
||||
import { Flipped, Flipper } from "react-flip-toolkit";
|
||||
import { z } from "zod";
|
||||
import { Main } from "~/components/Main";
|
||||
import { db } from "~/db";
|
||||
import styles from "~/styles/calendar.css";
|
||||
import { dateToWeekNumber, weekNumberToDate } from "~/utils/dates";
|
||||
import {
|
||||
databaseTimestampToDate,
|
||||
dateToWeekNumber,
|
||||
weekNumberToDate,
|
||||
} from "~/utils/dates";
|
||||
import type { Unpacked } from "~/utils/types";
|
||||
import { actualNumber } from "~/utils/zod";
|
||||
|
||||
export const links: LinksFunction = () => {
|
||||
@@ -36,8 +43,6 @@ export const loader = ({ request }: LoaderArgs) => {
|
||||
? parsedParams.data.year
|
||||
: now.getFullYear();
|
||||
|
||||
console.log({ weekToFetch, yearToFetch, thisWeek }); // TODO: db query to get events of this week
|
||||
|
||||
return json({
|
||||
thisWeek,
|
||||
weeks: closeByWeeks({ week: weekToFetch, year: yearToFetch }).map(
|
||||
@@ -46,6 +51,7 @@ export const loader = ({ request }: LoaderArgs) => {
|
||||
numberOfEvents: 12,
|
||||
})
|
||||
),
|
||||
events: fetchEventsOfWeek({ week: weekToFetch, year: yearToFetch }),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -65,10 +71,26 @@ function closeByWeeks(args: { week: number; year: number }) {
|
||||
});
|
||||
}
|
||||
|
||||
function fetchEventsOfWeek(args: { week: number; year: number }) {
|
||||
const startTime = weekNumberToDate(args);
|
||||
|
||||
const endTime = new Date(startTime);
|
||||
endTime.setDate(endTime.getDate() + 7);
|
||||
// so we get all events of sunday even from US west coast perspective
|
||||
endTime.setHours(endTime.getHours() + 12);
|
||||
|
||||
return db.calendar.findAllBetweenTwoTimestamps({ startTime, endTime });
|
||||
}
|
||||
|
||||
export default function CalendarPage() {
|
||||
const data = useLoaderData<typeof loader>();
|
||||
|
||||
return (
|
||||
<Main>
|
||||
<WeekLinks />
|
||||
{data.events.map((event) => {
|
||||
return <Event key={event.id} event={event} />;
|
||||
})}
|
||||
</Main>
|
||||
);
|
||||
}
|
||||
@@ -121,3 +143,21 @@ function WeekLinks() {
|
||||
</Flipper>
|
||||
);
|
||||
}
|
||||
|
||||
function Event({
|
||||
event,
|
||||
}: {
|
||||
event: Unpacked<UseDataFunctionReturn<typeof loader>["events"]>;
|
||||
}) {
|
||||
return (
|
||||
<section className="calendar__event">
|
||||
<time>
|
||||
{databaseTimestampToDate(event.startTime).toLocaleTimeString("en", {
|
||||
hour: "numeric",
|
||||
minute: "numeric",
|
||||
})}
|
||||
</time>{" "}
|
||||
{event.name}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,3 +70,7 @@
|
||||
.calendar__week:nth-child(6) > .calendar__event-count {
|
||||
font-size: var(--fonts-xxs);
|
||||
}
|
||||
|
||||
.calendar__event {
|
||||
font-size: var(--fonts-lg);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ export function weekNumberToDate({
|
||||
week: number;
|
||||
year: number;
|
||||
}) {
|
||||
const result = new Date(year, 0, 4);
|
||||
// xxx: possible problem of mismatch when server time and local time don't match
|
||||
// gotta make sure events which belong to monday are still shown for sunday
|
||||
const result = new Date(Date.UTC(year, 0, 4));
|
||||
result.setDate(
|
||||
result.getDate() - (result.getDay() || 7) + 1 + 7 * (week - 1)
|
||||
);
|
||||
|
||||
@@ -17,7 +17,8 @@ module.exports.up = function (db) {
|
||||
db.prepare(
|
||||
`
|
||||
create table "CalendarEventDate" (
|
||||
"eventId" integer primary key,
|
||||
"id" integer primary key,
|
||||
"eventId" integer not null,
|
||||
"startTime" integer not null,
|
||||
foreign key ("eventId") references "CalendarEvent"("id") on delete cascade
|
||||
) strict
|
||||
@@ -27,7 +28,7 @@ module.exports.up = function (db) {
|
||||
db.prepare(
|
||||
`
|
||||
create table "CalendarEventWinner" (
|
||||
"eventId" integer primary key,
|
||||
"eventId" integer not null,
|
||||
"teamName" text not null,
|
||||
"placement" integer not null,
|
||||
"userId" integer,
|
||||
@@ -41,7 +42,7 @@ module.exports.up = function (db) {
|
||||
db.prepare(
|
||||
`
|
||||
create table "CalendarEventBadge" (
|
||||
"eventId" integer primary key,
|
||||
"eventId" integer not null,
|
||||
"badgeId" integer not null,
|
||||
foreign key ("eventId") references "CalendarEvent"("id") on delete cascade,
|
||||
foreign key ("badgeId") references "Badge"("id") on delete restrict,
|
||||
|
||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -21,6 +21,7 @@
|
||||
"i18next-browser-languagedetector": "^6.1.4",
|
||||
"i18next-fs-backend": "^1.1.4",
|
||||
"i18next-http-backend": "^1.4.1",
|
||||
"just-capitalize": "^3.0.1",
|
||||
"just-shuffle": "^4.0.1",
|
||||
"node-cron": "3.0.0",
|
||||
"react": "^18.2.0",
|
||||
@@ -9484,6 +9485,11 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/just-capitalize": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/just-capitalize/-/just-capitalize-3.0.1.tgz",
|
||||
"integrity": "sha512-GPvagN7Y2IdNFdFh8wRtE0AoxjA+0zAOF+XS8i2Di+aNdTZ0SAb5bARqjklOxVgIUTBfTv6MqqrRmdGjnLbkQA=="
|
||||
},
|
||||
"node_modules/just-shuffle": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/just-shuffle/-/just-shuffle-4.0.1.tgz",
|
||||
@@ -22397,6 +22403,11 @@
|
||||
"object.assign": "^4.1.2"
|
||||
}
|
||||
},
|
||||
"just-capitalize": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/just-capitalize/-/just-capitalize-3.0.1.tgz",
|
||||
"integrity": "sha512-GPvagN7Y2IdNFdFh8wRtE0AoxjA+0zAOF+XS8i2Di+aNdTZ0SAb5bARqjklOxVgIUTBfTv6MqqrRmdGjnLbkQA=="
|
||||
},
|
||||
"just-shuffle": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/just-shuffle/-/just-shuffle-4.0.1.tgz",
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"i18next-browser-languagedetector": "^6.1.4",
|
||||
"i18next-fs-backend": "^1.1.4",
|
||||
"i18next-http-backend": "^1.4.1",
|
||||
"just-capitalize": "^3.0.1",
|
||||
"just-shuffle": "^4.0.1",
|
||||
"node-cron": "3.0.0",
|
||||
"react": "^18.2.0",
|
||||
|
||||
Reference in New Issue
Block a user