abdellah0l-stack 1.0.1
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.
- package/README.md +50 -0
- package/bin/cli.js +218 -0
- package/package.json +30 -0
- package/template/README.md +61 -0
- package/template/components.json +22 -0
- package/template/drizzle.config.ts +13 -0
- package/template/eslint.config.mjs +25 -0
- package/template/next.config.ts +114 -0
- package/template/package.json +62 -0
- package/template/postcss.config.mjs +7 -0
- package/template/public/file.svg +1 -0
- package/template/public/globe.svg +1 -0
- package/template/public/next.svg +1 -0
- package/template/public/vercel.svg +1 -0
- package/template/public/window.svg +1 -0
- package/template/src/app/api/v1/auth/[...all]/route.ts +5 -0
- package/template/src/app/api/v1/trpc/[trpc]/route.ts +13 -0
- package/template/src/app/api/v1/uploadthing/core.ts +50 -0
- package/template/src/app/api/v1/uploadthing/route.ts +11 -0
- package/template/src/app/favicon.ico +0 -0
- package/template/src/app/globals.css +121 -0
- package/template/src/app/layout.tsx +50 -0
- package/template/src/app/page.tsx +58 -0
- package/template/src/components/loading-spinner.tsx +18 -0
- package/template/src/components/navigation.tsx +54 -0
- package/template/src/components/query-provider.tsx +27 -0
- package/template/src/components/ui/badge.tsx +46 -0
- package/template/src/components/ui/button.tsx +58 -0
- package/template/src/components/ui/card.tsx +92 -0
- package/template/src/components/ui/dialog.tsx +143 -0
- package/template/src/components/ui/input.tsx +21 -0
- package/template/src/components/ui/label.tsx +26 -0
- package/template/src/components/ui/select.tsx +185 -0
- package/template/src/components/ui/tabs.tsx +55 -0
- package/template/src/components/ui/textarea.tsx +23 -0
- package/template/src/data/env/client.ts +7 -0
- package/template/src/data/env/server.ts +13 -0
- package/template/src/drizzle/db.ts +6 -0
- package/template/src/drizzle/schema/app-schema.ts +31 -0
- package/template/src/drizzle/schema/auth-schema.ts +55 -0
- package/template/src/drizzle/schema/index.ts +14 -0
- package/template/src/hooks/use-auth.ts +32 -0
- package/template/src/hooks/use-debounce.ts +18 -0
- package/template/src/lib/arcjet.ts +45 -0
- package/template/src/lib/auth-client.ts +7 -0
- package/template/src/lib/auth.ts +50 -0
- package/template/src/lib/use-mobile.ts +29 -0
- package/template/src/lib/utils.ts +6 -0
- package/template/src/middleware.ts +14 -0
- package/template/src/server/index.ts +13 -0
- package/template/src/server/routers/posts.ts +93 -0
- package/template/src/server/routers/users.ts +56 -0
- package/template/src/server/trpc.ts +38 -0
- package/template/src/types/index.ts +10 -0
- package/template/src/utils/trpc.ts +5 -0
- package/template/src/utils/uploadthing.ts +10 -0
- package/template/tsconfig.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# abdellah0l-stack
|
|
2
|
+
|
|
3
|
+
A CLI to scaffold a modern full-stack Next.js project with:
|
|
4
|
+
|
|
5
|
+
- ⚡ **Next.js 16** with App Router
|
|
6
|
+
- 🔷 **TypeScript**
|
|
7
|
+
- 🔗 **tRPC** for type-safe APIs
|
|
8
|
+
- ⚡ **TanStack Query** (React Query) for data fetching (optional)
|
|
9
|
+
- 🗃️ **Drizzle ORM** + PostgreSQL (Neon)
|
|
10
|
+
- 🔐 **Better-Auth** (GitHub, Google, Email)
|
|
11
|
+
- 🛡️ **Arcjet** for rate limiting (optional)
|
|
12
|
+
- 🎨 **Tailwind CSS**
|
|
13
|
+
- 🤖 **Vercel AI SDK** for ai integration (optional)
|
|
14
|
+
- 📁 **UploadThing** for uploading files (optional)
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npx abdellah0l-stack my-app
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or install globally:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g abdellah0l-stack
|
|
26
|
+
abdellah0l-stack my-app
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## After Scaffolding
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
cd my-app
|
|
33
|
+
npm install
|
|
34
|
+
|
|
35
|
+
# Set up your .env file with:
|
|
36
|
+
# - DATABASE_URL
|
|
37
|
+
# - BETTER_AUTH_SECRET
|
|
38
|
+
# - GITHUB_CLIENT_ID & GITHUB_CLIENT_SECRET
|
|
39
|
+
# - GOOGLE_CLIENT_ID & GOOGLE_CLIENT_SECRET
|
|
40
|
+
# - ARCJET_KEY
|
|
41
|
+
# - AI_GATEWAY_API_KEY (if using AI)
|
|
42
|
+
# - UPLOADTHING_TOKEN (if using uploads)
|
|
43
|
+
|
|
44
|
+
npm run db:push
|
|
45
|
+
npm run dev
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs-extra";
|
|
5
|
+
import prompts from "prompts";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
async function main() {
|
|
13
|
+
console.log(chalk.blue.bold("\n🚀 abdellah0l-Stack - Next.js + tRPC + Drizzle + Better-Auth\n"));
|
|
14
|
+
console.log(chalk.gray("Thnx for using abdellah0l-Stack :) Let's build something awesome together.\n"));
|
|
15
|
+
|
|
16
|
+
// Get project name from args or prompt
|
|
17
|
+
let projectName = process.argv[2];
|
|
18
|
+
|
|
19
|
+
if (!projectName) {
|
|
20
|
+
const response = await prompts({
|
|
21
|
+
type: "text",
|
|
22
|
+
name: "projectName",
|
|
23
|
+
message: "Project name:",
|
|
24
|
+
initial: "my-app",
|
|
25
|
+
});
|
|
26
|
+
projectName = response.projectName;
|
|
27
|
+
|
|
28
|
+
if (!projectName) {
|
|
29
|
+
console.log(chalk.red("\n❌ Project name is required!\n"));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Ask for additional options
|
|
35
|
+
const options = await prompts([
|
|
36
|
+
{
|
|
37
|
+
type: "text",
|
|
38
|
+
name: "description",
|
|
39
|
+
message: "Project description:",
|
|
40
|
+
initial: "A modern full-stack application",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: "confirm",
|
|
44
|
+
name: "includeAI",
|
|
45
|
+
message: "Include AI features (Vercel AI SDK)?",
|
|
46
|
+
initial: true,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
type: "confirm",
|
|
50
|
+
name: "includeUploadthing",
|
|
51
|
+
message: "Include file uploads (UploadThing)?",
|
|
52
|
+
initial: true,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
type: "confirm",
|
|
56
|
+
name: "includeRateLimiting",
|
|
57
|
+
message: "Include rate limiting (Arcjet)?",
|
|
58
|
+
initial: true,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
type: "confirm",
|
|
62
|
+
name: "includeTanstack",
|
|
63
|
+
message: "Include TanStack Query (React Query)?",
|
|
64
|
+
initial: true,
|
|
65
|
+
}
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
const targetDir = path.resolve(process.cwd(), projectName);
|
|
69
|
+
const templateDir = path.join(__dirname, "..", "template");
|
|
70
|
+
|
|
71
|
+
// Check if directory exists
|
|
72
|
+
if (fs.existsSync(targetDir)) {
|
|
73
|
+
const { overwrite } = await prompts({
|
|
74
|
+
type: "confirm",
|
|
75
|
+
name: "overwrite",
|
|
76
|
+
message: `Directory ${projectName} already exists. Overwrite?`,
|
|
77
|
+
initial: false,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (!overwrite) {
|
|
81
|
+
console.log(chalk.yellow("\n⚠️ Operation cancelled.\n"));
|
|
82
|
+
process.exit(0);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await fs.remove(targetDir);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(chalk.yellow(`\n📁 Creating project in ${chalk.white(targetDir)}...\n`));
|
|
89
|
+
|
|
90
|
+
// Copy template
|
|
91
|
+
await fs.copy(templateDir, targetDir);
|
|
92
|
+
|
|
93
|
+
// Update package.json with project name
|
|
94
|
+
const pkgPath = path.join(targetDir, "package.json");
|
|
95
|
+
if (fs.existsSync(pkgPath)) {
|
|
96
|
+
const pkg = await fs.readJson(pkgPath);
|
|
97
|
+
pkg.name = projectName;
|
|
98
|
+
pkg.description = options.description || "";
|
|
99
|
+
|
|
100
|
+
// Remove AI packages if not needed
|
|
101
|
+
if (!options.includeAI && pkg.dependencies) {
|
|
102
|
+
delete pkg.dependencies["@ai-sdk/anthropic"];
|
|
103
|
+
delete pkg.dependencies["ai"];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Remove uploadthing if not needed
|
|
107
|
+
if (!options.includeUploadthing && pkg.dependencies) {
|
|
108
|
+
delete pkg.dependencies["uploadthing"];
|
|
109
|
+
delete pkg.dependencies["@uploadthing/react"];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Remove arcjet if not needed
|
|
113
|
+
if (!options.includeRateLimiting && pkg.dependencies) {
|
|
114
|
+
delete pkg.dependencies["@arcjet/next"];
|
|
115
|
+
delete pkg.dependencies["arcjet"];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Remove TanStack Query if not needed
|
|
119
|
+
if (!options.includeTanstack && pkg.dependencies) {
|
|
120
|
+
delete pkg.dependencies["@tanstack/react-query"];
|
|
121
|
+
delete pkg.dependencies["@tanstack/react-query-devtools"];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Create .env from .env.example
|
|
128
|
+
const envExample = path.join(targetDir, ".env.example");
|
|
129
|
+
const envFile = path.join(targetDir, ".env");
|
|
130
|
+
if (fs.existsSync(envExample)) {
|
|
131
|
+
await fs.copy(envExample, envFile);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Remove AI files if not needed
|
|
135
|
+
if (!options.includeAI) {
|
|
136
|
+
const aiPaths = [
|
|
137
|
+
path.join(targetDir, "src", "app", "api", "v1", "ai"),
|
|
138
|
+
path.join(targetDir, "src", "modules", "ai"),
|
|
139
|
+
path.join(targetDir, "src", "lib", "ai-schemas.ts"),
|
|
140
|
+
path.join(targetDir, "src", "components", "ai-error-boundary.tsx"),
|
|
141
|
+
path.join(targetDir, "src", "components", "ai-loading-spinner.tsx"),
|
|
142
|
+
path.join(targetDir, "src", "components", "ai-recommendations.tsx"),
|
|
143
|
+
path.join(targetDir, "src", "components", "ai-summary.tsx"),
|
|
144
|
+
path.join(targetDir, "src", "components", "exploreAi.tsx"),
|
|
145
|
+
];
|
|
146
|
+
for (const p of aiPaths) {
|
|
147
|
+
await fs.remove(p).catch(() => {});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Remove uploadthing files if not needed
|
|
152
|
+
if (!options.includeUploadthing) {
|
|
153
|
+
const uploadPaths = [
|
|
154
|
+
path.join(targetDir, "src", "app", "api", "v1", "uploadthing"),
|
|
155
|
+
path.join(targetDir, "src", "utils", "uploadthing.ts"),
|
|
156
|
+
];
|
|
157
|
+
for (const p of uploadPaths) {
|
|
158
|
+
await fs.remove(p).catch(() => {});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Remove arcjet files if not needed
|
|
163
|
+
if (!options.includeRateLimiting) {
|
|
164
|
+
const arcjetPaths = [
|
|
165
|
+
path.join(targetDir, "src", "lib", "arcjet.ts"),
|
|
166
|
+
];
|
|
167
|
+
for (const p of arcjetPaths) {
|
|
168
|
+
await fs.remove(p).catch(() => {});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Remove TanStack Query files if not needed
|
|
173
|
+
if (!options.includeTanstack) {
|
|
174
|
+
const tanstackPaths = [
|
|
175
|
+
path.join(targetDir, "src", "components", "query-provider.tsx"),
|
|
176
|
+
];
|
|
177
|
+
for (const p of tanstackPaths) {
|
|
178
|
+
await fs.remove(p).catch(() => {});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Success message
|
|
183
|
+
console.log(chalk.green.bold("\n✅ Project created successfully!\n"));
|
|
184
|
+
|
|
185
|
+
console.log(chalk.white("📦 Stack included:"));
|
|
186
|
+
console.log(chalk.gray(" • Next.js 16 with App Router"));
|
|
187
|
+
console.log(chalk.gray(" • TypeScript"));
|
|
188
|
+
console.log(chalk.gray(" • tRPC"));
|
|
189
|
+
if (options.includeTanstack) console.log(chalk.gray(" • TanStack Query (React Query)"));
|
|
190
|
+
console.log(chalk.gray(" • Drizzle ORM + PostgreSQL"));
|
|
191
|
+
console.log(chalk.gray(" • Better-Auth (GitHub, Google, Email)"));
|
|
192
|
+
if (options.includeRateLimiting) console.log(chalk.gray(" • Arcjet (Rate Limiting)"));
|
|
193
|
+
console.log(chalk.gray(" • Tailwind CSS"));
|
|
194
|
+
if (options.includeAI) console.log(chalk.gray(" • Vercel AI SDK (Claude)"));
|
|
195
|
+
if (options.includeUploadthing) console.log(chalk.gray(" • UploadThing (File Uploads)"));
|
|
196
|
+
|
|
197
|
+
console.log(chalk.white("\n📝 Next steps:\n"));
|
|
198
|
+
console.log(chalk.cyan(` cd ${projectName}`));
|
|
199
|
+
console.log(chalk.cyan(" npm install"));
|
|
200
|
+
console.log(chalk.gray(" # Update .env with your credentials"));
|
|
201
|
+
console.log(chalk.cyan(" npm run db:push"));
|
|
202
|
+
console.log(chalk.cyan(" npm run dev\n"));
|
|
203
|
+
|
|
204
|
+
console.log(chalk.white("🔐 Required environment variables:"));
|
|
205
|
+
console.log(chalk.gray(" • DATABASE_URL (Neon/PostgreSQL)"));
|
|
206
|
+
console.log(chalk.gray(" • BETTER_AUTH_SECRET"));
|
|
207
|
+
console.log(chalk.gray(" • GITHUB_CLIENT_ID & GITHUB_CLIENT_SECRET"));
|
|
208
|
+
console.log(chalk.gray(" • GOOGLE_CLIENT_ID & GOOGLE_CLIENT_SECRET"));
|
|
209
|
+
if (options.includeRateLimiting) console.log(chalk.gray(" • ARCJET_KEY"));
|
|
210
|
+
if (options.includeAI) console.log(chalk.gray(" • AI_GATEWAY_API_KEY"));
|
|
211
|
+
if (options.includeUploadthing) console.log(chalk.gray(" • UPLOADTHING_TOKEN"));
|
|
212
|
+
console.log("");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
main().catch((err) => {
|
|
216
|
+
console.error(chalk.red("\n❌ Error:"), err.message);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "abdellah0l-stack",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Scaffold a Next.js project with TypeScript, tRPC, Drizzle, Better-Auth, Arcjet, and Vercel AI SDK",
|
|
5
|
+
"bin": {
|
|
6
|
+
"abdellah0l-stack": "./bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"template"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"nextjs",
|
|
14
|
+
"trpc",
|
|
15
|
+
"drizzle",
|
|
16
|
+
"better-auth",
|
|
17
|
+
"arcjet",
|
|
18
|
+
"vercel-ai",
|
|
19
|
+
"boilerplate",
|
|
20
|
+
"cli"
|
|
21
|
+
],
|
|
22
|
+
"author": "abdellah0l",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"chalk": "^5.3.0",
|
|
26
|
+
"fs-extra": "^11.2.0",
|
|
27
|
+
"prompts": "^2.4.2"
|
|
28
|
+
},
|
|
29
|
+
"type": "module"
|
|
30
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# My App
|
|
2
|
+
|
|
3
|
+
A modern full-stack application built with:
|
|
4
|
+
|
|
5
|
+
- **Next.js 16** - React framework with App Router
|
|
6
|
+
- **TypeScript** - Type safety
|
|
7
|
+
- **tRPC** - End-to-end type-safe APIs
|
|
8
|
+
- **Drizzle ORM** - Type-safe database queries
|
|
9
|
+
- **PostgreSQL** - Database (Neon recommended)
|
|
10
|
+
- **Better-Auth** - Authentication (GitHub, Google, Email)
|
|
11
|
+
- **Arcjet** - Rate limiting and security
|
|
12
|
+
- **Tailwind CSS** - Styling
|
|
13
|
+
- **Vercel AI SDK** - AI features (optional)
|
|
14
|
+
- **UploadThing** - File uploads (optional)
|
|
15
|
+
|
|
16
|
+
## Getting Started
|
|
17
|
+
|
|
18
|
+
1. Install dependencies:
|
|
19
|
+
```bash
|
|
20
|
+
npm install
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
2. Set up your environment variables (copy from `.env.example`):
|
|
24
|
+
```bash
|
|
25
|
+
cp .env.example .env
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
3. Push the database schema:
|
|
29
|
+
```bash
|
|
30
|
+
npm run db:push
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
4. Start the development server:
|
|
34
|
+
```bash
|
|
35
|
+
npm run dev
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Environment Variables
|
|
39
|
+
|
|
40
|
+
| Variable | Description |
|
|
41
|
+
|----------|-------------|
|
|
42
|
+
| `DATABASE_URL` | PostgreSQL connection string |
|
|
43
|
+
| `BETTER_AUTH_SECRET` | Secret for auth (generate with `openssl rand -base64 32`) |
|
|
44
|
+
| `BETTER_AUTH_URL` | Your app URL |
|
|
45
|
+
| `GITHUB_CLIENT_ID` | GitHub OAuth app client ID |
|
|
46
|
+
| `GITHUB_CLIENT_SECRET` | GitHub OAuth app client secret |
|
|
47
|
+
| `GOOGLE_CLIENT_ID` | Google OAuth client ID |
|
|
48
|
+
| `GOOGLE_CLIENT_SECRET` | Google OAuth client secret |
|
|
49
|
+
| `ARCJET_KEY` | Arcjet API key |
|
|
50
|
+
| `AI_GATEWAY_API_KEY` | Vercel-ai API key (for AI features) |
|
|
51
|
+
| `UPLOADTHING_TOKEN` | UploadThing token (for file uploads) |
|
|
52
|
+
|
|
53
|
+
## Scripts
|
|
54
|
+
|
|
55
|
+
- `npm run dev` - Start development server
|
|
56
|
+
- `npm run build` - Build for production
|
|
57
|
+
- `npm run start` - Start production server
|
|
58
|
+
- `npm run db:push` - Push schema to database
|
|
59
|
+
- `npm run db:generate` - Generate migrations
|
|
60
|
+
- `npm run db:migrate` - Run migrations
|
|
61
|
+
- `npm run db:studio` - Open Drizzle Studio
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "new-york",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/app/globals.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"iconLibrary": "lucide",
|
|
14
|
+
"aliases": {
|
|
15
|
+
"components": "@/components",
|
|
16
|
+
"utils": "@/lib/utils",
|
|
17
|
+
"ui": "@/components/ui",
|
|
18
|
+
"lib": "@/lib",
|
|
19
|
+
"hooks": "@/hooks"
|
|
20
|
+
},
|
|
21
|
+
"registries": {}
|
|
22
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig } from "drizzle-kit";
|
|
2
|
+
import { env } from "@/data/env/server";
|
|
3
|
+
|
|
4
|
+
// Drizzle configuration for database migrations
|
|
5
|
+
// install the extension "Drizzle ORM" in VSCode for a better experience (schema visulalization, autocompletion, etc)
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
schema: "./src/drizzle/schema/index.ts",
|
|
8
|
+
out: "./src/drizzle/migrations",
|
|
9
|
+
dialect: "postgresql",
|
|
10
|
+
dbCredentials: {
|
|
11
|
+
url: env.DATABASE_URL,
|
|
12
|
+
},
|
|
13
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { dirname } from "path";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import { FlatCompat } from "@eslint/eslintrc";
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
|
|
8
|
+
const compat = new FlatCompat({
|
|
9
|
+
baseDirectory: __dirname,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const eslintConfig = [
|
|
13
|
+
...compat.extends("next/core-web-vitals", "next/typescript"),
|
|
14
|
+
{
|
|
15
|
+
ignores: [
|
|
16
|
+
"node_modules/**",
|
|
17
|
+
".next/**",
|
|
18
|
+
"out/**",
|
|
19
|
+
"build/**",
|
|
20
|
+
"next-env.d.ts",
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export default eslintConfig;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { NextConfig } from "next";
|
|
2
|
+
|
|
3
|
+
const nextConfig: NextConfig = {
|
|
4
|
+
// this enables the experimental React compiler for better performance and smaller bundle sizes
|
|
5
|
+
reactCompiler: true,
|
|
6
|
+
images: {
|
|
7
|
+
remotePatterns: [
|
|
8
|
+
{
|
|
9
|
+
protocol: 'https',
|
|
10
|
+
hostname: 'picsum.photos',
|
|
11
|
+
port: '',
|
|
12
|
+
pathname: '/**',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
protocol: 'https',
|
|
16
|
+
hostname: 'images.unsplash.com',
|
|
17
|
+
port: '',
|
|
18
|
+
pathname: '/**',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
protocol: 'https',
|
|
22
|
+
hostname: 'via.placeholder.com',
|
|
23
|
+
port: '',
|
|
24
|
+
pathname: '/**',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
protocol: 'https',
|
|
28
|
+
hostname: 'placehold.co',
|
|
29
|
+
port: '',
|
|
30
|
+
pathname: '/**',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
protocol: 'https',
|
|
34
|
+
hostname: 'encrypted-tbn0.gstatic.com',
|
|
35
|
+
port: '',
|
|
36
|
+
pathname: '/**',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
protocol: 'https',
|
|
40
|
+
hostname: 'encrypted-tbn1.gstatic.com',
|
|
41
|
+
port: '',
|
|
42
|
+
pathname: '/**',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
protocol: 'https',
|
|
46
|
+
hostname: 'encrypted-tbn2.gstatic.com',
|
|
47
|
+
port: '',
|
|
48
|
+
pathname: '/**',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
protocol: 'https',
|
|
52
|
+
hostname: 'encrypted-tbn3.gstatic.com',
|
|
53
|
+
port: '',
|
|
54
|
+
pathname: '/**',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
protocol: 'https',
|
|
58
|
+
hostname: 'i.pravatar.cc',
|
|
59
|
+
port: '',
|
|
60
|
+
pathname: '/**',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
protocol: 'https',
|
|
64
|
+
hostname: 'utfs.io',
|
|
65
|
+
port: '',
|
|
66
|
+
pathname: '/a/**',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
protocol: 'https',
|
|
70
|
+
hostname: 'hwbus3icbz.ufs.sh',
|
|
71
|
+
port: '',
|
|
72
|
+
pathname: '/f/**',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
protocol: 'https',
|
|
76
|
+
hostname: 'avatars.githubusercontent.com',
|
|
77
|
+
port: '',
|
|
78
|
+
pathname: '/**',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
protocol: 'https',
|
|
82
|
+
hostname: 'lh3.googleusercontent.com',
|
|
83
|
+
port: '',
|
|
84
|
+
pathname: '/**',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
protocol: 'https',
|
|
88
|
+
hostname: 'm.media-amazon.com',
|
|
89
|
+
port: '',
|
|
90
|
+
pathname: '/**',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
protocol: 'https',
|
|
94
|
+
hostname: 'imgv2-1-f.scribdassets.com',
|
|
95
|
+
port: '',
|
|
96
|
+
pathname: '/**',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
protocol: 'https',
|
|
100
|
+
hostname: 'cdn2.penguin.com.au',
|
|
101
|
+
port: '',
|
|
102
|
+
pathname: '/**',
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
protocol: 'https',
|
|
106
|
+
hostname: 'glassboxx.com',
|
|
107
|
+
port: '',
|
|
108
|
+
pathname: '/**',
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export default nextConfig;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "next dev --turbopack",
|
|
8
|
+
"build": "next build",
|
|
9
|
+
"start": "next start",
|
|
10
|
+
"db:generate": "drizzle-kit generate",
|
|
11
|
+
"db:migrate": "drizzle-kit migrate",
|
|
12
|
+
"db:studio": "drizzle-kit studio",
|
|
13
|
+
"db:push": "drizzle-kit push",
|
|
14
|
+
"lint": "eslint"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@ai-sdk/anthropic": "^3.0.9",
|
|
18
|
+
"@arcjet/next": "^1.0.0-beta.13",
|
|
19
|
+
"@radix-ui/react-avatar": "^1.1.10",
|
|
20
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
21
|
+
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
22
|
+
"@radix-ui/react-label": "^2.1.7",
|
|
23
|
+
"@radix-ui/react-select": "^2.2.6",
|
|
24
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
25
|
+
"@radix-ui/react-tabs": "^1.1.13",
|
|
26
|
+
"@t3-oss/env-nextjs": "^0.13.8",
|
|
27
|
+
"@tanstack/react-query": "^5.90.16",
|
|
28
|
+
"@trpc/client": "^11.8.1",
|
|
29
|
+
"@trpc/react-query": "^11.8.1",
|
|
30
|
+
"@trpc/server": "^11.8.1",
|
|
31
|
+
"@uploadthing/react": "^7.3.3",
|
|
32
|
+
"ai": "^6.0.18",
|
|
33
|
+
"better-auth": "^1.3.27",
|
|
34
|
+
"class-variance-authority": "^0.7.1",
|
|
35
|
+
"clsx": "^2.1.1",
|
|
36
|
+
"drizzle-orm": "^0.45.1",
|
|
37
|
+
"gsap": "^3.13.0",
|
|
38
|
+
"lucide-react": "^0.545.0",
|
|
39
|
+
"next": "^16.0.0",
|
|
40
|
+
"pg": "^8.16.3",
|
|
41
|
+
"react": "^19.0.0",
|
|
42
|
+
"react-dom": "^19.0.0",
|
|
43
|
+
"react-hot-toast": "^2.6.0",
|
|
44
|
+
"tailwind-merge": "^3.3.1",
|
|
45
|
+
"uploadthing": "^7.7.4",
|
|
46
|
+
"zod": "^3.25.76"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@eslint/eslintrc": "^3",
|
|
50
|
+
"@tailwindcss/postcss": "^4",
|
|
51
|
+
"@types/node": "^20",
|
|
52
|
+
"@types/pg": "^8.15.5",
|
|
53
|
+
"@types/react": "^19",
|
|
54
|
+
"@types/react-dom": "^19",
|
|
55
|
+
"drizzle-kit": "^0.31.8",
|
|
56
|
+
"eslint": "^9",
|
|
57
|
+
"eslint-config-next": "^16.0.0",
|
|
58
|
+
"tailwindcss": "^4",
|
|
59
|
+
"tsx": "^4.20.6",
|
|
60
|
+
"typescript": "^5"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { fetchRequestHandler } from '@trpc/server/adapters/fetch';
|
|
2
|
+
import { appRouter } from '@/server';
|
|
3
|
+
import { createTRPCContext } from '@/server/trpc';
|
|
4
|
+
|
|
5
|
+
const handler = (req: Request) =>
|
|
6
|
+
fetchRequestHandler({
|
|
7
|
+
endpoint: '/api/v1/trpc',
|
|
8
|
+
req,
|
|
9
|
+
router: appRouter,
|
|
10
|
+
createContext: () => createTRPCContext({ req }),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export { handler as GET, handler as POST };
|