add nextjs and migrated api to next api

This commit is contained in:
Carl-Gerhard Lindesvärd
2023-10-12 23:19:02 +02:00
parent 7d474e8444
commit cef7fc6965
47 changed files with 1466 additions and 1 deletions

View 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);

View File

@@ -0,0 +1,5 @@
import NextAuth from "next-auth";
import { authOptions } from "@/server/auth";
export default NextAuth(authOptions);

View 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)
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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)
}
}

View 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);
}
}

View 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,
});

View 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>
);
}