shipd 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/base-package/app/globals.css +126 -0
  2. package/base-package/app/layout.tsx +53 -0
  3. package/base-package/app/page.tsx +15 -0
  4. package/base-package/base.config.json +57 -0
  5. package/base-package/components/ui/avatar.tsx +53 -0
  6. package/base-package/components/ui/badge.tsx +46 -0
  7. package/base-package/components/ui/button.tsx +59 -0
  8. package/base-package/components/ui/card.tsx +92 -0
  9. package/base-package/components/ui/chart.tsx +353 -0
  10. package/base-package/components/ui/checkbox.tsx +32 -0
  11. package/base-package/components/ui/dialog.tsx +135 -0
  12. package/base-package/components/ui/dropdown-menu.tsx +257 -0
  13. package/base-package/components/ui/form.tsx +167 -0
  14. package/base-package/components/ui/input.tsx +21 -0
  15. package/base-package/components/ui/label.tsx +24 -0
  16. package/base-package/components/ui/progress.tsx +31 -0
  17. package/base-package/components/ui/resizable.tsx +56 -0
  18. package/base-package/components/ui/select.tsx +185 -0
  19. package/base-package/components/ui/separator.tsx +28 -0
  20. package/base-package/components/ui/sheet.tsx +139 -0
  21. package/base-package/components/ui/skeleton.tsx +13 -0
  22. package/base-package/components/ui/sonner.tsx +25 -0
  23. package/base-package/components/ui/switch.tsx +31 -0
  24. package/base-package/components/ui/tabs.tsx +66 -0
  25. package/base-package/components/ui/textarea.tsx +18 -0
  26. package/base-package/components/ui/toggle-group.tsx +73 -0
  27. package/base-package/components/ui/toggle.tsx +47 -0
  28. package/base-package/components/ui/tooltip.tsx +61 -0
  29. package/base-package/components.json +21 -0
  30. package/base-package/eslint.config.mjs +16 -0
  31. package/base-package/lib/utils.ts +6 -0
  32. package/base-package/middleware.ts +12 -0
  33. package/base-package/next.config.ts +27 -0
  34. package/base-package/package.json +49 -0
  35. package/base-package/postcss.config.mjs +5 -0
  36. package/base-package/public/favicon.svg +4 -0
  37. package/base-package/tailwind.config.ts +89 -0
  38. package/base-package/tsconfig.json +27 -0
  39. package/dist/index.js +1862 -948
  40. package/features/ai-chat/README.md +258 -0
  41. package/features/ai-chat/app/api/chat/route.ts +16 -0
  42. package/features/ai-chat/app/dashboard/_components/chatbot.tsx +39 -0
  43. package/features/ai-chat/app/dashboard/chat/page.tsx +73 -0
  44. package/features/ai-chat/feature.config.json +22 -0
  45. package/features/analytics/README.md +308 -0
  46. package/features/analytics/feature.config.json +20 -0
  47. package/features/analytics/lib/posthog.ts +36 -0
  48. package/features/auth/README.md +336 -0
  49. package/features/auth/app/api/auth/[...all]/route.ts +4 -0
  50. package/features/auth/app/dashboard/layout.tsx +15 -0
  51. package/features/auth/app/dashboard/page.tsx +140 -0
  52. package/features/auth/app/sign-in/page.tsx +228 -0
  53. package/features/auth/app/sign-up/page.tsx +243 -0
  54. package/features/auth/auth-schema.ts +47 -0
  55. package/features/auth/components/auth/setup-instructions.tsx +123 -0
  56. package/features/auth/feature.config.json +33 -0
  57. package/features/auth/lib/auth-client.ts +8 -0
  58. package/features/auth/lib/auth.ts +295 -0
  59. package/features/auth/lib/email-stub.ts +55 -0
  60. package/features/auth/lib/email.ts +47 -0
  61. package/features/auth/middleware.patch.ts +43 -0
  62. package/features/database/README.md +256 -0
  63. package/features/database/db/drizzle.ts +48 -0
  64. package/features/database/db/schema.ts +21 -0
  65. package/features/database/drizzle.config.ts +13 -0
  66. package/features/database/feature.config.json +30 -0
  67. package/features/email/README.md +282 -0
  68. package/features/email/emails/components/layout.tsx +181 -0
  69. package/features/email/emails/password-reset.tsx +67 -0
  70. package/features/email/emails/payment-failed.tsx +167 -0
  71. package/features/email/emails/subscription-confirmation.tsx +129 -0
  72. package/features/email/emails/welcome.tsx +100 -0
  73. package/features/email/feature.config.json +22 -0
  74. package/features/email/lib/email.ts +118 -0
  75. package/features/file-upload/README.md +271 -0
  76. package/features/file-upload/app/api/upload-image/route.ts +64 -0
  77. package/features/file-upload/app/dashboard/upload/page.tsx +324 -0
  78. package/features/file-upload/feature.config.json +23 -0
  79. package/features/file-upload/lib/upload-image.ts +28 -0
  80. package/features/marketing-landing/README.md +266 -0
  81. package/features/marketing-landing/app/page.tsx +25 -0
  82. package/features/marketing-landing/components/homepage/cli-workflow-section.tsx +231 -0
  83. package/features/marketing-landing/components/homepage/features-section.tsx +152 -0
  84. package/features/marketing-landing/components/homepage/footer.tsx +53 -0
  85. package/features/marketing-landing/components/homepage/hero-section.tsx +112 -0
  86. package/features/marketing-landing/components/homepage/integrations.tsx +124 -0
  87. package/features/marketing-landing/components/homepage/navigation.tsx +116 -0
  88. package/features/marketing-landing/components/homepage/news-section.tsx +82 -0
  89. package/features/marketing-landing/components/homepage/pricing-section.tsx +98 -0
  90. package/features/marketing-landing/components/homepage/testimonials-section.tsx +34 -0
  91. package/features/marketing-landing/components/logos/BetterAuth.tsx +21 -0
  92. package/features/marketing-landing/components/logos/NeonPostgres.tsx +41 -0
  93. package/features/marketing-landing/components/logos/Nextjs.tsx +72 -0
  94. package/features/marketing-landing/components/logos/Polar.tsx +7 -0
  95. package/features/marketing-landing/components/logos/TailwindCSS.tsx +27 -0
  96. package/features/marketing-landing/components/logos/index.ts +6 -0
  97. package/features/marketing-landing/components/logos/shadcnui.tsx +8 -0
  98. package/features/marketing-landing/feature.config.json +23 -0
  99. package/features/payments/README.md +306 -0
  100. package/features/payments/app/api/subscription/route.ts +25 -0
  101. package/features/payments/app/dashboard/payment/_components/manage-subscription.tsx +22 -0
  102. package/features/payments/app/dashboard/payment/page.tsx +126 -0
  103. package/features/payments/app/success/page.tsx +123 -0
  104. package/features/payments/feature.config.json +31 -0
  105. package/features/payments/lib/polar-products.ts +49 -0
  106. package/features/payments/lib/subscription.ts +148 -0
  107. package/features/payments/payments-schema.ts +30 -0
  108. package/features/seo/README.md +244 -0
  109. package/features/seo/app/blog/[slug]/page.tsx +314 -0
  110. package/features/seo/app/blog/page.tsx +107 -0
  111. package/features/seo/app/robots.txt +13 -0
  112. package/features/seo/app/sitemap.ts +70 -0
  113. package/features/seo/feature.config.json +19 -0
  114. package/features/seo/lib/seo-utils.ts +163 -0
  115. package/package.json +3 -1
