add nextjs and migrated api to next api
This commit is contained in:
20
apps/web/src/pages/_app.tsx
Normal file
20
apps/web/src/pages/_app.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { type Session } from "next-auth";
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
import { type AppType } from "next/app";
|
||||
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
import "@/styles/globals.css";
|
||||
|
||||
const MyApp: AppType<{ session: Session | null }> = ({
|
||||
Component,
|
||||
pageProps: { session, ...pageProps },
|
||||
}) => {
|
||||
return (
|
||||
<SessionProvider session={session}>
|
||||
<Component {...pageProps} />
|
||||
</SessionProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default api.withTRPC(MyApp);
|
||||
5
apps/web/src/pages/api/auth/[...nextauth].ts
Normal file
5
apps/web/src/pages/api/auth/[...nextauth].ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import NextAuth from "next-auth";
|
||||
|
||||
import { authOptions } from "@/server/auth";
|
||||
|
||||
export default NextAuth(authOptions);
|
||||
37
apps/web/src/pages/api/sdk/events.ts
Normal file
37
apps/web/src/pages/api/sdk/events.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { validateSdkRequest } from '@/server/auth'
|
||||
import { db } from '@/server/db'
|
||||
import { createError, handleError } from '@/server/exceptions'
|
||||
import { EventPayload } from '@mixan/types'
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
interface Request extends NextApiRequest {
|
||||
body: Array<EventPayload>
|
||||
}
|
||||
|
||||
export default async function handler(
|
||||
req: Request,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
if(req.method !== 'POST') {
|
||||
return handleError(res, createError(405, 'Method not allowed'))
|
||||
}
|
||||
|
||||
try {
|
||||
// Check client id & secret
|
||||
const projectId = await validateSdkRequest(req)
|
||||
|
||||
await db.event.createMany({
|
||||
data: req.body.map((event) => ({
|
||||
name: event.name,
|
||||
properties: event.properties,
|
||||
createdAt: event.time,
|
||||
project_id: projectId,
|
||||
profile_id: event.profileId,
|
||||
}))
|
||||
})
|
||||
|
||||
res.status(200).end()
|
||||
} catch (error) {
|
||||
handleError(res, error)
|
||||
}
|
||||
}
|
||||
33
apps/web/src/pages/api/sdk/profiles/[profileId]/decrement.ts
Normal file
33
apps/web/src/pages/api/sdk/profiles/[profileId]/decrement.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { validateSdkRequest } from "@/server/auth";
|
||||
import { db } from "@/server/db";
|
||||
import { createError, handleError } from "@/server/exceptions";
|
||||
import { tickProfileProperty } from "@/services/profile.service";
|
||||
import { ProfileIncrementPayload, ProfilePayload } from "@mixan/types";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
interface Request extends NextApiRequest {
|
||||
body: ProfileIncrementPayload;
|
||||
}
|
||||
|
||||
export default async function handler(req: Request, res: NextApiResponse) {
|
||||
if (req.method !== "PUT") {
|
||||
return handleError(res, createError(405, "Method not allowed"));
|
||||
}
|
||||
|
||||
try {
|
||||
// Check client id & secret
|
||||
await validateSdkRequest(req)
|
||||
|
||||
const profileId = req.query.profileId as string;
|
||||
|
||||
await tickProfileProperty({
|
||||
name: req.body.name,
|
||||
tick: -Math.abs(req.body.value),
|
||||
profileId,
|
||||
});
|
||||
|
||||
res.status(200).end();
|
||||
} catch (error) {
|
||||
handleError(res, error);
|
||||
}
|
||||
}
|
||||
33
apps/web/src/pages/api/sdk/profiles/[profileId]/increment.ts
Normal file
33
apps/web/src/pages/api/sdk/profiles/[profileId]/increment.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { validateSdkRequest } from "@/server/auth";
|
||||
import { db } from "@/server/db";
|
||||
import { createError, handleError } from "@/server/exceptions";
|
||||
import { tickProfileProperty } from "@/services/profile.service";
|
||||
import { ProfileIncrementPayload, ProfilePayload } from "@mixan/types";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
interface Request extends NextApiRequest {
|
||||
body: ProfileIncrementPayload;
|
||||
}
|
||||
|
||||
export default async function handler(req: Request, res: NextApiResponse) {
|
||||
if (req.method !== "PUT") {
|
||||
return handleError(res, createError(405, "Method not allowed"));
|
||||
}
|
||||
|
||||
try {
|
||||
// Check client id & secret
|
||||
await validateSdkRequest(req)
|
||||
|
||||
const profileId = req.query.profileId as string;
|
||||
|
||||
await tickProfileProperty({
|
||||
name: req.body.name,
|
||||
tick: req.body.value,
|
||||
profileId,
|
||||
});
|
||||
|
||||
res.status(200).end();
|
||||
} catch (error) {
|
||||
handleError(res, error);
|
||||
}
|
||||
}
|
||||
48
apps/web/src/pages/api/sdk/profiles/[profileId]/index.ts
Normal file
48
apps/web/src/pages/api/sdk/profiles/[profileId]/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { validateSdkRequest } from "@/server/auth";
|
||||
import { db } from "@/server/db";
|
||||
import { createError, handleError } from "@/server/exceptions";
|
||||
import { getProfile } from "@/services/profile.service";
|
||||
import { ProfilePayload } from "@mixan/types";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
interface Request extends NextApiRequest {
|
||||
body: ProfilePayload;
|
||||
}
|
||||
|
||||
export default async function handler(req: Request, res: NextApiResponse) {
|
||||
if (req.method !== "PUT" && req.method !== "POST") {
|
||||
return handleError(res, createError(405, "Method not allowed"));
|
||||
}
|
||||
|
||||
try {
|
||||
// Check client id & secret
|
||||
await validateSdkRequest(req)
|
||||
|
||||
const profileId = req.query.profileId as string;
|
||||
const profile = await getProfile(profileId)
|
||||
|
||||
const { body } = req;
|
||||
await db.profile.update({
|
||||
where: {
|
||||
id: profileId,
|
||||
},
|
||||
data: {
|
||||
external_id: body.id,
|
||||
email: body.email,
|
||||
first_name: body.first_name,
|
||||
last_name: body.last_name,
|
||||
avatar: body.avatar,
|
||||
properties: {
|
||||
...(typeof profile.properties === "object"
|
||||
? profile.properties || {}
|
||||
: {}),
|
||||
...(body.properties || {}),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
res.status(200).end();
|
||||
} catch (error) {
|
||||
handleError(res, error);
|
||||
}
|
||||
}
|
||||
46
apps/web/src/pages/api/sdk/profiles/index.ts
Normal file
46
apps/web/src/pages/api/sdk/profiles/index.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { validateSdkRequest } from '@/server/auth'
|
||||
import { db } from '@/server/db'
|
||||
import { createError, handleError } from '@/server/exceptions'
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import randomAnimalName from 'random-animal-name'
|
||||
|
||||
interface Request extends NextApiRequest {
|
||||
body: {
|
||||
id: string
|
||||
properties?: Record<string, any>
|
||||
}
|
||||
}
|
||||
|
||||
export default async function handler(
|
||||
req: Request,
|
||||
res: NextApiResponse
|
||||
) {
|
||||
if(req.method !== 'POST') {
|
||||
return handleError(res, createError(405, 'Method not allowed'))
|
||||
}
|
||||
|
||||
try {
|
||||
// Check client id & secret
|
||||
const projectId = await validateSdkRequest(req)
|
||||
|
||||
const { id, properties } = req.body
|
||||
await db.profile.create({
|
||||
data: {
|
||||
id,
|
||||
external_id: null,
|
||||
email: null,
|
||||
first_name: randomAnimalName(),
|
||||
last_name: null,
|
||||
avatar: null,
|
||||
properties: {
|
||||
...(properties || {}),
|
||||
},
|
||||
project_id: projectId,
|
||||
},
|
||||
})
|
||||
|
||||
res.status(200).end()
|
||||
} catch (error) {
|
||||
handleError(res, error)
|
||||
}
|
||||
}
|
||||
47
apps/web/src/pages/api/setup.ts
Normal file
47
apps/web/src/pages/api/setup.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { db } from "@/server/db";
|
||||
import { handleError } from "@/server/exceptions";
|
||||
import { hashPassword } from "@/services/hash.service";
|
||||
import { randomUUID } from "crypto";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
export default async function (req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const counts = await db.$transaction([
|
||||
db.organization.count(),
|
||||
db.project.count(),
|
||||
db.client.count(),
|
||||
]);
|
||||
|
||||
if (counts.some((count) => count > 0)) {
|
||||
return res.json("Setup already done");
|
||||
}
|
||||
|
||||
const organization = await db.organization.create({
|
||||
data: {
|
||||
name: "Acme Inc.",
|
||||
},
|
||||
});
|
||||
|
||||
const project = await db.project.create({
|
||||
data: {
|
||||
name: "Acme Website",
|
||||
organization_id: organization.id,
|
||||
},
|
||||
});
|
||||
const secret = randomUUID();
|
||||
const client = await db.client.create({
|
||||
data: {
|
||||
name: "Acme Website Client",
|
||||
project_id: project.id,
|
||||
secret: await hashPassword(secret),
|
||||
},
|
||||
});
|
||||
|
||||
res.json({
|
||||
clientId: client.id,
|
||||
clientSecret: secret,
|
||||
});
|
||||
} catch (error) {
|
||||
handleError(res, error);
|
||||
}
|
||||
}
|
||||
19
apps/web/src/pages/api/trpc/[trpc].ts
Normal file
19
apps/web/src/pages/api/trpc/[trpc].ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { createNextApiHandler } from "@trpc/server/adapters/next";
|
||||
|
||||
import { env } from "@/env.mjs";
|
||||
import { appRouter } from "@/server/api/root";
|
||||
import { createTRPCContext } from "@/server/api/trpc";
|
||||
|
||||
// export API handler
|
||||
export default createNextApiHandler({
|
||||
router: appRouter,
|
||||
createContext: createTRPCContext,
|
||||
onError:
|
||||
env.NODE_ENV === "development"
|
||||
? ({ path, error }) => {
|
||||
console.error(
|
||||
`❌ tRPC failed on ${path ?? "<no-path>"}: ${error.message}`
|
||||
);
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
80
apps/web/src/pages/index.tsx
Normal file
80
apps/web/src/pages/index.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { signIn, signOut, useSession } from "next-auth/react";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
export default function Home() {
|
||||
const hello = api.example.hello.useQuery({ text: "from tRPC" });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Create T3 App</title>
|
||||
<meta name="description" content="Generated by create-t3-app" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<main className=" flex min-h-screen flex-col items-center justify-center bg-gradient-to-b from-[#2e026d] to-[#15162c]">
|
||||
<div className="container flex flex-col items-center justify-center gap-12 px-4 py-16 ">
|
||||
<h1 className="text-5xl font-extrabold tracking-tight text-white sm:text-[5rem]">
|
||||
Create <span className="text-[hsl(280,100%,70%)]">T3</span> App
|
||||
</h1>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 md:gap-8">
|
||||
<Link
|
||||
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 text-white hover:bg-white/20"
|
||||
href="https://create.t3.gg/en/usage/first-steps"
|
||||
target="_blank"
|
||||
>
|
||||
<h3 className="text-2xl font-bold">First Steps →</h3>
|
||||
<div className="text-lg">
|
||||
Just the basics - Everything you need to know to set up your
|
||||
database and authentication.
|
||||
</div>
|
||||
</Link>
|
||||
<Link
|
||||
className="flex max-w-xs flex-col gap-4 rounded-xl bg-white/10 p-4 text-white hover:bg-white/20"
|
||||
href="https://create.t3.gg/en/introduction"
|
||||
target="_blank"
|
||||
>
|
||||
<h3 className="text-2xl font-bold">Documentation →</h3>
|
||||
<div className="text-lg">
|
||||
Learn more about Create T3 App, the libraries it uses, and how
|
||||
to deploy it.
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<p className="text-2xl text-white">
|
||||
{hello.data ? hello.data.greeting : "Loading tRPC query..."}
|
||||
</p>
|
||||
<AuthShowcase />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function AuthShowcase() {
|
||||
const { data: sessionData } = useSession();
|
||||
|
||||
const { data: secretMessage } = api.example.getSecretMessage.useQuery(
|
||||
undefined, // no input
|
||||
{ enabled: sessionData?.user !== undefined }
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-4">
|
||||
<p className="text-center text-2xl text-white">
|
||||
{sessionData && <span>Logged in as {sessionData.user?.name}</span>}
|
||||
{secretMessage && <span> - {secretMessage}</span>}
|
||||
</p>
|
||||
<button
|
||||
className="rounded-full bg-white/10 px-10 py-3 font-semibold text-white no-underline transition hover:bg-white/20"
|
||||
onClick={sessionData ? () => void signOut() : () => void signIn()}
|
||||
>
|
||||
{sessionData ? "Sign out" : "Sign in"}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user