diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml new file mode 100644 index 00000000..5d3526ed --- /dev/null +++ b/.github/workflows/cli.yml @@ -0,0 +1,19 @@ +name: CLI +on: + push: + paths: + - 'cli/**' +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + - name: Install dependencies + run: bun install + - name: 🔦 Run linter + run: bun run lint + - name: 🪐 Check TypeScript + run: bun run typecheck \ No newline at end of file diff --git a/README.md b/README.md index 350c476a..d0ef3140 100644 --- a/README.md +++ b/README.md @@ -23,23 +23,27 @@ Everything you need to build a production ready SaaS, it's a opinionated stack b [Next.js](https://nextjs.org/) - Framework
[Turborepo](https://turbo.build) - Build system
[Biome](https://biomejs.dev) - Linter, formatter
+[Sherif](https://github.com/QuiiBz/sherif) - Linter for monorepos
[TailwindCSS](https://tailwindcss.com/) - Styling
[Shadcn](https://ui.shadcn.com/) - UI components
[TypeScript](https://www.typescriptlang.org/) - Type safety
[Supabase](https://supabase.com/) - Authentication, database, storage
-[Upstash](https://upstash.com/) - Cache and rate limiting
[React Email](https://react.email/) - Email templates
-[Resend](https://resend.com/) - Email delivery
[i18n](https://next-international.vercel.app/) - Internationalization
-[Sentry](https://sentry.io/) - Error handling/monitoring
[Dub](https://dub.sh/) - Sharable links
-[Trigger.dev](https://trigger.dev/) - Background jobs
-[OpenPanel](https://openpanel.dev/) - Analytics
-[Polar](https://polar.sh) - Billing (coming soon)
[react-safe-action](https://next-safe-action.dev) - Validated Server Actions
[nuqs](https://nuqs.47ng.com/) - Type-safe search params state manager
[next-themes](https://next-themes-example.vercel.app/) - Theme manager
+## Services directory (optional) +[Upstash](https://upstash.com/) - Cache and rate limiting
+[Trigger.dev](https://trigger.dev/) - Background jobs
+[Polar](https://polar.sh) - Billing (coming soon)
+[OpenPanel](https://openpanel.dev/) - Analytics
+[Sentry](https://sentry.io/) - Error handling/monitoring
+[Resend](https://resend.com/) - Email delivery
+ + ## Directory Structure ``` @@ -70,38 +74,17 @@ Everything you need to build a production ready SaaS, it's a opinionated stack b Bun
Docker
-Upstash
-Dub
-Trigger.dev
-Resend
Supabase
-Sentry
-OpenPanel
## Getting Started Clone this repo locally with the following command: ```bash -bunx degit midday-ai/v1 v1 -``` - -1. Install dependencies using bun: - -```sh -bun i -``` - -2. Copy `.env.example` to `.env` and update the variables. - -```sh -# Copy .env.example to .env for each app -cp apps/api/.env.example apps/api/.env -cp apps/app/.env.example apps/app/.env -cp apps/web/.env.example apps/web/.env +bunx v1-run@latest init ``` -4. Start the development server from either bun or turbo: +Start the development server from either bun or turbo: ```ts bun dev // starts everything in development mode (web, app, api, email) @@ -115,6 +98,12 @@ bun migrate // run migrations bun seed // run seed ``` +## Add services + +```bash +bunx v1-run@latest add service +``` + ## How to use This boilerplate is inspired by our work on Midday, and it's designed to serve as a reference for real-world apps. Feel free to dive into the code and see how we've tackled various features. Whether you're looking to understand authentication flows, database interactions, or UI components, you'll find practical, battle-tested implementations throughout the codebase. It's not just a starting point; it's a learning resource that can help you build your own applications. @@ -124,7 +113,7 @@ With this, you have a great starting point for your own project. Vercel deployment will guide you through creating a Supabase account and project. -[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fmidday-ai%2Fv1&env=RESEND_API_KEY,UPSTASH_REDIS_REST_URL,UPSTASH_REDIS_REST_TOKEN,SENTRY_AUTH_TOKEN,NEXT_PUBLIC_SENTRY_DSN,SENTRY_ORG,SENTRY_PROJECT,DUB_API_KEY,NEXT_PUBLIC_OPENPANEL_CLIENT_ID,OPENPANEL_SECRET_KEY&project-name=create-v1&repository-name=create-v1&redirect-url=https%3A%2F%2Fv1.run&demo-title=Create%20v1&demo-description=An%20open-source%20starter%20kit%20based%20on%20Midday.&demo-url=https%3A%2F%2Fv1.run&demo-image=https%3A%2F%2Fv1.run%2Fopengraph-image.png&integration-ids=oac_VqOgBHqhEoFTPzGkPd7L0iH6) +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fmidday-ai%2Fv1&project-name=create-v1&repository-name=create-v1&redirect-url=https%3A%2F%2Fv1.run&demo-title=Create%20v1&demo-description=An%20open-source%20starter%20kit%20based%20on%20Midday.&demo-url=https%3A%2F%2Fv1.run&demo-image=https%3A%2F%2Fv1.run%2Fopengraph-image.png&integration-ids=oac_VqOgBHqhEoFTPzGkPd7L0iH6) ## Recognition diff --git a/apps/app/.env.example b/apps/app/.env.example index a31ad30a..9f6536c4 100644 --- a/apps/app/.env.example +++ b/apps/app/.env.example @@ -1,24 +1,4 @@ # Supabase NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321 NEXT_PUBLIC_SUPABASE_ANON_KEY= -SUPABASE_SERVICE_KEY= - -# Resend -RESEND_API_KEY= - -# Upstash -UPSTASH_REDIS_REST_URL= -UPSTASH_REDIS_REST_TOKEN= - -# Dub -DUB_API_KEY= - -# OpenPanel -NEXT_PUBLIC_OPENPANEL_CLIENT_ID= -OPENPANEL_SECRET_KEY= - -# Sentry -SENTRY_AUTH_TOKEN= -NEXT_PUBLIC_SENTRY_DSN= -SENTRY_ORG= -SENTRY_PROJECT= \ No newline at end of file +SUPABASE_SERVICE_KEY= \ No newline at end of file diff --git a/apps/app/next.config.mjs b/apps/app/next.config.mjs index 15854529..45a5aa0b 100644 --- a/apps/app/next.config.mjs +++ b/apps/app/next.config.mjs @@ -1,19 +1,8 @@ import "./src/env.mjs"; -import { withSentryConfig } from "@sentry/nextjs"; /** @type {import('next').NextConfig} */ const nextConfig = { transpilePackages: ["@v1/supabase"], - experimental: { - instrumentationHook: process.env.NODE_ENV === "production", - }, }; -export default withSentryConfig(nextConfig, { - silent: !process.env.CI, - telemetry: false, - widenClientFileUpload: true, - hideSourceMaps: true, - disableLogger: true, - tunnelRoute: "/monitoring", -}); +export default nextConfig; diff --git a/apps/app/package.json b/apps/app/package.json index b61853a9..b59d580b 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -12,11 +12,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@v1/analytics": "workspace:*", - "@v1/kv": "workspace:*", "@v1/supabase": "workspace:*", "@v1/ui": "workspace:*", - "dub": "^0.36.5", "geist": "^1.3.1", "next": "14.2.7", "next-international": "^1.2.4", @@ -28,8 +25,6 @@ "zod": "^3.23.8" }, "devDependencies": { - "@sentry/nextjs": "^8", - "@supabase/sentry-js-integration": "^0.2.0", "@types/node": "^22", "@types/react": "^18", "@types/react-dom": "^18", diff --git a/apps/app/src/actions/post/schema.ts b/apps/app/src/actions/post/schema.ts deleted file mode 100644 index beb13d5c..00000000 --- a/apps/app/src/actions/post/schema.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { z } from "zod"; - -export const shareLinkSchema = z.object({ - postId: z.string(), - baseUrl: z.string(), -}); diff --git a/apps/app/src/actions/post/share-link-action.ts b/apps/app/src/actions/post/share-link-action.ts deleted file mode 100644 index ae692a82..00000000 --- a/apps/app/src/actions/post/share-link-action.ts +++ /dev/null @@ -1,18 +0,0 @@ -"use server"; - -import { authActionClient } from "@/actions/safe-action"; -import { dub } from "@/lib/dub"; -import { shareLinkSchema } from "./schema"; - -export const shareLinkAction = authActionClient - .schema(shareLinkSchema) - .metadata({ - name: "share-link", - }) - .action(async ({ parsedInput: { postId, baseUrl } }) => { - const link = await dub.links.create({ - url: `${baseUrl}/post/${postId}`, - }); - - return link?.shortLink; - }); diff --git a/apps/app/src/actions/safe-action.ts b/apps/app/src/actions/safe-action.ts index 7365baf8..f567b1df 100644 --- a/apps/app/src/actions/safe-action.ts +++ b/apps/app/src/actions/safe-action.ts @@ -1,6 +1,3 @@ -import * as Sentry from "@sentry/nextjs"; -import { setupAnalytics } from "@v1/analytics/server"; -import { ratelimit } from "@v1/kv/ratelimit"; import { logger } from "@v1/logger"; import { getUser } from "@v1/supabase/queries"; import { createClient } from "@v1/supabase/server"; @@ -8,8 +5,6 @@ import { DEFAULT_SERVER_ERROR_MESSAGE, createSafeActionClient, } from "next-safe-action"; -import { headers } from "next/headers"; -import { z } from "zod"; const handleServerError = (e: Error) => { console.error("Action error:", e.message); @@ -27,17 +22,6 @@ export const actionClient = createSafeActionClient({ export const actionClientWithMeta = createSafeActionClient({ handleServerError, - defineMetadataSchema() { - return z.object({ - name: z.string(), - track: z - .object({ - event: z.string(), - channel: z.string(), - }) - .optional(), - }); - }, }); export const authActionClient = actionClientWithMeta @@ -54,26 +38,8 @@ export const authActionClient = actionClientWithMeta return result; }) - .use(async ({ next, metadata }) => { - const ip = headers().get("x-forwarded-for"); - - const { success, remaining } = await ratelimit.limit( - `${ip}-${metadata.name}`, - ); - - if (!success) { - throw new Error("Too many requests"); - } - return next({ - ctx: { - ratelimit: { - remaining, - }, - }, - }); - }) - .use(async ({ next, metadata }) => { + .use(async ({ next }) => { const { data: { user }, } = await getUser(); @@ -83,22 +49,10 @@ export const authActionClient = actionClientWithMeta throw new Error("Unauthorized"); } - if (metadata) { - const analytics = await setupAnalytics({ - userId: user.id, - }); - - if (metadata.track) { - analytics.track(metadata.track); - } - } - - return Sentry.withServerActionInstrumentation(metadata.name, async () => { - return next({ - ctx: { - supabase, - user, - }, - }); + return next({ + ctx: { + supabase, + user, + }, }); }); diff --git a/apps/app/src/actions/user/update-post-action.ts b/apps/app/src/actions/user/update-post-action.ts index e3e51c2f..76f7937c 100644 --- a/apps/app/src/actions/user/update-post-action.ts +++ b/apps/app/src/actions/user/update-post-action.ts @@ -6,9 +6,6 @@ import { updateUserSchema } from "./schema"; export const updateUserAction = authActionClient .schema(updateUserSchema) - .metadata({ - name: "update-user", - }) .action(async ({ parsedInput: input, ctx: { user } }) => { const result = await updateUser(user.id, input); diff --git a/apps/app/src/env.mjs b/apps/app/src/env.mjs index 00c22418..b5bf8cc3 100644 --- a/apps/app/src/env.mjs +++ b/apps/app/src/env.mjs @@ -10,29 +10,17 @@ export const env = createEnv({ PORT: z.coerce.number().default(3000), }, server: { - OPENPANEL_SECRET_KEY: z.string(), - RESEND_API_KEY: z.string(), SUPABASE_SERVICE_KEY: z.string(), - UPSTASH_REDIS_REST_TOKEN: z.string(), - UPSTASH_REDIS_REST_URL: z.string(), }, client: { - NEXT_PUBLIC_OPENPANEL_CLIENT_ID: z.string(), NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string(), NEXT_PUBLIC_SUPABASE_URL: z.string(), }, runtimeEnv: { - NEXT_PUBLIC_OPENPANEL_CLIENT_ID: - process.env.NEXT_PUBLIC_OPENPANEL_CLIENT_ID, - NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN, NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY, NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL, - OPENPANEL_SECRET_KEY: process.env.OPENPANEL_SECRET_KEY, PORT: process.env.PORT, - RESEND_API_KEY: process.env.RESEND_API_KEY, SUPABASE_SERVICE_KEY: process.env.SUPABASE_SERVICE_KEY, - UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN, - UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL, VERCEL_URL: process.env.VERCEL_URL, }, skipValidation: !!process.env.CI || !!process.env.SKIP_ENV_VALIDATION, diff --git a/apps/app/src/services/.gitkeep b/apps/app/src/services/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/apps/web/.env.example b/apps/web/.env.example index 8de760ef..e69de29b 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -1,6 +0,0 @@ -# OpenPanel -NEXT_PUBLIC_OPENPANEL_CLIENT_ID= -OPENPANEL_SECRET_KEY= - -# Cal.com -NEXT_PUBLIC_CAL_LINK= \ No newline at end of file diff --git a/apps/web/package.json b/apps/web/package.json index 3c38cfbd..f1d7eb81 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -12,8 +12,6 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@calcom/embed-react": "^1.5.0", - "@v1/analytics": "workspace:*", "@v1/ui": "workspace:*", "geist": "^1.3.1", "next": "14.2.7", diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx index afa8bf35..94e149e2 100644 --- a/apps/web/src/app/layout.tsx +++ b/apps/web/src/app/layout.tsx @@ -1,7 +1,6 @@ import "@v1/ui/globals.css"; import { Footer } from "@/components/footer"; import { Header } from "@/components/header"; -import { Provider as AnalyticsProvider } from "@v1/analytics/client"; import { cn } from "@v1/ui/cn"; import { GeistMono } from "geist/font/mono"; import { GeistSans } from "geist/font/sans"; @@ -36,8 +35,6 @@ export default function RootLayout({
{children}