diff options
Diffstat (limited to 'apps/web/src')
| -rw-r--r-- | apps/web/src/app/account/client.tsx | 20 | ||||
| -rw-r--r-- | apps/web/src/app/account/page.tsx | 10 | ||||
| -rw-r--r-- | apps/web/src/app/api/auth/[...nextauth]/route.ts | 7 | ||||
| -rw-r--r-- | apps/web/src/app/api/store/route.ts | 21 | ||||
| -rw-r--r-- | apps/web/src/app/layout.tsx | 26 | ||||
| -rw-r--r-- | apps/web/src/app/page.tsx | 7 | ||||
| -rw-r--r-- | apps/web/src/env.js | 65 | ||||
| -rw-r--r-- | apps/web/src/server/auth.ts | 74 | ||||
| -rw-r--r-- | apps/web/src/server/db/index.ts | 12 | ||||
| -rw-r--r-- | apps/web/src/server/db/schema.ts | 111 | ||||
| -rw-r--r-- | apps/web/src/styles/globals.css | 3 |
11 files changed, 356 insertions, 0 deletions
diff --git a/apps/web/src/app/account/client.tsx b/apps/web/src/app/account/client.tsx new file mode 100644 index 00000000..f05d0a3c --- /dev/null +++ b/apps/web/src/app/account/client.tsx @@ -0,0 +1,20 @@ +'use client' + +import { useEffect } from "react" + +function MessagePoster({ jwt }: { jwt: string }) { + + useEffect(() => { + if (typeof window === 'undefined') return + + // post every 1000ms + setInterval(() => { + window.postMessage({ jwt }, '*') + }, 1000) + } + , [jwt]) + + return null +} + +export default MessagePoster
\ No newline at end of file diff --git a/apps/web/src/app/account/page.tsx b/apps/web/src/app/account/page.tsx new file mode 100644 index 00000000..4503f416 --- /dev/null +++ b/apps/web/src/app/account/page.tsx @@ -0,0 +1,10 @@ +import { cookies } from 'next/headers'; +import MessagePoster from './client'; + +async function Page() { + const token = cookies().get('next-auth.session-token')?.value + + return <MessagePoster jwt={token!} /> +} + +export default Page
\ No newline at end of file diff --git a/apps/web/src/app/api/auth/[...nextauth]/route.ts b/apps/web/src/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 00000000..1570f886 --- /dev/null +++ b/apps/web/src/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,7 @@ +import NextAuth from "next-auth"; + +import { authOptions } from "@/server/auth"; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment +const handler = NextAuth(authOptions); +export { handler as GET, handler as POST }; diff --git a/apps/web/src/app/api/store/route.ts b/apps/web/src/app/api/store/route.ts new file mode 100644 index 00000000..5c7e76d5 --- /dev/null +++ b/apps/web/src/app/api/store/route.ts @@ -0,0 +1,21 @@ +import { db } from "@/server/db"; +import { eq } from "drizzle-orm"; +import { sessions, users } from "@/server/db/schema"; +import { type NextRequest, NextResponse } from "next/server"; + +export async function GET(req: NextRequest) { + try { + const token = req.cookies.get("next-auth.session-token")?.value ?? req.headers.get("Authorization")?.replace("Bearer ", ""); + + const session = await db.select().from(sessions).where(eq(sessions.sessionToken, token!)) + .leftJoin(users, eq(sessions.userId, users.id)) + + if (!session || session.length === 0) { + return NextResponse.json({ message: "Invalid Key, session not found." }, { status: 404 }); + } + + return NextResponse.json({ message: "OK", data: session[0] }, { status: 200 }); + } catch (error) { + return NextResponse.json({ message: "Error", error }, { status: 500 }); + } +}
\ No newline at end of file diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx new file mode 100644 index 00000000..70f9df5d --- /dev/null +++ b/apps/web/src/app/layout.tsx @@ -0,0 +1,26 @@ +import "@/styles/globals.css"; + +import { Inter } from "next/font/google"; + +const inter = Inter({ + subsets: ["latin"], + variable: "--font-sans", +}); + +export const metadata = { + title: "Create T3 App", + description: "Generated by create-t3-app", + icons: [{ rel: "icon", url: "/favicon.ico" }], +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + <html lang="en"> + <body className={`font-sans ${inter.variable}`}>{children}</body> + </html> + ); +} diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx new file mode 100644 index 00000000..ba5030c7 --- /dev/null +++ b/apps/web/src/app/page.tsx @@ -0,0 +1,7 @@ +import Link from "next/link"; + +export default function HomePage() { + return ( + <main>hi</main> + ); +} diff --git a/apps/web/src/env.js b/apps/web/src/env.js new file mode 100644 index 00000000..7bc15152 --- /dev/null +++ b/apps/web/src/env.js @@ -0,0 +1,65 @@ +import { createEnv } from "@t3-oss/env-nextjs"; +import { z } from "zod"; + +export const env = createEnv({ + /** + * Specify your server-side environment variables schema here. This way you can ensure the app + * isn't built with invalid env vars. + */ + server: { + DATABASE_URL: z + .string() + .refine( + (str) => !str.includes("YOUR_MYSQL_URL_HERE"), + "You forgot to change the default URL" + ), + NODE_ENV: z + .enum(["development", "test", "production"]) + .default("development"), + NEXTAUTH_SECRET: + process.env.NODE_ENV === "production" + ? z.string() + : z.string().optional(), + NEXTAUTH_URL: z.preprocess( + // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL + // Since NextAuth.js automatically uses the VERCEL_URL if present. + (str) => process.env.VERCEL_URL ?? str, + // VERCEL_URL doesn't include `https` so it cant be validated as a URL + process.env.VERCEL ? z.string() : z.string().url() + ), + GOOGLE_CLIENT_ID: z.string(), + GOOGLE_CLIENT_SECRET: z.string(), + }, + + /** + * Specify your client-side environment variables schema here. This way you can ensure the app + * isn't built with invalid env vars. To expose them to the client, prefix them with + * `NEXT_PUBLIC_`. + */ + client: { + // NEXT_PUBLIC_CLIENTVAR: z.string(), + }, + + /** + * You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g. + * middlewares) or client-side so we need to destruct manually. + */ + runtimeEnv: { + DATABASE_URL: process.env.DATABASE_URL, + NODE_ENV: process.env.NODE_ENV, + NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, + NEXTAUTH_URL: process.env.NEXTAUTH_URL, + GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, + GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET, + }, + /** + * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially + * useful for Docker builds. + */ + skipValidation: !!process.env.SKIP_ENV_VALIDATION, + /** + * Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and + * `SOME_VAR=''` will throw an error. + */ + emptyStringAsUndefined: true, +}); diff --git a/apps/web/src/server/auth.ts b/apps/web/src/server/auth.ts new file mode 100644 index 00000000..cb0a26d9 --- /dev/null +++ b/apps/web/src/server/auth.ts @@ -0,0 +1,74 @@ +import { DrizzleAdapter } from "@auth/drizzle-adapter"; +import { + getServerSession, + type DefaultSession, + type NextAuthOptions, +} from "next-auth"; +import { type Adapter } from "next-auth/adapters"; +import GoogleProvider from "next-auth/providers/google"; + +import { env } from "@/env"; +import { db } from "@/server/db"; +import { createTable } from "@/server/db/schema"; + +/** + * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session` + * object and keep type safety. + * + * @see https://next-auth.js.org/getting-started/typescript#module-augmentation + */ +declare module "next-auth" { + interface Session extends DefaultSession { + user: { + id: string; + // ...other properties + // role: UserRole; + } & DefaultSession["user"]; + } + + // interface User { + // // ...other properties + // // role: UserRole; + // } +} + +/** + * Options for NextAuth.js used to configure adapters, providers, callbacks, etc. + * + * @see https://next-auth.js.org/configuration/options + */ +export const authOptions: NextAuthOptions = { + callbacks: { + session: ({ session, token }) => ({ + ...session, + user: { + ...session.user, + id: token.id, + token + }, + }) + }, + adapter: DrizzleAdapter(db, createTable) as Adapter, + providers: [ + GoogleProvider({ + clientId: env.GOOGLE_CLIENT_ID, + clientSecret: env.GOOGLE_CLIENT_SECRET, + }), + /** + * ...add more providers here. + * + * Most other providers require a bit more work than the Discord provider. For example, the + * GitHub provider requires you to add the `refresh_token_expires_in` field to the Account + * model. Refer to the NextAuth.js docs for the provider you want to use. Example: + * + * @see https://next-auth.js.org/providers/github + */ + ], +}; + +/** + * Wrapper for `getServerSession` so that you don't need to import the `authOptions` in every file. + * + * @see https://next-auth.js.org/configuration/nextjs + */ +export const getServerAuthSession = () => getServerSession(authOptions); diff --git a/apps/web/src/server/db/index.ts b/apps/web/src/server/db/index.ts new file mode 100644 index 00000000..5c2246d5 --- /dev/null +++ b/apps/web/src/server/db/index.ts @@ -0,0 +1,12 @@ +import Database from "better-sqlite3"; +import { drizzle } from "drizzle-orm/better-sqlite3"; + +import { env } from "@/env.js"; +import * as schema from "./schema"; + +export const db = drizzle( + new Database(env.DATABASE_URL, { + fileMustExist: false, + }), + { schema } +); diff --git a/apps/web/src/server/db/schema.ts b/apps/web/src/server/db/schema.ts new file mode 100644 index 00000000..7de02f15 --- /dev/null +++ b/apps/web/src/server/db/schema.ts @@ -0,0 +1,111 @@ +import { relations, sql } from "drizzle-orm"; +import { + index, + int, + primaryKey, + sqliteTableCreator, + text, +} from "drizzle-orm/sqlite-core"; +import { type AdapterAccount } from "next-auth/adapters"; + +/** + * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same + * database instance for multiple projects. + * + * @see https://orm.drizzle.team/docs/goodies#multi-project-schema + */ +export const createTable = sqliteTableCreator((name) => `anycontext_${name}`); + +export const posts = createTable( + "post", + { + id: int("id", { mode: "number" }).primaryKey({ autoIncrement: true }), + name: text("name", { length: 256 }), + createdById: text("createdById", { length: 255 }) + .notNull() + .references(() => users.id), + createdAt: int("created_at", { mode: "timestamp" }) + .default(sql`CURRENT_TIMESTAMP`) + .notNull(), + updatedAt: int("updatedAt", { mode: "timestamp" }), + }, + (example) => ({ + createdByIdIdx: index("createdById_idx").on(example.createdById), + nameIndex: index("name_idx").on(example.name), + }) +); + +export const users = createTable("user", { + id: text("id", { length: 255 }).notNull().primaryKey(), + name: text("name", { length: 255 }), + email: text("email", { length: 255 }).notNull(), + emailVerified: int("emailVerified", { + mode: "timestamp", + }).default(sql`CURRENT_TIMESTAMP`), + image: text("image", { length: 255 }), +}); + +export const usersRelations = relations(users, ({ many }) => ({ + accounts: many(accounts), +})); + +export const accounts = createTable( + "account", + { + userId: text("userId", { length: 255 }) + .notNull() + .references(() => users.id), + type: text("type", { length: 255 }) + .$type<AdapterAccount["type"]>() + .notNull(), + provider: text("provider", { length: 255 }).notNull(), + providerAccountId: text("providerAccountId", { length: 255 }).notNull(), + refresh_token: text("refresh_token"), + access_token: text("access_token"), + expires_at: int("expires_at"), + token_type: text("token_type", { length: 255 }), + scope: text("scope", { length: 255 }), + id_token: text("id_token"), + session_state: text("session_state", { length: 255 }), + }, + (account) => ({ + compoundKey: primaryKey({ + columns: [account.provider, account.providerAccountId], + }), + userIdIdx: index("account_userId_idx").on(account.userId), + }) +); + +export const accountsRelations = relations(accounts, ({ one }) => ({ + user: one(users, { fields: [accounts.userId], references: [users.id] }), +})); + +export const sessions = createTable( + "session", + { + sessionToken: text("sessionToken", { length: 255 }).notNull().primaryKey(), + userId: text("userId", { length: 255 }) + .notNull() + .references(() => users.id), + expires: int("expires", { mode: "timestamp" }).notNull(), + }, + (session) => ({ + userIdIdx: index("session_userId_idx").on(session.userId), + }) +); + +export const sessionsRelations = relations(sessions, ({ one }) => ({ + user: one(users, { fields: [sessions.userId], references: [users.id] }), +})); + +export const verificationTokens = createTable( + "verificationToken", + { + identifier: text("identifier", { length: 255 }).notNull(), + token: text("token", { length: 255 }).notNull(), + expires: int("expires", { mode: "timestamp" }).notNull(), + }, + (vt) => ({ + compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }), + }) +); diff --git a/apps/web/src/styles/globals.css b/apps/web/src/styles/globals.css new file mode 100644 index 00000000..b5c61c95 --- /dev/null +++ b/apps/web/src/styles/globals.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; |