diff --git a/.env.example b/.env.example index 102ee8789..cbc3c0f89 100644 --- a/.env.example +++ b/.env.example @@ -10,12 +10,12 @@ DISCORD_CLIENT_SECRET= PATREON_ACCESS_TOKEN= // Image upload -STORAGE_END_POINT= -STORAGE_ACCESS_KEY= -STORAGE_SECRET= -STORAGE_REGION= -STORAGE_BUCKET= -STORAGE_URL= +STORAGE_END_POINT=http://127.0.0.1:9000 +STORAGE_ACCESS_KEY=minio-user +STORAGE_SECRET=minio-password +STORAGE_REGION=us-east-1 +STORAGE_BUCKET=sendou +STORAGE_URL=http://127.0.0.1:9000 // Twitch integration for fetching streams TWITCH_CLIENT_ID= @@ -23,6 +23,7 @@ TWITCH_CLIENT_SECRET= SKALOP_SYSTEM_MESSAGE_URL=http://localhost:5900/system SKALOP_TOKEN=secret +REDIS_URL=redis://redis:6379 VITE_SITE_DOMAIN=http://localhost:5173 VITE_SKALOP_WS_URL=ws://localhost:5900 diff --git a/README.md b/README.md index 47e93cc5d..1e14eab00 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,16 @@ You should then be able to access the application by visiting http://localhost:5 Use the admin panel at http://localhost:5173/admin to log in (impersonate) as the admin user "Sendou" or as a regular user "N-ZAP" as well as re-seed the database if needed. +#### Docker + +Optionally, if you want to develop image upload, real-time features or chat, you can use Docker to spin up the Skalop service and Minio for image hosting. You will need [Docker](https://www.docker.com/) up and running and then run the following command: + +``` +docker compose up -d +``` + +Minio admin UI to manage uploaded photos should be up and running at http://localhost:9001 + ## Contributing - **Developers**: Read [CONTRIBUTING.md](./CONTRIBUTING.md) diff --git a/app/db/seed/index.ts b/app/db/seed/index.ts index 38be526e7..eb8aae70c 100644 --- a/app/db/seed/index.ts +++ b/app/db/seed/index.ts @@ -1616,25 +1616,12 @@ const detailedTeam = (seedVariation?: SeedVariation | null) => () => { sql .prepare( /* sql */ ` - insert into "UnvalidatedUserSubmittedImage" ("validatedAt", "url", "submitterUserId") - values - (1672587342, 'AiGSM5T-cxm6BFGT7N_lA-1673297699133.webp', ${ADMIN_ID}), - (1672587342, 'jTbWd95klxU2MzGFIdi1c-1673297932788.webp', ${ADMIN_ID}) - `, - ) - .run(); - - sql - .prepare( - /* sql */ ` - insert into "AllTeam" ("name", "customUrl", "inviteCode", "bio", "avatarImgId", "bannerImgId") + insert into "AllTeam" ("name", "customUrl", "inviteCode", "bio") values ( 'Alliance Rogue', 'alliance-rogue', '${shortNanoid()}', - '${faker.lorem.paragraph()}', - 1, - 2 + '${faker.lorem.paragraph()}' ) `, ) diff --git a/app/features/api-public/routes/org.$id.ts b/app/features/api-public/routes/org.$id.ts index ba85a0724..ec3611846 100644 --- a/app/features/api-public/routes/org.$id.ts +++ b/app/features/api-public/routes/org.$id.ts @@ -4,7 +4,7 @@ import { cors } from "remix-utils/cors"; import { z } from "zod/v4"; import { db } from "~/db/sql"; import { notFoundIfFalsy, parseParams } from "~/utils/remix.server"; -import { userSubmittedImage } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { id } from "~/utils/zod"; import { handleOptionsRequest, diff --git a/app/features/api-public/routes/tournament.$id.teams.ts b/app/features/api-public/routes/tournament.$id.teams.ts index 7617d9a10..de4ca814c 100644 --- a/app/features/api-public/routes/tournament.$id.teams.ts +++ b/app/features/api-public/routes/tournament.$id.teams.ts @@ -9,7 +9,7 @@ import i18next from "~/modules/i18n/i18next.server"; import { nullifyingAvg } from "~/utils/arrays"; import { databaseTimestampToDate } from "~/utils/dates"; import { parseParams } from "~/utils/remix.server"; -import { userSubmittedImage } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { id } from "~/utils/zod"; import { handleOptionsRequest, diff --git a/app/features/api-public/routes/tournament.$id.ts b/app/features/api-public/routes/tournament.$id.ts index 79f55e950..15b44c89c 100644 --- a/app/features/api-public/routes/tournament.$id.ts +++ b/app/features/api-public/routes/tournament.$id.ts @@ -6,7 +6,7 @@ import { db } from "~/db/sql"; import { HACKY_resolvePicture } from "~/features/tournament/tournament-utils"; import { databaseTimestampToDate } from "~/utils/dates"; import { notFoundIfFalsy, parseParams } from "~/utils/remix.server"; -import { userSubmittedImage } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { id } from "~/utils/zod"; import { handleOptionsRequest, diff --git a/app/features/art/components/ArtGrid.tsx b/app/features/art/components/ArtGrid.tsx index f7ddd8291..e4e4ceaeb 100644 --- a/app/features/art/components/ArtGrid.tsx +++ b/app/features/art/components/ArtGrid.tsx @@ -15,13 +15,8 @@ import { useIsMounted } from "~/hooks/useIsMounted"; import { usePagination } from "~/hooks/usePagination"; import { useSearchParamState } from "~/hooks/useSearchParamState"; import { databaseTimestampToDate } from "~/utils/dates"; -import { - artPage, - conditionalUserSubmittedImage, - newArtPage, - userArtPage, - userPage, -} from "~/utils/urls"; +import { artPage, newArtPage, userArtPage, userPage } from "~/utils/urls"; +import { conditionalUserSubmittedImage } from "~/utils/urls-img"; import { ResponsiveMasonry } from "../../../modules/responsive-masonry/components/ResponsiveMasonry"; import { ART_PER_PAGE } from "../art-constants"; import type { ListedArt } from "../art-types"; diff --git a/app/features/art/routes/art.new.tsx b/app/features/art/routes/art.new.tsx index d3fbf0d25..75614fd8a 100644 --- a/app/features/art/routes/art.new.tsx +++ b/app/features/art/routes/art.new.tsx @@ -17,11 +17,8 @@ import { useHasRole } from "~/modules/permissions/hooks"; import invariant from "~/utils/invariant"; import { logger } from "~/utils/logger"; import type { SendouRouteHandle } from "~/utils/remix.server"; -import { - artPage, - conditionalUserSubmittedImage, - navIconUrl, -} from "~/utils/urls"; +import { artPage, navIconUrl } from "~/utils/urls"; +import { conditionalUserSubmittedImage } from "~/utils/urls-img"; import { metaTitle } from "../../../utils/remix"; import { action } from "../actions/art.new.server"; import { ART } from "../art-constants"; diff --git a/app/features/calendar/components/TournamentCard.tsx b/app/features/calendar/components/TournamentCard.tsx index 36730e487..bc8e60032 100644 --- a/app/features/calendar/components/TournamentCard.tsx +++ b/app/features/calendar/components/TournamentCard.tsx @@ -11,7 +11,8 @@ import { BadgeDisplay } from "~/features/badges/components/BadgeDisplay"; import { HACKY_resolvePicture } from "~/features/tournament/tournament-utils"; import { useIsMounted } from "~/hooks/useIsMounted"; import { databaseTimestampToDate } from "~/utils/dates"; -import { navIconUrl, userSubmittedImage } from "~/utils/urls"; +import { navIconUrl } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import type { CalendarEvent, ShowcaseCalendarEvent } from "../calendar-types"; import { Tags } from "./Tags"; import styles from "./TournamentCard.module.css"; diff --git a/app/features/calendar/routes/calendar.new.tsx b/app/features/calendar/routes/calendar.new.tsx index c7f10752b..c6e313eca 100644 --- a/app/features/calendar/routes/calendar.new.tsx +++ b/app/features/calendar/routes/calendar.new.tsx @@ -32,7 +32,8 @@ import { import invariant from "~/utils/invariant"; import type { SendouRouteHandle } from "~/utils/remix.server"; import { pathnameFromPotentialURL } from "~/utils/strings"; -import { CREATING_TOURNAMENT_DOC_LINK, userSubmittedImage } from "~/utils/urls"; +import { CREATING_TOURNAMENT_DOC_LINK } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { CALENDAR_EVENT, REG_CLOSES_AT_OPTIONS, diff --git a/app/features/front-page/loaders/index.server.ts b/app/features/front-page/loaders/index.server.ts index 091fa5fed..17c9065a4 100644 --- a/app/features/front-page/loaders/index.server.ts +++ b/app/features/front-page/loaders/index.server.ts @@ -7,12 +7,8 @@ import { cachedFullUserLeaderboard } from "~/features/leaderboards/core/leaderbo import * as LeaderboardRepository from "~/features/leaderboards/LeaderboardRepository.server"; import * as Seasons from "~/features/mmr/core/Seasons"; import { cache, IN_MILLISECONDS, ttl } from "~/utils/cache.server"; -import { - discordAvatarUrl, - teamPage, - userPage, - userSubmittedImage, -} from "~/utils/urls"; +import { discordAvatarUrl, teamPage, userPage } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import * as ShowcaseTournaments from "../core/ShowcaseTournaments.server"; export const loader = async ({ request }: LoaderFunctionArgs) => { diff --git a/app/features/img-upload/routes/upload.admin.tsx b/app/features/img-upload/routes/upload.admin.tsx index 282a9a0fc..38a3d90bf 100644 --- a/app/features/img-upload/routes/upload.admin.tsx +++ b/app/features/img-upload/routes/upload.admin.tsx @@ -5,7 +5,7 @@ import { FormWithConfirm } from "~/components/FormWithConfirm"; import { TrashIcon } from "~/components/icons/Trash"; import { Main } from "~/components/Main"; import { SubmitButton } from "~/components/SubmitButton"; -import { userSubmittedImage } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { action } from "../actions/upload.admin.server"; import { loader } from "../loaders/upload.admin.server"; diff --git a/app/features/leaderboards/routes/leaderboards.tsx b/app/features/leaderboards/routes/leaderboards.tsx index 5563694d3..90d1ede21 100644 --- a/app/features/leaderboards/routes/leaderboards.tsx +++ b/app/features/leaderboards/routes/leaderboards.tsx @@ -19,8 +19,8 @@ import { topSearchPlayerPage, userPage, userSeasonsPage, - userSubmittedImage, } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { InfoPopover } from "../../../components/InfoPopover"; import { TopTenPlayer } from "../components/TopTenPlayer"; import { diff --git a/app/features/lfg/components/LFGPost.tsx b/app/features/lfg/components/LFGPost.tsx index c69a9a51c..fafdca32f 100644 --- a/app/features/lfg/components/LFGPost.tsx +++ b/app/features/lfg/components/LFGPost.tsx @@ -17,12 +17,8 @@ import type { TieredSkill } from "~/features/mmr/tiered.server"; import { useIsMounted } from "~/hooks/useIsMounted"; import { useHasRole } from "~/modules/permissions/hooks"; import { databaseTimestampToDate } from "~/utils/dates"; -import { - lfgNewPostPage, - navIconUrl, - userPage, - userSubmittedImage, -} from "~/utils/urls"; +import { lfgNewPostPage, navIconUrl, userPage } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { hourDifferenceBetweenTimezones } from "../core/timezone"; import type { LFGLoaderData, TiersMap } from "../routes/lfg"; diff --git a/app/features/scrims/routes/scrims.$id.tsx b/app/features/scrims/routes/scrims.$id.tsx index a7a9d747f..270136c61 100644 --- a/app/features/scrims/routes/scrims.$id.tsx +++ b/app/features/scrims/routes/scrims.$id.tsx @@ -13,6 +13,7 @@ import { cancelScrimSchema } from "~/features/scrims/scrims-schemas"; import { resolveRoomPass } from "~/features/tournament-bracket/tournament-bracket-utils"; import { useHasPermission } from "~/modules/permissions/hooks"; import type { SendouRouteHandle } from "~/utils/remix.server"; +import { userSubmittedImage } from "~/utils/urls-img"; import { Avatar } from "../../../components/Avatar"; import { Main } from "../../../components/Main"; import { databaseTimestampToDate } from "../../../utils/dates"; @@ -22,7 +23,6 @@ import { scrimsPage, teamPage, userPage, - userSubmittedImage, } from "../../../utils/urls"; import { ConnectedChat } from "../../chat/components/Chat"; import { action } from "../actions/scrims.$id.server"; diff --git a/app/features/scrims/routes/scrims.tsx b/app/features/scrims/routes/scrims.tsx index 2fbcbe560..82c4719f0 100644 --- a/app/features/scrims/routes/scrims.tsx +++ b/app/features/scrims/routes/scrims.tsx @@ -33,8 +33,8 @@ import { scrimPage, scrimsPage, userPage, - userSubmittedImage, } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { SendouTab, SendouTabList, diff --git a/app/features/sendouq-match/routes/q.match.$id.tsx b/app/features/sendouq-match/routes/q.match.$id.tsx index dca162555..fdaba9bef 100644 --- a/app/features/sendouq-match/routes/q.match.$id.tsx +++ b/app/features/sendouq-match/routes/q.match.$id.tsx @@ -66,8 +66,8 @@ import { sendouQMatchPage, specialWeaponImageUrl, teamPage, - userSubmittedImage, } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { action } from "../actions/q.match.$id.server"; import { matchEndedAtIndex } from "../core/match"; import { loader } from "../loaders/q.match.$id.server"; diff --git a/app/features/team/components/TeamResultsTable.tsx b/app/features/team/components/TeamResultsTable.tsx index 862ee4632..87a0aba62 100644 --- a/app/features/team/components/TeamResultsTable.tsx +++ b/app/features/team/components/TeamResultsTable.tsx @@ -9,12 +9,8 @@ import { Table } from "~/components/Table"; import type { TeamResultsLoaderData } from "~/features/team/loaders/t.$customUrl.results.server"; import { HACKY_resolvePicture } from "~/features/tournament/tournament-utils"; import { databaseTimestampToDate } from "~/utils/dates"; -import { - tournamentLogoUrl, - tournamentTeamPage, - userPage, - userSubmittedImage, -} from "~/utils/urls"; +import { tournamentLogoUrl, tournamentTeamPage, userPage } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import styles from "./TeamResultsTable.module.css"; diff --git a/app/features/team/routes/t.$customUrl.tsx b/app/features/team/routes/t.$customUrl.tsx index 4ba91fcfd..855462273 100644 --- a/app/features/team/routes/t.$customUrl.tsx +++ b/app/features/team/routes/t.$customUrl.tsx @@ -7,13 +7,8 @@ import { BskyIcon } from "~/components/icons/Bsky"; import { Main } from "~/components/Main"; import { metaTags } from "~/utils/remix"; import type { SendouRouteHandle } from "~/utils/remix.server"; -import { - bskyUrl, - navIconUrl, - TEAM_SEARCH_PAGE, - teamPage, - userSubmittedImage, -} from "~/utils/urls"; +import { bskyUrl, navIconUrl, TEAM_SEARCH_PAGE, teamPage } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { loader } from "../loaders/t.$customUrl.server"; export { loader }; diff --git a/app/features/team/routes/t.tsx b/app/features/team/routes/t.tsx index a969e1ddd..58de22326 100644 --- a/app/features/team/routes/t.tsx +++ b/app/features/team/routes/t.tsx @@ -22,8 +22,8 @@ import { navIconUrl, TEAM_SEARCH_PAGE, teamPage, - userSubmittedImage, } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { action } from "../actions/t.server"; import { loader } from "../loaders/t.server"; import { TEAM, TEAMS_PER_PAGE } from "../team-constants"; diff --git a/app/features/tournament-bracket/core/Tournament.ts b/app/features/tournament-bracket/core/Tournament.ts index 8ed382590..cb818040d 100644 --- a/app/features/tournament-bracket/core/Tournament.ts +++ b/app/features/tournament-bracket/core/Tournament.ts @@ -24,7 +24,7 @@ import { import invariant from "~/utils/invariant"; import { logger } from "~/utils/logger"; import { assertUnreachable } from "~/utils/types"; -import { userSubmittedImage } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { fillWithNullTillPowerOfTwo, groupNumberToLetters, diff --git a/app/features/tournament-organization/TournamentOrganizationRepository.server.ts b/app/features/tournament-organization/TournamentOrganizationRepository.server.ts index 98aeec49b..3ff397183 100644 --- a/app/features/tournament-organization/TournamentOrganizationRepository.server.ts +++ b/app/features/tournament-organization/TournamentOrganizationRepository.server.ts @@ -4,7 +4,8 @@ import { db } from "~/db/sql"; import type { Tables, TablesInsertable } from "~/db/tables"; import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates"; import { COMMON_USER_FIELDS } from "~/utils/kysely.server"; -import { mySlugify, userSubmittedImage } from "~/utils/urls"; +import { mySlugify } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { HACKY_resolvePicture } from "../tournament/tournament-utils"; import { TOURNAMENT_SERIES_EVENTS_PER_PAGE } from "./tournament-organization-constants"; diff --git a/app/features/tournament-organization/routes/org.$slug.tsx b/app/features/tournament-organization/routes/org.$slug.tsx index caaccc5b1..02e64ad59 100644 --- a/app/features/tournament-organization/routes/org.$slug.tsx +++ b/app/features/tournament-organization/routes/org.$slug.tsx @@ -32,8 +32,8 @@ import { tournamentOrganizationPage, tournamentPage, userPage, - userSubmittedImage, } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { action } from "../actions/org.$slug.server"; import { EventCalendar } from "../components/EventCalendar"; import { SocialLinksList } from "../components/SocialLinksList"; diff --git a/app/features/tournament/TournamentRepository.server.ts b/app/features/tournament/TournamentRepository.server.ts index 929806ec0..315ee172b 100644 --- a/app/features/tournament/TournamentRepository.server.ts +++ b/app/features/tournament/TournamentRepository.server.ts @@ -16,7 +16,7 @@ import { nullFilledArray, nullifyingAvg } from "~/utils/arrays"; import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates"; import { COMMON_USER_FIELDS, userChatNameColor } from "~/utils/kysely.server"; import type { Unwrapped } from "~/utils/types"; -import { userSubmittedImage } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { HACKY_resolvePicture } from "./tournament-utils"; export type FindById = NonNullable>; diff --git a/app/features/tournament/routes/to.$id.register.tsx b/app/features/tournament/routes/to.$id.register.tsx index f600b7874..426e95d15 100644 --- a/app/features/tournament/routes/to.$id.register.tsx +++ b/app/features/tournament/routes/to.$id.register.tsx @@ -54,8 +54,8 @@ import { tournamentSubsPage, userEditProfilePage, userPage, - userSubmittedImage, } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { AlertIcon } from "../../../components/icons/Alert"; import { action } from "../actions/to.$id.register.server"; import type { TournamentRegisterPageLoader } from "../loaders/to.$id.register.server"; diff --git a/app/features/tournament/routes/to.$id.teams.$tid.tsx b/app/features/tournament/routes/to.$id.teams.$tid.tsx index ee2c88d22..fdb568ac6 100644 --- a/app/features/tournament/routes/to.$id.teams.$tid.tsx +++ b/app/features/tournament/routes/to.$id.teams.$tid.tsx @@ -18,8 +18,8 @@ import { tournamentMatchPage, tournamentTeamPage, userPage, - userSubmittedImage, } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { TeamWithRoster } from "../components/TeamWithRoster"; import * as Standings from "../core/Standings"; import type { PlayedSet } from "../core/sets.server"; diff --git a/app/features/tournament/routes/to.$id.tsx b/app/features/tournament/routes/to.$id.tsx index bb92c39b2..02ae36b28 100644 --- a/app/features/tournament/routes/to.$id.tsx +++ b/app/features/tournament/routes/to.$id.tsx @@ -20,8 +20,8 @@ import { tournamentOrganizationPage, tournamentPage, tournamentRegisterPage, - userSubmittedImage, } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { metaTags } from "../../../utils/remix"; import { loader, type TournamentLoaderData } from "../loaders/to.$id.server"; diff --git a/app/features/user-page/components/UserResultsTable.tsx b/app/features/user-page/components/UserResultsTable.tsx index 69234e17c..a6c0877f8 100644 --- a/app/features/user-page/components/UserResultsTable.tsx +++ b/app/features/user-page/components/UserResultsTable.tsx @@ -14,8 +14,8 @@ import { tournamentLogoUrl, tournamentTeamPage, userPage, - userSubmittedImage, } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import type { UserResultsLoaderData } from "../loaders/u.$identifier.results.server"; import { ParticipationPill } from "./ParticipationPill"; diff --git a/app/features/user-page/routes/u.$identifier.builds.new.tsx b/app/features/user-page/routes/u.$identifier.builds.new.tsx index d82cdcb7c..75437b2ed 100644 --- a/app/features/user-page/routes/u.$identifier.builds.new.tsx +++ b/app/features/user-page/routes/u.$identifier.builds.new.tsx @@ -224,7 +224,7 @@ function WeaponsSelector() { return newWeapons; }) } - initialValue={weapon ?? undefined} + value={weapon ?? null} testId={`weapon-${i}`} /> {i === weapons.length - 1 && ( diff --git a/app/features/user-page/routes/u.$identifier.index.tsx b/app/features/user-page/routes/u.$identifier.index.tsx index 0d731e796..5c7657d69 100644 --- a/app/features/user-page/routes/u.$identifier.index.tsx +++ b/app/features/user-page/routes/u.$identifier.index.tsx @@ -23,8 +23,8 @@ import { navIconUrl, teamPage, topSearchPlayerPage, - userSubmittedImage, } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { loader } from "../loaders/u.$identifier.index.server"; import type { UserPageLoaderData } from "../loaders/u.$identifier.server"; export { loader }; diff --git a/app/features/user-page/routes/u.$identifier.seasons.tsx b/app/features/user-page/routes/u.$identifier.seasons.tsx index 4dd3f2e31..31b7b7386 100644 --- a/app/features/user-page/routes/u.$identifier.seasons.tsx +++ b/app/features/user-page/routes/u.$identifier.seasons.tsx @@ -56,8 +56,8 @@ import { TIERS_PAGE, tournamentTeamPage, userSeasonsPage, - userSubmittedImage, } from "~/utils/urls"; +import { userSubmittedImage } from "~/utils/urls-img"; import { loader, type UserSeasonsPageLoaderData, diff --git a/app/features/vods/routes/vods.new.tsx b/app/features/vods/routes/vods.new.tsx index 36c2c622f..714c4087a 100644 --- a/app/features/vods/routes/vods.new.tsx +++ b/app/features/vods/routes/vods.new.tsx @@ -320,7 +320,7 @@ function MatchesFieldset({ key={i} isRequired testId={`player-${i}-weapon`} - initialValue={value[i]} + value={value[i] ?? null} onChange={(weaponId) => { const weapons = [...value]; weapons[i] = weaponId; @@ -343,7 +343,7 @@ function MatchesFieldset({ key={i} isRequired testId={`player-${adjustedI}-weapon`} - initialValue={value[adjustedI]} + value={value[adjustedI] ?? null} onChange={(weaponId) => { const weapons = [...value]; weapons[adjustedI] = weaponId; @@ -361,7 +361,7 @@ function MatchesFieldset({ label={t("vods:forms.title.weapon")} isRequired testId={`match-${idx}-weapon`} - initialValue={value[0]} + value={value[0] ?? null} onChange={(weaponId) => onChange([weaponId])} /> )} diff --git a/app/utils/urls-img.ts b/app/utils/urls-img.ts new file mode 100644 index 000000000..01f4511bb --- /dev/null +++ b/app/utils/urls-img.ts @@ -0,0 +1,15 @@ +// TODO: separating this file from urls.ts is a temporary solution. The reason is that import.meta.env cannot currently be used in files that are consumed by plain Node.js + +const USER_SUBMITTED_IMAGE_ROOT = + process.env.NODE_ENV === "development" && + import.meta.env.VITE_PROD_MODE !== "true" + ? "http://127.0.0.1:9000/sendou" + : "https://sendou.nyc3.cdn.digitaloceanspaces.com"; + +// TODO: move development images to minio and deprecate this hack +// images with https are not hosted on spaces, this is used for local development +export const conditionalUserSubmittedImage = (fileName: string) => + fileName.includes("https") ? fileName : userSubmittedImage(fileName); + +export const userSubmittedImage = (fileName: string) => + `${USER_SUBMITTED_IMAGE_ROOT}/${fileName}`; diff --git a/app/utils/urls.ts b/app/utils/urls.ts index 06b57cc63..a2d5e807a 100644 --- a/app/utils/urls.ts +++ b/app/utils/urls.ts @@ -53,14 +53,6 @@ export const BADGES_DOC_LINK = export const CREATING_TOURNAMENT_DOC_LINK = "https://github.com/sendou-ink/sendou.ink/blob/rewrite/docs/tournament-creation.md"; -const USER_SUBMITTED_IMAGE_ROOT = - "https://sendou.nyc3.cdn.digitaloceanspaces.com"; -export const userSubmittedImage = (fileName: string) => - `${USER_SUBMITTED_IMAGE_ROOT}/${fileName}`; -// images with https are not hosted on spaces, this is used for local development -export const conditionalUserSubmittedImage = (fileName: string) => - fileName.includes("https") ? fileName : userSubmittedImage(fileName); - export const PLUS_SERVER_DISCORD_URL = "https://discord.gg/FW4dKrY"; export const SENDOU_INK_DISCORD_URL = "https://discord.gg/sendou"; export const SENDOU_INK_PATREON_URL = "https://patreon.com/sendou"; diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 000000000..c8e9208bf --- /dev/null +++ b/compose.yaml @@ -0,0 +1,41 @@ +services: + minio: + image: "minio/minio:latest" + ports: + - "9000:9000" + - "9001:9001" + environment: + MINIO_ROOT_USER: minio-user + MINIO_ROOT_PASSWORD: minio-password + entrypoint: > + /bin/sh -c ' + isAlive() { curl -sf http://127.0.0.1:9000/minio/health/live; } # check if Minio is alive + minio $$0 "$$@" --quiet & echo $$! > /tmp/minio.pid # start Minio in the background + while ! isAlive; do sleep 0.1; done # wait until Minio is alive + mc alias set minio http://127.0.0.1:9000 minio-user minio-password # setup Minio client + mc mb minio/sendou || true # create a test bucket + mc anonymous set public minio/sendou # make the test bucket public + kill -s INT $$(cat /tmp/minio.pid) && rm /tmp/minio.pid # stop Minio + while isAlive; do sleep 0.1; done # wait until Minio is stopped + exec minio $$0 "$$@" # start Minio in the foreground + ' + volumes: + - ~/minio/data:/data + command: server /data --console-address ":9001" + + skalop: + image: ghcr.io/sendou-ink/skalop:latest + ports: + - "5900:5900" + environment: + - REDIS_URL=${REDIS_URL} + - SKALOP_TOKEN=${SKALOP_TOKEN} + - SESSION_SECRET=${SESSION_SECRET} + - PORT=5900 + depends_on: + - redis + restart: unless-stopped + + redis: + image: redis:alpine + restart: unless-stopped diff --git a/scripts/setup.ts b/scripts/setup.ts index 26b93611a..70893c414 100644 --- a/scripts/setup.ts +++ b/scripts/setup.ts @@ -8,8 +8,13 @@ async function main() { // Step 1: Create .env if it doesn't exist if (!fs.existsSync(".env")) { logger.info("📄 .env not found. Creating from .env.example..."); - fs.copyFileSync(".env.example", ".env"); - logger.info(".env created with defaults values"); + const envContent = fs.readFileSync(".env.example", "utf-8"); + const filteredEnv = envContent + .split("\n") + .filter((line) => !line.trim().startsWith("//")) // remove comments to prevent issues with Docker + .join("\n"); + fs.writeFileSync(".env", filteredEnv); + logger.info(".env created with default values"); } const dbEmpty = !(await db.selectFrom("User").selectAll().executeTakeFirst());