mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-08 12:36:44 +03:00
Updates for starred and personal repos
This commit is contained in:
102
CLAUDE.md
102
CLAUDE.md
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
DONT HALLUCIATE THINGS. IF YOU DONT KNOW LOOK AT THE CODE OR ASK FOR DOCS
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
Gitea Mirror is a web application that automatically mirrors repositories from GitHub to self-hosted Gitea instances. It uses Astro for SSR, React for UI, SQLite for data storage, and Bun as the JavaScript runtime.
|
Gitea Mirror is a web application that automatically mirrors repositories from GitHub to self-hosted Gitea instances. It uses Astro for SSR, React for UI, SQLite for data storage, and Bun as the JavaScript runtime.
|
||||||
@@ -40,7 +42,7 @@ bun run start # Start production server
|
|||||||
- **Frontend**: Astro (SSR) + React + Tailwind CSS v4 + Shadcn UI
|
- **Frontend**: Astro (SSR) + React + Tailwind CSS v4 + Shadcn UI
|
||||||
- **Backend**: Bun runtime + SQLite + Drizzle ORM
|
- **Backend**: Bun runtime + SQLite + Drizzle ORM
|
||||||
- **APIs**: GitHub (Octokit) and Gitea APIs
|
- **APIs**: GitHub (Octokit) and Gitea APIs
|
||||||
- **Auth**: JWT tokens with bcryptjs password hashing
|
- **Auth**: Better Auth with email/password, SSO, and OIDC provider support
|
||||||
|
|
||||||
### Project Structure
|
### Project Structure
|
||||||
- `/src/pages/api/` - API endpoints (Astro API routes)
|
- `/src/pages/api/` - API endpoints (Astro API routes)
|
||||||
@@ -68,10 +70,15 @@ export async function POST({ request }: APIContext) {
|
|||||||
|
|
||||||
3. **Real-time Updates**: Server-Sent Events (SSE) endpoint at `/api/events` for live dashboard updates
|
3. **Real-time Updates**: Server-Sent Events (SSE) endpoint at `/api/events` for live dashboard updates
|
||||||
|
|
||||||
4. **Authentication Flow**:
|
4. **Authentication System**:
|
||||||
|
- Built on Better Auth library
|
||||||
|
- Three authentication methods:
|
||||||
|
- Email & Password (traditional auth)
|
||||||
|
- SSO (authenticate via external OIDC providers)
|
||||||
|
- OIDC Provider (act as OIDC provider for other apps)
|
||||||
|
- Session-based authentication with secure cookies
|
||||||
- First user signup creates admin account
|
- First user signup creates admin account
|
||||||
- JWT tokens stored in cookies
|
- Protected routes use Better Auth session validation
|
||||||
- Protected routes check auth via `getUserFromCookie()`
|
|
||||||
|
|
||||||
5. **Mirror Process**:
|
5. **Mirror Process**:
|
||||||
- Discovers repos from GitHub (user/org)
|
- Discovers repos from GitHub (user/org)
|
||||||
@@ -79,11 +86,18 @@ export async function POST({ request }: APIContext) {
|
|||||||
- Tracks status in database
|
- Tracks status in database
|
||||||
- Supports scheduled automatic mirroring
|
- Supports scheduled automatic mirroring
|
||||||
|
|
||||||
6. **Mirror Strategies**: Three ways to organize repositories in Gitea:
|
6. **Mirror Strategies**: Four ways to organize repositories in Gitea:
|
||||||
- **preserve**: Maintains GitHub structure (default)
|
- **preserve**: Maintains GitHub structure (default)
|
||||||
|
- Organization repos → Same organization name in Gitea
|
||||||
|
- Personal repos → Under your Gitea username
|
||||||
- **single-org**: All repos go to one organization
|
- **single-org**: All repos go to one organization
|
||||||
|
- All repos → Single configured organization
|
||||||
- **flat-user**: All repos go under user account
|
- **flat-user**: All repos go under user account
|
||||||
- Starred repos always go to separate organization (starredReposOrg)
|
- All repos → Under your Gitea username
|
||||||
|
- **mixed**: Hybrid approach
|
||||||
|
- Organization repos → Preserve structure
|
||||||
|
- Personal repos → Single configured organization
|
||||||
|
- Starred repos always go to separate organization (starredReposOrg, default: "starred")
|
||||||
- Routing logic in `getGiteaRepoOwner()` function
|
- Routing logic in `getGiteaRepoOwner()` function
|
||||||
|
|
||||||
### Database Schema (SQLite)
|
### Database Schema (SQLite)
|
||||||
@@ -102,11 +116,18 @@ export async function POST({ request }: APIContext) {
|
|||||||
|
|
||||||
### Development Tips
|
### Development Tips
|
||||||
- Environment variables in `.env` (copy from `.env.example`)
|
- Environment variables in `.env` (copy from `.env.example`)
|
||||||
- JWT_SECRET auto-generated if not provided
|
- BETTER_AUTH_SECRET required for session signing
|
||||||
- Database auto-initializes on first run
|
- Database auto-initializes on first run
|
||||||
- Use `bun run dev:clean` for fresh database start
|
- Use `bun run dev:clean` for fresh database start
|
||||||
- Tailwind CSS v4 configured with Vite plugin
|
- Tailwind CSS v4 configured with Vite plugin
|
||||||
|
|
||||||
|
### Authentication Setup
|
||||||
|
- **Better Auth** handles all authentication
|
||||||
|
- Configuration in `/src/lib/auth.ts` (server) and `/src/lib/auth-client.ts` (client)
|
||||||
|
- Auth endpoints available at `/api/auth/*`
|
||||||
|
- SSO providers configured through the web UI
|
||||||
|
- OIDC provider functionality for external applications
|
||||||
|
|
||||||
### Common Tasks
|
### Common Tasks
|
||||||
|
|
||||||
**Adding a new API endpoint:**
|
**Adding a new API endpoint:**
|
||||||
@@ -125,6 +146,73 @@ export async function POST({ request }: APIContext) {
|
|||||||
2. Run `bun run init-db` to recreate database
|
2. Run `bun run init-db` to recreate database
|
||||||
3. Update related queries in `/src/lib/db/queries/`
|
3. Update related queries in `/src/lib/db/queries/`
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
### GitHub Configuration (UI Fields)
|
||||||
|
|
||||||
|
#### Basic Settings (`githubConfig`)
|
||||||
|
- **username**: GitHub username
|
||||||
|
- **token**: GitHub personal access token (requires repo and admin:org scopes)
|
||||||
|
- **privateRepositories**: Include private repositories
|
||||||
|
- **mirrorStarred**: Mirror starred repositories
|
||||||
|
|
||||||
|
### Gitea Configuration (UI Fields)
|
||||||
|
- **url**: Gitea instance URL
|
||||||
|
- **username**: Gitea username
|
||||||
|
- **token**: Gitea access token
|
||||||
|
- **organization**: Destination organization (for single-org/mixed strategies)
|
||||||
|
- **starredReposOrg**: Organization for starred repositories (default: "starred")
|
||||||
|
- **visibility**: Organization visibility - "public", "private", "limited"
|
||||||
|
- **mirrorStrategy**: Repository organization strategy (set via UI)
|
||||||
|
- **preserveOrgStructure**: Automatically set based on mirrorStrategy
|
||||||
|
|
||||||
|
### Schedule Configuration (`scheduleConfig`)
|
||||||
|
- **enabled**: Enable automatic mirroring (default: false)
|
||||||
|
- **interval**: Cron expression or seconds (default: "0 2 * * *" - 2 AM daily)
|
||||||
|
- **concurrent**: Allow concurrent mirror operations (default: false)
|
||||||
|
- **batchSize**: Number of repos to process in parallel (default: 10)
|
||||||
|
|
||||||
|
### Database Cleanup Configuration (`cleanupConfig`)
|
||||||
|
- **enabled**: Enable automatic cleanup (default: false)
|
||||||
|
- **retentionDays**: Days to keep events (stored as seconds internally)
|
||||||
|
|
||||||
|
### Mirror Options (UI Fields)
|
||||||
|
- **mirrorReleases**: Mirror GitHub releases to Gitea
|
||||||
|
- **mirrorMetadata**: Enable metadata mirroring (master toggle)
|
||||||
|
- **metadataComponents** (only available when mirrorMetadata is enabled):
|
||||||
|
- **issues**: Mirror issues
|
||||||
|
- **pullRequests**: Mirror pull requests
|
||||||
|
- **labels**: Mirror labels
|
||||||
|
- **milestones**: Mirror milestones
|
||||||
|
- **wiki**: Mirror wiki content
|
||||||
|
|
||||||
|
### Advanced Options (UI Fields)
|
||||||
|
- **skipForks**: Skip forked repositories (default: false)
|
||||||
|
- **skipStarredIssues**: Skip issues for starred repositories (default: false) - enables "Lightweight mode" for starred repos
|
||||||
|
|
||||||
|
### Authentication Configuration
|
||||||
|
|
||||||
|
#### SSO Provider Configuration
|
||||||
|
- **issuerUrl**: OIDC issuer URL (e.g., https://accounts.google.com)
|
||||||
|
- **domain**: Email domain for this provider
|
||||||
|
- **providerId**: Unique identifier for the provider
|
||||||
|
- **clientId**: OAuth client ID from provider
|
||||||
|
- **clientSecret**: OAuth client secret from provider
|
||||||
|
- **authorizationEndpoint**: OAuth authorization URL (auto-discovered if supported)
|
||||||
|
- **tokenEndpoint**: OAuth token exchange URL (auto-discovered if supported)
|
||||||
|
- **jwksEndpoint**: JSON Web Key Set URL (optional, auto-discovered)
|
||||||
|
- **userInfoEndpoint**: User information endpoint (optional, auto-discovered)
|
||||||
|
|
||||||
|
#### OIDC Provider Settings (for external apps)
|
||||||
|
- **allowedRedirectUris**: Comma-separated list of allowed redirect URIs
|
||||||
|
- **clientId**: Generated client ID for the application
|
||||||
|
- **clientSecret**: Generated client secret for the application
|
||||||
|
- **scopes**: Available scopes (openid, profile, email)
|
||||||
|
|
||||||
|
#### Environment Variables
|
||||||
|
- **BETTER_AUTH_SECRET**: Secret key for signing sessions (required)
|
||||||
|
- **BETTER_AUTH_URL**: Base URL for authentication (default: http://localhost:4321)
|
||||||
|
|
||||||
## Security Guidelines
|
## Security Guidelines
|
||||||
|
|
||||||
- **Confidentiality Guidelines**:
|
- **Confidentiality Guidelines**:
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export function ConfigTabs() {
|
|||||||
token: '',
|
token: '',
|
||||||
organization: 'github-mirrors',
|
organization: 'github-mirrors',
|
||||||
visibility: 'public',
|
visibility: 'public',
|
||||||
starredReposOrg: 'github',
|
starredReposOrg: 'starred',
|
||||||
preserveOrgStructure: false,
|
preserveOrgStructure: false,
|
||||||
},
|
},
|
||||||
scheduleConfig: {
|
scheduleConfig: {
|
||||||
|
|||||||
@@ -354,6 +354,19 @@ describe("getGiteaRepoOwner - Organization Override Tests", () => {
|
|||||||
expect(result).toBe("starred");
|
expect(result).toBe("starred");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("starred repos default to 'starred' org when starredReposOrg is not configured", () => {
|
||||||
|
const repo = { ...baseRepo, isStarred: true };
|
||||||
|
const configWithoutStarredOrg = {
|
||||||
|
...baseConfig,
|
||||||
|
giteaConfig: {
|
||||||
|
...baseConfig.giteaConfig,
|
||||||
|
starredReposOrg: undefined
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const result = getGiteaRepoOwner({ config: configWithoutStarredOrg, repository: repo });
|
||||||
|
expect(result).toBe("starred");
|
||||||
|
});
|
||||||
|
|
||||||
// Removed test for personalReposOrg as this field no longer exists
|
// Removed test for personalReposOrg as this field no longer exists
|
||||||
|
|
||||||
test("preserve strategy: personal repos fallback to username when no override", () => {
|
test("preserve strategy: personal repos fallback to username when no override", () => {
|
||||||
|
|||||||
@@ -73,8 +73,8 @@ export const getGiteaRepoOwnerAsync = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if repository is starred - starred repos always go to starredReposOrg (highest priority)
|
// Check if repository is starred - starred repos always go to starredReposOrg (highest priority)
|
||||||
if (repository.isStarred && config.giteaConfig.starredReposOrg) {
|
if (repository.isStarred) {
|
||||||
return config.giteaConfig.starredReposOrg;
|
return config.giteaConfig.starredReposOrg || "starred";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for repository-specific override (second highest priority)
|
// Check for repository-specific override (second highest priority)
|
||||||
@@ -118,8 +118,8 @@ export const getGiteaRepoOwner = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if repository is starred - starred repos always go to starredReposOrg
|
// Check if repository is starred - starred repos always go to starredReposOrg
|
||||||
if (repository.isStarred && config.giteaConfig.starredReposOrg) {
|
if (repository.isStarred) {
|
||||||
return config.giteaConfig.starredReposOrg;
|
return config.giteaConfig.starredReposOrg || "starred";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the mirror strategy - use preserveOrgStructure for backward compatibility
|
// Get the mirror strategy - use preserveOrgStructure for backward compatibility
|
||||||
@@ -352,8 +352,8 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
|
|
||||||
const apiUrl = `${config.giteaConfig.url}/api/v1/repos/migrate`;
|
const apiUrl = `${config.giteaConfig.url}/api/v1/repos/migrate`;
|
||||||
|
|
||||||
// Handle organization creation if needed for single-org or preserve strategies
|
// Handle organization creation if needed for single-org, preserve strategies, or starred repos
|
||||||
if (repoOwner !== config.giteaConfig.defaultOwner && !repository.isStarred) {
|
if (repoOwner !== config.giteaConfig.defaultOwner) {
|
||||||
// Need to create the organization if it doesn't exist
|
// Need to create the organization if it doesn't exist
|
||||||
await getOrCreateGiteaOrg({
|
await getOrCreateGiteaOrg({
|
||||||
orgName: repoOwner,
|
orgName: repoOwner,
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ export function mapDbToUiConfig(dbConfig: any): {
|
|||||||
token: dbConfig.giteaConfig?.token || "",
|
token: dbConfig.giteaConfig?.token || "",
|
||||||
organization: dbConfig.githubConfig?.defaultOrg || "github-mirrors", // Get from GitHub config
|
organization: dbConfig.githubConfig?.defaultOrg || "github-mirrors", // Get from GitHub config
|
||||||
visibility: dbConfig.giteaConfig?.visibility === "default" ? "public" : dbConfig.giteaConfig?.visibility || "public",
|
visibility: dbConfig.giteaConfig?.visibility === "default" ? "public" : dbConfig.giteaConfig?.visibility || "public",
|
||||||
starredReposOrg: dbConfig.githubConfig?.starredReposOrg || "github", // Get from GitHub config
|
starredReposOrg: dbConfig.githubConfig?.starredReposOrg || "starred", // Get from GitHub config
|
||||||
preserveOrgStructure: dbConfig.giteaConfig?.preserveVisibility || false, // Map preserveVisibility
|
preserveOrgStructure: dbConfig.giteaConfig?.preserveVisibility || false, // Map preserveVisibility
|
||||||
mirrorStrategy: dbConfig.githubConfig?.mirrorStrategy || "preserve", // Get from GitHub config
|
mirrorStrategy: dbConfig.githubConfig?.mirrorStrategy || "preserve", // Get from GitHub config
|
||||||
personalReposOrg: undefined, // Not stored in current schema
|
personalReposOrg: undefined, // Not stored in current schema
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ export const GET: APIRoute = async ({ request }) => {
|
|||||||
includePrivate: false,
|
includePrivate: false,
|
||||||
includePublic: true,
|
includePublic: true,
|
||||||
includeOrganizations: [],
|
includeOrganizations: [],
|
||||||
starredReposOrg: "github",
|
starredReposOrg: "starred",
|
||||||
mirrorStrategy: "preserve",
|
mirrorStrategy: "preserve",
|
||||||
defaultOrg: "github-mirrors",
|
defaultOrg: "github-mirrors",
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user