🎉 Gitea Mirror: Added

This commit is contained in:
Arunavo Ray
2025-05-18 09:31:23 +05:30
commit 5d40023de0
139 changed files with 22033 additions and 0 deletions

150
src/hooks/useAuth.ts Normal file
View File

@@ -0,0 +1,150 @@
import * as React from "react";
import {
useState,
useEffect,
createContext,
useContext,
type Context,
} from "react";
import { authApi } from "@/lib/api";
import type { ExtendedUser } from "@/types/user";
interface AuthContextType {
user: ExtendedUser | null;
isLoading: boolean;
error: string | null;
login: (username: string, password: string) => Promise<void>;
register: (
username: string,
email: string,
password: string
) => Promise<void>;
logout: () => Promise<void>;
refreshUser: () => Promise<void>; // Added refreshUser function
}
const AuthContext: Context<AuthContextType | undefined> = createContext<
AuthContextType | undefined
>(undefined);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<ExtendedUser | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// Function to refetch the user data
const refreshUser = async () => {
// not using loading state to keep the ui seamless and refresh the data in bg
// setIsLoading(true);
try {
const user = await authApi.getCurrentUser();
console.log("User data refreshed:", user);
setUser(user);
} catch (err: any) {
setUser(null);
console.error("Failed to refresh user data", err);
} finally {
// setIsLoading(false);
}
};
// Automatically check the user status when the app loads
useEffect(() => {
const checkAuth = async () => {
try {
const user = await authApi.getCurrentUser();
console.log("User data fetched:", user);
setUser(user);
} catch (err: any) {
setUser(null);
// Redirect user based on error
if (err?.message === "No users found") {
window.location.href = "/signup";
} else {
window.location.href = "/login";
}
console.error("Auth check failed", err);
} finally {
setIsLoading(false);
}
};
checkAuth();
}, []);
const login = async (username: string, password: string) => {
setIsLoading(true);
setError(null);
try {
const user = await authApi.login(username, password);
setUser(user);
} catch (err) {
setError(err instanceof Error ? err.message : "Login failed");
throw err;
} finally {
setIsLoading(false);
}
};
const register = async (
username: string,
email: string,
password: string
) => {
setIsLoading(true);
setError(null);
try {
const user = await authApi.register(username, email, password);
setUser(user);
} catch (err) {
setError(err instanceof Error ? err.message : "Registration failed");
throw err;
} finally {
setIsLoading(false);
}
};
const logout = async () => {
setIsLoading(true);
try {
await authApi.logout();
setUser(null);
window.location.href = "/login";
} catch (err) {
console.error("Logout error:", err);
} finally {
setIsLoading(false);
}
};
// Create the context value with the added refreshUser function
const contextValue = {
user,
isLoading,
error,
login,
register,
logout,
refreshUser,
};
// Return the provider with the context value
return React.createElement(
AuthContext.Provider,
{ value: contextValue },
children
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
}

View File

@@ -0,0 +1,59 @@
import { useState, useEffect } from "react";
import type { FilterParams } from "@/types/filter";
const FILTER_KEYS: (keyof FilterParams)[] = [
"searchTerm",
"status",
"membershipRole",
"owner",
"organization",
"type",
"name",
];
export const useFilterParams = (
defaultFilters: FilterParams,
debounceDelay = 300
) => {
const getInitialFilter = (): FilterParams => {
if (typeof window === "undefined") return defaultFilters;
const params = new URLSearchParams(window.location.search);
const result: FilterParams = { ...defaultFilters };
FILTER_KEYS.forEach((key) => {
const value = params.get(key);
if (value !== null) {
(result as any)[key] = value;
}
});
return result;
};
const [filter, setFilter] = useState<FilterParams>(() => getInitialFilter());
// Debounced URL update
useEffect(() => {
const handler = setTimeout(() => {
const params = new URLSearchParams();
FILTER_KEYS.forEach((key) => {
const value = filter[key];
if (value) {
params.set(key, String(value));
}
});
const newUrl = `${window.location.pathname}?${params.toString()}`;
window.history.replaceState({}, "", newUrl);
}, debounceDelay);
return () => clearTimeout(handler); // Cleanup on unmount or when `filter` changes
}, [filter, debounceDelay]);
return {
filter,
setFilter,
};
};

83
src/hooks/useMirror.ts Normal file
View File