@@ -0,0 +1,295 @@
1
+ import { db } from "@/db/drizzle";
2
+ import { account, session, user, verification } from "@/db/schema";
3
+ import { eq } from "drizzle-orm";
4
+ // Subscription is optional (only if payments module is installed)
5
+ // We'll check for it at runtime in the webhook handler
6
+ import {
7
+ checkout,
8
+ polar,
9
+ portal,
10
+ usage,
11
+ webhooks,
12
+ } from "@polar-sh/better-auth";
13
+ import { Polar } from "@polar-sh/sdk";
14
+ import { betterAuth } from "better-auth";
15
+ import { drizzleAdapter } from "better-auth/adapters/drizzle";
16
+ import { nextCookies } from "better-auth/next-js";
17
+ import {
18
+ sendPasswordResetEmail,
19
+ sendSubscriptionConfirmationEmail,
20
+ sendPaymentFailedEmail,
21
+ } from "@/lib/email";
22
+
23
+ // Utility function to safely parse dates
24
+ function safeParseDate(value: string | Date | null | undefined): Date | null {
25
+ if (!value) return null;
26
+ if (value instanceof Date) return value;
27
+ return new Date(value);
28
+ }
29
+
30
+ // Check if Polar is configured
31
+ const isPolarConfigured = !!(
32
+ process.env.POLAR_ACCESS_TOKEN &&
33
+ process.env.POLAR_WEBHOOK_SECRET &&
34
+ process.env.NEXT_PUBLIC_STARTER_TIER &&
35
+ process.env.NEXT_PUBLIC_STARTER_SLUG
36
+ );
37
+
38
+ if (!isPolarConfigured && process.env.NODE_ENV === "development") {
39
+ console.warn(
40
+ "⚠️ Polar.sh not configured - subscription features disabled. Configure POLAR_* env vars to enable payments.",
41
+ );
42
+ }
43
+
44
+ const polarClient = isPolarConfigured
45
+ ? new Polar({
46
+ accessToken: process.env.POLAR_ACCESS_TOKEN,
47
+ })
48
+ : null;
49
+
50
+ export const auth = betterAuth({
51
+ trustedOrigins: [`${process.env.NEXT_PUBLIC_APP_URL}`],
52
+ allowedDevOrigins: [`${process.env.NEXT_PUBLIC_APP_URL}`],
53
+ cookieCache: {
54
+ enabled: true,
55
+ maxAge: 5 * 60, // Cache duration in seconds
56
+ },
57
+ database: drizzleAdapter(db, {
58
+ provider: "pg",
59
+ schema: {
60
+ user,
61
+ session,
62
+ account,
63
+ verification,
64
+ },
65
+ }),
66
+ emailAndPassword: {
67
+ enabled: true,
68
+ sendResetPassword: async ({ user, url }) => {
69
+ // Send password reset email
70
+ await sendPasswordResetEmail(user.email, url);
71
+ },
72
+ },
73
+ user: {
74
+ additionalFields: {
75
+ name: {
76
+ type: "string",
77
+ required: false,
78
+ },
79
+ },
80
+ },
81
+ socialProviders: {
82
+ google: {
83
+ clientId: process.env.GOOGLE_CLIENT_ID!,
84
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
85
+ },
86
+ },
87
+ // TODO: Implement welcome emails via a custom sign-up API route
88
+ // Better Auth hooks API may have changed - using webhook approach instead
89
+ plugins: [
90
+ ...(isPolarConfigured && polarClient
91
+ ? [
92
+ polar({
93
+ client: polarClient,
94
+ createCustomerOnSignUp: true,
95
+ use: [
96
+ checkout({
97
+ products: [
98
+ {
99
+ productId: process.env.NEXT_PUBLIC_STARTER_TIER!,
100
+ slug: process.env.NEXT_PUBLIC_STARTER_SLUG!,
101
+ },
102
+ ],
103
+ successUrl: `${process.env.NEXT_PUBLIC_APP_URL}/${process.env.POLAR_SUCCESS_URL}`,
104
+ authenticatedUsersOnly: true,
105
+ }),
106
+ portal(),
107
+ usage(),
108
+ webhooks({
109
+ secret: process.env.POLAR_WEBHOOK_SECRET!,
110
+ onPayload: async ({ data, type }) => {
111
+ if (
112
+ type === "subscription.created" ||
113
+ type === "subscription.active" ||
114
+ type === "subscription.canceled" ||
115
+ type === "subscription.revoked" ||
116
+ type === "subscription.uncanceled" ||
117
+ type === "subscription.updated"
118
+ ) {
119
+ console.log("🎯 Processing subscription webhook:", type);
120
+ console.log("📦 Payload data:", JSON.stringify(data, null, 2));
121
+
122
+ try {
123
+ // STEP 1: Extract user ID from customer data
124
+ const userId = data.customer?.externalId;
125
+ // STEP 2: Build subscription data
126
+ const subscriptionData = {
127
+ id: data.id,
128
+ createdAt: new Date(data.createdAt),
129
+ modifiedAt: safeParseDate(data.modifiedAt),
130
+ amount: data.amount,
131
+ currency: data.currency,
132
+ recurringInterval: data.recurringInterval,
133
+ status: data.status,
134
+ currentPeriodStart:
135
+ safeParseDate(data.currentPeriodStart) || new Date(),
136
+ currentPeriodEnd:
137
+ safeParseDate(data.currentPeriodEnd) || new Date(),
138
+ cancelAtPeriodEnd: data.cancelAtPeriodEnd || false,
139
+ canceledAt: safeParseDate(data.canceledAt),
140
+ startedAt: safeParseDate(data.startedAt) || new Date(),
141
+ endsAt: safeParseDate(data.endsAt),
142
+ endedAt: safeParseDate(data.endedAt),
143
+ customerId: data.customerId,
144
+ productId: data.productId,
145
+ discountId: data.discountId || null,
146
+ checkoutId: data.checkoutId || "",
147
+ customerCancellationReason:
148
+ data.customerCancellationReason || null,
149
+ customerCancellationComment:
150
+ data.customerCancellationComment || null,
151
+ metadata: data.metadata
152
+ ? JSON.stringify(data.metadata)
153
+ : null,
154
+ customFieldData: data.customFieldData
155
+ ? JSON.stringify(data.customFieldData)
156
+ : null,
157
+ userId: userId as string | null,
158
+ };
159
+
160
+ console.log("💾 Final subscription data:", {
161
+ id: subscriptionData.id,
162
+ status: subscriptionData.status,
163
+ userId: subscriptionData.userId,
164
+ amount: subscriptionData.amount,
165
+ });
166
+
167
+ // STEP 3: Use Drizzle's onConflictDoUpdate for proper upsert
168
+ // Only if subscription table exists (payments module installed)
169
+ // Dynamically import subscription table
170
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
171
+ let subscriptionTable: any;
172
+ try {
173
+ const schemaModule = await import("@/db/schema");
174
+ if ('subscription' in schemaModule && schemaModule.subscription) {
175
+ subscriptionTable = schemaModule.subscription;
176
+ } else {
177
+ console.warn("⚠️ Subscription table not found. Install payments module to enable subscription webhooks.");
178
+ return;
179
+ }
180
+ } catch {
181
+ console.warn("⚠️ Subscription table not found. Install payments module to enable subscription webhooks.");
182
+ return;
183
+ }
184
+
185
+ await db
186
+ .insert(subscriptionTable)
187
+ .values(subscriptionData)
188
+ .onConflictDoUpdate({
189
+ target: subscriptionTable.id,
190
+ set: {
191
+ modifiedAt: subscriptionData.modifiedAt || new Date(),
192
+ amount: subscriptionData.amount,
193
+ currency: subscriptionData.currency,
194
+ recurringInterval: subscriptionData.recurringInterval,
195
+ status: subscriptionData.status,
196
+ currentPeriodStart: subscriptionData.currentPeriodStart,
197
+ currentPeriodEnd: subscriptionData.currentPeriodEnd,
198
+ cancelAtPeriodEnd: subscriptionData.cancelAtPeriodEnd,
199
+ canceledAt: subscriptionData.canceledAt,
200
+ startedAt: subscriptionData.startedAt,
201
+ endsAt: subscriptionData.endsAt,
202
+ endedAt: subscriptionData.endedAt,
203
+ customerId: subscriptionData.customerId,
204
+ productId: subscriptionData.productId,
205
+ discountId: subscriptionData.discountId,
206
+ checkoutId: subscriptionData.checkoutId,
207
+ customerCancellationReason:
208
+ subscriptionData.customerCancellationReason,
209
+ customerCancellationComment:
210
+ subscriptionData.customerCancellationComment,
211
+ metadata: subscriptionData.metadata,
212
+ customFieldData: subscriptionData.customFieldData,
213
+ userId: subscriptionData.userId,
214
+ },
215
+ });
216
+
217
+ console.log("✅ Upserted subscription:", data.id);
218
+
219
+ // Send subscription emails based on event type
220
+ try {
221
+ // Get user email from database
222
+ if (userId) {
223
+ const userRecord = await db
224
+ .select()
225
+ .from(user)
226
+ .where(eq(user.id, userId))
227
+ .limit(1);
228
+
229
+ if (userRecord[0]?.email) {
230
+ const userEmail = userRecord[0].email;
231
+
232
+ // Send confirmation email for new or reactivated subscriptions
233
+ if (
234
+ type === "subscription.created" ||
235
+ type === "subscription.active"
236
+ ) {
237
+ const amount = `$${(data.amount / 100).toFixed(2)}/${data.recurringInterval}`;
238
+ await sendSubscriptionConfirmationEmail(
239
+ userEmail,
240
+ "Premium", // TODO: Get actual plan name from product
241
+ amount
242
+ );
243
+ console.log("📧 Sent subscription confirmation email to:", userEmail);
244
+ }
245
+ }
246
+ }
247
+ } catch (emailError) {
248
+ console.error("📧 Failed to send subscription email:", emailError);
249
+ // Don't throw - email failures shouldn't block webhook processing
250
+ }
251
+ } catch (error) {
252
+ console.error(
253
+ "💥 Error processing subscription webhook:",
254
+ error,
255
+ );
256
+ // Don't throw - let webhook succeed to avoid retries
257
+ }
258
+ }
259
+
260
+ // Handle payment failed events
261
+ if (type === "subscription.payment_failed") {
262
+ try {
263
+ const userId = data.customer?.externalId;
264
+ if (userId) {
265
+ const userRecord = await db
266
+ .select()
267
+ .from(user)
268
+ .where(eq(user.id, userId))
269
+ .limit(1);
270
+
271
+ if (userRecord[0]?.email) {
272
+ const retryDate = new Date();
273
+ retryDate.setDate(retryDate.getDate() + 3); // 3 days from now
274
+
275
+ await sendPaymentFailedEmail(
276
+ userRecord[0].email,
277
+ "Premium", // TODO: Get actual plan name
278
+ retryDate.toLocaleDateString()
279
+ );
280
+ console.log("📧 Sent payment failed email to:", userRecord[0].email);
281
+ }
282
+ }
283
+ } catch (error) {
284
+ console.error("📧 Failed to send payment failed email:", error);
285
+ }
286
+ }
287
+ },
288
+ }),
289
+ ],
290
+ }),
291
+ ]
292
+ : []),
293
+ nextCookies(),
294
+ ],
295
+ });
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Email stub for auth module
3
+ *
4
+ * This provides minimal email functionality when the full email module is not installed.
5
+ * Email sending will be logged to console in development.
6
+ */
7
+
8
+ interface EmailOptions {
9
+ to: string;
10
+ subject: string;
11
+ url?: string;
12
+ user?: { email: string; name?: string };
13
+ }
14
+
15
+ /**
16
+ * Send password reset email (stub)
17
+ */
18
+ export async function sendPasswordResetEmail(
19
+ email: string,
20
+ url: string
21
+ ): Promise<void> {
22
+ if (process.env.NODE_ENV === 'development') {
23
+ console.log(`📧 [Email Stub] Password reset email would be sent to ${email}`);
24
+ console.log(` Reset URL: ${url}`);
25
+ console.log(` Install the email module for actual email sending.`);
26
+ }
27
+ // In production, this would fail silently or you should install the email module
28
+ }
29
+
30
+ /**
31
+ * Send subscription confirmation email (stub)
32
+ */
33
+ export async function sendSubscriptionConfirmationEmail(
34
+ email: string,
35
+ subscriptionDetails: any
36
+ ): Promise<void> {
37
+ if (process.env.NODE_ENV === 'development') {
38
+ console.log(`📧 [Email Stub] Subscription confirmation email would be sent to ${email}`);
39
+ console.log(` Install the email module for actual email sending.`);
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Send payment failed email (stub)
45
+ */
46
+ export async function sendPaymentFailedEmail(
47
+ email: string,
48
+ errorDetails: any
49
+ ): Promise<void> {
50
+ if (process.env.NODE_ENV === 'development') {
51
+ console.log(`📧 [Email Stub] Payment failed email would be sent to ${email}`);
52
+ console.log(` Install the email module for actual email sending.`);
53
+ }
54
+ }
55
+
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Email functions for auth module
3
+ *
4
+ * This is a minimal email implementation that logs to console.
5
+ * Install the full email module (Resend) for production email sending.
6
+ */
7
+
8
+ /**
9
+ * Send password reset email
10
+ */
11
+ export async function sendPasswordResetEmail(
12
+ email: string,
13
+ url: string
14
+ ): Promise<void> {
15
+ if (process.env.NODE_ENV === 'development') {
16
+ console.log(`📧 [Email] Password reset email would be sent to ${email}`);
17
+ console.log(` Reset URL: ${url}`);
18
+ console.log(` Install the email module (Resend) for actual email sending.`);
19
+ }
20
+ // In production without email module, this fails silently
21
+ // Install the email module for production use
22
+ }
23
+
24
+ /**
25
+ * Send subscription confirmation email
26
+ */
27
+ export async function sendSubscriptionConfirmationEmail(
28
+ email: string
29
+ ): Promise<void> {
30
+ if (process.env.NODE_ENV === 'development') {
31
+ console.log(`📧 [Email] Subscription confirmation email would be sent to ${email}`);
32
+ console.log(` Install the email module (Resend) for actual email sending.`);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Send payment failed email
38
+ */
39
+ export async function sendPaymentFailedEmail(
40
+ email: string
41
+ ): Promise<void> {
42
+ if (process.env.NODE_ENV === 'development') {
43
+ console.log(`📧 [Email] Payment failed email would be sent to ${email}`);
44
+ console.log(` Install the email module (Resend) for actual email sending.`);
45
+ }
46
+ }
47
+
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Auth Middleware Patch
3
+ *
4
+ * This file contains the middleware logic for authentication.
5
+ * When the auth module is appended, this will be merged into the base middleware.ts
6
+ */
7
+
8
+ import { NextRequest, NextResponse } from "next/server";
9
+ import { getSessionCookie } from "better-auth/cookies";
10
+
11
+ /**
12
+ * Apply auth middleware logic
13
+ * This function should be called from the base middleware
14
+ */
15
+ export function applyAuthMiddleware(
16
+ request: NextRequest,
17
+ baseResponse: NextResponse
18
+ ): NextResponse | null {
19
+ const sessionCookie = getSessionCookie(request);
20
+ const { pathname } = request.nextUrl;
21
+
22
+ // Don't redirect from sign-in/sign-up pages to avoid redirect loops
23
+ if (["/sign-in", "/sign-up"].includes(pathname)) {
24
+ return null; // Let base middleware handle it
25
+ }
26
+
27
+ // Redirect unauthenticated users from protected routes
28
+ const protectedRoutes = ["/dashboard"]; // Can be customized
29
+ const isProtectedRoute = protectedRoutes.some(route => pathname.startsWith(route));
30
+
31
+ if (isProtectedRoute && !sessionCookie) {
32
+ const returnTo = encodeURIComponent(pathname);
33
+ return NextResponse.redirect(new URL(`/sign-in?returnTo=${returnTo}`, request.url));
34
+ }
35
+
36
+ return null; // Continue with base middleware
37
+ }
38
+
39
+ /**
40
+ * Middleware matcher configuration for auth
41
+ */
42
+ export const matcher = ["/dashboard/:path*", "/sign-in", "/sign-up"];
43
+
@@ -0,0 +1,256 @@
1
+ # Database Module - Integration Guide
2
+
3
+ **Module Version:** 1.0.0
4
+ **Last Updated:** 2025-12-22
5
+ **Standalone:** ✅ Yes (base database setup)
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ This module sets up PostgreSQL database connectivity using Drizzle ORM. It provides the foundation for database operations and can be extended by other modules (auth, payments) that add their own tables.
12
+
13
+ **Key Features:**
14
+ - PostgreSQL connection with Drizzle ORM
15
+ - Drizzle Kit configuration for migrations
16
+ - Base schema structure (extended by other modules)
17
+ - Database scripts (push, generate, migrate, studio)
18
+ - Support for Neon, Supabase, or any Postgres database
19
+
20
+ ---
21
+
22
+ ## Files Added
23
+
24
+ ### Configuration
25
+ - `drizzle.config.ts` - Drizzle Kit configuration
26
+ - `db/drizzle.ts` - Database connection setup
27
+ - `db/schema.ts` - Base schema file (modules will add tables here)
28
+
29
+ ### Migrations
30
+ - `db/migrations/` - Directory for database migrations
31
+
32
+ ---
33
+
34
+ ## Dependencies
35
+
36
+ ### Package Dependencies
37
+ The following packages will be added to `package.json`:
38
+ - `postgres@^3.4.4` - PostgreSQL client
39
+ - `drizzle-orm@^0.41.0` - Drizzle ORM
40
+
41
+ ### Dev Dependencies
42
+ - `drizzle-kit@^0.31.0` - Drizzle migration and studio tools
43
+
44
+ ### Package Scripts
45
+ The following scripts will be added to `package.json`:
46
+ - `db:push` - Push schema changes to database (development)
47
+ - `db:generate` - Generate migration files
48
+ - `db:migrate` - Run migrations
49
+ - `db:studio` - Open Drizzle Studio (database GUI)
50
+
51
+ ---
52
+
53
+ ## Environment Variables
54
+
55
+ Add this to your `.env.local`:
56
+
57
+ ```env
58
+ # Database
59
+ DATABASE_URL="postgresql://user:password@host:port/database"
60
+ ```
61
+
62
+ **Required Variables:**
63
+ - `DATABASE_URL` - PostgreSQL connection string
64
+
65
+ **Where to get it:**
66
+ - **Neon**: [neon.tech](https://neon.tech) - Free tier available
67
+ - **Supabase**: [supabase.com](https://supabase.com) - Free tier available
68
+ - **Local Postgres**: `postgresql://postgres:password@localhost:5432/mydb`
69
+ - **Any Postgres**: Standard PostgreSQL connection string format
70
+
71
+ ---
72
+
73
+ ## Manual Integration Steps
74
+
75
+ If you're appending this module to an existing project, follow these steps:
76
+
77
+ ### Step 1: Install Dependencies
78
+
79
+ ```bash
80
+ npm install
81
+ # or
82
+ pnpm install
83
+ # or
84
+ yarn install
85
+ ```
86
+
87
+ The append command automatically adds missing dependencies to `package.json`, but you need to install them.
88
+
89
+ ### Step 2: Add Environment Variable
90
+
91
+ 1. Copy `.env.example` to `.env.local` (if not exists)
92
+ 2. Add `DATABASE_URL` with your PostgreSQL connection string
93
+ 3. Get a database URL from:
94
+ - [Neon](https://neon.tech) (recommended for development)
95
+ - [Supabase](https://supabase.com)
96
+ - Your own PostgreSQL server
97
+
98
+ ### Step 3: Verify Installation
99
+
100
+ 1. Check that `drizzle.config.ts` exists in project root
101
+ 2. Check that `db/drizzle.ts` and `db/schema.ts` exist
102
+ 3. Verify package.json has database scripts
103
+
104
+ ### Step 4: Initialize Database (Optional)
105
+
106
+ If you want to create an initial migration:
107
+
108
+ ```bash
109
+ npm run db:generate
110
+ npm run db:push
111
+ ```
112
+
113
+ **Note:** The base schema is empty. Tables will be added when you install other modules (auth, payments, etc.).
114
+
115
+ ---
116
+
117
+ ## Usage Examples
118
+
119
+ ### Using the Database Connection
120
+
121
+ ```typescript
122
+ import { db } from "@/db/drizzle";
123
+
124
+ // Example query (after auth module adds user table)
125
+ const users = await db.select().from(user);
126
+ ```
127
+
128
+ ### Generating Migrations
129
+
130
+ After adding tables (via auth or payments modules):
131
+
132
+ ```bash
133
+ # Generate migration files
134
+ npm run db:generate
135
+
136
+ # Review the generated migration in db/migrations/
137
+
138
+ # Push to database (development)
139
+ npm run db:push
140
+
141
+ # Or run migrations (production)
142
+ npm run db:migrate
143
+ ```
144
+
145
+ ### Opening Drizzle Studio
146
+
147
+ ```bash
148
+ npm run db:studio
149
+ ```
150
+
151
+ This opens a web-based database GUI at `http://localhost:4983`
152
+
153
+ ---
154
+
155
+ ## Customization
156
+
157
+ ### Database Connection
158
+
159
+ Edit `db/drizzle.ts` to customize the connection:
160
+
161
+ ```typescript
162
+ // For Supabase compatibility (prepared statements disabled)
163
+ const client = postgres(connectionString, { prepare: false });
164
+
165
+ // For other Postgres providers
166
+ const client = postgres(connectionString);
167
+ ```
168
+
169
+ ### Schema Location
170
+
171
+ The schema file is at `db/schema.ts`. Other modules will add their tables here. You can also add custom tables:
172
+
173
+ ```typescript
174
+ // db/schema.ts
175
+ export const myCustomTable = pgTable("my_custom_table", {
176
+ id: text("id").primaryKey(),
177
+ name: text("name").notNull(),
178
+ // ...
179
+ });
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Integration Points
185
+
186
+ ### Files That May Need Manual Updates
187
+
188
+ **db/schema.ts**:
189
+ - Other modules (auth, payments) will add their tables to this file
190
+ - You can add custom tables here as well
191
+
192
+ **drizzle.config.ts**:
193
+ - Usually doesn't need changes
194
+ - Modify if you need custom migration settings
195
+
196
+ ---
197
+
198
+ ## Troubleshooting
199
+
200
+ ### Common Issues
201
+
202
+ **Issue:** "Missing DATABASE_URL" error
203
+ **Solution:**
204
+ - Ensure `DATABASE_URL` is set in `.env.local`
205
+ - Restart your dev server after adding env vars
206
+ - Check the connection string format
207
+
208
+ **Issue:** "Connection refused" or "ECONNREFUSED"
209
+ **Solution:**
210
+ - Verify your database is running
211
+ - Check the connection string is correct
212
+ - For cloud databases, ensure your IP is whitelisted (if required)
213
+
214
+ **Issue:** "relation does not exist" error
215
+ **Solution:**
216
+ - Run migrations: `npm run db:push` or `npm run db:migrate`
217
+ - Ensure other modules (auth, payments) have been installed and their tables added
218
+
219
+ **Issue:** Supabase connection issues
220
+ **Solution:**
221
+ - The connection is configured with `prepare: false` for Supabase compatibility
222
+ - If issues persist, check Supabase connection pooling settings
223
+
224
+ ---
225
+
226
+ ## Next Steps
227
+
228
+ After installing this module:
229
+
230
+ 1. Set up your database (Neon, Supabase, or local)
231
+ 2. Add `DATABASE_URL` to `.env.local`
232
+ 3. Install Auth module (will add user/session tables)
233
+ 4. Install Payments module (will add subscription table)
234
+ 5. Run `npm run db:push` to create tables
235
+
236
+ ---
237
+
238
+ ## Related Modules
239
+
240
+ This module is required by:
241
+ - **Auth Module** - Adds user, session, account, verification tables
242
+ - **Payments Module** - Adds subscription table
243
+
244
+ This module works well with:
245
+ - **Auth Module** - For user authentication
246
+ - **Payments Module** - For subscription management
247
+
248
+ ---
249
+
250
+ ## Module Status
251
+
252
+ ✅ **Standalone Package** - Can be installed independently
253
+ ✅ **Base Setup** - Provides foundation for other modules
254
+ ✅ **Ready to Use** - Works immediately after append
255
+ ✅ **Well Documented** - Comprehensive integration guide
256
+