mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-06 19:46:44 +03:00
Migrate to Drizzle kit
This commit is contained in:
92
MIGRATION_GUIDE.md
Normal file
92
MIGRATION_GUIDE.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# Drizzle Kit Migration Guide
|
||||||
|
|
||||||
|
This project now uses Drizzle Kit for database migrations, providing better schema management and migration tracking.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
- **Database**: SQLite (with preparation for future PostgreSQL migration)
|
||||||
|
- **ORM**: Drizzle ORM with Drizzle Kit for migrations
|
||||||
|
- **Schema Location**: `/src/lib/db/schema.ts`
|
||||||
|
- **Migrations Folder**: `/drizzle`
|
||||||
|
- **Configuration**: `/drizzle.config.ts`
|
||||||
|
|
||||||
|
## Available Commands
|
||||||
|
|
||||||
|
### Database Management
|
||||||
|
- `bun run init-db` - Initialize database with all migrations
|
||||||
|
- `bun run check-db` - Check database status and recent migrations
|
||||||
|
- `bun run reset-users` - Remove all users and related data
|
||||||
|
- `bun run cleanup-db` - Remove database files
|
||||||
|
|
||||||
|
### Drizzle Kit Commands
|
||||||
|
- `bun run db:generate` - Generate new migration files from schema changes
|
||||||
|
- `bun run db:migrate` - Apply pending migrations to database
|
||||||
|
- `bun run db:push` - Push schema changes directly (development)
|
||||||
|
- `bun run db:pull` - Pull schema from database
|
||||||
|
- `bun run db:check` - Check for migration issues
|
||||||
|
- `bun run db:studio` - Open Drizzle Studio for database browsing
|
||||||
|
|
||||||
|
## Making Schema Changes
|
||||||
|
|
||||||
|
1. **Update Schema**: Edit `/src/lib/db/schema.ts`
|
||||||
|
2. **Generate Migration**: Run `bun run db:generate`
|
||||||
|
3. **Review Migration**: Check the generated SQL in `/drizzle` folder
|
||||||
|
4. **Apply Migration**: Run `bun run db:migrate` or restart the application
|
||||||
|
|
||||||
|
## Migration Process
|
||||||
|
|
||||||
|
The application automatically runs migrations on startup:
|
||||||
|
- Checks for pending migrations
|
||||||
|
- Creates migrations table if needed
|
||||||
|
- Applies all pending migrations in order
|
||||||
|
- Tracks migration history
|
||||||
|
|
||||||
|
## Schema Organization
|
||||||
|
|
||||||
|
### Tables
|
||||||
|
- `users` - User authentication and accounts
|
||||||
|
- `configs` - GitHub/Gitea configurations
|
||||||
|
- `repositories` - Repository mirror tracking
|
||||||
|
- `organizations` - GitHub organizations
|
||||||
|
- `mirror_jobs` - Job tracking with resilience
|
||||||
|
- `events` - Real-time event notifications
|
||||||
|
|
||||||
|
### Indexes
|
||||||
|
All performance-critical indexes are automatically created:
|
||||||
|
- User lookups
|
||||||
|
- Repository status queries
|
||||||
|
- Organization filtering
|
||||||
|
- Job tracking
|
||||||
|
- Event channels
|
||||||
|
|
||||||
|
## Future PostgreSQL Migration
|
||||||
|
|
||||||
|
The setup is designed for easy PostgreSQL migration:
|
||||||
|
|
||||||
|
1. Update `drizzle.config.ts`:
|
||||||
|
```typescript
|
||||||
|
export default defineConfig({
|
||||||
|
dialect: "postgresql",
|
||||||
|
schema: "./src/lib/db/schema.ts",
|
||||||
|
out: "./drizzle",
|
||||||
|
dbCredentials: {
|
||||||
|
connectionString: process.env.DATABASE_URL,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Update connection in `/src/lib/db/index.ts`
|
||||||
|
3. Generate new migrations: `bun run db:generate`
|
||||||
|
4. Apply to PostgreSQL: `bun run db:migrate`
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Migration Errors
|
||||||
|
- Check `/drizzle` folder for migration files
|
||||||
|
- Verify database permissions
|
||||||
|
- Review migration SQL for conflicts
|
||||||
|
|
||||||
|
### Schema Conflicts
|
||||||
|
- Use `bun run db:check` to identify issues
|
||||||
|
- Review generated migrations before applying
|
||||||
|
- Keep schema.ts as single source of truth
|
||||||
63
bun.lock
63
bun.lock
@@ -61,6 +61,7 @@
|
|||||||
"@types/jsonwebtoken": "^9.0.10",
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@vitejs/plugin-react": "^4.6.0",
|
"@vitejs/plugin-react": "^4.6.0",
|
||||||
|
"drizzle-kit": "^0.31.4",
|
||||||
"jsdom": "^26.1.0",
|
"jsdom": "^26.1.0",
|
||||||
"tsx": "^4.20.3",
|
"tsx": "^4.20.3",
|
||||||
"vitest": "^3.2.4",
|
"vitest": "^3.2.4",
|
||||||
@@ -146,6 +147,8 @@
|
|||||||
|
|
||||||
"@csstools/css-tokenizer": ["@csstools/css-tokenizer@3.0.4", "", {}, "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw=="],
|
"@csstools/css-tokenizer": ["@csstools/css-tokenizer@3.0.4", "", {}, "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw=="],
|
||||||
|
|
||||||
|
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
|
||||||
|
|
||||||
"@emmetio/abbreviation": ["@emmetio/abbreviation@2.3.3", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA=="],
|
"@emmetio/abbreviation": ["@emmetio/abbreviation@2.3.3", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA=="],
|
||||||
|
|
||||||
"@emmetio/css-abbreviation": ["@emmetio/css-abbreviation@2.1.8", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw=="],
|
"@emmetio/css-abbreviation": ["@emmetio/css-abbreviation@2.1.8", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw=="],
|
||||||
@@ -162,6 +165,10 @@
|
|||||||
|
|
||||||
"@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
|
"@emnapi/runtime": ["@emnapi/runtime@1.4.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
|
||||||
|
|
||||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA=="],
|
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA=="],
|
||||||
|
|
||||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.5", "", { "os": "android", "cpu": "arm" }, "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA=="],
|
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.5", "", { "os": "android", "cpu": "arm" }, "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA=="],
|
||||||
@@ -630,6 +637,8 @@
|
|||||||
|
|
||||||
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
|
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
|
||||||
|
|
||||||
|
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||||
|
|
||||||
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
"cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="],
|
||||||
|
|
||||||
"camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="],
|
"camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="],
|
||||||
@@ -742,6 +751,8 @@
|
|||||||
|
|
||||||
"dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
|
"dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
|
||||||
|
|
||||||
|
"drizzle-kit": ["drizzle-kit@0.31.4", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-tCPWVZWZqWVx2XUsVpJRnH9Mx0ClVOf5YUHerZ5so1OKSlqww4zy1R5ksEdGRcO3tM3zj0PYN6V48TbQCL1RfA=="],
|
||||||
|
|
||||||
"drizzle-orm": ["drizzle-orm@0.44.2", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-zGAqBzWWkVSFjZpwPOrmCrgO++1kZ5H/rZ4qTGeGOe18iXGVJWf3WPfHOVwFIbmi8kHjfJstC6rJomzGx8g/dQ=="],
|
"drizzle-orm": ["drizzle-orm@0.44.2", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-zGAqBzWWkVSFjZpwPOrmCrgO++1kZ5H/rZ4qTGeGOe18iXGVJWf3WPfHOVwFIbmi8kHjfJstC6rJomzGx8g/dQ=="],
|
||||||
|
|
||||||
"dset": ["dset@3.1.4", "", {}, "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA=="],
|
"dset": ["dset@3.1.4", "", {}, "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA=="],
|
||||||
@@ -770,6 +781,8 @@
|
|||||||
|
|
||||||
"esbuild": ["esbuild@0.25.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.5", "@esbuild/android-arm": "0.25.5", "@esbuild/android-arm64": "0.25.5", "@esbuild/android-x64": "0.25.5", "@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-x64": "0.25.5", "@esbuild/freebsd-arm64": "0.25.5", "@esbuild/freebsd-x64": "0.25.5", "@esbuild/linux-arm": "0.25.5", "@esbuild/linux-arm64": "0.25.5", "@esbuild/linux-ia32": "0.25.5", "@esbuild/linux-loong64": "0.25.5", "@esbuild/linux-mips64el": "0.25.5", "@esbuild/linux-ppc64": "0.25.5", "@esbuild/linux-riscv64": "0.25.5", "@esbuild/linux-s390x": "0.25.5", "@esbuild/linux-x64": "0.25.5", "@esbuild/netbsd-arm64": "0.25.5", "@esbuild/netbsd-x64": "0.25.5", "@esbuild/openbsd-arm64": "0.25.5", "@esbuild/openbsd-x64": "0.25.5", "@esbuild/sunos-x64": "0.25.5", "@esbuild/win32-arm64": "0.25.5", "@esbuild/win32-ia32": "0.25.5", "@esbuild/win32-x64": "0.25.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ=="],
|
"esbuild": ["esbuild@0.25.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.5", "@esbuild/android-arm": "0.25.5", "@esbuild/android-arm64": "0.25.5", "@esbuild/android-x64": "0.25.5", "@esbuild/darwin-arm64": "0.25.5", "@esbuild/darwin-x64": "0.25.5", "@esbuild/freebsd-arm64": "0.25.5", "@esbuild/freebsd-x64": "0.25.5", "@esbuild/linux-arm": "0.25.5", "@esbuild/linux-arm64": "0.25.5", "@esbuild/linux-ia32": "0.25.5", "@esbuild/linux-loong64": "0.25.5", "@esbuild/linux-mips64el": "0.25.5", "@esbuild/linux-ppc64": "0.25.5", "@esbuild/linux-riscv64": "0.25.5", "@esbuild/linux-s390x": "0.25.5", "@esbuild/linux-x64": "0.25.5", "@esbuild/netbsd-arm64": "0.25.5", "@esbuild/netbsd-x64": "0.25.5", "@esbuild/openbsd-arm64": "0.25.5", "@esbuild/openbsd-x64": "0.25.5", "@esbuild/sunos-x64": "0.25.5", "@esbuild/win32-arm64": "0.25.5", "@esbuild/win32-ia32": "0.25.5", "@esbuild/win32-x64": "0.25.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ=="],
|
||||||
|
|
||||||
|
"esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
|
||||||
|
|
||||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||||
|
|
||||||
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
||||||
@@ -1324,6 +1337,8 @@
|
|||||||
|
|
||||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
|
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
|
||||||
|
|
||||||
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
||||||
|
|
||||||
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
|
||||||
@@ -1576,6 +1591,8 @@
|
|||||||
|
|
||||||
"@babel/template/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
|
"@babel/template/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
|
||||||
|
|
||||||
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
|
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.4.3", "", { "dependencies": { "@emnapi/wasi-threads": "1.0.2", "tslib": "^2.4.0" }, "bundled": true }, "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g=="],
|
||||||
@@ -1626,6 +1643,8 @@
|
|||||||
|
|
||||||
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
|
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
|
||||||
|
|
||||||
|
"source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||||
|
|
||||||
"strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
|
"strip-literal/js-tokens": ["js-tokens@9.0.1", "", {}, "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ=="],
|
||||||
|
|
||||||
"vscode-json-languageservice/jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
|
"vscode-json-languageservice/jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
|
||||||
@@ -1654,6 +1673,50 @@
|
|||||||
|
|
||||||
"@babel/helper-module-transforms/@babel/traverse/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
|
"@babel/helper-module-transforms/@babel/traverse/@babel/parser": ["@babel/parser@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" }, "bin": "./bin/babel-parser.js" }, "sha512-xyYxRj6+tLNDTWi0KCBcZ9V7yg3/lwL9DWh9Uwh/RIVlIfFidggcgxKX3GCXwCiswwcGRawBKbEg2LG/Y8eJhw=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="],
|
||||||
|
|
||||||
|
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
|
||||||
|
|
||||||
"boxen/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
|
"boxen/string-width/emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
|
||||||
|
|
||||||
"boxen/string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
"boxen/string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||||
|
|||||||
16
drizzle.config.ts
Normal file
16
drizzle.config.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { defineConfig } from "drizzle-kit";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
dialect: "sqlite",
|
||||||
|
schema: "./src/lib/db/schema.ts",
|
||||||
|
out: "./drizzle",
|
||||||
|
dbCredentials: {
|
||||||
|
url: "./data/gitea-mirror.db",
|
||||||
|
},
|
||||||
|
verbose: true,
|
||||||
|
strict: true,
|
||||||
|
migrations: {
|
||||||
|
table: "__drizzle_migrations",
|
||||||
|
schema: "main",
|
||||||
|
},
|
||||||
|
});
|
||||||
130
drizzle/0000_big_xorn.sql
Normal file
130
drizzle/0000_big_xorn.sql
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
CREATE TABLE `configs` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`user_id` text NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`is_active` integer DEFAULT true NOT NULL,
|
||||||
|
`github_config` text NOT NULL,
|
||||||
|
`gitea_config` text NOT NULL,
|
||||||
|
`include` text DEFAULT '["*"]' NOT NULL,
|
||||||
|
`exclude` text DEFAULT '[]' NOT NULL,
|
||||||
|
`schedule_config` text NOT NULL,
|
||||||
|
`cleanup_config` text NOT NULL,
|
||||||
|
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||||
|
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE `events` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`user_id` text NOT NULL,
|
||||||
|
`channel` text NOT NULL,
|
||||||
|
`payload` text NOT NULL,
|
||||||
|
`read` integer DEFAULT false NOT NULL,
|
||||||
|
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_events_user_channel` ON `events` (`user_id`,`channel`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_events_created_at` ON `events` (`created_at`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_events_read` ON `events` (`read`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `mirror_jobs` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`user_id` text NOT NULL,
|
||||||
|
`repository_id` text,
|
||||||
|
`repository_name` text,
|
||||||
|
`organization_id` text,
|
||||||
|
`organization_name` text,
|
||||||
|
`details` text,
|
||||||
|
`status` text DEFAULT 'imported' NOT NULL,
|
||||||
|
`message` text NOT NULL,
|
||||||
|
`timestamp` integer DEFAULT (unixepoch()) NOT NULL,
|
||||||
|
`job_type` text DEFAULT 'mirror' NOT NULL,
|
||||||
|
`batch_id` text,
|
||||||
|
`total_items` integer,
|
||||||
|
`completed_items` integer DEFAULT 0,
|
||||||
|
`item_ids` text,
|
||||||
|
`completed_item_ids` text DEFAULT '[]',
|
||||||
|
`in_progress` integer DEFAULT false NOT NULL,
|
||||||
|
`started_at` integer,
|
||||||
|
`completed_at` integer,
|
||||||
|
`last_checkpoint` integer,
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_mirror_jobs_user_id` ON `mirror_jobs` (`user_id`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_mirror_jobs_batch_id` ON `mirror_jobs` (`batch_id`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_mirror_jobs_in_progress` ON `mirror_jobs` (`in_progress`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_mirror_jobs_job_type` ON `mirror_jobs` (`job_type`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_mirror_jobs_timestamp` ON `mirror_jobs` (`timestamp`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `organizations` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`user_id` text NOT NULL,
|
||||||
|
`config_id` text NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`avatar_url` text NOT NULL,
|
||||||
|
`membership_role` text DEFAULT 'member' NOT NULL,
|
||||||
|
`is_included` integer DEFAULT true NOT NULL,
|
||||||
|
`destination_org` text,
|
||||||
|
`status` text DEFAULT 'imported' NOT NULL,
|
||||||
|
`last_mirrored` integer,
|
||||||
|
`error_message` text,
|
||||||
|
`repository_count` integer DEFAULT 0 NOT NULL,
|
||||||
|
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||||
|
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action,
|
||||||
|
FOREIGN KEY (`config_id`) REFERENCES `configs`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_organizations_user_id` ON `organizations` (`user_id`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_organizations_config_id` ON `organizations` (`config_id`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_organizations_status` ON `organizations` (`status`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_organizations_is_included` ON `organizations` (`is_included`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `repositories` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`user_id` text NOT NULL,
|
||||||
|
`config_id` text NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`full_name` text NOT NULL,
|
||||||
|
`url` text NOT NULL,
|
||||||
|
`clone_url` text NOT NULL,
|
||||||
|
`owner` text NOT NULL,
|
||||||
|
`organization` text,
|
||||||
|
`mirrored_location` text DEFAULT '',
|
||||||
|
`is_private` integer DEFAULT false NOT NULL,
|
||||||
|
`is_fork` integer DEFAULT false NOT NULL,
|
||||||
|
`forked_from` text,
|
||||||
|
`has_issues` integer DEFAULT false NOT NULL,
|
||||||
|
`is_starred` integer DEFAULT false NOT NULL,
|
||||||
|
`is_archived` integer DEFAULT false NOT NULL,
|
||||||
|
`size` integer DEFAULT 0 NOT NULL,
|
||||||
|
`has_lfs` integer DEFAULT false NOT NULL,
|
||||||
|
`has_submodules` integer DEFAULT false NOT NULL,
|
||||||
|
`language` text,
|
||||||
|
`description` text,
|
||||||
|
`default_branch` text NOT NULL,
|
||||||
|
`visibility` text DEFAULT 'public' NOT NULL,
|
||||||
|
`status` text DEFAULT 'imported' NOT NULL,
|
||||||
|
`last_mirrored` integer,
|
||||||
|
`error_message` text,
|
||||||
|
`destination_org` text,
|
||||||
|
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||||
|
`updated_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||||
|
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action,
|
||||||
|
FOREIGN KEY (`config_id`) REFERENCES `configs`(`id`) ON UPDATE no action ON DELETE no action
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_repositories_user_id` ON `repositories` (`user_id`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_repositories_config_id` ON `repositories` (`config_id`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_repositories_status` ON `repositories` (`status`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_repositories_owner` ON `repositories` (`owner`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_repositories_organization` ON `repositories` (`organization`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_repositories_is_fork` ON `repositories` (`is_fork`);--> statement-breakpoint
|
||||||
|
CREATE INDEX `idx_repositories_is_starred` ON `repositories` (`is_starred`);--> statement-breakpoint
|
||||||
|
CREATE TABLE `users` (
|
||||||
|
`id` text PRIMARY KEY NOT NULL,
|
||||||
|
`username` text NOT NULL,
|
||||||
|
`password` text NOT NULL,
|
||||||
|
`email` text NOT NULL,
|
||||||
|
`created_at` integer DEFAULT (unixepoch()) NOT NULL,
|
||||||
|
`updated_at` integer DEFAULT (unixepoch()) NOT NULL
|
||||||
|
);
|
||||||
955
drizzle/meta/0000_snapshot.json
Normal file
955
drizzle/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,955 @@
|
|||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "b963d828-412d-4192-b0aa-3b13b83cfba8",
|
||||||
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"tables": {
|
||||||
|
"configs": {
|
||||||
|
"name": "configs",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"is_active": {
|
||||||
|
"name": "is_active",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"github_config": {
|
||||||
|
"name": "github_config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"gitea_config": {
|
||||||
|
"name": "gitea_config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"include": {
|
||||||
|
"name": "include",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[\"*\"]'"
|
||||||
|
},
|
||||||
|
"exclude": {
|
||||||
|
"name": "exclude",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"schedule_config": {
|
||||||
|
"name": "schedule_config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"cleanup_config": {
|
||||||
|
"name": "cleanup_config",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"configs_user_id_users_id_fk": {
|
||||||
|
"name": "configs_user_id_users_id_fk",
|
||||||
|
"tableFrom": "configs",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"events": {
|
||||||
|
"name": "events",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"channel": {
|
||||||
|
"name": "channel",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"payload": {
|
||||||
|
"name": "payload",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"read": {
|
||||||
|
"name": "read",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"idx_events_user_channel": {
|
||||||
|
"name": "idx_events_user_channel",
|
||||||
|
"columns": [
|
||||||
|
"user_id",
|
||||||
|
"channel"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"idx_events_created_at": {
|
||||||
|
"name": "idx_events_created_at",
|
||||||
|
"columns": [
|
||||||
|
"created_at"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"idx_events_read": {
|
||||||
|
"name": "idx_events_read",
|
||||||
|
"columns": [
|
||||||
|
"read"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"events_user_id_users_id_fk": {
|
||||||
|
"name": "events_user_id_users_id_fk",
|
||||||
|
"tableFrom": "events",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"mirror_jobs": {
|
||||||
|
"name": "mirror_jobs",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"repository_id": {
|
||||||
|
"name": "repository_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"repository_name": {
|
||||||
|
"name": "repository_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"organization_id": {
|
||||||
|
"name": "organization_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"organization_name": {
|
||||||
|
"name": "organization_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"name": "details",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'imported'"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"name": "message",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"name": "timestamp",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
},
|
||||||
|
"job_type": {
|
||||||
|
"name": "job_type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'mirror'"
|
||||||
|
},
|
||||||
|
"batch_id": {
|
||||||
|
"name": "batch_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"total_items": {
|
||||||
|
"name": "total_items",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"completed_items": {
|
||||||
|
"name": "completed_items",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"item_ids": {
|
||||||
|
"name": "item_ids",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"completed_item_ids": {
|
||||||
|
"name": "completed_item_ids",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"in_progress": {
|
||||||
|
"name": "in_progress",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"started_at": {
|
||||||
|
"name": "started_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"completed_at": {
|
||||||
|
"name": "completed_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"last_checkpoint": {
|
||||||
|
"name": "last_checkpoint",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"idx_mirror_jobs_user_id": {
|
||||||
|
"name": "idx_mirror_jobs_user_id",
|
||||||
|
"columns": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"idx_mirror_jobs_batch_id": {
|
||||||
|
"name": "idx_mirror_jobs_batch_id",
|
||||||
|
"columns": [
|
||||||
|
"batch_id"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"idx_mirror_jobs_in_progress": {
|
||||||
|
"name": "idx_mirror_jobs_in_progress",
|
||||||
|
"columns": [
|
||||||
|
"in_progress"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"idx_mirror_jobs_job_type": {
|
||||||
|
"name": "idx_mirror_jobs_job_type",
|
||||||
|
"columns": [
|
||||||
|
"job_type"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"idx_mirror_jobs_timestamp": {
|
||||||
|
"name": "idx_mirror_jobs_timestamp",
|
||||||
|
"columns": [
|
||||||
|
"timestamp"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"mirror_jobs_user_id_users_id_fk": {
|
||||||
|
"name": "mirror_jobs_user_id_users_id_fk",
|
||||||
|
"tableFrom": "mirror_jobs",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"organizations": {
|
||||||
|
"name": "organizations",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"config_id": {
|
||||||
|
"name": "config_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"avatar_url": {
|
||||||
|
"name": "avatar_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"membership_role": {
|
||||||
|
"name": "membership_role",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'member'"
|
||||||
|
},
|
||||||
|
"is_included": {
|
||||||
|
"name": "is_included",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"destination_org": {
|
||||||
|
"name": "destination_org",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'imported'"
|
||||||
|
},
|
||||||
|
"last_mirrored": {
|
||||||
|
"name": "last_mirrored",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"error_message": {
|
||||||
|
"name": "error_message",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"repository_count": {
|
||||||
|
"name": "repository_count",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"idx_organizations_user_id": {
|
||||||
|
"name": "idx_organizations_user_id",
|
||||||
|
"columns": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"idx_organizations_config_id": {
|
||||||
|
"name": "idx_organizations_config_id",
|
||||||
|
"columns": [
|
||||||
|
"config_id"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"idx_organizations_status": {
|
||||||
|
"name": "idx_organizations_status",
|
||||||
|
"columns": [
|
||||||
|
"status"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"idx_organizations_is_included": {
|
||||||
|
"name": "idx_organizations_is_included",
|
||||||
|
"columns": [
|
||||||
|
"is_included"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"organizations_user_id_users_id_fk": {
|
||||||
|
"name": "organizations_user_id_users_id_fk",
|
||||||
|
"tableFrom": "organizations",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"organizations_config_id_configs_id_fk": {
|
||||||
|
"name": "organizations_config_id_configs_id_fk",
|
||||||
|
"tableFrom": "organizations",
|
||||||
|
"tableTo": "configs",
|
||||||
|
"columnsFrom": [
|
||||||
|
"config_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"repositories": {
|
||||||
|
"name": "repositories",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"config_id": {
|
||||||
|
"name": "config_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"full_name": {
|
||||||
|
"name": "full_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"name": "url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"clone_url": {
|
||||||
|
"name": "clone_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"name": "owner",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"organization": {
|
||||||
|
"name": "organization",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"mirrored_location": {
|
||||||
|
"name": "mirrored_location",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "''"
|
||||||
|
},
|
||||||
|
"is_private": {
|
||||||
|
"name": "is_private",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"is_fork": {
|
||||||
|
"name": "is_fork",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"forked_from": {
|
||||||
|
"name": "forked_from",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"has_issues": {
|
||||||
|
"name": "has_issues",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"is_starred": {
|
||||||
|
"name": "is_starred",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"is_archived": {
|
||||||
|
"name": "is_archived",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"name": "size",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"has_lfs": {
|
||||||
|
"name": "has_lfs",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"has_submodules": {
|
||||||
|
"name": "has_submodules",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"name": "language",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"default_branch": {
|
||||||
|
"name": "default_branch",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"name": "visibility",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'public'"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'imported'"
|
||||||
|
},
|
||||||
|
"last_mirrored": {
|
||||||
|
"name": "last_mirrored",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"error_message": {
|
||||||
|
"name": "error_message",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"destination_org": {
|
||||||
|
"name": "destination_org",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"idx_repositories_user_id": {
|
||||||
|
"name": "idx_repositories_user_id",
|
||||||
|
"columns": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"idx_repositories_config_id": {
|
||||||
|
"name": "idx_repositories_config_id",
|
||||||
|
"columns": [
|
||||||
|
"config_id"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"idx_repositories_status": {
|
||||||
|
"name": "idx_repositories_status",
|
||||||
|
"columns": [
|
||||||
|
"status"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"idx_repositories_owner": {
|
||||||
|
"name": "idx_repositories_owner",
|
||||||
|
"columns": [
|
||||||
|
"owner"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"idx_repositories_organization": {
|
||||||
|
"name": "idx_repositories_organization",
|
||||||
|
"columns": [
|
||||||
|
"organization"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"idx_repositories_is_fork": {
|
||||||
|
"name": "idx_repositories_is_fork",
|
||||||
|
"columns": [
|
||||||
|
"is_fork"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
},
|
||||||
|
"idx_repositories_is_starred": {
|
||||||
|
"name": "idx_repositories_is_starred",
|
||||||
|
"columns": [
|
||||||
|
"is_starred"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"repositories_user_id_users_id_fk": {
|
||||||
|
"name": "repositories_user_id_users_id_fk",
|
||||||
|
"tableFrom": "repositories",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"repositories_config_id_configs_id_fk": {
|
||||||
|
"name": "repositories_config_id_configs_id_fk",
|
||||||
|
"tableFrom": "repositories",
|
||||||
|
"tableTo": "configs",
|
||||||
|
"columnsFrom": [
|
||||||
|
"config_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "users",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"name": "password",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "(unixepoch())"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
drizzle/meta/_journal.json
Normal file
13
drizzle/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1752161775910,
|
||||||
|
"tag": "0000_big_xorn",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -16,6 +16,12 @@
|
|||||||
"check-db": "bun scripts/manage-db.ts check",
|
"check-db": "bun scripts/manage-db.ts check",
|
||||||
"fix-db": "bun scripts/manage-db.ts fix",
|
"fix-db": "bun scripts/manage-db.ts fix",
|
||||||
"reset-users": "bun scripts/manage-db.ts reset-users",
|
"reset-users": "bun scripts/manage-db.ts reset-users",
|
||||||
|
"db:generate": "bun drizzle-kit generate",
|
||||||
|
"db:migrate": "bun drizzle-kit migrate",
|
||||||
|
"db:push": "bun drizzle-kit push",
|
||||||
|
"db:pull": "bun drizzle-kit pull",
|
||||||
|
"db:check": "bun drizzle-kit check",
|
||||||
|
"db:studio": "bun drizzle-kit studio",
|
||||||
"startup-recovery": "bun scripts/startup-recovery.ts",
|
"startup-recovery": "bun scripts/startup-recovery.ts",
|
||||||
"startup-recovery-force": "bun scripts/startup-recovery.ts --force",
|
"startup-recovery-force": "bun scripts/startup-recovery.ts --force",
|
||||||
"test-recovery": "bun scripts/test-recovery.ts",
|
"test-recovery": "bun scripts/test-recovery.ts",
|
||||||
@@ -88,6 +94,7 @@
|
|||||||
"@types/jsonwebtoken": "^9.0.10",
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@vitejs/plugin-react": "^4.6.0",
|
"@vitejs/plugin-react": "^4.6.0",
|
||||||
|
"drizzle-kit": "^0.31.4",
|
||||||
"jsdom": "^26.1.0",
|
"jsdom": "^26.1.0",
|
||||||
"tsx": "^4.20.3",
|
"tsx": "^4.20.3",
|
||||||
"vitest": "^3.2.4"
|
"vitest": "^3.2.4"
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { Database } from "bun:sqlite";
|
import { Database } from "bun:sqlite";
|
||||||
|
import { drizzle } from "drizzle-orm/bun-sqlite";
|
||||||
|
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
import { users, configs, repositories, organizations, mirrorJobs, events } from "../src/lib/db/schema";
|
||||||
|
import bcrypt from "bcryptjs";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
// Command line arguments
|
// Command line arguments
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
@@ -13,750 +18,222 @@ if (!fs.existsSync(dataDir)) {
|
|||||||
fs.mkdirSync(dataDir, { recursive: true });
|
fs.mkdirSync(dataDir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Database paths
|
|
||||||
const rootDbFile = path.join(process.cwd(), "gitea-mirror.db");
|
|
||||||
const rootDevDbFile = path.join(process.cwd(), "gitea-mirror-dev.db");
|
|
||||||
const dataDbFile = path.join(dataDir, "gitea-mirror.db");
|
|
||||||
const dataDevDbFile = path.join(dataDir, "gitea-mirror-dev.db");
|
|
||||||
|
|
||||||
// Database path - ensure we use absolute path
|
// Database path - ensure we use absolute path
|
||||||
const dbPath = path.join(dataDir, "gitea-mirror.db");
|
const dbPath = path.join(dataDir, "gitea-mirror.db");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure all required tables exist
|
* Initialize database with migrations
|
||||||
*/
|
*/
|
||||||
async function ensureTablesExist() {
|
async function initDatabase() {
|
||||||
// Create or open the database
|
console.log("📦 Initializing database...");
|
||||||
const db = new Database(dbPath);
|
|
||||||
|
|
||||||
const requiredTables = [
|
// Create an empty database file if it doesn't exist
|
||||||
"users",
|
if (!fs.existsSync(dbPath)) {
|
||||||
"configs",
|
fs.writeFileSync(dbPath, "");
|
||||||
"repositories",
|
|
||||||
"organizations",
|
|
||||||
"mirror_jobs",
|
|
||||||
"events",
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const table of requiredTables) {
|
|
||||||
try {
|
|
||||||
// Check if table exists
|
|
||||||
const result = db.query(`SELECT name FROM sqlite_master WHERE type='table' AND name='${table}'`).get();
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
console.warn(`⚠️ Table '${table}' is missing. Creating it now...`);
|
|
||||||
|
|
||||||
switch (table) {
|
|
||||||
case "users":
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE users (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
username TEXT NOT NULL,
|
|
||||||
password TEXT NOT NULL,
|
|
||||||
email TEXT NOT NULL,
|
|
||||||
created_at INTEGER NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
break;
|
|
||||||
case "configs":
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE configs (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
is_active INTEGER NOT NULL DEFAULT 1,
|
|
||||||
github_config TEXT NOT NULL,
|
|
||||||
gitea_config TEXT NOT NULL,
|
|
||||||
include TEXT NOT NULL DEFAULT '["*"]',
|
|
||||||
exclude TEXT NOT NULL DEFAULT '[]',
|
|
||||||
schedule_config TEXT NOT NULL,
|
|
||||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
break;
|
|
||||||
case "repositories":
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE repositories (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
config_id TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
full_name TEXT NOT NULL,
|
|
||||||
url TEXT NOT NULL,
|
|
||||||
clone_url TEXT NOT NULL,
|
|
||||||
owner TEXT NOT NULL,
|
|
||||||
organization TEXT,
|
|
||||||
mirrored_location TEXT DEFAULT '',
|
|
||||||
is_private INTEGER NOT NULL DEFAULT 0,
|
|
||||||
is_fork INTEGER NOT NULL DEFAULT 0,
|
|
||||||
forked_from TEXT,
|
|
||||||
has_issues INTEGER NOT NULL DEFAULT 0,
|
|
||||||
is_starred INTEGER NOT NULL DEFAULT 0,
|
|
||||||
is_archived INTEGER NOT NULL DEFAULT 0,
|
|
||||||
size INTEGER NOT NULL DEFAULT 0,
|
|
||||||
has_lfs INTEGER NOT NULL DEFAULT 0,
|
|
||||||
has_submodules INTEGER NOT NULL DEFAULT 0,
|
|
||||||
default_branch TEXT NOT NULL,
|
|
||||||
visibility TEXT NOT NULL DEFAULT 'public',
|
|
||||||
status TEXT NOT NULL DEFAULT 'imported',
|
|
||||||
last_mirrored INTEGER,
|
|
||||||
error_message TEXT,
|
|
||||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
|
||||||
FOREIGN KEY (config_id) REFERENCES configs(id)
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
break;
|
|
||||||
case "organizations":
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE organizations (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
config_id TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
avatar_url TEXT NOT NULL,
|
|
||||||
membership_role TEXT NOT NULL DEFAULT 'member',
|
|
||||||
is_included INTEGER NOT NULL DEFAULT 1,
|
|
||||||
status TEXT NOT NULL DEFAULT 'imported',
|
|
||||||
last_mirrored INTEGER,
|
|
||||||
error_message TEXT,
|
|
||||||
repository_count INTEGER NOT NULL DEFAULT 0,
|
|
||||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
|
||||||
FOREIGN KEY (config_id) REFERENCES configs(id)
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
break;
|
|
||||||
case "mirror_jobs":
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE mirror_jobs (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
repository_id TEXT,
|
|
||||||
repository_name TEXT,
|
|
||||||
organization_id TEXT,
|
|
||||||
organization_name TEXT,
|
|
||||||
details TEXT,
|
|
||||||
status TEXT NOT NULL DEFAULT 'imported',
|
|
||||||
message TEXT NOT NULL,
|
|
||||||
timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
|
|
||||||
-- New fields for job resilience
|
|
||||||
job_type TEXT NOT NULL DEFAULT 'mirror',
|
|
||||||
batch_id TEXT,
|
|
||||||
total_items INTEGER,
|
|
||||||
completed_items INTEGER DEFAULT 0,
|
|
||||||
item_ids TEXT, -- JSON array as text
|
|
||||||
completed_item_ids TEXT DEFAULT '[]', -- JSON array as text
|
|
||||||
in_progress INTEGER NOT NULL DEFAULT 0, -- Boolean as integer
|
|
||||||
started_at TIMESTAMP,
|
|
||||||
completed_at TIMESTAMP,
|
|
||||||
last_checkpoint TIMESTAMP,
|
|
||||||
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Create indexes for better performance
|
|
||||||
db.exec(`
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_mirror_jobs_user_id ON mirror_jobs(user_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_mirror_jobs_batch_id ON mirror_jobs(batch_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_mirror_jobs_in_progress ON mirror_jobs(in_progress);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_mirror_jobs_job_type ON mirror_jobs(job_type);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_mirror_jobs_timestamp ON mirror_jobs(timestamp);
|
|
||||||
`);
|
|
||||||
break;
|
|
||||||
case "events":
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE events (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
channel TEXT NOT NULL,
|
|
||||||
payload TEXT NOT NULL,
|
|
||||||
read INTEGER NOT NULL DEFAULT 0,
|
|
||||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
db.exec(`
|
|
||||||
CREATE INDEX idx_events_user_channel ON events(user_id, channel);
|
|
||||||
CREATE INDEX idx_events_created_at ON events(created_at);
|
|
||||||
CREATE INDEX idx_events_read ON events(read);
|
|
||||||
`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
console.log(`✅ Table '${table}' created successfully.`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`❌ Error checking table '${table}':`, error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migration: Add cleanup_config column to existing configs table
|
// Create SQLite instance
|
||||||
|
const sqlite = new Database(dbPath);
|
||||||
|
const db = drizzle({ client: sqlite });
|
||||||
|
|
||||||
|
// Run migrations
|
||||||
|
console.log("🔄 Running migrations...");
|
||||||
try {
|
try {
|
||||||
const db = new Database(dbPath);
|
migrate(db, { migrationsFolder: "./drizzle" });
|
||||||
|
console.log("✅ Migrations completed successfully");
|
||||||
// Check if cleanup_config column exists
|
|
||||||
const tableInfo = db.query(`PRAGMA table_info(configs)`).all();
|
|
||||||
const hasCleanupConfig = tableInfo.some((column: any) => column.name === 'cleanup_config');
|
|
||||||
|
|
||||||
if (!hasCleanupConfig) {
|
|
||||||
console.log("Adding cleanup_config column to configs table...");
|
|
||||||
|
|
||||||
// Add the column with a default value
|
|
||||||
const defaultCleanupConfig = JSON.stringify({
|
|
||||||
enabled: false,
|
|
||||||
retentionDays: 7,
|
|
||||||
lastRun: null,
|
|
||||||
nextRun: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
db.exec(`ALTER TABLE configs ADD COLUMN cleanup_config TEXT NOT NULL DEFAULT '${defaultCleanupConfig}'`);
|
|
||||||
console.log("✅ cleanup_config column added successfully.");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ Error during cleanup_config migration:", error);
|
console.error("❌ Error running migrations:", error);
|
||||||
// Don't exit here as this is not critical for basic functionality
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sqlite.close();
|
||||||
|
console.log("✅ Database initialized successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check database status
|
* Check database status
|
||||||
*/
|
*/
|
||||||
async function checkDatabase() {
|
async function checkDatabase() {
|
||||||
console.log("Checking database status...");
|
console.log("🔍 Checking database status...");
|
||||||
|
|
||||||
// Check for database files in the root directory (which is incorrect)
|
if (!fs.existsSync(dbPath)) {
|
||||||
if (fs.existsSync(rootDbFile)) {
|
console.log("❌ Database does not exist at:", dbPath);
|
||||||
console.warn(
|
console.log("💡 Run 'bun run init-db' to create the database");
|
||||||
"⚠️ WARNING: Database file found in root directory: gitea-mirror.db"
|
process.exit(1);
|
||||||
);
|
|
||||||
console.warn("This file should be in the data directory.");
|
|
||||||
console.warn(
|
|
||||||
'Run "bun run manage-db fix" to fix this issue or "bun run cleanup-db" to remove it.'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if database files exist in the data directory (which is correct)
|
const sqlite = new Database(dbPath);
|
||||||
if (fs.existsSync(dataDbFile)) {
|
const db = drizzle({ client: sqlite });
|
||||||
console.log(
|
|
||||||
"✅ Database file found in data directory: data/gitea-mirror.db"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check for users
|
|
||||||
try {
|
|
||||||
const db = new Database(dbPath);
|
|
||||||
|
|
||||||
// Check for users
|
|
||||||
const userCountResult = db.query(`SELECT COUNT(*) as count FROM users`).get();
|
|
||||||
const userCount = userCountResult?.count || 0;
|
|
||||||
|
|
||||||
if (userCount === 0) {
|
|
||||||
console.log("ℹ️ No users found in the database.");
|
|
||||||
console.log(
|
|
||||||
"When you start the application, you will be directed to the signup page"
|
|
||||||
);
|
|
||||||
console.log("to create an initial admin account.");
|
|
||||||
} else {
|
|
||||||
console.log(`✅ ${userCount} user(s) found in the database.`);
|
|
||||||
console.log("The application will show the login page on startup.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for configurations
|
|
||||||
const configCountResult = db.query(`SELECT COUNT(*) as count FROM configs`).get();
|
|
||||||
const configCount = configCountResult?.count || 0;
|
|
||||||
|
|
||||||
if (configCount === 0) {
|
|
||||||
console.log("ℹ️ No configurations found in the database.");
|
|
||||||
console.log(
|
|
||||||
"You will need to set up your GitHub and Gitea configurations after login."
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
`✅ ${configCount} configuration(s) found in the database.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ Error connecting to the database:", error);
|
|
||||||
console.warn(
|
|
||||||
'The database file might be corrupted. Consider running "bun run manage-db init" to recreate it.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn("⚠️ WARNING: Database file not found in data directory.");
|
|
||||||
console.warn('Run "bun run manage-db init" to create it.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Database schema updates and migrations have been removed
|
|
||||||
// since the application is not used by anyone yet
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the database
|
|
||||||
*/
|
|
||||||
async function initializeDatabase() {
|
|
||||||
// Check if database already exists first
|
|
||||||
if (fs.existsSync(dataDbFile)) {
|
|
||||||
console.log("⚠️ Database already exists at data/gitea-mirror.db");
|
|
||||||
console.log(
|
|
||||||
'If you want to recreate the database, run "bun run cleanup-db" first.'
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
'Or use "bun run manage-db reset-users" to just remove users without recreating tables.'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Check if we can connect to it
|
|
||||||
try {
|
|
||||||
const db = new Database(dbPath);
|
|
||||||
db.query(`SELECT COUNT(*) as count FROM users`).get();
|
|
||||||
console.log("✅ Database is valid and accessible.");
|
|
||||||
return;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ Error connecting to the existing database:", error);
|
|
||||||
console.log(
|
|
||||||
"The database might be corrupted. Proceeding with reinitialization..."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Initializing database at ${dbPath}...`);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const db = new Database(dbPath);
|
// Check tables
|
||||||
|
const tables = sqlite.query(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
|
||||||
|
).all() as Array<{name: string}>;
|
||||||
|
|
||||||
// Create tables if they don't exist
|
console.log("\n📊 Tables found:");
|
||||||
db.exec(`
|
for (const table of tables) {
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
const count = sqlite.query(`SELECT COUNT(*) as count FROM ${table.name}`).get() as {count: number};
|
||||||
id TEXT PRIMARY KEY,
|
console.log(` - ${table.name}: ${count.count} records`);
|
||||||
username TEXT NOT NULL,
|
}
|
||||||
password TEXT NOT NULL,
|
|
||||||
email TEXT NOT NULL,
|
|
||||||
created_at INTEGER NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// NOTE: We no longer create a default admin user - user will create one via signup page
|
// Check migrations
|
||||||
|
const migrations = sqlite.query(
|
||||||
|
"SELECT * FROM __drizzle_migrations ORDER BY created_at DESC LIMIT 5"
|
||||||
|
).all() as Array<{hash: string, created_at: number}>;
|
||||||
|
|
||||||
db.exec(`
|
if (migrations.length > 0) {
|
||||||
CREATE TABLE IF NOT EXISTS configs (
|
console.log("\n📋 Recent migrations:");
|
||||||
id TEXT PRIMARY KEY,
|
for (const migration of migrations) {
|
||||||
user_id TEXT NOT NULL,
|
const date = new Date(migration.created_at);
|
||||||
name TEXT NOT NULL,
|
console.log(` - ${migration.hash} (${date.toLocaleString()})`);
|
||||||
is_active INTEGER NOT NULL DEFAULT 1,
|
|
||||||
github_config TEXT NOT NULL,
|
|
||||||
gitea_config TEXT NOT NULL,
|
|
||||||
include TEXT NOT NULL DEFAULT '["*"]',
|
|
||||||
exclude TEXT NOT NULL DEFAULT '[]',
|
|
||||||
schedule_config TEXT NOT NULL,
|
|
||||||
cleanup_config TEXT NOT NULL,
|
|
||||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE IF NOT EXISTS repositories (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
config_id TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
full_name TEXT NOT NULL,
|
|
||||||
url TEXT NOT NULL,
|
|
||||||
clone_url TEXT NOT NULL,
|
|
||||||
owner TEXT NOT NULL,
|
|
||||||
organization TEXT,
|
|
||||||
mirrored_location TEXT DEFAULT '',
|
|
||||||
is_private INTEGER NOT NULL DEFAULT 0,
|
|
||||||
is_fork INTEGER NOT NULL DEFAULT 0,
|
|
||||||
forked_from TEXT,
|
|
||||||
has_issues INTEGER NOT NULL DEFAULT 0,
|
|
||||||
is_starred INTEGER NOT NULL DEFAULT 0,
|
|
||||||
is_archived INTEGER NOT NULL DEFAULT 0,
|
|
||||||
size INTEGER NOT NULL DEFAULT 0,
|
|
||||||
has_lfs INTEGER NOT NULL DEFAULT 0,
|
|
||||||
has_submodules INTEGER NOT NULL DEFAULT 0,
|
|
||||||
default_branch TEXT NOT NULL,
|
|
||||||
visibility TEXT NOT NULL DEFAULT 'public',
|
|
||||||
status TEXT NOT NULL DEFAULT 'imported',
|
|
||||||
last_mirrored INTEGER,
|
|
||||||
error_message TEXT,
|
|
||||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
|
||||||
FOREIGN KEY (config_id) REFERENCES configs(id)
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE IF NOT EXISTS organizations (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
config_id TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
avatar_url TEXT NOT NULL,
|
|
||||||
membership_role TEXT NOT NULL DEFAULT 'member',
|
|
||||||
is_included INTEGER NOT NULL DEFAULT 1,
|
|
||||||
status TEXT NOT NULL DEFAULT 'imported',
|
|
||||||
last_mirrored INTEGER,
|
|
||||||
error_message TEXT,
|
|
||||||
repository_count INTEGER NOT NULL DEFAULT 0,
|
|
||||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
|
||||||
FOREIGN KEY (config_id) REFERENCES configs(id)
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE IF NOT EXISTS mirror_jobs (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
repository_id TEXT,
|
|
||||||
repository_name TEXT,
|
|
||||||
organization_id TEXT,
|
|
||||||
organization_name TEXT,
|
|
||||||
details TEXT,
|
|
||||||
status TEXT NOT NULL DEFAULT 'imported',
|
|
||||||
message TEXT NOT NULL,
|
|
||||||
timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE IF NOT EXISTS events (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
channel TEXT NOT NULL,
|
|
||||||
payload TEXT NOT NULL,
|
|
||||||
read INTEGER NOT NULL DEFAULT 0,
|
|
||||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
db.exec(`
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_events_user_channel ON events(user_id, channel);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_events_created_at ON events(created_at);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_events_read ON events(read);
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Insert default config if none exists
|
|
||||||
const configCountResult = db.query(`SELECT COUNT(*) as count FROM configs`).get();
|
|
||||||
const configCount = configCountResult?.count || 0;
|
|
||||||
|
|
||||||
if (configCount === 0) {
|
|
||||||
// Get the first user
|
|
||||||
const firstUserResult = db.query(`SELECT id FROM users LIMIT 1`).get();
|
|
||||||
|
|
||||||
if (firstUserResult) {
|
|
||||||
const userId = firstUserResult.id;
|
|
||||||
const configId = uuidv4();
|
|
||||||
const githubConfig = JSON.stringify({
|
|
||||||
username: process.env.GITHUB_USERNAME || "",
|
|
||||||
token: process.env.GITHUB_TOKEN || "",
|
|
||||||
skipForks: false,
|
|
||||||
privateRepositories: false,
|
|
||||||
mirrorIssues: false,
|
|
||||||
mirrorStarred: true,
|
|
||||||
useSpecificUser: false,
|
|
||||||
preserveOrgStructure: true,
|
|
||||||
skipStarredIssues: false,
|
|
||||||
});
|
|
||||||
const giteaConfig = JSON.stringify({
|
|
||||||
url: process.env.GITEA_URL || "",
|
|
||||||
token: process.env.GITEA_TOKEN || "",
|
|
||||||
username: process.env.GITEA_USERNAME || "",
|
|
||||||
organization: "",
|
|
||||||
visibility: "public",
|
|
||||||
starredReposOrg: "github",
|
|
||||||
});
|
|
||||||
const include = JSON.stringify(["*"]);
|
|
||||||
const exclude = JSON.stringify([]);
|
|
||||||
const scheduleConfig = JSON.stringify({
|
|
||||||
enabled: false,
|
|
||||||
interval: 3600,
|
|
||||||
lastRun: null,
|
|
||||||
nextRun: null,
|
|
||||||
});
|
|
||||||
const cleanupConfig = JSON.stringify({
|
|
||||||
enabled: false,
|
|
||||||
retentionDays: 7,
|
|
||||||
lastRun: null,
|
|
||||||
nextRun: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
const stmt = db.prepare(`
|
|
||||||
INSERT INTO configs (id, user_id, name, is_active, github_config, gitea_config, include, exclude, schedule_config, cleanup_config, created_at, updated_at)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
`);
|
|
||||||
|
|
||||||
stmt.run(
|
|
||||||
configId,
|
|
||||||
userId,
|
|
||||||
"Default Configuration",
|
|
||||||
1,
|
|
||||||
githubConfig,
|
|
||||||
giteaConfig,
|
|
||||||
include,
|
|
||||||
exclude,
|
|
||||||
scheduleConfig,
|
|
||||||
cleanupConfig,
|
|
||||||
Date.now(),
|
|
||||||
Date.now()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("✅ Database initialization completed successfully.");
|
sqlite.close();
|
||||||
|
console.log("\n✅ Database check complete");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ Error initializing database:", error);
|
console.error("❌ Error checking database:", error);
|
||||||
|
sqlite.close();
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset users in the database
|
* Reset user accounts (development only)
|
||||||
*/
|
*/
|
||||||
async function resetUsers() {
|
async function resetUsers() {
|
||||||
console.log(`Resetting users in database at ${dbPath}...`);
|
console.log("🗑️ Resetting all user accounts...");
|
||||||
|
|
||||||
try {
|
if (!fs.existsSync(dbPath)) {
|
||||||
// Check if the database exists
|
console.log("❌ Database does not exist");
|
||||||
const doesDbExist = fs.existsSync(dbPath);
|
|
||||||
|
|
||||||
if (!doesDbExist) {
|
|
||||||
console.log(
|
|
||||||
"❌ Database file doesn't exist. Run 'bun run manage-db init' first to create it."
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const db = new Database(dbPath);
|
|
||||||
|
|
||||||
// Count existing users
|
|
||||||
const userCountResult = db.query(`SELECT COUNT(*) as count FROM users`).get();
|
|
||||||
const userCount = userCountResult?.count || 0;
|
|
||||||
|
|
||||||
if (userCount === 0) {
|
|
||||||
console.log("ℹ️ No users found in the database. Nothing to reset.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete all users
|
|
||||||
db.exec(`DELETE FROM users`);
|
|
||||||
console.log(`✅ Deleted ${userCount} users from the database.`);
|
|
||||||
|
|
||||||
// Check dependent configurations that need to be removed
|
|
||||||
const configCountResult = db.query(`SELECT COUNT(*) as count FROM configs`).get();
|
|
||||||
const configCount = configCountResult?.count || 0;
|
|
||||||
|
|
||||||
if (configCount > 0) {
|
|
||||||
db.exec(`DELETE FROM configs`);
|
|
||||||
console.log(`✅ Deleted ${configCount} configurations.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for dependent repositories
|
|
||||||
const repoCountResult = db.query(`SELECT COUNT(*) as count FROM repositories`).get();
|
|
||||||
const repoCount = repoCountResult?.count || 0;
|
|
||||||
|
|
||||||
if (repoCount > 0) {
|
|
||||||
db.exec(`DELETE FROM repositories`);
|
|
||||||
console.log(`✅ Deleted ${repoCount} repositories.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for dependent organizations
|
|
||||||
const orgCountResult = db.query(`SELECT COUNT(*) as count FROM organizations`).get();
|
|
||||||
const orgCount = orgCountResult?.count || 0;
|
|
||||||
|
|
||||||
if (orgCount > 0) {
|
|
||||||
db.exec(`DELETE FROM organizations`);
|
|
||||||
console.log(`✅ Deleted ${orgCount} organizations.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for dependent mirror jobs
|
|
||||||
const jobCountResult = db.query(`SELECT COUNT(*) as count FROM mirror_jobs`).get();
|
|
||||||
const jobCount = jobCountResult?.count || 0;
|
|
||||||
|
|
||||||
if (jobCount > 0) {
|
|
||||||
db.exec(`DELETE FROM mirror_jobs`);
|
|
||||||
console.log(`✅ Deleted ${jobCount} mirror jobs.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"✅ Database has been reset. The application will now prompt for a new admin account setup on next run."
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ Error resetting users:", error);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const sqlite = new Database(dbPath);
|
||||||
|
const db = drizzle({ client: sqlite });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Delete all data in order of foreign key dependencies
|
||||||
|
await db.delete(events);
|
||||||
|
await db.delete(mirrorJobs);
|
||||||
|
await db.delete(repositories);
|
||||||
|
await db.delete(organizations);
|
||||||
|
await db.delete(configs);
|
||||||
|
await db.delete(users);
|
||||||
|
|
||||||
|
console.log("✅ All user accounts and related data have been removed");
|
||||||
|
|
||||||
|
sqlite.close();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Error resetting users:", error);
|
||||||
|
sqlite.close();
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up database files
|
||||||
|
*/
|
||||||
|
async function cleanupDatabase() {
|
||||||
|
console.log("🧹 Cleaning up database files...");
|
||||||
|
|
||||||
|
const filesToRemove = [
|
||||||
|
dbPath,
|
||||||
|
path.join(dataDir, "gitea-mirror-dev.db"),
|
||||||
|
path.join(process.cwd(), "gitea-mirror.db"),
|
||||||
|
path.join(process.cwd(), "gitea-mirror-dev.db"),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const file of filesToRemove) {
|
||||||
|
if (fs.existsSync(file)) {
|
||||||
|
fs.unlinkSync(file);
|
||||||
|
console.log(` - Removed: ${file}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("✅ Database cleanup complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fix database location issues
|
* Fix database location issues
|
||||||
*/
|
*/
|
||||||
async function fixDatabaseIssues() {
|
async function fixDatabase() {
|
||||||
console.log("Checking for database issues...");
|
console.log("🔧 Fixing database location issues...");
|
||||||
|
|
||||||
// Check for database files in the root directory
|
// Legacy database paths
|
||||||
|
const rootDbFile = path.join(process.cwd(), "gitea-mirror.db");
|
||||||
|
const rootDevDbFile = path.join(process.cwd(), "gitea-mirror-dev.db");
|
||||||
|
const dataDevDbFile = path.join(dataDir, "gitea-mirror-dev.db");
|
||||||
|
|
||||||
|
// Check for databases in wrong locations
|
||||||
if (fs.existsSync(rootDbFile)) {
|
if (fs.existsSync(rootDbFile)) {
|
||||||
console.log("Found database file in root directory: gitea-mirror.db");
|
console.log("📁 Found database in root directory");
|
||||||
|
if (!fs.existsSync(dbPath)) {
|
||||||
// If the data directory doesn't have the file, move it there
|
console.log(" → Moving to data directory...");
|
||||||
if (!fs.existsSync(dataDbFile)) {
|
fs.renameSync(rootDbFile, dbPath);
|
||||||
console.log("Moving database file to data directory...");
|
console.log("✅ Database moved successfully");
|
||||||
fs.copyFileSync(rootDbFile, dataDbFile);
|
|
||||||
console.log("Database file moved successfully.");
|
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.log(" ⚠️ Database already exists in data directory");
|
||||||
"Database file already exists in data directory. Checking for differences..."
|
console.log(" → Keeping existing data directory database");
|
||||||
);
|
fs.unlinkSync(rootDbFile);
|
||||||
|
console.log(" → Removed root directory database");
|
||||||
// Compare file sizes to see which is newer/larger
|
|
||||||
const rootStats = fs.statSync(rootDbFile);
|
|
||||||
const dataStats = fs.statSync(dataDbFile);
|
|
||||||
|
|
||||||
if (
|
|
||||||
rootStats.size > dataStats.size ||
|
|
||||||
rootStats.mtime > dataStats.mtime
|
|
||||||
) {
|
|
||||||
console.log(
|
|
||||||
"Root database file is newer or larger. Backing up data directory file and replacing it..."
|
|
||||||
);
|
|
||||||
fs.copyFileSync(dataDbFile, `${dataDbFile}.backup-${Date.now()}`);
|
|
||||||
fs.copyFileSync(rootDbFile, dataDbFile);
|
|
||||||
console.log("Database file replaced successfully.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the root file
|
|
||||||
console.log("Removing database file from root directory...");
|
|
||||||
fs.unlinkSync(rootDbFile);
|
|
||||||
console.log("Root database file removed.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do the same for dev database
|
// Clean up dev databases
|
||||||
if (fs.existsSync(rootDevDbFile)) {
|
if (fs.existsSync(rootDevDbFile)) {
|
||||||
console.log(
|
|
||||||
"Found development database file in root directory: gitea-mirror-dev.db"
|
|
||||||
);
|
|
||||||
|
|
||||||
// If the data directory doesn't have the file, move it there
|
|
||||||
if (!fs.existsSync(dataDevDbFile)) {
|
|
||||||
console.log("Moving development database file to data directory...");
|
|
||||||
fs.copyFileSync(rootDevDbFile, dataDevDbFile);
|
|
||||||
console.log("Development database file moved successfully.");
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
"Development database file already exists in data directory. Checking for differences..."
|
|
||||||
);
|
|
||||||
|
|
||||||
// Compare file sizes to see which is newer/larger
|
|
||||||
const rootStats = fs.statSync(rootDevDbFile);
|
|
||||||
const dataStats = fs.statSync(dataDevDbFile);
|
|
||||||
|
|
||||||
if (
|
|
||||||
rootStats.size > dataStats.size ||
|
|
||||||
rootStats.mtime > dataStats.mtime
|
|
||||||
) {
|
|
||||||
console.log(
|
|
||||||
"Root development database file is newer or larger. Backing up data directory file and replacing it..."
|
|
||||||
);
|
|
||||||
fs.copyFileSync(dataDevDbFile, `${dataDevDbFile}.backup-${Date.now()}`);
|
|
||||||
fs.copyFileSync(rootDevDbFile, dataDevDbFile);
|
|
||||||
console.log("Development database file replaced successfully.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the root file
|
|
||||||
console.log("Removing development database file from root directory...");
|
|
||||||
fs.unlinkSync(rootDevDbFile);
|
fs.unlinkSync(rootDevDbFile);
|
||||||
console.log("Root development database file removed.");
|
console.log(" → Removed root dev database");
|
||||||
|
}
|
||||||
|
if (fs.existsSync(dataDevDbFile)) {
|
||||||
|
fs.unlinkSync(dataDevDbFile);
|
||||||
|
console.log(" → Removed data dev database");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if database files exist in the data directory
|
console.log("✅ Database location fixed");
|
||||||
if (!fs.existsSync(dataDbFile)) {
|
|
||||||
console.warn(
|
|
||||||
"⚠️ WARNING: Production database file not found in data directory."
|
|
||||||
);
|
|
||||||
console.warn('Run "bun run manage-db init" to create it.');
|
|
||||||
} else {
|
|
||||||
console.log("✅ Production database file found in data directory.");
|
|
||||||
|
|
||||||
// Check if we can connect to the database
|
|
||||||
try {
|
|
||||||
// Try to query the database
|
|
||||||
const db = new Database(dbPath);
|
|
||||||
db.query(`SELECT 1 FROM sqlite_master LIMIT 1`).get();
|
|
||||||
console.log(`✅ Successfully connected to the database.`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("❌ Error connecting to the database:", error);
|
|
||||||
console.warn(
|
|
||||||
'The database file might be corrupted. Consider running "bun run manage-db init" to recreate it.'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Database check completed.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main function to handle the command
|
* Auto mode - check and initialize if needed
|
||||||
*/
|
*/
|
||||||
async function main() {
|
async function autoMode() {
|
||||||
console.log(`Database Management Tool for Gitea Mirror`);
|
if (!fs.existsSync(dbPath)) {
|
||||||
|
console.log("📦 Database not found, initializing...");
|
||||||
// Ensure all required tables exist
|
await initDatabase();
|
||||||
console.log("Ensuring all required tables exist...");
|
} else {
|
||||||
await ensureTablesExist();
|
console.log("✅ Database already exists");
|
||||||
|
await checkDatabase();
|
||||||
switch (command) {
|
|
||||||
case "check":
|
|
||||||
await checkDatabase();
|
|
||||||
break;
|
|
||||||
case "init":
|
|
||||||
await initializeDatabase();
|
|
||||||
break;
|
|
||||||
case "fix":
|
|
||||||
await fixDatabaseIssues();
|
|
||||||
break;
|
|
||||||
case "reset-users":
|
|
||||||
await resetUsers();
|
|
||||||
break;
|
|
||||||
case "auto":
|
|
||||||
// Auto mode: check, fix, and initialize if needed
|
|
||||||
console.log("Running in auto mode: check, fix, and initialize if needed");
|
|
||||||
await fixDatabaseIssues();
|
|
||||||
|
|
||||||
if (!fs.existsSync(dataDbFile)) {
|
|
||||||
await initializeDatabase();
|
|
||||||
} else {
|
|
||||||
await checkDatabase();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.log(`
|
|
||||||
Available commands:
|
|
||||||
check - Check database status
|
|
||||||
init - Initialize the database (only if it doesn't exist)
|
|
||||||
fix - Fix database location issues
|
|
||||||
reset-users - Remove all users and their data
|
|
||||||
auto - Automatic mode: check, fix, and initialize if needed
|
|
||||||
|
|
||||||
Usage: bun run manage-db [command]
|
|
||||||
`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((error) => {
|
// Execute command
|
||||||
console.error("Error during database management:", error);
|
switch (command) {
|
||||||
process.exit(1);
|
case "init":
|
||||||
});
|
await initDatabase();
|
||||||
|
break;
|
||||||
|
case "check":
|
||||||
|
await checkDatabase();
|
||||||
|
break;
|
||||||
|
case "fix":
|
||||||
|
await fixDatabase();
|
||||||
|
break;
|
||||||
|
case "reset-users":
|
||||||
|
await resetUsers();
|
||||||
|
break;
|
||||||
|
case "cleanup":
|
||||||
|
await cleanupDatabase();
|
||||||
|
break;
|
||||||
|
case "auto":
|
||||||
|
await autoMode();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("Available commands:");
|
||||||
|
console.log(" init - Initialize database with migrations");
|
||||||
|
console.log(" check - Check database status");
|
||||||
|
console.log(" fix - Fix database location issues");
|
||||||
|
console.log(" reset-users - Remove all users and related data");
|
||||||
|
console.log(" cleanup - Remove all database files");
|
||||||
|
console.log(" auto - Auto initialize if needed");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
|
|
||||||
import { Database } from "bun:sqlite";
|
import { Database } from "bun:sqlite";
|
||||||
import { drizzle } from "drizzle-orm/bun-sqlite";
|
import { drizzle } from "drizzle-orm/bun-sqlite";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { configSchema } from "./schema";
|
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
|
||||||
|
|
||||||
// Define the database URL - for development we'll use a local SQLite file
|
// Define the database URL - for development we'll use a local SQLite file
|
||||||
const dataDir = path.join(process.cwd(), "data");
|
const dataDir = path.join(process.cwd(), "data");
|
||||||
@@ -26,464 +24,41 @@ try {
|
|||||||
sqlite = new Database(dbPath);
|
sqlite = new Database(dbPath);
|
||||||
console.log("Successfully connected to SQLite database using Bun's native driver");
|
console.log("Successfully connected to SQLite database using Bun's native driver");
|
||||||
|
|
||||||
// Ensure all required tables exist
|
// Run Drizzle migrations if needed
|
||||||
ensureTablesExist(sqlite);
|
runDrizzleMigrations();
|
||||||
|
|
||||||
// Run migrations
|
|
||||||
runMigrations(sqlite);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error opening database:", error);
|
console.error("Error opening database:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Run database migrations
|
* Run Drizzle migrations
|
||||||
*/
|
*/
|
||||||
function runMigrations(db: Database) {
|
function runDrizzleMigrations() {
|
||||||
try {
|
try {
|
||||||
// Migration 1: Add destination_org column to organizations table
|
console.log("🔄 Checking for pending migrations...");
|
||||||
const orgTableInfo = db.query("PRAGMA table_info(organizations)").all() as Array<{name: string}>;
|
|
||||||
const hasDestinationOrg = orgTableInfo.some(col => col.name === 'destination_org');
|
|
||||||
|
|
||||||
if (!hasDestinationOrg) {
|
// Check if migrations table exists
|
||||||
console.log("🔄 Running migration: Adding destination_org column to organizations table");
|
const migrationsTableExists = sqlite
|
||||||
db.exec("ALTER TABLE organizations ADD COLUMN destination_org TEXT");
|
.query("SELECT name FROM sqlite_master WHERE type='table' AND name='__drizzle_migrations'")
|
||||||
console.log("✅ Migration completed: destination_org column added");
|
.get();
|
||||||
|
|
||||||
|
if (!migrationsTableExists) {
|
||||||
|
console.log("📦 First time setup - running initial migrations...");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migration 2: Add destination_org column to repositories table
|
// Run migrations using Drizzle migrate function
|
||||||
const repoTableInfo = db.query("PRAGMA table_info(repositories)").all() as Array<{name: string}>;
|
migrate(db, { migrationsFolder: "./drizzle" });
|
||||||
const hasRepoDestinationOrg = repoTableInfo.some(col => col.name === 'destination_org');
|
|
||||||
|
|
||||||
if (!hasRepoDestinationOrg) {
|
console.log("✅ Database migrations completed successfully");
|
||||||
console.log("🔄 Running migration: Adding destination_org column to repositories table");
|
|
||||||
db.exec("ALTER TABLE repositories ADD COLUMN destination_org TEXT");
|
|
||||||
console.log("✅ Migration completed: destination_org column added to repositories");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("❌ Error running migrations:", error);
|
console.error("❌ Error running migrations:", error);
|
||||||
// Don't throw - migrations should be non-breaking
|
throw error;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure all required tables exist in the database
|
|
||||||
*/
|
|
||||||
function ensureTablesExist(db: Database) {
|
|
||||||
const requiredTables = [
|
|
||||||
"users",
|
|
||||||
"configs",
|
|
||||||
"repositories",
|
|
||||||
"organizations",
|
|
||||||
"mirror_jobs",
|
|
||||||
"events",
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const table of requiredTables) {
|
|
||||||
try {
|
|
||||||
// Check if table exists
|
|
||||||
const result = db.query(`SELECT name FROM sqlite_master WHERE type='table' AND name='${table}'`).get();
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
console.warn(`⚠️ Table '${table}' is missing. Creating it now...`);
|
|
||||||
createTable(db, table);
|
|
||||||
console.log(`✅ Table '${table}' created successfully`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`❌ Error checking/creating table '${table}':`, error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a specific table with its schema
|
|
||||||
*/
|
|
||||||
function createTable(db: Database, tableName: string) {
|
|
||||||
switch (tableName) {
|
|
||||||
case "users":
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE users (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
username TEXT NOT NULL,
|
|
||||||
password TEXT NOT NULL,
|
|
||||||
email TEXT NOT NULL,
|
|
||||||
created_at INTEGER NOT NULL,
|
|
||||||
updated_at INTEGER NOT NULL
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "configs":
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE configs (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
is_active INTEGER NOT NULL DEFAULT 1,
|
|
||||||
github_config TEXT NOT NULL,
|
|
||||||
gitea_config TEXT NOT NULL,
|
|
||||||
include TEXT NOT NULL DEFAULT '["*"]',
|
|
||||||
exclude TEXT NOT NULL DEFAULT '[]',
|
|
||||||
schedule_config TEXT NOT NULL,
|
|
||||||
cleanup_config TEXT NOT NULL,
|
|
||||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "repositories":
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE repositories (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
config_id TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
full_name TEXT NOT NULL,
|
|
||||||
url TEXT NOT NULL,
|
|
||||||
clone_url TEXT NOT NULL,
|
|
||||||
owner TEXT NOT NULL,
|
|
||||||
organization TEXT,
|
|
||||||
mirrored_location TEXT DEFAULT '',
|
|
||||||
is_private INTEGER NOT NULL DEFAULT 0,
|
|
||||||
is_fork INTEGER NOT NULL DEFAULT 0,
|
|
||||||
forked_from TEXT,
|
|
||||||
has_issues INTEGER NOT NULL DEFAULT 0,
|
|
||||||
is_starred INTEGER NOT NULL DEFAULT 0,
|
|
||||||
language TEXT,
|
|
||||||
description TEXT,
|
|
||||||
default_branch TEXT NOT NULL,
|
|
||||||
visibility TEXT NOT NULL DEFAULT 'public',
|
|
||||||
status TEXT NOT NULL DEFAULT 'imported',
|
|
||||||
last_mirrored INTEGER,
|
|
||||||
error_message TEXT,
|
|
||||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
|
||||||
FOREIGN KEY (config_id) REFERENCES configs(id)
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Create indexes for repositories
|
|
||||||
db.exec(`
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_repositories_user_id ON repositories(user_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_repositories_config_id ON repositories(config_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_repositories_status ON repositories(status);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_repositories_owner ON repositories(owner);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_repositories_organization ON repositories(organization);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_repositories_is_fork ON repositories(is_fork);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_repositories_is_starred ON repositories(is_starred);
|
|
||||||
`);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "organizations":
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE organizations (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
config_id TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
avatar_url TEXT NOT NULL,
|
|
||||||
membership_role TEXT NOT NULL DEFAULT 'member',
|
|
||||||
is_included INTEGER NOT NULL DEFAULT 1,
|
|
||||||
status TEXT NOT NULL DEFAULT 'imported',
|
|
||||||
last_mirrored INTEGER,
|
|
||||||
error_message TEXT,
|
|
||||||
repository_count INTEGER NOT NULL DEFAULT 0,
|
|
||||||
destination_org TEXT,
|
|
||||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id),
|
|
||||||
FOREIGN KEY (config_id) REFERENCES configs(id)
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Create indexes for organizations
|
|
||||||
db.exec(`
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_organizations_user_id ON organizations(user_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_organizations_config_id ON organizations(config_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_organizations_status ON organizations(status);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_organizations_is_included ON organizations(is_included);
|
|
||||||
`);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "mirror_jobs":
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE mirror_jobs (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
repository_id TEXT,
|
|
||||||
repository_name TEXT,
|
|
||||||
organization_id TEXT,
|
|
||||||
organization_name TEXT,
|
|
||||||
details TEXT,
|
|
||||||
status TEXT NOT NULL DEFAULT 'imported',
|
|
||||||
message TEXT NOT NULL,
|
|
||||||
timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
|
|
||||||
-- New fields for job resilience
|
|
||||||
job_type TEXT NOT NULL DEFAULT 'mirror',
|
|
||||||
batch_id TEXT,
|
|
||||||
total_items INTEGER,
|
|
||||||
completed_items INTEGER DEFAULT 0,
|
|
||||||
item_ids TEXT, -- JSON array as text
|
|
||||||
completed_item_ids TEXT DEFAULT '[]', -- JSON array as text
|
|
||||||
in_progress INTEGER NOT NULL DEFAULT 0, -- Boolean as integer
|
|
||||||
started_at TIMESTAMP,
|
|
||||||
completed_at TIMESTAMP,
|
|
||||||
last_checkpoint TIMESTAMP,
|
|
||||||
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Create indexes for mirror_jobs
|
|
||||||
db.exec(`
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_mirror_jobs_user_id ON mirror_jobs(user_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_mirror_jobs_batch_id ON mirror_jobs(batch_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_mirror_jobs_in_progress ON mirror_jobs(in_progress);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_mirror_jobs_job_type ON mirror_jobs(job_type);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_mirror_jobs_timestamp ON mirror_jobs(timestamp);
|
|
||||||
`);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "events":
|
|
||||||
db.exec(`
|
|
||||||
CREATE TABLE events (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
channel TEXT NOT NULL,
|
|
||||||
payload TEXT NOT NULL,
|
|
||||||
read INTEGER NOT NULL DEFAULT 0,
|
|
||||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
||||||
)
|
|
||||||
`);
|
|
||||||
|
|
||||||
// Create indexes for events
|
|
||||||
db.exec(`
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_events_user_channel ON events(user_id, channel);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_events_created_at ON events(created_at);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_events_read ON events(read);
|
|
||||||
`);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown table: ${tableName}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create drizzle instance with the SQLite client
|
// Create drizzle instance with the SQLite client
|
||||||
export const db = drizzle({ client: sqlite });
|
export const db = drizzle({ client: sqlite });
|
||||||
|
|
||||||
// Simple async wrapper around SQLite API for compatibility
|
// Export all table definitions from schema
|
||||||
// This maintains backward compatibility with existing code
|
export { users, events, configs, repositories, mirrorJobs, organizations } from "./schema";
|
||||||
export const client = {
|
|
||||||
async execute(sql: string, params?: any[]) {
|
|
||||||
try {
|
|
||||||
const stmt = sqlite.query(sql);
|
|
||||||
if (/^\s*select/i.test(sql)) {
|
|
||||||
const rows = stmt.all(params ?? []);
|
|
||||||
return { rows } as { rows: any[] };
|
|
||||||
}
|
|
||||||
stmt.run(params ?? []);
|
|
||||||
return { rows: [] } as { rows: any[] };
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error executing SQL: ${sql}`, error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Define the tables
|
|
||||||
export const users = sqliteTable("users", {
|
|
||||||
id: text("id").primaryKey(),
|
|
||||||
username: text("username").notNull(),
|
|
||||||
password: text("password").notNull(),
|
|
||||||
email: text("email").notNull(),
|
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
|
||||||
.notNull()
|
|
||||||
.default(new Date()),
|
|
||||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
|
||||||
.notNull()
|
|
||||||
.default(new Date()),
|
|
||||||
});
|
|
||||||
|
|
||||||
// New table for event notifications (replacing Redis pub/sub)
|
|
||||||
export const events = sqliteTable("events", {
|
|
||||||
id: text("id").primaryKey(),
|
|
||||||
userId: text("user_id").notNull().references(() => users.id),
|
|
||||||
channel: text("channel").notNull(),
|
|
||||||
payload: text("payload", { mode: "json" }).notNull(),
|
|
||||||
read: integer("read", { mode: "boolean" }).notNull().default(false),
|
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
|
||||||
.notNull()
|
|
||||||
.default(new Date()),
|
|
||||||
});
|
|
||||||
|
|
||||||
const githubSchema = configSchema.shape.githubConfig;
|
|
||||||
const giteaSchema = configSchema.shape.giteaConfig;
|
|
||||||
const scheduleSchema = configSchema.shape.scheduleConfig;
|
|
||||||
const cleanupSchema = configSchema.shape.cleanupConfig;
|
|
||||||
|
|
||||||
export const configs = sqliteTable("configs", {
|
|
||||||
id: text("id").primaryKey(),
|
|
||||||
userId: text("user_id")
|
|
||||||
.notNull()
|
|
||||||
.references(() => users.id),
|
|
||||||
name: text("name").notNull(),
|
|
||||||
isActive: integer("is_active", { mode: "boolean" }).notNull().default(true),
|
|
||||||
|
|
||||||
githubConfig: text("github_config", { mode: "json" })
|
|
||||||
.$type<z.infer<typeof githubSchema>>()
|
|
||||||
.notNull(),
|
|
||||||
|
|
||||||
giteaConfig: text("gitea_config", { mode: "json" })
|
|
||||||
.$type<z.infer<typeof giteaSchema>>()
|
|
||||||
.notNull(),
|
|
||||||
|
|
||||||
include: text("include", { mode: "json" })
|
|
||||||
.$type<string[]>()
|
|
||||||
.notNull()
|
|
||||||
.default(["*"]),
|
|
||||||
|
|
||||||
exclude: text("exclude", { mode: "json" })
|
|
||||||
.$type<string[]>()
|
|
||||||
.notNull()
|
|
||||||
.default([]),
|
|
||||||
|
|
||||||
scheduleConfig: text("schedule_config", { mode: "json" })
|
|
||||||
.$type<z.infer<typeof scheduleSchema>>()
|
|
||||||
.notNull(),
|
|
||||||
|
|
||||||
cleanupConfig: text("cleanup_config", { mode: "json" })
|
|
||||||
.$type<z.infer<typeof cleanupSchema>>()
|
|
||||||
.notNull(),
|
|
||||||
|
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
|
||||||
.notNull()
|
|
||||||
.default(new Date()),
|
|
||||||
|
|
||||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
|
||||||
.notNull()
|
|
||||||
.default(new Date()),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const repositories = sqliteTable("repositories", {
|
|
||||||
id: text("id").primaryKey(),
|
|
||||||
userId: text("user_id")
|
|
||||||
.notNull()
|
|
||||||
.references(() => users.id),
|
|
||||||
configId: text("config_id")
|
|
||||||
.notNull()
|
|
||||||
.references(() => configs.id),
|
|
||||||
name: text("name").notNull(),
|
|
||||||
fullName: text("full_name").notNull(),
|
|
||||||
url: text("url").notNull(),
|
|
||||||
cloneUrl: text("clone_url").notNull(),
|
|
||||||
owner: text("owner").notNull(),
|
|
||||||
organization: text("organization"),
|
|
||||||
mirroredLocation: text("mirrored_location").default(""),
|
|
||||||
|
|
||||||
isPrivate: integer("is_private", { mode: "boolean" })
|
|
||||||
.notNull()
|
|
||||||
.default(false),
|
|
||||||
isForked: integer("is_fork", { mode: "boolean" }).notNull().default(false),
|
|
||||||
forkedFrom: text("forked_from"),
|
|
||||||
|
|
||||||
hasIssues: integer("has_issues", { mode: "boolean" })
|
|
||||||
.notNull()
|
|
||||||
.default(false),
|
|
||||||
isStarred: integer("is_starred", { mode: "boolean" })
|
|
||||||
.notNull()
|
|
||||||
.default(false),
|
|
||||||
isArchived: integer("is_archived", { mode: "boolean" })
|
|
||||||
.notNull()
|
|
||||||
.default(false),
|
|
||||||
|
|
||||||
size: integer("size").notNull().default(0),
|
|
||||||
hasLFS: integer("has_lfs", { mode: "boolean" }).notNull().default(false),
|
|
||||||
hasSubmodules: integer("has_submodules", { mode: "boolean" })
|
|
||||||
.notNull()
|
|
||||||
.default(false),
|
|
||||||
|
|
||||||
defaultBranch: text("default_branch").notNull(),
|
|
||||||
visibility: text("visibility").notNull().default("public"),
|
|
||||||
|
|
||||||
status: text("status").notNull().default("imported"),
|
|
||||||
lastMirrored: integer("last_mirrored", { mode: "timestamp" }),
|
|
||||||
errorMessage: text("error_message"),
|
|
||||||
|
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
|
||||||
.notNull()
|
|
||||||
.default(new Date()),
|
|
||||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
|
||||||
.notNull()
|
|
||||||
.default(new Date()),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const mirrorJobs = sqliteTable("mirror_jobs", {
|
|
||||||
id: text("id").primaryKey(),
|
|
||||||
userId: text("user_id")
|
|
||||||
.notNull()
|
|
||||||
.references(() => users.id),
|
|
||||||
repositoryId: text("repository_id"),
|
|
||||||
repositoryName: text("repository_name"),
|
|
||||||
organizationId: text("organization_id"),
|
|
||||||
organizationName: text("organization_name"),
|
|
||||||
details: text("details"),
|
|
||||||
status: text("status").notNull().default("imported"),
|
|
||||||
message: text("message").notNull(),
|
|
||||||
timestamp: integer("timestamp", { mode: "timestamp" })
|
|
||||||
.notNull()
|
|
||||||
.default(new Date()),
|
|
||||||
|
|
||||||
// New fields for job resilience
|
|
||||||
jobType: text("job_type").notNull().default("mirror"),
|
|
||||||
batchId: text("batch_id"),
|
|
||||||
totalItems: integer("total_items"),
|
|
||||||
completedItems: integer("completed_items").default(0),
|
|
||||||
itemIds: text("item_ids", { mode: "json" }).$type<string[]>(),
|
|
||||||
completedItemIds: text("completed_item_ids", { mode: "json" }).$type<string[]>().default([]),
|
|
||||||
inProgress: integer("in_progress", { mode: "boolean" }).notNull().default(false),
|
|
||||||
startedAt: integer("started_at", { mode: "timestamp" }),
|
|
||||||
completedAt: integer("completed_at", { mode: "timestamp" }),
|
|
||||||
lastCheckpoint: integer("last_checkpoint", { mode: "timestamp" }),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const organizations = sqliteTable("organizations", {
|
|
||||||
id: text("id").primaryKey(),
|
|
||||||
userId: text("user_id")
|
|
||||||
.notNull()
|
|
||||||
.references(() => users.id),
|
|
||||||
configId: text("config_id")
|
|
||||||
.notNull()
|
|
||||||
.references(() => configs.id),
|
|
||||||
name: text("name").notNull(),
|
|
||||||
|
|
||||||
avatarUrl: text("avatar_url").notNull(),
|
|
||||||
|
|
||||||
membershipRole: text("membership_role").notNull().default("member"),
|
|
||||||
|
|
||||||
isIncluded: integer("is_included", { mode: "boolean" })
|
|
||||||
.notNull()
|
|
||||||
.default(true),
|
|
||||||
|
|
||||||
// Override destination organization for this GitHub org's repos
|
|
||||||
destinationOrg: text("destination_org"),
|
|
||||||
|
|
||||||
status: text("status").notNull().default("imported"),
|
|
||||||
lastMirrored: integer("last_mirrored", { mode: "timestamp" }),
|
|
||||||
errorMessage: text("error_message"),
|
|
||||||
|
|
||||||
repositoryCount: integer("repository_count").notNull().default(0),
|
|
||||||
|
|
||||||
createdAt: integer("created_at", { mode: "timestamp" })
|
|
||||||
.notNull()
|
|
||||||
.default(new Date()),
|
|
||||||
updatedAt: integer("updated_at", { mode: "timestamp" })
|
|
||||||
.notNull()
|
|
||||||
.default(new Date()),
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
-- Users table
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
username TEXT NOT NULL UNIQUE,
|
|
||||||
password TEXT NOT NULL,
|
|
||||||
email TEXT NOT NULL,
|
|
||||||
created_at DATETIME NOT NULL,
|
|
||||||
updated_at DATETIME NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Configurations table
|
|
||||||
CREATE TABLE IF NOT EXISTS configs (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
user_id TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
is_active BOOLEAN NOT NULL DEFAULT 1,
|
|
||||||
github_config TEXT NOT NULL,
|
|
||||||
gitea_config TEXT NOT NULL,
|
|
||||||
schedule_config TEXT NOT NULL,
|
|
||||||
include TEXT NOT NULL,
|
|
||||||
exclude TEXT NOT NULL,
|
|
||||||
created_at DATETIME NOT NULL,
|
|
||||||
updated_at DATETIME NOT NULL,
|
|
||||||
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Repositories table
|
|
||||||
CREATE TABLE IF NOT EXISTS repositories (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
config_id TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
full_name TEXT NOT NULL,
|
|
||||||
url TEXT NOT NULL,
|
|
||||||
is_private BOOLEAN NOT NULL,
|
|
||||||
is_fork BOOLEAN NOT NULL,
|
|
||||||
owner TEXT NOT NULL,
|
|
||||||
organization TEXT,
|
|
||||||
mirrored_location TEXT DEFAULT '',
|
|
||||||
has_issues BOOLEAN NOT NULL,
|
|
||||||
is_starred BOOLEAN NOT NULL,
|
|
||||||
status TEXT NOT NULL,
|
|
||||||
error_message TEXT,
|
|
||||||
last_mirrored DATETIME,
|
|
||||||
created_at DATETIME NOT NULL,
|
|
||||||
updated_at DATETIME NOT NULL,
|
|
||||||
FOREIGN KEY (config_id) REFERENCES configs (id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Organizations table
|
|
||||||
CREATE TABLE IF NOT EXISTS organizations (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
config_id TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
type TEXT NOT NULL,
|
|
||||||
is_included BOOLEAN NOT NULL,
|
|
||||||
repository_count INTEGER NOT NULL,
|
|
||||||
created_at DATETIME NOT NULL,
|
|
||||||
updated_at DATETIME NOT NULL,
|
|
||||||
FOREIGN KEY (config_id) REFERENCES configs (id) ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- Mirror jobs table
|
|
||||||
CREATE TABLE IF NOT EXISTS mirror_jobs (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
config_id TEXT NOT NULL,
|
|
||||||
repository_id TEXT,
|
|
||||||
status TEXT NOT NULL,
|
|
||||||
started_at DATETIME NOT NULL,
|
|
||||||
completed_at DATETIME,
|
|
||||||
log TEXT NOT NULL,
|
|
||||||
created_at DATETIME NOT NULL,
|
|
||||||
updated_at DATETIME NOT NULL,
|
|
||||||
FOREIGN KEY (config_id) REFERENCES configs (id) ON DELETE CASCADE,
|
|
||||||
FOREIGN KEY (repository_id) REFERENCES repositories (id) ON DELETE SET NULL
|
|
||||||
);
|
|
||||||
@@ -1,182 +1,443 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { repositoryVisibilityEnum, repoStatusEnum } from "@/types/Repository";
|
import { sqliteTable, text, integer, index } from "drizzle-orm/sqlite-core";
|
||||||
import { membershipRoleEnum } from "@/types/organizations";
|
import { sql } from "drizzle-orm";
|
||||||
|
|
||||||
// User schema
|
// ===== Zod Validation Schemas =====
|
||||||
export const userSchema = z.object({
|
export const userSchema = z.object({
|
||||||
id: z.string().uuid().optional(),
|
id: z.string(),
|
||||||
username: z.string().min(3),
|
username: z.string(),
|
||||||
password: z.string().min(8).optional(), // Hashed password
|
password: z.string(),
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
createdAt: z.date().default(() => new Date()),
|
createdAt: z.coerce.date(),
|
||||||
updatedAt: z.date().default(() => new Date()),
|
updatedAt: z.coerce.date(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type User = z.infer<typeof userSchema>;
|
export const githubConfigSchema = z.object({
|
||||||
|
owner: z.string(),
|
||||||
|
type: z.enum(["personal", "organization"]),
|
||||||
|
token: z.string(),
|
||||||
|
includeStarred: z.boolean().default(false),
|
||||||
|
includeForks: z.boolean().default(true),
|
||||||
|
includeArchived: z.boolean().default(false),
|
||||||
|
includePrivate: z.boolean().default(true),
|
||||||
|
includePublic: z.boolean().default(true),
|
||||||
|
includeOrganizations: z.array(z.string()).default([]),
|
||||||
|
starredReposOrg: z.string().optional(),
|
||||||
|
mirrorStrategy: z.enum(["preserve", "single-org", "flat-user"]).default("preserve"),
|
||||||
|
defaultOrg: z.string().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const giteaConfigSchema = z.object({
|
||||||
|
url: z.string().url(),
|
||||||
|
token: z.string(),
|
||||||
|
defaultOwner: z.string(),
|
||||||
|
mirrorInterval: z.string().default("8h"),
|
||||||
|
lfs: z.boolean().default(false),
|
||||||
|
wiki: z.boolean().default(false),
|
||||||
|
visibility: z
|
||||||
|
.enum(["public", "private", "limited", "default"])
|
||||||
|
.default("default"),
|
||||||
|
createOrg: z.boolean().default(true),
|
||||||
|
templateOwner: z.string().optional(),
|
||||||
|
templateRepo: z.string().optional(),
|
||||||
|
addTopics: z.boolean().default(true),
|
||||||
|
topicPrefix: z.string().optional(),
|
||||||
|
preserveVisibility: z.boolean().default(true),
|
||||||
|
forkStrategy: z
|
||||||
|
.enum(["skip", "reference", "full-copy"])
|
||||||
|
.default("reference"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const scheduleConfigSchema = z.object({
|
||||||
|
enabled: z.boolean().default(false),
|
||||||
|
interval: z.string().default("0 2 * * *"),
|
||||||
|
concurrent: z.boolean().default(false),
|
||||||
|
batchSize: z.number().default(10),
|
||||||
|
pauseBetweenBatches: z.number().default(5000),
|
||||||
|
retryAttempts: z.number().default(3),
|
||||||
|
retryDelay: z.number().default(60000),
|
||||||
|
timeout: z.number().default(3600000),
|
||||||
|
autoRetry: z.boolean().default(true),
|
||||||
|
cleanupBeforeMirror: z.boolean().default(false),
|
||||||
|
notifyOnFailure: z.boolean().default(true),
|
||||||
|
notifyOnSuccess: z.boolean().default(false),
|
||||||
|
logLevel: z.enum(["error", "warn", "info", "debug"]).default("info"),
|
||||||
|
timezone: z.string().default("UTC"),
|
||||||
|
onlyMirrorUpdated: z.boolean().default(false),
|
||||||
|
updateInterval: z.number().default(86400000),
|
||||||
|
skipRecentlyMirrored: z.boolean().default(true),
|
||||||
|
recentThreshold: z.number().default(3600000),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const cleanupConfigSchema = z.object({
|
||||||
|
enabled: z.boolean().default(false),
|
||||||
|
deleteFromGitea: z.boolean().default(false),
|
||||||
|
deleteIfNotInGitHub: z.boolean().default(true),
|
||||||
|
protectedRepos: z.array(z.string()).default([]),
|
||||||
|
dryRun: z.boolean().default(true),
|
||||||
|
orphanedRepoAction: z
|
||||||
|
.enum(["skip", "archive", "delete"])
|
||||||
|
.default("archive"),
|
||||||
|
batchSize: z.number().default(10),
|
||||||
|
pauseBetweenDeletes: z.number().default(2000),
|
||||||
|
});
|
||||||
|
|
||||||
// Configuration schema
|
|
||||||
export const configSchema = z.object({
|
export const configSchema = z.object({
|
||||||
id: z.string().uuid().optional(),
|
id: z.string(),
|
||||||
userId: z.string().uuid(),
|
userId: z.string(),
|
||||||
name: z.string().min(1),
|
name: z.string(),
|
||||||
isActive: z.boolean().default(true),
|
isActive: z.boolean().default(true),
|
||||||
githubConfig: z.object({
|
githubConfig: githubConfigSchema,
|
||||||
username: z.string().min(1),
|
giteaConfig: giteaConfigSchema,
|
||||||
token: z.string().optional(),
|
|
||||||
skipForks: z.boolean().default(false),
|
|
||||||
privateRepositories: z.boolean().default(false),
|
|
||||||
mirrorIssues: z.boolean().default(false),
|
|
||||||
mirrorWiki: z.boolean().default(false),
|
|
||||||
mirrorStarred: z.boolean().default(false),
|
|
||||||
useSpecificUser: z.boolean().default(false),
|
|
||||||
singleRepo: z.string().optional(),
|
|
||||||
includeOrgs: z.array(z.string()).default([]),
|
|
||||||
excludeOrgs: z.array(z.string()).default([]),
|
|
||||||
mirrorPublicOrgs: z.boolean().default(false),
|
|
||||||
publicOrgs: z.array(z.string()).default([]),
|
|
||||||
skipStarredIssues: z.boolean().default(false),
|
|
||||||
}),
|
|
||||||
giteaConfig: z.object({
|
|
||||||
username: z.string().min(1),
|
|
||||||
url: z.string().url(),
|
|
||||||
token: z.string().min(1),
|
|
||||||
organization: z.string().optional(),
|
|
||||||
visibility: z.enum(["public", "private", "limited"]).default("public"),
|
|
||||||
starredReposOrg: z.string().default("github"),
|
|
||||||
preserveOrgStructure: z.boolean().default(false),
|
|
||||||
mirrorStrategy: z.enum(["preserve", "single-org", "flat-user", "mixed"]).optional(),
|
|
||||||
personalReposOrg: z.string().optional(), // Override destination for personal repos
|
|
||||||
}),
|
|
||||||
include: z.array(z.string()).default(["*"]),
|
include: z.array(z.string()).default(["*"]),
|
||||||
exclude: z.array(z.string()).default([]),
|
exclude: z.array(z.string()).default([]),
|
||||||
scheduleConfig: z.object({
|
scheduleConfig: scheduleConfigSchema,
|
||||||
enabled: z.boolean().default(false),
|
cleanupConfig: cleanupConfigSchema,
|
||||||
interval: z.number().min(1).default(3600), // in seconds
|
createdAt: z.coerce.date(),
|
||||||
lastRun: z.date().optional(),
|
updatedAt: z.coerce.date(),
|
||||||
nextRun: z.date().optional(),
|
|
||||||
}),
|
|
||||||
cleanupConfig: z.object({
|
|
||||||
enabled: z.boolean().default(false),
|
|
||||||
retentionDays: z.number().min(1).default(604800), // in seconds (default: 7 days)
|
|
||||||
lastRun: z.date().optional(),
|
|
||||||
nextRun: z.date().optional(),
|
|
||||||
}),
|
|
||||||
createdAt: z.date().default(() => new Date()),
|
|
||||||
updatedAt: z.date().default(() => new Date()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Config = z.infer<typeof configSchema>;
|
|
||||||
|
|
||||||
// Repository schema
|
|
||||||
export const repositorySchema = z.object({
|
export const repositorySchema = z.object({
|
||||||
id: z.string().uuid().optional(),
|
id: z.string(),
|
||||||
userId: z.string().uuid().optional(),
|
userId: z.string(),
|
||||||
configId: z.string().uuid(),
|
configId: z.string(),
|
||||||
|
name: z.string(),
|
||||||
name: z.string().min(1),
|
fullName: z.string(),
|
||||||
fullName: z.string().min(1),
|
|
||||||
url: z.string().url(),
|
url: z.string().url(),
|
||||||
cloneUrl: z.string().url(),
|
cloneUrl: z.string().url(),
|
||||||
|
owner: z.string(),
|
||||||
owner: z.string().min(1),
|
organization: z.string().optional().nullable(),
|
||||||
organization: z.string().optional(),
|
mirroredLocation: z.string().default(""),
|
||||||
|
|
||||||
isPrivate: z.boolean().default(false),
|
isPrivate: z.boolean().default(false),
|
||||||
isForked: z.boolean().default(false),
|
isForked: z.boolean().default(false),
|
||||||
forkedFrom: z.string().optional(),
|
forkedFrom: z.string().optional().nullable(),
|
||||||
|
|
||||||
hasIssues: z.boolean().default(false),
|
hasIssues: z.boolean().default(false),
|
||||||
isStarred: z.boolean().default(false),
|
isStarred: z.boolean().default(false),
|
||||||
isArchived: z.boolean().default(false),
|
isArchived: z.boolean().default(false),
|
||||||
|
size: z.number().default(0),
|
||||||
size: z.number(),
|
|
||||||
hasLFS: z.boolean().default(false),
|
hasLFS: z.boolean().default(false),
|
||||||
hasSubmodules: z.boolean().default(false),
|
hasSubmodules: z.boolean().default(false),
|
||||||
|
language: z.string().optional().nullable(),
|
||||||
|
description: z.string().optional().nullable(),
|
||||||
defaultBranch: z.string(),
|
defaultBranch: z.string(),
|
||||||
visibility: repositoryVisibilityEnum.default("public"),
|
visibility: z.enum(["public", "private", "internal"]).default("public"),
|
||||||
|
status: z
|
||||||
status: repoStatusEnum.default("imported"),
|
.enum([
|
||||||
lastMirrored: z.date().optional(),
|
"imported",
|
||||||
errorMessage: z.string().optional(),
|
"mirroring",
|
||||||
|
"mirrored",
|
||||||
mirroredLocation: z.string().default(""), // Store the full Gitea path where repo was mirrored
|
"failed",
|
||||||
destinationOrg: z.string().optional(), // Custom destination organization override
|
"skipped",
|
||||||
|
"deleting",
|
||||||
createdAt: z.date().default(() => new Date()),
|
"deleted",
|
||||||
updatedAt: z.date().default(() => new Date()),
|
])
|
||||||
|
.default("imported"),
|
||||||
|
lastMirrored: z.coerce.date().optional().nullable(),
|
||||||
|
errorMessage: z.string().optional().nullable(),
|
||||||
|
destinationOrg: z.string().optional().nullable(),
|
||||||
|
createdAt: z.coerce.date(),
|
||||||
|
updatedAt: z.coerce.date(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Repository = z.infer<typeof repositorySchema>;
|
|
||||||
|
|
||||||
// Mirror job schema
|
|
||||||
export const mirrorJobSchema = z.object({
|
export const mirrorJobSchema = z.object({
|
||||||
id: z.string().uuid().optional(),
|
id: z.string(),
|
||||||
userId: z.string().uuid().optional(),
|
userId: z.string(),
|
||||||
repositoryId: z.string().uuid().optional(),
|
repositoryId: z.string().optional().nullable(),
|
||||||
repositoryName: z.string().optional(),
|
repositoryName: z.string().optional().nullable(),
|
||||||
organizationId: z.string().uuid().optional(),
|
organizationId: z.string().optional().nullable(),
|
||||||
organizationName: z.string().optional(),
|
organizationName: z.string().optional().nullable(),
|
||||||
details: z.string().optional(),
|
details: z.string().optional().nullable(),
|
||||||
status: repoStatusEnum.default("imported"),
|
status: z
|
||||||
|
.enum([
|
||||||
|
"imported",
|
||||||
|
"mirroring",
|
||||||
|
"mirrored",
|
||||||
|
"failed",
|
||||||
|
"skipped",
|
||||||
|
"deleting",
|
||||||
|
"deleted",
|
||||||
|
])
|
||||||
|
.default("imported"),
|
||||||
message: z.string(),
|
message: z.string(),
|
||||||
timestamp: z.date().default(() => new Date()),
|
timestamp: z.coerce.date(),
|
||||||
|
jobType: z.enum(["mirror", "cleanup", "import"]).default("mirror"),
|
||||||
// New fields for job resilience
|
batchId: z.string().optional().nullable(),
|
||||||
jobType: z.enum(["mirror", "sync", "retry"]).default("mirror"),
|
totalItems: z.number().optional().nullable(),
|
||||||
batchId: z.string().uuid().optional(), // Group related jobs together
|
completedItems: z.number().default(0),
|
||||||
totalItems: z.number().optional(), // Total number of items to process
|
itemIds: z.array(z.string()).optional().nullable(),
|
||||||
completedItems: z.number().optional(), // Number of items completed
|
completedItemIds: z.array(z.string()).default([]),
|
||||||
itemIds: z.array(z.string()).optional(), // IDs of items to process
|
inProgress: z.boolean().default(false),
|
||||||
completedItemIds: z.array(z.string()).optional(), // IDs of completed items
|
startedAt: z.coerce.date().optional().nullable(),
|
||||||
inProgress: z.boolean().default(false), // Whether the job is currently running
|
completedAt: z.coerce.date().optional().nullable(),
|
||||||
startedAt: z.date().optional(), // When the job started
|
lastCheckpoint: z.coerce.date().optional().nullable(),
|
||||||
completedAt: z.date().optional(), // When the job completed
|
|
||||||
lastCheckpoint: z.date().optional(), // Last time progress was saved
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type MirrorJob = z.infer<typeof mirrorJobSchema>;
|
|
||||||
|
|
||||||
// Organization schema
|
|
||||||
export const organizationSchema = z.object({
|
export const organizationSchema = z.object({
|
||||||
id: z.string().uuid().optional(),
|
id: z.string(),
|
||||||
userId: z.string().uuid().optional(),
|
userId: z.string(),
|
||||||
configId: z.string().uuid(),
|
configId: z.string(),
|
||||||
|
name: z.string(),
|
||||||
avatarUrl: z.string().url(),
|
avatarUrl: z.string(),
|
||||||
|
membershipRole: z.enum(["admin", "member", "owner"]).default("member"),
|
||||||
name: z.string().min(1),
|
isIncluded: z.boolean().default(true),
|
||||||
|
destinationOrg: z.string().optional().nullable(),
|
||||||
membershipRole: membershipRoleEnum.default("member"),
|
status: z
|
||||||
|
.enum([
|
||||||
isIncluded: z.boolean().default(false),
|
"imported",
|
||||||
|
"mirroring",
|
||||||
status: repoStatusEnum.default("imported"),
|
"mirrored",
|
||||||
lastMirrored: z.date().optional(),
|
"failed",
|
||||||
errorMessage: z.string().optional(),
|
"skipped",
|
||||||
|
"deleting",
|
||||||
|
"deleted",
|
||||||
|
])
|
||||||
|
.default("imported"),
|
||||||
|
lastMirrored: z.coerce.date().optional().nullable(),
|
||||||
|
errorMessage: z.string().optional().nullable(),
|
||||||
repositoryCount: z.number().default(0),
|
repositoryCount: z.number().default(0),
|
||||||
publicRepositoryCount: z.number().optional(),
|
createdAt: z.coerce.date(),
|
||||||
privateRepositoryCount: z.number().optional(),
|
updatedAt: z.coerce.date(),
|
||||||
forkRepositoryCount: z.number().optional(),
|
|
||||||
|
|
||||||
// Override destination organization for this GitHub org's repos
|
|
||||||
destinationOrg: z.string().optional(),
|
|
||||||
|
|
||||||
createdAt: z.date().default(() => new Date()),
|
|
||||||
updatedAt: z.date().default(() => new Date()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Organization = z.infer<typeof organizationSchema>;
|
|
||||||
|
|
||||||
// Event schema (for SQLite-based pub/sub)
|
|
||||||
export const eventSchema = z.object({
|
export const eventSchema = z.object({
|
||||||
id: z.string().uuid().optional(),
|
id: z.string(),
|
||||||
userId: z.string().uuid(),
|
userId: z.string(),
|
||||||
channel: z.string().min(1),
|
channel: z.string(),
|
||||||
payload: z.any(),
|
payload: z.any(),
|
||||||
read: z.boolean().default(false),
|
read: z.boolean().default(false),
|
||||||
createdAt: z.date().default(() => new Date()),
|
createdAt: z.coerce.date(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ===== Drizzle Table Definitions =====
|
||||||
|
|
||||||
|
export const users = sqliteTable("users", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
username: text("username").notNull(),
|
||||||
|
password: text("password").notNull(),
|
||||||
|
email: text("email").notNull(),
|
||||||
|
createdAt: integer("created_at", { mode: "timestamp" })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`(unixepoch())`),
|
||||||
|
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`(unixepoch())`),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const events = sqliteTable("events", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
userId: text("user_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id),
|
||||||
|
channel: text("channel").notNull(),
|
||||||
|
payload: text("payload", { mode: "json" }).notNull(),
|
||||||
|
read: integer("read", { mode: "boolean" }).notNull().default(false),
|
||||||
|
createdAt: integer("created_at", { mode: "timestamp" })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`(unixepoch())`),
|
||||||
|
}, (table) => {
|
||||||
|
return {
|
||||||
|
userChannelIdx: index("idx_events_user_channel").on(table.userId, table.channel),
|
||||||
|
createdAtIdx: index("idx_events_created_at").on(table.createdAt),
|
||||||
|
readIdx: index("idx_events_read").on(table.read),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const configs = sqliteTable("configs", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
userId: text("user_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
isActive: integer("is_active", { mode: "boolean" }).notNull().default(true),
|
||||||
|
|
||||||
|
githubConfig: text("github_config", { mode: "json" })
|
||||||
|
.$type<z.infer<typeof githubConfigSchema>>()
|
||||||
|
.notNull(),
|
||||||
|
|
||||||
|
giteaConfig: text("gitea_config", { mode: "json" })
|
||||||
|
.$type<z.infer<typeof giteaConfigSchema>>()
|
||||||
|
.notNull(),
|
||||||
|
|
||||||
|
include: text("include", { mode: "json" })
|
||||||
|
.$type<string[]>()
|
||||||
|
.notNull()
|
||||||
|
.default(sql`'["*"]'`),
|
||||||
|
|
||||||
|
exclude: text("exclude", { mode: "json" })
|
||||||
|
.$type<string[]>()
|
||||||
|
.notNull()
|
||||||
|
.default(sql`'[]'`),
|
||||||
|
|
||||||
|
scheduleConfig: text("schedule_config", { mode: "json" })
|
||||||
|
.$type<z.infer<typeof scheduleConfigSchema>>()
|
||||||
|
.notNull(),
|
||||||
|
|
||||||
|
cleanupConfig: text("cleanup_config", { mode: "json" })
|
||||||
|
.$type<z.infer<typeof cleanupConfigSchema>>()
|
||||||
|
.notNull(),
|
||||||
|
|
||||||
|
createdAt: integer("created_at", { mode: "timestamp" })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`(unixepoch())`),
|
||||||
|
|
||||||
|
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`(unixepoch())`),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const repositories = sqliteTable("repositories", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
userId: text("user_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id),
|
||||||
|
configId: text("config_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => configs.id),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
fullName: text("full_name").notNull(),
|
||||||
|
url: text("url").notNull(),
|
||||||
|
cloneUrl: text("clone_url").notNull(),
|
||||||
|
owner: text("owner").notNull(),
|
||||||
|
organization: text("organization"),
|
||||||
|
mirroredLocation: text("mirrored_location").default(""),
|
||||||
|
|
||||||
|
isPrivate: integer("is_private", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
isForked: integer("is_fork", { mode: "boolean" }).notNull().default(false),
|
||||||
|
forkedFrom: text("forked_from"),
|
||||||
|
|
||||||
|
hasIssues: integer("has_issues", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
isStarred: integer("is_starred", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
isArchived: integer("is_archived", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
|
||||||
|
size: integer("size").notNull().default(0),
|
||||||
|
hasLFS: integer("has_lfs", { mode: "boolean" }).notNull().default(false),
|
||||||
|
hasSubmodules: integer("has_submodules", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
|
||||||
|
language: text("language"),
|
||||||
|
description: text("description"),
|
||||||
|
defaultBranch: text("default_branch").notNull(),
|
||||||
|
visibility: text("visibility").notNull().default("public"),
|
||||||
|
|
||||||
|
status: text("status").notNull().default("imported"),
|
||||||
|
lastMirrored: integer("last_mirrored", { mode: "timestamp" }),
|
||||||
|
errorMessage: text("error_message"),
|
||||||
|
|
||||||
|
destinationOrg: text("destination_org"),
|
||||||
|
|
||||||
|
createdAt: integer("created_at", { mode: "timestamp" })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`(unixepoch())`),
|
||||||
|
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`(unixepoch())`),
|
||||||
|
}, (table) => {
|
||||||
|
return {
|
||||||
|
userIdIdx: index("idx_repositories_user_id").on(table.userId),
|
||||||
|
configIdIdx: index("idx_repositories_config_id").on(table.configId),
|
||||||
|
statusIdx: index("idx_repositories_status").on(table.status),
|
||||||
|
ownerIdx: index("idx_repositories_owner").on(table.owner),
|
||||||
|
organizationIdx: index("idx_repositories_organization").on(table.organization),
|
||||||
|
isForkedIdx: index("idx_repositories_is_fork").on(table.isForked),
|
||||||
|
isStarredIdx: index("idx_repositories_is_starred").on(table.isStarred),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const mirrorJobs = sqliteTable("mirror_jobs", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
userId: text("user_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id),
|
||||||
|
repositoryId: text("repository_id"),
|
||||||
|
repositoryName: text("repository_name"),
|
||||||
|
organizationId: text("organization_id"),
|
||||||
|
organizationName: text("organization_name"),
|
||||||
|
details: text("details"),
|
||||||
|
status: text("status").notNull().default("imported"),
|
||||||
|
message: text("message").notNull(),
|
||||||
|
timestamp: integer("timestamp", { mode: "timestamp" })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`(unixepoch())`),
|
||||||
|
|
||||||
|
// Job resilience fields
|
||||||
|
jobType: text("job_type").notNull().default("mirror"),
|
||||||
|
batchId: text("batch_id"),
|
||||||
|
totalItems: integer("total_items"),
|
||||||
|
completedItems: integer("completed_items").default(0),
|
||||||
|
itemIds: text("item_ids", { mode: "json" }).$type<string[]>(),
|
||||||
|
completedItemIds: text("completed_item_ids", { mode: "json" })
|
||||||
|
.$type<string[]>()
|
||||||
|
.default(sql`'[]'`),
|
||||||
|
inProgress: integer("in_progress", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false),
|
||||||
|
startedAt: integer("started_at", { mode: "timestamp" }),
|
||||||
|
completedAt: integer("completed_at", { mode: "timestamp" }),
|
||||||
|
lastCheckpoint: integer("last_checkpoint", { mode: "timestamp" }),
|
||||||
|
}, (table) => {
|
||||||
|
return {
|
||||||
|
userIdIdx: index("idx_mirror_jobs_user_id").on(table.userId),
|
||||||
|
batchIdIdx: index("idx_mirror_jobs_batch_id").on(table.batchId),
|
||||||
|
inProgressIdx: index("idx_mirror_jobs_in_progress").on(table.inProgress),
|
||||||
|
jobTypeIdx: index("idx_mirror_jobs_job_type").on(table.jobType),
|
||||||
|
timestampIdx: index("idx_mirror_jobs_timestamp").on(table.timestamp),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export const organizations = sqliteTable("organizations", {
|
||||||
|
id: text("id").primaryKey(),
|
||||||
|
userId: text("user_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id),
|
||||||
|
configId: text("config_id")
|
||||||
|
.notNull()
|
||||||
|
.references(() => configs.id),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
|
||||||
|
avatarUrl: text("avatar_url").notNull(),
|
||||||
|
|
||||||
|
membershipRole: text("membership_role").notNull().default("member"),
|
||||||
|
|
||||||
|
isIncluded: integer("is_included", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(true),
|
||||||
|
|
||||||
|
destinationOrg: text("destination_org"),
|
||||||
|
|
||||||
|
status: text("status").notNull().default("imported"),
|
||||||
|
lastMirrored: integer("last_mirrored", { mode: "timestamp" }),
|
||||||
|
errorMessage: text("error_message"),
|
||||||
|
|
||||||
|
repositoryCount: integer("repository_count").notNull().default(0),
|
||||||
|
|
||||||
|
createdAt: integer("created_at", { mode: "timestamp" })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`(unixepoch())`),
|
||||||
|
updatedAt: integer("updated_at", { mode: "timestamp" })
|
||||||
|
.notNull()
|
||||||
|
.default(sql`(unixepoch())`),
|
||||||
|
}, (table) => {
|
||||||
|
return {
|
||||||
|
userIdIdx: index("idx_organizations_user_id").on(table.userId),
|
||||||
|
configIdIdx: index("idx_organizations_config_id").on(table.configId),
|
||||||
|
statusIdx: index("idx_organizations_status").on(table.status),
|
||||||
|
isIncludedIdx: index("idx_organizations_is_included").on(table.isIncluded),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Export type definitions
|
||||||
|
export type User = z.infer<typeof userSchema>;
|
||||||
|
export type Config = z.infer<typeof configSchema>;
|
||||||
|
export type Repository = z.infer<typeof repositorySchema>;
|
||||||
|
export type MirrorJob = z.infer<typeof mirrorJobSchema>;
|
||||||
|
export type Organization = z.infer<typeof organizationSchema>;
|
||||||
export type Event = z.infer<typeof eventSchema>;
|
export type Event = z.infer<typeof eventSchema>;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { APIRoute } from "astro";
|
import type { APIRoute } from "astro";
|
||||||
import { db, users, configs, client } from "@/lib/db";
|
import { db, users, configs } from "@/lib/db";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and, sql } from "drizzle-orm";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
const JWT_SECRET = process.env.JWT_SECRET || "your-secret-key";
|
const JWT_SECRET = process.env.JWT_SECRET || "your-secret-key";
|
||||||
@@ -10,10 +10,10 @@ export const GET: APIRoute = async ({ request, cookies }) => {
|
|||||||
const token = authHeader?.split(" ")[1] || cookies.get("token")?.value;
|
const token = authHeader?.split(" ")[1] || cookies.get("token")?.value;
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
const userCountResult = await client.execute(
|
const userCountResult = await db
|
||||||
`SELECT COUNT(*) as count FROM users`
|
.select({ count: sql<number>`count(*)` })
|
||||||
);
|
.from(users);
|
||||||
const userCount = userCountResult.rows[0].count;
|
const userCount = userCountResult[0].count;
|
||||||
|
|
||||||
if (userCount === 0) {
|
if (userCount === 0) {
|
||||||
return new Response(JSON.stringify({ error: "No users found" }), {
|
return new Response(JSON.stringify({ error: "No users found" }), {
|
||||||
|
|||||||
Reference in New Issue
Block a user