@@ -0,0 +1,83 @@
import { useState } from 'react';
import { mirrorApi } from '@/lib/api';
import type { MirrorJob } from '@/lib/db/schema';
export function useMirror() {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [currentJob, setCurrentJob] = useState<MirrorJob | null>(null);
const [jobs, setJobs] = useState<MirrorJob[]>([]);
const startMirror = async (configId: string, repositoryIds?: string[]) => {
setIsLoading(true);
setError(null);
try {
const job = await mirrorApi.startMirror(configId, repositoryIds);
setCurrentJob(job);
return job;
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to start mirroring');
throw err;
} finally {
setIsLoading(false);
}
};
const getMirrorJobs = async (configId: string) => {
setIsLoading(true);
setError(null);
try {
const fetchedJobs = await mirrorApi.getMirrorJobs(configId);
setJobs(fetchedJobs);
return fetchedJobs;
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch mirror jobs');
throw err;
} finally {
setIsLoading(false);
}
};
const getMirrorJob = async (jobId: string) => {
setIsLoading(true);
setError(null);
try {
const job = await mirrorApi.getMirrorJob(jobId);
setCurrentJob(job);
return job;
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch mirror job');
throw err;
} finally {
setIsLoading(false);
}
};
const cancelMirrorJob = async (jobId: string) => {
setIsLoading(true);
setError(null);
try {
const result = await mirrorApi.cancelMirrorJob(jobId);
if (result.success && currentJob?.id === jobId) {
setCurrentJob({ ...currentJob, status: 'failed' });
}
return result;
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to cancel mirror job');
throw err;
} finally {
setIsLoading(false);
}
};
return {
isLoading,
error,
currentJob,
jobs,
startMirror,
getMirrorJobs,
getMirrorJob,
cancelMirrorJob,
};
}

54
src/hooks/useSEE.ts Normal file
View File

@@ -0,0 +1,54 @@
import { useEffect, useState, useRef } from "react";
import type { MirrorJob } from "@/lib/db/schema";
interface UseSSEOptions {
userId?: string;
onMessage: (data: MirrorJob) => void;
}
export const useSSE = ({ userId, onMessage }: UseSSEOptions) => {
const [connected, setConnected] = useState<boolean>(false);
const onMessageRef = useRef(onMessage);
// Update the ref when onMessage changes
useEffect(() => {
onMessageRef.current = onMessage;
}, [onMessage]);
useEffect(() => {
if (!userId) return;
const eventSource = new EventSource(`/api/sse?userId=${userId}`);
const handleMessage = (event: MessageEvent) => {
try {
const parsedMessage: MirrorJob = JSON.parse(event.data);
// console.log("Received new log:", parsedMessage);
onMessageRef.current(parsedMessage); // Use ref instead of prop directly
} catch (error) {
console.error("Error parsing message:", error);
}
};
eventSource.onmessage = handleMessage;
eventSource.onopen = () => {
setConnected(true);
console.log(`Connected to SSE for user: ${userId}`);
};
eventSource.onerror = () => {
console.error("SSE connection error");
setConnected(false);
eventSource.close();
};
return () => {
eventSource.close();
};
}, [userId]); // Only depends on userId now
return { connected };
};

102
src/hooks/useSyncRepo.ts Normal file
View File

@@ -0,0 +1,102 @@
import { useEffect, useRef } from "react";
import { useAuth } from "./useAuth";
interface UseRepoSyncOptions {
userId?: string;
enabled?: boolean;
interval?: number;
lastSync?: Date | null;
nextSync?: Date | null;
}
export function useRepoSync({
userId,
enabled = true,
interval = 3600,
lastSync,
nextSync,
}: UseRepoSyncOptions) {
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const { refreshUser } = useAuth();
useEffect(() => {
if (!enabled || !userId) {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
return;
}
// Helper to convert possible nextSync types to Date
const getNextSyncDate = () => {
if (!nextSync) return null;
if (nextSync instanceof Date) return nextSync;
return new Date(nextSync); // Handles strings and numbers
};
const getLastSyncDate = () => {
if (!lastSync) return null;
if (lastSync instanceof Date) return lastSync;
return new Date(lastSync);
};
const isTimeToSync = () => {
const nextSyncDate = getNextSyncDate();
if (!nextSyncDate) return true; // No nextSync means sync immediately
const currentTime = new Date();
return currentTime >= nextSyncDate;
};
const sync = async () => {
try {
console.log("Attempting to sync...");
const response = await fetch("/api/job/schedule-sync-repo", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ userId }),
});
if (!response.ok) {
console.error("Sync failed:", await response.text());
return;
}
await refreshUser(); // refresh user data to get latest sync times. this can be taken from the schedule-sync-repo response but might not be reliable in cases of errors
const result = await response.json();
console.log("Sync successful:", result);
return result;
} catch (error) {
console.error("Sync failed:", error);
}
};
// Check if sync is overdue when the component mounts or interval passes
if (isTimeToSync()) {
sync();
}
// Periodically check if it's time to sync
intervalRef.current = setInterval(() => {
if (isTimeToSync()) {
sync();
}
}, interval * 1000);
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
};
}, [
enabled,
interval,
userId,
nextSync instanceof Date ? nextSync.getTime() : nextSync,
lastSync instanceof Date ? lastSync.getTime() : lastSync,
]);
}