mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
Docker setup for development (#2460)
This commit is contained in:
parent
cfb103800d
commit
baa4b43855
13
.env.example
13
.env.example
|
|
@ -10,12 +10,12 @@ DISCORD_CLIENT_SECRET=
|
||||||
PATREON_ACCESS_TOKEN=
|
PATREON_ACCESS_TOKEN=
|
||||||
|
|
||||||
// Image upload
|
// Image upload
|
||||||
STORAGE_END_POINT=
|
STORAGE_END_POINT=http://127.0.0.1:9000
|
||||||
STORAGE_ACCESS_KEY=
|
STORAGE_ACCESS_KEY=minio-user
|
||||||
STORAGE_SECRET=
|
STORAGE_SECRET=minio-password
|
||||||
STORAGE_REGION=
|
STORAGE_REGION=us-east-1
|
||||||
STORAGE_BUCKET=
|
STORAGE_BUCKET=sendou
|
||||||
STORAGE_URL=
|
STORAGE_URL=http://127.0.0.1:9000
|
||||||
|
|
||||||
// Twitch integration for fetching streams
|
// Twitch integration for fetching streams
|
||||||
TWITCH_CLIENT_ID=
|
TWITCH_CLIENT_ID=
|
||||||
|
|
@ -23,6 +23,7 @@ TWITCH_CLIENT_SECRET=
|
||||||
|
|
||||||
SKALOP_SYSTEM_MESSAGE_URL=http://localhost:5900/system
|
SKALOP_SYSTEM_MESSAGE_URL=http://localhost:5900/system
|
||||||
SKALOP_TOKEN=secret
|
SKALOP_TOKEN=secret
|
||||||
|
REDIS_URL=redis://redis:6379
|
||||||
|
|
||||||
VITE_SITE_DOMAIN=http://localhost:5173
|
VITE_SITE_DOMAIN=http://localhost:5173
|
||||||
VITE_SKALOP_WS_URL=ws://localhost:5900
|
VITE_SKALOP_WS_URL=ws://localhost:5900
|
||||||
|
|
|
||||||
10
README.md
10
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.
|
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
|
## Contributing
|
||||||
|
|
||||||
- **Developers**: Read [CONTRIBUTING.md](./CONTRIBUTING.md)
|
- **Developers**: Read [CONTRIBUTING.md](./CONTRIBUTING.md)
|
||||||
|
|
|
||||||
|
|
@ -1616,25 +1616,12 @@ const detailedTeam = (seedVariation?: SeedVariation | null) => () => {
|
||||||
sql
|
sql
|
||||||
.prepare(
|
.prepare(
|
||||||
/* sql */ `
|
/* sql */ `
|
||||||
insert into "UnvalidatedUserSubmittedImage" ("validatedAt", "url", "submitterUserId")
|
insert into "AllTeam" ("name", "customUrl", "inviteCode", "bio")
|
||||||
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")
|
|
||||||
values (
|
values (
|
||||||
'Alliance Rogue',
|
'Alliance Rogue',
|
||||||
'alliance-rogue',
|
'alliance-rogue',
|
||||||
'${shortNanoid()}',
|
'${shortNanoid()}',
|
||||||
'${faker.lorem.paragraph()}',
|
'${faker.lorem.paragraph()}'
|
||||||
1,
|
|
||||||
2
|
|
||||||
)
|
)
|
||||||
`,
|
`,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { cors } from "remix-utils/cors";
|
||||||
import { z } from "zod/v4";
|
import { z } from "zod/v4";
|
||||||
import { db } from "~/db/sql";
|
import { db } from "~/db/sql";
|
||||||
import { notFoundIfFalsy, parseParams } from "~/utils/remix.server";
|
import { notFoundIfFalsy, parseParams } from "~/utils/remix.server";
|
||||||
import { userSubmittedImage } from "~/utils/urls";
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
import { id } from "~/utils/zod";
|
import { id } from "~/utils/zod";
|
||||||
import {
|
import {
|
||||||
handleOptionsRequest,
|
handleOptionsRequest,
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import i18next from "~/modules/i18n/i18next.server";
|
||||||
import { nullifyingAvg } from "~/utils/arrays";
|
import { nullifyingAvg } from "~/utils/arrays";
|
||||||
import { databaseTimestampToDate } from "~/utils/dates";
|
import { databaseTimestampToDate } from "~/utils/dates";
|
||||||
import { parseParams } from "~/utils/remix.server";
|
import { parseParams } from "~/utils/remix.server";
|
||||||
import { userSubmittedImage } from "~/utils/urls";
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
import { id } from "~/utils/zod";
|
import { id } from "~/utils/zod";
|
||||||
import {
|
import {
|
||||||
handleOptionsRequest,
|
handleOptionsRequest,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import { db } from "~/db/sql";
|
||||||
import { HACKY_resolvePicture } from "~/features/tournament/tournament-utils";
|
import { HACKY_resolvePicture } from "~/features/tournament/tournament-utils";
|
||||||
import { databaseTimestampToDate } from "~/utils/dates";
|
import { databaseTimestampToDate } from "~/utils/dates";
|
||||||
import { notFoundIfFalsy, parseParams } from "~/utils/remix.server";
|
import { notFoundIfFalsy, parseParams } from "~/utils/remix.server";
|
||||||
import { userSubmittedImage } from "~/utils/urls";
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
import { id } from "~/utils/zod";
|
import { id } from "~/utils/zod";
|
||||||
import {
|
import {
|
||||||
handleOptionsRequest,
|
handleOptionsRequest,
|
||||||
|
|
|
||||||
|
|
@ -15,13 +15,8 @@ import { useIsMounted } from "~/hooks/useIsMounted";
|
||||||
import { usePagination } from "~/hooks/usePagination";
|
import { usePagination } from "~/hooks/usePagination";
|
||||||
import { useSearchParamState } from "~/hooks/useSearchParamState";
|
import { useSearchParamState } from "~/hooks/useSearchParamState";
|
||||||
import { databaseTimestampToDate } from "~/utils/dates";
|
import { databaseTimestampToDate } from "~/utils/dates";
|
||||||
import {
|
import { artPage, newArtPage, userArtPage, userPage } from "~/utils/urls";
|
||||||
artPage,
|
import { conditionalUserSubmittedImage } from "~/utils/urls-img";
|
||||||
conditionalUserSubmittedImage,
|
|
||||||
newArtPage,
|
|
||||||
userArtPage,
|
|
||||||
userPage,
|
|
||||||
} from "~/utils/urls";
|
|
||||||
import { ResponsiveMasonry } from "../../../modules/responsive-masonry/components/ResponsiveMasonry";
|
import { ResponsiveMasonry } from "../../../modules/responsive-masonry/components/ResponsiveMasonry";
|
||||||
import { ART_PER_PAGE } from "../art-constants";
|
import { ART_PER_PAGE } from "../art-constants";
|
||||||
import type { ListedArt } from "../art-types";
|
import type { ListedArt } from "../art-types";
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,8 @@ import { useHasRole } from "~/modules/permissions/hooks";
|
||||||
import invariant from "~/utils/invariant";
|
import invariant from "~/utils/invariant";
|
||||||
import { logger } from "~/utils/logger";
|
import { logger } from "~/utils/logger";
|
||||||
import type { SendouRouteHandle } from "~/utils/remix.server";
|
import type { SendouRouteHandle } from "~/utils/remix.server";
|
||||||
import {
|
import { artPage, navIconUrl } from "~/utils/urls";
|
||||||
artPage,
|
import { conditionalUserSubmittedImage } from "~/utils/urls-img";
|
||||||
conditionalUserSubmittedImage,
|
|
||||||
navIconUrl,
|
|
||||||
} from "~/utils/urls";
|
|
||||||
import { metaTitle } from "../../../utils/remix";
|
import { metaTitle } from "../../../utils/remix";
|
||||||
import { action } from "../actions/art.new.server";
|
import { action } from "../actions/art.new.server";
|
||||||
import { ART } from "../art-constants";
|
import { ART } from "../art-constants";
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ import { BadgeDisplay } from "~/features/badges/components/BadgeDisplay";
|
||||||
import { HACKY_resolvePicture } from "~/features/tournament/tournament-utils";
|
import { HACKY_resolvePicture } from "~/features/tournament/tournament-utils";
|
||||||
import { useIsMounted } from "~/hooks/useIsMounted";
|
import { useIsMounted } from "~/hooks/useIsMounted";
|
||||||
import { databaseTimestampToDate } from "~/utils/dates";
|
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 type { CalendarEvent, ShowcaseCalendarEvent } from "../calendar-types";
|
||||||
import { Tags } from "./Tags";
|
import { Tags } from "./Tags";
|
||||||
import styles from "./TournamentCard.module.css";
|
import styles from "./TournamentCard.module.css";
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,8 @@ import {
|
||||||
import invariant from "~/utils/invariant";
|
import invariant from "~/utils/invariant";
|
||||||
import type { SendouRouteHandle } from "~/utils/remix.server";
|
import type { SendouRouteHandle } from "~/utils/remix.server";
|
||||||
import { pathnameFromPotentialURL } from "~/utils/strings";
|
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 {
|
import {
|
||||||
CALENDAR_EVENT,
|
CALENDAR_EVENT,
|
||||||
REG_CLOSES_AT_OPTIONS,
|
REG_CLOSES_AT_OPTIONS,
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,8 @@ import { cachedFullUserLeaderboard } from "~/features/leaderboards/core/leaderbo
|
||||||
import * as LeaderboardRepository from "~/features/leaderboards/LeaderboardRepository.server";
|
import * as LeaderboardRepository from "~/features/leaderboards/LeaderboardRepository.server";
|
||||||
import * as Seasons from "~/features/mmr/core/Seasons";
|
import * as Seasons from "~/features/mmr/core/Seasons";
|
||||||
import { cache, IN_MILLISECONDS, ttl } from "~/utils/cache.server";
|
import { cache, IN_MILLISECONDS, ttl } from "~/utils/cache.server";
|
||||||
import {
|
import { discordAvatarUrl, teamPage, userPage } from "~/utils/urls";
|
||||||
discordAvatarUrl,
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
teamPage,
|
|
||||||
userPage,
|
|
||||||
userSubmittedImage,
|
|
||||||
} from "~/utils/urls";
|
|
||||||
import * as ShowcaseTournaments from "../core/ShowcaseTournaments.server";
|
import * as ShowcaseTournaments from "../core/ShowcaseTournaments.server";
|
||||||
|
|
||||||
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
export const loader = async ({ request }: LoaderFunctionArgs) => {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import { FormWithConfirm } from "~/components/FormWithConfirm";
|
||||||
import { TrashIcon } from "~/components/icons/Trash";
|
import { TrashIcon } from "~/components/icons/Trash";
|
||||||
import { Main } from "~/components/Main";
|
import { Main } from "~/components/Main";
|
||||||
import { SubmitButton } from "~/components/SubmitButton";
|
import { SubmitButton } from "~/components/SubmitButton";
|
||||||
import { userSubmittedImage } from "~/utils/urls";
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
|
|
||||||
import { action } from "../actions/upload.admin.server";
|
import { action } from "../actions/upload.admin.server";
|
||||||
import { loader } from "../loaders/upload.admin.server";
|
import { loader } from "../loaders/upload.admin.server";
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ import {
|
||||||
topSearchPlayerPage,
|
topSearchPlayerPage,
|
||||||
userPage,
|
userPage,
|
||||||
userSeasonsPage,
|
userSeasonsPage,
|
||||||
userSubmittedImage,
|
|
||||||
} from "~/utils/urls";
|
} from "~/utils/urls";
|
||||||
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
import { InfoPopover } from "../../../components/InfoPopover";
|
import { InfoPopover } from "../../../components/InfoPopover";
|
||||||
import { TopTenPlayer } from "../components/TopTenPlayer";
|
import { TopTenPlayer } from "../components/TopTenPlayer";
|
||||||
import {
|
import {
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,8 @@ import type { TieredSkill } from "~/features/mmr/tiered.server";
|
||||||
import { useIsMounted } from "~/hooks/useIsMounted";
|
import { useIsMounted } from "~/hooks/useIsMounted";
|
||||||
import { useHasRole } from "~/modules/permissions/hooks";
|
import { useHasRole } from "~/modules/permissions/hooks";
|
||||||
import { databaseTimestampToDate } from "~/utils/dates";
|
import { databaseTimestampToDate } from "~/utils/dates";
|
||||||
import {
|
import { lfgNewPostPage, navIconUrl, userPage } from "~/utils/urls";
|
||||||
lfgNewPostPage,
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
navIconUrl,
|
|
||||||
userPage,
|
|
||||||
userSubmittedImage,
|
|
||||||
} from "~/utils/urls";
|
|
||||||
import { hourDifferenceBetweenTimezones } from "../core/timezone";
|
import { hourDifferenceBetweenTimezones } from "../core/timezone";
|
||||||
import type { LFGLoaderData, TiersMap } from "../routes/lfg";
|
import type { LFGLoaderData, TiersMap } from "../routes/lfg";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import { cancelScrimSchema } from "~/features/scrims/scrims-schemas";
|
||||||
import { resolveRoomPass } from "~/features/tournament-bracket/tournament-bracket-utils";
|
import { resolveRoomPass } from "~/features/tournament-bracket/tournament-bracket-utils";
|
||||||
import { useHasPermission } from "~/modules/permissions/hooks";
|
import { useHasPermission } from "~/modules/permissions/hooks";
|
||||||
import type { SendouRouteHandle } from "~/utils/remix.server";
|
import type { SendouRouteHandle } from "~/utils/remix.server";
|
||||||
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
import { Avatar } from "../../../components/Avatar";
|
import { Avatar } from "../../../components/Avatar";
|
||||||
import { Main } from "../../../components/Main";
|
import { Main } from "../../../components/Main";
|
||||||
import { databaseTimestampToDate } from "../../../utils/dates";
|
import { databaseTimestampToDate } from "../../../utils/dates";
|
||||||
|
|
@ -22,7 +23,6 @@ import {
|
||||||
scrimsPage,
|
scrimsPage,
|
||||||
teamPage,
|
teamPage,
|
||||||
userPage,
|
userPage,
|
||||||
userSubmittedImage,
|
|
||||||
} from "../../../utils/urls";
|
} from "../../../utils/urls";
|
||||||
import { ConnectedChat } from "../../chat/components/Chat";
|
import { ConnectedChat } from "../../chat/components/Chat";
|
||||||
import { action } from "../actions/scrims.$id.server";
|
import { action } from "../actions/scrims.$id.server";
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,8 @@ import {
|
||||||
scrimPage,
|
scrimPage,
|
||||||
scrimsPage,
|
scrimsPage,
|
||||||
userPage,
|
userPage,
|
||||||
userSubmittedImage,
|
|
||||||
} from "~/utils/urls";
|
} from "~/utils/urls";
|
||||||
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
import {
|
import {
|
||||||
SendouTab,
|
SendouTab,
|
||||||
SendouTabList,
|
SendouTabList,
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,8 @@ import {
|
||||||
sendouQMatchPage,
|
sendouQMatchPage,
|
||||||
specialWeaponImageUrl,
|
specialWeaponImageUrl,
|
||||||
teamPage,
|
teamPage,
|
||||||
userSubmittedImage,
|
|
||||||
} from "~/utils/urls";
|
} from "~/utils/urls";
|
||||||
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
import { action } from "../actions/q.match.$id.server";
|
import { action } from "../actions/q.match.$id.server";
|
||||||
import { matchEndedAtIndex } from "../core/match";
|
import { matchEndedAtIndex } from "../core/match";
|
||||||
import { loader } from "../loaders/q.match.$id.server";
|
import { loader } from "../loaders/q.match.$id.server";
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,8 @@ import { Table } from "~/components/Table";
|
||||||
import type { TeamResultsLoaderData } from "~/features/team/loaders/t.$customUrl.results.server";
|
import type { TeamResultsLoaderData } from "~/features/team/loaders/t.$customUrl.results.server";
|
||||||
import { HACKY_resolvePicture } from "~/features/tournament/tournament-utils";
|
import { HACKY_resolvePicture } from "~/features/tournament/tournament-utils";
|
||||||
import { databaseTimestampToDate } from "~/utils/dates";
|
import { databaseTimestampToDate } from "~/utils/dates";
|
||||||
import {
|
import { tournamentLogoUrl, tournamentTeamPage, userPage } from "~/utils/urls";
|
||||||
tournamentLogoUrl,
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
tournamentTeamPage,
|
|
||||||
userPage,
|
|
||||||
userSubmittedImage,
|
|
||||||
} from "~/utils/urls";
|
|
||||||
|
|
||||||
import styles from "./TeamResultsTable.module.css";
|
import styles from "./TeamResultsTable.module.css";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,8 @@ import { BskyIcon } from "~/components/icons/Bsky";
|
||||||
import { Main } from "~/components/Main";
|
import { Main } from "~/components/Main";
|
||||||
import { metaTags } from "~/utils/remix";
|
import { metaTags } from "~/utils/remix";
|
||||||
import type { SendouRouteHandle } from "~/utils/remix.server";
|
import type { SendouRouteHandle } from "~/utils/remix.server";
|
||||||
import {
|
import { bskyUrl, navIconUrl, TEAM_SEARCH_PAGE, teamPage } from "~/utils/urls";
|
||||||
bskyUrl,
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
navIconUrl,
|
|
||||||
TEAM_SEARCH_PAGE,
|
|
||||||
teamPage,
|
|
||||||
userSubmittedImage,
|
|
||||||
} from "~/utils/urls";
|
|
||||||
import { loader } from "../loaders/t.$customUrl.server";
|
import { loader } from "../loaders/t.$customUrl.server";
|
||||||
export { loader };
|
export { loader };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,8 @@ import {
|
||||||
navIconUrl,
|
navIconUrl,
|
||||||
TEAM_SEARCH_PAGE,
|
TEAM_SEARCH_PAGE,
|
||||||
teamPage,
|
teamPage,
|
||||||
userSubmittedImage,
|
|
||||||
} from "~/utils/urls";
|
} from "~/utils/urls";
|
||||||
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
import { action } from "../actions/t.server";
|
import { action } from "../actions/t.server";
|
||||||
import { loader } from "../loaders/t.server";
|
import { loader } from "../loaders/t.server";
|
||||||
import { TEAM, TEAMS_PER_PAGE } from "../team-constants";
|
import { TEAM, TEAMS_PER_PAGE } from "../team-constants";
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import {
|
||||||
import invariant from "~/utils/invariant";
|
import invariant from "~/utils/invariant";
|
||||||
import { logger } from "~/utils/logger";
|
import { logger } from "~/utils/logger";
|
||||||
import { assertUnreachable } from "~/utils/types";
|
import { assertUnreachable } from "~/utils/types";
|
||||||
import { userSubmittedImage } from "~/utils/urls";
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
import {
|
import {
|
||||||
fillWithNullTillPowerOfTwo,
|
fillWithNullTillPowerOfTwo,
|
||||||
groupNumberToLetters,
|
groupNumberToLetters,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ import { db } from "~/db/sql";
|
||||||
import type { Tables, TablesInsertable } from "~/db/tables";
|
import type { Tables, TablesInsertable } from "~/db/tables";
|
||||||
import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates";
|
import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates";
|
||||||
import { COMMON_USER_FIELDS } from "~/utils/kysely.server";
|
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 { HACKY_resolvePicture } from "../tournament/tournament-utils";
|
||||||
import { TOURNAMENT_SERIES_EVENTS_PER_PAGE } from "./tournament-organization-constants";
|
import { TOURNAMENT_SERIES_EVENTS_PER_PAGE } from "./tournament-organization-constants";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ import {
|
||||||
tournamentOrganizationPage,
|
tournamentOrganizationPage,
|
||||||
tournamentPage,
|
tournamentPage,
|
||||||
userPage,
|
userPage,
|
||||||
userSubmittedImage,
|
|
||||||
} from "~/utils/urls";
|
} from "~/utils/urls";
|
||||||
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
import { action } from "../actions/org.$slug.server";
|
import { action } from "../actions/org.$slug.server";
|
||||||
import { EventCalendar } from "../components/EventCalendar";
|
import { EventCalendar } from "../components/EventCalendar";
|
||||||
import { SocialLinksList } from "../components/SocialLinksList";
|
import { SocialLinksList } from "../components/SocialLinksList";
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import { nullFilledArray, nullifyingAvg } from "~/utils/arrays";
|
||||||
import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates";
|
import { databaseTimestampNow, dateToDatabaseTimestamp } from "~/utils/dates";
|
||||||
import { COMMON_USER_FIELDS, userChatNameColor } from "~/utils/kysely.server";
|
import { COMMON_USER_FIELDS, userChatNameColor } from "~/utils/kysely.server";
|
||||||
import type { Unwrapped } from "~/utils/types";
|
import type { Unwrapped } from "~/utils/types";
|
||||||
import { userSubmittedImage } from "~/utils/urls";
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
import { HACKY_resolvePicture } from "./tournament-utils";
|
import { HACKY_resolvePicture } from "./tournament-utils";
|
||||||
|
|
||||||
export type FindById = NonNullable<Unwrapped<typeof findById>>;
|
export type FindById = NonNullable<Unwrapped<typeof findById>>;
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,8 @@ import {
|
||||||
tournamentSubsPage,
|
tournamentSubsPage,
|
||||||
userEditProfilePage,
|
userEditProfilePage,
|
||||||
userPage,
|
userPage,
|
||||||
userSubmittedImage,
|
|
||||||
} from "~/utils/urls";
|
} from "~/utils/urls";
|
||||||
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
import { AlertIcon } from "../../../components/icons/Alert";
|
import { AlertIcon } from "../../../components/icons/Alert";
|
||||||
import { action } from "../actions/to.$id.register.server";
|
import { action } from "../actions/to.$id.register.server";
|
||||||
import type { TournamentRegisterPageLoader } from "../loaders/to.$id.register.server";
|
import type { TournamentRegisterPageLoader } from "../loaders/to.$id.register.server";
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ import {
|
||||||
tournamentMatchPage,
|
tournamentMatchPage,
|
||||||
tournamentTeamPage,
|
tournamentTeamPage,
|
||||||
userPage,
|
userPage,
|
||||||
userSubmittedImage,
|
|
||||||
} from "~/utils/urls";
|
} from "~/utils/urls";
|
||||||
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
import { TeamWithRoster } from "../components/TeamWithRoster";
|
import { TeamWithRoster } from "../components/TeamWithRoster";
|
||||||
import * as Standings from "../core/Standings";
|
import * as Standings from "../core/Standings";
|
||||||
import type { PlayedSet } from "../core/sets.server";
|
import type { PlayedSet } from "../core/sets.server";
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ import {
|
||||||
tournamentOrganizationPage,
|
tournamentOrganizationPage,
|
||||||
tournamentPage,
|
tournamentPage,
|
||||||
tournamentRegisterPage,
|
tournamentRegisterPage,
|
||||||
userSubmittedImage,
|
|
||||||
} from "~/utils/urls";
|
} from "~/utils/urls";
|
||||||
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
import { metaTags } from "../../../utils/remix";
|
import { metaTags } from "../../../utils/remix";
|
||||||
|
|
||||||
import { loader, type TournamentLoaderData } from "../loaders/to.$id.server";
|
import { loader, type TournamentLoaderData } from "../loaders/to.$id.server";
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ import {
|
||||||
tournamentLogoUrl,
|
tournamentLogoUrl,
|
||||||
tournamentTeamPage,
|
tournamentTeamPage,
|
||||||
userPage,
|
userPage,
|
||||||
userSubmittedImage,
|
|
||||||
} from "~/utils/urls";
|
} from "~/utils/urls";
|
||||||
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
import type { UserResultsLoaderData } from "../loaders/u.$identifier.results.server";
|
import type { UserResultsLoaderData } from "../loaders/u.$identifier.results.server";
|
||||||
import { ParticipationPill } from "./ParticipationPill";
|
import { ParticipationPill } from "./ParticipationPill";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -224,7 +224,7 @@ function WeaponsSelector() {
|
||||||
return newWeapons;
|
return newWeapons;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
initialValue={weapon ?? undefined}
|
value={weapon ?? null}
|
||||||
testId={`weapon-${i}`}
|
testId={`weapon-${i}`}
|
||||||
/>
|
/>
|
||||||
{i === weapons.length - 1 && (
|
{i === weapons.length - 1 && (
|
||||||
|
|
|
||||||
|
|
@ -23,8 +23,8 @@ import {
|
||||||
navIconUrl,
|
navIconUrl,
|
||||||
teamPage,
|
teamPage,
|
||||||
topSearchPlayerPage,
|
topSearchPlayerPage,
|
||||||
userSubmittedImage,
|
|
||||||
} from "~/utils/urls";
|
} from "~/utils/urls";
|
||||||
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
import { loader } from "../loaders/u.$identifier.index.server";
|
import { loader } from "../loaders/u.$identifier.index.server";
|
||||||
import type { UserPageLoaderData } from "../loaders/u.$identifier.server";
|
import type { UserPageLoaderData } from "../loaders/u.$identifier.server";
|
||||||
export { loader };
|
export { loader };
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,8 @@ import {
|
||||||
TIERS_PAGE,
|
TIERS_PAGE,
|
||||||
tournamentTeamPage,
|
tournamentTeamPage,
|
||||||
userSeasonsPage,
|
userSeasonsPage,
|
||||||
userSubmittedImage,
|
|
||||||
} from "~/utils/urls";
|
} from "~/utils/urls";
|
||||||
|
import { userSubmittedImage } from "~/utils/urls-img";
|
||||||
import {
|
import {
|
||||||
loader,
|
loader,
|
||||||
type UserSeasonsPageLoaderData,
|
type UserSeasonsPageLoaderData,
|
||||||
|
|
|
||||||
|
|
@ -320,7 +320,7 @@ function MatchesFieldset({
|
||||||
key={i}
|
key={i}
|
||||||
isRequired
|
isRequired
|
||||||
testId={`player-${i}-weapon`}
|
testId={`player-${i}-weapon`}
|
||||||
initialValue={value[i]}
|
value={value[i] ?? null}
|
||||||
onChange={(weaponId) => {
|
onChange={(weaponId) => {
|
||||||
const weapons = [...value];
|
const weapons = [...value];
|
||||||
weapons[i] = weaponId;
|
weapons[i] = weaponId;
|
||||||
|
|
@ -343,7 +343,7 @@ function MatchesFieldset({
|
||||||
key={i}
|
key={i}
|
||||||
isRequired
|
isRequired
|
||||||
testId={`player-${adjustedI}-weapon`}
|
testId={`player-${adjustedI}-weapon`}
|
||||||
initialValue={value[adjustedI]}
|
value={value[adjustedI] ?? null}
|
||||||
onChange={(weaponId) => {
|
onChange={(weaponId) => {
|
||||||
const weapons = [...value];
|
const weapons = [...value];
|
||||||
weapons[adjustedI] = weaponId;
|
weapons[adjustedI] = weaponId;
|
||||||
|
|
@ -361,7 +361,7 @@ function MatchesFieldset({
|
||||||
label={t("vods:forms.title.weapon")}
|
label={t("vods:forms.title.weapon")}
|
||||||
isRequired
|
isRequired
|
||||||
testId={`match-${idx}-weapon`}
|
testId={`match-${idx}-weapon`}
|
||||||
initialValue={value[0]}
|
value={value[0] ?? null}
|
||||||
onChange={(weaponId) => onChange([weaponId])}
|
onChange={(weaponId) => onChange([weaponId])}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
15
app/utils/urls-img.ts
Normal file
15
app/utils/urls-img.ts
Normal file
|
|
@ -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}`;
|
||||||
|
|
@ -53,14 +53,6 @@ export const BADGES_DOC_LINK =
|
||||||
export const CREATING_TOURNAMENT_DOC_LINK =
|
export const CREATING_TOURNAMENT_DOC_LINK =
|
||||||
"https://github.com/sendou-ink/sendou.ink/blob/rewrite/docs/tournament-creation.md";
|
"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 PLUS_SERVER_DISCORD_URL = "https://discord.gg/FW4dKrY";
|
||||||
export const SENDOU_INK_DISCORD_URL = "https://discord.gg/sendou";
|
export const SENDOU_INK_DISCORD_URL = "https://discord.gg/sendou";
|
||||||
export const SENDOU_INK_PATREON_URL = "https://patreon.com/sendou";
|
export const SENDOU_INK_PATREON_URL = "https://patreon.com/sendou";
|
||||||
|
|
|
||||||
41
compose.yaml
Normal file
41
compose.yaml
Normal file
|
|
@ -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
|
||||||
|
|
@ -8,8 +8,13 @@ async function main() {
|
||||||
// Step 1: Create .env if it doesn't exist
|
// Step 1: Create .env if it doesn't exist
|
||||||
if (!fs.existsSync(".env")) {
|
if (!fs.existsSync(".env")) {
|
||||||
logger.info("📄 .env not found. Creating from .env.example...");
|
logger.info("📄 .env not found. Creating from .env.example...");
|
||||||
fs.copyFileSync(".env.example", ".env");
|
const envContent = fs.readFileSync(".env.example", "utf-8");
|
||||||
logger.info(".env created with defaults values");
|
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());
|
const dbEmpty = !(await db.selectFrom("User").selectAll().executeTakeFirst());
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user