migrate to nextjs
This commit is contained in:
33
src/lib/actions/auth.ts
Normal file
33
src/lib/actions/auth.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
'use server';
|
||||
|
||||
import { redirect } from 'next/navigation';
|
||||
import { loginUser } from '@/lib/api/server/auth';
|
||||
import { login, logout } from '@/lib/auth/server';
|
||||
|
||||
export async function loginAction(formData: FormData) {
|
||||
try {
|
||||
const username = formData.get('username') as string;
|
||||
const password = formData.get('password') as string;
|
||||
|
||||
if (!username || !password) {
|
||||
throw new Error('Username and password are required');
|
||||
}
|
||||
|
||||
const result = await loginUser(username, password);
|
||||
|
||||
if (result.token && result.user) {
|
||||
await login(result.token, result.user);
|
||||
} else {
|
||||
throw new Error('Invalid credentials');
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(error instanceof Error ? error.message : 'Authentication failed');
|
||||
}
|
||||
|
||||
redirect('/dashboard');
|
||||
}
|
||||
|
||||
export async function logoutAction() {
|
||||
await logout();
|
||||
redirect('/login');
|
||||
}
|
||||
197
src/lib/actions/configuration.ts
Normal file
197
src/lib/actions/configuration.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
'use server';
|
||||
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { requireAuth } from '@/lib/auth/server';
|
||||
import { updateServerConfiguration } from '@/lib/api/server/configuration';
|
||||
import { ConfigFile } from '@/lib/types/config';
|
||||
import type {
|
||||
Configuration,
|
||||
AssistRules,
|
||||
EventConfig,
|
||||
EventRules,
|
||||
ServerSettings
|
||||
} from '@/lib/types/config';
|
||||
|
||||
export async function updateConfigurationAction(serverId: string, formData: FormData) {
|
||||
try {
|
||||
const session = await requireAuth();
|
||||
const restart = formData.get('restart') === 'on';
|
||||
|
||||
const config: Configuration = {
|
||||
udpPort: parseInt(formData.get('udpPort') as string),
|
||||
tcpPort: parseInt(formData.get('tcpPort') as string),
|
||||
maxConnections: parseInt(formData.get('maxConnections') as string),
|
||||
lanDiscovery: parseInt(formData.get('lanDiscovery') as string),
|
||||
registerToLobby: parseInt(formData.get('registerToLobby') as string),
|
||||
configVersion: parseInt(formData.get('configVersion') as string) || 1
|
||||
};
|
||||
|
||||
await updateServerConfiguration(
|
||||
session.token!,
|
||||
serverId,
|
||||
ConfigFile.configuration,
|
||||
config,
|
||||
restart
|
||||
);
|
||||
revalidatePath(`/dashboard/server/${serverId}`);
|
||||
|
||||
return { success: true, message: 'Configuration updated successfully' };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : 'Failed to update configuration'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateAssistRulesAction(serverId: string, formData: FormData) {
|
||||
try {
|
||||
const session = await requireAuth();
|
||||
const restart = formData.get('restart') === 'on';
|
||||
|
||||
const config: AssistRules = {
|
||||
stabilityControlLevelMax: parseInt(formData.get('stabilityControlLevelMax') as string),
|
||||
disableAutosteer: parseInt(formData.get('disableAutosteer') as string),
|
||||
disableAutoLights: parseInt(formData.get('disableAutoLights') as string),
|
||||
disableAutoWiper: parseInt(formData.get('disableAutoWiper') as string),
|
||||
disableAutoEngineStart: parseInt(formData.get('disableAutoEngineStart') as string),
|
||||
disableAutoPitLimiter: parseInt(formData.get('disableAutoPitLimiter') as string),
|
||||
disableAutoGear: parseInt(formData.get('disableAutoGear') as string),
|
||||
disableAutoClutch: parseInt(formData.get('disableAutoClutch') as string),
|
||||
disableIdealLine: parseInt(formData.get('disableIdealLine') as string)
|
||||
};
|
||||
|
||||
await updateServerConfiguration(
|
||||
session.token!,
|
||||
serverId,
|
||||
ConfigFile.assistRules,
|
||||
config,
|
||||
restart
|
||||
);
|
||||
revalidatePath(`/dashboard/server/${serverId}`);
|
||||
|
||||
return { success: true, message: 'Assist rules updated successfully' };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : 'Failed to update assist rules'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateServerSettingsAction(serverId: string, formData: FormData) {
|
||||
try {
|
||||
const session = await requireAuth();
|
||||
const restart = formData.get('restart') === 'on';
|
||||
|
||||
const config: ServerSettings = {
|
||||
serverName: formData.get('serverName') as string,
|
||||
adminPassword: formData.get('adminPassword') as string,
|
||||
carGroup: formData.get('carGroup') as string,
|
||||
trackMedalsRequirement: parseInt(formData.get('trackMedalsRequirement') as string),
|
||||
safetyRatingRequirement: parseInt(formData.get('safetyRatingRequirement') as string),
|
||||
racecraftRatingRequirement: parseInt(formData.get('racecraftRatingRequirement') as string),
|
||||
password: formData.get('password') as string,
|
||||
spectatorPassword: formData.get('spectatorPassword') as string,
|
||||
maxCarSlots: parseInt(formData.get('maxCarSlots') as string),
|
||||
dumpLeaderboards: parseInt(formData.get('dumpLeaderboards') as string),
|
||||
isRaceLocked: parseInt(formData.get('isRaceLocked') as string),
|
||||
randomizeTrackWhenEmpty: parseInt(formData.get('randomizeTrackWhenEmpty') as string),
|
||||
centralEntryListPath: formData.get('centralEntryListPath') as string,
|
||||
allowAutoDQ: parseInt(formData.get('allowAutoDQ') as string),
|
||||
shortFormationLap: parseInt(formData.get('shortFormationLap') as string),
|
||||
formationLapType: parseInt(formData.get('formationLapType') as string),
|
||||
ignorePrematureDisconnects: parseInt(formData.get('ignorePrematureDisconnects') as string)
|
||||
};
|
||||
|
||||
await updateServerConfiguration(session.token!, serverId, ConfigFile.settings, config, restart);
|
||||
revalidatePath(`/dashboard/server/${serverId}`);
|
||||
|
||||
return { success: true, message: 'Server settings updated successfully' };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : 'Failed to update server settings'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateEventConfigAction(serverId: string, formData: FormData) {
|
||||
try {
|
||||
const session = await requireAuth();
|
||||
const restart = formData.get('restart') === 'on';
|
||||
|
||||
const sessionsData = formData.get('sessions') as string;
|
||||
const sessions = sessionsData ? JSON.parse(sessionsData) : [];
|
||||
|
||||
const config: EventConfig = {
|
||||
track: formData.get('track') as string,
|
||||
preRaceWaitingTimeSeconds: parseInt(formData.get('preRaceWaitingTimeSeconds') as string),
|
||||
sessionOverTimeSeconds: parseInt(formData.get('sessionOverTimeSeconds') as string),
|
||||
ambientTemp: parseInt(formData.get('ambientTemp') as string),
|
||||
cloudLevel: parseFloat(formData.get('cloudLevel') as string),
|
||||
rain: parseFloat(formData.get('rain') as string),
|
||||
weatherRandomness: parseInt(formData.get('weatherRandomness') as string),
|
||||
postQualySeconds: parseInt(formData.get('postQualySeconds') as string),
|
||||
postRaceSeconds: parseInt(formData.get('postRaceSeconds') as string),
|
||||
simracerWeatherConditions: parseInt(formData.get('simracerWeatherConditions') as string),
|
||||
isFixedConditionQualification: parseInt(
|
||||
formData.get('isFixedConditionQualification') as string
|
||||
),
|
||||
sessions
|
||||
};
|
||||
|
||||
await updateServerConfiguration(session.token!, serverId, ConfigFile.event, config, restart);
|
||||
revalidatePath(`/dashboard/server/${serverId}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Event configuration updated successfully'
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : 'Failed to update event configuration'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateEventRulesAction(serverId: string, formData: FormData) {
|
||||
try {
|
||||
const session = await requireAuth();
|
||||
const restart = formData.get('restart') === 'on';
|
||||
|
||||
const config: EventRules = {
|
||||
qualifyStandingType: parseInt(formData.get('qualifyStandingType') as string),
|
||||
pitWindowLengthSec: parseInt(formData.get('pitWindowLengthSec') as string),
|
||||
driverStintTimeSec: parseInt(formData.get('driverStintTimeSec') as string),
|
||||
mandatoryPitstopCount: parseInt(formData.get('mandatoryPitstopCount') as string),
|
||||
maxTotalDrivingTime: parseInt(formData.get('maxTotalDrivingTime') as string),
|
||||
isRefuellingAllowedInRace: formData.get('isRefuellingAllowedInRace') === 'true',
|
||||
isRefuellingTimeFixed: formData.get('isRefuellingTimeFixed') === 'true',
|
||||
isMandatoryPitstopRefuellingRequired:
|
||||
formData.get('isMandatoryPitstopRefuellingRequired') === 'true',
|
||||
isMandatoryPitstopTyreChangeRequired:
|
||||
formData.get('isMandatoryPitstopTyreChangeRequired') === 'true',
|
||||
isMandatoryPitstopSwapDriverRequired:
|
||||
formData.get('isMandatoryPitstopSwapDriverRequired') === 'true',
|
||||
tyreSetCount: parseInt(formData.get('tyreSetCount') as string)
|
||||
};
|
||||
|
||||
await updateServerConfiguration(
|
||||
session.token!,
|
||||
serverId,
|
||||
ConfigFile.eventRules,
|
||||
config,
|
||||
restart
|
||||
);
|
||||
revalidatePath(`/dashboard/server/${serverId}`);
|
||||
|
||||
return { success: true, message: 'Event rules updated successfully' };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : 'Failed to update event rules'
|
||||
};
|
||||
}
|
||||
}
|
||||
41
src/lib/actions/membership.ts
Normal file
41
src/lib/actions/membership.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
'use server';
|
||||
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { requireAuth } from '@/lib/auth/server';
|
||||
import { createUser, deleteUser } from '@/lib/api/server/membership';
|
||||
|
||||
export async function createUserAction(formData: FormData) {
|
||||
try {
|
||||
const session = await requireAuth();
|
||||
const username = formData.get('username') as string;
|
||||
const password = formData.get('password') as string;
|
||||
const role = formData.get('role') as string;
|
||||
|
||||
await createUser(session.token!, { username, password, role });
|
||||
revalidatePath('/dashboard/membership');
|
||||
|
||||
return { success: true, message: 'User created successfully' };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : 'Failed to create user'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteUserAction(formData: FormData) {
|
||||
try {
|
||||
const session = await requireAuth();
|
||||
const userId = formData.get('id') as string;
|
||||
|
||||
await deleteUser(session.token!, userId);
|
||||
revalidatePath('/dashboard/membership');
|
||||
|
||||
return { success: true, message: 'User deleted successfully' };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : 'Failed to delete user'
|
||||
};
|
||||
}
|
||||
}
|
||||
38
src/lib/actions/servers.ts
Normal file
38
src/lib/actions/servers.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
'use server';
|
||||
|
||||
import { revalidatePath } from 'next/cache';
|
||||
import { requireAuth } from '@/lib/auth/server';
|
||||
import { startService, stopService, restartService } from '@/lib/api/server/servers';
|
||||
|
||||
export async function startServerAction(serverId: string) {
|
||||
try {
|
||||
const session = await requireAuth();
|
||||
await startService(session.token!, serverId);
|
||||
revalidatePath('/dashboard');
|
||||
revalidatePath(`/dashboard/server/${serverId}`);
|
||||
} catch (error) {
|
||||
throw new Error(error instanceof Error ? error.message : 'Failed to start server');
|
||||
}
|
||||
}
|
||||
|
||||
export async function stopServerAction(serverId: string) {
|
||||
try {
|
||||
const session = await requireAuth();
|
||||
await stopService(session.token!, serverId);
|
||||
revalidatePath('/dashboard');
|
||||
revalidatePath(`/dashboard/server/${serverId}`);
|
||||
} catch (error) {
|
||||
throw new Error(error instanceof Error ? error.message : 'Failed to stop server');
|
||||
}
|
||||
}
|
||||
|
||||
export async function restartServerAction(serverId: string) {
|
||||
try {
|
||||
const session = await requireAuth();
|
||||
await restartService(session.token!, serverId);
|
||||
revalidatePath('/dashboard');
|
||||
revalidatePath(`/dashboard/server/${serverId}`);
|
||||
} catch (error) {
|
||||
throw new Error(error instanceof Error ? error.message : 'Failed to restart server');
|
||||
}
|
||||
}
|
||||
73
src/lib/api/client/hooks.ts
Normal file
73
src/lib/api/client/hooks.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
'use client';
|
||||
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
async function fetchFromAPI(endpoint: string, method: string = 'GET', body?: unknown) {
|
||||
const response = await fetch(endpoint, {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`API Error: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export function useServerStatus(serverId: string) {
|
||||
return useQuery({
|
||||
queryKey: ['server-status', serverId],
|
||||
queryFn: () => fetchFromAPI(`/api/server/${serverId}/status`),
|
||||
refetchInterval: 30000
|
||||
});
|
||||
}
|
||||
|
||||
export function useServerAction(serverId: string) {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ action }: { action: 'start' | 'stop' | 'restart' }) =>
|
||||
fetchFromAPI(`/api/server/${serverId}/action`, 'POST', { action }),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['server-status', serverId] });
|
||||
queryClient.invalidateQueries({ queryKey: ['servers'] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useUsers(params?: Record<string, string>) {
|
||||
const searchParams = new URLSearchParams(params).toString();
|
||||
const endpoint = `/api/membership${searchParams ? `?${searchParams}` : ''}`;
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['users', params],
|
||||
queryFn: () => fetchFromAPI(endpoint)
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateUser() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (userData: { username: string; password: string; role: string }) =>
|
||||
fetchFromAPI('/api/membership', 'POST', userData),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['users'] });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function useDeleteUser() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: (userId: string) => fetchFromAPI(`/api/membership/${userId}`, 'DELETE'),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['users'] });
|
||||
}
|
||||
});
|
||||
}
|
||||
12
src/lib/api/client/query-client.ts
Normal file
12
src/lib/api/client/query-client.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
'use client';
|
||||
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 5 * 60 * 1000,
|
||||
refetchOnWindowFocus: false
|
||||
}
|
||||
}
|
||||
});
|
||||
30
src/lib/api/server/auth.ts
Normal file
30
src/lib/api/server/auth.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { User } from '@/lib/types';
|
||||
import { fetchServerAPI } from './base';
|
||||
|
||||
const BASE_URL = process.env.API_BASE_URL || 'http://localhost:8080';
|
||||
|
||||
const authRoute = '/auth';
|
||||
|
||||
export async function loginUser(username: string, password: string) {
|
||||
const response = await fetch(`${BASE_URL}${authRoute}/login`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Login failed: ${response.statusText} - ${BASE_URL}${authRoute}/login`);
|
||||
}
|
||||
|
||||
const { token } = await response.json();
|
||||
|
||||
const userResponse = await getCurrentUser(token);
|
||||
|
||||
return { token, user: userResponse };
|
||||
}
|
||||
|
||||
export async function getCurrentUser(token: string) {
|
||||
return fetchServerAPI<User>(`${authRoute}/me`, token);
|
||||
}
|
||||
31
src/lib/api/server/base.ts
Normal file
31
src/lib/api/server/base.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
const BASE_URL = process.env.API_BASE_URL || 'http://localhost:8080';
|
||||
|
||||
export async function fetchServerAPI<T>(
|
||||
endpoint: string,
|
||||
token: string,
|
||||
method: string = 'GET',
|
||||
body?: object
|
||||
): Promise<T> {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
};
|
||||
|
||||
const response = await fetch(`${BASE_URL}${endpoint}`, {
|
||||
method,
|
||||
headers,
|
||||
body: body ? JSON.stringify(body) : undefined
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`API Error: ${response.statusText} - ${method} - ${BASE_URL}${endpoint} - ${token}`
|
||||
);
|
||||
}
|
||||
|
||||
if (response.headers.get('Content-Type')?.includes('application/json')) {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
return response.text() as T;
|
||||
}
|
||||
40
src/lib/api/server/configuration.ts
Normal file
40
src/lib/api/server/configuration.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { fetchServerAPI } from './base';
|
||||
import type {
|
||||
Configurations,
|
||||
Configuration,
|
||||
AssistRules,
|
||||
EventConfig,
|
||||
EventRules,
|
||||
ServerSettings,
|
||||
ConfigFile
|
||||
} from '@/lib/types/config';
|
||||
|
||||
const serverRoute = '/server';
|
||||
|
||||
export async function getServerConfigurations(
|
||||
token: string,
|
||||
serverId: string
|
||||
): Promise<Configurations> {
|
||||
return fetchServerAPI<Configurations>(`${serverRoute}/${serverId}/config`, token);
|
||||
}
|
||||
|
||||
export async function getServerConfiguration(
|
||||
token: string,
|
||||
serverId: string,
|
||||
configType: ConfigFile
|
||||
): Promise<Configuration | AssistRules | EventConfig | EventRules | ServerSettings> {
|
||||
return fetchServerAPI(`${serverRoute}/${serverId}/config/${configType}`, token);
|
||||
}
|
||||
|
||||
export async function updateServerConfiguration(
|
||||
token: string,
|
||||
serverId: string,
|
||||
configType: ConfigFile,
|
||||
config: Configuration | AssistRules | EventConfig | EventRules | ServerSettings,
|
||||
restart = false
|
||||
): Promise<void> {
|
||||
await fetchServerAPI(`${serverRoute}/${serverId}/config/${configType}`, token, 'PUT', {
|
||||
...config,
|
||||
restart
|
||||
});
|
||||
}
|
||||
24
src/lib/api/server/lookups.ts
Normal file
24
src/lib/api/server/lookups.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { fetchServerAPI } from './base';
|
||||
import { Track, CarModel, CupCategory, DriverCategory, SessionType } from '@/lib/types';
|
||||
|
||||
const lookupRoute = '/lookup';
|
||||
|
||||
export async function getTracks(token: string): Promise<Track[]> {
|
||||
return fetchServerAPI(`${lookupRoute}/tracks`, token);
|
||||
}
|
||||
|
||||
export async function getCarModels(token: string): Promise<CarModel[]> {
|
||||
return fetchServerAPI(`${lookupRoute}/car-models`, token);
|
||||
}
|
||||
|
||||
export async function getCupCategories(token: string): Promise<CupCategory[]> {
|
||||
return fetchServerAPI(`${lookupRoute}/cup-categories`, token);
|
||||
}
|
||||
|
||||
export async function getDriverCategories(token: string): Promise<DriverCategory[]> {
|
||||
return fetchServerAPI(`${lookupRoute}/driver-categories`, token);
|
||||
}
|
||||
|
||||
export async function getSessionTypes(token: string): Promise<SessionType[]> {
|
||||
return fetchServerAPI(`${lookupRoute}/session-types`, token);
|
||||
}
|
||||
51
src/lib/api/server/membership.ts
Normal file
51
src/lib/api/server/membership.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { fetchServerAPI } from './base';
|
||||
import { User, Role } from '@/lib/types';
|
||||
|
||||
export interface UserListParams {
|
||||
username?: string;
|
||||
role_name?: string;
|
||||
sort_by?: string;
|
||||
sort_desc?: boolean;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
const membershipRoute = '/membership';
|
||||
|
||||
export async function getUsers(token: string, params: UserListParams = {}): Promise<User[]> {
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
searchParams.set(key, value.toString());
|
||||
}
|
||||
});
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
const endpoint = `${membershipRoute}${queryString ? `?${queryString}` : ''}`;
|
||||
|
||||
return fetchServerAPI(endpoint, token);
|
||||
}
|
||||
|
||||
export async function createUser(
|
||||
token: string,
|
||||
userData: { username: string; password: string; role: string }
|
||||
) {
|
||||
return fetchServerAPI(membershipRoute, token, 'POST', userData);
|
||||
}
|
||||
|
||||
export async function getUserById(token: string, userId: string): Promise<User> {
|
||||
return fetchServerAPI(`${membershipRoute}/${userId}`, token);
|
||||
}
|
||||
|
||||
export async function updateUser(token: string, userId: string, userData: Partial<User>) {
|
||||
return fetchServerAPI(`${membershipRoute}/${userId}`, token, 'PUT', userData);
|
||||
}
|
||||
|
||||
export async function deleteUser(token: string, userId: string) {
|
||||
return fetchServerAPI(`${membershipRoute}/${userId}`, token, 'DELETE');
|
||||
}
|
||||
|
||||
export async function getRoles(token: string): Promise<Role[]> {
|
||||
return fetchServerAPI(`${membershipRoute}/roles`, token);
|
||||
}
|
||||
30
src/lib/api/server/servers.ts
Normal file
30
src/lib/api/server/servers.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { fetchServerAPI } from './base';
|
||||
import { Server } from '@/lib/types/server';
|
||||
|
||||
const serverRoute = '/server';
|
||||
|
||||
export async function getServers(token: string): Promise<Server[]> {
|
||||
const response = await fetchServerAPI<Server[]>(serverRoute, token);
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function getServer(token: string, serverId: string): Promise<Server> {
|
||||
const response = await fetchServerAPI<Server>(`${serverRoute}/${serverId}`, token);
|
||||
return response;
|
||||
}
|
||||
|
||||
export async function restartService(token: string, serverId: string) {
|
||||
return fetchServerAPI(`${serverRoute}/${serverId}/service/restart`, token, 'POST');
|
||||
}
|
||||
|
||||
export async function startService(token: string, serverId: string) {
|
||||
return fetchServerAPI(`${serverRoute}/${serverId}/service/start`, token, 'POST');
|
||||
}
|
||||
|
||||
export async function stopService(token: string, serverId: string) {
|
||||
return fetchServerAPI(`${serverRoute}/${serverId}/service/stop`, token, 'POST');
|
||||
}
|
||||
|
||||
export async function getServiceStatus(token: string, serverId: string) {
|
||||
return fetchServerAPI(`${serverRoute}/${serverId}/service`, token);
|
||||
}
|
||||
16
src/lib/api/server/statistics.ts
Normal file
16
src/lib/api/server/statistics.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { fetchServerAPI } from './base';
|
||||
import type { StateHistoryStats } from '@/lib/types/statistics';
|
||||
|
||||
const serverRoute = '/server';
|
||||
|
||||
export async function getServerStatistics(
|
||||
token: string,
|
||||
serverId: string,
|
||||
startDate: string,
|
||||
endDate: string
|
||||
): Promise<StateHistoryStats> {
|
||||
return fetchServerAPI<StateHistoryStats>(
|
||||
`${serverRoute}/${serverId}/state-history/statistics?start_date=${startDate}&end_date=${endDate}`,
|
||||
token
|
||||
);
|
||||
}
|
||||
31
src/lib/auth/server.ts
Normal file
31
src/lib/auth/server.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { getIronSession } from 'iron-session';
|
||||
import { cookies } from 'next/headers';
|
||||
import { SessionData, sessionOptions } from '@/lib/session/config';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export async function getSession() {
|
||||
const session = await getIronSession<SessionData>(await cookies(), sessionOptions);
|
||||
return session;
|
||||
}
|
||||
|
||||
export async function requireAuth() {
|
||||
const session = await getSession();
|
||||
|
||||
if (!session.token || !session.user) {
|
||||
redirect('/login');
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
export async function login(token: string, user: SessionData['user']) {
|
||||
const session = await getSession();
|
||||
session.token = token;
|
||||
session.user = user;
|
||||
await session.save();
|
||||
}
|
||||
|
||||
export async function logout() {
|
||||
const session = await getSession();
|
||||
session.destroy();
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
17
src/lib/session/config.ts
Normal file
17
src/lib/session/config.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { SessionOptions } from 'iron-session';
|
||||
import { User } from '@/lib/types';
|
||||
|
||||
export interface SessionData {
|
||||
token?: string;
|
||||
user?: User;
|
||||
}
|
||||
|
||||
export const sessionOptions: SessionOptions = {
|
||||
password: process.env.SECRET || 'development-secret-key-must-be-32-characters-long',
|
||||
cookieName: 'acc-session',
|
||||
cookieOptions: {
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
httpOnly: true,
|
||||
maxAge: 30 * 24 * 60 * 60 // 30 days
|
||||
}
|
||||
};
|
||||
105
src/lib/types/config.ts
Normal file
105
src/lib/types/config.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
export enum ConfigFile {
|
||||
configuration = 'configuration.json',
|
||||
assistRules = 'assistRules.json',
|
||||
event = 'event.json',
|
||||
eventRules = 'eventRules.json',
|
||||
settings = 'settings.json'
|
||||
}
|
||||
|
||||
export enum ServerTab {
|
||||
statistics = 'statistics',
|
||||
configuration = 'configuration',
|
||||
assistRules = 'assistRules',
|
||||
event = 'event',
|
||||
eventRules = 'eventRules',
|
||||
settings = 'settings'
|
||||
}
|
||||
|
||||
// Configuration interfaces
|
||||
export interface Configuration {
|
||||
udpPort: number;
|
||||
tcpPort: number;
|
||||
maxConnections: number;
|
||||
lanDiscovery: number;
|
||||
registerToLobby: number;
|
||||
configVersion: number;
|
||||
}
|
||||
|
||||
export interface AssistRules {
|
||||
stabilityControlLevelMax: number;
|
||||
disableAutosteer: number;
|
||||
disableAutoLights: number;
|
||||
disableAutoWiper: number;
|
||||
disableAutoEngineStart: number;
|
||||
disableAutoPitLimiter: number;
|
||||
disableAutoGear: number;
|
||||
disableAutoClutch: number;
|
||||
disableIdealLine: number;
|
||||
}
|
||||
|
||||
export interface ServerSettings {
|
||||
serverName: string;
|
||||
adminPassword: string;
|
||||
carGroup: string;
|
||||
trackMedalsRequirement: number;
|
||||
safetyRatingRequirement: number;
|
||||
racecraftRatingRequirement: number;
|
||||
password: string;
|
||||
spectatorPassword: string;
|
||||
maxCarSlots: number;
|
||||
dumpLeaderboards: number;
|
||||
isRaceLocked: number;
|
||||
randomizeTrackWhenEmpty: number;
|
||||
centralEntryListPath: string;
|
||||
allowAutoDQ: number;
|
||||
shortFormationLap: number;
|
||||
formationLapType: number;
|
||||
ignorePrematureDisconnects: number;
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
hourOfDay: number;
|
||||
dayOfWeekend: number;
|
||||
timeMultiplier: number;
|
||||
sessionType: string;
|
||||
sessionDurationMinutes: number;
|
||||
}
|
||||
|
||||
export interface EventConfig {
|
||||
track: string;
|
||||
preRaceWaitingTimeSeconds: number;
|
||||
sessionOverTimeSeconds: number;
|
||||
ambientTemp: number;
|
||||
cloudLevel: number;
|
||||
rain: number;
|
||||
weatherRandomness: number;
|
||||
postQualySeconds: number;
|
||||
postRaceSeconds: number;
|
||||
simracerWeatherConditions: number;
|
||||
isFixedConditionQualification: number;
|
||||
sessions: Session[];
|
||||
}
|
||||
|
||||
export interface EventRules {
|
||||
qualifyStandingType: number;
|
||||
pitWindowLengthSec: number;
|
||||
driverStintTimeSec: number;
|
||||
mandatoryPitstopCount: number;
|
||||
maxTotalDrivingTime: number;
|
||||
isRefuellingAllowedInRace: boolean;
|
||||
isRefuellingTimeFixed: boolean;
|
||||
isMandatoryPitstopRefuellingRequired: boolean;
|
||||
isMandatoryPitstopTyreChangeRequired: boolean;
|
||||
isMandatoryPitstopSwapDriverRequired: boolean;
|
||||
tyreSetCount: number;
|
||||
}
|
||||
|
||||
export interface Configurations {
|
||||
configuration: Configuration;
|
||||
assistRules: AssistRules;
|
||||
event: EventConfig;
|
||||
eventRules: EventRules;
|
||||
settings: ServerSettings;
|
||||
}
|
||||
|
||||
export type Config = Configuration | AssistRules | EventConfig | EventRules | ServerSettings;
|
||||
6
src/lib/types/index.ts
Normal file
6
src/lib/types/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// Re-export all types for easier imports
|
||||
export * from './server';
|
||||
export * from './user';
|
||||
export * from './config';
|
||||
export * from './statistics';
|
||||
export * from './lookups';
|
||||
25
src/lib/types/lookups.ts
Normal file
25
src/lib/types/lookups.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export interface Track {
|
||||
track: string;
|
||||
uniquePitBoxes: number;
|
||||
privateServerSlots: number;
|
||||
}
|
||||
|
||||
export interface CarModel {
|
||||
value: number;
|
||||
carModel: string;
|
||||
}
|
||||
|
||||
export interface DriverCategory {
|
||||
value: number;
|
||||
category: string;
|
||||
}
|
||||
|
||||
export interface CupCategory {
|
||||
value: number;
|
||||
category: string;
|
||||
}
|
||||
|
||||
export interface SessionType {
|
||||
value: number;
|
||||
sessionType: string;
|
||||
}
|
||||
@@ -1,32 +1,56 @@
|
||||
import type { ServiceStatus } from './serviceStatus';
|
||||
export enum ServiceStatus {
|
||||
Unknown,
|
||||
Stopped,
|
||||
Stopping,
|
||||
Restarting,
|
||||
Starting,
|
||||
Running
|
||||
}
|
||||
|
||||
export interface ServerState {
|
||||
export const serviceStatusToString = (status: ServiceStatus): string => {
|
||||
switch (status) {
|
||||
case ServiceStatus.Running:
|
||||
return 'Running';
|
||||
case ServiceStatus.Stopped:
|
||||
return 'Stopped';
|
||||
case ServiceStatus.Starting:
|
||||
return 'Starting';
|
||||
case ServiceStatus.Stopping:
|
||||
return 'Stopping';
|
||||
case ServiceStatus.Restarting:
|
||||
return 'Restarting';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
export const getStatusColor = (status: ServiceStatus): string => {
|
||||
switch (status) {
|
||||
case ServiceStatus.Running:
|
||||
return 'bg-green-500';
|
||||
case ServiceStatus.Stopped:
|
||||
return 'bg-red-500';
|
||||
case ServiceStatus.Starting:
|
||||
return 'bg-blue-500';
|
||||
case ServiceStatus.Stopping:
|
||||
return 'bg-yellow-500';
|
||||
case ServiceStatus.Restarting:
|
||||
return 'bg-purple-500';
|
||||
default:
|
||||
return 'bg-gray-500';
|
||||
}
|
||||
};
|
||||
|
||||
interface State {
|
||||
session: string;
|
||||
sessionStart: string;
|
||||
playerCount: number;
|
||||
track: string;
|
||||
maxConnections: number;
|
||||
}
|
||||
|
||||
export interface Server {
|
||||
id: number;
|
||||
id: string;
|
||||
name: string;
|
||||
status: ServiceStatus;
|
||||
state: ServerState;
|
||||
state: State;
|
||||
}
|
||||
|
||||
// Helper function to parse server data from API
|
||||
export const parseServer = (data: any): Server => {
|
||||
return {
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
status: data.status,
|
||||
state: {
|
||||
session: data.state.session,
|
||||
sessionStart: data.state.sessionStart,
|
||||
playerCount: data.state.playerCount,
|
||||
track: data.state.track,
|
||||
maxConnections: data.state.maxConnections
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
export enum ServiceStatus {
|
||||
Unknown,
|
||||
Stopped,
|
||||
Stopping,
|
||||
Restarting,
|
||||
Starting,
|
||||
Running
|
||||
}
|
||||
|
||||
export const serviceStatusToString = (status: ServiceStatus): string => {
|
||||
switch (status) {
|
||||
case ServiceStatus.Running:
|
||||
return 'Running';
|
||||
case ServiceStatus.Stopped:
|
||||
return 'Stopped';
|
||||
case ServiceStatus.Starting:
|
||||
return 'Starting';
|
||||
case ServiceStatus.Stopping:
|
||||
return 'Stopping';
|
||||
case ServiceStatus.Restarting:
|
||||
return 'Restarting';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
};
|
||||
|
||||
export const parseServiceStatus = (status: string): ServiceStatus => {
|
||||
switch (status) {
|
||||
case 'Running':
|
||||
return ServiceStatus.Running;
|
||||
case 'SERVICE_STOPPED':
|
||||
return ServiceStatus.Stopped;
|
||||
case 'SERVICE_STARTING':
|
||||
return ServiceStatus.Starting;
|
||||
case 'SERVICE_STOPPING':
|
||||
return ServiceStatus.Stopping;
|
||||
case 'SERVICE_RESTARTING':
|
||||
return ServiceStatus.Restarting;
|
||||
default:
|
||||
return ServiceStatus.Unknown;
|
||||
}
|
||||
};
|
||||
|
||||
export const getStatusColor = (status: ServiceStatus): string => {
|
||||
switch (status) {
|
||||
case ServiceStatus.Running:
|
||||
return 'bg-green-500';
|
||||
case ServiceStatus.Stopped:
|
||||
return 'bg-red-500';
|
||||
case ServiceStatus.Starting:
|
||||
return 'bg-blue-500';
|
||||
case ServiceStatus.Stopping:
|
||||
return 'bg-yellow-500';
|
||||
case ServiceStatus.Restarting:
|
||||
return 'bg-purple-500';
|
||||
default:
|
||||
return 'bg-gray-500';
|
||||
}
|
||||
};
|
||||
43
src/lib/types/statistics.ts
Normal file
43
src/lib/types/statistics.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
export interface StateHistory {
|
||||
dateCreated: string;
|
||||
sessionStart: string;
|
||||
playerCount: number;
|
||||
track: string;
|
||||
sessionDurationMinutes: number;
|
||||
session: string;
|
||||
}
|
||||
|
||||
interface SessionCount {
|
||||
name: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
interface DailyActivity {
|
||||
date: string;
|
||||
sessionsCount: number;
|
||||
}
|
||||
|
||||
interface PlayerCountPoint {
|
||||
timestamp: string;
|
||||
count: number;
|
||||
}
|
||||
|
||||
interface RecentSession {
|
||||
id: number;
|
||||
date: string;
|
||||
type: string;
|
||||
track: string;
|
||||
duration: number;
|
||||
players: number;
|
||||
}
|
||||
|
||||
export interface StateHistoryStats {
|
||||
averagePlayers: number;
|
||||
peakPlayers: number;
|
||||
totalSessions: number;
|
||||
totalPlaytime: number;
|
||||
playerCountOverTime: PlayerCountPoint[];
|
||||
sessionTypes: SessionCount[];
|
||||
dailyActivity: DailyActivity[];
|
||||
recentSessions: RecentSession[];
|
||||
}
|
||||
28
src/lib/types/user.ts
Normal file
28
src/lib/types/user.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export interface Permission {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Role {
|
||||
id: string;
|
||||
name: string;
|
||||
permissions: Permission[];
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id: string;
|
||||
username: string;
|
||||
role_id: string;
|
||||
role: Role;
|
||||
}
|
||||
|
||||
export function hasPermission(user: User | null, permission: string): boolean {
|
||||
if (!user || !user.role || !user.role.permissions) {
|
||||
return false;
|
||||
}
|
||||
// Super Admins have all permissions
|
||||
if (user.role.name === 'Super Admin') {
|
||||
return true;
|
||||
}
|
||||
return user.role.permissions.some((p) => p.name === permission);
|
||||
}
|
||||
6
src/lib/utils.ts
Normal file
6
src/lib/utils.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { clsx, type ClassValue } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
Reference in New Issue
Block a user