mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-07 03:56:46 +03:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fb5f9e190 | ||
|
|
dacec93f55 | ||
|
|
b41438f686 |
61
CHANGELOG.md
61
CHANGELOG.md
@@ -7,6 +7,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [3.2.6] - 2025-08-09
|
||||
|
||||
### Fixed
|
||||
- Added missing release asset mirroring functionality (APK, ZIP, Binary files)
|
||||
- Release assets (attachments) are now properly downloaded from GitHub and uploaded to Gitea
|
||||
- Fixed missing metadata component configuration checks
|
||||
|
||||
### Added
|
||||
- Full support for mirroring release assets/attachments
|
||||
- Debug logging for metadata component configuration to help troubleshoot mirroring issues
|
||||
- Download and upload progress logging for release assets
|
||||
|
||||
### Improved
|
||||
- Enhanced release mirroring to include all associated binary files and attachments
|
||||
- Better visibility into which metadata components are enabled/disabled
|
||||
- More detailed logging during the release asset transfer process
|
||||
|
||||
### Notes
|
||||
This patch adds the missing functionality to mirror release assets (APK, ZIP, Binary files, etc.) that was reported in Issue #68. Previously only release metadata was being mirrored, now all attachments are properly transferred to Gitea.
|
||||
|
||||
## [3.2.5] - 2025-08-09
|
||||
|
||||
### Fixed
|
||||
- Fixed critical authentication issue in releases mirroring that was still using encrypted tokens
|
||||
- Added missing repository existence check for releases mirroring function
|
||||
- Fixed "user does not exist [uid: 0]" error specifically affecting GitHub releases synchronization
|
||||
|
||||
### Improved
|
||||
- Enhanced releases mirroring with duplicate detection to avoid errors on re-runs
|
||||
- Better error handling and logging for release operations with [Releases] prefix
|
||||
- Added individual release error handling to continue mirroring even if some releases fail
|
||||
|
||||
### Notes
|
||||
This patch completes the authentication fixes started in v3.2.4, specifically addressing the releases mirroring function that was accidentally missed in the previous update.
|
||||
|
||||
## [3.2.4] - 2025-08-09
|
||||
|
||||
### Fixed
|
||||
- Fixed critical authentication issue causing "user does not exist [uid: 0]" errors during metadata mirroring (Issue #68)
|
||||
- Fixed inconsistent token handling across Gitea API calls
|
||||
- Fixed metadata mirroring functions attempting to operate on non-existent repositories
|
||||
- Fixed organization creation failing silently without proper error messages
|
||||
|
||||
### Added
|
||||
- Pre-flight authentication validation for all Gitea operations
|
||||
- Repository existence verification before metadata mirroring
|
||||
- Graceful fallback to user account when organization creation fails due to permissions
|
||||
- Authentication validation utilities for debugging configuration issues
|
||||
- Diagnostic test scripts for troubleshooting authentication problems
|
||||
|
||||
### Improved
|
||||
- Enhanced error messages with specific guidance for authentication failures
|
||||
- Better identification and logging of permission-related errors
|
||||
- More robust organization creation with retry logic and better error handling
|
||||
- Consistent token decryption across all API operations
|
||||
- Clearer error reporting for metadata mirroring failures
|
||||
|
||||
### Security
|
||||
- Fixed potential exposure of encrypted tokens in API calls
|
||||
- Improved token handling to ensure proper decryption before use
|
||||
|
||||
## [3.2.0] - 2025-07-31
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "gitea-mirror",
|
||||
"type": "module",
|
||||
"version": "3.2.3",
|
||||
"version": "3.2.6",
|
||||
"engines": {
|
||||
"bun": ">=1.2.9"
|
||||
},
|
||||
|
||||
202
src/lib/gitea-auth-validator.ts
Normal file
202
src/lib/gitea-auth-validator.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* Gitea authentication and permission validation utilities
|
||||
*/
|
||||
|
||||
import type { Config } from "@/types/config";
|
||||
import { httpGet, HttpError } from "./http-client";
|
||||
import { decryptConfigTokens } from "./utils/config-encryption";
|
||||
|
||||
export interface GiteaUser {
|
||||
id: number;
|
||||
login: string;
|
||||
username: string;
|
||||
full_name?: string;
|
||||
email?: string;
|
||||
is_admin: boolean;
|
||||
created?: string;
|
||||
restricted?: boolean;
|
||||
active?: boolean;
|
||||
prohibit_login?: boolean;
|
||||
location?: string;
|
||||
website?: string;
|
||||
description?: string;
|
||||
visibility?: string;
|
||||
followers_count?: number;
|
||||
following_count?: number;
|
||||
starred_repos_count?: number;
|
||||
language?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates Gitea authentication and returns user information
|
||||
*/
|
||||
export async function validateGiteaAuth(config: Partial<Config>): Promise<GiteaUser> {
|
||||
if (!config.giteaConfig?.url || !config.giteaConfig?.token) {
|
||||
throw new Error("Gitea URL and token are required for authentication validation");
|
||||
}
|
||||
|
||||
const decryptedConfig = decryptConfigTokens(config as Config);
|
||||
|
||||
try {
|
||||
const response = await httpGet<GiteaUser>(
|
||||
`${config.giteaConfig.url}/api/v1/user`,
|
||||
{
|
||||
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
|
||||
}
|
||||
);
|
||||
|
||||
const user = response.data;
|
||||
|
||||
// Validate user data
|
||||
if (!user.id || user.id === 0) {
|
||||
throw new Error("Invalid user data received from Gitea: User ID is 0 or missing");
|
||||
}
|
||||
|
||||
if (!user.username && !user.login) {
|
||||
throw new Error("Invalid user data received from Gitea: Username is missing");
|
||||
}
|
||||
|
||||
console.log(`[Auth Validator] Successfully authenticated as: ${user.username || user.login} (ID: ${user.id}, Admin: ${user.is_admin})`);
|
||||
|
||||
return user;
|
||||
} catch (error) {
|
||||
if (error instanceof HttpError) {
|
||||
if (error.status === 401) {
|
||||
throw new Error(
|
||||
"Authentication failed: The provided Gitea token is invalid or expired. " +
|
||||
"Please check your Gitea configuration and ensure the token has the necessary permissions."
|
||||
);
|
||||
} else if (error.status === 403) {
|
||||
throw new Error(
|
||||
"Permission denied: The Gitea token does not have sufficient permissions. " +
|
||||
"Please ensure your token has 'read:user' scope at minimum."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Failed to validate Gitea authentication: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the authenticated user can create organizations
|
||||
*/
|
||||
export async function canCreateOrganizations(config: Partial<Config>): Promise<boolean> {
|
||||
try {
|
||||
const user = await validateGiteaAuth(config);
|
||||
|
||||
// Admin users can always create organizations
|
||||
if (user.is_admin) {
|
||||
console.log(`[Auth Validator] User is admin, can create organizations`);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if the instance allows regular users to create organizations
|
||||
// This would require checking instance settings, which may not be publicly available
|
||||
// For now, we'll try to create a test org and see if it fails
|
||||
|
||||
if (!config.giteaConfig?.url || !config.giteaConfig?.token) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const decryptedConfig = decryptConfigTokens(config as Config);
|
||||
|
||||
try {
|
||||
// Try to list user's organizations as a proxy for permission check
|
||||
const orgsResponse = await httpGet(
|
||||
`${config.giteaConfig.url}/api/v1/user/orgs`,
|
||||
{
|
||||
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
|
||||
}
|
||||
);
|
||||
|
||||
// If we can list orgs, we likely can create them
|
||||
console.log(`[Auth Validator] User can list organizations, likely can create them`);
|
||||
return true;
|
||||
} catch (listError) {
|
||||
if (listError instanceof HttpError && listError.status === 403) {
|
||||
console.log(`[Auth Validator] User cannot list/create organizations`);
|
||||
return false;
|
||||
}
|
||||
// For other errors, assume we can try
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`[Auth Validator] Error checking organization creation permissions:`, error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or validates the default owner for repositories
|
||||
*/
|
||||
export async function getValidatedDefaultOwner(config: Partial<Config>): Promise<string> {
|
||||
const user = await validateGiteaAuth(config);
|
||||
const username = user.username || user.login;
|
||||
|
||||
if (!username) {
|
||||
throw new Error("Unable to determine Gitea username from authentication");
|
||||
}
|
||||
|
||||
// Check if the configured defaultOwner matches the authenticated user
|
||||
if (config.giteaConfig?.defaultOwner && config.giteaConfig.defaultOwner !== username) {
|
||||
console.warn(
|
||||
`[Auth Validator] Configured defaultOwner (${config.giteaConfig.defaultOwner}) ` +
|
||||
`does not match authenticated user (${username}). Using authenticated user.`
|
||||
);
|
||||
}
|
||||
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the Gitea configuration is properly set up for mirroring
|
||||
*/
|
||||
export async function validateGiteaConfigForMirroring(config: Partial<Config>): Promise<{
|
||||
valid: boolean;
|
||||
user: GiteaUser;
|
||||
canCreateOrgs: boolean;
|
||||
warnings: string[];
|
||||
errors: string[];
|
||||
}> {
|
||||
const warnings: string[] = [];
|
||||
const errors: string[] = [];
|
||||
|
||||
try {
|
||||
// Validate authentication
|
||||
const user = await validateGiteaAuth(config);
|
||||
|
||||
// Check organization creation permissions
|
||||
const canCreateOrgs = await canCreateOrganizations(config);
|
||||
|
||||
if (!canCreateOrgs && config.giteaConfig?.preserveOrgStructure) {
|
||||
warnings.push(
|
||||
"User cannot create organizations but 'preserveOrgStructure' is enabled. " +
|
||||
"Repositories will be mirrored to the user account instead."
|
||||
);
|
||||
}
|
||||
|
||||
// Validate token scopes (this would require additional API calls to check specific permissions)
|
||||
// For now, we'll just check if basic operations work
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
user,
|
||||
canCreateOrgs,
|
||||
warnings,
|
||||
errors,
|
||||
};
|
||||
} catch (error) {
|
||||
errors.push(error instanceof Error ? error.message : String(error));
|
||||
|
||||
return {
|
||||
valid: false,
|
||||
user: {} as GiteaUser,
|
||||
canCreateOrgs: false,
|
||||
warnings,
|
||||
errors,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,24 @@ let createOrgCalled = false;
|
||||
|
||||
const mockHttpGet = mock(async (url: string, headers?: any) => {
|
||||
// Return different responses based on URL patterns
|
||||
|
||||
// Handle user authentication endpoint
|
||||
if (url.includes("/api/v1/user")) {
|
||||
return {
|
||||
data: {
|
||||
id: 1,
|
||||
login: "testuser",
|
||||
username: "testuser",
|
||||
email: "test@example.com",
|
||||
is_admin: false,
|
||||
full_name: "Test User"
|
||||
},
|
||||
status: 200,
|
||||
statusText: "OK",
|
||||
headers: new Headers()
|
||||
};
|
||||
}
|
||||
|
||||
if (url.includes("/api/v1/repos/starred/test-repo")) {
|
||||
return {
|
||||
data: {
|
||||
|
||||
@@ -85,6 +85,25 @@ export async function getOrCreateGiteaOrgEnhanced({
|
||||
|
||||
const decryptedConfig = decryptConfigTokens(config as Config);
|
||||
|
||||
// First, validate the user's authentication by getting their information
|
||||
console.log(`[Org Creation] Validating user authentication before organization operations`);
|
||||
try {
|
||||
const userResponse = await httpGet(
|
||||
`${config.giteaConfig.url}/api/v1/user`,
|
||||
{
|
||||
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
|
||||
}
|
||||
);
|
||||
console.log(`[Org Creation] Authenticated as user: ${userResponse.data.username || userResponse.data.login} (ID: ${userResponse.data.id})`);
|
||||
} catch (authError) {
|
||||
if (authError instanceof HttpError && authError.status === 401) {
|
||||
console.error(`[Org Creation] Authentication failed: Invalid or expired token`);
|
||||
throw new Error(`Authentication failed: Please check your Gitea token has the required permissions. The token may be invalid or expired.`);
|
||||
}
|
||||
console.error(`[Org Creation] Failed to validate authentication:`, authError);
|
||||
throw new Error(`Failed to validate Gitea authentication: ${authError instanceof Error ? authError.message : String(authError)}`);
|
||||
}
|
||||
|
||||
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
||||
try {
|
||||
console.log(`[Org Creation] Attempting to get or create organization: ${orgName} (attempt ${attempt + 1}/${maxRetries})`);
|
||||
@@ -164,6 +183,18 @@ export async function getOrCreateGiteaOrgEnhanced({
|
||||
}
|
||||
continue; // Retry the loop
|
||||
}
|
||||
|
||||
// Check for permission errors
|
||||
if (createError.status === 403) {
|
||||
console.error(`[Org Creation] Permission denied: User may not have rights to create organizations`);
|
||||
throw new Error(`Permission denied: Your Gitea user account does not have permission to create organizations. Please ensure your account has the necessary privileges or contact your Gitea administrator.`);
|
||||
}
|
||||
|
||||
// Check for authentication errors
|
||||
if (createError.status === 401) {
|
||||
console.error(`[Org Creation] Authentication failed when creating organization`);
|
||||
throw new Error(`Authentication failed: The Gitea token does not have sufficient permissions to create organizations. Please ensure your token has 'write:organization' scope.`);
|
||||
}
|
||||
}
|
||||
throw createError;
|
||||
}
|
||||
|
||||
234
src/lib/gitea.ts
234
src/lib/gitea.ts
@@ -272,7 +272,7 @@ export const mirrorGithubRepoToGitea = async ({
|
||||
const decryptedConfig = decryptConfigTokens(config as Config);
|
||||
|
||||
// Get the correct owner based on the strategy (with organization overrides)
|
||||
const repoOwner = await getGiteaRepoOwnerAsync({ config, repository });
|
||||
let repoOwner = await getGiteaRepoOwnerAsync({ config, repository });
|
||||
|
||||
const isExisting = await isRepoPresentInGitea({
|
||||
config,
|
||||
@@ -355,10 +355,37 @@ export const mirrorGithubRepoToGitea = async ({
|
||||
// Handle organization creation if needed for single-org, preserve strategies, or starred repos
|
||||
if (repoOwner !== config.giteaConfig.defaultOwner) {
|
||||
// Need to create the organization if it doesn't exist
|
||||
await getOrCreateGiteaOrg({
|
||||
orgName: repoOwner,
|
||||
config,
|
||||
});
|
||||
try {
|
||||
await getOrCreateGiteaOrg({
|
||||
orgName: repoOwner,
|
||||
config,
|
||||
});
|
||||
} catch (orgError) {
|
||||
console.error(`Failed to create/access organization ${repoOwner}: ${orgError instanceof Error ? orgError.message : String(orgError)}`);
|
||||
|
||||
// Check if we should fallback to user account
|
||||
if (orgError instanceof Error &&
|
||||
(orgError.message.includes('Permission denied') ||
|
||||
orgError.message.includes('Authentication failed') ||
|
||||
orgError.message.includes('does not have permission'))) {
|
||||
console.warn(`[Fallback] Organization creation/access failed. Attempting to mirror to user account instead.`);
|
||||
|
||||
// Update the repository owner to use the user account
|
||||
repoOwner = config.giteaConfig.defaultOwner;
|
||||
|
||||
// Log this fallback in the database
|
||||
await db
|
||||
.update(repositories)
|
||||
.set({
|
||||
errorMessage: `Organization creation failed, using user account. ${orgError.message}`,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(repositories.id, repository.id!));
|
||||
} else {
|
||||
// Re-throw if it's not a permission issue
|
||||
throw orgError;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if repository already exists as a non-mirror
|
||||
@@ -402,6 +429,7 @@ export const mirrorGithubRepoToGitea = async ({
|
||||
);
|
||||
|
||||
//mirror releases
|
||||
console.log(`[Metadata] Release mirroring check: mirrorReleases=${config.giteaConfig?.mirrorReleases}`);
|
||||
if (config.giteaConfig?.mirrorReleases) {
|
||||
await mirrorGitHubReleasesToGitea({
|
||||
config,
|
||||
@@ -415,6 +443,8 @@ export const mirrorGithubRepoToGitea = async ({
|
||||
const shouldMirrorIssues = config.giteaConfig?.mirrorIssues &&
|
||||
!(repository.isStarred && config.githubConfig?.skipStarredIssues);
|
||||
|
||||
console.log(`[Metadata] Issue mirroring check: mirrorIssues=${config.giteaConfig?.mirrorIssues}, isStarred=${repository.isStarred}, skipStarredIssues=${config.githubConfig?.skipStarredIssues}, shouldMirrorIssues=${shouldMirrorIssues}`);
|
||||
|
||||
if (shouldMirrorIssues) {
|
||||
await mirrorGitRepoIssuesToGitea({
|
||||
config,
|
||||
@@ -425,6 +455,7 @@ export const mirrorGithubRepoToGitea = async ({
|
||||
}
|
||||
|
||||
// Mirror pull requests if enabled
|
||||
console.log(`[Metadata] Pull request mirroring check: mirrorPullRequests=${config.giteaConfig?.mirrorPullRequests}`);
|
||||
if (config.giteaConfig?.mirrorPullRequests) {
|
||||
await mirrorGitRepoPullRequestsToGitea({
|
||||
config,
|
||||
@@ -435,6 +466,7 @@ export const mirrorGithubRepoToGitea = async ({
|
||||
}
|
||||
|
||||
// Mirror labels if enabled (and not already done via issues)
|
||||
console.log(`[Metadata] Label mirroring check: mirrorLabels=${config.giteaConfig?.mirrorLabels}, shouldMirrorIssues=${shouldMirrorIssues}`);
|
||||
if (config.giteaConfig?.mirrorLabels && !shouldMirrorIssues) {
|
||||
await mirrorGitRepoLabelsToGitea({
|
||||
config,
|
||||
@@ -445,6 +477,7 @@ export const mirrorGithubRepoToGitea = async ({
|
||||
}
|
||||
|
||||
// Mirror milestones if enabled
|
||||
console.log(`[Metadata] Milestone mirroring check: mirrorMilestones=${config.giteaConfig?.mirrorMilestones}`);
|
||||
if (config.giteaConfig?.mirrorMilestones) {
|
||||
await mirrorGitRepoMilestonesToGitea({
|
||||
config,
|
||||
@@ -656,6 +689,7 @@ export async function mirrorGitHubRepoToGiteaOrg({
|
||||
);
|
||||
|
||||
//mirror releases
|
||||
console.log(`[Metadata] Release mirroring check: mirrorReleases=${config.giteaConfig?.mirrorReleases}`);
|
||||
if (config.giteaConfig?.mirrorReleases) {
|
||||
await mirrorGitHubReleasesToGitea({
|
||||
config,
|
||||
@@ -679,6 +713,7 @@ export async function mirrorGitHubRepoToGiteaOrg({
|
||||
}
|
||||
|
||||
// Mirror pull requests if enabled
|
||||
console.log(`[Metadata] Pull request mirroring check: mirrorPullRequests=${config.giteaConfig?.mirrorPullRequests}`);
|
||||
if (config.giteaConfig?.mirrorPullRequests) {
|
||||
await mirrorGitRepoPullRequestsToGitea({
|
||||
config,
|
||||
@@ -689,6 +724,7 @@ export async function mirrorGitHubRepoToGiteaOrg({
|
||||
}
|
||||
|
||||
// Mirror labels if enabled (and not already done via issues)
|
||||
console.log(`[Metadata] Label mirroring check: mirrorLabels=${config.giteaConfig?.mirrorLabels}, shouldMirrorIssues=${shouldMirrorIssues}`);
|
||||
if (config.giteaConfig?.mirrorLabels && !shouldMirrorIssues) {
|
||||
await mirrorGitRepoLabelsToGitea({
|
||||
config,
|
||||
@@ -699,6 +735,7 @@ export async function mirrorGitHubRepoToGiteaOrg({
|
||||
}
|
||||
|
||||
// Mirror milestones if enabled
|
||||
console.log(`[Metadata] Milestone mirroring check: mirrorMilestones=${config.giteaConfig?.mirrorMilestones}`);
|
||||
if (config.giteaConfig?.mirrorMilestones) {
|
||||
await mirrorGitRepoMilestonesToGitea({
|
||||
config,
|
||||
@@ -1065,6 +1102,19 @@ export const mirrorGitRepoIssuesToGitea = async ({
|
||||
// Decrypt config tokens for API usage
|
||||
const decryptedConfig = decryptConfigTokens(config as Config);
|
||||
|
||||
// Verify the repository exists in Gitea before attempting to mirror metadata
|
||||
console.log(`[Issues] Verifying repository ${repository.name} exists at ${giteaOwner}`);
|
||||
const repoExists = await isRepoPresentInGitea({
|
||||
config,
|
||||
owner: giteaOwner,
|
||||
repoName: repository.name,
|
||||
});
|
||||
|
||||
if (!repoExists) {
|
||||
console.error(`[Issues] Repository ${repository.name} not found at ${giteaOwner}. Cannot mirror issues.`);
|
||||
throw new Error(`Repository ${repository.name} does not exist in Gitea at ${giteaOwner}. Please ensure the repository is mirrored first.`);
|
||||
}
|
||||
|
||||
const [owner, repo] = repository.fullName.split("/");
|
||||
|
||||
// Fetch GitHub issues
|
||||
@@ -1130,7 +1180,7 @@ export const mirrorGitRepoIssuesToGitea = async ({
|
||||
}/labels`,
|
||||
{ name, color: "#ededed" }, // Default color
|
||||
{
|
||||
Authorization: `token ${config.giteaConfig!.token}`,
|
||||
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1167,7 +1217,7 @@ export const mirrorGitRepoIssuesToGitea = async ({
|
||||
}/issues`,
|
||||
issuePayload,
|
||||
{
|
||||
Authorization: `token ${config.giteaConfig!.token}`,
|
||||
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1196,7 +1246,7 @@ export const mirrorGitRepoIssuesToGitea = async ({
|
||||
body: `@${comment.user?.login} commented on GitHub:\n\n${comment.body}`,
|
||||
},
|
||||
{
|
||||
Authorization: `token ${config.giteaConfig!.token}`,
|
||||
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
||||
}
|
||||
);
|
||||
return comment;
|
||||
@@ -1258,36 +1308,131 @@ export async function mirrorGitHubReleasesToGitea({
|
||||
throw new Error("Gitea config is incomplete for mirroring releases.");
|
||||
}
|
||||
|
||||
// Decrypt config tokens for API usage
|
||||
const decryptedConfig = decryptConfigTokens(config as Config);
|
||||
|
||||
const repoOwner = await getGiteaRepoOwnerAsync({
|
||||
config,
|
||||
repository,
|
||||
});
|
||||
|
||||
const { url, token } = config.giteaConfig;
|
||||
// Verify the repository exists in Gitea before attempting to mirror releases
|
||||
console.log(`[Releases] Verifying repository ${repository.name} exists at ${repoOwner}`);
|
||||
const repoExists = await isRepoPresentInGitea({
|
||||
config,
|
||||
owner: repoOwner,
|
||||
repoName: repository.name,
|
||||
});
|
||||
|
||||
if (!repoExists) {
|
||||
console.error(`[Releases] Repository ${repository.name} not found at ${repoOwner}. Cannot mirror releases.`);
|
||||
throw new Error(`Repository ${repository.name} does not exist in Gitea at ${repoOwner}. Please ensure the repository is mirrored first.`);
|
||||
}
|
||||
|
||||
const releases = await octokit.rest.repos.listReleases({
|
||||
owner: repository.owner,
|
||||
repo: repository.name,
|
||||
});
|
||||
|
||||
for (const release of releases.data) {
|
||||
await httpPost(
|
||||
`${url}/api/v1/repos/${repoOwner}/${repository.name}/releases`,
|
||||
{
|
||||
tag_name: release.tag_name,
|
||||
target: release.target_commitish,
|
||||
title: release.name || release.tag_name,
|
||||
note: release.body || "",
|
||||
draft: release.draft,
|
||||
prerelease: release.prerelease,
|
||||
},
|
||||
{
|
||||
Authorization: `token ${token}`,
|
||||
}
|
||||
);
|
||||
console.log(`[Releases] Found ${releases.data.length} releases to mirror for ${repository.fullName}`);
|
||||
|
||||
if (releases.data.length === 0) {
|
||||
console.log(`[Releases] No releases to mirror for ${repository.fullName}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✅ Mirrored ${releases.data.length} GitHub releases to Gitea`);
|
||||
let mirroredCount = 0;
|
||||
let skippedCount = 0;
|
||||
|
||||
for (const release of releases.data) {
|
||||
try {
|
||||
// Check if release already exists
|
||||
const existingReleasesResponse = await httpGet(
|
||||
`${config.giteaConfig.url}/api/v1/repos/${repoOwner}/${repository.name}/releases/tags/${release.tag_name}`,
|
||||
{
|
||||
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
|
||||
}
|
||||
).catch(() => null);
|
||||
|
||||
if (existingReleasesResponse) {
|
||||
console.log(`[Releases] Release ${release.tag_name} already exists, skipping`);
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create the release
|
||||
const createReleaseResponse = await httpPost(
|
||||
`${config.giteaConfig.url}/api/v1/repos/${repoOwner}/${repository.name}/releases`,
|
||||
{
|
||||
tag_name: release.tag_name,
|
||||
target: release.target_commitish,
|
||||
title: release.name || release.tag_name,
|
||||
note: release.body || "",
|
||||
draft: release.draft,
|
||||
prerelease: release.prerelease,
|
||||
},
|
||||
{
|
||||
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
|
||||
}
|
||||
);
|
||||
|
||||
// Mirror release assets if they exist
|
||||
if (release.assets && release.assets.length > 0) {
|
||||
console.log(`[Releases] Mirroring ${release.assets.length} assets for release ${release.tag_name}`);
|
||||
|
||||
for (const asset of release.assets) {
|
||||
try {
|
||||
// Download the asset from GitHub
|
||||
console.log(`[Releases] Downloading asset: ${asset.name} (${asset.size} bytes)`);
|
||||
const assetResponse = await fetch(asset.browser_download_url, {
|
||||
headers: {
|
||||
'Accept': 'application/octet-stream',
|
||||
'Authorization': `token ${decryptedConfig.githubConfig.token}`,
|
||||
},
|
||||
});
|
||||
|
||||
if (!assetResponse.ok) {
|
||||
console.error(`[Releases] Failed to download asset ${asset.name}: ${assetResponse.statusText}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const assetData = await assetResponse.arrayBuffer();
|
||||
|
||||
// Upload the asset to Gitea release
|
||||
const formData = new FormData();
|
||||
formData.append('attachment', new Blob([assetData]), asset.name);
|
||||
|
||||
const uploadResponse = await fetch(
|
||||
`${config.giteaConfig.url}/api/v1/repos/${repoOwner}/${repository.name}/releases/${createReleaseResponse.data.id}/assets?name=${encodeURIComponent(asset.name)}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `token ${decryptedConfig.giteaConfig.token}`,
|
||||
},
|
||||
body: formData,
|
||||
}
|
||||
);
|
||||
|
||||
if (uploadResponse.ok) {
|
||||
console.log(`[Releases] Successfully uploaded asset: ${asset.name}`);
|
||||
} else {
|
||||
const errorText = await uploadResponse.text();
|
||||
console.error(`[Releases] Failed to upload asset ${asset.name}: ${errorText}`);
|
||||
}
|
||||
} catch (assetError) {
|
||||
console.error(`[Releases] Error processing asset ${asset.name}: ${assetError instanceof Error ? assetError.message : String(assetError)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mirroredCount++;
|
||||
console.log(`[Releases] Successfully mirrored release: ${release.tag_name}`);
|
||||
} catch (error) {
|
||||
console.error(`[Releases] Failed to mirror release ${release.tag_name}: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ Mirrored ${mirroredCount} new releases to Gitea (${skippedCount} already existed)`);
|
||||
}
|
||||
|
||||
export async function mirrorGitRepoPullRequestsToGitea({
|
||||
@@ -1313,6 +1458,19 @@ export async function mirrorGitRepoPullRequestsToGitea({
|
||||
// Decrypt config tokens for API usage
|
||||
const decryptedConfig = decryptConfigTokens(config as Config);
|
||||
|
||||
// Verify the repository exists in Gitea before attempting to mirror metadata
|
||||
console.log(`[Pull Requests] Verifying repository ${repository.name} exists at ${giteaOwner}`);
|
||||
const repoExists = await isRepoPresentInGitea({
|
||||
config,
|
||||
owner: giteaOwner,
|
||||
repoName: repository.name,
|
||||
});
|
||||
|
||||
if (!repoExists) {
|
||||
console.error(`[Pull Requests] Repository ${repository.name} not found at ${giteaOwner}. Cannot mirror PRs.`);
|
||||
throw new Error(`Repository ${repository.name} does not exist in Gitea at ${giteaOwner}. Please ensure the repository is mirrored first.`);
|
||||
}
|
||||
|
||||
const [owner, repo] = repository.fullName.split("/");
|
||||
|
||||
// Fetch GitHub pull requests
|
||||
@@ -1415,6 +1573,19 @@ export async function mirrorGitRepoLabelsToGitea({
|
||||
// Decrypt config tokens for API usage
|
||||
const decryptedConfig = decryptConfigTokens(config as Config);
|
||||
|
||||
// Verify the repository exists in Gitea before attempting to mirror metadata
|
||||
console.log(`[Labels] Verifying repository ${repository.name} exists at ${giteaOwner}`);
|
||||
const repoExists = await isRepoPresentInGitea({
|
||||
config,
|
||||
owner: giteaOwner,
|
||||
repoName: repository.name,
|
||||
});
|
||||
|
||||
if (!repoExists) {
|
||||
console.error(`[Labels] Repository ${repository.name} not found at ${giteaOwner}. Cannot mirror labels.`);
|
||||
throw new Error(`Repository ${repository.name} does not exist in Gitea at ${giteaOwner}. Please ensure the repository is mirrored first.`);
|
||||
}
|
||||
|
||||
const [owner, repo] = repository.fullName.split("/");
|
||||
|
||||
// Fetch GitHub labels
|
||||
@@ -1496,6 +1667,19 @@ export async function mirrorGitRepoMilestonesToGitea({
|
||||
// Decrypt config tokens for API usage
|
||||
const decryptedConfig = decryptConfigTokens(config as Config);
|
||||
|
||||
// Verify the repository exists in Gitea before attempting to mirror metadata
|
||||
console.log(`[Milestones] Verifying repository ${repository.name} exists at ${giteaOwner}`);
|
||||
const repoExists = await isRepoPresentInGitea({
|
||||
config,
|
||||
owner: giteaOwner,
|
||||
repoName: repository.name,
|
||||
});
|
||||
|
||||
if (!repoExists) {
|
||||
console.error(`[Milestones] Repository ${repository.name} not found at ${giteaOwner}. Cannot mirror milestones.`);
|
||||
throw new Error(`Repository ${repository.name} does not exist in Gitea at ${giteaOwner}. Please ensure the repository is mirrored first.`);
|
||||
}
|
||||
|
||||
const [owner, repo] = repository.fullName.split("/");
|
||||
|
||||
// Fetch GitHub milestones
|
||||
|
||||
@@ -47,12 +47,32 @@ export async function httpRequest<T = any>(
|
||||
try {
|
||||
responseText = await responseClone.text();
|
||||
if (responseText) {
|
||||
errorMessage += ` - ${responseText}`;
|
||||
// Try to parse as JSON for better error messages
|
||||
try {
|
||||
const errorData = JSON.parse(responseText);
|
||||
if (errorData.message) {
|
||||
errorMessage = `HTTP ${response.status}: ${errorData.message}`;
|
||||
} else {
|
||||
errorMessage += ` - ${responseText}`;
|
||||
}
|
||||
} catch {
|
||||
// Not JSON, use as-is
|
||||
errorMessage += ` - ${responseText}`;
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore text parsing errors
|
||||
}
|
||||
|
||||
// Log authentication-specific errors for debugging
|
||||
if (response.status === 401) {
|
||||
console.error(`[HTTP Client] Authentication failed for ${url}`);
|
||||
console.error(`[HTTP Client] Response: ${responseText}`);
|
||||
if (responseText.includes('user does not exist') && responseText.includes('uid: 0')) {
|
||||
console.error(`[HTTP Client] Token appears to be invalid or the user account is not properly configured in Gitea`);
|
||||
}
|
||||
}
|
||||
|
||||
throw new HttpError(
|
||||
errorMessage,
|
||||
response.status,
|
||||
|
||||
161
src/tests/test-gitea-auth.ts
Normal file
161
src/tests/test-gitea-auth.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
/**
|
||||
* Test script to validate Gitea authentication and permissions
|
||||
* Run with: bun run src/tests/test-gitea-auth.ts
|
||||
*/
|
||||
|
||||
import { validateGiteaAuth, canCreateOrganizations, validateGiteaConfigForMirroring } from "@/lib/gitea-auth-validator";
|
||||
import { getConfigsByUserId } from "@/lib/db/queries/configs";
|
||||
import { db, users } from "@/lib/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
async function testGiteaAuthentication() {
|
||||
console.log("=".repeat(60));
|
||||
console.log("GITEA AUTHENTICATION TEST");
|
||||
console.log("=".repeat(60));
|
||||
|
||||
try {
|
||||
// Get the first user for testing
|
||||
const userList = await db.select().from(users).limit(1);
|
||||
|
||||
if (userList.length === 0) {
|
||||
console.error("❌ No users found in database. Please set up a user first.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const user = userList[0];
|
||||
console.log(`\n✅ Found user: ${user.email} (ID: ${user.id})`);
|
||||
|
||||
// Get the user's configuration
|
||||
const configs = await getConfigsByUserId(user.id);
|
||||
|
||||
if (configs.length === 0) {
|
||||
console.error("❌ No configuration found for user. Please configure Gitea settings.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const config = configs[0];
|
||||
console.log(`✅ Found configuration (ID: ${config.id})`);
|
||||
|
||||
if (!config.giteaConfig?.url || !config.giteaConfig?.token) {
|
||||
console.error("❌ Gitea configuration is incomplete. URL or token is missing.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\n📡 Testing connection to: ${config.giteaConfig.url}`);
|
||||
console.log("-".repeat(60));
|
||||
|
||||
// Test 1: Validate authentication
|
||||
console.log("\n🔐 Test 1: Validating authentication...");
|
||||
try {
|
||||
const giteaUser = await validateGiteaAuth(config);
|
||||
console.log(`✅ Authentication successful!`);
|
||||
console.log(` - Username: ${giteaUser.username || giteaUser.login}`);
|
||||
console.log(` - User ID: ${giteaUser.id}`);
|
||||
console.log(` - Is Admin: ${giteaUser.is_admin}`);
|
||||
console.log(` - Email: ${giteaUser.email || 'Not provided'}`);
|
||||
} catch (error) {
|
||||
console.error(`❌ Authentication failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Test 2: Check organization creation permissions
|
||||
console.log("\n🏢 Test 2: Checking organization creation permissions...");
|
||||
try {
|
||||
const canCreate = await canCreateOrganizations(config);
|
||||
if (canCreate) {
|
||||
console.log(`✅ User can create organizations`);
|
||||
} else {
|
||||
console.log(`⚠️ User cannot create organizations (will use fallback to user account)`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Error checking permissions: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
|
||||
// Test 3: Full validation for mirroring
|
||||
console.log("\n🔍 Test 3: Full validation for mirroring...");
|
||||
try {
|
||||
const validation = await validateGiteaConfigForMirroring(config);
|
||||
|
||||
if (validation.valid) {
|
||||
console.log(`✅ Configuration is valid for mirroring`);
|
||||
} else {
|
||||
console.log(`❌ Configuration is not valid for mirroring`);
|
||||
}
|
||||
|
||||
if (validation.warnings.length > 0) {
|
||||
console.log(`\n⚠️ Warnings:`);
|
||||
validation.warnings.forEach(warning => {
|
||||
console.log(` - ${warning}`);
|
||||
});
|
||||
}
|
||||
|
||||
if (validation.errors.length > 0) {
|
||||
console.log(`\n❌ Errors:`);
|
||||
validation.errors.forEach(error => {
|
||||
console.log(` - ${error}`);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Validation error: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
|
||||
// Test 4: Check specific API endpoints
|
||||
console.log("\n🔧 Test 4: Testing specific API endpoints...");
|
||||
|
||||
// Import HTTP client for direct API testing
|
||||
const { httpGet } = await import("@/lib/http-client");
|
||||
const { decryptConfigTokens } = await import("@/lib/utils/config-encryption");
|
||||
const decryptedConfig = decryptConfigTokens(config);
|
||||
|
||||
// Test organization listing
|
||||
try {
|
||||
const orgsResponse = await httpGet(
|
||||
`${config.giteaConfig.url}/api/v1/user/orgs`,
|
||||
{
|
||||
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
|
||||
}
|
||||
);
|
||||
console.log(`✅ Can list organizations (found ${orgsResponse.data.length})`);
|
||||
} catch (error) {
|
||||
console.log(`⚠️ Cannot list organizations: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
}
|
||||
|
||||
// Test repository listing
|
||||
try {
|
||||
const reposResponse = await httpGet(
|
||||
`${config.giteaConfig.url}/api/v1/user/repos`,
|
||||
{
|
||||
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
|
||||
}
|
||||
);
|
||||
console.log(`✅ Can list repositories (found ${reposResponse.data.length})`);
|
||||
} catch (error) {
|
||||
console.error(`❌ Cannot list repositories: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
}
|
||||
|
||||
console.log("\n" + "=".repeat(60));
|
||||
console.log("TEST COMPLETE");
|
||||
console.log("=".repeat(60));
|
||||
|
||||
// Summary
|
||||
console.log("\n📊 Summary:");
|
||||
console.log(` - Gitea URL: ${config.giteaConfig.url}`);
|
||||
console.log(` - Default Owner: ${config.giteaConfig.defaultOwner || 'Not set'}`);
|
||||
console.log(` - Mirror Strategy: ${config.githubConfig?.mirrorStrategy || 'Not set'}`);
|
||||
console.log(` - Organization: ${config.giteaConfig.organization || 'Not set'}`);
|
||||
console.log(` - Preserve Org Structure: ${config.giteaConfig.preserveOrgStructure || false}`);
|
||||
|
||||
console.log("\n✨ All tests completed successfully!");
|
||||
|
||||
} catch (error) {
|
||||
console.error("\n❌ Test failed with error:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Run the test
|
||||
testGiteaAuthentication().catch(console.error);
|
||||
173
src/tests/test-metadata-mirroring.ts
Normal file
173
src/tests/test-metadata-mirroring.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
/**
|
||||
* Test script to verify metadata mirroring authentication works correctly
|
||||
* This tests the fix for issue #68 - "user does not exist [uid: 0, name: ]"
|
||||
* Run with: bun run src/tests/test-metadata-mirroring.ts
|
||||
*/
|
||||
|
||||
import { mirrorGitRepoIssuesToGitea } from "@/lib/gitea";
|
||||
import { validateGiteaAuth } from "@/lib/gitea-auth-validator";
|
||||
import { getConfigsByUserId } from "@/lib/db/queries/configs";
|
||||
import { db, users, repositories } from "@/lib/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import type { Repository } from "@/lib/db/schema";
|
||||
|
||||
async function testMetadataMirroringAuth() {
|
||||
console.log("=".repeat(60));
|
||||
console.log("METADATA MIRRORING AUTHENTICATION TEST");
|
||||
console.log("=".repeat(60));
|
||||
|
||||
try {
|
||||
// Get the first user for testing
|
||||
const userList = await db.select().from(users).limit(1);
|
||||
|
||||
if (userList.length === 0) {
|
||||
console.error("❌ No users found in database. Please set up a user first.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const user = userList[0];
|
||||
console.log(`\n✅ Found user: ${user.email} (ID: ${user.id})`);
|
||||
|
||||
// Get the user's configuration
|
||||
const configs = await getConfigsByUserId(user.id);
|
||||
|
||||
if (configs.length === 0) {
|
||||
console.error("❌ No configuration found for user. Please configure GitHub and Gitea settings.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const config = configs[0];
|
||||
console.log(`✅ Found configuration (ID: ${config.id})`);
|
||||
|
||||
if (!config.giteaConfig?.url || !config.giteaConfig?.token) {
|
||||
console.error("❌ Gitea configuration is incomplete. URL or token is missing.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!config.githubConfig?.token) {
|
||||
console.error("❌ GitHub configuration is incomplete. Token is missing.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\n📡 Testing Gitea connection to: ${config.giteaConfig.url}`);
|
||||
console.log("-".repeat(60));
|
||||
|
||||
// Test 1: Validate Gitea authentication
|
||||
console.log("\n🔐 Test 1: Validating Gitea authentication...");
|
||||
let giteaUser;
|
||||
try {
|
||||
giteaUser = await validateGiteaAuth(config);
|
||||
console.log(`✅ Gitea authentication successful!`);
|
||||
console.log(` - Username: ${giteaUser.username || giteaUser.login}`);
|
||||
console.log(` - User ID: ${giteaUser.id}`);
|
||||
console.log(` - Is Admin: ${giteaUser.is_admin}`);
|
||||
} catch (error) {
|
||||
console.error(`❌ Gitea authentication failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
console.error(` This is the root cause of the "user does not exist [uid: 0]" error`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Test 2: Check if we can access a test repository
|
||||
console.log("\n📦 Test 2: Looking for a test repository...");
|
||||
|
||||
// Get a repository from the database
|
||||
const repos = await db.select().from(repositories)
|
||||
.where(eq(repositories.userId, user.id))
|
||||
.limit(1);
|
||||
|
||||
if (repos.length === 0) {
|
||||
console.log("⚠️ No repositories found in database. Skipping metadata mirroring test.");
|
||||
console.log(" Please run a mirror operation first to test metadata mirroring.");
|
||||
} else {
|
||||
const testRepo = repos[0] as Repository;
|
||||
console.log(`✅ Found test repository: ${testRepo.fullName}`);
|
||||
|
||||
// Test 3: Verify repository exists in Gitea
|
||||
console.log("\n🔍 Test 3: Verifying repository exists in Gitea...");
|
||||
|
||||
const { isRepoPresentInGitea } = await import("@/lib/gitea");
|
||||
const giteaOwner = giteaUser.username || giteaUser.login;
|
||||
|
||||
const repoExists = await isRepoPresentInGitea({
|
||||
config,
|
||||
owner: giteaOwner,
|
||||
repoName: testRepo.name,
|
||||
});
|
||||
|
||||
if (!repoExists) {
|
||||
console.log(`⚠️ Repository ${testRepo.name} not found in Gitea at ${giteaOwner}`);
|
||||
console.log(` This would cause metadata mirroring to fail with authentication errors`);
|
||||
console.log(` Please ensure the repository is mirrored first before attempting metadata sync`);
|
||||
} else {
|
||||
console.log(`✅ Repository exists in Gitea at ${giteaOwner}/${testRepo.name}`);
|
||||
|
||||
// Test 4: Attempt to mirror metadata (dry run)
|
||||
console.log("\n🔄 Test 4: Testing metadata mirroring authentication...");
|
||||
|
||||
try {
|
||||
// Create Octokit instance
|
||||
const octokit = new Octokit({
|
||||
auth: config.githubConfig.token,
|
||||
});
|
||||
|
||||
// Test by attempting to fetch labels (lightweight operation)
|
||||
const { httpGet } = await import("@/lib/http-client");
|
||||
const { decryptConfigTokens } = await import("@/lib/utils/config-encryption");
|
||||
const decryptedConfig = decryptConfigTokens(config);
|
||||
|
||||
const labelsResponse = await httpGet(
|
||||
`${config.giteaConfig.url}/api/v1/repos/${giteaOwner}/${testRepo.name}/labels`,
|
||||
{
|
||||
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
|
||||
}
|
||||
);
|
||||
|
||||
console.log(`✅ Successfully authenticated for metadata operations`);
|
||||
console.log(` - Can access repository labels endpoint`);
|
||||
console.log(` - Found ${labelsResponse.data.length} existing labels`);
|
||||
console.log(` - Authentication token is valid and has proper permissions`);
|
||||
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message.includes('uid: 0')) {
|
||||
console.error(`❌ CRITICAL: Authentication failed with "uid: 0" error!`);
|
||||
console.error(` This is the exact issue from GitHub issue #68`);
|
||||
console.error(` Error: ${error.message}`);
|
||||
} else {
|
||||
console.error(`❌ Metadata operation failed: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("\n" + "=".repeat(60));
|
||||
console.log("TEST COMPLETE");
|
||||
console.log("=".repeat(60));
|
||||
|
||||
// Summary
|
||||
console.log("\n📊 Summary:");
|
||||
console.log(` - Gitea URL: ${config.giteaConfig.url}`);
|
||||
console.log(` - Gitea User: ${giteaUser?.username || giteaUser?.login || 'Unknown'}`);
|
||||
console.log(` - Authentication: ${giteaUser ? '✅ Valid' : '❌ Invalid'}`);
|
||||
console.log(` - Metadata Mirroring: ${config.giteaConfig.mirrorMetadata ? 'Enabled' : 'Disabled'}`);
|
||||
if (config.giteaConfig.mirrorMetadata) {
|
||||
console.log(` - Issues: ${config.giteaConfig.mirrorIssues ? 'Yes' : 'No'}`);
|
||||
console.log(` - Pull Requests: ${config.giteaConfig.mirrorPullRequests ? 'Yes' : 'No'}`);
|
||||
console.log(` - Labels: ${config.giteaConfig.mirrorLabels ? 'Yes' : 'No'}`);
|
||||
console.log(` - Milestones: ${config.giteaConfig.mirrorMilestones ? 'Yes' : 'No'}`);
|
||||
}
|
||||
|
||||
console.log("\n✨ If all tests passed, metadata mirroring should work without uid:0 errors!");
|
||||
|
||||
} catch (error) {
|
||||
console.error("\n❌ Test failed with error:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Run the test
|
||||
testMetadataMirroringAuth().catch(console.error);
|
||||
Reference in New Issue
Block a user