mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-03-21 18:04:39 -05:00
Tournament data from tRPC
This commit is contained in:
parent
ca16aa5d07
commit
a8a255fba6
10
Routes.tsx
10
Routes.tsx
|
|
@ -1,6 +1,6 @@
|
|||
import { Routes as SolidAppRoutes, Route } from "solid-app-router";
|
||||
import { lazy } from "solid-js";
|
||||
import HelloData from "./scenes/tournament/components/TournamentsPage.data";
|
||||
import TournamentData from "./scenes/tournament/components/TournamentsPage.data";
|
||||
|
||||
const TournamentsPage = lazy(
|
||||
() => import("./scenes/tournament/components/TournamentsPage")
|
||||
|
|
@ -9,15 +9,11 @@ const TournamentsPage = lazy(
|
|||
export function Routes() {
|
||||
return (
|
||||
<SolidAppRoutes>
|
||||
{/* <Route path="/users/:id" element={<User />}>
|
||||
<Route path="/" element={<UserHome />} />
|
||||
<Route path="/settings" element={<UserSettings />} />
|
||||
<Route path="/*all" element={<UserNotFound />} />
|
||||
</Route> */}
|
||||
<Route
|
||||
path="/to/:identifier"
|
||||
element={<TournamentsPage />}
|
||||
data={HelloData}
|
||||
// TODO: fix type error
|
||||
data={TournamentData as any}
|
||||
>
|
||||
<Route path="/*all" element={() => <>overview</>} />
|
||||
<Route path="/map-pool" element={() => <>map pool</>} />
|
||||
|
|
|
|||
2046
package-lock.json
generated
2046
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
40
package.json
40
package.json
|
|
@ -1,22 +1,42 @@
|
|||
{
|
||||
"name": "sendou.ink",
|
||||
"type": "module",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"serve": "vite preview"
|
||||
"dev": "concurrently \"npm run dev:frontend\" \"npm run dev:server\"",
|
||||
"dev:frontend": "vite",
|
||||
"dev:server": "node --experimental-specifier-resolution=node --loader ts-node/esm server.ts",
|
||||
"build:frontend": "vite build",
|
||||
"migration:create": "npx prisma migrate dev --create-only",
|
||||
"migration:apply:dev": "npx prisma migrate dev",
|
||||
"migration:apply:prod": "npx prisma migrate deploy",
|
||||
"seed": "npx prisma db seed",
|
||||
"seed:reset": "npx prisma migrate reset"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@prisma/client": "^3.4.2",
|
||||
"@trpc/client": "^9.13.0",
|
||||
"@trpc/server": "^9.13.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"normalize.css": "^8.0.1",
|
||||
"solid-app-router": "^0.1.11",
|
||||
"solid-js": "^1.1.3",
|
||||
"zod": "^3.11.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.4.3",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/node": "^16.11.7",
|
||||
"concurrently": "^6.4.0",
|
||||
"prisma": "^3.4.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"typescript": "^4.4.4",
|
||||
"vite": "^2.5.7",
|
||||
"vite-plugin-solid": "^2.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@trpc/client": "^9.13.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"solid-app-router": "^0.1.11",
|
||||
"solid-js": "^1.1.3"
|
||||
"prisma": {
|
||||
"seed": "node --experimental-specifier-resolution=node --loader ts-node/esm prisma/seed.ts"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
6
prisma/client.ts
Normal file
6
prisma/client.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import pkg from "@prisma/client";
|
||||
const { PrismaClient } = pkg;
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export default prisma;
|
||||
92
prisma/migrations/20211114103710_initial/migration.sql
Normal file
92
prisma/migrations/20211114103710_initial/migration.sql
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
-- CreateEnum
|
||||
CREATE TYPE "Mode" AS ENUM ('TW', 'SZ', 'TC', 'RM', 'CB');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"discordId" TEXT NOT NULL,
|
||||
"discordName" TEXT NOT NULL,
|
||||
"discordDiscriminator" TEXT NOT NULL,
|
||||
"discordAvatar" TEXT,
|
||||
"discordRefreshToken" TEXT NOT NULL,
|
||||
"twitch" TEXT,
|
||||
"twitter" TEXT,
|
||||
"youtubeId" TEXT,
|
||||
"youtubeName" TEXT,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Organization" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"nameForUrl" TEXT NOT NULL,
|
||||
"ownerId" INTEGER NOT NULL,
|
||||
"discordInvite" TEXT NOT NULL,
|
||||
"twitter" TEXT,
|
||||
|
||||
CONSTRAINT "Organization_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Tournament" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"nameForUrl" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"startTime" TIMESTAMP(3) NOT NULL,
|
||||
"checkInTime" TIMESTAMP(3) NOT NULL,
|
||||
"bannerBackground" TEXT NOT NULL,
|
||||
"bannerTextHSLArgs" TEXT NOT NULL,
|
||||
"organizerId" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "Tournament_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Stage" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"mode" "Mode" NOT NULL,
|
||||
|
||||
CONSTRAINT "Stage_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_StageToTournament" (
|
||||
"A" INTEGER NOT NULL,
|
||||
"B" INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_discordId_key" ON "User"("discordId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Organization_nameForUrl_key" ON "Organization"("nameForUrl");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Organization_ownerId_key" ON "Organization"("ownerId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Tournament_nameForUrl_organizerId_key" ON "Tournament"("nameForUrl", "organizerId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_StageToTournament_AB_unique" ON "_StageToTournament"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_StageToTournament_B_index" ON "_StageToTournament"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Organization" ADD CONSTRAINT "Organization_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Tournament" ADD CONSTRAINT "Tournament_organizerId_fkey" FOREIGN KEY ("organizerId") REFERENCES "Organization"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_StageToTournament" ADD FOREIGN KEY ("A") REFERENCES "Stage"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_StageToTournament" ADD FOREIGN KEY ("B") REFERENCES "Tournament"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
70
prisma/schema.prisma
Normal file
70
prisma/schema.prisma
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
discordId String @unique
|
||||
discordName String
|
||||
discordDiscriminator String
|
||||
discordAvatar String?
|
||||
discordRefreshToken String
|
||||
twitch String?
|
||||
twitter String?
|
||||
youtubeId String?
|
||||
youtubeName String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
ownedOrganization Organization?
|
||||
}
|
||||
|
||||
model Organization {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
/// Name in lower case to show in URL
|
||||
nameForUrl String @unique
|
||||
ownerId Int @unique
|
||||
owner User @relation(fields: [ownerId], references: [id])
|
||||
discordInvite String
|
||||
twitter String?
|
||||
tournaments Tournament[]
|
||||
}
|
||||
|
||||
model Tournament {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
/// Name in lower case to show in URL
|
||||
nameForUrl String
|
||||
description String?
|
||||
startTime DateTime
|
||||
checkInTime DateTime
|
||||
/// CSS for tournament banner's background value
|
||||
bannerBackground String
|
||||
/// CSS for tournament banner's color value
|
||||
bannerTextHSLArgs String
|
||||
mapPool Stage[]
|
||||
organizerId Int
|
||||
organizer Organization @relation(fields: [organizerId], references: [id])
|
||||
// There might be duplicate nameForUrl's but inside an organization they're unique
|
||||
@@unique([nameForUrl, organizerId])
|
||||
}
|
||||
|
||||
enum Mode {
|
||||
TW
|
||||
SZ
|
||||
TC
|
||||
RM
|
||||
CB
|
||||
}
|
||||
|
||||
model Stage {
|
||||
id Int @id @default(autoincrement())
|
||||
name String
|
||||
mode Mode
|
||||
tournamentMapPools Tournament[]
|
||||
}
|
||||
104
prisma/seed.ts
Normal file
104
prisma/seed.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import pkg from "@prisma/client";
|
||||
import { stages as stagesList } from "../utils/constants";
|
||||
const { PrismaClient } = pkg;
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
const user = await users();
|
||||
const organization = await organizations(user.id);
|
||||
const tournament = await tournaments(organization.id);
|
||||
await stages();
|
||||
await tournamentAddMaps(tournament.id);
|
||||
}
|
||||
|
||||
async function users() {
|
||||
return prisma.user.create({
|
||||
data: {
|
||||
discordDiscriminator: "4059",
|
||||
discordId: "79237403620945920",
|
||||
discordName: "Sendou",
|
||||
discordRefreshToken: "none",
|
||||
twitch: "Sendou",
|
||||
youtubeId: "UCWbJLXByvsfQvTcR4HLPs5Q",
|
||||
youtubeName: "Sendou",
|
||||
discordAvatar: "fcfd65a3bea598905abb9ca25296816b",
|
||||
twitter: "sendouc",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function organizations(userId: number) {
|
||||
return prisma.organization.create({
|
||||
data: {
|
||||
name: "Sendou's Tournaments",
|
||||
discordInvite: "sendou",
|
||||
nameForUrl: "sendou",
|
||||
twitter: "sendouc",
|
||||
ownerId: userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const modesList = ["TW", "SZ", "TC", "RM", "CB"] as const;
|
||||
|
||||
async function tournaments(organizationId: number) {
|
||||
return prisma.tournament.create({
|
||||
data: {
|
||||
bannerBackground: "linear-gradient(to bottom, #9796f0, #fbc7d4)",
|
||||
bannerTextHSLArgs: "231, 9%, 16%",
|
||||
checkInTime: new Date(2025, 11, 17, 11),
|
||||
startTime: new Date(2025, 11, 17, 12),
|
||||
name: "In The Zone X",
|
||||
nameForUrl: "in-the-zone-x",
|
||||
organizerId: organizationId,
|
||||
description: "In The Zone eXtreme",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function getRandomInt(maxInclusive: number) {
|
||||
let result = -1;
|
||||
|
||||
while (result < 24) {
|
||||
result = Math.floor(Math.random() * maxInclusive) + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: why this can't be done while creating?
|
||||
async function tournamentAddMaps(id: number) {
|
||||
const ids = Array.from(
|
||||
new Set(new Array(24).fill(null).map(() => ({ id: getRandomInt(115) })))
|
||||
);
|
||||
|
||||
return prisma.tournament.update({
|
||||
where: { id },
|
||||
data: {
|
||||
mapPool: {
|
||||
connect: ids,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function stages() {
|
||||
return prisma.stage.createMany({
|
||||
data: modesList.flatMap((mode) => {
|
||||
return stagesList.map((name) => {
|
||||
return {
|
||||
name,
|
||||
mode,
|
||||
};
|
||||
});
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
|
@ -1,19 +1,19 @@
|
|||
import { useData } from "solid-app-router";
|
||||
import { createResource } from "solid-js";
|
||||
import { trpcClient } from "../../../utils/trpc";
|
||||
import { InferQueryOutput, trpcClient } from "../../../utils/trpc-client";
|
||||
|
||||
function fetchHello(input: string) {
|
||||
return trpcClient.query("hello", input);
|
||||
}
|
||||
|
||||
export default function HelloData({
|
||||
export default function TournamentData({
|
||||
params,
|
||||
}: {
|
||||
params: { identifier: string };
|
||||
}) {
|
||||
const [user] = createResource(() => params.identifier, fetchHello);
|
||||
const [user] = createResource(
|
||||
() => params.identifier,
|
||||
(identifier: string) => trpcClient.query("tournament.get", identifier)
|
||||
);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
export const useHelloData = () => useData<() => string>();
|
||||
export const useTournamentData = () =>
|
||||
useData<() => InferQueryOutput<"tournament.get">>();
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { Outlet, useData } from "solid-app-router";
|
||||
import { Outlet } from "solid-app-router";
|
||||
import { useTournamentData } from "./TournamentsPage.data";
|
||||
|
||||
export default function TournamentsPage() {
|
||||
const user = useData<() => string>();
|
||||
const tournament = useTournamentData();
|
||||
|
||||
return (
|
||||
<>
|
||||
response: "{user()}"
|
||||
response: "<pre>{JSON.stringify(tournament(), null, 2)}</pre>"
|
||||
<p>
|
||||
<Outlet />
|
||||
</p>
|
||||
|
|
|
|||
12
scenes/tournament/tournament-router.ts
Normal file
12
scenes/tournament/tournament-router.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { z } from "zod";
|
||||
import { createRouter } from "../../utils/trpc-server";
|
||||
import { findTournamentByNameForUrl } from "./tournament-service";
|
||||
|
||||
export const tournament = createRouter().query("get", {
|
||||
input: z.string(),
|
||||
resolve: ({ input }) =>
|
||||
findTournamentByNameForUrl({
|
||||
organizationNameForUrl: "sendou",
|
||||
tournamentNameForUrl: input,
|
||||
}),
|
||||
});
|
||||
61
scenes/tournament/tournament-service.ts
Normal file
61
scenes/tournament/tournament-service.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import prisma from "../../prisma/client";
|
||||
|
||||
export async function findTournamentByNameForUrl({
|
||||
organizationNameForUrl,
|
||||
tournamentNameForUrl,
|
||||
}: {
|
||||
organizationNameForUrl: string;
|
||||
tournamentNameForUrl: string;
|
||||
}) {
|
||||
const tournaments = await prisma.tournament.findMany({
|
||||
where: {
|
||||
nameForUrl: tournamentNameForUrl.toLowerCase(),
|
||||
},
|
||||
select: {
|
||||
name: true,
|
||||
description: true,
|
||||
startTime: true,
|
||||
checkInTime: true,
|
||||
bannerBackground: true,
|
||||
bannerTextHSLArgs: true,
|
||||
organizer: {
|
||||
select: {
|
||||
name: true,
|
||||
discordInvite: true,
|
||||
twitter: true,
|
||||
nameForUrl: true,
|
||||
},
|
||||
},
|
||||
mapPool: {
|
||||
select: {
|
||||
mode: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = tournaments.find(
|
||||
(tournament) =>
|
||||
tournament.organizer.nameForUrl === organizationNameForUrl.toLowerCase()
|
||||
);
|
||||
|
||||
if (!result) return result;
|
||||
|
||||
result.organizer.twitter = twitterToUrl(result.organizer.twitter);
|
||||
result.organizer.discordInvite = discordInviteToUrl(
|
||||
result.organizer.discordInvite
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function twitterToUrl(twitter: string | null) {
|
||||
if (!twitter) return twitter;
|
||||
|
||||
return `https://twitter.com/${twitter}`;
|
||||
}
|
||||
|
||||
function discordInviteToUrl(discordInvite: string) {
|
||||
return `https://discord.com/invite/${discordInvite}`;
|
||||
}
|
||||
35
server.ts
Normal file
35
server.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import * as trpcExpress from "@trpc/server/adapters/express/dist/trpc-server-adapters-express.cjs";
|
||||
import cors from "cors";
|
||||
import express from "express";
|
||||
import { tournament as tournamentRouter } from "./scenes/tournament/tournament-router";
|
||||
import { createContext, createRouter } from "./utils/trpc-server";
|
||||
|
||||
export const appRouter = createRouter().merge("tournament.", tournamentRouter);
|
||||
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
||||
async function main() {
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
|
||||
app.use((req, _res, next) => {
|
||||
console.log("⬅️ ", req.method, req.path, req.body ?? req.query);
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(
|
||||
"/trpc",
|
||||
trpcExpress.createExpressMiddleware({
|
||||
router: appRouter,
|
||||
createContext,
|
||||
})
|
||||
);
|
||||
|
||||
app.listen(2021, () => {
|
||||
console.log("listening on port 2021");
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
147
server/index.ts
147
server/index.ts
|
|
@ -1,147 +0,0 @@
|
|||
import { EventEmitter } from "events";
|
||||
import express from "express";
|
||||
import cors from "cors";
|
||||
import * as trpc from "@trpc/server";
|
||||
import { z } from "zod";
|
||||
import * as trpcExpress from "@trpc/server/adapters/express/dist/trpc-server-adapters-express.cjs";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
const createContext = ({
|
||||
req,
|
||||
res,
|
||||
}: trpcExpress.CreateExpressContextOptions) => {
|
||||
const getUser = () => {
|
||||
if (req.headers.authorization !== "secret") {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
name: "alex",
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
req,
|
||||
res,
|
||||
user: getUser(),
|
||||
};
|
||||
};
|
||||
type Context = trpc.inferAsyncReturnType<typeof createContext>;
|
||||
|
||||
function createRouter() {
|
||||
return trpc.router<Context>();
|
||||
}
|
||||
|
||||
// --------- create procedures etc
|
||||
|
||||
let id = 0;
|
||||
|
||||
const ee = new EventEmitter();
|
||||
const db = {
|
||||
posts: [
|
||||
{
|
||||
id: ++id,
|
||||
title: "hello",
|
||||
},
|
||||
],
|
||||
messages: [createMessage("initial message")],
|
||||
};
|
||||
function createMessage(text: string) {
|
||||
const msg = {
|
||||
id: ++id,
|
||||
text,
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
ee.emit("newMessage", msg);
|
||||
return msg;
|
||||
}
|
||||
|
||||
const posts = createRouter()
|
||||
.mutation("create", {
|
||||
input: z.object({
|
||||
title: z.string(),
|
||||
}),
|
||||
resolve: ({ input }) => {
|
||||
const post = {
|
||||
id: ++id,
|
||||
...input,
|
||||
};
|
||||
db.posts.push(post);
|
||||
return post;
|
||||
},
|
||||
})
|
||||
.query("list", {
|
||||
resolve: () => db.posts,
|
||||
});
|
||||
|
||||
const messages = createRouter()
|
||||
.query("list", {
|
||||
resolve: () => db.messages,
|
||||
})
|
||||
.mutation("add", {
|
||||
input: z.string(),
|
||||
resolve: async ({ input }) => {
|
||||
const msg = createMessage(input);
|
||||
|
||||
db.messages.push(msg);
|
||||
|
||||
return msg;
|
||||
},
|
||||
});
|
||||
|
||||
// root router to call
|
||||
export const appRouter = createRouter()
|
||||
.query("hello", {
|
||||
input: z.string().nullish(),
|
||||
resolve: ({ input, ctx }) => {
|
||||
return `hello ${input ?? ctx.user?.name ?? "world"}`;
|
||||
},
|
||||
})
|
||||
.merge("post.", posts)
|
||||
.merge(
|
||||
"admin.",
|
||||
createRouter().query("secret", {
|
||||
resolve: ({ ctx }) => {
|
||||
if (!ctx.user) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
if (ctx.user?.name !== "alex") {
|
||||
throw new TRPCError({ code: "FORBIDDEN" });
|
||||
}
|
||||
return {
|
||||
secret: "sauce",
|
||||
};
|
||||
},
|
||||
})
|
||||
)
|
||||
.merge("messages.", messages);
|
||||
|
||||
export type AppRouter = typeof appRouter;
|
||||
|
||||
async function main() {
|
||||
// express implementation
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
|
||||
app.use((req, _res, next) => {
|
||||
// request logger
|
||||
console.log("⬅️ ", req.method, req.path, req.body ?? req.query);
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
app.use(
|
||||
"/trpc",
|
||||
trpcExpress.createExpressMiddleware({
|
||||
router: appRouter,
|
||||
createContext,
|
||||
})
|
||||
);
|
||||
app.get("/", (_req, res) => res.send("hello"));
|
||||
app.listen(2021, () => {
|
||||
console.log("listening on port 2021");
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
1369
server/package-lock.json
generated
1369
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
"name": "@sendou.ink/server",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "node --experimental-specifier-resolution=node --loader ts-node/esm index.ts",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@trpc/server": "^9.13.0",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.17.1",
|
||||
"zod": "^3.11.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/express": "^4.17.13",
|
||||
"ts-node": "^10.4.0"
|
||||
}
|
||||
}
|
||||
27
utils/constants.ts
Normal file
27
utils/constants.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
export const stages = [
|
||||
"The Reef",
|
||||
"Musselforge Fitness",
|
||||
"Starfish Mainstage",
|
||||
"Humpback Pump Track",
|
||||
"Inkblot Art Academy",
|
||||
"Sturgeon Shipyard",
|
||||
"Moray Towers",
|
||||
"Port Mackerel",
|
||||
"Manta Maria",
|
||||
"Kelp Dome",
|
||||
"Snapper Canal",
|
||||
"Blackbelly Skatepark",
|
||||
"MakoMart",
|
||||
"Walleye Warehouse",
|
||||
"Shellendorf Institute",
|
||||
"Arowana Mall",
|
||||
"Goby Arena",
|
||||
"Piranha Pit",
|
||||
"Camp Triggerfish",
|
||||
"Wahoo World",
|
||||
"New Albacore Hotel",
|
||||
"Ancho-V Games",
|
||||
"Skipper Pavilion",
|
||||
];
|
||||
|
||||
export const modesShort = ["TW", "SZ", "TC", "RM", "CB"];
|
||||
11
utils/trpc-client.ts
Normal file
11
utils/trpc-client.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import { createTRPCClient } from "@trpc/client";
|
||||
import type { inferProcedureOutput } from "@trpc/server";
|
||||
import type { AppRouter } from "../server";
|
||||
|
||||
export const trpcClient = createTRPCClient<AppRouter>({
|
||||
url: "http://localhost:2021/trpc",
|
||||
});
|
||||
|
||||
export type InferQueryOutput<
|
||||
TRouteKey extends keyof AppRouter["_def"]["queries"]
|
||||
> = inferProcedureOutput<AppRouter["_def"]["queries"][TRouteKey]>;
|
||||
27
utils/trpc-server.ts
Normal file
27
utils/trpc-server.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import * as trpc from "@trpc/server";
|
||||
import * as trpcExpress from "@trpc/server/adapters/express/dist/trpc-server-adapters-express.cjs";
|
||||
|
||||
export const createContext = ({
|
||||
req,
|
||||
res,
|
||||
}: trpcExpress.CreateExpressContextOptions) => {
|
||||
const getUser = () => {
|
||||
if (req.headers.authorization !== "secret") {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
name: "alex",
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
req,
|
||||
res,
|
||||
user: getUser(),
|
||||
};
|
||||
};
|
||||
type Context = trpc.inferAsyncReturnType<typeof createContext>;
|
||||
|
||||
export function createRouter() {
|
||||
return trpc.router<Context>();
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
import type { AppRouter } from "../server";
|
||||
import { createTRPCClient } from "@trpc/client";
|
||||
|
||||
export const trpcClient = createTRPCClient<AppRouter>({
|
||||
url: "http://localhost:2021/trpc",
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user