mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-08 04:26:44 +03:00
🎉 Gitea Mirror: Added
This commit is contained in:
177
src/lib/db/index.ts
Normal file
177
src/lib/db/index.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { z } from "zod";
|
||||
import { createClient } from "@libsql/client";
|
||||
import { drizzle } from "drizzle-orm/libsql";
|
||||
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
|
||||
|
||||
import path from "path";
|
||||
import { configSchema } from "./schema";
|
||||
|
||||
// Define the database URL - for development we'll use a local SQLite file
|
||||
const dataDir = path.join(process.cwd(), "data");
|
||||
const dbUrl =
|
||||
process.env.DATABASE_URL || `file:${path.join(dataDir, "gitea-mirror.db")}`;
|
||||
|
||||
// Create a client connection to the database
|
||||
export const client = createClient({ url: dbUrl });
|
||||
|
||||
// Create a drizzle instance
|
||||
export const db = drizzle(client);
|
||||
|
||||
// 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()),
|
||||
});
|
||||
|
||||
const githubSchema = configSchema.shape.githubConfig;
|
||||
const giteaSchema = configSchema.shape.giteaConfig;
|
||||
const scheduleSchema = configSchema.shape.scheduleConfig;
|
||||
|
||||
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(),
|
||||
|
||||
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()),
|
||||
});
|
||||
|
||||
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),
|
||||
|
||||
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()),
|
||||
});
|
||||
27
src/lib/db/migrations/add-mirrored-location.ts
Normal file
27
src/lib/db/migrations/add-mirrored-location.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { client } from "@/lib/db";
|
||||
|
||||
/**
|
||||
* Migration script to add the mirrored_location column to the repositories table
|
||||
*/
|
||||
export async function addMirroredLocationColumn() {
|
||||
try {
|
||||
console.log("Starting migration: Adding mirrored_location column to repositories table");
|
||||
|
||||
// Check if the column already exists
|
||||
const tableInfo = await client.execute(`PRAGMA table_info(repositories)`);
|
||||
const columnExists = tableInfo.rows.some((row: any) => row.name === "mirrored_location");
|
||||
|
||||
if (columnExists) {
|
||||
console.log("Column mirrored_location already exists, skipping migration");
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the mirrored_location column
|
||||
await client.execute(`ALTER TABLE repositories ADD COLUMN mirrored_location TEXT DEFAULT ''`);
|
||||
|
||||
console.log("Migration completed successfully: mirrored_location column added");
|
||||
} catch (error) {
|
||||
console.error("Migration failed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
75
src/lib/db/schema.sql
Normal file
75
src/lib/db/schema.sql
Normal file
@@ -0,0 +1,75 @@
|
||||
-- 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
|
||||
);
|
||||
142
src/lib/db/schema.ts
Normal file
142
src/lib/db/schema.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import { z } from "zod";
|
||||
import { repositoryVisibilityEnum, repoStatusEnum } from "@/types/Repository";
|
||||
import { membershipRoleEnum } from "@/types/organizations";
|
||||
|
||||
// User schema
|
||||
export const userSchema = z.object({
|
||||
id: z.string().uuid().optional(),
|
||||
username: z.string().min(3),
|
||||
password: z.string().min(8).optional(), // Hashed password
|
||||
email: z.string().email(),
|
||||
createdAt: z.date().default(() => new Date()),
|
||||
updatedAt: z.date().default(() => new Date()),
|
||||
});
|
||||
|
||||
export type User = z.infer<typeof userSchema>;
|
||||
|
||||
// Configuration schema
|
||||
export const configSchema = z.object({
|
||||
id: z.string().uuid().optional(),
|
||||
userId: z.string().uuid(),
|
||||
name: z.string().min(1),
|
||||
isActive: z.boolean().default(true),
|
||||
githubConfig: z.object({
|
||||
username: z.string().min(1),
|
||||
token: z.string().optional(),
|
||||
skipForks: z.boolean().default(false),
|
||||
privateRepositories: z.boolean().default(false),
|
||||
mirrorIssues: 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([]),
|
||||
preserveOrgStructure: z.boolean().default(false),
|
||||
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"),
|
||||
}),
|
||||
include: z.array(z.string()).default(["*"]),
|
||||
exclude: z.array(z.string()).default([]),
|
||||
scheduleConfig: z.object({
|
||||
enabled: z.boolean().default(false),
|
||||
interval: z.number().min(1).default(3600), // in seconds
|
||||
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({
|
||||
id: z.string().uuid().optional(),
|
||||
userId: z.string().uuid().optional(),
|
||||
configId: z.string().uuid(),
|
||||
|
||||
name: z.string().min(1),
|
||||
fullName: z.string().min(1),
|
||||
url: z.string().url(),
|
||||
cloneUrl: z.string().url(),
|
||||
|
||||
owner: z.string().min(1),
|
||||
organization: z.string().optional(),
|
||||
|
||||
isPrivate: z.boolean().default(false),
|
||||
isForked: z.boolean().default(false),
|
||||
forkedFrom: z.string().optional(),
|
||||
|
||||
hasIssues: z.boolean().default(false),
|
||||
isStarred: z.boolean().default(false),
|
||||
isArchived: z.boolean().default(false),
|
||||
|
||||
size: z.number(),
|
||||
hasLFS: z.boolean().default(false),
|
||||
hasSubmodules: z.boolean().default(false),
|
||||
|
||||
defaultBranch: z.string(),
|
||||
visibility: repositoryVisibilityEnum.default("public"),
|
||||
|
||||
status: repoStatusEnum.default("imported"),
|
||||
lastMirrored: z.date().optional(),
|
||||
errorMessage: z.string().optional(),
|
||||
|
||||
mirroredLocation: z.string().default(""), // Store the full Gitea path where repo was mirrored
|
||||
|
||||
createdAt: z.date().default(() => new Date()),
|
||||
updatedAt: z.date().default(() => new Date()),
|
||||
});
|
||||
|
||||
export type Repository = z.infer<typeof repositorySchema>;
|
||||
|
||||
// Mirror job schema
|
||||
export const mirrorJobSchema = z.object({
|
||||
id: z.string().uuid().optional(),
|
||||
userId: z.string().uuid().optional(),
|
||||
repositoryId: z.string().uuid().optional(),
|
||||
repositoryName: z.string().optional(),
|
||||
organizationId: z.string().uuid().optional(),
|
||||
organizationName: z.string().optional(),
|
||||
details: z.string().optional(),
|
||||
status: repoStatusEnum.default("imported"),
|
||||
message: z.string(),
|
||||
timestamp: z.date().default(() => new Date()),
|
||||
});
|
||||
|
||||
export type MirrorJob = z.infer<typeof mirrorJobSchema>;
|
||||
|
||||
// Organization schema
|
||||
export const organizationSchema = z.object({
|
||||
id: z.string().uuid().optional(),
|
||||
userId: z.string().uuid().optional(),
|
||||
configId: z.string().uuid(),
|
||||
|
||||
avatarUrl: z.string().url(),
|
||||
|
||||
name: z.string().min(1),
|
||||
|
||||
membershipRole: membershipRoleEnum.default("member"),
|
||||
|
||||
isIncluded: z.boolean().default(false),
|
||||
|
||||
status: repoStatusEnum.default("imported"),
|
||||
lastMirrored: z.date().optional(),
|
||||
errorMessage: z.string().optional(),
|
||||
|
||||
repositoryCount: z.number().default(0),
|
||||
|
||||
createdAt: z.date().default(() => new Date()),
|
||||
updatedAt: z.date().default(() => new Date()),
|
||||
});
|
||||
|
||||
export type Organization = z.infer<typeof organizationSchema>;
|
||||
Reference in New Issue
Block a user