Update README.md
This commit is contained in:
445
AGENTS.md
445
AGENTS.md
@@ -1,5 +1,34 @@
|
|||||||
# AGENTS.md - Convex Turbo Monorepo
|
# AGENTS.md - Convex Turbo Monorepo
|
||||||
|
|
||||||
|
## For AI Agents Working in This Repo
|
||||||
|
|
||||||
|
**Testing & Quality Checks:**
|
||||||
|
|
||||||
|
- **ALWAYS prefer `bun typecheck`** over `bun build` when checking your work
|
||||||
|
- Typecheck is faster and sufficient for validation
|
||||||
|
- Build is for production deployment only and takes much longer
|
||||||
|
|
||||||
|
**Scope Focus:**
|
||||||
|
|
||||||
|
- **Ignore `apps/expo/` directory** unless explicitly asked to work on it
|
||||||
|
- Focus on Next.js app and backend for now
|
||||||
|
- Expo setup is incomplete and will be addressed later
|
||||||
|
|
||||||
|
**Backend Package Note:**
|
||||||
|
|
||||||
|
- `@gib/backend` has no root `tsconfig.json` (only `convex/tsconfig.json`)
|
||||||
|
- This is intentional - follows Convex's recommended structure
|
||||||
|
- Running `bun typecheck` on backend will show TypeScript help output
|
||||||
|
- This is expected behavior, not an error
|
||||||
|
|
||||||
|
**Self-Hosted Setup:**
|
||||||
|
|
||||||
|
- This project uses self-hosted Convex (not Convex cloud)
|
||||||
|
- Database runs on separate server via Docker
|
||||||
|
- See `docker/` directory for deployment configuration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Quick Reference
|
## Quick Reference
|
||||||
|
|
||||||
### Build/Lint/Test Commands
|
### Build/Lint/Test Commands
|
||||||
@@ -12,14 +41,14 @@ bun dev:expo # Run Expo + Convex backend only
|
|||||||
bun dev:backend # Run Convex backend only
|
bun dev:backend # Run Convex backend only
|
||||||
bun dev:expo:tunnel # Expo with tunnel for physical device
|
bun dev:expo:tunnel # Expo with tunnel for physical device
|
||||||
|
|
||||||
# Quality
|
# Quality (PREFER typecheck over build for testing)
|
||||||
|
bun typecheck # TypeScript checking (PREFERRED for validation)
|
||||||
bun lint # Lint all packages
|
bun lint # Lint all packages
|
||||||
bun lint:fix # Lint and auto-fix
|
bun lint:fix # Lint and auto-fix
|
||||||
bun format # Check formatting
|
bun format # Check formatting
|
||||||
bun format:fix # Fix formatting
|
bun format:fix # Fix formatting
|
||||||
bun typecheck # TypeScript type checking
|
|
||||||
|
|
||||||
# Build
|
# Build (production only, slower)
|
||||||
bun build # Build all packages
|
bun build # Build all packages
|
||||||
|
|
||||||
# Single Package Commands (use Turborepo filters)
|
# Single Package Commands (use Turborepo filters)
|
||||||
@@ -32,16 +61,20 @@ bun clean # Clean all node_modules (git clean)
|
|||||||
bun clean:ws # Clean workspace caches
|
bun clean:ws # Clean workspace caches
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
convex-monorepo/
|
convex-monorepo/
|
||||||
├── apps/
|
├── apps/
|
||||||
│ ├── next/ # Next.js 16 web app (@gib/next)
|
│ ├── next/ # Next.js 16.0.0 web app (@gib/next)
|
||||||
│ └── expo/ # Expo 54 mobile app (@gib/expo)
|
│ └── expo/ # Expo 54 mobile app (@gib/expo) [IGNORE FOR NOW]
|
||||||
├── packages/
|
├── packages/
|
||||||
│ ├── backend/ # Convex backend (@gib/backend)
|
│ ├── backend/ # Convex backend (@gib/backend)
|
||||||
│ │ └── convex/ # Convex functions, schema, auth
|
│ │ ├── convex/ # Convex functions, schema, auth (synced to cloud)
|
||||||
|
│ │ ├── scripts/ # Build utilities (e.g., key generation)
|
||||||
|
│ │ └── types/ # Shared type definitions
|
||||||
│ └── ui/ # Shared shadcn/ui components (@gib/ui)
|
│ └── ui/ # Shared shadcn/ui components (@gib/ui)
|
||||||
├── tools/
|
├── tools/
|
||||||
│ ├── eslint/ # @gib/eslint-config
|
│ ├── eslint/ # @gib/eslint-config
|
||||||
@@ -52,6 +85,10 @@ convex-monorepo/
|
|||||||
└── .env # Central environment variables
|
└── .env # Central environment variables
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Note:** Only `packages/backend/convex/` is synced to Convex cloud. Other directories (`scripts/`, `types/`) are kept separate to avoid sync issues.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Dependency Management
|
## Dependency Management
|
||||||
|
|
||||||
### Catalogs (Single Source of Truth)
|
### Catalogs (Single Source of Truth)
|
||||||
@@ -62,11 +99,12 @@ All shared dependencies are defined in root `package.json` catalogs:
|
|||||||
"catalog": {
|
"catalog": {
|
||||||
"prettier": "^3.6.2",
|
"prettier": "^3.6.2",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"eslint": "^9.38.0"
|
"eslint": "^9.38.0",
|
||||||
|
"zod": "^4.1.12"
|
||||||
},
|
},
|
||||||
"catalogs": {
|
"catalogs": {
|
||||||
"convex": { "convex": "^1.28.0", "@convex-dev/auth": "^0.0.81" },
|
"convex": { "convex": "^1.28.0", "@convex-dev/auth": "^0.0.81" },
|
||||||
"react19": { "react": "^19.1.4", "react-dom": "19.1.4" }
|
"react19": { "react": "19.1.4", "react-dom": "19.1.4" }
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -76,7 +114,8 @@ All shared dependencies are defined in root `package.json` catalogs:
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"convex": "catalog:convex",
|
"convex": "catalog:convex",
|
||||||
"react": "catalog:react19",
|
"react": "catalog:react19",
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:",
|
||||||
|
"zod": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@gib/eslint-config": "workspace:*",
|
"@gib/eslint-config": "workspace:*",
|
||||||
@@ -86,7 +125,7 @@ All shared dependencies are defined in root `package.json` catalogs:
|
|||||||
|
|
||||||
### Updating Dependencies
|
### Updating Dependencies
|
||||||
|
|
||||||
**IMPORTANT:** Do NOT use `bun update` directly - it may replace catalog: with versions.
|
**IMPORTANT:** Do NOT use `bun update` directly - it may replace `catalog:` with hard-coded versions.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Correct workflow:
|
# Correct workflow:
|
||||||
@@ -95,22 +134,52 @@ All shared dependencies are defined in root `package.json` catalogs:
|
|||||||
3. Verify with: bun lint:ws (runs sherif)
|
3. Verify with: bun lint:ws (runs sherif)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Code Style Guidelines
|
## Code Style Guidelines
|
||||||
|
|
||||||
### Imports (via @gib/prettier-config)
|
### Imports (via @gib/prettier-config)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Order: Types → React → Next/Expo → Third-party → @gib/* → Local
|
// Order: Types → React → Next/Expo → Third-party → @gib/* → Local
|
||||||
|
import type { Metadata } from 'next';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { api } from '@/convex/_generated/api';
|
import { useMutation, useQuery } from 'convex/react';
|
||||||
import { ConvexError } from 'convex/values';
|
import { ConvexError } from 'convex/values';
|
||||||
|
|
||||||
|
import type { Id } from '@gib/backend/convex/_generated/dataModel.js';
|
||||||
|
import { api } from '@gib/backend/convex/_generated/api.js'; // Note .js!
|
||||||
|
|
||||||
import { cn } from '@gib/ui';
|
import { cn } from '@gib/ui';
|
||||||
|
import { Button } from '@gib/ui/button';
|
||||||
|
|
||||||
import type { User } from './types';
|
import type { User } from './types';
|
||||||
|
import { helper } from '../utils';
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Convex Imports (.js Extensions)
|
||||||
|
|
||||||
|
**IMPORTANT:** Convex generated files require `.js` extensions due to ESM compatibility:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Correct - with .js extension
|
||||||
|
import type { Id } from '@gib/backend/convex/_generated/dataModel.js';
|
||||||
|
// ❌ Wrong - will fail to import
|
||||||
|
import { api } from '@gib/backend/convex/_generated/api';
|
||||||
|
import { api } from '@gib/backend/convex/_generated/api.js';
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why?** The project uses `"type": "module"` in package.json, requiring explicit file extensions for local module imports per ESM specification.
|
||||||
|
|
||||||
|
**Files using this pattern:**
|
||||||
|
|
||||||
|
- `apps/next/src/app/(auth)/profile/page.tsx:12`
|
||||||
|
- `apps/next/src/components/layout/auth/profile/avatar-upload.tsx:10-11`
|
||||||
|
- `apps/next/src/components/layout/auth/profile/header.tsx:6`
|
||||||
|
- `apps/next/src/components/layout/auth/profile/reset-password.tsx:11`
|
||||||
|
- `apps/next/src/components/layout/auth/profile/user-info.tsx:11`
|
||||||
|
|
||||||
### TypeScript
|
### TypeScript
|
||||||
|
|
||||||
- Strict mode enabled (`noUncheckedIndexedAccess: true`)
|
- Strict mode enabled (`noUncheckedIndexedAccess: true`)
|
||||||
@@ -130,11 +199,16 @@ import type { User } from './types';
|
|||||||
```typescript
|
```typescript
|
||||||
// Convex functions - use ConvexError
|
// Convex functions - use ConvexError
|
||||||
import { ConvexError } from 'convex/values';
|
import { ConvexError } from 'convex/values';
|
||||||
|
|
||||||
throw new ConvexError('User not found.');
|
throw new ConvexError('User not found.');
|
||||||
|
|
||||||
// Client-side - handle gracefully
|
// Client-side - handle gracefully
|
||||||
try { ... } catch (e) {
|
try {
|
||||||
if (e instanceof ConvexError) { /* handle */ }
|
await updateUser({ name });
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof ConvexError) {
|
||||||
|
toast.error(e.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -145,6 +219,8 @@ try { ... } catch (e) {
|
|||||||
- Tailwind classes sorted via prettier-plugin-tailwindcss
|
- Tailwind classes sorted via prettier-plugin-tailwindcss
|
||||||
- Use `cn()` for conditional classes: `cn('base', condition && 'active')`
|
- Use `cn()` for conditional classes: `cn('base', condition && 'active')`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
### Central .env (root)
|
### Central .env (root)
|
||||||
@@ -161,13 +237,24 @@ All env vars in `/.env`. Apps load via `with-env` script:
|
|||||||
### Required Variables
|
### Required Variables
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Convex
|
# Convex Backend (Self-Hosted)
|
||||||
CONVEX_SELF_HOSTED_URL=https://api.convex.example.com
|
CONVEX_SELF_HOSTED_URL=https://api.convex.example.com
|
||||||
CONVEX_SELF_HOSTED_ADMIN_KEY=<generated>
|
CONVEX_SELF_HOSTED_ADMIN_KEY=<generated via generateKeys.mjs>
|
||||||
CONVEX_SITE_URL=https://convex.example.com
|
CONVEX_SITE_URL=https://convex.example.com
|
||||||
NEXT_PUBLIC_CONVEX_URL=https://api.convex.example.com
|
|
||||||
|
|
||||||
# Auth (sync to Convex deployment)
|
# Next.js Public
|
||||||
|
NEXT_PUBLIC_CONVEX_URL=https://api.convex.example.com
|
||||||
|
NEXT_PUBLIC_SITE_URL=https://example.com
|
||||||
|
NEXT_PUBLIC_PLAUSIBLE_URL=https://plausible.example.com
|
||||||
|
NEXT_PUBLIC_SENTRY_DSN=
|
||||||
|
NEXT_PUBLIC_SENTRY_URL=
|
||||||
|
NEXT_PUBLIC_SENTRY_ORG=
|
||||||
|
NEXT_PUBLIC_SENTRY_PROJECT_NAME=
|
||||||
|
|
||||||
|
# Server-side
|
||||||
|
SENTRY_AUTH_TOKEN=
|
||||||
|
|
||||||
|
# Auth (sync to Convex deployment - see below)
|
||||||
AUTH_AUTHENTIK_ID=
|
AUTH_AUTHENTIK_ID=
|
||||||
AUTH_AUTHENTIK_SECRET=
|
AUTH_AUTHENTIK_SECRET=
|
||||||
AUTH_AUTHENTIK_ISSUER=
|
AUTH_AUTHENTIK_ISSUER=
|
||||||
@@ -176,60 +263,161 @@ USESEND_API_KEY=
|
|||||||
|
|
||||||
### Syncing to Convex Deployment
|
### Syncing to Convex Deployment
|
||||||
|
|
||||||
|
Environment variables needed by backend functions must be synced to Convex:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Upload env vars to self-hosted Convex
|
# Via CLI (from packages/backend/)
|
||||||
npx convex env set AUTH_AUTHENTIK_ID "value"
|
bun with-env npx convex env set AUTH_AUTHENTIK_ID "value"
|
||||||
npx convex env set AUTH_AUTHENTIK_SECRET "value"
|
bun with-env npx convex env set AUTH_AUTHENTIK_SECRET "value"
|
||||||
npx convex env set AUTH_AUTHENTIK_ISSUER "value"
|
bun with-env npx convex env set AUTH_AUTHENTIK_ISSUER "value"
|
||||||
npx convex env set USESEND_API_KEY "value"
|
bun with-env npx convex env set USESEND_API_KEY "value"
|
||||||
npx convex env set CONVEX_SITE_URL "https://convex.example.com"
|
bun with-env npx convex env set CONVEX_SITE_URL "https://convex.example.com"
|
||||||
|
|
||||||
|
# Or via Convex Dashboard at your self-hosted URL
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Convex Backend Patterns
|
## Convex Backend Patterns
|
||||||
|
|
||||||
|
### Package Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/backend/
|
||||||
|
├── convex/ # Convex functions (synced to cloud)
|
||||||
|
│ ├── _generated/ # Auto-generated API/types
|
||||||
|
│ ├── custom/ # Custom auth providers
|
||||||
|
│ │ └── auth/
|
||||||
|
│ │ └── providers/
|
||||||
|
│ │ ├── password.ts
|
||||||
|
│ │ └── usesend.ts
|
||||||
|
│ ├── auth.config.ts # Auth CORS configuration
|
||||||
|
│ ├── auth.ts # Auth setup + user queries/mutations
|
||||||
|
│ ├── crons.ts # Scheduled jobs
|
||||||
|
│ ├── files.ts # File upload/storage utilities
|
||||||
|
│ ├── http.ts # HTTP routes for auth
|
||||||
|
│ ├── schema.ts # Database schema
|
||||||
|
│ ├── utils.ts # Helper functions
|
||||||
|
│ └── tsconfig.json # Convex-specific TypeScript config
|
||||||
|
├── scripts/ # Build scripts (outside convex/)
|
||||||
|
│ └── generateKeys.mjs # JWT key generation for auth
|
||||||
|
└── types/ # Shared type exports (outside convex/)
|
||||||
|
├── auth.ts # Password validation constants
|
||||||
|
└── index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Only `convex/` directory is synced to Convex cloud. Other directories (`scripts/`, `types/`) are kept separate to avoid sync issues with Convex deployment.
|
||||||
|
|
||||||
### Schema (`packages/backend/convex/schema.ts`)
|
### Schema (`packages/backend/convex/schema.ts`)
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { defineSchema, defineTable } from 'convex/server';
|
|
||||||
import { authTables } from '@convex-dev/auth/server';
|
import { authTables } from '@convex-dev/auth/server';
|
||||||
|
import { defineSchema, defineTable } from 'convex/server';
|
||||||
|
import { v } from 'convex/values';
|
||||||
|
|
||||||
export default defineSchema({
|
export default defineSchema({
|
||||||
...authTables,
|
...authTables,
|
||||||
users: defineTable({ ... }).index('email', ['email']),
|
users: defineTable({
|
||||||
|
name: v.optional(v.string()),
|
||||||
|
image: v.optional(v.id('_storage')),
|
||||||
|
email: v.optional(v.string()),
|
||||||
|
emailVerificationTime: v.optional(v.number()),
|
||||||
|
phone: v.optional(v.string()),
|
||||||
|
phoneVerificationTime: v.optional(v.number()),
|
||||||
|
isAnonymous: v.optional(v.boolean()),
|
||||||
|
})
|
||||||
|
.index('email', ['email'])
|
||||||
|
.index('name', ['name'])
|
||||||
|
.index('phone', ['phone']),
|
||||||
|
|
||||||
|
profiles: defineTable({
|
||||||
|
userId: v.id('users'),
|
||||||
|
theme_preference: v.optional(v.string()),
|
||||||
|
}).index('userId', ['userId']),
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Queries & Mutations
|
### Queries & Mutations
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { v } from 'convex/values';
|
import { getAuthUserId } from '@convex-dev/auth/server';
|
||||||
|
import { ConvexError, v } from 'convex/values';
|
||||||
|
|
||||||
import { mutation, query } from './_generated/server';
|
import { mutation, query } from './_generated/server';
|
||||||
|
|
||||||
export const getUser = query({
|
export const getUser = query({
|
||||||
args: { userId: v.id('users') },
|
args: { userId: v.optional(v.id('users')) },
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
return await ctx.db.get(args.userId);
|
const userId = args.userId ?? (await getAuthUserId(ctx));
|
||||||
|
if (!userId) throw new ConvexError('Not authenticated.');
|
||||||
|
|
||||||
|
const user = await ctx.db.get(userId);
|
||||||
|
if (!user) throw new ConvexError('User not found.');
|
||||||
|
|
||||||
|
return user;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const updateUser = mutation({
|
||||||
|
args: {
|
||||||
|
name: v.optional(v.string()),
|
||||||
|
email: v.optional(v.string()),
|
||||||
|
image: v.optional(v.id('_storage')),
|
||||||
|
},
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
const userId = await getAuthUserId(ctx);
|
||||||
|
if (!userId) throw new ConvexError('Not authenticated.');
|
||||||
|
|
||||||
|
await ctx.db.patch(userId, args);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
### Auth
|
### Auth Setup
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { getAuthUserId } from '@convex-dev/auth/server';
|
import { Authentik } from '@convex-dev/auth/providers/authentik';
|
||||||
|
import { convexAuth } from '@convex-dev/auth/server';
|
||||||
|
|
||||||
// In any query/mutation:
|
import { Password } from './custom/auth/providers/password';
|
||||||
const userId = await getAuthUserId(ctx);
|
|
||||||
if (!userId) throw new ConvexError('Not authenticated.');
|
export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
|
||||||
|
providers: [Authentik({ allowDangerousEmailAccountLinking: true }), Password],
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Providers:**
|
||||||
|
|
||||||
|
1. **Authentik** - OAuth SSO provider
|
||||||
|
2. **Password** - Custom password auth with email OTP verification via UseSend
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Next.js Patterns
|
## Next.js Patterns
|
||||||
|
|
||||||
### Convex Provider Setup
|
### Convex Provider Setup (Dual-Layer)
|
||||||
|
|
||||||
|
**Root Layout** (`app/layout.tsx`):
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { ConvexAuthNextjsServerProvider } from '@convex-dev/auth/nextjs/server';
|
||||||
|
|
||||||
|
export default function RootLayout({ children }) {
|
||||||
|
return (
|
||||||
|
<ConvexAuthNextjsServerProvider>
|
||||||
|
<html lang="en">
|
||||||
|
<body>
|
||||||
|
<ConvexClientProvider>{children}</ConvexClientProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</ConvexAuthNextjsServerProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Client Provider** (`components/providers/ConvexClientProvider.tsx`):
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
// src/components/providers/ConvexClientProvider.tsx
|
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ConvexAuthNextjsProvider } from '@convex-dev/auth/nextjs';
|
import { ConvexAuthNextjsProvider } from '@convex-dev/auth/nextjs';
|
||||||
@@ -237,11 +425,54 @@ import { ConvexReactClient } from 'convex/react';
|
|||||||
|
|
||||||
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
|
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
|
||||||
|
|
||||||
export const ConvexClientProvider = ({ children }) => (
|
export function ConvexClientProvider({ children }) {
|
||||||
|
return (
|
||||||
<ConvexAuthNextjsProvider client={convex}>
|
<ConvexAuthNextjsProvider client={convex}>
|
||||||
{children}
|
{children}
|
||||||
</ConvexAuthNextjsProvider>
|
</ConvexAuthNextjsProvider>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Server Component Data Preloading
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
'use server';
|
||||||
|
|
||||||
|
import { ProfileComponent } from '@/components/profile';
|
||||||
|
import { preloadQuery } from 'convex/nextjs';
|
||||||
|
|
||||||
|
import { api } from '@gib/backend/convex/_generated/api.js';
|
||||||
|
|
||||||
|
const ProfilePage = async () => {
|
||||||
|
const preloadedUser = await preloadQuery(api.auth.getUser);
|
||||||
|
|
||||||
|
return <ProfileComponent preloadedUser={preloadedUser} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProfilePage;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Client Component Hydration
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import type { Preloaded } from 'convex/react';
|
||||||
|
import { useMutation, usePreloadedQuery } from 'convex/react';
|
||||||
|
|
||||||
|
import type { api } from '@gib/backend/convex/_generated/api.js';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
preloadedUser: Preloaded<typeof api.auth.getUser>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ProfileComponent({ preloadedUser }: Props) {
|
||||||
|
const user = usePreloadedQuery(preloadedUser);
|
||||||
|
const updateUser = useMutation(api.auth.updateUser);
|
||||||
|
|
||||||
|
// ... interactive logic
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Path Aliases
|
### Path Aliases
|
||||||
@@ -249,7 +480,30 @@ export const ConvexClientProvider = ({ children }) => (
|
|||||||
- `@/*` → `./src/*` (Next.js)
|
- `@/*` → `./src/*` (Next.js)
|
||||||
- `~/*` → `./src/*` (Expo)
|
- `~/*` → `./src/*` (Expo)
|
||||||
|
|
||||||
## Expo Patterns
|
### Middleware (Auth Protection)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {
|
||||||
|
convexAuthNextjsMiddleware,
|
||||||
|
isAuthenticatedNextjs,
|
||||||
|
} from '@convex-dev/auth/nextjs/server';
|
||||||
|
|
||||||
|
export default convexAuthNextjsMiddleware(async (request, { convexAuth }) => {
|
||||||
|
// Redirect authenticated users away from sign-in
|
||||||
|
if (isSignInPage(request) && (await convexAuth.isAuthenticated())) {
|
||||||
|
return nextjsMiddlewareRedirect(request, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protect routes
|
||||||
|
if (isProtectedRoute(request) && !(await convexAuth.isAuthenticated())) {
|
||||||
|
return nextjsMiddlewareRedirect(request, '/sign-in');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Expo Patterns [IGNORE FOR NOW]
|
||||||
|
|
||||||
### NativeWind (Tailwind for RN)
|
### NativeWind (Tailwind for RN)
|
||||||
|
|
||||||
@@ -267,15 +521,124 @@ import * as SecureStore from 'expo-secure-store';
|
|||||||
// Tokens stored via SecureStore, not AsyncStorage
|
// Tokens stored via SecureStore, not AsyncStorage
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Self-Hosted Deployment
|
||||||
|
|
||||||
|
This repo includes Docker configuration for self-hosted Convex deployment in `docker/`.
|
||||||
|
|
||||||
|
**Services:**
|
||||||
|
|
||||||
|
- `convex-backend` - Self-hosted Convex instance (port 3210)
|
||||||
|
- `convex-dashboard` - Admin dashboard (port 6791)
|
||||||
|
- `next-app` - Next.js application (standalone build)
|
||||||
|
|
||||||
|
**Quick Start:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd docker/
|
||||||
|
./generate_convex_admin_key # Generate CONVEX_SELF_HOSTED_ADMIN_KEY
|
||||||
|
# Edit .env with your values
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
**Network:** Uses external `nginx-bridge` network by default. Change in `compose.yml` if needed.
|
||||||
|
|
||||||
|
See `docker/compose.yml` and `docker/.env.example` for full configuration details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Known Issues / Cleanup Needed
|
## Known Issues / Cleanup Needed
|
||||||
|
|
||||||
1. **Apps reference @acme/_ instead of @gib/_** - Legacy T3 template imports
|
### Next.js App (apps/next/)
|
||||||
2. **TRPC imports exist but no TRPC** - Replace with Convex client
|
|
||||||
3. **Expo uses better-auth** - Should use @convex-dev/auth
|
1. ✅ **No @acme references** - Successfully migrated to @gib namespace
|
||||||
4. **@gib/ui uses pnpm dlx** - Should use bunx for shadcn
|
|
||||||
|
2. ⚠️ **TRPC in middleware matcher** (`src/middleware.ts:32`)
|
||||||
|
```typescript
|
||||||
|
// Can safely remove 'trpc' from this line (unused pattern):
|
||||||
|
matcher: ['/(api|trpc)(.*)'];
|
||||||
|
```
|
||||||
|
|
||||||
|
### Expo App (apps/expo/) - IGNORE FOR NOW
|
||||||
|
|
||||||
|
3. ⚠️ **@acme TRPC references** (`src/utils/api.tsx:1`, line 49)
|
||||||
|
- Imports `AppRouter` from `@acme/api`
|
||||||
|
- Exports `RouterInputs`, `RouterOutputs` from `@acme/api`
|
||||||
|
|
||||||
|
4. ⚠️ **@acme tailwind config** (`postcss.config.js:1`)
|
||||||
|
- Requires `@acme/tailwind-config/postcss-config`
|
||||||
|
- Should be `@gib/tailwind-config/postcss-config`
|
||||||
|
|
||||||
|
5. ⚠️ **Uses better-auth** - Should migrate to `@convex-dev/auth` (future work)
|
||||||
|
|
||||||
|
### Backend Package (packages/backend/)
|
||||||
|
|
||||||
|
6. ℹ️ **No root tsconfig.json** - This is intentional, not a bug
|
||||||
|
- Only `convex/tsconfig.json` exists (Convex requirement)
|
||||||
|
- Running `bun typecheck` shows TypeScript help message (expected)
|
||||||
|
- `scripts/` and `types/` folders outside `convex/` to avoid sync issues with Convex cloud
|
||||||
|
|
||||||
|
7. ⚠️ **Hardcoded branding** (`convex/custom/auth/providers/usesend.ts:26`)
|
||||||
|
- Email templates reference "TechTracker" and "Study Buddy"
|
||||||
|
- Should be parameterized or use generic branding
|
||||||
|
|
||||||
|
8. ⚠️ **Password validation mismatch** (`convex/custom/auth/providers/password.ts`)
|
||||||
|
- `types/auth.ts` `PASSWORD_REGEX` requires special characters
|
||||||
|
- `validatePassword()` function doesn't enforce special characters
|
||||||
|
- Either update regex or update validation function
|
||||||
|
|
||||||
|
9. ⚠️ **getAllUsers lacks auth** (`convex/auth.ts`)
|
||||||
|
- Public query accessible to anyone
|
||||||
|
- Should require authentication or be internal-only
|
||||||
|
|
||||||
|
### UI Package (packages/ui/)
|
||||||
|
|
||||||
|
10. ⚠️ **Uses pnpm dlx** (`package.json:40`)
|
||||||
|
```json
|
||||||
|
"ui-add": "pnpm dlx shadcn@latest add && prettier src --write --list-different"
|
||||||
|
```
|
||||||
|
|
||||||
|
- Should use `bunx shadcn@latest add` instead
|
||||||
|
- Keep prettier command as-is
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Adding UI Components
|
## Adding UI Components
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun ui-add # Interactive shadcn/ui component addition
|
bun ui-add # Interactive shadcn/ui component addition
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Note:** Currently uses `pnpm dlx` internally (see Known Issues #10). Will be updated to use `bunx`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Backend typecheck shows help message
|
||||||
|
|
||||||
|
**Issue:** Running `bun typecheck` from root shows TypeScript help for `@gib/backend`.
|
||||||
|
|
||||||
|
**Explanation:** This is expected. The backend package has no root `tsconfig.json` (only `convex/tsconfig.json`) following Convex's recommended structure.
|
||||||
|
|
||||||
|
**Solution:** No action needed. This is intentional behavior.
|
||||||
|
|
||||||
|
### Imports from Convex fail without .js extension
|
||||||
|
|
||||||
|
**Issue:** TypeScript can't resolve `@gib/backend/convex/_generated/api`.
|
||||||
|
|
||||||
|
**Solution:** Add `.js` extension: `@gib/backend/convex/_generated/api.js`
|
||||||
|
|
||||||
|
**Why:** Project uses `"type": "module"` requiring explicit extensions for ESM.
|
||||||
|
|
||||||
|
### Catalog updates break workspace
|
||||||
|
|
||||||
|
**Issue:** After updating dependencies, packages show version mismatches.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
|
||||||
|
1. Never use `bun update` directly
|
||||||
|
2. Edit version in root `package.json` catalog
|
||||||
|
3. Run `bun install`
|
||||||
|
4. Verify with `bun lint:ws`
|
||||||
|
|||||||
429
README.md
429
README.md
@@ -1,138 +1,359 @@
|
|||||||
# Turborepo starter
|
# Convex Turbo Monorepo
|
||||||
|
|
||||||
This Turborepo starter is maintained by Gib.
|
A production-ready Turborepo starter with Next.js, Expo, and self-hosted Convex backend. Built with TypeScript, Tailwind CSS, and modern tooling.
|
||||||
|
|
||||||
## Using this example
|
---
|
||||||
|
|
||||||
Run the following command:
|
## What's Inside?
|
||||||
|
|
||||||
```sh
|
### Apps & Packages
|
||||||
npx create-turbo@latest -e https://git.gbrown.org/gib/convex-monorepo.git
|
|
||||||
|
- **`apps/next`** - Next.js 16 web application with App Router
|
||||||
|
- **`apps/expo`** - Expo 54 mobile application _(in progress)_
|
||||||
|
- **`@gib/backend`** - Self-hosted Convex backend with authentication
|
||||||
|
- **`@gib/ui`** - Shared shadcn/ui component library
|
||||||
|
- **`@gib/eslint-config`** - ESLint configuration
|
||||||
|
- **`@gib/prettier-config`** - Prettier configuration with import sorting
|
||||||
|
- **`@gib/tailwind-config`** - Tailwind CSS v4 configuration
|
||||||
|
- **`@gib/tsconfig`** - Shared TypeScript configurations
|
||||||
|
|
||||||
|
### Tech Stack
|
||||||
|
|
||||||
|
- **Framework:** Next.js 16 (App Router) + Expo 54
|
||||||
|
- **Backend:** Convex (self-hosted)
|
||||||
|
- **Auth:** @convex-dev/auth with Authentik OAuth & Password providers
|
||||||
|
- **Styling:** Tailwind CSS v4 + shadcn/ui
|
||||||
|
- **Language:** TypeScript (strict mode)
|
||||||
|
- **Package Manager:** Bun
|
||||||
|
- **Monorepo:** Turborepo
|
||||||
|
- **Deployment:** Docker (standalone)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- [Bun](https://bun.sh) (v1.2.19+)
|
||||||
|
- [Docker](https://www.docker.com/) & Docker Compose (for self-hosted Convex)
|
||||||
|
- Node.js 22.20.0+ (for compatibility)
|
||||||
|
|
||||||
|
### Development Setup
|
||||||
|
|
||||||
|
#### 1. Clone & Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone <your-repo-url>
|
||||||
|
cd convex-monorepo
|
||||||
|
bun install
|
||||||
```
|
```
|
||||||
|
|
||||||
## What's inside?
|
#### 2. Configure Environment Variables
|
||||||
|
|
||||||
This Turborepo includes the following packages/apps:
|
Create a `.env` file in the project root with the following variables:
|
||||||
|
|
||||||
### Apps and Packages
|
```bash
|
||||||
|
# Convex Backend (Self-Hosted)
|
||||||
|
CONVEX_SELF_HOSTED_URL=https://api.convex.example.com
|
||||||
|
CONVEX_SELF_HOSTED_ADMIN_KEY=<generated>
|
||||||
|
CONVEX_SITE_URL=https://convex.example.com
|
||||||
|
|
||||||
- `next`: a [Next.js](https://nextjs.org/) app
|
# Next.js Public
|
||||||
- `expo`: another [Next.js](https://nextjs.org/) app
|
NEXT_PUBLIC_CONVEX_URL=https://api.convex.example.com
|
||||||
- `@gib/backend`: a [Convex](https://convex.dev) Backend
|
NEXT_PUBLIC_SITE_URL=https://example.com
|
||||||
- `@gib/ui`: a Shadcn React component library primarily for Next.js (In case we want to add another web application).
|
NEXT_PUBLIC_PLAUSIBLE_URL=https://plausible.example.com
|
||||||
- `@gib/eslint-config`: `eslint` configurations (includes `eslint-config-next` and `eslint-config-prettier`)
|
NEXT_PUBLIC_SENTRY_DSN=
|
||||||
- `@gib/prettier-config`: Prettier configurations
|
NEXT_PUBLIC_SENTRY_URL=
|
||||||
- `@gib/tailwind-config`: Tailwind configurations
|
NEXT_PUBLIC_SENTRY_ORG=
|
||||||
- `@gib/typescript-config`: `tsconfig.json`s used throughout the monorepo
|
NEXT_PUBLIC_SENTRY_PROJECT_NAME=
|
||||||
|
|
||||||
Each package/app is 100% [TypeScript](https://www.typescriptlang.org/).
|
# Server-side
|
||||||
|
SENTRY_AUTH_TOKEN=
|
||||||
|
|
||||||
### Utilities
|
# Auth (will be synced to Convex)
|
||||||
|
AUTH_AUTHENTIK_ID=
|
||||||
This Turborepo has some additional tools already setup for you:
|
AUTH_AUTHENTIK_SECRET=
|
||||||
|
AUTH_AUTHENTIK_ISSUER=
|
||||||
- [TypeScript](https://www.typescriptlang.org/) for static type checking
|
USESEND_API_KEY=
|
||||||
- [ESLint](https://eslint.org/) for code linting
|
|
||||||
- [Prettier](https://prettier.io) for code formatting
|
|
||||||
|
|
||||||
### Build
|
|
||||||
|
|
||||||
To build all apps and packages, run the following command:
|
|
||||||
|
|
||||||
```
|
|
||||||
cd my-turborepo
|
|
||||||
|
|
||||||
# With [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation) installed (recommended)
|
|
||||||
turbo build
|
|
||||||
|
|
||||||
# Without [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation), use your package manager
|
|
||||||
npx turbo build
|
|
||||||
yarn dlx turbo build
|
|
||||||
pnpm exec turbo build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can build a specific package by using a [filter](https://turborepo.com/docs/crafting-your-repository/running-tasks#using-filters):
|
**For local development:** Use `http://localhost:3210` for Convex URLs.
|
||||||
|
|
||||||
```
|
#### 3. Configure Docker Environment
|
||||||
# With [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation) installed (recommended)
|
|
||||||
turbo build --filter=docs
|
|
||||||
|
|
||||||
# Without [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation), use your package manager
|
Check and update environment variables in `docker/.env` for your deployment:
|
||||||
npx turbo build --filter=docs
|
|
||||||
yarn exec turbo build --filter=docs
|
```bash
|
||||||
pnpm exec turbo build --filter=docs
|
cd docker/
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env with your configuration
|
||||||
```
|
```
|
||||||
|
|
||||||
### Develop
|
#### 4. Start Self-Hosted Convex
|
||||||
|
|
||||||
To develop all apps and packages, run the following command:
|
Spin up the Convex backend and dashboard:
|
||||||
|
|
||||||
```
|
```bash
|
||||||
cd my-turborepo
|
cd docker/
|
||||||
|
docker compose up -d convex-backend convex-dashboard
|
||||||
# With [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation) installed (recommended)
|
|
||||||
turbo dev
|
|
||||||
|
|
||||||
# Without [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation), use your package manager
|
|
||||||
npx turbo dev
|
|
||||||
yarn exec turbo dev
|
|
||||||
pnpm exec turbo dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can develop a specific package by using a [filter](https://turborepo.com/docs/crafting-your-repository/running-tasks#using-filters):
|
**Services:**
|
||||||
|
|
||||||
```
|
- **Backend:** http://localhost:3210
|
||||||
# With [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation) installed (recommended)
|
- **Dashboard:** http://localhost:6791
|
||||||
turbo dev --filter=web
|
|
||||||
|
|
||||||
# Without [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation), use your package manager
|
#### 5. Generate Auth Keys & Sync Environment Variables
|
||||||
npx turbo dev --filter=web
|
|
||||||
yarn exec turbo dev --filter=web
|
Generate JWT keys for Convex Auth:
|
||||||
pnpm exec turbo dev --filter=web
|
|
||||||
|
```bash
|
||||||
|
cd packages/backend
|
||||||
|
bun run scripts/generateKeys.mjs
|
||||||
```
|
```
|
||||||
|
|
||||||
### Remote Caching
|
Sync environment variables to Convex deployment (via CLI or Dashboard):
|
||||||
|
|
||||||
> [!TIP]
|
```bash
|
||||||
> Vercel Remote Cache is free for all plans. Get started today at [vercel.com](https://vercel.com/signup?/signup?utm_source=remote-cache-sdk&utm_campaign=free_remote_cache).
|
cd packages/backend
|
||||||
|
bun with-env npx convex env set AUTH_AUTHENTIK_ID "your-value"
|
||||||
Turborepo can use a technique known as [Remote Caching](https://turborepo.com/docs/core-concepts/remote-caching) to share cache artifacts across machines, enabling you to share build caches with your team and CI/CD pipelines.
|
bun with-env npx convex env set AUTH_AUTHENTIK_SECRET "your-value"
|
||||||
|
bun with-env npx convex env set AUTH_AUTHENTIK_ISSUER "your-issuer-url"
|
||||||
By default, Turborepo will cache locally. To enable Remote Caching you will need an account with Vercel. If you don't have an account you can [create one](https://vercel.com/signup?utm_source=turborepo-examples), then enter the following commands:
|
bun with-env npx convex env set USESEND_API_KEY "your-api-key"
|
||||||
|
bun with-env npx convex env set CONVEX_SITE_URL "http://localhost:3000"
|
||||||
```
|
|
||||||
cd my-turborepo
|
|
||||||
|
|
||||||
# With [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation) installed (recommended)
|
|
||||||
turbo login
|
|
||||||
|
|
||||||
# Without [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation), use your package manager
|
|
||||||
npx turbo login
|
|
||||||
yarn exec turbo login
|
|
||||||
pnpm exec turbo login
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This will authenticate the Turborepo CLI with your [Vercel account](https://vercel.com/docs/concepts/personal-accounts/overview).
|
**Important:** For local development, set `CONVEX_SITE_URL` to `http://localhost:3000`.
|
||||||
|
|
||||||
Next, you can link your Turborepo to your Remote Cache by running the following command from the root of your Turborepo:
|
#### 6. Start Development Server
|
||||||
|
|
||||||
```
|
```bash
|
||||||
# With [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation) installed (recommended)
|
# From project root
|
||||||
turbo link
|
bun dev:next # Next.js app + Convex backend
|
||||||
|
# or
|
||||||
# Without [global `turbo`](https://turborepo.com/docs/getting-started/installation#global-installation), use your package manager
|
bun dev # All apps (Next.js + Expo + Backend)
|
||||||
npx turbo link
|
|
||||||
yarn exec turbo link
|
|
||||||
pnpm exec turbo link
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Useful Links
|
**App URLs:**
|
||||||
|
|
||||||
Learn more about the power of Turborepo:
|
- **Next.js:** http://localhost:3000
|
||||||
|
- **Convex Dashboard:** http://localhost:6791
|
||||||
|
|
||||||
- [Tasks](https://turborepo.com/docs/crafting-your-repository/running-tasks)
|
---
|
||||||
- [Caching](https://turborepo.com/docs/crafting-your-repository/caching)
|
|
||||||
- [Remote Caching](https://turborepo.com/docs/core-concepts/remote-caching)
|
## Development Commands
|
||||||
- [Filtering](https://turborepo.com/docs/crafting-your-repository/running-tasks#using-filters)
|
|
||||||
- [Configuration Options](https://turborepo.com/docs/reference/configuration)
|
```bash
|
||||||
- [CLI Usage](https://turborepo.com/docs/reference/command-line-reference)
|
# Development
|
||||||
|
bun dev # Run all apps
|
||||||
|
bun dev:next # Next.js + Convex backend
|
||||||
|
bun dev:expo # Expo + Convex backend
|
||||||
|
bun dev:backend # Convex backend only
|
||||||
|
|
||||||
|
# Quality Checks
|
||||||
|
bun typecheck # Type checking (recommended for testing)
|
||||||
|
bun lint # Lint all packages
|
||||||
|
bun lint:fix # Lint and auto-fix
|
||||||
|
bun format # Check formatting
|
||||||
|
bun format:fix # Fix formatting
|
||||||
|
|
||||||
|
# Build
|
||||||
|
bun build # Build all packages (production)
|
||||||
|
|
||||||
|
# Utilities
|
||||||
|
bun ui-add # Add shadcn/ui components
|
||||||
|
bun clean # Clean node_modules
|
||||||
|
bun clean:ws # Clean workspace caches
|
||||||
|
```
|
||||||
|
|
||||||
|
### Single Package Commands
|
||||||
|
|
||||||
|
Use Turborepo filters to target specific packages:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun turbo run dev -F @gib/next # Run Next.js only
|
||||||
|
bun turbo run typecheck -F @gib/backend # Typecheck backend
|
||||||
|
bun turbo run lint -F @gib/ui # Lint UI package
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
convex-monorepo/
|
||||||
|
├── apps/
|
||||||
|
│ ├── next/ # Next.js web app
|
||||||
|
│ │ ├── src/
|
||||||
|
│ │ │ ├── app/ # App Router pages
|
||||||
|
│ │ │ ├── components/ # React components
|
||||||
|
│ │ │ └── lib/ # Utilities
|
||||||
|
│ │ └── package.json
|
||||||
|
│ └── expo/ # Expo mobile app (WIP)
|
||||||
|
│
|
||||||
|
├── packages/
|
||||||
|
│ ├── backend/ # Convex backend
|
||||||
|
│ │ ├── convex/ # Convex functions (synced to cloud)
|
||||||
|
│ │ ├── scripts/ # Utilities (generateKeys.mjs)
|
||||||
|
│ │ └── types/ # Shared types
|
||||||
|
│ └── ui/ # shadcn/ui components
|
||||||
|
│ └── src/ # Component source files
|
||||||
|
│
|
||||||
|
├── tools/ # Shared configurations
|
||||||
|
│ ├── eslint/
|
||||||
|
│ ├── prettier/
|
||||||
|
│ ├── tailwind/
|
||||||
|
│ └── typescript/
|
||||||
|
│
|
||||||
|
├── docker/ # Self-hosted deployment
|
||||||
|
│ ├── compose.yml
|
||||||
|
│ ├── Dockerfile
|
||||||
|
│ └── .env.example
|
||||||
|
│
|
||||||
|
├── turbo.json # Turborepo configuration
|
||||||
|
└── package.json # Root workspace & catalogs
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### Authentication
|
||||||
|
|
||||||
|
- **OAuth:** Authentik SSO integration
|
||||||
|
- **Password:** Custom password auth with email verification
|
||||||
|
- **OTP:** Email verification via self-hosted UseSend
|
||||||
|
- **Session Management:** Secure cookie-based sessions
|
||||||
|
|
||||||
|
### Next.js App
|
||||||
|
|
||||||
|
- **App Router:** Next.js 16 with React Server Components
|
||||||
|
- **Data Preloading:** Server-side data fetching with Convex
|
||||||
|
- **Middleware:** Route protection & authentication
|
||||||
|
- **Styling:** Tailwind CSS v4 with dark mode
|
||||||
|
- **Analytics:** Plausible (privacy-focused)
|
||||||
|
- **Monitoring:** Sentry error tracking
|
||||||
|
|
||||||
|
### Backend
|
||||||
|
|
||||||
|
- **Real-time:** Convex reactive queries
|
||||||
|
- **File Storage:** Built-in file upload/storage
|
||||||
|
- **Auth:** Multi-provider authentication
|
||||||
|
- **Type-safe:** Full TypeScript with generated types
|
||||||
|
- **Self-hosted:** Complete control over your data
|
||||||
|
|
||||||
|
### Developer Experience
|
||||||
|
|
||||||
|
- **Monorepo:** Turborepo for efficient builds
|
||||||
|
- **Type Safety:** Strict TypeScript throughout
|
||||||
|
- **Code Quality:** ESLint + Prettier with auto-fix
|
||||||
|
- **Hot Reload:** Fast refresh for all packages
|
||||||
|
- **Catalog Deps:** Centralized version management
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
### Docker (Recommended)
|
||||||
|
|
||||||
|
Build and deploy with Docker Compose:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd docker/
|
||||||
|
|
||||||
|
# Start all services
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# View logs
|
||||||
|
docker compose logs -f
|
||||||
|
|
||||||
|
# Stop services
|
||||||
|
docker compose down
|
||||||
|
```
|
||||||
|
|
||||||
|
**Services:**
|
||||||
|
|
||||||
|
- `next-app` - Next.js standalone build
|
||||||
|
- `convex-backend` - Convex backend (port 3210)
|
||||||
|
- `convex-dashboard` - Admin dashboard (port 6791)
|
||||||
|
|
||||||
|
**Network:** Uses `nginx-bridge` network (configurable in `compose.yml`).
|
||||||
|
|
||||||
|
### Production Checklist
|
||||||
|
|
||||||
|
- [ ] Update environment variables in `docker/.env`
|
||||||
|
- [ ] Generate `CONVEX_SELF_HOSTED_ADMIN_KEY`
|
||||||
|
- [ ] Configure reverse proxy (Nginx/Traefik)
|
||||||
|
- [ ] Set up SSL certificates
|
||||||
|
- [ ] Sync auth environment variables to Convex
|
||||||
|
- [ ] Configure backup strategy for `docker/data/`
|
||||||
|
- [ ] Test authentication flow
|
||||||
|
- [ ] Enable Sentry error tracking
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- **[AGENTS.md](./AGENTS.md)** - Comprehensive guide for AI agents & developers
|
||||||
|
- **[Convex Docs](https://docs.convex.dev)** - Official Convex documentation
|
||||||
|
- **[Turborepo Docs](https://turbo.build/repo/docs)** - Turborepo documentation
|
||||||
|
- **[Next.js Docs](https://nextjs.org/docs)** - Next.js documentation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Backend typecheck shows help message
|
||||||
|
|
||||||
|
This is expected behavior. The backend package follows Convex's structure with only `convex/tsconfig.json` (no root tsconfig). See [AGENTS.md](./AGENTS.md) for details.
|
||||||
|
|
||||||
|
### Imports from Convex require .js extension
|
||||||
|
|
||||||
|
The project uses ESM (`"type": "module"`), requiring explicit file extensions:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ✅ Correct
|
||||||
|
|
||||||
|
// ❌ Wrong
|
||||||
|
import { api } from '@gib/backend/convex/_generated/api';
|
||||||
|
import { api } from '@gib/backend/convex/_generated/api.js';
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker containers won't start
|
||||||
|
|
||||||
|
1. Check Docker logs: `docker compose logs`
|
||||||
|
2. Verify environment variables in `docker/.env`
|
||||||
|
3. Ensure ports 3210 and 6791 are available
|
||||||
|
4. Check network configuration (`nginx-bridge`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
This is a personal monorepo template. Feel free to fork and adapt for your needs.
|
||||||
|
|
||||||
|
### Code Style
|
||||||
|
|
||||||
|
- Single quotes, trailing commas
|
||||||
|
- 80 character line width
|
||||||
|
- ESLint + Prettier enforced
|
||||||
|
- Import order: Types → React → Next → Third-party → @gib → Local
|
||||||
|
|
||||||
|
Run `bun lint:fix` and `bun format:fix` before committing.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
Built with inspiration from:
|
||||||
|
|
||||||
|
- [T3 Turbo](https://github.com/t3-oss/create-t3-turbo)
|
||||||
|
- [Convex](https://convex.dev)
|
||||||
|
- [shadcn/ui](https://ui.shadcn.com)
|
||||||
|
- [Turborepo](https://turbo.build)
|
||||||
|
|||||||
Reference in New Issue
Block a user