Compare commits
16 Commits
activitypa
...
pin_code
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbede48d4f | ||
|
|
6cd3c3a020 | ||
|
|
7ecbef806d | ||
|
|
1c70ca97bb | ||
|
|
a6493990d3 | ||
|
|
f8f4d23c4e | ||
|
|
1ef1711eea | ||
|
|
85981a880d | ||
|
|
84503b6404 | ||
|
|
bcc3ce036d | ||
|
|
0fe0514660 | ||
|
|
431ec861ba | ||
|
|
648b608036 | ||
|
|
4ac71d967f | ||
|
|
75d304ec6d | ||
|
|
144034a305 |
273
backend/clients/myTischtennisClient.js
Normal file
273
backend/clients/myTischtennisClient.js
Normal file
@@ -0,0 +1,273 @@
|
||||
import axios from 'axios';
|
||||
|
||||
const BASE_URL = 'https://www.mytischtennis.de';
|
||||
|
||||
class MyTischtennisClient {
|
||||
constructor() {
|
||||
this.baseURL = BASE_URL;
|
||||
this.client = axios.create({
|
||||
baseURL: this.baseURL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Accept': '*/*'
|
||||
},
|
||||
maxRedirects: 0, // Don't follow redirects automatically
|
||||
validateStatus: (status) => status >= 200 && status < 400 // Accept 3xx as success
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Login to myTischtennis API
|
||||
* @param {string} email - myTischtennis email (not username!)
|
||||
* @param {string} password - myTischtennis password
|
||||
* @returns {Promise<Object>} Login response with token and session data
|
||||
*/
|
||||
async login(email, password) {
|
||||
try {
|
||||
// Create form data
|
||||
const formData = new URLSearchParams();
|
||||
formData.append('email', email);
|
||||
formData.append('password', password);
|
||||
formData.append('intent', 'login');
|
||||
|
||||
const response = await this.client.post(
|
||||
'/login?next=%2F&_data=routes%2F_auth%2B%2Flogin',
|
||||
formData.toString()
|
||||
);
|
||||
|
||||
// Extract the cookie from response headers
|
||||
const setCookie = response.headers['set-cookie'];
|
||||
if (!setCookie || !Array.isArray(setCookie)) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Keine Session-Cookie erhalten'
|
||||
};
|
||||
}
|
||||
|
||||
// Find the sb-10-auth-token cookie
|
||||
const authCookie = setCookie.find(cookie => cookie.startsWith('sb-10-auth-token='));
|
||||
if (!authCookie) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Kein Auth-Token in Response gefunden'
|
||||
};
|
||||
}
|
||||
|
||||
// Extract and decode the token
|
||||
const tokenMatch = authCookie.match(/sb-10-auth-token=base64-([^;]+)/);
|
||||
if (!tokenMatch) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Token-Format ungültig'
|
||||
};
|
||||
}
|
||||
|
||||
const base64Token = tokenMatch[1];
|
||||
let tokenData;
|
||||
try {
|
||||
const decodedToken = Buffer.from(base64Token, 'base64').toString('utf-8');
|
||||
tokenData = JSON.parse(decodedToken);
|
||||
} catch (decodeError) {
|
||||
console.error('Error decoding token:', decodeError);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Token konnte nicht dekodiert werden'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
accessToken: tokenData.access_token,
|
||||
refreshToken: tokenData.refresh_token,
|
||||
expiresAt: tokenData.expires_at,
|
||||
expiresIn: tokenData.expires_in,
|
||||
user: tokenData.user,
|
||||
cookie: authCookie.split(';')[0] // Just the cookie value without attributes
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('MyTischtennis login error:', error.message);
|
||||
return {
|
||||
success: false,
|
||||
error: error.response?.data?.message || 'Login fehlgeschlagen',
|
||||
status: error.response?.status || 500
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify login credentials
|
||||
* @param {string} email - myTischtennis email
|
||||
* @param {string} password - myTischtennis password
|
||||
* @returns {Promise<boolean>} True if credentials are valid
|
||||
*/
|
||||
async verifyCredentials(email, password) {
|
||||
const result = await this.login(email, password);
|
||||
return result.success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an authenticated request
|
||||
* @param {string} endpoint - API endpoint
|
||||
* @param {string} cookie - Authentication cookie (sb-10-auth-token)
|
||||
* @param {Object} options - Additional axios options
|
||||
* @returns {Promise<Object>} API response
|
||||
*/
|
||||
async authenticatedRequest(endpoint, cookie, options = {}) {
|
||||
try {
|
||||
const response = await this.client.request({
|
||||
url: endpoint,
|
||||
...options,
|
||||
headers: {
|
||||
...options.headers,
|
||||
'Cookie': cookie,
|
||||
'Accept': '*/*',
|
||||
'Accept-Language': 'de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7',
|
||||
'Referer': 'https://www.mytischtennis.de/',
|
||||
'sec-fetch-dest': 'empty',
|
||||
'sec-fetch-mode': 'cors',
|
||||
'sec-fetch-site': 'same-origin'
|
||||
}
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
data: response.data
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('MyTischtennis API error:', error.message);
|
||||
return {
|
||||
success: false,
|
||||
error: error.response?.data?.message || 'API-Anfrage fehlgeschlagen',
|
||||
status: error.response?.status || 500
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user profile and club information
|
||||
* @param {string} cookie - Authentication cookie (sb-10-auth-token)
|
||||
* @returns {Promise<Object>} User profile with club info
|
||||
*/
|
||||
async getUserProfile(cookie) {
|
||||
|
||||
const result = await this.authenticatedRequest('/?_data=root', cookie, {
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
|
||||
if (result.success) {
|
||||
console.log('[getUserProfile] - Response structure:', {
|
||||
hasUserProfile: !!result.data?.userProfile,
|
||||
hasClub: !!result.data?.userProfile?.club,
|
||||
hasOrganization: !!result.data?.userProfile?.organization,
|
||||
clubnr: result.data?.userProfile?.club?.clubnr,
|
||||
clubName: result.data?.userProfile?.club?.name,
|
||||
orgShort: result.data?.userProfile?.organization?.short,
|
||||
ttr: result.data?.userProfile?.ttr,
|
||||
qttr: result.data?.userProfile?.qttr
|
||||
});
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
clubId: result.data?.userProfile?.club?.clubnr || null,
|
||||
clubName: result.data?.userProfile?.club?.name || null,
|
||||
fedNickname: result.data?.userProfile?.organization?.short || null,
|
||||
ttr: result.data?.userProfile?.ttr || null,
|
||||
qttr: result.data?.userProfile?.qttr || null,
|
||||
userProfile: result.data?.userProfile || null
|
||||
};
|
||||
}
|
||||
|
||||
console.error('[getUserProfile] - Failed:', result.error);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get club rankings (andro-Rangliste)
|
||||
* @param {string} cookie - Authentication cookie
|
||||
* @param {string} clubId - Club number (e.g., "43030")
|
||||
* @param {string} fedNickname - Federation nickname (e.g., "HeTTV")
|
||||
* @returns {Promise<Object>} Rankings with player entries (all pages)
|
||||
*/
|
||||
async getClubRankings(cookie, clubId, fedNickname) {
|
||||
const allEntries = [];
|
||||
let currentPage = 0;
|
||||
let hasMorePages = true;
|
||||
|
||||
|
||||
while (hasMorePages) {
|
||||
const endpoint = `/rankings/andro-rangliste?all-players=on&clubnr=${clubId}&fednickname=${fedNickname}&results-per-page=100&page=${currentPage}&_data=routes%2F%24`;
|
||||
|
||||
|
||||
const result = await this.authenticatedRequest(endpoint, cookie, {
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
console.error(`[getClubRankings] - Failed to fetch page ${currentPage}:`, result.error);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Find the dynamic key that contains entries
|
||||
const blockLoaderData = result.data?.pageContent?.blockLoaderData;
|
||||
if (!blockLoaderData) {
|
||||
console.error('[getClubRankings] - No blockLoaderData found');
|
||||
return {
|
||||
success: false,
|
||||
error: 'Keine blockLoaderData gefunden'
|
||||
};
|
||||
}
|
||||
|
||||
// Finde den Schlüssel, der entries enthält
|
||||
let entries = null;
|
||||
let rankingData = null;
|
||||
|
||||
for (const key in blockLoaderData) {
|
||||
if (blockLoaderData[key]?.entries) {
|
||||
entries = blockLoaderData[key].entries;
|
||||
rankingData = blockLoaderData[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!entries) {
|
||||
console.error('[getClubRankings] - No entries found in blockLoaderData');
|
||||
return {
|
||||
success: false,
|
||||
error: 'Keine entries in blockLoaderData gefunden'
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Füge Entries hinzu
|
||||
allEntries.push(...entries);
|
||||
|
||||
// Prüfe ob es weitere Seiten gibt
|
||||
// Wenn die aktuelle Seite weniger Einträge hat als das Limit, sind wir am Ende
|
||||
// Oder wenn wir alle erwarteten Einträge haben
|
||||
if (entries.length === 0) {
|
||||
hasMorePages = false;
|
||||
} else if (rankingData.numberOfPages && currentPage >= rankingData.numberOfPages - 1) {
|
||||
hasMorePages = false;
|
||||
} else if (allEntries.length >= rankingData.resultLength) {
|
||||
hasMorePages = false;
|
||||
} else {
|
||||
currentPage++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
entries: allEntries,
|
||||
metadata: {
|
||||
totalEntries: allEntries.length,
|
||||
pagesFetched: currentPage + 1
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default new MyTischtennisClient();
|
||||
|
||||
128
backend/controllers/clubTeamController.js
Normal file
128
backend/controllers/clubTeamController.js
Normal file
@@ -0,0 +1,128 @@
|
||||
import ClubTeamService from '../services/clubTeamService.js';
|
||||
import { getUserByToken } from '../utils/userUtils.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
export const getClubTeams = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { seasonid: seasonId } = req.query;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
// Check if user has access to this club
|
||||
const clubTeams = await ClubTeamService.getAllClubTeamsByClub(clubId, seasonId);
|
||||
|
||||
res.status(200).json(clubTeams);
|
||||
} catch (error) {
|
||||
console.error('[getClubTeams] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getClubTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubteamid: clubTeamId } = req.params;
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const clubTeam = await ClubTeamService.getClubTeamById(clubTeamId);
|
||||
if (!clubTeam) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json(clubTeam);
|
||||
} catch (error) {
|
||||
console.error('[getClubTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const createClubTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { name, leagueId, seasonId } = req.body;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
if (!name) {
|
||||
return res.status(400).json({ error: "missingname" });
|
||||
}
|
||||
|
||||
const clubTeamData = {
|
||||
name,
|
||||
clubId: parseInt(clubId),
|
||||
leagueId: leagueId ? parseInt(leagueId) : null,
|
||||
seasonId: seasonId ? parseInt(seasonId) : null
|
||||
};
|
||||
|
||||
const newClubTeam = await ClubTeamService.createClubTeam(clubTeamData);
|
||||
|
||||
res.status(201).json(newClubTeam);
|
||||
} catch (error) {
|
||||
console.error('[createClubTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateClubTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubteamid: clubTeamId } = req.params;
|
||||
const { name, leagueId, seasonId } = req.body;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const updateData = {};
|
||||
if (name !== undefined) updateData.name = name;
|
||||
if (leagueId !== undefined) updateData.leagueId = leagueId ? parseInt(leagueId) : null;
|
||||
if (seasonId !== undefined) updateData.seasonId = seasonId ? parseInt(seasonId) : null;
|
||||
|
||||
const success = await ClubTeamService.updateClubTeam(clubTeamId, updateData);
|
||||
if (!success) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
const updatedClubTeam = await ClubTeamService.getClubTeamById(clubTeamId);
|
||||
res.status(200).json(updatedClubTeam);
|
||||
} catch (error) {
|
||||
console.error('[updateClubTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteClubTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubteamid: clubTeamId } = req.params;
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const success = await ClubTeamService.deleteClubTeam(clubTeamId);
|
||||
if (!success) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json({ message: "Club team deleted successfully" });
|
||||
} catch (error) {
|
||||
console.error('[deleteClubTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getLeagues = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { seasonid: seasonId } = req.query;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const leagues = await ClubTeamService.getLeaguesByClub(clubId, seasonId);
|
||||
|
||||
res.status(200).json(leagues);
|
||||
} catch (error) {
|
||||
console.error('[getLeagues] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
@@ -1,77 +1,57 @@
|
||||
import ClubService from '../services/clubService.js';
|
||||
import { getUserByToken } from '../utils/userUtils.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
export const getClubs = async (req, res) => {
|
||||
try {
|
||||
console.log('[getClubs] - get clubs');
|
||||
const clubs = await ClubService.getAllClubs();
|
||||
console.log('[getClubs] - prepare response');
|
||||
res.status(200).json(clubs);
|
||||
console.log('[getClubs] - done');
|
||||
} catch (error) {
|
||||
console.log('[getClubs] - error');
|
||||
console.log(error);
|
||||
console.error('[getClubs] - error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const addClub = async (req, res) => {
|
||||
console.log('[addClub] - Read out parameters');
|
||||
const { authcode: token } = req.headers;
|
||||
const { name: clubName } = req.body;
|
||||
|
||||
try {
|
||||
console.log('[addClub] - find club by name');
|
||||
const club = await ClubService.findClubByName(clubName);
|
||||
console.log('[addClub] - get user');
|
||||
const user = await getUserByToken(token);
|
||||
console.log('[addClub] - check if club already exists');
|
||||
if (club) {
|
||||
res.status(409).json({ error: "alreadyexists" });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[addClub] - create club');
|
||||
const newClub = await ClubService.createClub(clubName);
|
||||
console.log('[addClub] - add user to new club');
|
||||
await ClubService.addUserToClub(user.id, newClub.id);
|
||||
console.log('[addClub] - prepare response');
|
||||
res.status(200).json(newClub);
|
||||
console.log('[addClub] - done');
|
||||
} catch (error) {
|
||||
console.log('[addClub] - error');
|
||||
console.log(error);
|
||||
console.error('[addClub] - error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getClub = async (req, res) => {
|
||||
console.log('[getClub] - start');
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
console.log('[getClub] - get user');
|
||||
const user = await getUserByToken(token);
|
||||
console.log('[getClub] - get users club');
|
||||
const access = await ClubService.getUserClubAccess(user.id, clubId);
|
||||
console.log('[getClub] - check access');
|
||||
if (access.length === 0 || !access[0].approved) {
|
||||
res.status(403).json({ error: "noaccess", status: access.length === 0 ? "notrequested" : "requested" });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[getClub] - get club');
|
||||
const club = await ClubService.findClubById(clubId);
|
||||
console.log('[getClub] - check club exists');
|
||||
if (!club) {
|
||||
return res.status(404).json({ message: 'Club not found' });
|
||||
}
|
||||
|
||||
console.log('[getClub] - set response');
|
||||
res.status(200).json(club);
|
||||
console.log('[getClub] - done');
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error('[getClub] - error:', error);
|
||||
res.status(500).json({ message: 'Server error' });
|
||||
}
|
||||
};
|
||||
@@ -82,7 +62,6 @@ export const requestClubAccess = async (req, res) => {
|
||||
|
||||
try {
|
||||
const user = await getUserByToken(token);
|
||||
console.log(user);
|
||||
|
||||
await ClubService.requestAccessToClub(user.id, clubId);
|
||||
res.status(200).json({});
|
||||
@@ -92,6 +71,7 @@ export const requestClubAccess = async (req, res) => {
|
||||
} else if (error.message === 'clubnotfound') {
|
||||
res.status(404).json({ err: "clubnotfound" });
|
||||
} else {
|
||||
console.error('[requestClubAccess] - error:', error);
|
||||
res.status(500).json({ err: "internalerror" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import diaryService from '../services/diaryService.js';
|
||||
import HttpError from '../exceptions/HttpError.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
const getDatesForClub = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.params;
|
||||
@@ -38,7 +39,7 @@ const updateTrainingTimes = async (req, res) => {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { dateId, trainingStart, trainingEnd } = req.body;
|
||||
if (!dateId || !trainingStart) {
|
||||
console.log(dateId, trainingStart, trainingEnd);
|
||||
devLog(dateId, trainingStart, trainingEnd);
|
||||
throw new HttpError('notallfieldsfilled', 400);
|
||||
}
|
||||
const updatedDate = await diaryService.updateTrainingTimes(userToken, clubId, dateId, trainingStart, trainingEnd);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import diaryDateActivityService from '../services/diaryDateActivityService.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
export const createDiaryDateActivity = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
@@ -15,7 +16,7 @@ export const createDiaryDateActivity = async (req, res) => {
|
||||
});
|
||||
res.status(201).json(activityItem);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
devLog(error);
|
||||
res.status(500).json({ error: 'Error creating activity' });
|
||||
}
|
||||
};
|
||||
@@ -58,7 +59,7 @@ export const updateDiaryDateActivityOrder = async (req, res) => {
|
||||
const updatedActivity = await diaryDateActivityService.updateActivityOrder(userToken, clubId, id, orderId);
|
||||
res.status(200).json(updatedActivity);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
devLog(error);
|
||||
res.status(500).json({ error: 'Error updating activity order' });
|
||||
}
|
||||
};
|
||||
@@ -70,7 +71,7 @@ export const getDiaryDateActivities = async (req, res) => {
|
||||
const activities = await diaryDateActivityService.getActivities(userToken, clubId, diaryDateId);
|
||||
res.status(200).json(activities);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
devLog(error);
|
||||
res.status(500).json({ error: 'Error getting activities' });
|
||||
}
|
||||
}
|
||||
@@ -82,7 +83,7 @@ export const addGroupActivity = async(req, res) => {
|
||||
const activityItem = await diaryDateActivityService.addGroupActivity(userToken, clubId, diaryDateId, groupId, activity);
|
||||
res.status(201).json(activityItem);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
devLog(error);
|
||||
res.status(500).json({ error: 'Error adding group activity' });
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import diaryDateTagService from "../services/diaryDateTagService.js"
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
export const getDiaryDateMemberTags = async (req, res) => {
|
||||
console.log("getDiaryDateMemberTags");
|
||||
devLog("getDiaryDateMemberTags");
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId, memberId } = req.params;
|
||||
@@ -14,7 +15,7 @@ export const getDiaryDateMemberTags = async (req, res) => {
|
||||
}
|
||||
|
||||
export const addDiaryDateTag = async (req, res) => {
|
||||
console.log("addDiaryDateTag");
|
||||
devLog("addDiaryDateTag");
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId } = req.params;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import DiaryMemberService from '../services/diaryMemberService.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
const getMemberTags = async (req, res) => {
|
||||
try {
|
||||
const { diaryDateId, memberId } = req.query;
|
||||
const { clubId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
console.log(diaryDateId, memberId, clubId);
|
||||
devLog(diaryDateId, memberId, clubId);
|
||||
const tags = await DiaryMemberService.getTagsForMemberAndDate(userToken, clubId, diaryDateId, memberId);
|
||||
res.status(200).json(tags);
|
||||
} catch (error) {
|
||||
@@ -19,7 +20,7 @@ const getMemberNotes = async (req, res) => {
|
||||
const { diaryDateId, memberId } = req.query;
|
||||
const { clubId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
console.log('---------->', userToken, clubId);
|
||||
devLog('---------->', userToken, clubId);
|
||||
const notes = await DiaryMemberService.getNotesForMember(userToken, clubId, diaryDateId, memberId);
|
||||
res.status(200).json(notes);
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { DiaryTag, DiaryDateTag } from '../models/index.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
export const getTags = async (req, res) => {
|
||||
try {
|
||||
const tags = await DiaryTag.findAll();
|
||||
@@ -12,11 +13,10 @@ export const getTags = async (req, res) => {
|
||||
export const createTag = async (req, res) => {
|
||||
try {
|
||||
const { name } = req.body;
|
||||
console.log(name);
|
||||
devLog(name);
|
||||
const newTag = await DiaryTag.findOrCreate({ where: { name }, defaults: { name } });
|
||||
res.status(201).json(newTag);
|
||||
} catch (error) {
|
||||
console.log('[createTag] - Error:', error);
|
||||
res.status(500).json({ error: 'Error creating tag' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import HttpError from '../exceptions/HttpError.js';
|
||||
import groupService from '../services/groupService.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
const addGroup = async(req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
@@ -9,7 +10,7 @@ const addGroup = async(req, res) => {
|
||||
res.status(201).json(result);
|
||||
} catch (error) {
|
||||
console.error('[addGroup] - Error:', error);
|
||||
console.log(req.params, req.headers, req.body)
|
||||
devLog(req.params, req.headers, req.body)
|
||||
res.status(error.statusCode || 500).json({ error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import MatchService from '../services/matchService.js';
|
||||
import fs from 'fs';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
export const uploadCSV = async (req, res) => {
|
||||
try {
|
||||
const { clubId } = req.body;
|
||||
@@ -21,10 +22,11 @@ export const uploadCSV = async (req, res) => {
|
||||
|
||||
export const getLeaguesForCurrentSeason = async (req, res) => {
|
||||
try {
|
||||
console.log(req.headers, req.params);
|
||||
devLog(req.headers, req.params);
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId } = req.params;
|
||||
const leagues = await MatchService.getLeaguesForCurrentSeason(userToken, clubId);
|
||||
const { seasonid: seasonId } = req.query;
|
||||
const leagues = await MatchService.getLeaguesForCurrentSeason(userToken, clubId, seasonId);
|
||||
return res.status(200).json(leagues);
|
||||
} catch (error) {
|
||||
console.error('Error retrieving leagues:', error);
|
||||
@@ -36,7 +38,8 @@ export const getMatchesForLeagues = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { clubId } = req.params;
|
||||
const matches = await MatchService.getMatchesForLeagues(userToken, clubId);
|
||||
const { seasonid: seasonId } = req.query;
|
||||
const matches = await MatchService.getMatchesForLeagues(userToken, clubId, seasonId);
|
||||
return res.status(200).json(matches);
|
||||
} catch (error) {
|
||||
console.error('Error retrieving matches:', error);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import MemberService from "../services/memberService.js";
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
const getClubMembers = async(req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
@@ -9,24 +10,17 @@ const getClubMembers = async(req, res) => {
|
||||
}
|
||||
res.status(200).json(await MemberService.getClubMembers(userToken, clubId, showAll));
|
||||
} catch(error) {
|
||||
console.log('[getClubMembers] - Error: ', error);
|
||||
res.status(500).json({ error: 'systemerror' });
|
||||
}
|
||||
}
|
||||
|
||||
const getWaitingApprovals = async(req, res) => {
|
||||
try {
|
||||
console.log('[getWaitingApprovals] - Start');
|
||||
const { id: clubId } = req.params;
|
||||
console.log('[getWaitingApprovals] - get token');
|
||||
const { authcode: userToken } = req.headers;
|
||||
console.log('[getWaitingApprovals] - load for waiting approvals');
|
||||
const waitingApprovals = await MemberService.getApprovalRequests(userToken, clubId);
|
||||
console.log('[getWaitingApprovals] - set response');
|
||||
res.status(200).json(waitingApprovals);
|
||||
console.log('[getWaitingApprovals] - done');
|
||||
} catch(error) {
|
||||
console.log('[getWaitingApprovals] - Error: ', error);
|
||||
res.status(403).json({ error: error });
|
||||
}
|
||||
}
|
||||
@@ -34,11 +28,11 @@ const getWaitingApprovals = async(req, res) => {
|
||||
const setClubMembers = async (req, res) => {
|
||||
try {
|
||||
const { id: memberId, firstname: firstName, lastname: lastName, street, city, birthdate, phone, email, active,
|
||||
testMembership, picsInInternetAllowed, gender } = req.body;
|
||||
testMembership, picsInInternetAllowed, gender, ttr, qttr } = req.body;
|
||||
const { id: clubId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const addResult = await MemberService.setClubMember(userToken, clubId, memberId, firstName, lastName, street, city, birthdate,
|
||||
phone, email, active, testMembership, picsInInternetAllowed, gender);
|
||||
phone, email, active, testMembership, picsInInternetAllowed, gender, ttr, qttr);
|
||||
res.status(addResult.status || 500).json(addResult.response);
|
||||
} catch (error) {
|
||||
console.error('[setClubMembers] - Error:', error);
|
||||
@@ -59,7 +53,6 @@ const uploadMemberImage = async (req, res) => {
|
||||
};
|
||||
|
||||
const getMemberImage = async (req, res) => {
|
||||
console.log('[getMemberImage]');
|
||||
try {
|
||||
const { clubId, memberId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
@@ -75,4 +68,16 @@ const getMemberImage = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
export { getClubMembers, getWaitingApprovals, setClubMembers, uploadMemberImage, getMemberImage };
|
||||
const updateRatingsFromMyTischtennis = async (req, res) => {
|
||||
try {
|
||||
const { id: clubId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const result = await MemberService.updateRatingsFromMyTischtennis(userToken, clubId);
|
||||
res.status(result.status).json(result.response);
|
||||
} catch (error) {
|
||||
console.error('[updateRatingsFromMyTischtennis] - Error:', error);
|
||||
res.status(500).json({ error: 'Failed to update ratings' });
|
||||
}
|
||||
};
|
||||
|
||||
export { getClubMembers, getWaitingApprovals, setClubMembers, uploadMemberImage, getMemberImage, updateRatingsFromMyTischtennis };
|
||||
@@ -1,15 +1,14 @@
|
||||
import MemberNoteService from "../services/memberNoteService.js";
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
const getMemberNotes = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { memberId } = req.params;
|
||||
const { clubId } = req.query;
|
||||
console.log('[getMemberNotes]', userToken, memberId, clubId);
|
||||
const notes = await MemberNoteService.getNotesForMember(userToken, clubId, memberId);
|
||||
res.status(200).json(notes);
|
||||
} catch (error) {
|
||||
console.log('[getMemberNotes] - Error: ', error);
|
||||
res.status(500).json({ error: 'systemerror' });
|
||||
}
|
||||
};
|
||||
@@ -18,12 +17,10 @@ const addMemberNote = async (req, res) => {
|
||||
try {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { memberId, content, clubId } = req.body;
|
||||
console.log('[addMemberNote]', userToken, memberId, content, clubId);
|
||||
await MemberNoteService.addNoteToMember(userToken, clubId, memberId, content);
|
||||
const notes = await MemberNoteService.getNotesForMember(userToken, clubId, memberId);
|
||||
res.status(201).json(notes);
|
||||
} catch (error) {
|
||||
console.log('[addMemberNote] - Error: ', error);
|
||||
res.status(500).json({ error: 'systemerror' });
|
||||
}
|
||||
};
|
||||
@@ -33,13 +30,11 @@ const deleteMemberNote = async (req, res) => {
|
||||
const { authcode: userToken } = req.headers;
|
||||
const { noteId } = req.params;
|
||||
const { clubId } = req.body;
|
||||
console.log('[deleteMemberNote]', userToken, noteId, clubId);
|
||||
const memberId = await MemberNoteService.getMemberIdForNote(noteId); // Member ID ermitteln
|
||||
await MemberNoteService.deleteNoteForMember(userToken, clubId, noteId);
|
||||
const notes = await MemberNoteService.getNotesForMember(userToken, clubId, memberId);
|
||||
res.status(200).json(notes);
|
||||
} catch (error) {
|
||||
console.log('[deleteMemberNote] - Error: ', error);
|
||||
res.status(500).json({ error: 'systemerror' });
|
||||
}
|
||||
};
|
||||
|
||||
133
backend/controllers/myTischtennisController.js
Normal file
133
backend/controllers/myTischtennisController.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import myTischtennisService from '../services/myTischtennisService.js';
|
||||
import HttpError from '../exceptions/HttpError.js';
|
||||
|
||||
class MyTischtennisController {
|
||||
/**
|
||||
* GET /api/mytischtennis/account
|
||||
* Get current user's myTischtennis account
|
||||
*/
|
||||
async getAccount(req, res, next) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const account = await myTischtennisService.getAccount(userId);
|
||||
|
||||
if (!account) {
|
||||
return res.status(200).json({ account: null });
|
||||
}
|
||||
|
||||
res.status(200).json({ account });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/mytischtennis/status
|
||||
* Check account configuration status
|
||||
*/
|
||||
async getStatus(req, res, next) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const status = await myTischtennisService.checkAccountStatus(userId);
|
||||
res.status(200).json(status);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/mytischtennis/account
|
||||
* Create or update myTischtennis account
|
||||
*/
|
||||
async upsertAccount(req, res, next) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const { email, password, savePassword, userPassword } = req.body;
|
||||
|
||||
if (!email) {
|
||||
throw new HttpError(400, 'E-Mail-Adresse erforderlich');
|
||||
}
|
||||
|
||||
// Wenn ein Passwort gesetzt wird, muss das App-Passwort angegeben werden
|
||||
if (password && !userPassword) {
|
||||
throw new HttpError(400, 'App-Passwort erforderlich zum Setzen des myTischtennis-Passworts');
|
||||
}
|
||||
|
||||
const account = await myTischtennisService.upsertAccount(
|
||||
userId,
|
||||
email,
|
||||
password,
|
||||
savePassword || false,
|
||||
userPassword
|
||||
);
|
||||
|
||||
res.status(200).json({
|
||||
message: 'myTischtennis-Account erfolgreich gespeichert',
|
||||
account
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /api/mytischtennis/account
|
||||
* Delete myTischtennis account
|
||||
*/
|
||||
async deleteAccount(req, res, next) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const deleted = await myTischtennisService.deleteAccount(userId);
|
||||
|
||||
if (!deleted) {
|
||||
throw new HttpError(404, 'Kein myTischtennis-Account gefunden');
|
||||
}
|
||||
|
||||
res.status(200).json({ message: 'myTischtennis-Account gelöscht' });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/mytischtennis/verify
|
||||
* Verify login credentials
|
||||
*/
|
||||
async verifyLogin(req, res, next) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const { password } = req.body;
|
||||
|
||||
const result = await myTischtennisService.verifyLogin(userId, password);
|
||||
|
||||
res.status(200).json({
|
||||
message: 'Login erfolgreich',
|
||||
success: true,
|
||||
accessToken: result.accessToken,
|
||||
expiresAt: result.expiresAt,
|
||||
clubId: result.clubId,
|
||||
clubName: result.clubName
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/mytischtennis/session
|
||||
* Get stored session data for authenticated requests
|
||||
*/
|
||||
async getSession(req, res, next) {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const session = await myTischtennisService.getSession(userId);
|
||||
|
||||
res.status(200).json({ session });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new MyTischtennisController();
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import Participant from '../models/Participant.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
export const getParticipants = async (req, res) => {
|
||||
try {
|
||||
const { dateId } = req.params;
|
||||
const participants = await Participant.findAll({ where: { diaryDateId: dateId } });
|
||||
res.status(200).json(participants);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
devLog(error);
|
||||
res.status(500).json({ error: 'Fehler beim Abrufen der Teilnehmer' });
|
||||
}
|
||||
};
|
||||
@@ -17,7 +18,7 @@ export const addParticipant = async (req, res) => {
|
||||
const participant = await Participant.create({ diaryDateId, memberId });
|
||||
res.status(201).json(participant);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
devLog(error);
|
||||
res.status(500).json({ error: 'Fehler beim Hinzufügen des Teilnehmers' });
|
||||
}
|
||||
};
|
||||
@@ -28,7 +29,7 @@ export const removeParticipant = async (req, res) => {
|
||||
await Participant.destroy({ where: { diaryDateId, memberId } });
|
||||
res.status(200).json({ message: 'Teilnehmer entfernt' });
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
devLog(error);
|
||||
res.status(500).json({ error: 'Fehler beim Entfernen des Teilnehmers' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
import sharp from 'sharp';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
export const uploadPredefinedActivityImage = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params; // predefinedActivityId
|
||||
@@ -35,7 +36,6 @@ export const uploadPredefinedActivityImage = async (req, res) => {
|
||||
|
||||
// Extrahiere Zeichnungsdaten aus dem Request
|
||||
const drawingData = req.body.drawingData ? JSON.parse(req.body.drawingData) : null;
|
||||
console.log('[uploadPredefinedActivityImage] - drawingData:', drawingData);
|
||||
|
||||
const imageRecord = await PredefinedActivityImage.create({
|
||||
predefinedActivityId: id,
|
||||
|
||||
103
backend/controllers/seasonController.js
Normal file
103
backend/controllers/seasonController.js
Normal file
@@ -0,0 +1,103 @@
|
||||
import SeasonService from '../services/seasonService.js';
|
||||
import { getUserByToken } from '../utils/userUtils.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
export const getSeasons = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const seasons = await SeasonService.getAllSeasons();
|
||||
|
||||
res.status(200).json(seasons);
|
||||
} catch (error) {
|
||||
console.error('[getSeasons] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getCurrentSeason = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const season = await SeasonService.getOrCreateCurrentSeason();
|
||||
|
||||
res.status(200).json(season);
|
||||
} catch (error) {
|
||||
console.error('[getCurrentSeason] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const createSeason = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { season } = req.body;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
if (!season) {
|
||||
return res.status(400).json({ error: "missingseason" });
|
||||
}
|
||||
|
||||
// Validiere Saison-Format (z.B. "2023/2024")
|
||||
const seasonRegex = /^\d{4}\/\d{4}$/;
|
||||
if (!seasonRegex.test(season)) {
|
||||
return res.status(400).json({ error: "invalidseasonformat" });
|
||||
}
|
||||
|
||||
const newSeason = await SeasonService.createSeason(season);
|
||||
|
||||
res.status(201).json(newSeason);
|
||||
} catch (error) {
|
||||
console.error('[createSeason] - Error:', error);
|
||||
if (error.message === 'Season already exists') {
|
||||
res.status(409).json({ error: "alreadyexists" });
|
||||
} else {
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getSeason = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { seasonid: seasonId } = req.params;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const season = await SeasonService.getSeasonById(seasonId);
|
||||
if (!season) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json(season);
|
||||
} catch (error) {
|
||||
console.error('[getSeason] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteSeason = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { seasonid: seasonId } = req.params;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const success = await SeasonService.deleteSeason(seasonId);
|
||||
if (!success) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json({ message: "deleted" });
|
||||
} catch (error) {
|
||||
console.error('[deleteSeason] - Error:', error);
|
||||
if (error.message === 'Season is used by teams' || error.message === 'Season is used by leagues') {
|
||||
res.status(409).json({ error: "seasoninuse" });
|
||||
} else {
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
}
|
||||
};
|
||||
130
backend/controllers/teamController.js
Normal file
130
backend/controllers/teamController.js
Normal file
@@ -0,0 +1,130 @@
|
||||
import TeamService from '../services/teamService.js';
|
||||
import { getUserByToken } from '../utils/userUtils.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
export const getTeams = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { seasonid: seasonId } = req.query;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
// Check if user has access to this club
|
||||
const teams = await TeamService.getAllTeamsByClub(clubId, seasonId);
|
||||
|
||||
res.status(200).json(teams);
|
||||
} catch (error) {
|
||||
console.error('[getTeams] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { teamid: teamId } = req.params;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const team = await TeamService.getTeamById(teamId);
|
||||
if (!team) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json(team);
|
||||
} catch (error) {
|
||||
console.error('[getTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const createTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { name, leagueId, seasonId } = req.body;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
if (!name) {
|
||||
return res.status(400).json({ error: "missingname" });
|
||||
}
|
||||
|
||||
const teamData = {
|
||||
name,
|
||||
clubId: parseInt(clubId),
|
||||
leagueId: leagueId ? parseInt(leagueId) : null,
|
||||
seasonId: seasonId ? parseInt(seasonId) : null
|
||||
};
|
||||
|
||||
const newTeam = await TeamService.createTeam(teamData);
|
||||
|
||||
res.status(201).json(newTeam);
|
||||
} catch (error) {
|
||||
console.error('[createTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const updateTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { teamid: teamId } = req.params;
|
||||
const { name, leagueId, seasonId } = req.body;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const updateData = {};
|
||||
if (name !== undefined) updateData.name = name;
|
||||
if (leagueId !== undefined) updateData.leagueId = leagueId ? parseInt(leagueId) : null;
|
||||
if (seasonId !== undefined) updateData.seasonId = seasonId ? parseInt(seasonId) : null;
|
||||
|
||||
const success = await TeamService.updateTeam(teamId, updateData);
|
||||
if (!success) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
const updatedTeam = await TeamService.getTeamById(teamId);
|
||||
res.status(200).json(updatedTeam);
|
||||
} catch (error) {
|
||||
console.error('[updateTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteTeam = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { teamid: teamId } = req.params;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const success = await TeamService.deleteTeam(teamId);
|
||||
if (!success) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json({ message: "deleted" });
|
||||
} catch (error) {
|
||||
console.error('[deleteTeam] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getLeagues = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubid: clubId } = req.params;
|
||||
const { seasonid: seasonId } = req.query;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const leagues = await TeamService.getLeaguesByClub(clubId, seasonId);
|
||||
|
||||
res.status(200).json(leagues);
|
||||
} catch (error) {
|
||||
console.error('[getLeagues] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
215
backend/controllers/teamDocumentController.js
Normal file
215
backend/controllers/teamDocumentController.js
Normal file
@@ -0,0 +1,215 @@
|
||||
import multer from 'multer';
|
||||
import path from 'path';
|
||||
import TeamDocumentService from '../services/teamDocumentService.js';
|
||||
import PDFParserService from '../services/pdfParserService.js';
|
||||
import { getUserByToken } from '../utils/userUtils.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
// Multer-Konfiguration für Datei-Uploads
|
||||
const storage = multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
cb(null, 'uploads/temp/');
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
limits: {
|
||||
fileSize: 10 * 1024 * 1024 // 10MB Limit
|
||||
},
|
||||
fileFilter: (req, file, cb) => {
|
||||
// Erlaube nur PDF, DOC, DOCX, TXT, CSV Dateien
|
||||
const allowedTypes = /pdf|doc|docx|txt|csv/;
|
||||
const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase());
|
||||
const mimetype = allowedTypes.test(file.mimetype);
|
||||
|
||||
if (mimetype && extname) {
|
||||
return cb(null, true);
|
||||
} else {
|
||||
cb(new Error('Nur PDF, DOC, DOCX, TXT und CSV Dateien sind erlaubt!'));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const uploadMiddleware = upload.single('document');
|
||||
|
||||
export const uploadDocument = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubteamid: clubTeamId } = req.params;
|
||||
const { documentType } = req.body;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: "nofile" });
|
||||
}
|
||||
|
||||
if (!documentType || !['code_list', 'pin_list'].includes(documentType)) {
|
||||
return res.status(400).json({ error: "invaliddocumenttype" });
|
||||
}
|
||||
|
||||
const document = await TeamDocumentService.uploadDocument(req.file, clubTeamId, documentType);
|
||||
|
||||
res.status(201).json(document);
|
||||
} catch (error) {
|
||||
console.error('[uploadDocument] - Error:', error);
|
||||
|
||||
// Lösche temporäre Datei bei Fehler
|
||||
if (req.file && req.file.path) {
|
||||
try {
|
||||
const fs = await import('fs');
|
||||
fs.unlinkSync(req.file.path);
|
||||
} catch (cleanupError) {
|
||||
console.error('Fehler beim Löschen der temporären Datei:', cleanupError);
|
||||
}
|
||||
}
|
||||
|
||||
if (error.message === 'Club-Team nicht gefunden') {
|
||||
return res.status(404).json({ error: "clubteamnotfound" });
|
||||
}
|
||||
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getDocuments = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { clubteamid: clubTeamId } = req.params;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const documents = await TeamDocumentService.getDocumentsByClubTeam(clubTeamId);
|
||||
|
||||
res.status(200).json(documents);
|
||||
} catch (error) {
|
||||
console.error('[getDocuments] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const getDocument = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { documentid: documentId } = req.params;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const document = await TeamDocumentService.getDocumentById(documentId);
|
||||
if (!document) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json(document);
|
||||
} catch (error) {
|
||||
console.error('[getDocument] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const downloadDocument = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { documentid: documentId } = req.params;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const document = await TeamDocumentService.getDocumentById(documentId);
|
||||
if (!document) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
const filePath = await TeamDocumentService.getDocumentPath(documentId);
|
||||
if (!filePath) {
|
||||
return res.status(404).json({ error: "filenotfound" });
|
||||
}
|
||||
|
||||
// Prüfe ob Datei existiert
|
||||
const fs = await import('fs');
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return res.status(404).json({ error: "filenotfound" });
|
||||
}
|
||||
|
||||
// Setze Headers für Inline-Anzeige (PDF-Viewer)
|
||||
res.setHeader('Content-Disposition', `inline; filename="${document.originalFileName}"`);
|
||||
res.setHeader('Content-Type', document.mimeType);
|
||||
|
||||
// Sende die Datei
|
||||
res.sendFile(filePath);
|
||||
} catch (error) {
|
||||
console.error('[downloadDocument] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteDocument = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { documentid: documentId } = req.params;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
const success = await TeamDocumentService.deleteDocument(documentId);
|
||||
if (!success) {
|
||||
return res.status(404).json({ error: "notfound" });
|
||||
}
|
||||
|
||||
res.status(200).json({ message: "Document deleted successfully" });
|
||||
} catch (error) {
|
||||
console.error('[deleteDocument] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
|
||||
export const parsePDF = async (req, res) => {
|
||||
try {
|
||||
const { authcode: token } = req.headers;
|
||||
const { documentid: documentId } = req.params;
|
||||
const { leagueid: leagueId } = req.query;
|
||||
|
||||
const user = await getUserByToken(token);
|
||||
|
||||
if (!leagueId) {
|
||||
return res.status(400).json({ error: "missingleagueid" });
|
||||
}
|
||||
|
||||
// Hole Dokument-Informationen
|
||||
const document = await TeamDocumentService.getDocumentById(documentId);
|
||||
if (!document) {
|
||||
return res.status(404).json({ error: "documentnotfound" });
|
||||
}
|
||||
|
||||
// Prüfe ob es eine PDF- oder TXT-Datei ist
|
||||
if (!document.mimeType.includes('pdf') && !document.mimeType.includes('text/plain')) {
|
||||
return res.status(400).json({ error: "notapdfortxt" });
|
||||
}
|
||||
|
||||
// Parse PDF
|
||||
const parseResult = await PDFParserService.parsePDF(document.filePath, document.clubTeam.clubId);
|
||||
|
||||
// Speichere Matches in Datenbank
|
||||
const saveResult = await PDFParserService.saveMatchesToDatabase(parseResult.matches, parseInt(leagueId));
|
||||
|
||||
res.status(200).json({
|
||||
parseResult: {
|
||||
matchesFound: parseResult.matches.length,
|
||||
debugInfo: parseResult.debugInfo,
|
||||
allLines: parseResult.allLines,
|
||||
rawText: parseResult.rawText
|
||||
},
|
||||
saveResult: {
|
||||
created: saveResult.created,
|
||||
updated: saveResult.updated,
|
||||
errors: saveResult.errors
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[parsePDF] - Error:', error);
|
||||
res.status(500).json({ error: "internalerror" });
|
||||
}
|
||||
};
|
||||
@@ -19,6 +19,25 @@ class TrainingStatsController {
|
||||
}
|
||||
});
|
||||
|
||||
// Anzahl der Trainings im jeweiligen Zeitraum berechnen
|
||||
const trainingsCount12Months = await DiaryDate.count({
|
||||
where: {
|
||||
clubId: parseInt(clubId),
|
||||
date: {
|
||||
[Op.gte]: twelveMonthsAgo
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const trainingsCount3Months = await DiaryDate.count({
|
||||
where: {
|
||||
clubId: parseInt(clubId),
|
||||
date: {
|
||||
[Op.gte]: threeMonthsAgo
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const stats = [];
|
||||
|
||||
for (const member of members) {
|
||||
@@ -116,7 +135,36 @@ class TrainingStatsController {
|
||||
// Nach Gesamtteilnahme absteigend sortieren
|
||||
stats.sort((a, b) => b.participationTotal - a.participationTotal);
|
||||
|
||||
res.json(stats);
|
||||
// Trainingstage mit Teilnehmerzahlen abrufen (letzte 12 Monate, absteigend sortiert)
|
||||
const trainingDays = await DiaryDate.findAll({
|
||||
where: {
|
||||
clubId: parseInt(clubId),
|
||||
date: {
|
||||
[Op.gte]: twelveMonthsAgo
|
||||
}
|
||||
},
|
||||
include: [{
|
||||
model: Participant,
|
||||
as: 'participantList',
|
||||
attributes: ['id']
|
||||
}],
|
||||
order: [['date', 'DESC']]
|
||||
});
|
||||
|
||||
// Formatiere Trainingstage mit Teilnehmerzahl
|
||||
const formattedTrainingDays = trainingDays.map(day => ({
|
||||
id: day.id,
|
||||
date: day.date,
|
||||
participantCount: day.participantList ? day.participantList.length : 0
|
||||
}));
|
||||
|
||||
// Zusätzliche Metadaten mit Trainingsanzahl zurückgeben
|
||||
res.json({
|
||||
members: stats,
|
||||
trainingsCount12Months,
|
||||
trainingsCount3Months,
|
||||
trainingDays: formattedTrainingDays
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Trainings-Statistik:', error);
|
||||
|
||||
44
backend/migrations/add_season_to_teams.sql
Normal file
44
backend/migrations/add_season_to_teams.sql
Normal file
@@ -0,0 +1,44 @@
|
||||
-- Migration: Add season_id to teams table
|
||||
-- First, add the column as nullable
|
||||
ALTER TABLE `team` ADD COLUMN `season_id` INT NULL;
|
||||
|
||||
-- Get or create current season
|
||||
SET @current_season_id = (
|
||||
SELECT id FROM `season`
|
||||
WHERE season = (
|
||||
CASE
|
||||
WHEN MONTH(CURDATE()) >= 7 THEN CONCAT(YEAR(CURDATE()), '/', YEAR(CURDATE()) + 1)
|
||||
ELSE CONCAT(YEAR(CURDATE()) - 1, '/', YEAR(CURDATE()))
|
||||
END
|
||||
)
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
-- If no season exists, create it
|
||||
INSERT IGNORE INTO `season` (season) VALUES (
|
||||
CASE
|
||||
WHEN MONTH(CURDATE()) >= 7 THEN CONCAT(YEAR(CURDATE()), '/', YEAR(CURDATE()) + 1)
|
||||
ELSE CONCAT(YEAR(CURDATE()) - 1, '/', YEAR(CURDATE()))
|
||||
END
|
||||
);
|
||||
|
||||
-- Get the season ID again (in case we just created it)
|
||||
SET @current_season_id = (
|
||||
SELECT id FROM `season`
|
||||
WHERE season = (
|
||||
CASE
|
||||
WHEN MONTH(CURDATE()) >= 7 THEN CONCAT(YEAR(CURDATE()), '/', YEAR(CURDATE()) + 1)
|
||||
ELSE CONCAT(YEAR(CURDATE()) - 1, '/', YEAR(CURDATE()))
|
||||
END
|
||||
)
|
||||
LIMIT 1
|
||||
);
|
||||
|
||||
-- Update all existing teams to use the current season
|
||||
UPDATE `team` SET `season_id` = @current_season_id WHERE `season_id` IS NULL;
|
||||
|
||||
-- Now make the column NOT NULL and add the foreign key constraint
|
||||
ALTER TABLE `team` MODIFY COLUMN `season_id` INT NOT NULL;
|
||||
ALTER TABLE `team` ADD CONSTRAINT `team_season_id_foreign_idx`
|
||||
FOREIGN KEY (`season_id`) REFERENCES `season` (`id`)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
54
backend/models/ClubTeam.js
Normal file
54
backend/models/ClubTeam.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import Club from './Club.js';
|
||||
import League from './League.js';
|
||||
import Season from './Season.js';
|
||||
|
||||
const ClubTeam = sequelize.define('ClubTeam', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false,
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
clubId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: Club,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
leagueId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: League,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'SET NULL',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
seasonId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: Season,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'club_team',
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
export default ClubTeam;
|
||||
@@ -3,7 +3,6 @@ import sequelize from '../database.js';
|
||||
import Club from './Club.js';
|
||||
import League from './League.js';
|
||||
import Team from './Team.js';
|
||||
import Season from './Season.js';
|
||||
import Location from './Location.js';
|
||||
|
||||
const Match = sequelize.define('Match', {
|
||||
@@ -21,14 +20,6 @@ const Match = sequelize.define('Match', {
|
||||
type: DataTypes.TIME,
|
||||
allowNull: true,
|
||||
},
|
||||
seasonId: {
|
||||
type: DataTypes.INTEGER,
|
||||
references: {
|
||||
model: Season,
|
||||
key: 'id',
|
||||
},
|
||||
allowNull: false,
|
||||
},
|
||||
locationId: {
|
||||
type: DataTypes.INTEGER,
|
||||
references: {
|
||||
@@ -69,6 +60,21 @@ const Match = sequelize.define('Match', {
|
||||
},
|
||||
allowNull: false,
|
||||
},
|
||||
code: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
comment: 'Spiel-Code aus PDF-Parsing'
|
||||
},
|
||||
homePin: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
comment: 'Pin-Code für Heimteam aus PDF-Parsing'
|
||||
},
|
||||
guestPin: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
comment: 'Pin-Code für Gastteam aus PDF-Parsing'
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'match',
|
||||
|
||||
@@ -127,6 +127,16 @@ const Member = sequelize.define('Member', {
|
||||
type: DataTypes.ENUM('male','female','diverse','unknown'),
|
||||
allowNull: true,
|
||||
defaultValue: 'unknown'
|
||||
},
|
||||
ttr: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
},
|
||||
qttr: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
defaultValue: null
|
||||
}
|
||||
}, {
|
||||
underscored: true,
|
||||
|
||||
122
backend/models/MyTischtennis.js
Normal file
122
backend/models/MyTischtennis.js
Normal file
@@ -0,0 +1,122 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import { encryptData, decryptData } from '../utils/encrypt.js';
|
||||
|
||||
const MyTischtennis = sequelize.define('MyTischtennis', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
references: {
|
||||
model: 'user',
|
||||
key: 'id'
|
||||
},
|
||||
onDelete: 'CASCADE'
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
encryptedPassword: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'encrypted_password'
|
||||
},
|
||||
savePassword: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false,
|
||||
allowNull: false,
|
||||
field: 'save_password'
|
||||
},
|
||||
accessToken: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'access_token'
|
||||
},
|
||||
refreshToken: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
field: 'refresh_token'
|
||||
},
|
||||
expiresAt: {
|
||||
type: DataTypes.BIGINT,
|
||||
allowNull: true,
|
||||
field: 'expires_at'
|
||||
},
|
||||
cookie: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
userData: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: true,
|
||||
field: 'user_data'
|
||||
},
|
||||
clubId: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
field: 'club_id'
|
||||
},
|
||||
clubName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
field: 'club_name'
|
||||
},
|
||||
fedNickname: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
field: 'fed_nickname'
|
||||
},
|
||||
lastLoginAttempt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
field: 'last_login_attempt'
|
||||
},
|
||||
lastLoginSuccess: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: true,
|
||||
field: 'last_login_success'
|
||||
}
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'my_tischtennis',
|
||||
timestamps: true,
|
||||
hooks: {
|
||||
beforeSave: async (instance) => {
|
||||
// Wenn savePassword false ist, password auf null setzen
|
||||
if (!instance.savePassword) {
|
||||
instance.encryptedPassword = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Virtuelle Felder für password handling
|
||||
MyTischtennis.prototype.setPassword = function(password) {
|
||||
if (password && this.savePassword) {
|
||||
this.encryptedPassword = encryptData(password);
|
||||
} else {
|
||||
this.encryptedPassword = null;
|
||||
}
|
||||
};
|
||||
|
||||
MyTischtennis.prototype.getPassword = function() {
|
||||
if (this.encryptedPassword) {
|
||||
try {
|
||||
return decryptData(this.encryptedPassword);
|
||||
} catch (error) {
|
||||
console.error('Error decrypting myTischtennis password:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export default MyTischtennis;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import Club from './Club.js';
|
||||
import League from './League.js';
|
||||
import Season from './Season.js';
|
||||
|
||||
const Team = sequelize.define('Team', {
|
||||
id: {
|
||||
@@ -23,6 +25,26 @@ const Team = sequelize.define('Team', {
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
leagueId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: League,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'SET NULL',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
seasonId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: {
|
||||
model: Season,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'team',
|
||||
|
||||
52
backend/models/TeamDocument.js
Normal file
52
backend/models/TeamDocument.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import sequelize from '../database.js';
|
||||
import ClubTeam from './ClubTeam.js';
|
||||
|
||||
const TeamDocument = sequelize.define('TeamDocument', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true,
|
||||
allowNull: false,
|
||||
},
|
||||
fileName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
originalFileName: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
filePath: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
fileSize: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
},
|
||||
mimeType: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
},
|
||||
documentType: {
|
||||
type: DataTypes.ENUM('code_list', 'pin_list'),
|
||||
allowNull: false,
|
||||
},
|
||||
clubTeamId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: ClubTeam,
|
||||
key: 'id',
|
||||
},
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
},
|
||||
}, {
|
||||
underscored: true,
|
||||
tableName: 'team_document',
|
||||
timestamps: true,
|
||||
});
|
||||
|
||||
export default TeamDocument;
|
||||
@@ -19,6 +19,8 @@ import DiaryDateActivity from './DiaryDateActivity.js';
|
||||
import Match from './Match.js';
|
||||
import League from './League.js';
|
||||
import Team from './Team.js';
|
||||
import ClubTeam from './ClubTeam.js';
|
||||
import TeamDocument from './TeamDocument.js';
|
||||
import Season from './Season.js';
|
||||
import Location from './Location.js';
|
||||
import Group from './Group.js';
|
||||
@@ -33,6 +35,7 @@ import UserToken from './UserToken.js';
|
||||
import OfficialTournament from './OfficialTournament.js';
|
||||
import OfficialCompetition from './OfficialCompetition.js';
|
||||
import OfficialCompetitionMember from './OfficialCompetitionMember.js';
|
||||
import MyTischtennis from './MyTischtennis.js';
|
||||
// Official tournaments relations
|
||||
OfficialTournament.hasMany(OfficialCompetition, { foreignKey: 'tournamentId', as: 'competitions' });
|
||||
OfficialCompetition.belongsTo(OfficialTournament, { foreignKey: 'tournamentId', as: 'tournament' });
|
||||
@@ -117,8 +120,25 @@ Team.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
|
||||
Club.hasMany(League, { foreignKey: 'clubId', as: 'leagues' });
|
||||
League.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
|
||||
|
||||
Match.belongsTo(Season, { foreignKey: 'seasonId', as: 'season' });
|
||||
Season.hasMany(Match, { foreignKey: 'seasonId', as: 'matches' });
|
||||
League.hasMany(Team, { foreignKey: 'leagueId', as: 'teams' });
|
||||
Team.belongsTo(League, { foreignKey: 'leagueId', as: 'league' });
|
||||
|
||||
Season.hasMany(Team, { foreignKey: 'seasonId', as: 'teams' });
|
||||
Team.belongsTo(Season, { foreignKey: 'seasonId', as: 'season' });
|
||||
|
||||
// ClubTeam relationships
|
||||
Club.hasMany(ClubTeam, { foreignKey: 'clubId', as: 'clubTeams' });
|
||||
ClubTeam.belongsTo(Club, { foreignKey: 'clubId', as: 'club' });
|
||||
|
||||
League.hasMany(ClubTeam, { foreignKey: 'leagueId', as: 'clubTeams' });
|
||||
ClubTeam.belongsTo(League, { foreignKey: 'leagueId', as: 'league' });
|
||||
|
||||
Season.hasMany(ClubTeam, { foreignKey: 'seasonId', as: 'clubTeams' });
|
||||
ClubTeam.belongsTo(Season, { foreignKey: 'seasonId', as: 'season' });
|
||||
|
||||
// TeamDocument relationships
|
||||
ClubTeam.hasMany(TeamDocument, { foreignKey: 'clubTeamId', as: 'documents' });
|
||||
TeamDocument.belongsTo(ClubTeam, { foreignKey: 'clubTeamId', as: 'clubTeam' });
|
||||
|
||||
Match.belongsTo(Location, { foreignKey: 'locationId', as: 'location' });
|
||||
Location.hasMany(Match, { foreignKey: 'locationId', as: 'matches' });
|
||||
@@ -204,6 +224,9 @@ Member.hasMany(Accident, { foreignKey: 'memberId', as: 'accidents' });
|
||||
Accident.belongsTo(DiaryDate, { foreignKey: 'diaryDateId', as: 'diaryDates' });
|
||||
DiaryDate.hasMany(Accident, { foreignKey: 'diaryDateId', as: 'accidents' });
|
||||
|
||||
User.hasOne(MyTischtennis, { foreignKey: 'userId', as: 'myTischtennis' });
|
||||
MyTischtennis.belongsTo(User, { foreignKey: 'userId', as: 'user' });
|
||||
|
||||
export {
|
||||
User,
|
||||
Log,
|
||||
@@ -227,6 +250,8 @@ export {
|
||||
Match,
|
||||
League,
|
||||
Team,
|
||||
ClubTeam,
|
||||
TeamDocument,
|
||||
Group,
|
||||
GroupActivity,
|
||||
Tournament,
|
||||
@@ -239,4 +264,5 @@ export {
|
||||
OfficialTournament,
|
||||
OfficialCompetition,
|
||||
OfficialCompetitionMember,
|
||||
MyTischtennis,
|
||||
};
|
||||
|
||||
236
backend/node_modules/.package-lock.json
generated
vendored
236
backend/node_modules/.package-lock.json
generated
vendored
@@ -4,6 +4,15 @@
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz",
|
||||
@@ -810,6 +819,12 @@
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/aws-ssl-profiles": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.1.tgz",
|
||||
@@ -818,6 +833,17 @@
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -955,6 +981,19 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -1084,6 +1123,18 @@
|
||||
"color-support": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -1227,6 +1278,22 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "2.30.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
|
||||
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.21.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.11"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/date-fns"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
@@ -1259,6 +1326,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delegates": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
@@ -1315,6 +1391,20 @@
|
||||
"resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz",
|
||||
"integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA=="
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
@@ -1342,13 +1432,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@@ -1362,6 +1449,33 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
@@ -1752,6 +1866,42 @@
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -1849,16 +1999,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -1867,6 +2022,19 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@@ -1913,12 +2081,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -1945,10 +2113,10 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -1957,11 +2125,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@@ -2405,6 +2576,15 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
@@ -2972,6 +3152,12 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pstree.remy": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||
|
||||
14
backend/node_modules/es-define-property/CHANGELOG.md
generated
vendored
14
backend/node_modules/es-define-property/CHANGELOG.md
generated
vendored
@@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [v1.0.1](https://github.com/ljharb/es-define-property/compare/v1.0.0...v1.0.1) - 2024-12-06
|
||||
|
||||
### Commits
|
||||
|
||||
- [types] use shared tsconfig [`954a663`](https://github.com/ljharb/es-define-property/commit/954a66360326e508a0e5daa4b07493d58f5e110e)
|
||||
- [actions] split out node 10-20, and 20+ [`3a8e84b`](https://github.com/ljharb/es-define-property/commit/3a8e84b23883f26ff37b3e82ff283834228e18c6)
|
||||
- [Dev Deps] update `@ljharb/eslint-config`, `@ljharb/tsconfig`, `@types/get-intrinsic`, `@types/tape`, `auto-changelog`, `gopd`, `tape` [`86ae27b`](https://github.com/ljharb/es-define-property/commit/86ae27bb8cc857b23885136fad9cbe965ae36612)
|
||||
- [Refactor] avoid using `get-intrinsic` [`02480c0`](https://github.com/ljharb/es-define-property/commit/02480c0353ef6118965282977c3864aff53d98b1)
|
||||
- [Tests] replace `aud` with `npm audit` [`f6093ff`](https://github.com/ljharb/es-define-property/commit/f6093ff74ab51c98015c2592cd393bd42478e773)
|
||||
- [Tests] configure testling [`7139e66`](https://github.com/ljharb/es-define-property/commit/7139e66959247a56086d9977359caef27c6849e7)
|
||||
- [Dev Deps] update `tape` [`b901b51`](https://github.com/ljharb/es-define-property/commit/b901b511a75e001a40ce1a59fef7d9ffcfc87482)
|
||||
- [Tests] fix types in tests [`469d269`](https://github.com/ljharb/es-define-property/commit/469d269fd141b1e773ec053a9fa35843493583e0)
|
||||
- [Dev Deps] add missing peer dep [`733acfb`](https://github.com/ljharb/es-define-property/commit/733acfb0c4c96edf337e470b89a25a5b3724c352)
|
||||
|
||||
## v1.0.0 - 2024-02-12
|
||||
|
||||
### Commits
|
||||
|
||||
4
backend/node_modules/es-define-property/index.js
generated
vendored
4
backend/node_modules/es-define-property/index.js
generated
vendored
@@ -1,9 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
var GetIntrinsic = require('get-intrinsic');
|
||||
|
||||
/** @type {import('.')} */
|
||||
var $defineProperty = GetIntrinsic('%Object.defineProperty%', true) || false;
|
||||
var $defineProperty = Object.defineProperty || false;
|
||||
if ($defineProperty) {
|
||||
try {
|
||||
$defineProperty({}, 'a', { value: 1 });
|
||||
|
||||
24
backend/node_modules/es-define-property/package.json
generated
vendored
24
backend/node_modules/es-define-property/package.json
generated
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "es-define-property",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "`Object.defineProperty`, but not IE 8's broken one.",
|
||||
"main": "index.js",
|
||||
"types": "./index.d.ts",
|
||||
@@ -19,7 +19,7 @@
|
||||
"pretest": "npm run lint",
|
||||
"tests-only": "nyc tape 'test/**/*.js'",
|
||||
"test": "npm run tests-only",
|
||||
"posttest": "aud --production",
|
||||
"posttest": "npx npm@'>= 10.2' audit --production",
|
||||
"version": "auto-changelog && git add CHANGELOG.md",
|
||||
"postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\""
|
||||
},
|
||||
@@ -42,29 +42,29 @@
|
||||
"url": "https://github.com/ljharb/es-define-property/issues"
|
||||
},
|
||||
"homepage": "https://github.com/ljharb/es-define-property#readme",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ljharb/eslint-config": "^21.1.0",
|
||||
"@types/get-intrinsic": "^1.2.2",
|
||||
"@ljharb/eslint-config": "^21.1.1",
|
||||
"@ljharb/tsconfig": "^0.2.2",
|
||||
"@types/gopd": "^1.0.3",
|
||||
"@types/tape": "^5.6.4",
|
||||
"aud": "^2.0.4",
|
||||
"auto-changelog": "^2.4.0",
|
||||
"@types/tape": "^5.6.5",
|
||||
"auto-changelog": "^2.5.0",
|
||||
"encoding": "^0.1.13",
|
||||
"eslint": "^8.8.0",
|
||||
"evalmd": "^0.0.19",
|
||||
"gopd": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"in-publish": "^2.0.1",
|
||||
"npmignore": "^0.3.1",
|
||||
"nyc": "^10.3.2",
|
||||
"safe-publish-latest": "^2.0.0",
|
||||
"tape": "^5.7.4",
|
||||
"tape": "^5.9.0",
|
||||
"typescript": "next"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"testling": {
|
||||
"files": "test/index.js"
|
||||
},
|
||||
"auto-changelog": {
|
||||
"output": "CHANGELOG.md",
|
||||
"template": "keepachangelog",
|
||||
|
||||
1
backend/node_modules/es-define-property/test/index.js
generated
vendored
1
backend/node_modules/es-define-property/test/index.js
generated
vendored
@@ -10,6 +10,7 @@ test('defineProperty: supported', { skip: !$defineProperty }, function (t) {
|
||||
|
||||
t.equal(typeof $defineProperty, 'function', 'defineProperty is supported');
|
||||
if ($defineProperty && gOPD) { // this `if` check is just to shut TS up
|
||||
/** @type {{ a: number, b?: number, c?: number }} */
|
||||
var o = { a: 1 };
|
||||
|
||||
$defineProperty(o, 'b', { enumerable: true, value: 2 });
|
||||
|
||||
44
backend/node_modules/es-define-property/tsconfig.json
generated
vendored
44
backend/node_modules/es-define-property/tsconfig.json
generated
vendored
@@ -1,47 +1,7 @@
|
||||
{
|
||||
"extends": "@ljharb/tsconfig",
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
"useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": ["types"], /* Specify multiple folders that act like `./node_modules/@types`. */
|
||||
"resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
|
||||
/* JavaScript Support */
|
||||
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
|
||||
"checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
"maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
|
||||
|
||||
/* Emit */
|
||||
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
"declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
"noEmit": true, /* Disable emitting files from a compilation. */
|
||||
|
||||
/* Interop Constraints */
|
||||
"allowSyntheticDefaultImports": true, /* Allow `import x from y` when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
"target": "es2022",
|
||||
},
|
||||
"exclude": [
|
||||
"coverage",
|
||||
|
||||
4
backend/node_modules/get-intrinsic/.eslintrc
generated
vendored
4
backend/node_modules/get-intrinsic/.eslintrc
generated
vendored
@@ -11,6 +11,10 @@
|
||||
"es2022": true,
|
||||
},
|
||||
|
||||
"globals": {
|
||||
"Float16Array": false,
|
||||
},
|
||||
|
||||
"rules": {
|
||||
"array-bracket-newline": 0,
|
||||
"complexity": 0,
|
||||
|
||||
43
backend/node_modules/get-intrinsic/CHANGELOG.md
generated
vendored
43
backend/node_modules/get-intrinsic/CHANGELOG.md
generated
vendored
@@ -5,6 +5,49 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [v1.3.0](https://github.com/ljharb/get-intrinsic/compare/v1.2.7...v1.3.0) - 2025-02-22
|
||||
|
||||
### Commits
|
||||
|
||||
- [Dev Deps] update `es-abstract`, `es-value-fixtures`, `for-each`, `object-inspect` [`9b61553`](https://github.com/ljharb/get-intrinsic/commit/9b61553c587f1c1edbd435597e88c7d387da97dd)
|
||||
- [Deps] update `call-bind-apply-helpers`, `es-object-atoms`, `get-proto` [`a341fee`](https://github.com/ljharb/get-intrinsic/commit/a341fee0f39a403b0f0069e82c97642d5eb11043)
|
||||
- [New] add `Float16Array` [`de22116`](https://github.com/ljharb/get-intrinsic/commit/de22116b492fb989a0341bceb6e573abfaed73dc)
|
||||
|
||||
## [v1.2.7](https://github.com/ljharb/get-intrinsic/compare/v1.2.6...v1.2.7) - 2025-01-02
|
||||
|
||||
### Commits
|
||||
|
||||
- [Refactor] use `get-proto` directly [`00ab955`](https://github.com/ljharb/get-intrinsic/commit/00ab95546a0980c8ad42a84253daaa8d2adcedf9)
|
||||
- [Deps] update `math-intrinsics` [`c716cdd`](https://github.com/ljharb/get-intrinsic/commit/c716cdd6bbe36b438057025561b8bb5a879ac8a0)
|
||||
- [Dev Deps] update `call-bound`, `es-abstract` [`dc648a6`](https://github.com/ljharb/get-intrinsic/commit/dc648a67eb359037dff8d8619bfa71d86debccb1)
|
||||
|
||||
## [v1.2.6](https://github.com/ljharb/get-intrinsic/compare/v1.2.5...v1.2.6) - 2024-12-11
|
||||
|
||||
### Commits
|
||||
|
||||
- [Refactor] use `math-intrinsics` [`841be86`](https://github.com/ljharb/get-intrinsic/commit/841be8641a9254c4c75483b30c8871b5d5065926)
|
||||
- [Refactor] use `es-object-atoms` [`42057df`](https://github.com/ljharb/get-intrinsic/commit/42057dfa16f66f64787e66482af381cc6f31d2c1)
|
||||
- [Deps] update `call-bind-apply-helpers` [`45afa24`](https://github.com/ljharb/get-intrinsic/commit/45afa24a9ee4d6d3c172db1f555b16cb27843ef4)
|
||||
- [Dev Deps] update `call-bound` [`9cba9c6`](https://github.com/ljharb/get-intrinsic/commit/9cba9c6e70212bc163b7a5529cb25df46071646f)
|
||||
|
||||
## [v1.2.5](https://github.com/ljharb/get-intrinsic/compare/v1.2.4...v1.2.5) - 2024-12-06
|
||||
|
||||
### Commits
|
||||
|
||||
- [actions] split out node 10-20, and 20+ [`6e2b9dd`](https://github.com/ljharb/get-intrinsic/commit/6e2b9dd23902665681ebe453256ccfe21d7966f0)
|
||||
- [Refactor] use `dunder-proto` and `call-bind-apply-helpers` instead of `has-proto` [`c095d17`](https://github.com/ljharb/get-intrinsic/commit/c095d179ad0f4fbfff20c8a3e0cb4fe668018998)
|
||||
- [Refactor] use `gopd` [`9841d5b`](https://github.com/ljharb/get-intrinsic/commit/9841d5b35f7ab4fd2d193f0c741a50a077920e90)
|
||||
- [Dev Deps] update `@ljharb/eslint-config`, `auto-changelog`, `es-abstract`, `es-value-fixtures`, `gopd`, `mock-property`, `object-inspect`, `tape` [`2d07e01`](https://github.com/ljharb/get-intrinsic/commit/2d07e01310cee2cbaedfead6903df128b1f5d425)
|
||||
- [Deps] update `gopd`, `has-proto`, `has-symbols`, `hasown` [`974d8bf`](https://github.com/ljharb/get-intrinsic/commit/974d8bf5baad7939eef35c25cc1dd88c10a30fa6)
|
||||
- [Dev Deps] update `call-bind`, `es-abstract`, `tape` [`df9dde1`](https://github.com/ljharb/get-intrinsic/commit/df9dde178186631ab8a3165ede056549918ce4bc)
|
||||
- [Refactor] cache `es-define-property` as well [`43ef543`](https://github.com/ljharb/get-intrinsic/commit/43ef543cb02194401420e3a914a4ca9168691926)
|
||||
- [Deps] update `has-proto`, `has-symbols`, `hasown` [`ad4949d`](https://github.com/ljharb/get-intrinsic/commit/ad4949d5467316505aad89bf75f9417ed782f7af)
|
||||
- [Tests] use `call-bound` directly [`ad5c406`](https://github.com/ljharb/get-intrinsic/commit/ad5c4069774bfe90e520a35eead5fe5ca9d69e80)
|
||||
- [Deps] update `has-proto`, `hasown` [`45414ca`](https://github.com/ljharb/get-intrinsic/commit/45414caa312333a2798953682c68f85c550627dd)
|
||||
- [Tests] replace `aud` with `npm audit` [`18d3509`](https://github.com/ljharb/get-intrinsic/commit/18d3509f79460e7924da70409ee81e5053087523)
|
||||
- [Deps] update `es-define-property` [`aadaa3b`](https://github.com/ljharb/get-intrinsic/commit/aadaa3b2188d77ad9bff394ce5d4249c49eb21f5)
|
||||
- [Dev Deps] add missing peer dep [`c296a16`](https://github.com/ljharb/get-intrinsic/commit/c296a16246d0c9a5981944f4cc5cf61fbda0cf6a)
|
||||
|
||||
## [v1.2.4](https://github.com/ljharb/get-intrinsic/compare/v1.2.3...v1.2.4) - 2024-02-05
|
||||
|
||||
### Commits
|
||||
|
||||
61
backend/node_modules/get-intrinsic/index.js
generated
vendored
61
backend/node_modules/get-intrinsic/index.js
generated
vendored
@@ -2,6 +2,8 @@
|
||||
|
||||
var undefined;
|
||||
|
||||
var $Object = require('es-object-atoms');
|
||||
|
||||
var $Error = require('es-errors');
|
||||
var $EvalError = require('es-errors/eval');
|
||||
var $RangeError = require('es-errors/range');
|
||||
@@ -10,6 +12,14 @@ var $SyntaxError = require('es-errors/syntax');
|
||||
var $TypeError = require('es-errors/type');
|
||||
var $URIError = require('es-errors/uri');
|
||||
|
||||
var abs = require('math-intrinsics/abs');
|
||||
var floor = require('math-intrinsics/floor');
|
||||
var max = require('math-intrinsics/max');
|
||||
var min = require('math-intrinsics/min');
|
||||
var pow = require('math-intrinsics/pow');
|
||||
var round = require('math-intrinsics/round');
|
||||
var sign = require('math-intrinsics/sign');
|
||||
|
||||
var $Function = Function;
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
@@ -19,14 +29,8 @@ var getEvalledConstructor = function (expressionSyntax) {
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
var $gOPD = Object.getOwnPropertyDescriptor;
|
||||
if ($gOPD) {
|
||||
try {
|
||||
$gOPD({}, '');
|
||||
} catch (e) {
|
||||
$gOPD = null; // this is IE 8, which has a broken gOPD
|
||||
}
|
||||
}
|
||||
var $gOPD = require('gopd');
|
||||
var $defineProperty = require('es-define-property');
|
||||
|
||||
var throwTypeError = function () {
|
||||
throw new $TypeError();
|
||||
@@ -49,13 +53,13 @@ var ThrowTypeError = $gOPD
|
||||
: throwTypeError;
|
||||
|
||||
var hasSymbols = require('has-symbols')();
|
||||
var hasProto = require('has-proto')();
|
||||
|
||||
var getProto = Object.getPrototypeOf || (
|
||||
hasProto
|
||||
? function (x) { return x.__proto__; } // eslint-disable-line no-proto
|
||||
: null
|
||||
);
|
||||
var getProto = require('get-proto');
|
||||
var $ObjectGPO = require('get-proto/Object.getPrototypeOf');
|
||||
var $ReflectGPO = require('get-proto/Reflect.getPrototypeOf');
|
||||
|
||||
var $apply = require('call-bind-apply-helpers/functionApply');
|
||||
var $call = require('call-bind-apply-helpers/functionCall');
|
||||
|
||||
var needsEval = {};
|
||||
|
||||
@@ -86,6 +90,7 @@ var INTRINSICS = {
|
||||
'%Error%': $Error,
|
||||
'%eval%': eval, // eslint-disable-line no-eval
|
||||
'%EvalError%': $EvalError,
|
||||
'%Float16Array%': typeof Float16Array === 'undefined' ? undefined : Float16Array,
|
||||
'%Float32Array%': typeof Float32Array === 'undefined' ? undefined : Float32Array,
|
||||
'%Float64Array%': typeof Float64Array === 'undefined' ? undefined : Float64Array,
|
||||
'%FinalizationRegistry%': typeof FinalizationRegistry === 'undefined' ? undefined : FinalizationRegistry,
|
||||
@@ -102,7 +107,8 @@ var INTRINSICS = {
|
||||
'%MapIteratorPrototype%': typeof Map === 'undefined' || !hasSymbols || !getProto ? undefined : getProto(new Map()[Symbol.iterator]()),
|
||||
'%Math%': Math,
|
||||
'%Number%': Number,
|
||||
'%Object%': Object,
|
||||
'%Object%': $Object,
|
||||
'%Object.getOwnPropertyDescriptor%': $gOPD,
|
||||
'%parseFloat%': parseFloat,
|
||||
'%parseInt%': parseInt,
|
||||
'%Promise%': typeof Promise === 'undefined' ? undefined : Promise,
|
||||
@@ -128,7 +134,20 @@ var INTRINSICS = {
|
||||
'%URIError%': $URIError,
|
||||
'%WeakMap%': typeof WeakMap === 'undefined' ? undefined : WeakMap,
|
||||
'%WeakRef%': typeof WeakRef === 'undefined' ? undefined : WeakRef,
|
||||
'%WeakSet%': typeof WeakSet === 'undefined' ? undefined : WeakSet
|
||||
'%WeakSet%': typeof WeakSet === 'undefined' ? undefined : WeakSet,
|
||||
|
||||
'%Function.prototype.call%': $call,
|
||||
'%Function.prototype.apply%': $apply,
|
||||
'%Object.defineProperty%': $defineProperty,
|
||||
'%Object.getPrototypeOf%': $ObjectGPO,
|
||||
'%Math.abs%': abs,
|
||||
'%Math.floor%': floor,
|
||||
'%Math.max%': max,
|
||||
'%Math.min%': min,
|
||||
'%Math.pow%': pow,
|
||||
'%Math.round%': round,
|
||||
'%Math.sign%': sign,
|
||||
'%Reflect.getPrototypeOf%': $ReflectGPO
|
||||
};
|
||||
|
||||
if (getProto) {
|
||||
@@ -223,11 +242,11 @@ var LEGACY_ALIASES = {
|
||||
|
||||
var bind = require('function-bind');
|
||||
var hasOwn = require('hasown');
|
||||
var $concat = bind.call(Function.call, Array.prototype.concat);
|
||||
var $spliceApply = bind.call(Function.apply, Array.prototype.splice);
|
||||
var $replace = bind.call(Function.call, String.prototype.replace);
|
||||
var $strSlice = bind.call(Function.call, String.prototype.slice);
|
||||
var $exec = bind.call(Function.call, RegExp.prototype.exec);
|
||||
var $concat = bind.call($call, Array.prototype.concat);
|
||||
var $spliceApply = bind.call($apply, Array.prototype.splice);
|
||||
var $replace = bind.call($call, String.prototype.replace);
|
||||
var $strSlice = bind.call($call, String.prototype.slice);
|
||||
var $exec = bind.call($call, RegExp.prototype.exec);
|
||||
|
||||
/* adapted from https://github.com/lodash/lodash/blob/4.17.15/dist/lodash.js#L6735-L6744 */
|
||||
var rePropName = /[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g;
|
||||
|
||||
44
backend/node_modules/get-intrinsic/package.json
generated
vendored
44
backend/node_modules/get-intrinsic/package.json
generated
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "get-intrinsic",
|
||||
"version": "1.2.4",
|
||||
"version": "1.3.0",
|
||||
"description": "Get and robustly cache all JS language-level intrinsics at first require time",
|
||||
"main": "index.js",
|
||||
"exports": {
|
||||
@@ -17,7 +17,7 @@
|
||||
"pretest": "npm run lint",
|
||||
"tests-only": "nyc tape 'test/**/*.js'",
|
||||
"test": "npm run tests-only",
|
||||
"posttest": "aud --production",
|
||||
"posttest": "npx npm@'>= 10.2' audit --production",
|
||||
"version": "auto-changelog && git add CHANGELOG.md",
|
||||
"postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\""
|
||||
},
|
||||
@@ -43,26 +43,37 @@
|
||||
"url": "https://github.com/ljharb/get-intrinsic/issues"
|
||||
},
|
||||
"homepage": "https://github.com/ljharb/get-intrinsic#readme",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ljharb/eslint-config": "^21.1.0",
|
||||
"aud": "^2.0.4",
|
||||
"auto-changelog": "^2.4.0",
|
||||
"call-bind": "^1.0.5",
|
||||
"es-abstract": "^1.22.3",
|
||||
"es-value-fixtures": "^1.4.2",
|
||||
"@ljharb/eslint-config": "^21.1.1",
|
||||
"auto-changelog": "^2.5.0",
|
||||
"call-bound": "^1.0.3",
|
||||
"encoding": "^0.1.13",
|
||||
"es-abstract": "^1.23.9",
|
||||
"es-value-fixtures": "^1.7.1",
|
||||
"eslint": "=8.8.0",
|
||||
"evalmd": "^0.0.19",
|
||||
"for-each": "^0.3.3",
|
||||
"gopd": "^1.0.1",
|
||||
"for-each": "^0.3.5",
|
||||
"make-async-function": "^1.0.0",
|
||||
"make-async-generator-function": "^1.0.0",
|
||||
"make-generator-function": "^2.0.0",
|
||||
"mock-property": "^1.0.3",
|
||||
"mock-property": "^1.1.0",
|
||||
"npmignore": "^0.3.1",
|
||||
"nyc": "^10.3.2",
|
||||
"object-inspect": "^1.13.1",
|
||||
"object-inspect": "^1.13.4",
|
||||
"safe-publish-latest": "^2.0.0",
|
||||
"tape": "^5.7.4"
|
||||
"tape": "^5.9.0"
|
||||
},
|
||||
"auto-changelog": {
|
||||
"output": "CHANGELOG.md",
|
||||
@@ -72,13 +83,6 @@
|
||||
"backfillLimit": false,
|
||||
"hideCredit": true
|
||||
},
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
},
|
||||
"testling": {
|
||||
"files": "test/GetIntrinsic.js"
|
||||
},
|
||||
|
||||
4
backend/node_modules/get-intrinsic/test/GetIntrinsic.js
generated
vendored
4
backend/node_modules/get-intrinsic/test/GetIntrinsic.js
generated
vendored
@@ -10,10 +10,10 @@ var asyncFns = require('make-async-function').list();
|
||||
var asyncGenFns = require('make-async-generator-function')();
|
||||
var mockProperty = require('mock-property');
|
||||
|
||||
var callBound = require('call-bind/callBound');
|
||||
var callBound = require('call-bound');
|
||||
var v = require('es-value-fixtures');
|
||||
var $gOPD = require('gopd');
|
||||
var DefinePropertyOrThrow = require('es-abstract/2021/DefinePropertyOrThrow');
|
||||
var DefinePropertyOrThrow = require('es-abstract/2023/DefinePropertyOrThrow');
|
||||
|
||||
var $isProto = callBound('%Object.prototype.isPrototypeOf%');
|
||||
|
||||
|
||||
20
backend/node_modules/gopd/CHANGELOG.md
generated
vendored
20
backend/node_modules/gopd/CHANGELOG.md
generated
vendored
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [v1.2.0](https://github.com/ljharb/gopd/compare/v1.1.0...v1.2.0) - 2024-12-03
|
||||
|
||||
### Commits
|
||||
|
||||
- [New] add `gOPD` entry point; remove `get-intrinsic` [`5b61232`](https://github.com/ljharb/gopd/commit/5b61232dedea4591a314bcf16101b1961cee024e)
|
||||
|
||||
## [v1.1.0](https://github.com/ljharb/gopd/compare/v1.0.1...v1.1.0) - 2024-11-29
|
||||
|
||||
### Commits
|
||||
|
||||
- [New] add types [`f585e39`](https://github.com/ljharb/gopd/commit/f585e397886d270e4ba84e53d226e4f9ca2eb0e6)
|
||||
- [Dev Deps] update `@ljharb/eslint-config`, `auto-changelog`, `tape` [`0b8e4fd`](https://github.com/ljharb/gopd/commit/0b8e4fded64397a7726a9daa144a6cc9a5e2edfa)
|
||||
- [Dev Deps] update `aud`, `npmignore`, `tape` [`48378b2`](https://github.com/ljharb/gopd/commit/48378b2443f09a4f7efbd0fb6c3ee845a6cabcf3)
|
||||
- [Dev Deps] update `@ljharb/eslint-config`, `aud`, `tape` [`78099ee`](https://github.com/ljharb/gopd/commit/78099eeed41bfdc134c912280483689cc8861c31)
|
||||
- [Tests] replace `aud` with `npm audit` [`4e0d0ac`](https://github.com/ljharb/gopd/commit/4e0d0ac47619d24a75318a8e1f543ee04b2a2632)
|
||||
- [meta] add missing `engines.node` [`1443316`](https://github.com/ljharb/gopd/commit/14433165d07835c680155b3dfd62d9217d735eca)
|
||||
- [Deps] update `get-intrinsic` [`eee5f51`](https://github.com/ljharb/gopd/commit/eee5f51769f3dbaf578b70e2a3199116b01aa670)
|
||||
- [Deps] update `get-intrinsic` [`550c378`](https://github.com/ljharb/gopd/commit/550c3780e3a9c77b62565712a001b4ed64ea61f5)
|
||||
- [Dev Deps] add missing peer dep [`8c2ecf8`](https://github.com/ljharb/gopd/commit/8c2ecf848122e4e30abfc5b5086fb48b390dce75)
|
||||
|
||||
## [v1.0.1](https://github.com/ljharb/gopd/compare/v1.0.0...v1.0.1) - 2022-11-01
|
||||
|
||||
### Commits
|
||||
|
||||
5
backend/node_modules/gopd/index.js
generated
vendored
5
backend/node_modules/gopd/index.js
generated
vendored
@@ -1,8 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
var GetIntrinsic = require('get-intrinsic');
|
||||
|
||||
var $gOPD = GetIntrinsic('%Object.getOwnPropertyDescriptor%', true);
|
||||
/** @type {import('.')} */
|
||||
var $gOPD = require('./gOPD');
|
||||
|
||||
if ($gOPD) {
|
||||
try {
|
||||
|
||||
26
backend/node_modules/gopd/package.json
generated
vendored
26
backend/node_modules/gopd/package.json
generated
vendored
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"name": "gopd",
|
||||
"version": "1.0.1",
|
||||
"version": "1.2.0",
|
||||
"description": "`Object.getOwnPropertyDescriptor`, but accounts for IE's broken implementation.",
|
||||
"main": "index.js",
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./gOPD": "./gOPD.js",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"sideEffects": false,
|
||||
@@ -12,12 +13,13 @@
|
||||
"prepack": "npmignore --auto --commentLines=autogenerated",
|
||||
"prepublishOnly": "safe-publish-latest",
|
||||
"prepublish": "not-in-publish || npm run prepublishOnly",
|
||||
"prelint": "tsc -p . && attw -P",
|
||||
"lint": "eslint --ext=js,mjs .",
|
||||
"postlint": "evalmd README.md",
|
||||
"pretest": "npm run lint",
|
||||
"tests-only": "tape 'test/**/*.js'",
|
||||
"test": "npm run tests-only",
|
||||
"posttest": "aud --production",
|
||||
"posttest": "npx npm@'>=10.2' audit --production",
|
||||
"version": "auto-changelog && git add CHANGELOG.md",
|
||||
"postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\""
|
||||
},
|
||||
@@ -41,19 +43,20 @@
|
||||
"url": "https://github.com/ljharb/gopd/issues"
|
||||
},
|
||||
"homepage": "https://github.com/ljharb/gopd#readme",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ljharb/eslint-config": "^21.0.0",
|
||||
"aud": "^2.0.1",
|
||||
"auto-changelog": "^2.4.0",
|
||||
"@arethetypeswrong/cli": "^0.17.0",
|
||||
"@ljharb/eslint-config": "^21.1.1",
|
||||
"@ljharb/tsconfig": "^0.2.0",
|
||||
"@types/tape": "^5.6.5",
|
||||
"auto-changelog": "^2.5.0",
|
||||
"encoding": "^0.1.13",
|
||||
"eslint": "=8.8.0",
|
||||
"evalmd": "^0.0.19",
|
||||
"in-publish": "^2.0.1",
|
||||
"npmignore": "^0.3.0",
|
||||
"npmignore": "^0.3.1",
|
||||
"safe-publish-latest": "^2.0.0",
|
||||
"tape": "^5.6.1"
|
||||
"tape": "^5.9.0",
|
||||
"typescript": "next"
|
||||
},
|
||||
"auto-changelog": {
|
||||
"output": "CHANGELOG.md",
|
||||
@@ -67,5 +70,8 @@
|
||||
"ignore": [
|
||||
".github/workflows"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
}
|
||||
|
||||
3
backend/node_modules/gopd/test/index.js
generated
vendored
3
backend/node_modules/gopd/test/index.js
generated
vendored
@@ -10,6 +10,7 @@ test('gOPD', function (t) {
|
||||
var obj = { x: 1 };
|
||||
st.ok('x' in obj, 'property exists');
|
||||
|
||||
// @ts-expect-error TS can't figure out narrowing from `skip`
|
||||
var desc = gOPD(obj, 'x');
|
||||
st.deepEqual(
|
||||
desc,
|
||||
@@ -25,7 +26,7 @@ test('gOPD', function (t) {
|
||||
st.end();
|
||||
});
|
||||
|
||||
t.test('not supported', { skip: gOPD }, function (st) {
|
||||
t.test('not supported', { skip: !!gOPD }, function (st) {
|
||||
st.notOk(gOPD, 'is falsy');
|
||||
|
||||
st.end();
|
||||
|
||||
5
backend/node_modules/has-proto/.eslintrc
generated
vendored
5
backend/node_modules/has-proto/.eslintrc
generated
vendored
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
|
||||
"extends": "@ljharb",
|
||||
}
|
||||
12
backend/node_modules/has-proto/.github/FUNDING.yml
generated
vendored
12
backend/node_modules/has-proto/.github/FUNDING.yml
generated
vendored
@@ -1,12 +0,0 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [ljharb]
|
||||
patreon: # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: npm/has-proto
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
38
backend/node_modules/has-proto/CHANGELOG.md
generated
vendored
38
backend/node_modules/has-proto/CHANGELOG.md
generated
vendored
@@ -1,38 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [v1.0.3](https://github.com/inspect-js/has-proto/compare/v1.0.2...v1.0.3) - 2024-02-19
|
||||
|
||||
### Commits
|
||||
|
||||
- [types] add missing declaration file [`26ecade`](https://github.com/inspect-js/has-proto/commit/26ecade05d253bb5dc376945ee3186d1fbe334f8)
|
||||
|
||||
## [v1.0.2](https://github.com/inspect-js/has-proto/compare/v1.0.1...v1.0.2) - 2024-02-19
|
||||
|
||||
### Commits
|
||||
|
||||
- add types [`6435262`](https://github.com/inspect-js/has-proto/commit/64352626cf511c0276d5f4bb6be770a0bf0f8524)
|
||||
- [Dev Deps] update `@ljharb/eslint-config`, `aud`, `npmignore`, `tape` [`f16a5e4`](https://github.com/inspect-js/has-proto/commit/f16a5e4121651e551271419f9d60fdd3561fd82c)
|
||||
- [Refactor] tiny cleanup [`d1f1a4b`](https://github.com/inspect-js/has-proto/commit/d1f1a4bdc135f115a10f148ce302676224534702)
|
||||
- [meta] add `sideEffects` flag [`e7ab1a6`](https://github.com/inspect-js/has-proto/commit/e7ab1a6f153b3e80dee68d1748b71e46767a0531)
|
||||
|
||||
## [v1.0.1](https://github.com/inspect-js/has-proto/compare/v1.0.0...v1.0.1) - 2022-12-21
|
||||
|
||||
### Commits
|
||||
|
||||
- [meta] correct URLs and description [`ef34483`](https://github.com/inspect-js/has-proto/commit/ef34483ca0d35680f271b6b96e35526151b25dfc)
|
||||
- [patch] add an additional criteria [`e81959e`](https://github.com/inspect-js/has-proto/commit/e81959ed7c7a77fbf459f00cb4ef824f1099497f)
|
||||
- [Dev Deps] update `aud` [`2bec2c4`](https://github.com/inspect-js/has-proto/commit/2bec2c47b072b122ff5443fba0263f6dc649531f)
|
||||
|
||||
## v1.0.0 - 2022-12-12
|
||||
|
||||
### Commits
|
||||
|
||||
- Initial implementation, tests, readme [`6886fea`](https://github.com/inspect-js/has-proto/commit/6886fea578f67daf69a7920b2eb7637ea6ebb0bc)
|
||||
- Initial commit [`99129c8`](https://github.com/inspect-js/has-proto/commit/99129c8f42471ac89cb681ba9cb9d52a583eb94f)
|
||||
- npm init [`2844ad8`](https://github.com/inspect-js/has-proto/commit/2844ad8e75b84d66a46765b3bab9d2e8ea692e10)
|
||||
- Only apps should have lockfiles [`c65bc5e`](https://github.com/inspect-js/has-proto/commit/c65bc5e40b9004463f7336d47c67245fb139a36a)
|
||||
21
backend/node_modules/has-proto/LICENSE
generated
vendored
21
backend/node_modules/has-proto/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Inspect JS
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
38
backend/node_modules/has-proto/README.md
generated
vendored
38
backend/node_modules/has-proto/README.md
generated
vendored
@@ -1,38 +0,0 @@
|
||||
# has-proto <sup>[![Version Badge][npm-version-svg]][package-url]</sup>
|
||||
|
||||
[![github actions][actions-image]][actions-url]
|
||||
[![coverage][codecov-image]][codecov-url]
|
||||
[![License][license-image]][license-url]
|
||||
[![Downloads][downloads-image]][downloads-url]
|
||||
|
||||
[![npm badge][npm-badge-png]][package-url]
|
||||
|
||||
Does this environment have the ability to set the [[Prototype]] of an object on creation with `__proto__`?
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
var hasProto = require('has-proto');
|
||||
var assert = require('assert');
|
||||
|
||||
assert.equal(typeof hasProto(), 'boolean');
|
||||
```
|
||||
|
||||
## Tests
|
||||
Simply clone the repo, `npm install`, and run `npm test`
|
||||
|
||||
[package-url]: https://npmjs.org/package/has-proto
|
||||
[npm-version-svg]: https://versionbadg.es/inspect-js/has-proto.svg
|
||||
[deps-svg]: https://david-dm.org/inspect-js/has-proto.svg
|
||||
[deps-url]: https://david-dm.org/inspect-js/has-proto
|
||||
[dev-deps-svg]: https://david-dm.org/inspect-js/has-proto/dev-status.svg
|
||||
[dev-deps-url]: https://david-dm.org/inspect-js/has-proto#info=devDependencies
|
||||
[npm-badge-png]: https://nodei.co/npm/has-proto.png?downloads=true&stars=true
|
||||
[license-image]: https://img.shields.io/npm/l/has-proto.svg
|
||||
[license-url]: LICENSE
|
||||
[downloads-image]: https://img.shields.io/npm/dm/has-proto.svg
|
||||
[downloads-url]: https://npm-stat.com/charts.html?package=has-proto
|
||||
[codecov-image]: https://codecov.io/gh/inspect-js/has-proto/branch/main/graphs/badge.svg
|
||||
[codecov-url]: https://app.codecov.io/gh/inspect-js/has-proto/
|
||||
[actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/inspect-js/has-proto
|
||||
[actions-url]: https://github.com/inspect-js/has-proto/actions
|
||||
3
backend/node_modules/has-proto/index.d.ts
generated
vendored
3
backend/node_modules/has-proto/index.d.ts
generated
vendored
@@ -1,3 +0,0 @@
|
||||
declare function hasProto(): boolean;
|
||||
|
||||
export = hasProto;
|
||||
15
backend/node_modules/has-proto/index.js
generated
vendored
15
backend/node_modules/has-proto/index.js
generated
vendored
@@ -1,15 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var test = {
|
||||
__proto__: null,
|
||||
foo: {}
|
||||
};
|
||||
|
||||
var $Object = Object;
|
||||
|
||||
/** @type {import('.')} */
|
||||
module.exports = function hasProto() {
|
||||
// @ts-expect-error: TS errors on an inherited property for some reason
|
||||
return { __proto__: test }.foo === test.foo
|
||||
&& !(test instanceof $Object);
|
||||
};
|
||||
78
backend/node_modules/has-proto/package.json
generated
vendored
78
backend/node_modules/has-proto/package.json
generated
vendored
@@ -1,78 +0,0 @@
|
||||
{
|
||||
"name": "has-proto",
|
||||
"version": "1.0.3",
|
||||
"description": "Does this environment have the ability to get the [[Prototype]] of an object on creation with `__proto__`?",
|
||||
"main": "index.js",
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"prepack": "npmignore --auto --commentLines=autogenerated",
|
||||
"prepublishOnly": "safe-publish-latest",
|
||||
"prepublish": "not-in-publish || npm run prepublishOnly",
|
||||
"lint": "eslint --ext=js,mjs .",
|
||||
"postlint": "tsc -p .",
|
||||
"pretest": "npm run lint",
|
||||
"tests-only": "tape 'test/**/*.js'",
|
||||
"test": "npm run tests-only",
|
||||
"posttest": "aud --production",
|
||||
"version": "auto-changelog && git add CHANGELOG.md",
|
||||
"postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\""
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/inspect-js/has-proto.git"
|
||||
},
|
||||
"keywords": [
|
||||
"prototype",
|
||||
"proto",
|
||||
"set",
|
||||
"get",
|
||||
"__proto__",
|
||||
"getPrototypeOf",
|
||||
"setPrototypeOf",
|
||||
"has"
|
||||
],
|
||||
"author": "Jordan Harband <ljharb@gmail.com>",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/inspect-js/has-proto/issues"
|
||||
},
|
||||
"homepage": "https://github.com/inspect-js/has-proto#readme",
|
||||
"testling": {
|
||||
"files": "test/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ljharb/eslint-config": "^21.1.0",
|
||||
"@types/tape": "^5.6.4",
|
||||
"aud": "^2.0.4",
|
||||
"auto-changelog": "^2.4.0",
|
||||
"eslint": "=8.8.0",
|
||||
"in-publish": "^2.0.1",
|
||||
"npmignore": "^0.3.1",
|
||||
"safe-publish-latest": "^2.0.0",
|
||||
"tape": "^5.7.5",
|
||||
"typescript": "next"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"auto-changelog": {
|
||||
"output": "CHANGELOG.md",
|
||||
"template": "keepachangelog",
|
||||
"unreleased": false,
|
||||
"commitLimit": false,
|
||||
"backfillLimit": false,
|
||||
"hideCredit": true
|
||||
},
|
||||
"publishConfig": {
|
||||
"ignore": [
|
||||
".github/workflows"
|
||||
]
|
||||
}
|
||||
}
|
||||
19
backend/node_modules/has-proto/test/index.js
generated
vendored
19
backend/node_modules/has-proto/test/index.js
generated
vendored
@@ -1,19 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var test = require('tape');
|
||||
var hasProto = require('../');
|
||||
|
||||
test('hasProto', function (t) {
|
||||
var result = hasProto();
|
||||
t.equal(typeof result, 'boolean', 'returns a boolean (' + result + ')');
|
||||
|
||||
var obj = { __proto__: null };
|
||||
if (result) {
|
||||
t.notOk('toString' in obj, 'null object lacks toString');
|
||||
} else {
|
||||
t.ok('toString' in obj, 'without proto, null object has toString');
|
||||
t.equal(obj.__proto__, null); // eslint-disable-line no-proto
|
||||
}
|
||||
|
||||
t.end();
|
||||
});
|
||||
49
backend/node_modules/has-proto/tsconfig.json
generated
vendored
49
backend/node_modules/has-proto/tsconfig.json
generated
vendored
@@ -1,49 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
"useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs", /* Specify what module code is generated. */
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
"typeRoots": ["types"], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
"resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
|
||||
/* JavaScript Support */
|
||||
"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
"checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
"maxNodeModuleJsDepth": 0, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
"declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
"noEmit": true, /* Disable emitting files from a compilation. */
|
||||
|
||||
/* Interop Constraints */
|
||||
"allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
|
||||
/* Completeness */
|
||||
//"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
},
|
||||
"exclude": [
|
||||
"coverage"
|
||||
]
|
||||
}
|
||||
16
backend/node_modules/has-symbols/CHANGELOG.md
generated
vendored
16
backend/node_modules/has-symbols/CHANGELOG.md
generated
vendored
@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [v1.1.0](https://github.com/inspect-js/has-symbols/compare/v1.0.3...v1.1.0) - 2024-12-02
|
||||
|
||||
### Commits
|
||||
|
||||
- [actions] update workflows [`548c0bf`](https://github.com/inspect-js/has-symbols/commit/548c0bf8c9b1235458df7a1c0490b0064647a282)
|
||||
- [actions] further shard; update action deps [`bec56bb`](https://github.com/inspect-js/has-symbols/commit/bec56bb0fb44b43a786686b944875a3175cf3ff3)
|
||||
- [meta] use `npmignore` to autogenerate an npmignore file [`ac81032`](https://github.com/inspect-js/has-symbols/commit/ac81032809157e0a079e5264e9ce9b6f1275777e)
|
||||
- [New] add types [`6469cbf`](https://github.com/inspect-js/has-symbols/commit/6469cbff1866cfe367b2b3d181d9296ec14b2a3d)
|
||||
- [actions] update rebase action to use reusable workflow [`9c9d4d0`](https://github.com/inspect-js/has-symbols/commit/9c9d4d0d8938e4b267acdf8e421f4e92d1716d72)
|
||||
- [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `aud`, `tape` [`adb5887`](https://github.com/inspect-js/has-symbols/commit/adb5887ca9444849b08beb5caaa9e1d42320cdfb)
|
||||
- [Dev Deps] update `@ljharb/eslint-config`, `aud`, `tape` [`13ec198`](https://github.com/inspect-js/has-symbols/commit/13ec198ec80f1993a87710af1606a1970b22c7cb)
|
||||
- [Dev Deps] update `auto-changelog`, `core-js`, `tape` [`941be52`](https://github.com/inspect-js/has-symbols/commit/941be5248387cab1da72509b22acf3fdb223f057)
|
||||
- [Tests] replace `aud` with `npm audit` [`74f49e9`](https://github.com/inspect-js/has-symbols/commit/74f49e9a9d17a443020784234a1c53ce765b3559)
|
||||
- [Dev Deps] update `npmignore` [`9c0ac04`](https://github.com/inspect-js/has-symbols/commit/9c0ac0452a834f4c2a4b54044f2d6a89f17e9a70)
|
||||
- [Dev Deps] add missing peer dep [`52337a5`](https://github.com/inspect-js/has-symbols/commit/52337a5621cced61f846f2afdab7707a8132cc12)
|
||||
|
||||
## [v1.0.3](https://github.com/inspect-js/has-symbols/compare/v1.0.2...v1.0.3) - 2022-03-01
|
||||
|
||||
### Commits
|
||||
|
||||
1
backend/node_modules/has-symbols/index.js
generated
vendored
1
backend/node_modules/has-symbols/index.js
generated
vendored
@@ -3,6 +3,7 @@
|
||||
var origSymbol = typeof Symbol !== 'undefined' && Symbol;
|
||||
var hasSymbolSham = require('./shams');
|
||||
|
||||
/** @type {import('.')} */
|
||||
module.exports = function hasNativeSymbols() {
|
||||
if (typeof origSymbol !== 'function') { return false; }
|
||||
if (typeof Symbol !== 'function') { return false; }
|
||||
|
||||
28
backend/node_modules/has-symbols/package.json
generated
vendored
28
backend/node_modules/has-symbols/package.json
generated
vendored
@@ -1,21 +1,23 @@
|
||||
{
|
||||
"name": "has-symbols",
|
||||
"version": "1.0.3",
|
||||
"version": "1.1.0",
|
||||
"description": "Determine if the JS environment has Symbol support. Supports spec, or shams.",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"prepack": "npmignore --auto --commentLines=autogenerated",
|
||||
"prepublishOnly": "safe-publish-latest",
|
||||
"prepublish": "not-in-publish || npm run prepublishOnly",
|
||||
"pretest": "npm run --silent lint",
|
||||
"test": "npm run tests-only",
|
||||
"posttest": "aud --production",
|
||||
"tests-only": "npm run test:stock && npm run test:staging && npm run test:shams",
|
||||
"posttest": "npx npm@'>=10.2' audit --production",
|
||||
"tests-only": "npm run test:stock && npm run test:shams",
|
||||
"test:stock": "nyc node test",
|
||||
"test:staging": "nyc node --harmony --es-staging test",
|
||||
"test:shams": "npm run --silent test:shams:getownpropertysymbols && npm run --silent test:shams:corejs",
|
||||
"test:shams:corejs": "nyc node test/shams/core-js.js",
|
||||
"test:shams:getownpropertysymbols": "nyc node test/shams/get-own-property-symbols.js",
|
||||
"lint": "eslint --ext=js,mjs .",
|
||||
"postlint": "tsc -p . && attw -P",
|
||||
"version": "auto-changelog && git add CHANGELOG.md",
|
||||
"postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\""
|
||||
},
|
||||
@@ -54,15 +56,22 @@
|
||||
},
|
||||
"homepage": "https://github.com/ljharb/has-symbols#readme",
|
||||
"devDependencies": {
|
||||
"@ljharb/eslint-config": "^20.2.3",
|
||||
"aud": "^2.0.0",
|
||||
"auto-changelog": "^2.4.0",
|
||||
"@arethetypeswrong/cli": "^0.17.0",
|
||||
"@ljharb/eslint-config": "^21.1.1",
|
||||
"@ljharb/tsconfig": "^0.2.0",
|
||||
"@types/core-js": "^2.5.8",
|
||||
"@types/tape": "^5.6.5",
|
||||
"auto-changelog": "^2.5.0",
|
||||
"core-js": "^2.6.12",
|
||||
"encoding": "^0.1.13",
|
||||
"eslint": "=8.8.0",
|
||||
"get-own-property-symbols": "^0.9.5",
|
||||
"in-publish": "^2.0.1",
|
||||
"npmignore": "^0.3.1",
|
||||
"nyc": "^10.3.2",
|
||||
"safe-publish-latest": "^2.0.0",
|
||||
"tape": "^5.5.2"
|
||||
"tape": "^5.9.0",
|
||||
"typescript": "next"
|
||||
},
|
||||
"testling": {
|
||||
"files": "test/index.js",
|
||||
@@ -93,9 +102,10 @@
|
||||
"backfillLimit": false,
|
||||
"hideCredit": true
|
||||
},
|
||||
"greenkeeper": {
|
||||
"publishConfig": {
|
||||
"ignore": [
|
||||
"core-js"
|
||||
".github/workflows",
|
||||
"types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
7
backend/node_modules/has-symbols/shams.js
generated
vendored
7
backend/node_modules/has-symbols/shams.js
generated
vendored
@@ -1,10 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
/** @type {import('./shams')} */
|
||||
/* eslint complexity: [2, 18], max-statements: [2, 33] */
|
||||
module.exports = function hasSymbols() {
|
||||
if (typeof Symbol !== 'function' || typeof Object.getOwnPropertySymbols !== 'function') { return false; }
|
||||
if (typeof Symbol.iterator === 'symbol') { return true; }
|
||||
|
||||
/** @type {{ [k in symbol]?: unknown }} */
|
||||
var obj = {};
|
||||
var sym = Symbol('test');
|
||||
var symObj = Object(sym);
|
||||
@@ -23,7 +25,7 @@ module.exports = function hasSymbols() {
|
||||
|
||||
var symVal = 42;
|
||||
obj[sym] = symVal;
|
||||
for (sym in obj) { return false; } // eslint-disable-line no-restricted-syntax, no-unreachable-loop
|
||||
for (var _ in obj) { return false; } // eslint-disable-line no-restricted-syntax, no-unreachable-loop
|
||||
if (typeof Object.keys === 'function' && Object.keys(obj).length !== 0) { return false; }
|
||||
|
||||
if (typeof Object.getOwnPropertyNames === 'function' && Object.getOwnPropertyNames(obj).length !== 0) { return false; }
|
||||
@@ -34,7 +36,8 @@ module.exports = function hasSymbols() {
|
||||
if (!Object.prototype.propertyIsEnumerable.call(obj, sym)) { return false; }
|
||||
|
||||
if (typeof Object.getOwnPropertyDescriptor === 'function') {
|
||||
var descriptor = Object.getOwnPropertyDescriptor(obj, sym);
|
||||
// eslint-disable-next-line no-extra-parens
|
||||
var descriptor = /** @type {PropertyDescriptor} */ (Object.getOwnPropertyDescriptor(obj, sym));
|
||||
if (descriptor.value !== symVal || descriptor.enumerable !== true) { return false; }
|
||||
}
|
||||
|
||||
|
||||
1
backend/node_modules/has-symbols/test/shams/core-js.js
generated
vendored
1
backend/node_modules/has-symbols/test/shams/core-js.js
generated
vendored
@@ -8,6 +8,7 @@ if (typeof Symbol === 'function' && typeof Symbol() === 'symbol') {
|
||||
t.equal(typeof Symbol(), 'symbol');
|
||||
t.end();
|
||||
});
|
||||
// @ts-expect-error TS is stupid and doesn't know about top level return
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
1
backend/node_modules/has-symbols/test/shams/get-own-property-symbols.js
generated
vendored
1
backend/node_modules/has-symbols/test/shams/get-own-property-symbols.js
generated
vendored
@@ -8,6 +8,7 @@ if (typeof Symbol === 'function' && typeof Symbol() === 'symbol') {
|
||||
t.equal(typeof Symbol(), 'symbol');
|
||||
t.end();
|
||||
});
|
||||
// @ts-expect-error TS is stupid and doesn't know about top level return
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
6
backend/node_modules/has-symbols/test/tests.js
generated
vendored
6
backend/node_modules/has-symbols/test/tests.js
generated
vendored
@@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
/** @type {(t: import('tape').Test) => false | void} */
|
||||
// eslint-disable-next-line consistent-return
|
||||
module.exports = function runSymbolTests(t) {
|
||||
t.equal(typeof Symbol, 'function', 'global Symbol is a function');
|
||||
@@ -31,6 +32,7 @@ module.exports = function runSymbolTests(t) {
|
||||
|
||||
t.equal(typeof Object.getOwnPropertySymbols, 'function', 'Object.getOwnPropertySymbols is a function');
|
||||
|
||||
/** @type {{ [k in symbol]?: unknown }} */
|
||||
var obj = {};
|
||||
var sym = Symbol('test');
|
||||
var symObj = Object(sym);
|
||||
@@ -40,8 +42,8 @@ module.exports = function runSymbolTests(t) {
|
||||
|
||||
var symVal = 42;
|
||||
obj[sym] = symVal;
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (sym in obj) { t.fail('symbol property key was found in for..in of object'); }
|
||||
// eslint-disable-next-line no-restricted-syntax, no-unused-vars
|
||||
for (var _ in obj) { t.fail('symbol property key was found in for..in of object'); }
|
||||
|
||||
t.deepEqual(Object.keys(obj), [], 'no enumerable own keys on symbol-valued object');
|
||||
t.deepEqual(Object.getOwnPropertyNames(obj), [], 'no own names on symbol-valued object');
|
||||
|
||||
238
backend/package-lock.json
generated
238
backend/package-lock.json
generated
@@ -10,10 +10,12 @@
|
||||
"hasInstallScript": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^1.12.2",
|
||||
"bcrypt": "^5.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"crypto": "^1.0.1",
|
||||
"csv-parser": "^3.0.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"iconv-lite": "^0.6.3",
|
||||
@@ -30,6 +32,15 @@
|
||||
"vue-eslint-parser": "9.4.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.28.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.2.0.tgz",
|
||||
@@ -820,6 +831,12 @@
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/aws-ssl-profiles": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.1.tgz",
|
||||
@@ -828,6 +845,17 @@
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz",
|
||||
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -965,6 +993,19 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -1094,6 +1135,18 @@
|
||||
"color-support": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -1237,6 +1290,22 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "2.30.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
|
||||
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.21.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.11"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/date-fns"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
@@ -1269,6 +1338,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delegates": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
@@ -1325,6 +1403,20 @@
|
||||
"resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.6.tgz",
|
||||
"integrity": "sha512-iGCHkfUc5kFekGiqhe8B/mdaurD+lakO9txNnTvKtA6PISrw86LgqHvRzWYPyoE2Ph5aMIrCw9/uko6XHTKCwA=="
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ecdsa-sig-formatter": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||
@@ -1352,13 +1444,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
"integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
@@ -1372,6 +1461,33 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-set-tostringtag": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"get-intrinsic": "^1.2.6",
|
||||
"has-tostringtag": "^1.0.2",
|
||||
"hasown": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
@@ -1762,6 +1878,42 @@
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -1858,16 +2010,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
|
||||
"integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"has-proto": "^1.0.1",
|
||||
"has-symbols": "^1.0.3",
|
||||
"hasown": "^2.0.0"
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -1876,6 +2033,19 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
@@ -1922,12 +2092,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
|
||||
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-intrinsic": "^1.1.3"
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
@@ -1954,10 +2124,10 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-proto": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
|
||||
"integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
@@ -1966,11 +2136,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
|
||||
"node_modules/has-tostringtag": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-symbols": "^1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
@@ -2414,6 +2587,15 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
@@ -2981,6 +3163,12 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pstree.remy": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"axios": "^1.12.2",
|
||||
"bcrypt": "^5.1.1",
|
||||
"cors": "^2.8.5",
|
||||
"crypto": "^1.0.1",
|
||||
|
||||
32
backend/routes/clubTeamRoutes.js
Normal file
32
backend/routes/clubTeamRoutes.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import express from 'express';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import {
|
||||
getClubTeams,
|
||||
getClubTeam,
|
||||
createClubTeam,
|
||||
updateClubTeam,
|
||||
deleteClubTeam,
|
||||
getLeagues
|
||||
} from '../controllers/clubTeamController.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all club teams for a club
|
||||
router.get('/club/:clubid', authenticate, getClubTeams);
|
||||
|
||||
// Create a new club team
|
||||
router.post('/club/:clubid', authenticate, createClubTeam);
|
||||
|
||||
// Get leagues for a club
|
||||
router.get('/leagues/:clubid', authenticate, getLeagues);
|
||||
|
||||
// Get a specific club team
|
||||
router.get('/:clubteamid', authenticate, getClubTeam);
|
||||
|
||||
// Update a club team
|
||||
router.put('/:clubteamid', authenticate, updateClubTeam);
|
||||
|
||||
// Delete a club team
|
||||
router.delete('/:clubteamid', authenticate, deleteClubTeam);
|
||||
|
||||
export default router;
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getClubMembers, getWaitingApprovals, setClubMembers, uploadMemberImage, getMemberImage } from '../controllers/memberController.js';
|
||||
import { getClubMembers, getWaitingApprovals, setClubMembers, uploadMemberImage, getMemberImage, updateRatingsFromMyTischtennis } from '../controllers/memberController.js';
|
||||
import express from 'express';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import multer from 'multer';
|
||||
@@ -13,5 +13,6 @@ router.get('/image/:clubId/:memberId', authenticate, getMemberImage);
|
||||
router.get('/get/:id/:showAll', authenticate, getClubMembers);
|
||||
router.post('/set/:id', authenticate, setClubMembers);
|
||||
router.get('/notapproved/:id', authenticate, getWaitingApprovals);
|
||||
router.post('/update-ratings/:id', authenticate, updateRatingsFromMyTischtennis);
|
||||
|
||||
export default router;
|
||||
|
||||
29
backend/routes/myTischtennisRoutes.js
Normal file
29
backend/routes/myTischtennisRoutes.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import express from 'express';
|
||||
import myTischtennisController from '../controllers/myTischtennisController.js';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// All routes require authentication
|
||||
router.use(authenticate);
|
||||
|
||||
// GET /api/mytischtennis/account - Get account
|
||||
router.get('/account', myTischtennisController.getAccount);
|
||||
|
||||
// GET /api/mytischtennis/status - Check status
|
||||
router.get('/status', myTischtennisController.getStatus);
|
||||
|
||||
// POST /api/mytischtennis/account - Create or update account
|
||||
router.post('/account', myTischtennisController.upsertAccount);
|
||||
|
||||
// DELETE /api/mytischtennis/account - Delete account
|
||||
router.delete('/account', myTischtennisController.deleteAccount);
|
||||
|
||||
// POST /api/mytischtennis/verify - Verify login
|
||||
router.post('/verify', myTischtennisController.verifyLogin);
|
||||
|
||||
// GET /api/mytischtennis/session - Get stored session
|
||||
router.get('/session', myTischtennisController.getSession);
|
||||
|
||||
export default router;
|
||||
|
||||
28
backend/routes/seasonRoutes.js
Normal file
28
backend/routes/seasonRoutes.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import express from 'express';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import {
|
||||
getSeasons,
|
||||
getCurrentSeason,
|
||||
createSeason,
|
||||
getSeason,
|
||||
deleteSeason
|
||||
} from '../controllers/seasonController.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all seasons
|
||||
router.get('/', authenticate, getSeasons);
|
||||
|
||||
// Get current season (creates if not exists)
|
||||
router.get('/current', authenticate, getCurrentSeason);
|
||||
|
||||
// Get a specific season
|
||||
router.get('/:seasonid', authenticate, getSeason);
|
||||
|
||||
// Create a new season
|
||||
router.post('/', authenticate, createSeason);
|
||||
|
||||
// Delete a season
|
||||
router.delete('/:seasonid', authenticate, deleteSeason);
|
||||
|
||||
export default router;
|
||||
33
backend/routes/teamDocumentRoutes.js
Normal file
33
backend/routes/teamDocumentRoutes.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import express from 'express';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import {
|
||||
uploadMiddleware,
|
||||
uploadDocument,
|
||||
getDocuments,
|
||||
getDocument,
|
||||
downloadDocument,
|
||||
deleteDocument,
|
||||
parsePDF
|
||||
} from '../controllers/teamDocumentController.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Upload eines Dokuments für ein Club-Team
|
||||
router.post('/club-team/:clubteamid/upload', authenticate, uploadMiddleware, uploadDocument);
|
||||
|
||||
// Alle Dokumente für ein Club-Team abrufen
|
||||
router.get('/club-team/:clubteamid', authenticate, getDocuments);
|
||||
|
||||
// Ein spezifisches Dokument abrufen
|
||||
router.get('/:documentid', authenticate, getDocument);
|
||||
|
||||
// Ein Dokument herunterladen
|
||||
router.get('/:documentid/download', authenticate, downloadDocument);
|
||||
|
||||
// Ein Dokument löschen
|
||||
router.delete('/:documentid', authenticate, deleteDocument);
|
||||
|
||||
// PDF parsen und Matches extrahieren
|
||||
router.post('/:documentid/parse', authenticate, parsePDF);
|
||||
|
||||
export default router;
|
||||
32
backend/routes/teamRoutes.js
Normal file
32
backend/routes/teamRoutes.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import express from 'express';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import {
|
||||
getTeams,
|
||||
getTeam,
|
||||
createTeam,
|
||||
updateTeam,
|
||||
deleteTeam,
|
||||
getLeagues
|
||||
} from '../controllers/teamController.js';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all teams for a club
|
||||
router.get('/club/:clubid', authenticate, getTeams);
|
||||
|
||||
// Get leagues for a club
|
||||
router.get('/leagues/:clubid', authenticate, getLeagues);
|
||||
|
||||
// Get a specific team
|
||||
router.get('/:teamid', authenticate, getTeam);
|
||||
|
||||
// Create a new team
|
||||
router.post('/club/:clubid', authenticate, createTeam);
|
||||
|
||||
// Update a team
|
||||
router.put('/:teamid', authenticate, updateTeam);
|
||||
|
||||
// Delete a team
|
||||
router.delete('/:teamid', authenticate, deleteTeam);
|
||||
|
||||
export default router;
|
||||
@@ -6,9 +6,9 @@ import cors from 'cors';
|
||||
import {
|
||||
User, Log, Club, UserClub, Member, DiaryDate, Participant, Activity, MemberNote,
|
||||
DiaryNote, DiaryTag, MemberDiaryTag, DiaryDateTag, DiaryMemberNote, DiaryMemberTag,
|
||||
PredefinedActivity, PredefinedActivityImage, DiaryDateActivity, DiaryMemberActivity, Match, League, Team, Group,
|
||||
PredefinedActivity, PredefinedActivityImage, DiaryDateActivity, DiaryMemberActivity, Match, League, Team, ClubTeam, TeamDocument, Group,
|
||||
GroupActivity, Tournament, TournamentGroup, TournamentMatch, TournamentResult,
|
||||
TournamentMember, Accident, UserToken, OfficialTournament, OfficialCompetition, OfficialCompetitionMember
|
||||
TournamentMember, Accident, UserToken, OfficialTournament, OfficialCompetition, OfficialCompetitionMember, MyTischtennis
|
||||
} from './models/index.js';
|
||||
import authRoutes from './routes/authRoutes.js';
|
||||
import clubRoutes from './routes/clubRoutes.js';
|
||||
@@ -33,6 +33,11 @@ import tournamentRoutes from './routes/tournamentRoutes.js';
|
||||
import accidentRoutes from './routes/accidentRoutes.js';
|
||||
import trainingStatsRoutes from './routes/trainingStatsRoutes.js';
|
||||
import officialTournamentRoutes from './routes/officialTournamentRoutes.js';
|
||||
import myTischtennisRoutes from './routes/myTischtennisRoutes.js';
|
||||
import teamRoutes from './routes/teamRoutes.js';
|
||||
import clubTeamRoutes from './routes/clubTeamRoutes.js';
|
||||
import teamDocumentRoutes from './routes/teamDocumentRoutes.js';
|
||||
import seasonRoutes from './routes/seasonRoutes.js';
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
@@ -40,7 +45,12 @@ const port = process.env.PORT || 3000;
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
app.use(cors());
|
||||
app.use(cors({
|
||||
origin: true,
|
||||
credentials: true,
|
||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||
allowedHeaders: ['Content-Type', 'Authorization', 'authcode', 'userid']
|
||||
}));
|
||||
app.use(express.json());
|
||||
|
||||
// Globale Fehlerbehandlung, damit der Server bei unerwarteten Fehlern nicht hart abstürzt
|
||||
@@ -72,6 +82,11 @@ app.use('/api/tournament', tournamentRoutes);
|
||||
app.use('/api/accident', accidentRoutes);
|
||||
app.use('/api/training-stats', trainingStatsRoutes);
|
||||
app.use('/api/official-tournaments', officialTournamentRoutes);
|
||||
app.use('/api/mytischtennis', myTischtennisRoutes);
|
||||
app.use('/api/teams', teamRoutes);
|
||||
app.use('/api/club-teams', clubTeamRoutes);
|
||||
app.use('/api/team-documents', teamDocumentRoutes);
|
||||
app.use('/api/seasons', seasonRoutes);
|
||||
|
||||
app.use(express.static(path.join(__dirname, '../frontend/dist')));
|
||||
|
||||
@@ -171,6 +186,7 @@ app.get('*', (req, res) => {
|
||||
await safeSync(TournamentResult);
|
||||
await safeSync(Accident);
|
||||
await safeSync(UserToken);
|
||||
await safeSync(MyTischtennis);
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Server is running on http://localhost:${port}`);
|
||||
|
||||
@@ -3,6 +3,7 @@ import DiaryDate from '../models/DiaryDates.js';
|
||||
import Member from '../models/Member.js';
|
||||
import { checkAccess, getUserByToken} from '../utils/userUtils.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
class AccidentService {
|
||||
async createAccident(userToken, clubId, memberId, diaryDateId, accident) {
|
||||
await checkAccess(userToken, clubId);
|
||||
@@ -14,7 +15,7 @@ class AccidentService {
|
||||
if (!member || member.clubId != clubId) {
|
||||
throw new Error('Member not found');
|
||||
}
|
||||
console.log(diaryDateId);
|
||||
devLog(diaryDateId);
|
||||
const diaryDate = await DiaryDate.findByPk(diaryDateId);
|
||||
if (!diaryDate || diaryDate.clubId != clubId) {
|
||||
throw new Error('Diary date not found');
|
||||
|
||||
@@ -4,6 +4,7 @@ import User from '../models/User.js';
|
||||
import UserToken from '../models/UserToken.js';
|
||||
import { sendActivationEmail } from './emailService.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
const register = async (email, password) => {
|
||||
try {
|
||||
const activationCode = Math.random().toString(36).substring(2, 15);
|
||||
@@ -11,7 +12,7 @@ const register = async (email, password) => {
|
||||
await sendActivationEmail(email, activationCode);
|
||||
return user;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
devLog(error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
180
backend/services/clubTeamService.js
Normal file
180
backend/services/clubTeamService.js
Normal file
@@ -0,0 +1,180 @@
|
||||
import ClubTeam from '../models/ClubTeam.js';
|
||||
import League from '../models/League.js';
|
||||
import Season from '../models/Season.js';
|
||||
import SeasonService from './seasonService.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
class ClubTeamService {
|
||||
/**
|
||||
* Holt alle ClubTeams für einen Verein, optional gefiltert nach Saison.
|
||||
* Wenn keine Saison-ID angegeben ist, wird die aktuelle Saison verwendet.
|
||||
* @param {number} clubId - Die ID des Vereins.
|
||||
* @param {number|null} seasonId - Optionale Saison-ID.
|
||||
* @returns {Promise<Array<ClubTeam>>} Eine Liste von ClubTeams.
|
||||
*/
|
||||
static async getAllClubTeamsByClub(clubId, seasonId = null) {
|
||||
try {
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
if (!seasonId) {
|
||||
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
|
||||
seasonId = currentSeason.id;
|
||||
}
|
||||
|
||||
const clubTeams = await ClubTeam.findAll({
|
||||
where: { clubId, seasonId },
|
||||
order: [['name', 'ASC']]
|
||||
});
|
||||
|
||||
// Manuelle Datenanreicherung für Liga und Saison
|
||||
const enrichedClubTeams = [];
|
||||
for (const clubTeam of clubTeams) {
|
||||
const enrichedTeam = {
|
||||
id: clubTeam.id,
|
||||
name: clubTeam.name,
|
||||
clubId: clubTeam.clubId,
|
||||
leagueId: clubTeam.leagueId,
|
||||
seasonId: clubTeam.seasonId,
|
||||
createdAt: clubTeam.createdAt,
|
||||
updatedAt: clubTeam.updatedAt,
|
||||
league: { name: 'Unbekannt' },
|
||||
season: { season: 'Unbekannt' }
|
||||
};
|
||||
|
||||
// Lade Liga-Daten
|
||||
if (clubTeam.leagueId) {
|
||||
const league = await League.findByPk(clubTeam.leagueId, { attributes: ['name'] });
|
||||
if (league) enrichedTeam.league = league;
|
||||
}
|
||||
|
||||
// Lade Saison-Daten
|
||||
if (clubTeam.seasonId) {
|
||||
const season = await Season.findByPk(clubTeam.seasonId, { attributes: ['season'] });
|
||||
if (season) enrichedTeam.season = season;
|
||||
}
|
||||
|
||||
enrichedClubTeams.push(enrichedTeam);
|
||||
}
|
||||
return enrichedClubTeams;
|
||||
} catch (error) {
|
||||
console.error('[ClubTeamService.getAllClubTeamsByClub] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt ein ClubTeam anhand seiner ID
|
||||
* @param {number} clubTeamId - Die ID des ClubTeams
|
||||
* @returns {Promise<ClubTeam|null>} Das ClubTeam oder null, wenn nicht gefunden
|
||||
*/
|
||||
static async getClubTeamById(clubTeamId) {
|
||||
try {
|
||||
const clubTeam = await ClubTeam.findByPk(clubTeamId, {
|
||||
include: [
|
||||
{
|
||||
model: League,
|
||||
as: 'league',
|
||||
attributes: ['id', 'name']
|
||||
},
|
||||
{
|
||||
model: Season,
|
||||
as: 'season',
|
||||
attributes: ['id', 'season']
|
||||
}
|
||||
]
|
||||
});
|
||||
return clubTeam;
|
||||
} catch (error) {
|
||||
console.error('[ClubTeamService.getClubTeamById] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt ein neues ClubTeam.
|
||||
* Wenn keine Saison-ID angegeben ist, wird die aktuelle Saison zugewiesen.
|
||||
* @param {object} clubTeamData - Die Daten des neuen ClubTeams (name, clubId, optional leagueId, seasonId).
|
||||
* @returns {Promise<ClubTeam>} Das erstellte ClubTeam.
|
||||
*/
|
||||
static async createClubTeam(clubTeamData) {
|
||||
try {
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
if (!clubTeamData.seasonId) {
|
||||
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
|
||||
clubTeamData.seasonId = currentSeason.id;
|
||||
}
|
||||
|
||||
const clubTeam = await ClubTeam.create(clubTeamData);
|
||||
return clubTeam;
|
||||
} catch (error) {
|
||||
console.error('[ClubTeamService.createClubTeam] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert ein bestehendes ClubTeam.
|
||||
* @param {number} clubTeamId - Die ID des zu aktualisierenden ClubTeams.
|
||||
* @param {object} updateData - Die zu aktualisierenden Daten.
|
||||
* @returns {Promise<boolean>} True, wenn das ClubTeam aktualisiert wurde, sonst false.
|
||||
*/
|
||||
static async updateClubTeam(clubTeamId, updateData) {
|
||||
try {
|
||||
const [updatedRowsCount] = await ClubTeam.update(updateData, {
|
||||
where: { id: clubTeamId }
|
||||
});
|
||||
return updatedRowsCount > 0;
|
||||
} catch (error) {
|
||||
console.error('[ClubTeamService.updateClubTeam] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht ein ClubTeam.
|
||||
* @param {number} clubTeamId - Die ID des zu löschenden ClubTeams.
|
||||
* @returns {Promise<boolean>} True, wenn das ClubTeam gelöscht wurde, sonst false.
|
||||
*/
|
||||
static async deleteClubTeam(clubTeamId) {
|
||||
try {
|
||||
const deletedRows = await ClubTeam.destroy({
|
||||
where: { id: clubTeamId }
|
||||
});
|
||||
return deletedRows > 0;
|
||||
} catch (error) {
|
||||
console.error('[ClubTeamService.deleteClubTeam] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt alle Ligen für einen Verein, optional gefiltert nach Saison.
|
||||
* Wenn keine Saison-ID angegeben ist, wird die aktuelle Saison verwendet.
|
||||
* @param {number} clubId - Die ID des Vereins.
|
||||
* @param {number|null} seasonId - Optionale Saison-ID.
|
||||
* @returns {Promise<Array<League>>} Eine Liste von Ligen.
|
||||
*/
|
||||
static async getLeaguesByClub(clubId, seasonId = null) {
|
||||
try {
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
if (!seasonId) {
|
||||
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
|
||||
seasonId = currentSeason.id;
|
||||
}
|
||||
|
||||
const leagues = await League.findAll({
|
||||
where: { clubId, seasonId },
|
||||
attributes: ['id', 'name', 'seasonId'],
|
||||
order: [['name', 'ASC']]
|
||||
});
|
||||
return leagues;
|
||||
} catch (error) {
|
||||
console.error('[ClubTeamService.getLeaguesByClub] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ClubTeamService;
|
||||
@@ -6,12 +6,11 @@ import PredefinedActivityImage from '../models/PredefinedActivityImage.js';
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
class DiaryDateActivityService {
|
||||
|
||||
async createActivity(userToken, clubId, data) {
|
||||
console.log('[DiaryDateActivityService::createActivity] - check user access');
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log('[DiaryDateActivityService::createActivity] - add: ', data);
|
||||
const { activity, ...restData } = data;
|
||||
// Versuche, die PredefinedActivity robust zu finden:
|
||||
// 1) per übergebener ID
|
||||
@@ -59,23 +58,18 @@ class DiaryDateActivityService {
|
||||
});
|
||||
const newOrderId = maxOrderId !== null ? maxOrderId + 1 : 1;
|
||||
restData.orderId = newOrderId;
|
||||
console.log('[DiaryDateActivityService::createActivity] - create diary date activity');
|
||||
return await DiaryDateActivity.create(restData);
|
||||
}
|
||||
|
||||
async updateActivity(userToken, clubId, id, data) {
|
||||
console.log('[DiaryDateActivityService::updateActivity] - check user access');
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log('[DiaryDateActivityService::updateActivity] - load activity', id);
|
||||
const activity = await DiaryDateActivity.findByPk(id);
|
||||
if (!activity) {
|
||||
console.log('[DiaryDateActivityService::updateActivity] - activity not found');
|
||||
throw new Error('Activity not found');
|
||||
}
|
||||
|
||||
// Wenn customActivityName gesendet wird, müssen wir die PredefinedActivity behandeln
|
||||
if (data.customActivityName) {
|
||||
console.log('[DiaryDateActivityService::updateActivity] - handling customActivityName:', data.customActivityName);
|
||||
|
||||
// Suche nach einer existierenden PredefinedActivity mit diesem Namen
|
||||
let predefinedActivity = await PredefinedActivity.findOne({
|
||||
@@ -84,7 +78,6 @@ class DiaryDateActivityService {
|
||||
|
||||
if (!predefinedActivity) {
|
||||
// Erstelle eine neue PredefinedActivity
|
||||
console.log('[DiaryDateActivityService::updateActivity] - creating new PredefinedActivity');
|
||||
predefinedActivity = await PredefinedActivity.create({
|
||||
name: data.customActivityName,
|
||||
description: data.description || '',
|
||||
@@ -99,7 +92,6 @@ class DiaryDateActivityService {
|
||||
delete data.customActivityName;
|
||||
}
|
||||
|
||||
console.log('[DiaryDateActivityService::updateActivity] - update activity', clubId, id, data, JSON.stringify(data));
|
||||
return await activity.update(data);
|
||||
}
|
||||
|
||||
@@ -113,22 +105,14 @@ class DiaryDateActivityService {
|
||||
}
|
||||
|
||||
async updateActivityOrder(userToken, clubId, id, newOrderId) {
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Start update for activity id: ${id}`);
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - User token: ${userToken}, Club id: ${clubId}, New order id: ${newOrderId}`);
|
||||
console.log('[DiaryDateActivityService::updateActivityOrder] - Checking user access');
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log('[DiaryDateActivityService::updateActivityOrder] - User access confirmed');
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Finding activity with id: ${id}`);
|
||||
const activity = await DiaryDateActivity.findByPk(id);
|
||||
if (!activity) {
|
||||
console.error('[DiaryDateActivityService::updateActivityOrder] - Activity not found, throwing error');
|
||||
throw new Error('Activity not found');
|
||||
}
|
||||
console.log('[DiaryDateActivityService::updateActivityOrder] - Activity found:', activity);
|
||||
const currentOrderId = activity.orderId;
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Current order id: ${currentOrderId}`);
|
||||
if (newOrderId < currentOrderId) {
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Shifting items down. Moving activities with orderId between ${newOrderId} and ${currentOrderId - 1}`);
|
||||
await DiaryDateActivity.increment(
|
||||
{ orderId: 1 },
|
||||
{
|
||||
@@ -138,9 +122,7 @@ class DiaryDateActivityService {
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Items shifted down`);
|
||||
} else if (newOrderId > currentOrderId) {
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Shifting items up. Moving activities with orderId between ${currentOrderId + 1} and ${newOrderId}`);
|
||||
await DiaryDateActivity.decrement(
|
||||
{ orderId: 1 },
|
||||
{
|
||||
@@ -150,16 +132,10 @@ class DiaryDateActivityService {
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Items shifted up`);
|
||||
} else {
|
||||
console.log('[DiaryDateActivityService::updateActivityOrder] - New order id is the same as the current order id. No shift required.');
|
||||
}
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Setting new order id for activity id: ${id}`);
|
||||
activity.orderId = newOrderId;
|
||||
console.log('[DiaryDateActivityService::updateActivityOrder] - Saving activity with new order id');
|
||||
const savedActivity = await activity.save();
|
||||
console.log('[DiaryDateActivityService::updateActivityOrder] - Activity saved:', savedActivity);
|
||||
console.log(`[DiaryDateActivityService::updateActivityOrder] - Finished update for activity id: ${id}`);
|
||||
return savedActivity;
|
||||
}
|
||||
|
||||
@@ -256,9 +232,7 @@ class DiaryDateActivityService {
|
||||
}
|
||||
|
||||
async addGroupActivity(userToken, clubId, diaryDateId, groupId, activity) {
|
||||
console.log('[DiaryDateActivityService::addGroupActivity] Check user access');
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log('[DiaryDateActivityService::addGroupActivity] Check diary date');
|
||||
const diaryDateActivity = await DiaryDateActivity.findOne({
|
||||
where: {
|
||||
diaryDateId,
|
||||
@@ -271,26 +245,23 @@ class DiaryDateActivityService {
|
||||
console.error('[DiaryDateActivityService::addGroupActivity] Activity not found');
|
||||
throw new Error('Activity not found');
|
||||
}
|
||||
console.log('[DiaryDateActivityService::addGroupActivity] Check group');
|
||||
const group = await Group.findByPk(groupId);
|
||||
if (!group || group.diaryDateId !== diaryDateActivity.diaryDateId) {
|
||||
console.error('[DiaryDateActivityService::addGroupActivity] Group and date don\'t fit');
|
||||
throw new Error('Group isn\'t related to date');
|
||||
}
|
||||
console.log('[DiaryDateActivityService::addGroupActivity] Get predefined activity');
|
||||
const [predefinedActivity, created] = await PredefinedActivity.findOrCreate({
|
||||
where: {
|
||||
name: activity
|
||||
}
|
||||
});
|
||||
console.log('[DiaryDateActivityService::addGroupActivity] Add group activity');
|
||||
console.log(predefinedActivity);
|
||||
devLog(predefinedActivity);
|
||||
const activityData = {
|
||||
diaryDateActivity: diaryDateActivity.id,
|
||||
groupId: groupId,
|
||||
customActivity: predefinedActivity.id
|
||||
}
|
||||
console.log(activityData);
|
||||
devLog(activityData);
|
||||
return await GroupActivity.create(activityData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import Member from "../models/Member.js";
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
import { Op, literal } from "sequelize";
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
class DiaryDateTagService {
|
||||
async getDiaryDateMemberTags(userToken, clubId, memberId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
@@ -35,7 +36,7 @@ class DiaryDateTagService {
|
||||
}
|
||||
|
||||
async addDiaryDateTag(userToken, clubId, diaryDateId, memberId, tag) {
|
||||
console.log(userToken, clubId, diaryDateId, memberId, tag);
|
||||
devLog(userToken, clubId, diaryDateId, memberId, tag);
|
||||
await checkAccess(userToken, clubId);
|
||||
const tagObject = await DiaryTag.findOne({ where: { id: tag.id } });
|
||||
if (!tagObject) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import DiaryMemberTag from '../models/DiaryMemberTag.js';
|
||||
import { DiaryTag } from '../models/DiaryTag.js';
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
class DiaryMemberService {
|
||||
async addNoteToMember(userToken, clubId, diaryDateId, memberId, content) {
|
||||
await checkAccess(userToken, clubId);
|
||||
@@ -14,7 +15,7 @@ class DiaryMemberService {
|
||||
|
||||
async getNotesForMember(userToken, clubId, diaryDateId, memberId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log(clubId, diaryDateId, memberId);
|
||||
devLog(clubId, diaryDateId, memberId);
|
||||
return await DiaryMemberNote.findAll({ where: { diaryDateId, memberId }, order: [['createdAt', 'DESC']] });
|
||||
}
|
||||
|
||||
@@ -50,7 +51,7 @@ class DiaryMemberService {
|
||||
if (tagLink) {
|
||||
await tagLink.destroy();
|
||||
} else {
|
||||
console.log(diaryDateId, memberId, tagId);
|
||||
devLog(diaryDateId, memberId, tagId);
|
||||
throw new Error('Das Tag ist nicht verknüpft.');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,14 @@ import DiaryDateTag from '../models/DiaryDateTag.js';
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
import HttpError from '../exceptions/HttpError.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
class DiaryService {
|
||||
async getDatesForClub(userToken, clubId) {
|
||||
console.log('[DiaryService::getDatesForClub] - Check user access');
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log('[DiaryService::getDatesForClub] - Validate club existence');
|
||||
const club = await Club.findByPk(clubId);
|
||||
if (!club) {
|
||||
throw new HttpError('Club not found', 404);
|
||||
}
|
||||
console.log('[DiaryService::getDatesForClub] - Load diary dates');
|
||||
const dates = await DiaryDate.findAll({
|
||||
where: { clubId },
|
||||
include: [
|
||||
@@ -29,14 +27,11 @@ class DiaryService {
|
||||
}
|
||||
|
||||
async createDateForClub(userToken, clubId, date, trainingStart, trainingEnd) {
|
||||
console.log('[DiaryService::createDateForClub] - Check user access');
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log('[DiaryService::createDateForClub] - Validate club existence');
|
||||
const club = await Club.findByPk(clubId);
|
||||
if (!club) {
|
||||
throw new HttpError('Club not found', 404);
|
||||
}
|
||||
console.log('[DiaryService::createDateForClub] - Validate date');
|
||||
const parsedDate = new Date(date);
|
||||
if (isNaN(parsedDate.getTime())) {
|
||||
throw new HttpError('Invalid date format', 400);
|
||||
@@ -44,7 +39,6 @@ class DiaryService {
|
||||
if (trainingStart && trainingEnd && trainingStart >= trainingEnd) {
|
||||
throw new HttpError('Training start time must be before training end time', 400);
|
||||
}
|
||||
console.log('[DiaryService::createDateForClub] - Create new diary date');
|
||||
const newDate = await DiaryDate.create({
|
||||
date: parsedDate,
|
||||
clubId,
|
||||
@@ -56,9 +50,7 @@ class DiaryService {
|
||||
}
|
||||
|
||||
async updateTrainingTimes(userToken, clubId, dateId, trainingStart, trainingEnd) {
|
||||
console.log('[DiaryService::updateTrainingTimes] - Check user access');
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log('[DiaryService::updateTrainingTimes] - Validate date');
|
||||
const diaryDate = await DiaryDate.findOne({ where: { clubId, id: dateId } });
|
||||
if (!diaryDate) {
|
||||
throw new HttpError('Diary entry not found', 404);
|
||||
@@ -66,7 +58,6 @@ class DiaryService {
|
||||
if (trainingStart && trainingEnd && trainingStart >= trainingEnd) {
|
||||
throw new HttpError('Training start time must be before training end time', 400);
|
||||
}
|
||||
console.log('[DiaryService::updateTrainingTimes] - Update training times');
|
||||
diaryDate.trainingStart = trainingStart || null;
|
||||
diaryDate.trainingEnd = trainingEnd || null;
|
||||
await diaryDate.save();
|
||||
@@ -74,14 +65,12 @@ class DiaryService {
|
||||
}
|
||||
|
||||
async addNoteToDate(userToken, diaryDateId, content) {
|
||||
console.log('[DiaryService::addNoteToDate] - Add note');
|
||||
await checkAccess(userToken, diaryDateId);
|
||||
await DiaryNote.create({ diaryDateId, content });
|
||||
return await DiaryNote.findAll({ where: { diaryDateId }, order: [['createdAt', 'DESC']] });
|
||||
}
|
||||
|
||||
async deleteNoteFromDate(userToken, noteId) {
|
||||
console.log('[DiaryService::deleteNoteFromDate] - Delete note');
|
||||
const note = await DiaryNote.findByPk(noteId);
|
||||
if (!note) {
|
||||
throw new HttpError('Note not found', 404);
|
||||
@@ -92,7 +81,6 @@ class DiaryService {
|
||||
}
|
||||
|
||||
async addTagToDate(userToken, diaryDateId, tagName) {
|
||||
console.log('[DiaryService::addTagToDate] - Add tag');
|
||||
await checkAccess(userToken, diaryDateId);
|
||||
let tag = await DiaryTag.findOne({ where: { name: tagName } });
|
||||
if (!tag) {
|
||||
@@ -105,29 +93,24 @@ class DiaryService {
|
||||
|
||||
async addTagToDiaryDate(userToken, clubId, diaryDateId, tagId) {
|
||||
checkAccess(userToken, clubId);
|
||||
console.log(`[DiaryService::addTagToDiaryDate] - diaryDateId: ${diaryDateId}, tagId: ${tagId}`);
|
||||
const diaryDate = await DiaryDate.findByPk(diaryDateId);
|
||||
if (!diaryDate) {
|
||||
throw new HttpError('DiaryDate not found', 404);
|
||||
}
|
||||
console.log('[DiaryService::addTagToDiaryDate] - Add tag to diary date');
|
||||
const existingEntry = await DiaryDateTag.findOne({
|
||||
where: { diaryDateId, tagId }
|
||||
});
|
||||
if (existingEntry) {
|
||||
return;
|
||||
}
|
||||
console.log('[DiaryService::addTagToDiaryDate] - Tag not found, creating new entry');
|
||||
const tag = await DiaryTag.findByPk(tagId);
|
||||
if (!tag) {
|
||||
throw new HttpError('Tag not found', 404);
|
||||
}
|
||||
console.log('[DiaryService::addTagToDiaryDate] - Add tag to diary date');
|
||||
await DiaryDateTag.create({
|
||||
diaryDateId,
|
||||
tagId
|
||||
});
|
||||
console.log('[DiaryService::addTagToDiaryDate] - Get tags');
|
||||
const tags = await DiaryDateTag.findAll({ where: {
|
||||
diaryDateId: diaryDateId },
|
||||
include: {
|
||||
@@ -135,12 +118,11 @@ class DiaryService {
|
||||
as: 'tag'
|
||||
}
|
||||
});
|
||||
console.log(tags);
|
||||
devLog(tags);
|
||||
return tags.map(tag => tag.tag);
|
||||
}
|
||||
|
||||
async getDiaryNotesForDateAndMember(diaryDateId, memberId) {
|
||||
console.log('[DiaryService::getDiaryNotesForDateAndMember] - Fetching notes');
|
||||
return await DiaryNote.findAll({
|
||||
where: { diaryDateId, memberId },
|
||||
order: [['createdAt', 'DESC']]
|
||||
@@ -153,19 +135,15 @@ class DiaryService {
|
||||
}
|
||||
|
||||
async removeDateForClub(userToken, clubId, dateId) {
|
||||
console.log('[DiaryService::removeDateForClub] - Check user access');
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log('[DiaryService::removeDateForClub] - Validate date');
|
||||
const diaryDate = await DiaryDate.findOne({ where: { id: dateId, clubId } });
|
||||
if (!diaryDate) {
|
||||
throw new HttpError('Diary entry not found', 404);
|
||||
}
|
||||
console.log('[DiaryService::removeDateForClub] - Check for activities');
|
||||
const activityCount = await DiaryDateActivity.count({ where: { diaryDateId: dateId } });
|
||||
if (activityCount > 0) {
|
||||
throw new HttpError('Cannot delete date with activities', 409);
|
||||
}
|
||||
console.log('[DiaryService::removeDateForClub] - Delete diary date');
|
||||
await diaryDate.destroy();
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import DiaryDate from '../models/DiaryDates.js';
|
||||
import HttpError from '../exceptions/HttpError.js';
|
||||
import Group from '../models/Group.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
class GroupService {
|
||||
|
||||
async checkDiaryDateToClub(clubId, dateId) {
|
||||
@@ -40,7 +41,7 @@ class GroupService {
|
||||
}
|
||||
|
||||
async changeGroup(userToken, groupId, clubId, dateId, name, lead) {
|
||||
console.log("changeGroup: ", groupId, clubId, dateId, name, lead);
|
||||
devLog("changeGroup: ", groupId, clubId, dateId, name, lead);
|
||||
await checkAccess(userToken, clubId);
|
||||
await this.checkDiaryDateToClub(clubId, dateId);
|
||||
const group = await Group.findOne({
|
||||
|
||||
94
backend/services/leagueService.js
Normal file
94
backend/services/leagueService.js
Normal file
@@ -0,0 +1,94 @@
|
||||
import League from '../models/League.js';
|
||||
import Season from '../models/Season.js';
|
||||
import SeasonService from './seasonService.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
class LeagueService {
|
||||
static async getAllLeaguesByClub(clubId, seasonId = null) {
|
||||
try {
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
if (!seasonId) {
|
||||
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
|
||||
seasonId = currentSeason.id;
|
||||
}
|
||||
|
||||
const leagues = await League.findAll({
|
||||
where: { clubId, seasonId },
|
||||
include: [
|
||||
{
|
||||
model: Season,
|
||||
as: 'season',
|
||||
attributes: ['id', 'season']
|
||||
}
|
||||
],
|
||||
order: [['name', 'ASC']]
|
||||
});
|
||||
return leagues;
|
||||
} catch (error) {
|
||||
console.error('[LeagueService.getAllLeaguesByClub] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async getLeagueById(leagueId) {
|
||||
try {
|
||||
const league = await League.findByPk(leagueId, {
|
||||
include: [
|
||||
{
|
||||
model: Season,
|
||||
as: 'season',
|
||||
attributes: ['id', 'season']
|
||||
}
|
||||
]
|
||||
});
|
||||
return league;
|
||||
} catch (error) {
|
||||
console.error('[LeagueService.getLeagueById] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async createLeague(leagueData) {
|
||||
try {
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
if (!leagueData.seasonId) {
|
||||
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
|
||||
leagueData.seasonId = currentSeason.id;
|
||||
}
|
||||
|
||||
const league = await League.create(leagueData);
|
||||
return league;
|
||||
} catch (error) {
|
||||
console.error('[LeagueService.createLeague] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async updateLeague(leagueId, updateData) {
|
||||
try {
|
||||
const [updatedRowsCount] = await League.update(updateData, {
|
||||
where: { id: leagueId }
|
||||
});
|
||||
return updatedRowsCount > 0;
|
||||
} catch (error) {
|
||||
console.error('[LeagueService.updateLeague] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async deleteLeague(leagueId) {
|
||||
try {
|
||||
const deletedRowsCount = await League.destroy({
|
||||
where: { id: leagueId }
|
||||
});
|
||||
return deletedRowsCount > 0;
|
||||
} catch (error) {
|
||||
console.error('[LeagueService.deleteLeague] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default LeagueService;
|
||||
@@ -7,9 +7,11 @@ import Season from '../models/Season.js';
|
||||
import Location from '../models/Location.js';
|
||||
import League from '../models/League.js';
|
||||
import Team from '../models/Team.js';
|
||||
import SeasonService from './seasonService.js';
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
class MatchService {
|
||||
|
||||
generateSeasonString(date = new Date()) {
|
||||
@@ -21,8 +23,7 @@ class MatchService {
|
||||
seasonStartYear = currentYear - 1;
|
||||
}
|
||||
const seasonEndYear = seasonStartYear + 1;
|
||||
const seasonEndYearString = seasonEndYear.toString().slice(-2);
|
||||
return `${seasonStartYear}/${seasonEndYearString}`;
|
||||
return `${seasonStartYear}/${seasonEndYear}`;
|
||||
}
|
||||
|
||||
async importCSV(userToken, clubId, filePath) {
|
||||
@@ -57,7 +58,6 @@ class MatchService {
|
||||
},
|
||||
});
|
||||
matches.push({
|
||||
seasonId: season.id,
|
||||
date: parsedDate,
|
||||
time: row['Termin'].split(' ')[1],
|
||||
homeTeamId: homeTeamId,
|
||||
@@ -71,14 +71,21 @@ class MatchService {
|
||||
if (seasonString) {
|
||||
season = await Season.findOne({ where: { season: seasonString } });
|
||||
if (season) {
|
||||
await Match.destroy({ where: { clubId, seasonId: season.id } });
|
||||
// Lösche alle Matches für Ligen dieser Saison
|
||||
const leagues = await League.findAll({
|
||||
where: { seasonId: season.id, clubId }
|
||||
});
|
||||
const leagueIds = leagues.map(league => league.id);
|
||||
if (leagueIds.length > 0) {
|
||||
await Match.destroy({ where: { clubId, leagueId: leagueIds } });
|
||||
}
|
||||
}
|
||||
}
|
||||
const result = await Match.bulkCreate(matches);
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
console.log('Error during CSV import:', error);
|
||||
devLog('Error during CSV import:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -98,33 +105,28 @@ class MatchService {
|
||||
}
|
||||
|
||||
|
||||
async getLeaguesForCurrentSeason(userToken, clubId) {
|
||||
async getLeaguesForCurrentSeason(userToken, clubId, seasonId = null) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const seasonString = this.generateSeasonString();
|
||||
const season = await Season.findOne({
|
||||
where: {
|
||||
season: {
|
||||
[Op.like]: `%${seasonString}%`
|
||||
}
|
||||
|
||||
// Verwende SeasonService für korrekte Saison-Verwaltung
|
||||
let season;
|
||||
if (!seasonId) {
|
||||
season = await SeasonService.getOrCreateCurrentSeason();
|
||||
} else {
|
||||
season = await SeasonService.getSeasonById(seasonId);
|
||||
if (!season) {
|
||||
throw new Error('Season not found');
|
||||
}
|
||||
});
|
||||
if (!season) {
|
||||
await Season.create({ season: seasonString });
|
||||
throw new Error('Season not found');
|
||||
}
|
||||
|
||||
try {
|
||||
const leagues = await League.findAll({
|
||||
include: [{
|
||||
model: Match,
|
||||
as: 'leagueMatches',
|
||||
where: {
|
||||
seasonId: season.id,
|
||||
clubId: clubId
|
||||
},
|
||||
attributes: [],
|
||||
}],
|
||||
where: {
|
||||
clubId: clubId,
|
||||
seasonId: season.id
|
||||
},
|
||||
attributes: ['id', 'name'],
|
||||
group: ['League.id'],
|
||||
order: [['name', 'ASC']]
|
||||
});
|
||||
return leagues;
|
||||
} catch (error) {
|
||||
@@ -133,48 +135,69 @@ class MatchService {
|
||||
}
|
||||
}
|
||||
|
||||
async getMatchesForLeagues(userToken, clubId) {
|
||||
async getMatchesForLeagues(userToken, clubId, seasonId = null) {
|
||||
await checkAccess(userToken, clubId);
|
||||
const seasonString = this.generateSeasonString();
|
||||
const season = await Season.findOne({
|
||||
where: {
|
||||
season: {
|
||||
[Op.like]: `%${seasonString}%`
|
||||
}
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
let season;
|
||||
if (!seasonId) {
|
||||
season = await SeasonService.getOrCreateCurrentSeason();
|
||||
} else {
|
||||
season = await SeasonService.getSeasonById(seasonId);
|
||||
if (!season) {
|
||||
throw new Error('Season not found');
|
||||
}
|
||||
});
|
||||
if (!season) {
|
||||
throw new Error('Season not found');
|
||||
}
|
||||
const matches = await Match.findAll({
|
||||
where: {
|
||||
seasonId: season.id,
|
||||
clubId: clubId,
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: League,
|
||||
as: 'leagueDetails',
|
||||
attributes: ['name'],
|
||||
},
|
||||
{
|
||||
model: Team,
|
||||
as: 'homeTeam', // Assuming your associations are set correctly
|
||||
attributes: ['name'],
|
||||
},
|
||||
{
|
||||
model: Team,
|
||||
as: 'guestTeam',
|
||||
attributes: ['name'],
|
||||
},
|
||||
{
|
||||
model: Location,
|
||||
as: 'location',
|
||||
attributes: ['name', 'address', 'city', 'zip'],
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return matches;
|
||||
|
||||
// Filtere Matches nach Liga-Saison und lade Daten manuell
|
||||
const enrichedMatches = [];
|
||||
for (const match of matches) {
|
||||
// Lade Liga-Daten
|
||||
const league = await League.findByPk(match.leagueId, { attributes: ['name', 'seasonId'] });
|
||||
if (!league || league.seasonId !== season.id) {
|
||||
continue; // Skip matches from other seasons
|
||||
}
|
||||
|
||||
const enrichedMatch = {
|
||||
id: match.id,
|
||||
date: match.date,
|
||||
time: match.time,
|
||||
homeTeamId: match.homeTeamId,
|
||||
guestTeamId: match.guestTeamId,
|
||||
locationId: match.locationId,
|
||||
leagueId: match.leagueId,
|
||||
code: match.code,
|
||||
homePin: match.homePin,
|
||||
guestPin: match.guestPin,
|
||||
homeTeam: { name: 'Unbekannt' },
|
||||
guestTeam: { name: 'Unbekannt' },
|
||||
location: { name: 'Unbekannt', address: '', city: '', zip: '' },
|
||||
leagueDetails: { name: league.name }
|
||||
};
|
||||
|
||||
if (match.homeTeamId) {
|
||||
const homeTeam = await Team.findByPk(match.homeTeamId, { attributes: ['name'] });
|
||||
if (homeTeam) enrichedMatch.homeTeam = homeTeam;
|
||||
}
|
||||
if (match.guestTeamId) {
|
||||
const guestTeam = await Team.findByPk(match.guestTeamId, { attributes: ['name'] });
|
||||
if (guestTeam) enrichedMatch.guestTeam = guestTeam;
|
||||
}
|
||||
if (match.locationId) {
|
||||
const location = await Location.findByPk(match.locationId, {
|
||||
attributes: ['name', 'address', 'city', 'zip']
|
||||
});
|
||||
if (location) enrichedMatch.location = location;
|
||||
}
|
||||
|
||||
enrichedMatches.push(enrichedMatch);
|
||||
}
|
||||
return enrichedMatches;
|
||||
}
|
||||
|
||||
async getMatchesForLeague(userToken, clubId, leagueId) {
|
||||
@@ -192,34 +215,53 @@ class MatchService {
|
||||
}
|
||||
const matches = await Match.findAll({
|
||||
where: {
|
||||
seasonId: season.id,
|
||||
clubId: clubId,
|
||||
leagueId: leagueId
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: League,
|
||||
as: 'leagueDetails',
|
||||
attributes: ['name'],
|
||||
},
|
||||
{
|
||||
model: Team,
|
||||
as: 'homeTeam',
|
||||
attributes: ['name'],
|
||||
},
|
||||
{
|
||||
model: Team,
|
||||
as: 'guestTeam',
|
||||
attributes: ['name'],
|
||||
},
|
||||
{
|
||||
model: Location,
|
||||
as: 'location',
|
||||
attributes: ['name', 'address', 'city', 'zip'],
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
return matches;
|
||||
|
||||
// Lade Team- und Location-Daten manuell
|
||||
const enrichedMatches = [];
|
||||
for (const match of matches) {
|
||||
const enrichedMatch = {
|
||||
id: match.id,
|
||||
date: match.date,
|
||||
time: match.time,
|
||||
homeTeamId: match.homeTeamId,
|
||||
guestTeamId: match.guestTeamId,
|
||||
locationId: match.locationId,
|
||||
leagueId: match.leagueId,
|
||||
code: match.code,
|
||||
homePin: match.homePin,
|
||||
guestPin: match.guestPin,
|
||||
homeTeam: { name: 'Unbekannt' },
|
||||
guestTeam: { name: 'Unbekannt' },
|
||||
location: { name: 'Unbekannt', address: '', city: '', zip: '' },
|
||||
leagueDetails: { name: 'Unbekannt' }
|
||||
};
|
||||
|
||||
if (match.homeTeamId) {
|
||||
const homeTeam = await Team.findByPk(match.homeTeamId, { attributes: ['name'] });
|
||||
if (homeTeam) enrichedMatch.homeTeam = homeTeam;
|
||||
}
|
||||
if (match.guestTeamId) {
|
||||
const guestTeam = await Team.findByPk(match.guestTeamId, { attributes: ['name'] });
|
||||
if (guestTeam) enrichedMatch.guestTeam = guestTeam;
|
||||
}
|
||||
if (match.locationId) {
|
||||
const location = await Location.findByPk(match.locationId, {
|
||||
attributes: ['name', 'address', 'city', 'zip']
|
||||
});
|
||||
if (location) enrichedMatch.location = location;
|
||||
}
|
||||
if (match.leagueId) {
|
||||
const league = await League.findByPk(match.leagueId, { attributes: ['name'] });
|
||||
if (league) enrichedMatch.leagueDetails = league;
|
||||
}
|
||||
|
||||
enrichedMatches.push(enrichedMatch);
|
||||
}
|
||||
return enrichedMatches;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import MemberNote from '../models/MemberNote.js';
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
class MemberNoteService {
|
||||
async addNoteToMember(userToken, clubId, memberId, content) {
|
||||
await checkAccess(userToken, clubId);
|
||||
@@ -8,7 +9,7 @@ class MemberNoteService {
|
||||
}
|
||||
|
||||
async getNotesForMember(userToken, clubId, memberId) {
|
||||
console.log(userToken, clubId);
|
||||
devLog(userToken, clubId);
|
||||
await checkAccess(userToken, clubId);
|
||||
return await MemberNote.findAll({
|
||||
where: { memberId },
|
||||
|
||||
@@ -6,13 +6,11 @@ import path from 'path';
|
||||
import fs from 'fs';
|
||||
import sharp from 'sharp';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
class MemberService {
|
||||
async getApprovalRequests(userToken, clubId) {
|
||||
console.log('[MemberService::getApprovalRequest] - Check user access');
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log('[MemberService::getApprovalRequest] - Load user');
|
||||
const user = await getUserByToken(userToken);
|
||||
console.log('[MemberService::getApprovalRequest] - Load userclub');
|
||||
return await UserClub.findAll({
|
||||
where: {
|
||||
clubId: clubId,
|
||||
@@ -23,9 +21,7 @@ class MemberService {
|
||||
}
|
||||
|
||||
async getClubMembers(userToken, clubId, showAll) {
|
||||
console.log('[getClubMembers] - Check access');
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log('[getClubMembers] - Find members');
|
||||
const where = {
|
||||
clubId: clubId
|
||||
};
|
||||
@@ -44,7 +40,6 @@ class MemberService {
|
||||
});
|
||||
})
|
||||
.then(membersWithImageStatus => {
|
||||
console.log('[getClubMembers] - return members');
|
||||
return membersWithImageStatus;
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -54,17 +49,13 @@ class MemberService {
|
||||
}
|
||||
|
||||
async setClubMember(userToken, clubId, memberId, firstName, lastName, street, city, birthdate, phone, email, active = true, testMembership = false,
|
||||
picsInInternetAllowed = false, gender = 'unknown') {
|
||||
picsInInternetAllowed = false, gender = 'unknown', ttr = null, qttr = null) {
|
||||
try {
|
||||
console.log('[setClubMembers] - Check access');
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log('[setClubMembers] - set default member');
|
||||
let member = null;
|
||||
console.log('[setClubMembers] - load member if possible');
|
||||
if (memberId) {
|
||||
member = await Member.findOne({ where: { id: memberId } });
|
||||
}
|
||||
console.log('[setClubMembers] - set member');
|
||||
if (member) {
|
||||
member.firstName = firstName;
|
||||
member.lastName = lastName;
|
||||
@@ -77,6 +68,8 @@ class MemberService {
|
||||
member.testMembership = testMembership;
|
||||
member.picsInInternetAllowed = picsInInternetAllowed;
|
||||
if (gender) member.gender = gender;
|
||||
if (ttr !== undefined) member.ttr = ttr;
|
||||
if (qttr !== undefined) member.qttr = qttr;
|
||||
await member.save();
|
||||
} else {
|
||||
await Member.create({
|
||||
@@ -92,15 +85,16 @@ class MemberService {
|
||||
testMembership: testMembership,
|
||||
picsInInternetAllowed: picsInInternetAllowed,
|
||||
gender: gender || 'unknown',
|
||||
ttr: ttr,
|
||||
qttr: qttr,
|
||||
});
|
||||
}
|
||||
console.log('[setClubMembers] - return response');
|
||||
return {
|
||||
status: 200,
|
||||
response: { result: "success" },
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
devLog(error);
|
||||
return {
|
||||
status: error.statusCode || 500,
|
||||
response: { error: "nocreation" }
|
||||
@@ -110,7 +104,7 @@ class MemberService {
|
||||
|
||||
async uploadMemberImage(userToken, clubId, memberId, imageBuffer) {
|
||||
try {
|
||||
console.log('------>', userToken, clubId, memberId, imageBuffer);
|
||||
devLog('------>', userToken, clubId, memberId, imageBuffer);
|
||||
await checkAccess(userToken, clubId);
|
||||
const member = await Member.findOne({ where: { id: memberId, clubId: clubId } });
|
||||
if (!member) {
|
||||
@@ -146,6 +140,156 @@ class MemberService {
|
||||
return { status: 500, error: 'Failed to retrieve image' };
|
||||
}
|
||||
}
|
||||
|
||||
async updateRatingsFromMyTischtennis(userToken, clubId) {
|
||||
await checkAccess(userToken, clubId);
|
||||
|
||||
const user = await getUserByToken(userToken);
|
||||
|
||||
const myTischtennisService = (await import('./myTischtennisService.js')).default;
|
||||
const myTischtennisClient = (await import('../clients/myTischtennisClient.js')).default;
|
||||
|
||||
try {
|
||||
// 1. myTischtennis-Session abrufen
|
||||
const session = await myTischtennisService.getSession(user.id);
|
||||
|
||||
const account = await myTischtennisService.getAccount(user.id);
|
||||
|
||||
if (!account) {
|
||||
console.error('[updateRatingsFromMyTischtennis] - No account found!');
|
||||
return {
|
||||
status: 400,
|
||||
response: {
|
||||
message: 'Kein myTischtennis-Account gefunden.',
|
||||
updated: 0,
|
||||
errors: [],
|
||||
debug: { userId: user.id }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (!account.clubId || !account.fedNickname) {
|
||||
console.error('[updateRatingsFromMyTischtennis] - Missing clubId or fedNickname:', {
|
||||
clubId: account.clubId,
|
||||
fedNickname: account.fedNickname
|
||||
});
|
||||
return {
|
||||
status: 400,
|
||||
response: {
|
||||
message: 'Club-ID oder Verbandskürzel nicht verfügbar. Bitte einmal einloggen.',
|
||||
updated: 0,
|
||||
errors: [],
|
||||
debug: {
|
||||
hasClubId: !!account.clubId,
|
||||
hasFedNickname: !!account.fedNickname,
|
||||
clubId: account.clubId,
|
||||
fedNickname: account.fedNickname
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 2. Rangliste vom Verein abrufen
|
||||
const rankings = await myTischtennisClient.getClubRankings(
|
||||
session.cookie,
|
||||
account.clubId,
|
||||
account.fedNickname
|
||||
);
|
||||
|
||||
if (!rankings.success) {
|
||||
return {
|
||||
status: 500,
|
||||
response: {
|
||||
message: rankings.error || 'Fehler beim Abrufen der Rangliste',
|
||||
updated: 0,
|
||||
errors: [],
|
||||
debug: {
|
||||
clubId: account.clubId,
|
||||
fedNickname: account.fedNickname,
|
||||
rankingsError: rankings.error
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 3. Alle Mitglieder des Clubs laden
|
||||
const members = await Member.findAll({ where: { clubId } });
|
||||
|
||||
let updated = 0;
|
||||
const errors = [];
|
||||
const notFound = [];
|
||||
const matched = [];
|
||||
|
||||
// 4. Für jedes Mitglied TTR aktualisieren
|
||||
for (const member of members) {
|
||||
const firstName = member.firstName;
|
||||
const lastName = member.lastName;
|
||||
|
||||
// Suche nach Match in rankings entries
|
||||
const rankingEntry = rankings.entries.find(entry =>
|
||||
entry.firstname.toLowerCase() === firstName.toLowerCase() &&
|
||||
entry.lastname.toLowerCase() === lastName.toLowerCase()
|
||||
);
|
||||
|
||||
if (rankingEntry) {
|
||||
try {
|
||||
// fedRank ist der TTR-Wert
|
||||
const oldTtr = member.ttr;
|
||||
member.ttr = rankingEntry.fedRank;
|
||||
// TODO: QTTR muss von einem anderen Endpoint geholt werden
|
||||
await member.save();
|
||||
updated++;
|
||||
matched.push({
|
||||
name: `${firstName} ${lastName}`,
|
||||
oldTtr: oldTtr,
|
||||
newTtr: rankingEntry.fedRank
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`[updateRatingsFromMyTischtennis] - Error updating ${firstName} ${lastName}:`, error);
|
||||
errors.push({
|
||||
member: `${firstName} ${lastName}`,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
} else {
|
||||
notFound.push(`${firstName} ${lastName}`);
|
||||
}
|
||||
}
|
||||
|
||||
devLog(`Updated: ${updated}, Not found: ${notFound.length}, Errors: ${errors.length}`);
|
||||
|
||||
let message = `${updated} Mitglied(er) aktualisiert.`;
|
||||
if (notFound.length > 0) {
|
||||
message += ` ${notFound.length} nicht in myTischtennis-Rangliste gefunden.`;
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
message += ` ${errors.length} Fehler beim Speichern.`;
|
||||
}
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
response: {
|
||||
message: message,
|
||||
updated: updated,
|
||||
matched: matched,
|
||||
notFound: notFound,
|
||||
errors: errors,
|
||||
totalEntries: rankings.entries.length,
|
||||
totalMembers: members.length
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[updateRatingsFromMyTischtennis] - Error:', error);
|
||||
return {
|
||||
status: 500,
|
||||
response: {
|
||||
message: error.message || 'Fehler beim Aktualisieren',
|
||||
updated: 0,
|
||||
errors: [error.message]
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new MemberService();
|
||||
241
backend/services/myTischtennisService.js
Normal file
241
backend/services/myTischtennisService.js
Normal file
@@ -0,0 +1,241 @@
|
||||
import MyTischtennis from '../models/MyTischtennis.js';
|
||||
import User from '../models/User.js';
|
||||
import myTischtennisClient from '../clients/myTischtennisClient.js';
|
||||
import HttpError from '../exceptions/HttpError.js';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
class MyTischtennisService {
|
||||
/**
|
||||
* Get myTischtennis account for user
|
||||
*/
|
||||
async getAccount(userId) {
|
||||
const account = await MyTischtennis.findOne({
|
||||
where: { userId },
|
||||
attributes: ['id', 'email', 'savePassword', 'lastLoginAttempt', 'lastLoginSuccess', 'expiresAt', 'userData', 'clubId', 'clubName', 'fedNickname', 'createdAt', 'updatedAt']
|
||||
});
|
||||
return account;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update myTischtennis account
|
||||
*/
|
||||
async upsertAccount(userId, email, password, savePassword, userPassword) {
|
||||
// Verify user's app password
|
||||
const user = await User.findByPk(userId);
|
||||
if (!user) {
|
||||
throw new HttpError(404, 'Benutzer nicht gefunden');
|
||||
}
|
||||
|
||||
let loginResult = null;
|
||||
|
||||
// Wenn ein Passwort gesetzt/geändert wird, App-Passwort verifizieren
|
||||
if (password) {
|
||||
const isValidPassword = await user.validatePassword(userPassword);
|
||||
if (!isValidPassword) {
|
||||
throw new HttpError(401, 'Ungültiges Passwort');
|
||||
}
|
||||
|
||||
// Login-Versuch bei myTischtennis
|
||||
loginResult = await myTischtennisClient.login(email, password);
|
||||
if (!loginResult.success) {
|
||||
throw new HttpError(401, loginResult.error || 'myTischtennis-Login fehlgeschlagen. Bitte überprüfen Sie Ihre Zugangsdaten.');
|
||||
}
|
||||
}
|
||||
|
||||
// Find or create account
|
||||
let account = await MyTischtennis.findOne({ where: { userId } });
|
||||
|
||||
const now = new Date();
|
||||
|
||||
if (account) {
|
||||
// Update existing
|
||||
account.email = email;
|
||||
account.savePassword = savePassword;
|
||||
|
||||
if (password && savePassword) {
|
||||
account.setPassword(password);
|
||||
} else if (!savePassword) {
|
||||
account.encryptedPassword = null;
|
||||
}
|
||||
|
||||
if (loginResult && loginResult.success) {
|
||||
account.lastLoginAttempt = now;
|
||||
account.lastLoginSuccess = now;
|
||||
account.accessToken = loginResult.accessToken;
|
||||
account.refreshToken = loginResult.refreshToken;
|
||||
account.expiresAt = loginResult.expiresAt;
|
||||
account.cookie = loginResult.cookie;
|
||||
account.userData = loginResult.user;
|
||||
|
||||
// Hole Club-ID und Federation
|
||||
const profileResult = await myTischtennisClient.getUserProfile(loginResult.cookie);
|
||||
|
||||
if (profileResult.success) {
|
||||
account.clubId = profileResult.clubId;
|
||||
account.clubName = profileResult.clubName;
|
||||
account.fedNickname = profileResult.fedNickname;
|
||||
} else {
|
||||
console.error('[myTischtennisService] - Failed to get profile:', profileResult.error);
|
||||
}
|
||||
} else if (password) {
|
||||
account.lastLoginAttempt = now;
|
||||
}
|
||||
|
||||
await account.save();
|
||||
} else {
|
||||
// Create new
|
||||
const accountData = {
|
||||
userId,
|
||||
email,
|
||||
savePassword,
|
||||
lastLoginAttempt: password ? now : null,
|
||||
lastLoginSuccess: loginResult?.success ? now : null
|
||||
};
|
||||
|
||||
if (loginResult && loginResult.success) {
|
||||
accountData.accessToken = loginResult.accessToken;
|
||||
accountData.refreshToken = loginResult.refreshToken;
|
||||
accountData.expiresAt = loginResult.expiresAt;
|
||||
accountData.cookie = loginResult.cookie;
|
||||
accountData.userData = loginResult.user;
|
||||
|
||||
// Hole Club-ID
|
||||
const profileResult = await myTischtennisClient.getUserProfile(loginResult.cookie);
|
||||
if (profileResult.success) {
|
||||
accountData.clubId = profileResult.clubId;
|
||||
accountData.clubName = profileResult.clubName;
|
||||
}
|
||||
}
|
||||
|
||||
account = await MyTischtennis.create(accountData);
|
||||
|
||||
if (password && savePassword) {
|
||||
account.setPassword(password);
|
||||
await account.save();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: account.id,
|
||||
email: account.email,
|
||||
savePassword: account.savePassword,
|
||||
lastLoginAttempt: account.lastLoginAttempt,
|
||||
lastLoginSuccess: account.lastLoginSuccess,
|
||||
expiresAt: account.expiresAt
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete myTischtennis account
|
||||
*/
|
||||
async deleteAccount(userId) {
|
||||
const deleted = await MyTischtennis.destroy({
|
||||
where: { userId }
|
||||
});
|
||||
return deleted > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify login with stored or provided credentials
|
||||
*/
|
||||
async verifyLogin(userId, providedPassword = null) {
|
||||
const account = await MyTischtennis.findOne({ where: { userId } });
|
||||
|
||||
if (!account) {
|
||||
throw new HttpError(404, 'Kein myTischtennis-Account verknüpft');
|
||||
}
|
||||
|
||||
let password = providedPassword;
|
||||
|
||||
// Wenn kein Passwort übergeben wurde, versuche gespeichertes Passwort zu verwenden
|
||||
if (!password) {
|
||||
if (!account.savePassword || !account.encryptedPassword) {
|
||||
throw new HttpError(400, 'Kein Passwort gespeichert. Bitte geben Sie Ihr Passwort ein.');
|
||||
}
|
||||
password = account.getPassword();
|
||||
}
|
||||
|
||||
// Login-Versuch
|
||||
const now = new Date();
|
||||
account.lastLoginAttempt = now;
|
||||
const loginResult = await myTischtennisClient.login(account.email, password);
|
||||
|
||||
if (loginResult.success) {
|
||||
account.lastLoginSuccess = now;
|
||||
account.accessToken = loginResult.accessToken;
|
||||
account.refreshToken = loginResult.refreshToken;
|
||||
account.expiresAt = loginResult.expiresAt;
|
||||
account.cookie = loginResult.cookie;
|
||||
account.userData = loginResult.user;
|
||||
|
||||
// Hole Club-ID und Federation
|
||||
const profileResult = await myTischtennisClient.getUserProfile(loginResult.cookie);
|
||||
|
||||
if (profileResult.success) {
|
||||
account.clubId = profileResult.clubId;
|
||||
account.clubName = profileResult.clubName;
|
||||
account.fedNickname = profileResult.fedNickname;
|
||||
} else {
|
||||
console.error('[myTischtennisService] - Failed to get profile:', profileResult.error);
|
||||
}
|
||||
|
||||
await account.save();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
accessToken: loginResult.accessToken,
|
||||
refreshToken: loginResult.refreshToken,
|
||||
expiresAt: loginResult.expiresAt,
|
||||
user: loginResult.user,
|
||||
clubId: account.clubId,
|
||||
clubName: account.clubName
|
||||
};
|
||||
} else {
|
||||
await account.save(); // Save lastLoginAttempt
|
||||
throw new HttpError(401, loginResult.error || 'myTischtennis-Login fehlgeschlagen');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if account is configured and ready
|
||||
*/
|
||||
async checkAccountStatus(userId) {
|
||||
const account = await MyTischtennis.findOne({ where: { userId } });
|
||||
|
||||
return {
|
||||
exists: !!account,
|
||||
hasEmail: !!account?.email,
|
||||
hasPassword: !!(account?.savePassword && account?.encryptedPassword),
|
||||
hasValidSession: !!account?.accessToken && account?.expiresAt > Date.now() / 1000,
|
||||
needsConfiguration: !account || !account.email,
|
||||
needsPassword: !!account && (!account.savePassword || !account.encryptedPassword)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stored session for user (for authenticated API requests)
|
||||
*/
|
||||
async getSession(userId) {
|
||||
const account = await MyTischtennis.findOne({ where: { userId } });
|
||||
|
||||
if (!account) {
|
||||
throw new HttpError(404, 'Kein myTischtennis-Account verknüpft');
|
||||
}
|
||||
|
||||
// Check if session is valid
|
||||
if (!account.accessToken || !account.expiresAt || account.expiresAt < Date.now() / 1000) {
|
||||
throw new HttpError(401, 'Session abgelaufen. Bitte erneut einloggen.');
|
||||
}
|
||||
|
||||
return {
|
||||
accessToken: account.accessToken,
|
||||
refreshToken: account.refreshToken,
|
||||
cookie: account.cookie,
|
||||
expiresAt: account.expiresAt,
|
||||
userData: account.userData
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default new MyTischtennisService();
|
||||
|
||||
638
backend/services/pdfParserService.js
Normal file
638
backend/services/pdfParserService.js
Normal file
@@ -0,0 +1,638 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { createRequire } from 'module';
|
||||
const require = createRequire(import.meta.url);
|
||||
const pdfParse = require('pdf-parse/lib/pdf-parse.js');
|
||||
import { Op } from 'sequelize';
|
||||
import Match from '../models/Match.js';
|
||||
import Team from '../models/Team.js';
|
||||
import ClubTeam from '../models/ClubTeam.js';
|
||||
import League from '../models/League.js';
|
||||
import Location from '../models/Location.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
class PDFParserService {
|
||||
/**
|
||||
* Parst eine PDF-Datei und extrahiert Spiel-Daten
|
||||
* @param {string} filePath - Pfad zur PDF-Datei
|
||||
* @param {number} clubId - ID des Vereins
|
||||
* @returns {Promise<Object>} Geparste Spiel-Daten
|
||||
*/
|
||||
static async parsePDF(filePath, clubId) {
|
||||
try {
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw new Error('PDF-Datei nicht gefunden');
|
||||
}
|
||||
|
||||
// Bestimme Dateityp basierend auf Dateiendung
|
||||
const fileExtension = path.extname(filePath).toLowerCase();
|
||||
let fileContent;
|
||||
|
||||
if (fileExtension === '.pdf') {
|
||||
// Echte PDF-Parsing
|
||||
const pdfBuffer = fs.readFileSync(filePath);
|
||||
const pdfData = await pdfParse(pdfBuffer);
|
||||
fileContent = pdfData.text;
|
||||
} else {
|
||||
// Fallback für TXT-Dateien (für Tests)
|
||||
fileContent = fs.readFileSync(filePath, 'utf8');
|
||||
}
|
||||
|
||||
// Parse den Text nach Spiel-Daten
|
||||
const parsedData = this.extractMatchData(fileContent, clubId);
|
||||
|
||||
|
||||
return parsedData;
|
||||
} catch (error) {
|
||||
console.error('[PDFParserService.parsePDF] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrahiert Spiel-Daten aus dem PDF-Text
|
||||
* @param {string} text - Der extrahierte Text aus der PDF
|
||||
* @param {number} clubId - ID des Vereins
|
||||
* @returns {Object} Geparste Daten mit Matches und Metadaten
|
||||
*/
|
||||
static extractMatchData(text, clubId) {
|
||||
const matches = [];
|
||||
const errors = [];
|
||||
const metadata = {
|
||||
totalLines: 0,
|
||||
parsedMatches: 0,
|
||||
errors: 0
|
||||
};
|
||||
|
||||
try {
|
||||
// Teile Text in Zeilen auf
|
||||
const lines = text.split('\n').map(line => line.trim()).filter(line => line.length > 0);
|
||||
metadata.totalLines = lines.length;
|
||||
|
||||
|
||||
// Verschiedene Parsing-Strategien je nach PDF-Format
|
||||
const strategies = [
|
||||
{ name: 'Standard Format', fn: this.parseStandardFormat },
|
||||
{ name: 'Table Format', fn: this.parseTableFormat },
|
||||
{ name: 'List Format', fn: this.parseListFormat }
|
||||
];
|
||||
|
||||
|
||||
for (const strategy of strategies) {
|
||||
try {
|
||||
const result = strategy.fn(lines, clubId);
|
||||
|
||||
if (result.matches.length > 0) {
|
||||
matches.push(...result.matches);
|
||||
metadata.parsedMatches += result.matches.length;
|
||||
break; // Erste erfolgreiche Strategie verwenden
|
||||
}
|
||||
} catch (strategyError) {
|
||||
errors.push(`Strategy ${strategy.name} failed: ${strategyError.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
metadata.errors = errors.length;
|
||||
|
||||
return {
|
||||
matches,
|
||||
errors,
|
||||
metadata,
|
||||
rawText: text.substring(0, 1000), // Erste 1000 Zeichen für Debugging
|
||||
allLines: lines, // Alle Zeilen für Debugging
|
||||
debugInfo: {
|
||||
totalTextLength: text.length,
|
||||
totalLines: lines.length,
|
||||
firstFewLines: lines.slice(0, 10),
|
||||
lastFewLines: lines.slice(-5)
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[PDFParserService.extractMatchData] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard-Format Parser (Datum, Zeit, Heimteam, Gastteam, Code, Pins)
|
||||
* @param {Array} lines - Textzeilen
|
||||
* @param {number} clubId - ID des Vereins
|
||||
* @returns {Object} Geparste Matches
|
||||
*/
|
||||
static parseStandardFormat(lines, clubId) {
|
||||
const matches = [];
|
||||
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
// Suche nach Datum-Pattern (dd.mm.yyyy oder dd/mm/yyyy)
|
||||
const dateMatch = line.match(/(\d{1,2})[./](\d{1,2})[./](\d{4})/);
|
||||
if (dateMatch) {
|
||||
|
||||
// Debug: Zeige die gesamte Zeile mit sichtbaren Whitespaces
|
||||
const debugLine = line.replace(/\s/g, (match) => {
|
||||
if (match === ' ') return '·'; // Mittelpunkt für normales Leerzeichen
|
||||
if (match === '\t') return '→'; // Pfeil für Tab
|
||||
if (match === '\n') return '↵'; // Enter-Zeichen
|
||||
if (match === '\r') return '⏎'; // Carriage Return
|
||||
return `[${match.charCodeAt(0)}]`; // Zeichencode für andere Whitespaces
|
||||
});
|
||||
|
||||
try {
|
||||
const [, day, month, year] = dateMatch;
|
||||
const date = new Date(`${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`);
|
||||
|
||||
// Suche nach Zeit-Pattern direkt nach dem Datum (hh:mm) - Format: Wt.dd.mm.yyyyhh:MM
|
||||
const timeMatch = line.match(/(\d{1,2})[./](\d{1,2})[./](\d{4})(\d{1,2}):(\d{2})/);
|
||||
let time = null;
|
||||
if (timeMatch) {
|
||||
time = `${timeMatch[4].padStart(2, '0')}:${timeMatch[5]}`;
|
||||
}
|
||||
|
||||
|
||||
// Entferne Datum und Zeit vom Anfang der Zeile
|
||||
const cleanLine = line.replace(/^[A-Za-z]{2}\.(\d{1,2})[./](\d{1,2})[./](\d{4})(\d{1,2}):(\d{2})\s*/, '');
|
||||
|
||||
// Entferne Nummerierung am Anfang (z.B. "(1)")
|
||||
const cleanLine2 = cleanLine.replace(/^\(\d+\)/, '');
|
||||
|
||||
// Entferne alle Inhalte in Klammern (z.B. "(J11)")
|
||||
const cleanLine3 = cleanLine2.replace(/\([^)]*\)/g, '');
|
||||
|
||||
// Suche nach Code (12 Zeichen) oder PIN (4 Ziffern) am Ende
|
||||
const codeMatch = cleanLine3.match(/([A-Z0-9]{12})$/);
|
||||
const pinMatch = cleanLine3.match(/(\d{4})$/);
|
||||
|
||||
let code = null;
|
||||
let homePin = null;
|
||||
let guestPin = null;
|
||||
let teamsPart = cleanLine3;
|
||||
|
||||
if (codeMatch) {
|
||||
// Code gefunden (12 Zeichen)
|
||||
code = codeMatch[1];
|
||||
teamsPart = cleanLine3.substring(0, cleanLine3.length - code.length).trim();
|
||||
} else if (pinMatch) {
|
||||
// PIN gefunden (4 Ziffern)
|
||||
const pin = pinMatch[1];
|
||||
teamsPart = cleanLine3.substring(0, cleanLine3.length - pin.length).trim();
|
||||
|
||||
// PIN gehört zu dem Team, das direkt vor der PIN steht
|
||||
// Analysiere die Position der PIN in der ursprünglichen Zeile
|
||||
const pinIndex = cleanLine3.lastIndexOf(pin);
|
||||
const teamsPartIndex = cleanLine3.indexOf(teamsPart);
|
||||
|
||||
// Wenn PIN direkt nach dem Teams-Part steht, gehört sie zur Heimmannschaft
|
||||
// Wenn PIN zwischen den Teams steht, gehört sie zur Gastmannschaft
|
||||
if (pinIndex === teamsPartIndex + teamsPart.length) {
|
||||
// PIN steht direkt nach den Teams -> Heimmannschaft
|
||||
homePin = pin;
|
||||
} else {
|
||||
// PIN steht zwischen den Teams -> Gastmannschaft
|
||||
guestPin = pin;
|
||||
}
|
||||
}
|
||||
|
||||
if (code || pinMatch) {
|
||||
|
||||
|
||||
// Debug: Zeige Whitespaces als lesbare Zeichen
|
||||
const debugTeamsPart = teamsPart.replace(/\s/g, (match) => {
|
||||
if (match === ' ') return '·'; // Mittelpunkt für normales Leerzeichen
|
||||
if (match === '\t') return '→'; // Pfeil für Tab
|
||||
return `[${match.charCodeAt(0)}]`; // Zeichencode für andere Whitespaces
|
||||
});
|
||||
|
||||
// Neue Strategie: Teile die Zeile durch mehrere Leerzeichen (wie in der Tabelle)
|
||||
// Die Struktur ist: Heimmannschaft Gastmannschaft Code
|
||||
const parts = teamsPart.split(/\s{2,}/); // Mindestens 2 Leerzeichen als Trenner
|
||||
|
||||
|
||||
let homeTeamName = '';
|
||||
let guestTeamName = '';
|
||||
|
||||
if (parts.length >= 2) {
|
||||
homeTeamName = parts[0].trim();
|
||||
guestTeamName = parts[1].trim();
|
||||
|
||||
// Entferne noch verbleibende Klammern aus den Team-Namen
|
||||
homeTeamName = homeTeamName.replace(/\([^)]*\)/g, '').trim();
|
||||
guestTeamName = guestTeamName.replace(/\([^)]*\)/g, '').trim();
|
||||
|
||||
|
||||
// Erkenne römische Ziffern am Ende der Team-Namen
|
||||
// Römische Ziffern: I, II, III, IV, V, VI, VII, VIII, IX, X, XI, XII, etc.
|
||||
const romanNumeralPattern = /\s+(I{1,3}|IV|V|VI{0,3}|IX|X|XI{0,2})$/;
|
||||
|
||||
// Prüfe Heimteam auf römische Ziffern
|
||||
const homeRomanMatch = homeTeamName.match(romanNumeralPattern);
|
||||
if (homeRomanMatch) {
|
||||
const romanNumeral = homeRomanMatch[1];
|
||||
const baseName = homeTeamName.replace(romanNumeralPattern, '').trim();
|
||||
homeTeamName = `${baseName} ${romanNumeral}`;
|
||||
}
|
||||
|
||||
// Prüfe Gastteam auf römische Ziffern
|
||||
const guestRomanMatch = guestTeamName.match(romanNumeralPattern);
|
||||
if (guestRomanMatch) {
|
||||
const romanNumeral = guestRomanMatch[1];
|
||||
const baseName = guestTeamName.replace(romanNumeralPattern, '').trim();
|
||||
guestTeamName = `${baseName} ${romanNumeral}`;
|
||||
}
|
||||
|
||||
} else {
|
||||
// Fallback: Versuche mit einzelnen Leerzeichen zu trennen
|
||||
|
||||
// Strategie 1: Suche nach "Harheimer TC" als Heimteam
|
||||
if (teamsPart.includes('Harheimer TC')) {
|
||||
const harheimerIndex = teamsPart.indexOf('Harheimer TC');
|
||||
homeTeamName = 'Harheimer TC';
|
||||
guestTeamName = teamsPart.substring(harheimerIndex + 'Harheimer TC'.length).trim();
|
||||
|
||||
// Entferne Klammern aus Gastteam
|
||||
guestTeamName = guestTeamName.replace(/\([^)]*\)/g, '').trim();
|
||||
|
||||
} else {
|
||||
// Strategie 2: Suche nach Großbuchstaben am Anfang des zweiten Teams
|
||||
const teamSplitMatch = teamsPart.match(/^([A-Za-z0-9\s\-\.]+?)\s+([A-Z][A-Za-z0-9\s\-\.]+)$/);
|
||||
|
||||
if (teamSplitMatch) {
|
||||
homeTeamName = teamSplitMatch[1].trim();
|
||||
guestTeamName = teamSplitMatch[2].trim();
|
||||
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (homeTeamName && guestTeamName) {
|
||||
let debugInfo;
|
||||
if (code) {
|
||||
debugInfo = `code: "${code}"`;
|
||||
} else if (homePin && guestPin) {
|
||||
debugInfo = `homePin: "${homePin}", guestPin: "${guestPin}"`;
|
||||
} else if (homePin) {
|
||||
debugInfo = `homePin: "${homePin}"`;
|
||||
} else if (guestPin) {
|
||||
debugInfo = `guestPin: "${guestPin}"`;
|
||||
}
|
||||
|
||||
matches.push({
|
||||
date: date,
|
||||
time: time,
|
||||
homeTeamName: homeTeamName,
|
||||
guestTeamName: guestTeamName,
|
||||
code: code,
|
||||
homePin: homePin,
|
||||
guestPin: guestPin,
|
||||
clubId: clubId,
|
||||
rawLine: line
|
||||
});
|
||||
}
|
||||
} else {
|
||||
}
|
||||
} catch (parseError) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { matches };
|
||||
}
|
||||
|
||||
/**
|
||||
* Tabellen-Format Parser
|
||||
* @param {Array} lines - Textzeilen
|
||||
* @param {number} clubId - ID des Vereins
|
||||
* @returns {Object} Geparste Matches
|
||||
*/
|
||||
static parseTableFormat(lines, clubId) {
|
||||
const matches = [];
|
||||
|
||||
// Suche nach Tabellen-Header
|
||||
let headerIndex = -1;
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (lines[i].toLowerCase().includes('datum') &&
|
||||
lines[i].toLowerCase().includes('zeit') &&
|
||||
lines[i].toLowerCase().includes('heim') &&
|
||||
lines[i].toLowerCase().includes('gast')) {
|
||||
headerIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (headerIndex >= 0) {
|
||||
// Parse Tabellen-Zeilen
|
||||
for (let i = headerIndex + 1; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const columns = line.split(/\s{2,}|\t/); // Split bei mehreren Leerzeichen oder Tabs
|
||||
|
||||
if (columns.length >= 4) {
|
||||
try {
|
||||
const dateStr = columns[0];
|
||||
const timeStr = columns[1];
|
||||
const homeTeam = columns[2];
|
||||
const guestTeam = columns[3];
|
||||
const code = columns[4] || null;
|
||||
const homePin = columns[5] || null;
|
||||
const guestPin = columns[6] || null;
|
||||
|
||||
// Parse Datum
|
||||
const dateMatch = dateStr.match(/(\d{1,2})[./](\d{1,2})[./](\d{4})/);
|
||||
if (dateMatch) {
|
||||
const [, day, month, year] = dateMatch;
|
||||
const date = new Date(`${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`);
|
||||
|
||||
matches.push({
|
||||
date: date,
|
||||
time: timeStr || null,
|
||||
homeTeamName: homeTeam.trim(),
|
||||
guestTeamName: guestTeam.trim(),
|
||||
code: code ? code.trim() : null,
|
||||
homePin: homePin ? homePin.trim() : null,
|
||||
guestPin: guestPin ? guestPin.trim() : null,
|
||||
clubId: clubId,
|
||||
rawLine: line
|
||||
});
|
||||
}
|
||||
} catch (parseError) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { matches };
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen-Format Parser
|
||||
* @param {Array} lines - Textzeilen
|
||||
* @param {number} clubId - ID des Vereins
|
||||
* @returns {Object} Geparste Matches
|
||||
*/
|
||||
static parseListFormat(lines, clubId) {
|
||||
const matches = [];
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
// Suche nach Nummerierten Listen (1., 2., etc.)
|
||||
const listMatch = line.match(/^\d+\.\s*(.+)/);
|
||||
if (listMatch) {
|
||||
const content = listMatch[1];
|
||||
|
||||
// Versuche verschiedene Formate zu parsen
|
||||
const patterns = [
|
||||
/(\d{1,2}[./]\d{1,2}[./]\d{4})\s+(\d{1,2}:\d{2})?\s+(.+?)\s+vs?\s+(.+?)(?:\s+code[:\s]*([A-Za-z0-9]+))?(?:\s+home[:\s]*pin[:\s]*([A-Za-z0-9]+))?(?:\s+guest[:\s]*pin[:\s]*([A-Za-z0-9]+))?/i,
|
||||
/(\d{1,2}[./]\d{1,2}[./]\d{4})\s+(\d{1,2}:\d{2})?\s+(.+?)\s+-\s+(.+?)(?:\s+code[:\s]*([A-Za-z0-9]+))?(?:\s+heim[:\s]*pin[:\s]*([A-Za-z0-9]+))?(?:\s+gast[:\s]*pin[:\s]*([A-Za-z0-9]+))?/i
|
||||
];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = content.match(pattern);
|
||||
if (match) {
|
||||
try {
|
||||
const [, dateStr, timeStr, homeTeam, guestTeam, code, homePin, guestPin] = match;
|
||||
|
||||
// Parse Datum
|
||||
const dateMatch = dateStr.match(/(\d{1,2})[./](\d{1,2})[./](\d{4})/);
|
||||
if (dateMatch) {
|
||||
const [, day, month, year] = dateMatch;
|
||||
const date = new Date(`${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`);
|
||||
|
||||
matches.push({
|
||||
date: date,
|
||||
time: timeStr || null,
|
||||
homeTeamName: homeTeam.trim(),
|
||||
guestTeamName: guestTeam.trim(),
|
||||
code: code ? code.trim() : null,
|
||||
homePin: homePin ? homePin.trim() : null,
|
||||
guestPin: guestPin ? guestPin.trim() : null,
|
||||
clubId: clubId,
|
||||
rawLine: line
|
||||
});
|
||||
break; // Erste erfolgreiche Pattern verwenden
|
||||
}
|
||||
} catch (parseError) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { matches };
|
||||
}
|
||||
|
||||
/**
|
||||
* Speichert geparste Matches in der Datenbank
|
||||
* @param {Array} matches - Array von Match-Objekten
|
||||
* @param {number} leagueId - ID der Liga
|
||||
* @returns {Promise<Object>} Ergebnis der Speicherung
|
||||
*/
|
||||
static async saveMatchesToDatabase(matches, leagueId) {
|
||||
try {
|
||||
|
||||
const results = {
|
||||
created: 0,
|
||||
updated: 0,
|
||||
errors: []
|
||||
};
|
||||
|
||||
for (const matchData of matches) {
|
||||
try {
|
||||
let debugInfo;
|
||||
if (matchData.code) {
|
||||
debugInfo = `Code: ${matchData.code}`;
|
||||
} else if (matchData.homePin && matchData.guestPin) {
|
||||
debugInfo = `HomePin: ${matchData.homePin}, GuestPin: ${matchData.guestPin}`;
|
||||
} else if (matchData.homePin) {
|
||||
debugInfo = `HomePin: ${matchData.homePin}`;
|
||||
} else if (matchData.guestPin) {
|
||||
debugInfo = `GuestPin: ${matchData.guestPin}`;
|
||||
}
|
||||
|
||||
// Lade alle Matches für das Datum und die Liga
|
||||
|
||||
// Konvertiere das Datum zu einem Datum ohne Zeit für den Vergleich
|
||||
const dateOnly = new Date(matchData.date.getFullYear(), matchData.date.getMonth(), matchData.date.getDate());
|
||||
const nextDay = new Date(dateOnly);
|
||||
nextDay.setDate(nextDay.getDate() + 1);
|
||||
|
||||
const existingMatches = await Match.findAll({
|
||||
where: {
|
||||
date: {
|
||||
[Op.gte]: dateOnly, // Größer oder gleich dem Datum
|
||||
[Op.lt]: nextDay // Kleiner als der nächste Tag
|
||||
},
|
||||
leagueId: leagueId,
|
||||
...(matchData.time && { time: matchData.time }) // Füge Zeit hinzu wenn vorhanden
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: Team,
|
||||
as: 'homeTeam',
|
||||
attributes: ['id', 'name']
|
||||
},
|
||||
{
|
||||
model: Team,
|
||||
as: 'guestTeam',
|
||||
attributes: ['id', 'name']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
|
||||
const timeFilter = matchData.time ? ` and time ${matchData.time}` : '';
|
||||
|
||||
// Debug: Zeige alle gefundenen Matches und lade Teams manuell
|
||||
for (let i = 0; i < existingMatches.length; i++) {
|
||||
const match = existingMatches[i];
|
||||
|
||||
// Lade Teams manuell
|
||||
const homeTeam = await Team.findByPk(match.homeTeamId);
|
||||
const guestTeam = await Team.findByPk(match.guestTeamId);
|
||||
|
||||
|
||||
// Füge die Teams zum Match-Objekt hinzu
|
||||
match.homeTeam = homeTeam;
|
||||
match.guestTeam = guestTeam;
|
||||
}
|
||||
|
||||
// Suche nach dem passenden Match basierend auf Gastmannschaft
|
||||
const matchingMatch = existingMatches.find(match => {
|
||||
if (!match.guestTeam) return false;
|
||||
|
||||
const guestTeamName = match.guestTeam.name.toLowerCase();
|
||||
const searchGuestName = matchData.guestTeamName.toLowerCase();
|
||||
|
||||
// Exakte Übereinstimmung oder Teilstring-Match
|
||||
return guestTeamName === searchGuestName ||
|
||||
guestTeamName.includes(searchGuestName) ||
|
||||
searchGuestName.includes(guestTeamName);
|
||||
});
|
||||
|
||||
if (matchingMatch) {
|
||||
|
||||
// Update das bestehende Match mit Code und Pins
|
||||
// Erstelle Update-Objekt nur mit vorhandenen Feldern
|
||||
const updateData = {};
|
||||
if (matchData.code) {
|
||||
updateData.code = matchData.code;
|
||||
}
|
||||
if (matchData.homePin) {
|
||||
updateData.homePin = matchData.homePin;
|
||||
}
|
||||
if (matchData.guestPin) {
|
||||
updateData.guestPin = matchData.guestPin;
|
||||
}
|
||||
|
||||
await matchingMatch.update(updateData);
|
||||
results.updated++;
|
||||
|
||||
let updateInfo;
|
||||
if (matchData.code) {
|
||||
updateInfo = `code: ${matchData.code}`;
|
||||
} else if (matchData.homePin && matchData.guestPin) {
|
||||
updateInfo = `homePin: ${matchData.homePin}, guestPin: ${matchData.guestPin}`;
|
||||
} else if (matchData.homePin) {
|
||||
updateInfo = `homePin: ${matchData.homePin}`;
|
||||
} else if (matchData.guestPin) {
|
||||
updateInfo = `guestPin: ${matchData.guestPin}`;
|
||||
}
|
||||
|
||||
// Lade das aktualisierte Match neu, um die aktuellen Werte zu zeigen
|
||||
await matchingMatch.reload();
|
||||
const currentValues = [];
|
||||
if (matchingMatch.code) currentValues.push(`code: ${matchingMatch.code}`);
|
||||
if (matchingMatch.homePin) currentValues.push(`homePin: ${matchingMatch.homePin}`);
|
||||
if (matchingMatch.guestPin) currentValues.push(`guestPin: ${matchingMatch.guestPin}`);
|
||||
} else {
|
||||
|
||||
// Fallback: Versuche Teams direkt zu finden
|
||||
const homeTeam = await Team.findOne({
|
||||
where: {
|
||||
name: matchData.homeTeamName,
|
||||
clubId: matchData.clubId
|
||||
}
|
||||
});
|
||||
|
||||
const guestTeam = await Team.findOne({
|
||||
where: {
|
||||
name: matchData.guestTeamName,
|
||||
clubId: matchData.clubId
|
||||
}
|
||||
});
|
||||
|
||||
// Debug: Zeige alle verfügbaren Teams für diesen Club
|
||||
if (!homeTeam || !guestTeam) {
|
||||
const allTeams = await Team.findAll({
|
||||
where: { clubId: matchData.clubId },
|
||||
attributes: ['id', 'name']
|
||||
});
|
||||
|
||||
// Versuche Fuzzy-Matching für Team-Namen
|
||||
const homeTeamFuzzy = allTeams.find(t =>
|
||||
t.name.toLowerCase().includes(matchData.homeTeamName.toLowerCase()) ||
|
||||
matchData.homeTeamName.toLowerCase().includes(t.name.toLowerCase())
|
||||
);
|
||||
const guestTeamFuzzy = allTeams.find(t =>
|
||||
t.name.toLowerCase().includes(matchData.guestTeamName.toLowerCase()) ||
|
||||
matchData.guestTeamName.toLowerCase().includes(t.name.toLowerCase())
|
||||
);
|
||||
|
||||
if (homeTeamFuzzy) {
|
||||
}
|
||||
if (guestTeamFuzzy) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!homeTeam || !guestTeam) {
|
||||
let errorInfo;
|
||||
if (matchData.code) {
|
||||
errorInfo = `Code: ${matchData.code}`;
|
||||
} else if (matchData.homePin && matchData.guestPin) {
|
||||
errorInfo = `HomePin: ${matchData.homePin}, GuestPin: ${matchData.guestPin}`;
|
||||
} else if (matchData.homePin) {
|
||||
errorInfo = `HomePin: ${matchData.homePin}`;
|
||||
} else if (matchData.guestPin) {
|
||||
errorInfo = `GuestPin: ${matchData.guestPin}`;
|
||||
}
|
||||
results.errors.push(`Teams nicht gefunden: "${matchData.homeTeamName}" oder "${matchData.guestTeamName}" (Datum: ${matchData.date.toISOString().split('T')[0]}, Zeit: ${matchData.time}, ${errorInfo})`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Erstelle neues Match (Fallback)
|
||||
await Match.create({
|
||||
date: matchData.date,
|
||||
time: matchData.time,
|
||||
homeTeamId: homeTeam.id,
|
||||
guestTeamId: guestTeam.id,
|
||||
leagueId: leagueId,
|
||||
clubId: matchData.clubId,
|
||||
code: matchData.code,
|
||||
homePin: matchData.homePin,
|
||||
guestPin: matchData.guestPin,
|
||||
locationId: 1 // Default Location, kann später angepasst werden
|
||||
});
|
||||
results.created++;
|
||||
}
|
||||
} catch (matchError) {
|
||||
console.error('[PDFParserService.saveMatchesToDatabase] - Error:', matchError);
|
||||
results.errors.push(`Fehler beim Speichern von Match: ${matchData.rawLine} - ${matchError.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
console.error('[PDFParserService.saveMatchesToDatabase] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default PDFParserService;
|
||||
@@ -5,9 +5,9 @@ import PredefinedActivityImage from '../models/PredefinedActivityImage.js';
|
||||
import sequelize from '../database.js';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
class PredefinedActivityService {
|
||||
async createPredefinedActivity(data) {
|
||||
console.log('[PredefinedActivityService::createPredefinedActivity] - Creating predefined activity');
|
||||
return await PredefinedActivity.create({
|
||||
name: data.name,
|
||||
code: data.code,
|
||||
@@ -20,10 +20,8 @@ class PredefinedActivityService {
|
||||
}
|
||||
|
||||
async updatePredefinedActivity(id, data) {
|
||||
console.log(`[PredefinedActivityService::updatePredefinedActivity] - Updating predefined activity with id: ${id}`);
|
||||
const activity = await PredefinedActivity.findByPk(id);
|
||||
if (!activity) {
|
||||
console.log('[PredefinedActivityService::updatePredefinedActivity] - Activity not found');
|
||||
throw new Error('Predefined activity not found');
|
||||
}
|
||||
return await activity.update({
|
||||
@@ -38,7 +36,6 @@ class PredefinedActivityService {
|
||||
}
|
||||
|
||||
async getAllPredefinedActivities() {
|
||||
console.log('[PredefinedActivityService::getAllPredefinedActivities] - Fetching all predefined activities');
|
||||
return await PredefinedActivity.findAll({
|
||||
order: [
|
||||
[sequelize.literal('code IS NULL'), 'ASC'], // Non-null codes first
|
||||
@@ -49,10 +46,8 @@ class PredefinedActivityService {
|
||||
}
|
||||
|
||||
async getPredefinedActivityById(id) {
|
||||
console.log(`[PredefinedActivityService::getPredefinedActivityById] - Fetching predefined activity with id: ${id}`);
|
||||
const activity = await PredefinedActivity.findByPk(id);
|
||||
if (!activity) {
|
||||
console.log('[PredefinedActivityService::getPredefinedActivityById] - Activity not found');
|
||||
throw new Error('Predefined activity not found');
|
||||
}
|
||||
return activity;
|
||||
@@ -80,7 +75,6 @@ class PredefinedActivityService {
|
||||
}
|
||||
|
||||
async mergeActivities(sourceId, targetId) {
|
||||
console.log(`[PredefinedActivityService::mergeActivities] - Merge ${sourceId} -> ${targetId}`);
|
||||
if (!sourceId || !targetId) throw new Error('sourceId and targetId are required');
|
||||
if (Number(sourceId) === Number(targetId)) throw new Error('sourceId and targetId must differ');
|
||||
|
||||
@@ -120,7 +114,6 @@ class PredefinedActivityService {
|
||||
}
|
||||
|
||||
async deduplicateActivities() {
|
||||
console.log('[PredefinedActivityService::deduplicateActivities] - Start');
|
||||
const all = await PredefinedActivity.findAll();
|
||||
const nameToActivities = new Map();
|
||||
for (const activity of all) {
|
||||
@@ -142,7 +135,6 @@ class PredefinedActivityService {
|
||||
mergedCount++;
|
||||
}
|
||||
}
|
||||
console.log('[PredefinedActivityService::deduplicateActivities] - Done', { mergedCount, groupCount });
|
||||
return { mergedCount, groupCount };
|
||||
}
|
||||
}
|
||||
|
||||
149
backend/services/seasonService.js
Normal file
149
backend/services/seasonService.js
Normal file
@@ -0,0 +1,149 @@
|
||||
import Season from '../models/Season.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
class SeasonService {
|
||||
/**
|
||||
* Ermittelt die aktuelle Saison basierend auf dem aktuellen Datum
|
||||
* @returns {string} Saison im Format "2023/2024"
|
||||
*/
|
||||
static getCurrentSeasonString() {
|
||||
const now = new Date();
|
||||
const currentYear = now.getFullYear();
|
||||
const currentMonth = now.getMonth() + 1; // getMonth() ist 0-basiert
|
||||
|
||||
// Ab 1. Juli: neue Saison beginnt
|
||||
if (currentMonth >= 7) {
|
||||
return `${currentYear}/${currentYear + 1}`;
|
||||
} else {
|
||||
return `${currentYear - 1}/${currentYear}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt oder erstellt die aktuelle Saison
|
||||
* @returns {Promise<Season>} Die aktuelle Saison
|
||||
*/
|
||||
static async getOrCreateCurrentSeason() {
|
||||
try {
|
||||
const currentSeasonString = this.getCurrentSeasonString();
|
||||
|
||||
// Versuche die aktuelle Saison zu finden
|
||||
let season = await Season.findOne({
|
||||
where: { season: currentSeasonString }
|
||||
});
|
||||
|
||||
// Falls nicht vorhanden, erstelle sie
|
||||
if (!season) {
|
||||
season = await Season.create({
|
||||
season: currentSeasonString
|
||||
});
|
||||
}
|
||||
|
||||
return season;
|
||||
} catch (error) {
|
||||
console.error('[SeasonService.getOrCreateCurrentSeason] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt alle verfügbaren Saisons
|
||||
* @returns {Promise<Array<Season>>} Alle Saisons sortiert nach Name
|
||||
*/
|
||||
static async getAllSeasons() {
|
||||
try {
|
||||
const seasons = await Season.findAll({
|
||||
order: [['season', 'DESC']] // Neueste zuerst
|
||||
});
|
||||
return seasons;
|
||||
} catch (error) {
|
||||
console.error('[SeasonService.getAllSeasons] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine neue Saison
|
||||
* @param {string} seasonString - Saison im Format "2023/2024"
|
||||
* @returns {Promise<Season>} Die erstellte Saison
|
||||
*/
|
||||
static async createSeason(seasonString) {
|
||||
try {
|
||||
|
||||
// Prüfe ob Saison bereits existiert
|
||||
const existingSeason = await Season.findOne({
|
||||
where: { season: seasonString }
|
||||
});
|
||||
|
||||
if (existingSeason) {
|
||||
throw new Error('Season already exists');
|
||||
}
|
||||
|
||||
const season = await Season.create({
|
||||
season: seasonString
|
||||
});
|
||||
|
||||
return season;
|
||||
} catch (error) {
|
||||
console.error('[SeasonService.createSeason] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt eine Saison nach ID
|
||||
* @param {number} seasonId - Die Saison-ID
|
||||
* @returns {Promise<Season|null>} Die Saison oder null
|
||||
*/
|
||||
static async getSeasonById(seasonId) {
|
||||
try {
|
||||
const season = await Season.findByPk(seasonId);
|
||||
return season;
|
||||
} catch (error) {
|
||||
console.error('[SeasonService.getSeasonById] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht eine Saison (nur wenn keine Teams/Ligen damit verknüpft sind)
|
||||
* @param {number} seasonId - Die Saison-ID
|
||||
* @returns {Promise<boolean>} True wenn gelöscht, false wenn nicht möglich
|
||||
*/
|
||||
static async deleteSeason(seasonId) {
|
||||
try {
|
||||
|
||||
// Prüfe ob Saison verwendet wird
|
||||
const season = await Season.findByPk(seasonId, {
|
||||
include: [
|
||||
{ association: 'teams' },
|
||||
{ association: 'leagues' }
|
||||
]
|
||||
});
|
||||
|
||||
if (!season) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prüfe ob Saison verwendet wird
|
||||
if (season.teams && season.teams.length > 0) {
|
||||
throw new Error('Season is used by teams');
|
||||
}
|
||||
|
||||
if (season.leagues && season.leagues.length > 0) {
|
||||
throw new Error('Season is used by leagues');
|
||||
}
|
||||
|
||||
await Season.destroy({
|
||||
where: { id: seasonId }
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('[SeasonService.deleteSeason] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SeasonService;
|
||||
188
backend/services/teamDocumentService.js
Normal file
188
backend/services/teamDocumentService.js
Normal file
@@ -0,0 +1,188 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import TeamDocument from '../models/TeamDocument.js';
|
||||
import ClubTeam from '../models/ClubTeam.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
class TeamDocumentService {
|
||||
/**
|
||||
* Speichert ein hochgeladenes Dokument für ein Club-Team
|
||||
* @param {Object} file - Das hochgeladene File-Objekt (von multer)
|
||||
* @param {number} clubTeamId - Die ID des Club-Teams
|
||||
* @param {string} documentType - Der Typ des Dokuments ('code_list' oder 'pin_list')
|
||||
* @returns {Promise<TeamDocument>} Das erstellte TeamDocument
|
||||
*/
|
||||
static async uploadDocument(file, clubTeamId, documentType) {
|
||||
try {
|
||||
// Prüfe ob das Club-Team existiert
|
||||
const clubTeam = await ClubTeam.findByPk(clubTeamId);
|
||||
if (!clubTeam) {
|
||||
throw new Error('Club-Team nicht gefunden');
|
||||
}
|
||||
|
||||
// Generiere einen eindeutigen Dateinamen
|
||||
const fileExtension = path.extname(file.originalname);
|
||||
const uniqueFileName = `${clubTeamId}_${documentType}_${Date.now()}${fileExtension}`;
|
||||
|
||||
// Zielverzeichnis für Team-Dokumente
|
||||
const uploadDir = path.join(__dirname, '..', 'uploads', 'team-documents');
|
||||
|
||||
// Erstelle Upload-Verzeichnis falls es nicht existiert
|
||||
if (!fs.existsSync(uploadDir)) {
|
||||
fs.mkdirSync(uploadDir, { recursive: true });
|
||||
}
|
||||
|
||||
const filePath = path.join(uploadDir, uniqueFileName);
|
||||
|
||||
// Verschiebe die Datei vom temporären Verzeichnis zum finalen Speicherort
|
||||
fs.renameSync(file.path, filePath);
|
||||
|
||||
// Lösche alte Dokumente des gleichen Typs für dieses Team
|
||||
await this.deleteDocumentsByType(clubTeamId, documentType);
|
||||
|
||||
// Erstelle Datenbankeintrag
|
||||
const teamDocument = await TeamDocument.create({
|
||||
fileName: uniqueFileName,
|
||||
originalFileName: file.originalname,
|
||||
filePath: filePath,
|
||||
fileSize: file.size,
|
||||
mimeType: file.mimetype,
|
||||
documentType: documentType,
|
||||
clubTeamId: clubTeamId
|
||||
});
|
||||
|
||||
return teamDocument;
|
||||
} catch (error) {
|
||||
console.error('[TeamDocumentService.uploadDocument] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt alle Dokumente für ein Club-Team
|
||||
* @param {number} clubTeamId - Die ID des Club-Teams
|
||||
* @returns {Promise<Array<TeamDocument>>} Liste der Dokumente
|
||||
*/
|
||||
static async getDocumentsByClubTeam(clubTeamId) {
|
||||
try {
|
||||
|
||||
const documents = await TeamDocument.findAll({
|
||||
where: { clubTeamId },
|
||||
order: [['createdAt', 'DESC']]
|
||||
});
|
||||
|
||||
return documents;
|
||||
} catch (error) {
|
||||
console.error('[TeamDocumentService.getDocumentsByClubTeam] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt ein spezifisches Dokument
|
||||
* @param {number} documentId - Die ID des Dokuments
|
||||
* @returns {Promise<TeamDocument|null>} Das Dokument oder null
|
||||
*/
|
||||
static async getDocumentById(documentId) {
|
||||
try {
|
||||
|
||||
const document = await TeamDocument.findByPk(documentId, {
|
||||
include: [{
|
||||
model: ClubTeam,
|
||||
as: 'clubTeam',
|
||||
attributes: ['id', 'name', 'clubId']
|
||||
}]
|
||||
});
|
||||
|
||||
return document;
|
||||
} catch (error) {
|
||||
console.error('[TeamDocumentService.getDocumentById] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht ein Dokument
|
||||
* @param {number} documentId - Die ID des Dokuments
|
||||
* @returns {Promise<boolean>} True wenn gelöscht, sonst false
|
||||
*/
|
||||
static async deleteDocument(documentId) {
|
||||
try {
|
||||
|
||||
const document = await TeamDocument.findByPk(documentId);
|
||||
if (!document) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lösche die physische Datei
|
||||
if (fs.existsSync(document.filePath)) {
|
||||
fs.unlinkSync(document.filePath);
|
||||
}
|
||||
|
||||
// Lösche den Datenbankeintrag
|
||||
const deletedRows = await TeamDocument.destroy({
|
||||
where: { id: documentId }
|
||||
});
|
||||
|
||||
return deletedRows > 0;
|
||||
} catch (error) {
|
||||
console.error('[TeamDocumentService.deleteDocument] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht alle Dokumente eines bestimmten Typs für ein Club-Team
|
||||
* @param {number} clubTeamId - Die ID des Club-Teams
|
||||
* @param {string} documentType - Der Typ des Dokuments
|
||||
* @returns {Promise<number>} Anzahl der gelöschten Dokumente
|
||||
*/
|
||||
static async deleteDocumentsByType(clubTeamId, documentType) {
|
||||
try {
|
||||
|
||||
const documents = await TeamDocument.findAll({
|
||||
where: { clubTeamId, documentType }
|
||||
});
|
||||
|
||||
let deletedCount = 0;
|
||||
for (const document of documents) {
|
||||
// Lösche die physische Datei
|
||||
if (fs.existsSync(document.filePath)) {
|
||||
fs.unlinkSync(document.filePath);
|
||||
}
|
||||
deletedCount++;
|
||||
}
|
||||
|
||||
// Lösche die Datenbankeinträge
|
||||
const deletedRows = await TeamDocument.destroy({
|
||||
where: { clubTeamId, documentType }
|
||||
});
|
||||
|
||||
return deletedRows;
|
||||
} catch (error) {
|
||||
console.error('[TeamDocumentService.deleteDocumentsByType] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt den Dateipfad für ein Dokument
|
||||
* @param {number} documentId - Die ID des Dokuments
|
||||
* @returns {Promise<string|null>} Der Dateipfad oder null
|
||||
*/
|
||||
static async getDocumentPath(documentId) {
|
||||
try {
|
||||
const document = await TeamDocument.findByPk(documentId);
|
||||
return document ? document.filePath : null;
|
||||
} catch (error) {
|
||||
console.error('[TeamDocumentService.getDocumentPath] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TeamDocumentService;
|
||||
132
backend/services/teamService.js
Normal file
132
backend/services/teamService.js
Normal file
@@ -0,0 +1,132 @@
|
||||
import Team from '../models/Team.js';
|
||||
import League from '../models/League.js';
|
||||
import Club from '../models/Club.js';
|
||||
import Season from '../models/Season.js';
|
||||
import SeasonService from './seasonService.js';
|
||||
import { devLog } from '../utils/logger.js';
|
||||
|
||||
class TeamService {
|
||||
static async getAllTeamsByClub(clubId, seasonId = null) {
|
||||
try {
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
if (!seasonId) {
|
||||
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
|
||||
seasonId = currentSeason.id;
|
||||
}
|
||||
|
||||
const teams = await Team.findAll({
|
||||
where: { clubId, seasonId },
|
||||
include: [
|
||||
{
|
||||
model: League,
|
||||
as: 'league',
|
||||
attributes: ['id', 'name']
|
||||
},
|
||||
{
|
||||
model: Season,
|
||||
as: 'season',
|
||||
attributes: ['id', 'season']
|
||||
}
|
||||
],
|
||||
order: [['name', 'ASC']]
|
||||
});
|
||||
return teams;
|
||||
} catch (error) {
|
||||
console.error('[TeamService.getAllTeamsByClub] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async getTeamById(teamId) {
|
||||
try {
|
||||
const team = await Team.findByPk(teamId, {
|
||||
include: [
|
||||
{
|
||||
model: League,
|
||||
as: 'league',
|
||||
attributes: ['id', 'name']
|
||||
},
|
||||
{
|
||||
model: Club,
|
||||
as: 'club',
|
||||
attributes: ['id', 'name']
|
||||
},
|
||||
{
|
||||
model: Season,
|
||||
as: 'season',
|
||||
attributes: ['id', 'season']
|
||||
}
|
||||
]
|
||||
});
|
||||
return team;
|
||||
} catch (error) {
|
||||
console.error('[TeamService.getTeamById] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async createTeam(teamData) {
|
||||
try {
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
if (!teamData.seasonId) {
|
||||
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
|
||||
teamData.seasonId = currentSeason.id;
|
||||
}
|
||||
|
||||
const team = await Team.create(teamData);
|
||||
return team;
|
||||
} catch (error) {
|
||||
console.error('[TeamService.createTeam] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async updateTeam(teamId, updateData) {
|
||||
try {
|
||||
const [updatedRowsCount] = await Team.update(updateData, {
|
||||
where: { id: teamId }
|
||||
});
|
||||
return updatedRowsCount > 0;
|
||||
} catch (error) {
|
||||
console.error('[TeamService.updateTeam] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async deleteTeam(teamId) {
|
||||
try {
|
||||
const deletedRowsCount = await Team.destroy({
|
||||
where: { id: teamId }
|
||||
});
|
||||
return deletedRowsCount > 0;
|
||||
} catch (error) {
|
||||
console.error('[TeamService.deleteTeam] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
static async getLeaguesByClub(clubId, seasonId = null) {
|
||||
try {
|
||||
|
||||
// Wenn keine Saison angegeben, verwende die aktuelle
|
||||
if (!seasonId) {
|
||||
const currentSeason = await SeasonService.getOrCreateCurrentSeason();
|
||||
seasonId = currentSeason.id;
|
||||
}
|
||||
|
||||
const leagues = await League.findAll({
|
||||
where: { clubId, seasonId },
|
||||
attributes: ['id', 'name', 'seasonId'],
|
||||
order: [['name', 'ASC']]
|
||||
});
|
||||
return leagues;
|
||||
} catch (error) {
|
||||
console.error('[TeamService.getLeaguesByClub] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TeamService;
|
||||
@@ -9,6 +9,7 @@ import { checkAccess } from '../utils/userUtils.js';
|
||||
import { Op, literal } from 'sequelize';
|
||||
|
||||
|
||||
import { devLog } from '../utils/logger.js';
|
||||
function getRoundName(size) {
|
||||
switch (size) {
|
||||
case 2: return "Finale";
|
||||
@@ -88,7 +89,7 @@ class TournamentService {
|
||||
include: [{
|
||||
model: Member,
|
||||
as: 'member',
|
||||
attributes: ['id', 'firstName', 'lastName'],
|
||||
attributes: ['id', 'firstName', 'lastName', 'ttr', 'qttr'],
|
||||
}],
|
||||
order: [[{ model: Member, as: 'member' }, 'firstName', 'ASC']]
|
||||
});
|
||||
@@ -186,7 +187,7 @@ class TournamentService {
|
||||
|
||||
if (alreadyAssigned.length > 0) {
|
||||
// Spieler sind bereits manuell zugeordnet - nicht neu verteilen
|
||||
console.log(`${alreadyAssigned.length} Spieler bereits zugeordnet, ${unassigned.length} noch nicht zugeordnet`);
|
||||
devLog(`${alreadyAssigned.length} Spieler bereits zugeordnet, ${unassigned.length} noch nicht zugeordnet`);
|
||||
} else {
|
||||
// Keine manuellen Zuordnungen - zufällig verteilen
|
||||
const shuffled = members.slice();
|
||||
@@ -202,11 +203,8 @@ class TournamentService {
|
||||
}
|
||||
|
||||
// 4) Round‑Robin anlegen wie gehabt - NUR innerhalb jeder Gruppe
|
||||
console.log(`[fillGroups] Erstelle Matches für ${groups.length} Gruppen`);
|
||||
for (const g of groups) {
|
||||
console.log(`[fillGroups] Verarbeite Gruppe ${g.id}`);
|
||||
const gm = await TournamentMember.findAll({ where: { groupId: g.id } });
|
||||
console.log(`[fillGroups] Gruppe ${g.id} hat ${gm.length} Teilnehmer:`, gm.map(m => ({ id: m.id, name: m.member?.firstName + ' ' + m.member?.lastName })));
|
||||
|
||||
if (gm.length < 2) {
|
||||
console.warn(`Gruppe ${g.id} hat nur ${gm.length} Teilnehmer - keine Matches erstellt`);
|
||||
@@ -214,10 +212,8 @@ class TournamentService {
|
||||
}
|
||||
|
||||
const rounds = this.generateRoundRobinSchedule(gm);
|
||||
console.log(`[fillGroups] Gruppe ${g.id} hat ${rounds.length} Runden`);
|
||||
|
||||
for (let roundIndex = 0; roundIndex < rounds.length; roundIndex++) {
|
||||
console.log(`[fillGroups] Runde ${roundIndex + 1} für Gruppe ${g.id}:`, rounds[roundIndex]);
|
||||
for (const [p1Id, p2Id] of rounds[roundIndex]) {
|
||||
// Prüfe, ob beide Spieler zur gleichen Gruppe gehören
|
||||
const p1 = gm.find(p => p.id === p1Id);
|
||||
@@ -231,7 +227,6 @@ class TournamentService {
|
||||
player2Id: p2Id,
|
||||
groupRound: roundIndex + 1
|
||||
});
|
||||
console.log(`[fillGroups] Match erstellt: ${match.id} - Spieler ${p1Id} vs ${p2Id} in Gruppe ${g.id}`);
|
||||
} else {
|
||||
console.warn(`Spieler gehören nicht zur gleichen Gruppe: ${p1Id} (${p1?.groupId}) vs ${p2Id} (${p2?.groupId}) in Gruppe ${g.id}`);
|
||||
}
|
||||
|
||||
7
backend/test-spielplan.txt
Normal file
7
backend/test-spielplan.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Spielplan 2025/2026 - Test Liga
|
||||
|
||||
1. 15.01.2025 19:00 Team Alpha vs Team Beta code: ABC123 home pin: PIN001 guest pin: PIN002
|
||||
2. 22.01.2025 20:00 Team Gamma gegen Team Delta code: DEF456 heim pin: PIN003 gast pin: PIN004
|
||||
3. 29.01.2025 18:30 Team Epsilon - Team Zeta code: GHI789 home pin: PIN005 guest pin: PIN006
|
||||
4. 05.02.2025 19:30 Team Alpha vs Team Gamma code: JKL012 home pin: PIN007 guest pin: PIN008
|
||||
5. 12.02.2025 20:00 Team Beta gegen Team Delta code: MNO345 heim pin: PIN009 gast pin: PIN010
|
||||
BIN
backend/uploads/team-documents/7_code_list_1759354257578.pdf
Normal file
BIN
backend/uploads/team-documents/7_code_list_1759354257578.pdf
Normal file
Binary file not shown.
BIN
backend/uploads/team-documents/9_code_list_1759357969975.pdf
Normal file
BIN
backend/uploads/team-documents/9_code_list_1759357969975.pdf
Normal file
Binary file not shown.
BIN
backend/uploads/team-documents/9_pin_list_1759386673266.pdf
Normal file
BIN
backend/uploads/team-documents/9_pin_list_1759386673266.pdf
Normal file
Binary file not shown.
19
backend/utils/logger.js
Normal file
19
backend/utils/logger.js
Normal file
@@ -0,0 +1,19 @@
|
||||
// Zentrale Logger-Utility für dev/prod Umgebungen
|
||||
const isDev = process.env.STAGE === 'dev';
|
||||
|
||||
// Debug-Logs nur im dev-Modus
|
||||
export const devLog = (...args) => isDev && console.log(...args);
|
||||
|
||||
// Fehler-Logs immer ausgeben
|
||||
export const errorLog = (...args) => console.error(...args);
|
||||
|
||||
// Info-Logs immer ausgeben (für wichtige Produktions-Events)
|
||||
export const infoLog = (...args) => console.log(...args);
|
||||
|
||||
export default {
|
||||
devLog,
|
||||
errorLog,
|
||||
infoLog,
|
||||
isDev
|
||||
};
|
||||
|
||||
@@ -2,9 +2,10 @@ import jwt from 'jsonwebtoken';
|
||||
import { Op } from 'sequelize';
|
||||
import User from '../models/User.js';
|
||||
import UserToken from '../models/UserToken.js';
|
||||
import UserClub from '../models/UserClub.js'; // <-- hier hinzufügen
|
||||
import UserClub from '../models/UserClub.js';
|
||||
import HttpError from '../exceptions/HttpError.js';
|
||||
import { config } from 'dotenv';
|
||||
import { devLog } from './logger.js';
|
||||
config(); // sorgt dafür, dass process.env.JWT_SECRET geladen wird
|
||||
|
||||
export const getUserByToken = async (token) => {
|
||||
@@ -41,7 +42,6 @@ export const getUserByToken = async (token) => {
|
||||
|
||||
export const hasUserClubAccess = async (userId, clubId) => {
|
||||
try {
|
||||
console.log('[hasUserClubAccess]');
|
||||
const userClub = await UserClub.findOne({
|
||||
where: {
|
||||
user_id: userId,
|
||||
@@ -51,10 +51,9 @@ export const hasUserClubAccess = async (userId, clubId) => {
|
||||
});
|
||||
return userClub !== null;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
console.error('[hasUserClubAccess] - error:', error);
|
||||
throw new HttpError('notfound', 500);
|
||||
}
|
||||
console.log('---- no user found');
|
||||
}
|
||||
|
||||
export const checkAccess = async (userToken, clubId) => {
|
||||
@@ -62,11 +61,9 @@ export const checkAccess = async (userToken, clubId) => {
|
||||
const user = await getUserByToken(userToken);
|
||||
const hasAccess = await hasUserClubAccess(user.id, clubId);
|
||||
if (!hasAccess) {
|
||||
console.log('no club access');
|
||||
throw new HttpError('noaccess', 403);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -76,7 +73,6 @@ export const checkGlobalAccess = async (userToken) => {
|
||||
const user = await getUserByToken(userToken);
|
||||
return user; // Einfach den User zurückgeben, da globale Zugriffe nur Authentifizierung benötigen
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -65,6 +65,20 @@
|
||||
<span class="nav-icon">⚙️</span>
|
||||
Vordefinierte Aktivitäten
|
||||
</a>
|
||||
<a href="/team-management" class="nav-link">
|
||||
<span class="nav-icon">👥</span>
|
||||
Team-Verwaltung
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<nav class="sidebar-footer">
|
||||
<div class="nav-section">
|
||||
<h4 class="nav-title">Einstellungen</h4>
|
||||
<a href="/mytischtennis-account" class="nav-link">
|
||||
<span class="nav-icon">🔗</span>
|
||||
myTischtennis-Account
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -123,7 +137,7 @@ export default {
|
||||
if (newVal === 'new') {
|
||||
this.$router.push('/createclub');
|
||||
} else if (newVal) {
|
||||
this.$router.push(`/showclub/${newVal}`);
|
||||
this.$router.push('/training-stats');
|
||||
}
|
||||
},
|
||||
isAuthenticated(newVal) {
|
||||
@@ -160,8 +174,8 @@ export default {
|
||||
},
|
||||
|
||||
loadClub() {
|
||||
this.setCurrentClub(this.currentClub);
|
||||
this.$router.push(`/showclub/${this.currentClub}`);
|
||||
this.setCurrentClub(this.selectedClub);
|
||||
this.$router.push('/training-stats');
|
||||
},
|
||||
|
||||
async checkSession() {
|
||||
@@ -331,6 +345,15 @@ export default {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.nav-menu-no-flex {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
flex: 0;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.nav-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -110,7 +110,6 @@ export default {
|
||||
this.config.canvas.width = this.width || 600;
|
||||
this.config.canvas.height = this.height || 400;
|
||||
this.$nextTick(() => {
|
||||
console.log('CourtDrawingRender: mounted with drawingData =', this.drawingData);
|
||||
this.init();
|
||||
this.redraw();
|
||||
});
|
||||
@@ -120,11 +119,9 @@ export default {
|
||||
this.canvas = this.$refs.canvas;
|
||||
if (this.canvas) {
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
console.log('CourtDrawingRender: canvas/context initialized');
|
||||
}
|
||||
},
|
||||
redraw() {
|
||||
console.log('CourtDrawingRender: redraw called with data =', this.drawingData);
|
||||
if (!this.ctx) return;
|
||||
const { width, height } = this.config.canvas;
|
||||
// clear and background
|
||||
|
||||
@@ -344,7 +344,6 @@ export default {
|
||||
this.loadDrawingFromMetadata();
|
||||
} else if (oldVal && !newVal) {
|
||||
// drawingData wurde auf null gesetzt - reset alle Werte und zeichne leeres Canvas
|
||||
console.log('CourtDrawingTool: drawingData set to null, resetting all values');
|
||||
this.resetAllValues();
|
||||
this.clearCanvas();
|
||||
this.drawCourt(true); // forceRedraw = true
|
||||
@@ -354,7 +353,6 @@ export default {
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
console.log('CourtDrawingTool: mounted');
|
||||
this.$nextTick(() => {
|
||||
this.initCanvas();
|
||||
this.drawCourt();
|
||||
@@ -365,12 +363,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
initCanvas() {
|
||||
console.log('CourtDrawingTool: initCanvas called');
|
||||
this.canvas = this.$refs.drawingCanvas;
|
||||
console.log('CourtDrawingTool: canvas =', this.canvas);
|
||||
if (this.canvas) {
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
console.log('CourtDrawingTool: ctx =', this.ctx);
|
||||
this.ctx.lineCap = this.config.pen.cap;
|
||||
this.ctx.lineJoin = this.config.pen.join;
|
||||
} else {
|
||||
@@ -379,7 +374,6 @@ export default {
|
||||
},
|
||||
|
||||
drawCourt(forceRedraw = false) {
|
||||
console.log('CourtDrawingTool: drawCourt called, forceRedraw:', forceRedraw);
|
||||
const ctx = this.ctx;
|
||||
const canvas = this.canvas;
|
||||
const config = this.config;
|
||||
@@ -389,8 +383,6 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('CourtDrawingTool: Drawing court...');
|
||||
console.log('Canvas dimensions:', canvas.width, 'x', canvas.height);
|
||||
|
||||
// Hintergrund immer zeichnen wenn forceRedraw=true, sonst nur wenn Canvas leer ist
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
@@ -400,9 +392,7 @@ export default {
|
||||
// Hintergrund
|
||||
ctx.fillStyle = '#f0f0f0';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
console.log('Background drawn');
|
||||
} else {
|
||||
console.log('Canvas not empty, skipping background');
|
||||
}
|
||||
|
||||
// Tischtennis-Tisch
|
||||
@@ -411,8 +401,6 @@ export default {
|
||||
const tableX = (canvas.width - tableWidth) / 2;
|
||||
const tableY = (canvas.height - tableHeight) / 2;
|
||||
|
||||
console.log('Table dimensions:', tableWidth, 'x', tableHeight);
|
||||
console.log('Table position:', tableX, ',', tableY);
|
||||
|
||||
// Tischtennis-Tisch Hintergrund
|
||||
ctx.fillStyle = config.table.color;
|
||||
@@ -1074,9 +1062,6 @@ export default {
|
||||
},
|
||||
|
||||
testDraw() {
|
||||
console.log('CourtDrawingTool: testDraw called');
|
||||
console.log('Canvas:', this.canvas);
|
||||
console.log('Context:', this.ctx);
|
||||
|
||||
if (!this.canvas || !this.ctx) {
|
||||
console.error('Canvas or context not available, trying to reinitialize...');
|
||||
@@ -1084,7 +1069,6 @@ export default {
|
||||
}
|
||||
|
||||
if (this.canvas && this.ctx) {
|
||||
console.log('Drawing simple test...');
|
||||
|
||||
// Einfacher Test: Roter Kreis
|
||||
this.ctx.fillStyle = 'red';
|
||||
@@ -1092,18 +1076,15 @@ export default {
|
||||
this.ctx.arc(300, 200, 50, 0, 2 * Math.PI);
|
||||
this.ctx.fill();
|
||||
|
||||
console.log('Red circle drawn');
|
||||
} else {
|
||||
console.error('Still no canvas or context available');
|
||||
}
|
||||
},
|
||||
|
||||
async saveDrawing() {
|
||||
console.log('CourtDrawingTool: saveDrawing called');
|
||||
async saveDrawing() {
|
||||
|
||||
try {
|
||||
const dataURL = this.canvas.toDataURL('image/png');
|
||||
console.log('CourtDrawingTool: dataURL created, length:', dataURL.length);
|
||||
|
||||
this.$emit('input', dataURL);
|
||||
|
||||
@@ -1121,7 +1102,6 @@ export default {
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
console.log('CourtDrawingTool: drawingData created:', drawingData);
|
||||
// Immer Metadaten nach oben geben
|
||||
this.$emit('update-drawing-data', drawingData);
|
||||
|
||||
@@ -1129,18 +1109,12 @@ export default {
|
||||
// Konvertiere DataURL zu Blob für Upload
|
||||
const response = await fetch(dataURL);
|
||||
const blob = await response.blob();
|
||||
console.log('CourtDrawingTool: blob created, size:', blob.size);
|
||||
|
||||
// Erstelle File-Objekt
|
||||
const file = new File([blob], `exercise-${Date.now()}.png`, { type: 'image/png' });
|
||||
console.log('CourtDrawingTool: file created:', file);
|
||||
console.log('CourtDrawingTool: file type:', file.type);
|
||||
console.log('CourtDrawingTool: file size:', file.size);
|
||||
|
||||
// Emittiere das File und die Zeichnungsdaten für Upload
|
||||
console.log('CourtDrawingTool: emitting upload-image event');
|
||||
this.$emit('upload-image', file, drawingData);
|
||||
console.log('CourtDrawingTool: upload-image event emitted');
|
||||
} else {
|
||||
// Kein Bild-Upload mehr: gebe lediglich die Zeichnungsdaten an den Parent weiter,
|
||||
// damit Felder (Kürzel/Name/Beschreibung) gefüllt werden können
|
||||
@@ -1173,7 +1147,6 @@ export default {
|
||||
},
|
||||
|
||||
resetAllValues() {
|
||||
console.log('CourtDrawingTool: Resetting all values to initial state');
|
||||
this.selectedStartPosition = null;
|
||||
this.selectedCirclePosition = null;
|
||||
this.strokeType = null;
|
||||
@@ -1189,7 +1162,6 @@ export default {
|
||||
|
||||
loadDrawingFromMetadata() {
|
||||
if (this.drawingData) {
|
||||
console.log('CourtDrawingTool: Loading drawing from metadata:', this.drawingData);
|
||||
|
||||
// Lade alle Zeichnungsdaten
|
||||
this.selectedStartPosition = this.drawingData.selectedStartPosition || null;
|
||||
@@ -1213,7 +1185,7 @@ export default {
|
||||
this.selectedCirclePosition = 'bottom';
|
||||
}
|
||||
|
||||
console.log('CourtDrawingTool: Loaded values:', {
|
||||
this.$emit('drawing-data', {
|
||||
selectedStartPosition: this.selectedStartPosition,
|
||||
selectedCirclePosition: this.selectedCirclePosition,
|
||||
strokeType: this.strokeType,
|
||||
@@ -1229,7 +1201,6 @@ export default {
|
||||
this.drawCourt();
|
||||
});
|
||||
|
||||
console.log('CourtDrawingTool: Drawing loaded from metadata');
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
293
frontend/src/components/MyTischtennisDialog.vue
Normal file
293
frontend/src/components/MyTischtennisDialog.vue
Normal file
@@ -0,0 +1,293 @@
|
||||
<template>
|
||||
<div class="modal-overlay" @click.self="$emit('close')">
|
||||
<div class="modal">
|
||||
<div class="modal-header">
|
||||
<h3>{{ account ? 'myTischtennis-Account bearbeiten' : 'myTischtennis-Account verknüpfen' }}</h3>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div class="form-group">
|
||||
<label for="mtt-email">myTischtennis-E-Mail:</label>
|
||||
<input
|
||||
type="email"
|
||||
id="mtt-email"
|
||||
v-model="formData.email"
|
||||
placeholder="Ihre myTischtennis-E-Mail-Adresse"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="mtt-password">myTischtennis-Passwort:</label>
|
||||
<input
|
||||
type="password"
|
||||
id="mtt-password"
|
||||
v-model="formData.password"
|
||||
:placeholder="account && account.savePassword ? 'Leer lassen um beizubehalten' : 'Ihr myTischtennis-Passwort'"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group checkbox-group">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="formData.savePassword"
|
||||
/>
|
||||
<span>myTischtennis-Passwort speichern</span>
|
||||
</label>
|
||||
<p class="hint">
|
||||
Wenn aktiviert, wird Ihr myTischtennis-Passwort verschlüsselt gespeichert,
|
||||
sodass automatische Synchronisationen möglich sind.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group" v-if="formData.password">
|
||||
<label for="app-password">Ihr App-Passwort zur Bestätigung:</label>
|
||||
<input
|
||||
type="password"
|
||||
id="app-password"
|
||||
v-model="formData.userPassword"
|
||||
placeholder="Ihr Passwort für diese App"
|
||||
required
|
||||
/>
|
||||
<p class="hint">
|
||||
Aus Sicherheitsgründen benötigen wir Ihr App-Passwort,
|
||||
um das myTischtennis-Passwort zu speichern.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="error-message">
|
||||
{{ error }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="btn-secondary" @click="$emit('close')" :disabled="saving">
|
||||
Abbrechen
|
||||
</button>
|
||||
<button class="btn-primary" @click="saveAccount" :disabled="!canSave || saving">
|
||||
{{ saving ? 'Speichere...' : 'Speichern' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '../apiClient.js';
|
||||
|
||||
export default {
|
||||
name: 'MyTischtennisDialog',
|
||||
props: {
|
||||
account: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formData: {
|
||||
email: this.account?.email || '',
|
||||
password: '',
|
||||
savePassword: this.account?.savePassword || false,
|
||||
userPassword: ''
|
||||
},
|
||||
saving: false,
|
||||
error: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canSave() {
|
||||
// E-Mail ist erforderlich
|
||||
if (!this.formData.email.trim()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wenn ein Passwort eingegeben wurde, muss auch das App-Passwort eingegeben werden
|
||||
if (this.formData.password && !this.formData.userPassword) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async saveAccount() {
|
||||
if (!this.canSave) return;
|
||||
|
||||
this.error = null;
|
||||
this.saving = true;
|
||||
|
||||
try {
|
||||
const payload = {
|
||||
email: this.formData.email,
|
||||
savePassword: this.formData.savePassword
|
||||
};
|
||||
|
||||
// Nur password und userPassword hinzufügen, wenn ein Passwort eingegeben wurde
|
||||
if (this.formData.password) {
|
||||
payload.password = this.formData.password;
|
||||
payload.userPassword = this.formData.userPassword;
|
||||
}
|
||||
|
||||
await apiClient.post('/mytischtennis/account', payload);
|
||||
this.$emit('saved');
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Speichern:', error);
|
||||
this.error = error.response?.data?.message || 'Fehler beim Speichern des Accounts';
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
max-width: 600px;
|
||||
width: 90%;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 1.5rem;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
}
|
||||
|
||||
.modal-header h3 {
|
||||
margin: 0;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 1.5rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
padding: 1rem 1.5rem;
|
||||
border-top: 1px solid #dee2e6;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.form-group input[type="text"],
|
||||
.form-group input[type="email"],
|
||||
.form-group input[type="password"] {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-group input[type="text"]:focus,
|
||||
.form-group input[type="email"]:focus,
|
||||
.form-group input[type="password"]:focus {
|
||||
outline: none;
|
||||
border-color: #007bff;
|
||||
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
|
||||
}
|
||||
|
||||
.checkbox-group label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-weight: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.checkbox-group input[type="checkbox"] {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hint {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
color: #6c757d;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
padding: 0.75rem;
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
border-radius: 4px;
|
||||
color: #721c24;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.btn-primary, .btn-secondary {
|
||||
padding: 0.75rem 1.5rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
background-color: #6c757d;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover:not(:disabled) {
|
||||
background-color: #545b62;
|
||||
}
|
||||
|
||||
.btn-secondary:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
299
frontend/src/components/SeasonSelector.vue
Normal file
299
frontend/src/components/SeasonSelector.vue
Normal file
@@ -0,0 +1,299 @@
|
||||
<template>
|
||||
<div class="season-selector">
|
||||
<label>
|
||||
<span>Saison:</span>
|
||||
<div class="season-input-group">
|
||||
<select v-model="selectedSeasonId" @change="onSeasonChange" class="season-select" :disabled="loading">
|
||||
<option value="">{{ loading ? 'Lade...' : 'Saison wählen...' }}</option>
|
||||
<option v-for="season in seasons" :key="season.id" :value="season.id">
|
||||
{{ season.season }}
|
||||
</option>
|
||||
</select>
|
||||
<button @click="showNewSeasonForm = !showNewSeasonForm" class="btn-add-season" title="Neue Saison hinzufügen">
|
||||
{{ showNewSeasonForm ? '✕' : '+' }}
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<div v-if="error" class="error-message">
|
||||
{{ error }}
|
||||
</div>
|
||||
|
||||
<div v-if="showNewSeasonForm" class="new-season-form">
|
||||
<label>
|
||||
<span>Neue Saison:</span>
|
||||
<input
|
||||
type="text"
|
||||
v-model="newSeasonString"
|
||||
placeholder="z.B. 2023/2024"
|
||||
@keyup.enter="createSeason"
|
||||
class="season-input"
|
||||
>
|
||||
</label>
|
||||
<div class="form-actions">
|
||||
<button @click="createSeason" :disabled="!isValidSeasonFormat" class="btn-create">
|
||||
Erstellen
|
||||
</button>
|
||||
<button @click="cancelNewSeason" class="btn-cancel">
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, computed, onMounted, watch } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
import apiClient from '../apiClient.js';
|
||||
|
||||
export default {
|
||||
name: 'SeasonSelector',
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
default: null
|
||||
},
|
||||
showCurrentSeason: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
emits: ['update:modelValue', 'season-change'],
|
||||
setup(props, { emit }) {
|
||||
const store = useStore();
|
||||
|
||||
// Reactive data
|
||||
const seasons = ref([]);
|
||||
const selectedSeasonId = ref(props.modelValue);
|
||||
const showNewSeasonForm = ref(false);
|
||||
const newSeasonString = ref('');
|
||||
const loading = ref(false);
|
||||
const error = ref(null);
|
||||
|
||||
// Computed
|
||||
const isValidSeasonFormat = computed(() => {
|
||||
const seasonRegex = /^\d{4}\/\d{4}$/;
|
||||
return seasonRegex.test(newSeasonString.value);
|
||||
});
|
||||
|
||||
// Methods
|
||||
const loadSeasons = async () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
const response = await apiClient.get('/seasons');
|
||||
seasons.value = response.data;
|
||||
|
||||
// Wenn showCurrentSeason true ist und keine Saison ausgewählt, wähle die aktuelle
|
||||
if (props.showCurrentSeason && !selectedSeasonId.value && seasons.value.length > 0) {
|
||||
// Die erste Saison ist die neueste (sortiert nach DESC)
|
||||
selectedSeasonId.value = seasons.value[0].id;
|
||||
emit('update:modelValue', selectedSeasonId.value);
|
||||
emit('season-change', seasons.value[0]);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Laden der Saisons:', err);
|
||||
error.value = 'Fehler beim Laden der Saisons';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onSeasonChange = () => {
|
||||
const selectedSeason = seasons.value.find(s => s.id == selectedSeasonId.value);
|
||||
emit('update:modelValue', selectedSeasonId.value);
|
||||
emit('season-change', selectedSeason);
|
||||
};
|
||||
|
||||
const createSeason = async () => {
|
||||
if (!isValidSeasonFormat.value) return;
|
||||
|
||||
try {
|
||||
const response = await apiClient.post('/seasons', {
|
||||
season: newSeasonString.value
|
||||
});
|
||||
|
||||
const newSeason = response.data;
|
||||
seasons.value.unshift(newSeason); // Am Anfang einfügen (neueste zuerst)
|
||||
selectedSeasonId.value = newSeason.id;
|
||||
emit('update:modelValue', selectedSeasonId.value);
|
||||
emit('season-change', newSeason);
|
||||
|
||||
// Formular zurücksetzen
|
||||
newSeasonString.value = '';
|
||||
showNewSeasonForm.value = false;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen der Saison:', error);
|
||||
if (error.response?.data?.error === 'alreadyexists') {
|
||||
alert('Diese Saison existiert bereits!');
|
||||
} else {
|
||||
alert('Fehler beim Erstellen der Saison');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const cancelNewSeason = () => {
|
||||
newSeasonString.value = '';
|
||||
showNewSeasonForm.value = false;
|
||||
};
|
||||
|
||||
// Watch for prop changes
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
selectedSeasonId.value = newValue;
|
||||
});
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
loadSeasons();
|
||||
});
|
||||
|
||||
return {
|
||||
seasons,
|
||||
selectedSeasonId,
|
||||
showNewSeasonForm,
|
||||
newSeasonString,
|
||||
loading,
|
||||
error,
|
||||
isValidSeasonFormat,
|
||||
onSeasonChange,
|
||||
createSeason,
|
||||
cancelNewSeason
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.season-selector {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.season-selector label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.season-selector label span {
|
||||
font-weight: 600;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.season-input-group {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.season-select {
|
||||
flex: 1;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius-small);
|
||||
font-size: 1rem;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.season-select:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 2px var(--primary-light);
|
||||
}
|
||||
|
||||
.btn-add-season {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: var(--border-radius-small);
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.btn-add-season:hover {
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
.new-season-form {
|
||||
background: var(--background-light);
|
||||
padding: 1rem;
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--border-color);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.new-season-form label {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.season-input {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius-small);
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.season-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 2px var(--primary-light);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn-create {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: var(--border-radius-small);
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.btn-create:hover:not(:disabled) {
|
||||
background: var(--primary-dark);
|
||||
}
|
||||
|
||||
.btn-create:disabled {
|
||||
background: var(--text-muted);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: var(--background-light);
|
||||
color: var(--text-color);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: var(--border-radius-small);
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.btn-cancel:hover {
|
||||
background: var(--border-color);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #dc3545;
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
background: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
border-radius: var(--border-radius-small);
|
||||
}
|
||||
</style>
|
||||
@@ -13,6 +13,8 @@ import TournamentsView from './views/TournamentsView.vue';
|
||||
import TrainingStatsView from './views/TrainingStatsView.vue';
|
||||
import PredefinedActivities from './views/PredefinedActivities.vue';
|
||||
import OfficialTournaments from './views/OfficialTournaments.vue';
|
||||
import MyTischtennisAccount from './views/MyTischtennisAccount.vue';
|
||||
import TeamManagementView from './views/TeamManagementView.vue';
|
||||
import Impressum from './views/Impressum.vue';
|
||||
import Datenschutz from './views/Datenschutz.vue';
|
||||
|
||||
@@ -31,6 +33,8 @@ const routes = [
|
||||
{ path: '/training-stats', component: TrainingStatsView },
|
||||
{ path: '/predefined-activities', component: PredefinedActivities },
|
||||
{ path: '/official-tournaments', component: OfficialTournaments },
|
||||
{ path: '/mytischtennis-account', component: MyTischtennisAccount },
|
||||
{ path: '/team-management', component: TeamManagementView },
|
||||
{ path: '/impressum', component: Impressum },
|
||||
{ path: '/datenschutz', component: Datenschutz },
|
||||
];
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>Mitglieder</h2>
|
||||
<div>
|
||||
<button @click="createPhoneList">Telefonliste generieren</button>Es werden nur aktive Mitglieder ausgegeben
|
||||
<div class="action-buttons">
|
||||
<button @click="createPhoneList">Telefonliste generieren</button>
|
||||
<span class="info-text">Es werden nur aktive Mitglieder ausgegeben</span>
|
||||
<button @click="updateRatingsFromMyTischtennis" class="btn-update-ratings" :disabled="isUpdatingRatings">
|
||||
{{ isUpdatingRatings ? 'Aktualisiere...' : 'TTR/QTTR von myTischtennis aktualisieren' }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="newmember">
|
||||
<div class="toggle-new-member">
|
||||
@@ -56,6 +60,7 @@
|
||||
<th>Bild (Inet?)</th>
|
||||
<th>Testm.</th>
|
||||
<th>Name, Vorname</th>
|
||||
<th>TTR / QTTR</th>
|
||||
<th>Adresse</th>
|
||||
<th>Geburtsdatum</th>
|
||||
<th>Telefon-Nr.</th>
|
||||
@@ -80,6 +85,14 @@
|
||||
<span v-if="!member.active && showInactiveMembers" class="inactive-badge">inaktiv</span>
|
||||
</span>
|
||||
</td>
|
||||
<td class="rating-cell">
|
||||
<span v-if="member.ttr || member.qttr">
|
||||
<span v-if="member.ttr" class="ttr-value">{{ member.ttr }}</span>
|
||||
<span v-if="member.ttr && member.qttr" class="rating-separator">/</span>
|
||||
<span v-if="member.qttr" class="qttr-value">{{ member.qttr }}</span>
|
||||
</span>
|
||||
<span v-else class="no-rating">-</span>
|
||||
</td>
|
||||
<td>{{ member.street }}, {{ member.city }}</td>
|
||||
<td>{{ getFormattedBirthdate(member.birthDate) }}</td>
|
||||
<td>{{ member.phone }}</td>
|
||||
@@ -152,7 +165,8 @@ export default {
|
||||
selectedImageUrl: null,
|
||||
testMembership: false,
|
||||
showInactiveMembers: false,
|
||||
newPicsInInternetAllowed: false
|
||||
newPicsInInternetAllowed: false,
|
||||
isUpdatingRatings: false
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
@@ -371,6 +385,29 @@ export default {
|
||||
if (v === 'female') return '♀';
|
||||
if (v === 'diverse') return '⚧';
|
||||
return '';
|
||||
},
|
||||
async updateRatingsFromMyTischtennis() {
|
||||
if (!confirm('TTR/QTTR-Werte von myTischtennis aktualisieren?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isUpdatingRatings = true;
|
||||
try {
|
||||
const response = await apiClient.post(`/clubmembers/update-ratings/${this.currentClub}`);
|
||||
|
||||
if (response.data.message) {
|
||||
alert(response.data.message);
|
||||
}
|
||||
|
||||
// Mitglieder neu laden um aktualisierte Werte anzuzeigen
|
||||
await this.loadMembers();
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Aktualisieren der Ratings:', error);
|
||||
const message = error.response?.data?.error || error.response?.data?.message || 'Fehler beim Aktualisieren der TTR/QTTR-Werte';
|
||||
alert(message);
|
||||
} finally {
|
||||
this.isUpdatingRatings = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -489,4 +526,61 @@ table td {
|
||||
.row-inactive { opacity: .6; }
|
||||
.is-inactive { text-decoration: line-through; }
|
||||
.inactive-badge { margin-left: .5rem; font-size: .85em; color: #666; text-transform: lowercase; }
|
||||
|
||||
.rating-cell {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
.ttr-value {
|
||||
font-weight: 600;
|
||||
color: #1a73e8;
|
||||
}
|
||||
|
||||
.qttr-value {
|
||||
font-weight: 600;
|
||||
color: #d81b60;
|
||||
}
|
||||
|
||||
.rating-separator {
|
||||
color: #999;
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
|
||||
.no-rating {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
font-size: 0.9em;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.btn-update-ratings {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9em;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-update-ratings:hover:not(:disabled) {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.btn-update-ratings:disabled {
|
||||
background-color: #6c757d;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user