From abc30fd12b8d5281a093dbcb1fb194c9bd133666 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Fri, 22 Jul 2022 23:20:39 +0300 Subject: [PATCH] Calendar events from DB initial --- app/db/index.ts | 2 + app/db/models/calendar.server.ts | 32 ++++++++++++ app/db/seed.ts | 90 ++++++++++++++++++++++++++++++++ app/db/types.ts | 1 + app/routes/calendar.tsx | 46 ++++++++++++++-- app/styles/calendar.css | 4 ++ app/utils/dates.ts | 4 +- migrations/006-calendar.js | 7 +-- package-lock.json | 11 ++++ package.json | 1 + 10 files changed, 191 insertions(+), 7 deletions(-) create mode 100644 app/db/models/calendar.server.ts diff --git a/app/db/index.ts b/app/db/index.ts index 551e703a9..d359266fb 100644 --- a/app/db/index.ts +++ b/app/db/index.ts @@ -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, }; diff --git a/app/db/models/calendar.server.ts b/app/db/models/calendar.server.ts new file mode 100644 index 000000000..6abddc697 --- /dev/null +++ b/app/db/models/calendar.server.ts @@ -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 & + Pick + >; +} diff --git a/app/db/seed.ts b/app/db/seed.ts index 9c89bbf5d..ec0e9dcdb 100644 --- a/app/db/seed.ts +++ b/app/db/seed.ts @@ -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 + } +} diff --git a/app/db/types.ts b/app/db/types.ts index 21b99aa8c..81da9bfd9 100644 --- a/app/db/types.ts +++ b/app/db/types.ts @@ -88,6 +88,7 @@ export interface CalendarEvent { } export interface CalendarEventDate { + id: number; eventId: number; startTime: number; } diff --git a/app/routes/calendar.tsx b/app/routes/calendar.tsx index c9d35c00c..08a7b7b82 100644 --- a/app/routes/calendar.tsx +++ b/app/routes/calendar.tsx @@ -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(); + return (
+ {data.events.map((event) => { + return ; + })}
); } @@ -121,3 +143,21 @@ function WeekLinks() { ); } + +function Event({ + event, +}: { + event: Unpacked["events"]>; +}) { + return ( +
+ {" "} + {event.name} +
+ ); +} diff --git a/app/styles/calendar.css b/app/styles/calendar.css index 41a4c66b7..55b315d30 100644 --- a/app/styles/calendar.css +++ b/app/styles/calendar.css @@ -70,3 +70,7 @@ .calendar__week:nth-child(6) > .calendar__event-count { font-size: var(--fonts-xxs); } + +.calendar__event { + font-size: var(--fonts-lg); +} diff --git a/app/utils/dates.ts b/app/utils/dates.ts index f323d8cb9..4c51bc222 100644 --- a/app/utils/dates.ts +++ b/app/utils/dates.ts @@ -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) ); diff --git a/migrations/006-calendar.js b/migrations/006-calendar.js index a74dab7d3..33bf8a4bd 100644 --- a/migrations/006-calendar.js +++ b/migrations/006-calendar.js @@ -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, diff --git a/package-lock.json b/package-lock.json index 3dc4e58a9..7c5524ac4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 8c5a13943..b651a727f 100644 --- a/package.json +++ b/package.json @@ -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",