mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 02:32:57 +00:00
dev day #80
This commit is contained in:
@@ -23,6 +23,24 @@ export async function adminBanUser(user_id: number, reason: string, duration_hou
|
||||
return res.data as { ok: boolean };
|
||||
}
|
||||
|
||||
export type CommentBan = {
|
||||
id: number;
|
||||
user_id: number;
|
||||
reason?: string;
|
||||
until?: string | null;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export async function adminListBans(): Promise<{ items: CommentBan[] }>{
|
||||
const res = await api.get('/admin/comments/bans');
|
||||
return res.data as { items: CommentBan[] };
|
||||
}
|
||||
|
||||
export async function adminLiftBan(id: number): Promise<{ ok: boolean }>{
|
||||
const res = await api.post(`/admin/comments/bans/${id}/lift`);
|
||||
return res.data as { ok: boolean };
|
||||
}
|
||||
|
||||
export type UnbanRequest = {
|
||||
id: number;
|
||||
user_id: number;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import api from '../api';
|
||||
import { RewardItem } from '../../services/engagement';
|
||||
import type { LeaderboardResponse } from '../../services/engagement';
|
||||
|
||||
export type AdminRewardItem = RewardItem & {
|
||||
active: boolean;
|
||||
@@ -66,3 +67,48 @@ export async function adminUpdateRedemptionStatus(id: number, action: 'approve'|
|
||||
const res = await api.patch(`/admin/engagement/redemptions/${id}`, { action });
|
||||
return res.data as { ok: boolean; status: string };
|
||||
}
|
||||
|
||||
export async function adminGetLeaderboard(metric: 'points'|'level'|'xp' = 'points', limit?: number): Promise<LeaderboardResponse> {
|
||||
const res = await api.get('/admin/engagement/leaderboard', { params: { metric, limit } });
|
||||
return res.data as LeaderboardResponse;
|
||||
}
|
||||
|
||||
export type AdminPointsTx = {
|
||||
id: number;
|
||||
user_id: number;
|
||||
delta: number;
|
||||
xp_delta?: number;
|
||||
reason: string;
|
||||
meta?: Record<string, any>;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export async function adminListTransactions(params?: { user_id?: number|string; reason?: string; limit?: number }): Promise<AdminPointsTx[]> {
|
||||
const res = await api.get('/admin/engagement/transactions', { params });
|
||||
return (res.data?.items || []) as AdminPointsTx[];
|
||||
}
|
||||
|
||||
export async function adminAdjustPoints(body: { user_id: number; delta: number; reason?: string; meta?: Record<string, any> }): Promise<{ ok: boolean }>{
|
||||
const res = await api.post('/admin/engagement/adjust', body);
|
||||
return res.data as { ok: boolean };
|
||||
}
|
||||
|
||||
export type AdminUserProfile = {
|
||||
user_id: number;
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
email?: string;
|
||||
role?: string;
|
||||
points: number;
|
||||
level: number;
|
||||
xp: number;
|
||||
username?: string;
|
||||
avatar_url?: string;
|
||||
animated_avatar_url?: string;
|
||||
avatar_upload_unlocked?: boolean;
|
||||
};
|
||||
|
||||
export async function adminGetUserProfile(user_id: number | string): Promise<AdminUserProfile> {
|
||||
const res = await api.get(`/admin/engagement/profile/${user_id}`);
|
||||
return res.data as AdminUserProfile;
|
||||
}
|
||||
|
||||
@@ -19,8 +19,9 @@ export type CommentItem = {
|
||||
id: number;
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
email?: string;
|
||||
role?: string;
|
||||
username?: string;
|
||||
avatar_url?: string;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -5,8 +5,11 @@ export type EngagementProfile = {
|
||||
points: number;
|
||||
level: number;
|
||||
xp: number;
|
||||
username?: string;
|
||||
avatar_url?: string;
|
||||
animated_avatar_url?: string;
|
||||
avatar_upload_unlocked?: boolean;
|
||||
animated_avatar_upload_unlocked?: boolean;
|
||||
achievements: number;
|
||||
};
|
||||
|
||||
@@ -15,6 +18,11 @@ export async function getProfile(): Promise<EngagementProfile> {
|
||||
return res.data as EngagementProfile;
|
||||
}
|
||||
|
||||
export async function patchProfile(body: { username?: string }): Promise<{ ok: boolean }>{
|
||||
const res = await api.patch('/engagement/profile', body);
|
||||
return res.data as { ok: boolean };
|
||||
}
|
||||
|
||||
export type RewardItem = {
|
||||
id: number;
|
||||
name: string;
|
||||
@@ -64,3 +72,53 @@ export async function getAchievements(): Promise<AchievementsResponse> {
|
||||
const res = await api.get('/engagement/achievements');
|
||||
return res.data as AchievementsResponse;
|
||||
}
|
||||
|
||||
export type LeaderboardItem = {
|
||||
rank: number;
|
||||
user_id: number;
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
username?: string;
|
||||
role?: string;
|
||||
points: number;
|
||||
level: number;
|
||||
xp: number;
|
||||
avatar_url?: string;
|
||||
animated_avatar_url?: string;
|
||||
};
|
||||
|
||||
export type LeaderboardResponse = { items: LeaderboardItem[] };
|
||||
|
||||
export async function getLeaderboard(metric: 'points'|'level'|'xp' = 'points', limit?: number): Promise<LeaderboardResponse> {
|
||||
const res = await api.get('/engagement/leaderboard', { params: { metric, limit } });
|
||||
return res.data as LeaderboardResponse;
|
||||
}
|
||||
|
||||
export type PointsTx = {
|
||||
id: number;
|
||||
user_id: number;
|
||||
delta: number;
|
||||
xp_delta?: number;
|
||||
reason: string;
|
||||
meta?: Record<string, any>;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export async function getMyTransactions(params?: { limit?: number; reason?: string }): Promise<PointsTx[]> {
|
||||
const res = await api.get('/engagement/transactions', { params });
|
||||
return (res.data?.items || []) as PointsTx[];
|
||||
}
|
||||
|
||||
export type CheckinResponse = { ok: boolean; awarded: boolean; points?: number; level?: number; xp?: number };
|
||||
|
||||
export async function checkin(): Promise<CheckinResponse> {
|
||||
const res = await api.post('/engagement/checkin');
|
||||
return res.data as CheckinResponse;
|
||||
}
|
||||
|
||||
export type ArticleReadResponse = { ok: boolean; awarded: boolean; points?: number; level?: number; xp?: number };
|
||||
|
||||
export async function articleRead(article_id: number): Promise<ArticleReadResponse> {
|
||||
const res = await api.post('/engagement/article-read', { article_id });
|
||||
return res.data as ArticleReadResponse;
|
||||
}
|
||||
|
||||
@@ -140,6 +140,48 @@ export async function startSecondHalf(): Promise<void> {
|
||||
await api.post('/admin/scoreboard/second-half');
|
||||
}
|
||||
|
||||
// Admin: presets
|
||||
export async function listPresets(): Promise<string[]> {
|
||||
const res = await api.get<string[]>('/admin/scoreboard/saves');
|
||||
// API returns an array of filenames
|
||||
return (res.data || []).slice();
|
||||
}
|
||||
|
||||
export async function savePreset(filename?: string): Promise<{ saved: string }> {
|
||||
const payload = filename && filename.trim() ? { filename: filename.trim() } : {};
|
||||
const res = await api.post<{ saved: string }>('/admin/scoreboard/save', payload);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function loadPreset(filename: string): Promise<void> {
|
||||
const name = (filename || '').trim();
|
||||
if (!name) throw new Error('Missing filename');
|
||||
await api.post('/admin/scoreboard/load', { filename: name });
|
||||
}
|
||||
|
||||
// Admin: sponsors management
|
||||
export async function listSponsorsAdmin(): Promise<string[]> {
|
||||
const res = await api.get<string[]>('/admin/scoreboard/sponsors');
|
||||
return res.data || [];
|
||||
}
|
||||
|
||||
export async function uploadSponsors(files: File[]): Promise<{ saved: number }> {
|
||||
const fd = new FormData();
|
||||
for (const f of files) fd.append('files', f);
|
||||
const res = await api.post<{ saved: number }>('/admin/scoreboard/sponsors/upload', fd, { headers: { 'Content-Type': 'multipart/form-data' } });
|
||||
return res.data || { saved: 0 };
|
||||
}
|
||||
|
||||
export async function deleteSponsor(name: string): Promise<void> {
|
||||
await api.delete('/admin/scoreboard/sponsors', { params: { name } });
|
||||
}
|
||||
|
||||
// Public: sponsors list for overlay
|
||||
export async function listSponsorsPublic(): Promise<string[]> {
|
||||
const res = await api.get<string[]>('/scoreboard/sponsors');
|
||||
return res.data || [];
|
||||
}
|
||||
|
||||
// Utilities
|
||||
export function deriveShort(name?: string): string {
|
||||
if (!name) return '---';
|
||||
|
||||
@@ -120,6 +120,11 @@ export type AdminSettings = PublicSettings & {
|
||||
api_base_url?: string;
|
||||
// Homepage matches display configuration
|
||||
finished_match_display_days?: number; // Number of days to show finished matches with scores on homepage
|
||||
|
||||
// Storage quota and thresholds
|
||||
storage_quota_mb?: number;
|
||||
storage_warn_threshold?: number;
|
||||
storage_critical_threshold?: number;
|
||||
};
|
||||
|
||||
export const getPublicSettings = async (): Promise<PublicSettings> => {
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
import api from './api';
|
||||
|
||||
export type Sweepstake = {
|
||||
id: number;
|
||||
title: string;
|
||||
description?: string;
|
||||
image_url?: string;
|
||||
rules_url?: string;
|
||||
start_at: string;
|
||||
end_at: string;
|
||||
status: string;
|
||||
picker_style?: 'wheel' | 'cycler' | string;
|
||||
total_prizes?: number;
|
||||
prize_summary?: string;
|
||||
winners_selected_at?: string | null;
|
||||
visibility_until?: string | null;
|
||||
};
|
||||
|
||||
export type SweepstakePrize = {
|
||||
id: number;
|
||||
sweepstake_id: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
image_url?: string;
|
||||
value?: string;
|
||||
quantity: number;
|
||||
display_order: number;
|
||||
kind?: 'physical' | 'points' | 'xp' | 'points_xp';
|
||||
points?: number;
|
||||
xp?: number;
|
||||
};
|
||||
|
||||
export type SweepstakeWinner = {
|
||||
id: number;
|
||||
sweepstake_id: number;
|
||||
entry_id: number;
|
||||
user_id: number;
|
||||
prize_id?: number | null;
|
||||
prize_name?: string;
|
||||
claim_status: string;
|
||||
announced_at?: string | null;
|
||||
};
|
||||
|
||||
export type CurrentSweepstakeResponse = {
|
||||
sweepstake: Sweepstake | null;
|
||||
prizes?: SweepstakePrize[];
|
||||
winners?: SweepstakeWinner[];
|
||||
state?: 'upcoming' | 'active' | 'finalized';
|
||||
has_entered?: boolean;
|
||||
visual_played_at?: string | null;
|
||||
};
|
||||
|
||||
export async function getCurrentSweepstake(): Promise<CurrentSweepstakeResponse> {
|
||||
const res = await api.get('/sweepstakes/current');
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function enterSweepstake(id: number): Promise<void> {
|
||||
await api.post(`/sweepstakes/${id}/enter`, {});
|
||||
}
|
||||
|
||||
export async function markSweepstakeVisualPlayed(id: number): Promise<void> {
|
||||
await api.post(`/sweepstakes/${id}/played`, {});
|
||||
}
|
||||
|
||||
export async function getMyWinnings(): Promise<{ items: SweepstakeWinner[] }> {
|
||||
const res = await api.get('/sweepstakes/my-winnings');
|
||||
return res.data;
|
||||
}
|
||||
|
||||
// Admin
|
||||
export async function adminListSweepstakes(params?: { status?: string }) {
|
||||
const res = await api.get('/admin/sweepstakes', { params });
|
||||
return res.data?.items || [];
|
||||
}
|
||||
|
||||
export async function adminCreateSweepstake(data: Partial<Sweepstake> & { start_at: string; end_at: string }) {
|
||||
const res = await api.post('/admin/sweepstakes', data);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function adminUpdateSweepstake(id: number, data: Partial<Sweepstake>) {
|
||||
const res = await api.put(`/admin/sweepstakes/${id}`, data);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function adminDeleteSweepstake(id: number) {
|
||||
const res = await api.delete(`/admin/sweepstakes/${id}`);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function adminListEntries(id: number) {
|
||||
const res = await api.get(`/admin/sweepstakes/${id}/entries`);
|
||||
return res.data?.items || [];
|
||||
}
|
||||
|
||||
export async function adminListWinners(id: number) {
|
||||
const res = await api.get(`/admin/sweepstakes/${id}/winners`);
|
||||
return res.data?.items || [];
|
||||
}
|
||||
|
||||
export async function adminFinalizeSweepstake(id: number, seed?: string) {
|
||||
const res = await api.post(`/admin/sweepstakes/${id}/finalize`, seed ? { seed } : {});
|
||||
return res.data;
|
||||
}
|
||||
|
||||
// Visualizer data
|
||||
export type VisualEntry = { user_id: number; display_name: string; avatar_url?: string };
|
||||
export type VisualWinner = { id?: number; user_id: number; prize_name?: string; claim_status?: string };
|
||||
export type VisualData = { sweepstake: Sweepstake; entries: VisualEntry[]; winners: VisualWinner[] };
|
||||
export async function adminGetVisualData(id: number): Promise<VisualData> {
|
||||
const res = await api.get(`/admin/sweepstakes/${id}/visual`);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function getPublicVisualData(id: number): Promise<VisualData> {
|
||||
const res = await api.get(`/sweepstakes/${id}/visual`);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
// Prizes CRUD
|
||||
export type SweepstakePrizeInput = Partial<SweepstakePrize> & {
|
||||
name?: string;
|
||||
quantity?: number;
|
||||
display_order?: number;
|
||||
kind?: 'physical' | 'points' | 'xp' | 'points_xp';
|
||||
points?: number;
|
||||
xp?: number;
|
||||
};
|
||||
export async function adminListPrizes(id: number): Promise<SweepstakePrize[]> {
|
||||
const res = await api.get(`/admin/sweepstakes/${id}/prizes`);
|
||||
return res.data?.items || [];
|
||||
}
|
||||
export async function adminCreatePrize(id: number, data: SweepstakePrizeInput) {
|
||||
const res = await api.post(`/admin/sweepstakes/${id}/prizes`, data);
|
||||
return res.data;
|
||||
}
|
||||
export async function adminUpdatePrize(id: number, prizeId: number, data: SweepstakePrizeInput) {
|
||||
const res = await api.put(`/admin/sweepstakes/${id}/prizes/${prizeId}`, data);
|
||||
return res.data;
|
||||
}
|
||||
export async function adminDeletePrize(id: number, prizeId: number) {
|
||||
const res = await api.delete(`/admin/sweepstakes/${id}/prizes/${prizeId}`);
|
||||
return res.data;
|
||||
}
|
||||
export async function adminReorderPrizes(id: number, order: number[]) {
|
||||
const res = await api.post(`/admin/sweepstakes/${id}/prizes/reorder`, { order });
|
||||
return res.data;
|
||||
}
|
||||
|
||||
// Winners management
|
||||
export async function adminUpdateWinner(id: number, winnerId: number, data: { claim_status?: 'pending'|'claimed'|'delivered'; claim_note?: string }) {
|
||||
const res = await api.patch(`/admin/sweepstakes/${id}/winners/${winnerId}`, data);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function adminSetWinnerPrize(id: number, winnerId: number, prizeId: number) {
|
||||
const res = await api.patch(`/admin/sweepstakes/${id}/winners/${winnerId}/prize`, { prize_id: prizeId });
|
||||
return res.data;
|
||||
}
|
||||
Reference in New Issue
Block a user