Skip to content

Commit

Permalink
Add Db Package (#17)
Browse files Browse the repository at this point in the history
* add db package with drizzle orm

* update lock file

* switch to regular postgres

* adding db to trpc context

* add dotenv

* copy env before building

* set db adapter to vercel pg

* adding health check

* add db health check

* fixed eslint

* fix env vars

* fix false positive linting errors

* make sheld nonnullable in UI component
  • Loading branch information
shawnmclean authored Jul 17, 2024
1 parent 8e55f60 commit ccd3d1b
Show file tree
Hide file tree
Showing 29 changed files with 1,368 additions and 226 deletions.
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Since .env is gitignored, you can use .env.example to build a new `.env` file when you clone the repo.
# Keep this file up-to-date when you add new variables to \`.env\`.

# This file will be committed to version control, so make sure not to have any secrets in it.
# If you are cloning this repo, create a copy of this file named `.env` and populate it with your secrets.

# The database URL is used to connect to your Supabase database.
POSTGRES_URL="postgresql://postgres:postgres@localhost:5432/sovoli"

8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ jobs:
node-version: 20
cache: "pnpm"

# - name: Copy env
# shell: bash
# run: cp .env.example .env

- name: 📦 Install dependencies
run: pnpm install

Expand Down Expand Up @@ -92,6 +96,10 @@ jobs:
node-version: 20
cache: "pnpm"

# - name: Copy env
# shell: bash
# run: cp .env.example .env

- name: 🏗 Setup EAS
uses: expo/expo-github-action@v8
with:
Expand Down
20 changes: 20 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug full stack",
"type": "node-terminal",
"request": "launch",
"command": "pnpm dev",
"cwd": "${workspaceFolder}/apps/sovoli.com",
"serverReadyAction": {
"pattern": "- Local:.+(https?://.+)",
"uriFormat": "%s",
"action": "debugWithChrome"
}
}
]
}
1 change: 1 addition & 0 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"react-native": "0.74.3",
"react-native-web": "0.19.12",
"react-native-reanimated": "3.10.1",
"superjson": "2.2.1",
"tailwind-merge": "2.1.0",
"tailwindcss-animate": "1.0.7",
"class-variance-authority": "0.7.0",
Expand Down
4 changes: 4 additions & 0 deletions apps/sovoli.com/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ const nextConfig = {
],
},
webpack: (config) => {
// Needed to get postgres db adapter: https://github.com/vercel/next.js/discussions/50177#discussioncomment-9409065
// config.externals.push("cloudflare:sockets");
// config.externalsType = "commonjs";

config.resolve.alias = {
...(config.resolve.alias || {}),
// Transform all direct `react-native` imports to `react-native-web`
Expand Down
14 changes: 10 additions & 4 deletions apps/sovoli.com/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,29 @@
"private": true,
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"build": "pnpm with-env next build",
"clean": "git clean -xdf .next .turbo node_modules",
"dev": "pnpm with-env next dev",
"start": "pnpm with-env next start",
"lint": "eslint",
"typecheck": "tsc --noEmit"
"typecheck": "tsc --noEmit",
"with-env": "dotenv -e ../../.env --"
},
"dependencies": {
"@expo/next-adapter": "6.0.0",
"@rn-primitives/portal": "1.0.3",
"@sovoli/ui": "workspace:*",
"@sovoli/api": "workspace:*",
"@sovoli/db": "workspace:*",
"@tanstack/react-query": "^5.51.1",
"@trpc/client": "11.0.0-rc.461",
"@t3-oss/env-nextjs": "0.10.1",
"@trpc/next": "11.0.0-rc.461",
"@trpc/react-query": "11.0.0-rc.461",
"@trpc/server": "11.0.0-rc.461",
"@vercel/analytics": "1.3.1",
"@vercel/speed-insights": "1.0.12",
"superjson": "2.2.1",
"nativewind": "4.0.36",
"next": "14.2.4",
"raf": "3.4.1",
Expand All @@ -39,6 +44,7 @@
"@types/react-dom": "18.2.18",
"autoprefixer": "10.4.19",
"babel-plugin-react-native-web": "0.19.10",
"dotenv-cli": "7.4.2",
"eslint": "9.6.0",
"postcss": "8.4.38",
"tailwindcss": "3.4.4",
Expand Down
21 changes: 21 additions & 0 deletions apps/sovoli.com/src/app/(profile)/[username]/_components/User.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"use client";

import { api } from "~/trpc/react";
import { UserScreen } from "@sovoli/ui/screens/user";

export function User({ username }: { username: string }) {
const [user] = api.user.byUsername.useSuspenseQuery({ username });

if (!user) {
return null;
}

return (
<>
<h1>
{user.username} - {user.id} - {user.name}
</h1>
<UserScreen username={user.username} />
</>
);
}
17 changes: 11 additions & 6 deletions apps/sovoli.com/src/app/(profile)/[username]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
/** @jsxImportSource react */

import { UserScreen } from "@sovoli/ui/screens/user";
import { api, HydrateClient } from "~/trpc/server";
import { User } from "./_components/User";
import { Suspense } from "react";

export default function UserPage({ params }: { params: { username: string } }) {
void api.user.byUsername.prefetch({ username: params.username });

export default function Profile({ params }: { params: { username: string } }) {
return (
<div className="min-h-screen sm:pl-60 dark:bg-black">
<h1>Profile: {params.username}</h1>
<UserScreen username={params.username} />
</div>
<HydrateClient>
<Suspense fallback={<div>Loading...</div>}>
<User username={params.username} />
</Suspense>
</HydrateClient>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import { ShelfScreen } from "@sovoli/ui/screens/mybooks/shelf";
export function Shelf({ username, slug }: { username: string; slug: string }) {
const [shelf] = api.shelf.bySlug.useSuspenseQuery({ slug, username });

if (!shelf) {
return null;
}

return (
<>
<h1>
Expand Down
45 changes: 45 additions & 0 deletions apps/sovoli.com/src/app/api/health/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { api } from "~/trpc/server";
import { db } from "@sovoli/db/client";
import { sql } from "drizzle-orm";

export async function GET() {
// health check for all systems
// Systems:
// - db
// - auth
// - storage
// - analytics
// - database
// - AI Gateway (OpenAI, etc)
let trpcHealth, dbHealth;

try {
trpcHealth = await api.health.check();
} catch (e) {
trpcHealth = { status: "error", error: e };
}
try {
// check round trip time to db health check
const startTime = Date.now();

const dbResponse = await db.execute(sql`SELECT NOW()`);

const endTime = Date.now();
const roundTripTime = endTime - startTime;

dbHealth = {
status: "ok",
roundTripTime: `${roundTripTime}ms`,
dbTime: dbResponse.rows[0]?.now,
};
} catch (e) {
dbHealth = {
status: "error",
error: e instanceof Error ? e.message : String(e),
};
}

const data = { status: "ok", trpc: trpcHealth, db: dbHealth };

return Response.json(data);
}
39 changes: 39 additions & 0 deletions apps/sovoli.com/src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { createEnv } from "@t3-oss/env-nextjs";
import { vercel } from "@t3-oss/env-nextjs/presets";
import { z } from "zod";

// import { env as authEnv } from "@acme/auth/env";

export const env = createEnv({
extends: [vercel()],
shared: {
NODE_ENV: z
.enum(["development", "production", "test"])
.default("development"),
},
/**
* Specify your server-side environment variables schema here.
* This way you can ensure the app isn't built with invalid env vars.
*/
server: {
POSTGRES_URL: z.string().url(),
},

/**
* Specify your client-side environment variables schema here.
* For them to be exposed to the client, prefix them with `NEXT_PUBLIC_`.
*/
client: {
// NEXT_PUBLIC_CLIENTVAR: z.string(),
},
/**
* Destructure all variables from `process.env` to make sure they aren't tree-shaken away.
*/
experimental__runtimeEnv: {
NODE_ENV: process.env.NODE_ENV,

// NEXT_PUBLIC_CLIENTVAR: process.env.NEXT_PUBLIC_CLIENTVAR,
},
skipValidation:
!!process.env.CI || process.env.npm_lifecycle_event === "lint",
});
7 changes: 3 additions & 4 deletions apps/sovoli.com/src/trpc/react.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import SuperJSON from "superjson";

import type { AppRouter } from "@sovoli/api";

// import { env } from "~/env";
import { env } from "~/env";
import { createQueryClient } from "./query-client";

let clientQueryClientSingleton: QueryClient | undefined = undefined;
Expand All @@ -33,7 +33,7 @@ export function TRPCReactProvider(props: { children: React.ReactNode }) {
links: [
loggerLink({
enabled: (op) =>
process.env.NODE_ENV === "development" ||
env.NODE_ENV === "development" ||
(op.direction === "down" && op.result instanceof Error),
}),
unstable_httpBatchStreamLink({
Expand All @@ -60,7 +60,6 @@ export function TRPCReactProvider(props: { children: React.ReactNode }) {

const getBaseUrl = () => {
if (typeof window !== "undefined") return window.location.origin;
if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
// eslint-disable-next-line no-restricted-properties
if (env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`;
return `http://localhost:${process.env.PORT ?? 3000}`;
};
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
"lint": "turbo run lint",
"clean": "git clean -xdf node_modules",
"clean:workspaces": "turbo run clean",
"db:push": "turbo -F @sovoli/db push",
"db:studio": "turbo -F @sovoli/db studio",
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\" --ignore-path .gitignore",
"typecheck": "turbo run typecheck"
},
Expand Down
1 change: 1 addition & 0 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"typecheck": "tsc --noEmit --emitDeclarationOnly false"
},
"dependencies": {
"@sovoli/db": "workspace:*",
"@trpc/server": "11.0.0-rc.461",
"superjson": "2.2.1",
"zod": "3.23.8"
Expand Down
4 changes: 4 additions & 0 deletions packages/api/src/root.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { healthRouter } from "./router/health";
import { shelfRouter } from "./router/shelf";
import { userRouter } from "./router/user";
import { createTRPCRouter } from "./trpc";

export const appRouter = createTRPCRouter({
user: userRouter,
shelf: shelfRouter,
health: healthRouter,
});

// export type definition of API
Expand Down
12 changes: 12 additions & 0 deletions packages/api/src/router/health.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { TRPCRouterRecord } from "@trpc/server";
// import { z } from "zod";
// import { eq } from "@sovoli/db";
// import { User } from "@sovoli/db/schema";

import { publicProcedure } from "../trpc";

export const healthRouter = {
check: publicProcedure.query(() => {
return { status: "ok" };
}),
} satisfies TRPCRouterRecord;
7 changes: 1 addition & 6 deletions packages/api/src/router/shelf.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { TRPCError } from "@trpc/server";
import type { TRPCRouterRecord } from "@trpc/server";
import { z } from "zod";

Expand All @@ -9,13 +8,9 @@ export const shelfRouter = {
bySlug: publicProcedure
.input(z.object({ username: z.string(), slug: z.string() }))
.query(({ input }) => {
const shelf = shelves.find(
return shelves.find(
(shelf) =>
shelf.username === input.username && shelf.slug === input.slug
);
if (!shelf) {
throw new TRPCError({ code: "NOT_FOUND" });
}
return shelf;
}),
} satisfies TRPCRouterRecord;
16 changes: 16 additions & 0 deletions packages/api/src/router/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { TRPCRouterRecord } from "@trpc/server";
import { z } from "zod";
import { eq } from "@sovoli/db";
import { User } from "@sovoli/db/schema";

import { publicProcedure } from "../trpc";

export const userRouter = {
byUsername: publicProcedure
.input(z.object({ username: z.string() }))
.query(({ ctx, input }) => {
return ctx.db.query.User.findFirst({
where: eq(User.username, input.username),
});
}),
} satisfies TRPCRouterRecord;
4 changes: 2 additions & 2 deletions packages/api/src/trpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ZodError } from "zod";

// import type { Session } from "@acme/auth";
// import { auth, validateToken } from "@acme/auth";
// import { db } from "@acme/db/client";
import { db } from "@sovoli/db/client";

/**
* Isomorphic Session getter for API requests
Expand Down Expand Up @@ -49,7 +49,7 @@ export const createTRPCContext = (opts: {

return {
// session,
// db,
db,
token: authToken,
};
};
Expand Down
12 changes: 12 additions & 0 deletions packages/db/drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineConfig } from "drizzle-kit";

if (!process.env.POSTGRES_URL) {
throw new Error("Missing POSTGRES_URL");
}

export default defineConfig({
schema: "./src/schema.ts",
out: "./drizzle",
dialect: "postgresql",
dbCredentials: { url: process.env.POSTGRES_URL },
});
Loading

0 comments on commit ccd3d1b

Please sign in to comment.