Initial commit
This commit is contained in:
18
packages/api/index.ts
Normal file
18
packages/api/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
|
||||
|
||||
import type { AppRouter } from "./src/root";
|
||||
|
||||
export { appRouter, type AppRouter } from "./src/root";
|
||||
export { createTRPCContext } from "./src/trpc";
|
||||
|
||||
/**
|
||||
* Inference helpers for input types
|
||||
* @example type HelloInput = RouterInputs['example']['hello']
|
||||
**/
|
||||
export type RouterInputs = inferRouterInputs<AppRouter>;
|
||||
|
||||
/**
|
||||
* Inference helpers for output types
|
||||
* @example type HelloOutput = RouterOutputs['example']['hello']
|
||||
**/
|
||||
export type RouterOutputs = inferRouterOutputs<AppRouter>;
|
||||
37
packages/api/package.json
Normal file
37
packages/api/package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "@acme/api",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "./index.ts",
|
||||
"types": "./index.ts",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@acme/auth": "workspace:^0.1.0",
|
||||
"@acme/db": "workspace:^0.1.0",
|
||||
"@trpc/client": "next",
|
||||
"@trpc/server": "next",
|
||||
"superjson": "2.2.1",
|
||||
"zod": "^3.22.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@acme/eslint-config": "workspace:^0.2.0",
|
||||
"@acme/prettier-config": "workspace:^0.1.0",
|
||||
"@acme/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^8.53.0",
|
||||
"prettier": "^3.1.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"extends": [
|
||||
"@acme/eslint-config/base"
|
||||
]
|
||||
},
|
||||
"prettier": "@acme/prettier-config"
|
||||
}
|
||||
11
packages/api/src/root.ts
Normal file
11
packages/api/src/root.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { authRouter } from "./router/auth";
|
||||
import { postRouter } from "./router/post";
|
||||
import { createTRPCRouter } from "./trpc";
|
||||
|
||||
export const appRouter = createTRPCRouter({
|
||||
auth: authRouter,
|
||||
post: postRouter,
|
||||
});
|
||||
|
||||
// export type definition of API
|
||||
export type AppRouter = typeof appRouter;
|
||||
11
packages/api/src/router/auth.ts
Normal file
11
packages/api/src/router/auth.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
|
||||
|
||||
export const authRouter = createTRPCRouter({
|
||||
getSession: publicProcedure.query(({ ctx }) => {
|
||||
return ctx.session;
|
||||
}),
|
||||
getSecretMessage: protectedProcedure.query(() => {
|
||||
// testing type validation of overridden next-auth Session in @acme/auth package
|
||||
return "you can see this secret message!";
|
||||
}),
|
||||
});
|
||||
40
packages/api/src/router/post.ts
Normal file
40
packages/api/src/router/post.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { desc, eq, schema } from "@acme/db";
|
||||
|
||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc";
|
||||
|
||||
export const postRouter = createTRPCRouter({
|
||||
all: publicProcedure.query(({ ctx }) => {
|
||||
// return ctx.db.select().from(schema.post).orderBy(desc(schema.post.id));
|
||||
return ctx.db.query.post.findMany({ orderBy: desc(schema.post.id) });
|
||||
}),
|
||||
|
||||
byId: publicProcedure
|
||||
.input(z.object({ id: z.number() }))
|
||||
.query(({ ctx, input }) => {
|
||||
// return ctx.db
|
||||
// .select()
|
||||
// .from(schema.post)
|
||||
// .where(eq(schema.post.id, input.id));
|
||||
|
||||
return ctx.db.query.post.findFirst({
|
||||
where: eq(schema.post.id, input.id),
|
||||
});
|
||||
}),
|
||||
|
||||
create: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
title: z.string().min(1),
|
||||
content: z.string().min(1),
|
||||
}),
|
||||
)
|
||||
.mutation(({ ctx, input }) => {
|
||||
return ctx.db.insert(schema.post).values(input);
|
||||
}),
|
||||
|
||||
delete: protectedProcedure.input(z.number()).mutation(({ ctx, input }) => {
|
||||
return ctx.db.delete(schema.post).where(eq(schema.post.id, input));
|
||||
}),
|
||||
});
|
||||
132
packages/api/src/trpc.ts
Normal file
132
packages/api/src/trpc.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* YOU PROBABLY DON'T NEED TO EDIT THIS FILE, UNLESS:
|
||||
* 1. You want to modify request context (see Part 1)
|
||||
* 2. You want to create a new middleware or type of procedure (see Part 3)
|
||||
*
|
||||
* tl;dr - this is where all the tRPC server stuff is created and plugged in.
|
||||
* The pieces you will need to use are documented accordingly near the end
|
||||
*/
|
||||
import { initTRPC, TRPCError } from "@trpc/server";
|
||||
import superjson from "superjson";
|
||||
import { ZodError } from "zod";
|
||||
|
||||
import { auth } from "@acme/auth";
|
||||
import type { Session } from "@acme/auth";
|
||||
import { db } from "@acme/db";
|
||||
|
||||
/**
|
||||
* 1. CONTEXT
|
||||
*
|
||||
* This section defines the "contexts" that are available in the backend API
|
||||
*
|
||||
* These allow you to access things like the database, the session, etc, when
|
||||
* processing a request
|
||||
*
|
||||
*/
|
||||
interface CreateContextOptions {
|
||||
session: Session | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* This helper generates the "internals" for a tRPC context. If you need to use
|
||||
* it, you can export it from here
|
||||
*
|
||||
* Examples of things you may need it for:
|
||||
* - testing, so we dont have to mock Next.js' req/res
|
||||
* - trpc's `createSSGHelpers` where we don't have req/res
|
||||
* @see https://create.t3.gg/en/usage/trpc#-servertrpccontextts
|
||||
*/
|
||||
const createInnerTRPCContext = (opts: CreateContextOptions) => {
|
||||
return {
|
||||
session: opts.session,
|
||||
db,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the actual context you'll use in your router. It will be used to
|
||||
* process every request that goes through your tRPC endpoint
|
||||
* @link https://trpc.io/docs/context
|
||||
*/
|
||||
export const createTRPCContext = async (opts: {
|
||||
req?: Request;
|
||||
auth: Session | null;
|
||||
}) => {
|
||||
const session = opts.auth ?? (await auth());
|
||||
const source = opts.req?.headers.get("x-trpc-source") ?? "unknown";
|
||||
|
||||
console.log(">>> tRPC Request from", source, "by", session?.user);
|
||||
|
||||
return createInnerTRPCContext({
|
||||
session,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 2. INITIALIZATION
|
||||
*
|
||||
* This is where the trpc api is initialized, connecting the context and
|
||||
* transformer
|
||||
*/
|
||||
const t = initTRPC.context<typeof createTRPCContext>().create({
|
||||
transformer: superjson,
|
||||
errorFormatter({ shape, error }) {
|
||||
return {
|
||||
...shape,
|
||||
data: {
|
||||
...shape.data,
|
||||
zodError:
|
||||
error.cause instanceof ZodError ? error.cause.flatten() : null,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 3. ROUTER & PROCEDURE (THE IMPORTANT BIT)
|
||||
*
|
||||
* These are the pieces you use to build your tRPC API. You should import these
|
||||
* a lot in the /src/server/api/routers folder
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is how you create new routers and subrouters in your tRPC API
|
||||
* @see https://trpc.io/docs/router
|
||||
*/
|
||||
export const createTRPCRouter = t.router;
|
||||
|
||||
/**
|
||||
* Public (unauthed) procedure
|
||||
*
|
||||
* This is the base piece you use to build new queries and mutations on your
|
||||
* tRPC API. It does not guarantee that a user querying is authorized, but you
|
||||
* can still access user session data if they are logged in
|
||||
*/
|
||||
export const publicProcedure = t.procedure;
|
||||
|
||||
/**
|
||||
* Reusable middleware that enforces users are logged in before running the
|
||||
* procedure
|
||||
*/
|
||||
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
|
||||
if (!ctx.session?.user) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
return next({
|
||||
ctx: {
|
||||
// infers the `session` as non-nullable
|
||||
session: { ...ctx.session, user: ctx.session.user },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Protected (authed) procedure
|
||||
*
|
||||
* If you want a query or mutation to ONLY be accessible to logged in users, use
|
||||
* this. It verifies the session is valid and guarantees ctx.session.user is not
|
||||
* null
|
||||
*
|
||||
* @see https://trpc.io/docs/procedures
|
||||
*/
|
||||
export const protectedProcedure = t.procedure.use(enforceUserIsAuthed);
|
||||
8
packages/api/tsconfig.json
Normal file
8
packages/api/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@acme/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||
},
|
||||
"include": ["."],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
28
packages/auth/env.mjs
Normal file
28
packages/auth/env.mjs
Normal file
@@ -0,0 +1,28 @@
|
||||
import { createEnv } from "@t3-oss/env-nextjs";
|
||||
import { z } from "zod";
|
||||
|
||||
export const env = createEnv({
|
||||
server: {
|
||||
AUTH_DISCORD_ID: z.string().min(1),
|
||||
AUTH_DISCORD_SECRET: z.string().min(1),
|
||||
AUTH_SECRET:
|
||||
process.env.NODE_ENV === "production"
|
||||
? z.string().min(1)
|
||||
: z.string().min(1).optional(),
|
||||
AUTH_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(),
|
||||
),
|
||||
},
|
||||
client: {},
|
||||
runtimeEnv: {
|
||||
AUTH_DISCORD_ID: process.env.AUTH_DISCORD_ID,
|
||||
AUTH_DISCORD_SECRET: process.env.AUTH_DISCORD_SECRET,
|
||||
AUTH_SECRET: process.env.AUTH_SECRET,
|
||||
AUTH_URL: process.env.AUTH_URL,
|
||||
},
|
||||
skipValidation: !!process.env.CI || !!process.env.SKIP_ENV_VALIDATION,
|
||||
});
|
||||
38
packages/auth/index.ts
Normal file
38
packages/auth/index.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
/* @see https://github.com/nextauthjs/next-auth/pull/8932 */
|
||||
|
||||
import Discord from "@auth/core/providers/discord";
|
||||
import type { DefaultSession } from "@auth/core/types";
|
||||
import { DrizzleAdapter } from "@auth/drizzle-adapter";
|
||||
import NextAuth from "next-auth";
|
||||
|
||||
import { db, tableCreator } from "@acme/db";
|
||||
|
||||
export type { Session } from "next-auth";
|
||||
|
||||
declare module "next-auth" {
|
||||
interface Session {
|
||||
user: {
|
||||
id: string;
|
||||
} & DefaultSession["user"];
|
||||
}
|
||||
}
|
||||
|
||||
export const {
|
||||
handlers: { GET, POST },
|
||||
auth,
|
||||
signIn,
|
||||
signOut,
|
||||
} = NextAuth({
|
||||
adapter: DrizzleAdapter(db, tableCreator),
|
||||
providers: [Discord],
|
||||
callbacks: {
|
||||
session: ({ session, user }) => ({
|
||||
...session,
|
||||
user: {
|
||||
...session.user,
|
||||
id: user.id,
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
40
packages/auth/package.json
Normal file
40
packages/auth/package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@acme/auth",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "./index.ts",
|
||||
"types": "./index.ts",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@acme/db": "workspace:^0.1.0",
|
||||
"@auth/core": "^0.18.4",
|
||||
"@auth/drizzle-adapter": "^0.3.9",
|
||||
"@t3-oss/env-nextjs": "^0.7.1",
|
||||
"next": "^14.0.3",
|
||||
"next-auth": "5.0.0-beta.4",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"zod": "^3.22.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@acme/eslint-config": "workspace:^0.2.0",
|
||||
"@acme/prettier-config": "workspace:^0.1.0",
|
||||
"@acme/tsconfig": "workspace:^0.1.0",
|
||||
"eslint": "^8.53.0",
|
||||
"prettier": "^3.1.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"extends": [
|
||||
"@acme/eslint-config/base"
|
||||
]
|
||||
},
|
||||
"prettier": "@acme/prettier-config"
|
||||
}
|
||||
8
packages/auth/tsconfig.json
Normal file
8
packages/auth/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@acme/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||
},
|
||||
"include": ["."],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
23
packages/db/drizzle.config.ts
Normal file
23
packages/db/drizzle.config.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import * as dotenv from "dotenv";
|
||||
import type { Config } from "drizzle-kit";
|
||||
|
||||
dotenv.config({ path: "../../.env" });
|
||||
|
||||
const uri = [
|
||||
"mysql://",
|
||||
process.env.DB_USERNAME,
|
||||
":",
|
||||
process.env.DB_PASSWORD,
|
||||
"@",
|
||||
process.env.DB_HOST,
|
||||
":3306/",
|
||||
process.env.DB_NAME,
|
||||
'?ssl={"rejectUnauthorized":true}',
|
||||
].join("");
|
||||
|
||||
export default {
|
||||
schema: "./schema",
|
||||
driver: "mysql2",
|
||||
dbCredentials: { uri },
|
||||
tablesFilter: ["t3turbo_*"],
|
||||
} satisfies Config;
|
||||
20
packages/db/index.ts
Normal file
20
packages/db/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Client } from "@planetscale/database";
|
||||
import { drizzle } from "drizzle-orm/planetscale-serverless";
|
||||
|
||||
import * as auth from "./schema/auth";
|
||||
import * as post from "./schema/post";
|
||||
|
||||
export const schema = { ...auth, ...post };
|
||||
|
||||
export { mySqlTable as tableCreator } from "./schema/_table";
|
||||
|
||||
export * from "drizzle-orm";
|
||||
|
||||
export const db = drizzle(
|
||||
new Client({
|
||||
host: process.env.DB_HOST,
|
||||
username: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
}).connection(),
|
||||
{ schema },
|
||||
);
|
||||
37
packages/db/package.json
Normal file
37
packages/db/package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "@acme/db",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"main": "./index.ts",
|
||||
"types": "./index.ts",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"clean": "rm -rf .turbo node_modules",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"push": "drizzle-kit push:mysql",
|
||||
"studio": "drizzle-kit studio",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@planetscale/database": "^1.11.0",
|
||||
"drizzle-orm": "^0.29.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@acme/eslint-config": "workspace:^0.2.0",
|
||||
"@acme/prettier-config": "workspace:^0.1.0",
|
||||
"@acme/tsconfig": "workspace:^0.1.0",
|
||||
"dotenv-cli": "^7.3.0",
|
||||
"drizzle-kit": "^0.20.6",
|
||||
"eslint": "^8.53.0",
|
||||
"prettier": "^3.1.0",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"extends": [
|
||||
"@acme/eslint-config/base"
|
||||
]
|
||||
},
|
||||
"prettier": "@acme/prettier-config"
|
||||
}
|
||||
9
packages/db/schema/_table.ts
Normal file
9
packages/db/schema/_table.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { mysqlTableCreator } from "drizzle-orm/mysql-core";
|
||||
|
||||
/**
|
||||
* 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 mySqlTable = mysqlTableCreator((name) => `t3turbo_${name}`);
|
||||
84
packages/db/schema/auth.ts
Normal file
84
packages/db/schema/auth.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import type { AdapterAccount } from "@auth/core/adapters";
|
||||
import { relations, sql } from "drizzle-orm";
|
||||
import {
|
||||
index,
|
||||
int,
|
||||
primaryKey,
|
||||
text,
|
||||
timestamp,
|
||||
varchar,
|
||||
} from "drizzle-orm/mysql-core";
|
||||
|
||||
import { mySqlTable } from "./_table";
|
||||
|
||||
export const users = mySqlTable("user", {
|
||||
id: varchar("id", { length: 255 }).notNull().primaryKey(),
|
||||
name: varchar("name", { length: 255 }),
|
||||
email: varchar("email", { length: 255 }).notNull(),
|
||||
emailVerified: timestamp("emailVerified", {
|
||||
mode: "date",
|
||||
fsp: 3,
|
||||
}).default(sql`CURRENT_TIMESTAMP(3)`),
|
||||
image: varchar("image", { length: 255 }),
|
||||
});
|
||||
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
accounts: many(accounts),
|
||||
}));
|
||||
|
||||
export const accounts = mySqlTable(
|
||||
"account",
|
||||
{
|
||||
userId: varchar("userId", { length: 255 }).notNull(),
|
||||
type: varchar("type", { length: 255 })
|
||||
.$type<AdapterAccount["type"]>()
|
||||
.notNull(),
|
||||
provider: varchar("provider", { length: 255 }).notNull(),
|
||||
providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
|
||||
refresh_token: varchar("refresh_token", { length: 255 }),
|
||||
access_token: varchar("access_token", { length: 255 }),
|
||||
expires_at: int("expires_at"),
|
||||
token_type: varchar("token_type", { length: 255 }),
|
||||
scope: varchar("scope", { length: 255 }),
|
||||
id_token: text("id_token"),
|
||||
session_state: varchar("session_state", { length: 255 }),
|
||||
},
|
||||
(account) => ({
|
||||
compoundKey: primaryKey(account.provider, account.providerAccountId),
|
||||
userIdIdx: index("userId_idx").on(account.userId),
|
||||
}),
|
||||
);
|
||||
|
||||
export const accountsRelations = relations(accounts, ({ one }) => ({
|
||||
user: one(users, { fields: [accounts.userId], references: [users.id] }),
|
||||
}));
|
||||
|
||||
export const sessions = mySqlTable(
|
||||
"session",
|
||||
{
|
||||
sessionToken: varchar("sessionToken", { length: 255 })
|
||||
.notNull()
|
||||
.primaryKey(),
|
||||
userId: varchar("userId", { length: 255 }).notNull(),
|
||||
expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||
},
|
||||
(session) => ({
|
||||
userIdIdx: index("userId_idx").on(session.userId),
|
||||
}),
|
||||
);
|
||||
|
||||
export const sessionsRelations = relations(sessions, ({ one }) => ({
|
||||
user: one(users, { fields: [sessions.userId], references: [users.id] }),
|
||||
}));
|
||||
|
||||
export const verificationTokens = mySqlTable(
|
||||
"verificationToken",
|
||||
{
|
||||
identifier: varchar("identifier", { length: 255 }).notNull(),
|
||||
token: varchar("token", { length: 255 }).notNull(),
|
||||
expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||
},
|
||||
(vt) => ({
|
||||
compoundKey: primaryKey(vt.identifier, vt.token),
|
||||
}),
|
||||
);
|
||||
14
packages/db/schema/post.ts
Normal file
14
packages/db/schema/post.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { serial, timestamp, varchar } from "drizzle-orm/mysql-core";
|
||||
|
||||
import { mySqlTable } from "./_table";
|
||||
|
||||
export const post = mySqlTable("post", {
|
||||
id: serial("id").primaryKey(),
|
||||
title: varchar("name", { length: 256 }).notNull(),
|
||||
content: varchar("content", { length: 256 }).notNull(),
|
||||
createdAt: timestamp("created_at")
|
||||
.default(sql`CURRENT_TIMESTAMP`)
|
||||
.notNull(),
|
||||
updatedAt: timestamp("updatedAt").onUpdateNow(),
|
||||
});
|
||||
8
packages/db/tsconfig.json
Normal file
8
packages/db/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@acme/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||
},
|
||||
"include": ["."],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user