mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-12 06:26:47 +03:00
fix: Complete Issue #72 - Fix automatic mirroring and repository cleanup
Major fixes for Docker environment variable issues and cleanup functionality: 🔧 **Duration Parser & Scheduler Fixes** - Add comprehensive duration parser supporting "8h", "30m", "24h" formats - Fix GITEA_MIRROR_INTERVAL environment variable mapping to scheduler - Auto-enable scheduler when GITEA_MIRROR_INTERVAL is set - Improve scheduler logging to clarify timing behavior (from last run, not startup) 🧹 **Repository Cleanup Service** - Complete repository cleanup service for orphaned repos (unstarred, deleted) - Fix cleanup configuration logic - now works with CLEANUP_DELETE_IF_NOT_IN_GITHUB=true - Auto-enable cleanup when deleteIfNotInGitHub is enabled - Add manual cleanup trigger API endpoint (/api/cleanup/trigger) - Support archive/delete actions with dry-run mode and protected repos 🐛 **Environment Variable Integration** - Fix scheduler not recognizing GITEA_MIRROR_INTERVAL=8h - Fix cleanup requiring both CLEANUP_DELETE_FROM_GITEA and CLEANUP_DELETE_IF_NOT_IN_GITHUB - Auto-enable services when relevant environment variables are set - Better error logging and debugging information 📚 **Documentation Updates** - Update .env.example with auto-enabling behavior notes - Update ENVIRONMENT_VARIABLES.md with clarified functionality - Add comprehensive tests for duration parsing This resolves the core issues where: 1. GITEA_MIRROR_INTERVAL=8h was not working for automatic mirroring 2. Repository cleanup was not working despite CLEANUP_DELETE_IF_NOT_IN_GITHUB=true 3. Users had no visibility into why scheduling/cleanup wasn't working 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
130
src/pages/api/cleanup/trigger.ts
Normal file
130
src/pages/api/cleanup/trigger.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { auth } from '@/lib/auth';
|
||||
import { createSecureErrorResponse } from '@/lib/utils/error-handler';
|
||||
import { triggerRepositoryCleanup } from '@/lib/repository-cleanup-service';
|
||||
|
||||
/**
|
||||
* Manually trigger repository cleanup for the current user
|
||||
* This can be called when repositories are updated or when immediate cleanup is needed
|
||||
*/
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
// Get user session
|
||||
const session = await auth.api.getSession({
|
||||
headers: request.headers,
|
||||
});
|
||||
|
||||
if (!session?.user?.id) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Unauthorized' }),
|
||||
{
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`[Cleanup API] Manual cleanup triggered for user ${session.user.id}`);
|
||||
|
||||
// Trigger immediate cleanup for this user
|
||||
const results = await triggerRepositoryCleanup(session.user.id);
|
||||
|
||||
console.log(`[Cleanup API] Cleanup completed: ${results.processedCount}/${results.orphanedCount} repositories processed, ${results.errors.length} errors`);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: 'Repository cleanup completed',
|
||||
results: {
|
||||
orphanedCount: results.orphanedCount,
|
||||
processedCount: results.processedCount,
|
||||
errorCount: results.errors.length,
|
||||
errors: results.errors,
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('[Cleanup API] Error during manual cleanup:', error);
|
||||
return createSecureErrorResponse(error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get cleanup status and configuration for the current user
|
||||
*/
|
||||
export const GET: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
// Get user session
|
||||
const session = await auth.api.getSession({
|
||||
headers: request.headers,
|
||||
});
|
||||
|
||||
if (!session?.user?.id) {
|
||||
return new Response(
|
||||
JSON.stringify({ error: 'Unauthorized' }),
|
||||
{
|
||||
status: 401,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Import inside the function to avoid import issues
|
||||
const { db, configs } = await import('@/lib/db');
|
||||
const { eq, and } = await import('drizzle-orm');
|
||||
|
||||
// Get user's cleanup configuration
|
||||
const [config] = await db
|
||||
.select()
|
||||
.from(configs)
|
||||
.where(and(
|
||||
eq(configs.userId, session.user.id),
|
||||
eq(configs.isActive, true)
|
||||
))
|
||||
.limit(1);
|
||||
|
||||
if (!config) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
message: 'No active configuration found',
|
||||
cleanupEnabled: false,
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const cleanupConfig = config.cleanupConfig || {};
|
||||
const isCleanupEnabled = cleanupConfig.enabled || cleanupConfig.deleteIfNotInGitHub;
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
cleanupEnabled: isCleanupEnabled,
|
||||
configuration: {
|
||||
enabled: cleanupConfig.enabled,
|
||||
deleteFromGitea: cleanupConfig.deleteFromGitea,
|
||||
deleteIfNotInGitHub: cleanupConfig.deleteIfNotInGitHub,
|
||||
dryRun: cleanupConfig.dryRun,
|
||||
orphanedRepoAction: cleanupConfig.orphanedRepoAction || 'archive',
|
||||
lastRun: cleanupConfig.lastRun,
|
||||
nextRun: cleanupConfig.nextRun,
|
||||
},
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('[Cleanup API] Error getting cleanup status:', error);
|
||||
return createSecureErrorResponse(error);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user