mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-13 06:56:45 +03:00
🎉 Gitea Mirror: Added
This commit is contained in:
150
src/hooks/useAuth.ts
Normal file
150
src/hooks/useAuth.ts
Normal 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;
|
||||
}
|
||||
59
src/hooks/useFilterParams.ts
Normal file
59
src/hooks/useFilterParams.ts
Normal 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
83
src/hooks/useMirror.ts
Normal 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
54
src/hooks/useSEE.ts
Normal 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
102
src/hooks/useSyncRepo.ts
Normal 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,
|
||||
]);
|
||||
}
|
||||
Reference in New Issue
Block a user