Compare commits
9 Commits
spielplaen
...
mytischten
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ac71d967f | ||
|
|
75d304ec6d | ||
|
|
afd96f5df1 | ||
|
|
4bfa6a5889 | ||
|
|
f4187512ba | ||
|
|
b557297bf0 | ||
|
|
eb2273e28c | ||
|
|
091599b745 | ||
|
|
d70a5ca63e |
284
backend/clients/myTischtennisClient.js
Normal file
284
backend/clients/myTischtennisClient.js
Normal file
@@ -0,0 +1,284 @@
|
||||
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) {
|
||||
console.log('[getUserProfile] - Calling /?_data=root with cookie:', cookie?.substring(0, 50) + '...');
|
||||
|
||||
const result = await this.authenticatedRequest('/?_data=root', cookie, {
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
console.log('[getUserProfile] - Result success:', result.success);
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
console.log('[getUserProfile] - Full userProfile.club:', result.data?.userProfile?.club);
|
||||
console.log('[getUserProfile] - Full userProfile.organization:', result.data?.userProfile?.organization);
|
||||
|
||||
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;
|
||||
|
||||
console.log('[getClubRankings] - Starting to fetch rankings for club', clubId);
|
||||
|
||||
while (hasMorePages) {
|
||||
const endpoint = `/rankings/andro-rangliste?all-players=on&clubnr=${clubId}&fednickname=${fedNickname}&results-per-page=100&page=${currentPage}&_data=routes%2F%24`;
|
||||
|
||||
console.log(`[getClubRankings] - Fetching page ${currentPage}...`);
|
||||
|
||||
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'
|
||||
};
|
||||
}
|
||||
|
||||
console.log(`[getClubRankings] - Page ${currentPage}: Found ${entries.length} entries`);
|
||||
|
||||
// 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;
|
||||
console.log('[getClubRankings] - No more entries, stopping');
|
||||
} else if (rankingData.numberOfPages && currentPage >= rankingData.numberOfPages - 1) {
|
||||
hasMorePages = false;
|
||||
console.log(`[getClubRankings] - Reached last page (${rankingData.numberOfPages})`);
|
||||
} else if (allEntries.length >= rankingData.resultLength) {
|
||||
hasMorePages = false;
|
||||
console.log(`[getClubRankings] - Got all entries (${allEntries.length}/${rankingData.resultLength})`);
|
||||
} else {
|
||||
currentPage++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[getClubRankings] - Total entries fetched: ${allEntries.length}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
entries: allEntries,
|
||||
metadata: {
|
||||
totalEntries: allEntries.length,
|
||||
pagesFetched: currentPage + 1
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default new MyTischtennisClient();
|
||||
|
||||
@@ -34,11 +34,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);
|
||||
@@ -75,4 +75,17 @@ const getMemberImage = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
export { getClubMembers, getWaitingApprovals, setClubMembers, uploadMemberImage, getMemberImage };
|
||||
const updateRatingsFromMyTischtennis = async (req, res) => {
|
||||
console.log('[updateRatingsFromMyTischtennis]');
|
||||
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 };
|
||||
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();
|
||||
|
||||
@@ -5,8 +5,8 @@ import fs from 'fs';
|
||||
|
||||
export const createPredefinedActivity = async (req, res) => {
|
||||
try {
|
||||
const { name, code, description, durationText, duration, imageLink } = req.body;
|
||||
const predefinedActivity = await predefinedActivityService.createPredefinedActivity({ name, code, description, durationText, duration, imageLink });
|
||||
const { name, code, description, durationText, duration, imageLink, drawingData } = req.body;
|
||||
const predefinedActivity = await predefinedActivityService.createPredefinedActivity({ name, code, description, durationText, duration, imageLink, drawingData });
|
||||
res.status(201).json(predefinedActivity);
|
||||
} catch (error) {
|
||||
console.error('[createPredefinedActivity] - Error:', error);
|
||||
@@ -42,8 +42,8 @@ export const getPredefinedActivityById = async (req, res) => {
|
||||
export const updatePredefinedActivity = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const { name, code, description, durationText, duration, imageLink } = req.body;
|
||||
const updatedActivity = await predefinedActivityService.updatePredefinedActivity(id, { name, code, description, durationText, duration, imageLink });
|
||||
const { name, code, description, durationText, duration, imageLink, drawingData } = req.body;
|
||||
const updatedActivity = await predefinedActivityService.updatePredefinedActivity(id, { name, code, description, durationText, duration, imageLink, drawingData });
|
||||
res.status(200).json(updatedActivity);
|
||||
} catch (error) {
|
||||
console.error('[updatePredefinedActivity] - Error:', error);
|
||||
|
||||
@@ -33,10 +33,15 @@ export const uploadPredefinedActivityImage = async (req, res) => {
|
||||
.jpeg({ quality: 85 })
|
||||
.toFile(filePath);
|
||||
|
||||
// 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,
|
||||
imagePath: filePath,
|
||||
mimeType: 'image/jpeg',
|
||||
drawingData: drawingData ? JSON.stringify(drawingData) : null,
|
||||
});
|
||||
|
||||
// Optional: als imageLink am Activity-Datensatz setzen
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
-- Migration: Add drawing_data column to predefined_activity_images table
|
||||
-- Date: 2025-09-22
|
||||
-- Description: Adds drawing_data column to store Court Drawing Tool metadata
|
||||
|
||||
ALTER TABLE `predefined_activity_images`
|
||||
ADD COLUMN `drawing_data` TEXT NULL
|
||||
COMMENT 'JSON string containing drawing metadata for Court Drawing Tool'
|
||||
AFTER `mime_type`;
|
||||
|
||||
-- Verify the column was added
|
||||
DESCRIBE `predefined_activity_images`;
|
||||
@@ -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;
|
||||
|
||||
@@ -19,6 +19,11 @@ const PredefinedActivity = sequelize.define('PredefinedActivity', {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
},
|
||||
drawingData: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'JSON string with metadata for Court Drawing Tool'
|
||||
},
|
||||
durationText: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
|
||||
@@ -19,6 +19,11 @@ const PredefinedActivityImage = sequelize.define('PredefinedActivityImage', {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
drawingData: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true,
|
||||
comment: 'JSON string containing drawing metadata for Court Drawing Tool'
|
||||
},
|
||||
}, {
|
||||
tableName: 'predefined_activity_images',
|
||||
timestamps: true,
|
||||
|
||||
@@ -33,6 +33,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' });
|
||||
@@ -204,6 +205,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,
|
||||
@@ -239,4 +243,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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -23,6 +23,7 @@ router.get('/', authenticate, getAllPredefinedActivities);
|
||||
router.get('/:id', authenticate, getPredefinedActivityById);
|
||||
router.put('/:id', authenticate, updatePredefinedActivity);
|
||||
router.post('/:id/image', authenticate, upload.single('image'), uploadPredefinedActivityImage);
|
||||
router.put('/:id/image', authenticate, upload.single('image'), uploadPredefinedActivityImage);
|
||||
router.delete('/:id/image/:imageId', authenticate, deletePredefinedActivityImage);
|
||||
router.get('/search/query', authenticate, searchPredefinedActivities);
|
||||
router.post('/merge', authenticate, mergePredefinedActivities);
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
DiaryNote, DiaryTag, MemberDiaryTag, DiaryDateTag, DiaryMemberNote, DiaryMemberTag,
|
||||
PredefinedActivity, PredefinedActivityImage, DiaryDateActivity, DiaryMemberActivity, Match, League, Team, 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,7 @@ 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';
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
@@ -40,7 +41,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 +78,7 @@ 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(express.static(path.join(__dirname, '../frontend/dist')));
|
||||
|
||||
@@ -171,6 +178,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}`);
|
||||
|
||||
@@ -33,11 +33,11 @@ const login = async (email, password) => {
|
||||
if (!user || !(await bcrypt.compare(password, user.password))) {
|
||||
throw { status: 401, message: 'Ungültige Anmeldedaten' };
|
||||
}
|
||||
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' });
|
||||
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '3h' });
|
||||
await UserToken.create({
|
||||
userId: user.id,
|
||||
token,
|
||||
expiresAt: new Date(Date.now() + 3600 * 1000),
|
||||
expiresAt: new Date(Date.now() + 3 * 3600 * 1000),
|
||||
});
|
||||
return { token };
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import DiaryDateActivity from '../models/DiaryDateActivity.js';
|
||||
import GroupActivity from '../models/GroupActivity.js';
|
||||
import Group from '../models/Group.js';
|
||||
import PredefinedActivity from '../models/PredefinedActivity.js';
|
||||
import PredefinedActivityImage from '../models/PredefinedActivityImage.js';
|
||||
import { checkAccess } from '../utils/userUtils.js';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
@@ -12,15 +13,47 @@ class DiaryDateActivityService {
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log('[DiaryDateActivityService::createActivity] - add: ', data);
|
||||
const { activity, ...restData } = data;
|
||||
let predefinedActivity = await PredefinedActivity.findOne({ where: { name: data.activity } });
|
||||
// Versuche, die PredefinedActivity robust zu finden:
|
||||
// 1) per übergebener ID
|
||||
// 2) per Name ODER Code (das Feld "activity" kann Kürzel oder Name sein)
|
||||
// 3) erst dann neu anlegen
|
||||
let predefinedActivity = null;
|
||||
|
||||
if (data.predefinedActivityId) {
|
||||
predefinedActivity = await PredefinedActivity.findByPk(data.predefinedActivityId);
|
||||
}
|
||||
|
||||
if (!predefinedActivity) {
|
||||
const normalized = (data.activity || '').trim();
|
||||
if (normalized) {
|
||||
predefinedActivity = await PredefinedActivity.findOne({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ name: normalized },
|
||||
{ code: normalized }
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!predefinedActivity) {
|
||||
predefinedActivity = await PredefinedActivity.create({
|
||||
name: data.activity,
|
||||
description: '',
|
||||
duration: data.duration
|
||||
name: data.name || data.activity || '',
|
||||
code: data.code || (data.activity || ''),
|
||||
description: data.description || '',
|
||||
duration: data.duration && data.duration !== '' ? parseInt(data.duration) : null
|
||||
});
|
||||
}
|
||||
restData.predefinedActivityId = predefinedActivity.id;
|
||||
|
||||
// Bereinige duration-Feld für DiaryDateActivity
|
||||
if (restData.duration === '' || restData.duration === undefined) {
|
||||
restData.duration = null;
|
||||
} else if (typeof restData.duration === 'string') {
|
||||
restData.duration = parseInt(restData.duration);
|
||||
}
|
||||
|
||||
const maxOrderId = await DiaryDateActivity.max('orderId', {
|
||||
where: { diaryDateId: data.diaryDateId }
|
||||
});
|
||||
@@ -54,8 +87,8 @@ class DiaryDateActivityService {
|
||||
console.log('[DiaryDateActivityService::updateActivity] - creating new PredefinedActivity');
|
||||
predefinedActivity = await PredefinedActivity.create({
|
||||
name: data.customActivityName,
|
||||
description: '',
|
||||
duration: data.duration || activity.duration
|
||||
description: data.description || '',
|
||||
duration: data.duration && data.duration !== '' ? parseInt(data.duration) : (activity.duration || null)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -131,9 +164,7 @@ class DiaryDateActivityService {
|
||||
}
|
||||
|
||||
async getActivities(userToken, clubId, diaryDateId) {
|
||||
console.log('[DiaryDateActivityService::getActivities] - check user access');
|
||||
await checkAccess(userToken, clubId);
|
||||
console.log(`[DiaryDateActivityService::getActivities] - fetch activities for diaryDateId: ${diaryDateId}`);
|
||||
const activities = await DiaryDateActivity.findAll({
|
||||
where: { diaryDateId },
|
||||
order: [['orderId', 'ASC']],
|
||||
@@ -141,6 +172,12 @@ class DiaryDateActivityService {
|
||||
{
|
||||
model: PredefinedActivity,
|
||||
as: 'predefinedActivity',
|
||||
include: [
|
||||
{
|
||||
model: PredefinedActivityImage,
|
||||
as: 'images'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
model: GroupActivity,
|
||||
@@ -158,8 +195,64 @@ class DiaryDateActivityService {
|
||||
}
|
||||
]
|
||||
});
|
||||
console.log(`[DiaryDateActivityService::getActivities] - found ${activities.length} activities`);
|
||||
return activities;
|
||||
|
||||
// Füge imageUrl zu jeder PredefinedActivity hinzu
|
||||
const activitiesWithImages = await Promise.all(activities.map(async activity => {
|
||||
// Konvertiere zu JSON und zurück, um alle Eigenschaften zu serialisieren
|
||||
const activityData = activity.toJSON();
|
||||
|
||||
if (activityData.predefinedActivity) {
|
||||
// Hole die erste verfügbare Image-ID direkt aus der Datenbank
|
||||
const allImages = await PredefinedActivityImage.findAll({
|
||||
where: { predefinedActivityId: activityData.predefinedActivity.id },
|
||||
order: [['createdAt', 'ASC']]
|
||||
});
|
||||
|
||||
const firstImage = allImages.length > 0 ? allImages[0] : null;
|
||||
|
||||
// Füge Zeichnungsdaten hinzu, falls vorhanden
|
||||
if (firstImage && firstImage.drawingData) {
|
||||
try {
|
||||
activityData.predefinedActivity.drawingData = JSON.parse(firstImage.drawingData);
|
||||
} catch (error) {
|
||||
console.error(`Activity ${activityData.predefinedActivity.id}: Error parsing drawingData:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
if (firstImage) {
|
||||
// Füge sowohl imageUrl als auch imageLink mit Image-ID hinzu
|
||||
activityData.predefinedActivity.imageUrl = `/api/predefined-activities/${activityData.predefinedActivity.id}/image/${firstImage.id}`;
|
||||
activityData.predefinedActivity.imageLink = `/api/predefined-activities/${activityData.predefinedActivity.id}/image/${firstImage.id}`;
|
||||
} else {
|
||||
// Fallback: Verwende den Basis-Pfad ohne Image-ID
|
||||
activityData.predefinedActivity.imageUrl = `/api/predefined-activities/${activityData.predefinedActivity.id}/image`;
|
||||
activityData.predefinedActivity.imageLink = `/api/predefined-activities/${activityData.predefinedActivity.id}/image`;
|
||||
}
|
||||
}
|
||||
|
||||
// Auch für GroupActivities
|
||||
if (activityData.groupActivities && activityData.groupActivities.length > 0) {
|
||||
for (const groupActivity of activityData.groupActivities) {
|
||||
if (groupActivity.groupPredefinedActivity) {
|
||||
// Hole die erste verfügbare Image-ID direkt aus der Datenbank
|
||||
const firstImage = await PredefinedActivityImage.findOne({
|
||||
where: { predefinedActivityId: groupActivity.groupPredefinedActivity.id },
|
||||
order: [['createdAt', 'ASC']]
|
||||
});
|
||||
|
||||
if (firstImage) {
|
||||
groupActivity.groupPredefinedActivity.imageUrl = `/api/predefined-activities/${groupActivity.groupPredefinedActivity.id}/image/${firstImage.id}`;
|
||||
groupActivity.groupPredefinedActivity.imageLink = `/api/predefined-activities/${groupActivity.groupPredefinedActivity.id}/image/${firstImage.id}`;
|
||||
} else {
|
||||
groupActivity.groupPredefinedActivity.imageUrl = `/api/predefined-activities/${groupActivity.groupPredefinedActivity.id}/image`;
|
||||
groupActivity.groupPredefinedActivity.imageLink = `/api/predefined-activities/${groupActivity.groupPredefinedActivity.id}/image`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return activityData;
|
||||
}));
|
||||
return activitiesWithImages;
|
||||
}
|
||||
|
||||
async addGroupActivity(userToken, clubId, diaryDateId, groupId, activity) {
|
||||
|
||||
@@ -54,7 +54,7 @@ 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);
|
||||
@@ -77,6 +77,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,6 +94,8 @@ class MemberService {
|
||||
testMembership: testMembership,
|
||||
picsInInternetAllowed: picsInInternetAllowed,
|
||||
gender: gender || 'unknown',
|
||||
ttr: ttr,
|
||||
qttr: qttr,
|
||||
});
|
||||
}
|
||||
console.log('[setClubMembers] - return response');
|
||||
@@ -146,6 +150,189 @@ class MemberService {
|
||||
return { status: 500, error: 'Failed to retrieve image' };
|
||||
}
|
||||
}
|
||||
|
||||
async updateRatingsFromMyTischtennis(userToken, clubId) {
|
||||
console.log('[updateRatingsFromMyTischtennis] - Check access');
|
||||
await checkAccess(userToken, clubId);
|
||||
|
||||
const user = await getUserByToken(userToken);
|
||||
console.log('[updateRatingsFromMyTischtennis] - User:', user.id);
|
||||
|
||||
const myTischtennisService = (await import('./myTischtennisService.js')).default;
|
||||
const myTischtennisClient = (await import('../clients/myTischtennisClient.js')).default;
|
||||
|
||||
try {
|
||||
// 1. myTischtennis-Session abrufen
|
||||
console.log('[updateRatingsFromMyTischtennis] - Get session for user', user.id);
|
||||
const session = await myTischtennisService.getSession(user.id);
|
||||
console.log('[updateRatingsFromMyTischtennis] - Session retrieved:', {
|
||||
hasAccessToken: !!session.accessToken,
|
||||
hasCookie: !!session.cookie,
|
||||
expiresAt: session.expiresAt
|
||||
});
|
||||
|
||||
const account = await myTischtennisService.getAccount(user.id);
|
||||
console.log('[updateRatingsFromMyTischtennis] - Account data:', {
|
||||
id: account?.id,
|
||||
email: account?.email,
|
||||
clubId: account?.clubId,
|
||||
clubName: account?.clubName,
|
||||
fedNickname: account?.fedNickname,
|
||||
hasSession: !!(account?.accessToken)
|
||||
});
|
||||
|
||||
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
|
||||
console.log('[updateRatingsFromMyTischtennis] - Get club rankings', {
|
||||
clubId: account.clubId,
|
||||
fedNickname: account.fedNickname,
|
||||
hasCookie: !!session.cookie
|
||||
});
|
||||
|
||||
const rankings = await myTischtennisClient.getClubRankings(
|
||||
session.cookie,
|
||||
account.clubId,
|
||||
account.fedNickname
|
||||
);
|
||||
|
||||
console.log('[updateRatingsFromMyTischtennis] - Rankings result:', {
|
||||
success: rankings.success,
|
||||
entriesCount: rankings.entries?.length || 0,
|
||||
error: rankings.error
|
||||
});
|
||||
|
||||
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
|
||||
console.log('[updateRatingsFromMyTischtennis] - Load club members for clubId:', clubId);
|
||||
const members = await Member.findAll({ where: { clubId } });
|
||||
console.log('[updateRatingsFromMyTischtennis] - Found members:', members.length);
|
||||
|
||||
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
|
||||
});
|
||||
console.log(`[updateRatingsFromMyTischtennis] - Updated ${firstName} ${lastName}: TTR ${oldTtr} → ${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}`);
|
||||
console.log(`[updateRatingsFromMyTischtennis] - Not found in rankings: ${firstName} ${lastName}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[updateRatingsFromMyTischtennis] - Update complete');
|
||||
console.log(`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();
|
||||
256
backend/services/myTischtennisService.js
Normal file
256
backend/services/myTischtennisService.js
Normal file
@@ -0,0 +1,256 @@
|
||||
import MyTischtennis from '../models/MyTischtennis.js';
|
||||
import User from '../models/User.js';
|
||||
import myTischtennisClient from '../clients/myTischtennisClient.js';
|
||||
import HttpError from '../exceptions/HttpError.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
|
||||
console.log('[myTischtennisService] - Getting user profile...');
|
||||
const profileResult = await myTischtennisClient.getUserProfile(loginResult.cookie);
|
||||
console.log('[myTischtennisService] - Profile result:', {
|
||||
success: profileResult.success,
|
||||
clubId: profileResult.clubId,
|
||||
clubName: profileResult.clubName,
|
||||
fedNickname: profileResult.fedNickname
|
||||
});
|
||||
|
||||
if (profileResult.success) {
|
||||
account.clubId = profileResult.clubId;
|
||||
account.clubName = profileResult.clubName;
|
||||
account.fedNickname = profileResult.fedNickname;
|
||||
console.log('[myTischtennisService] - Updated account with club data');
|
||||
} 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
|
||||
console.log('[myTischtennisService] - Getting user profile...');
|
||||
const profileResult = await myTischtennisClient.getUserProfile(loginResult.cookie);
|
||||
console.log('[myTischtennisService] - Profile result:', {
|
||||
success: profileResult.success,
|
||||
clubId: profileResult.clubId,
|
||||
clubName: profileResult.clubName,
|
||||
fedNickname: profileResult.fedNickname
|
||||
});
|
||||
|
||||
if (profileResult.success) {
|
||||
account.clubId = profileResult.clubId;
|
||||
account.clubName = profileResult.clubName;
|
||||
account.fedNickname = profileResult.fedNickname;
|
||||
console.log('[myTischtennisService] - Updated account with club data');
|
||||
} 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();
|
||||
|
||||
@@ -15,6 +15,7 @@ class PredefinedActivityService {
|
||||
durationText: data.durationText,
|
||||
duration: data.duration,
|
||||
imageLink: data.imageLink,
|
||||
drawingData: data.drawingData ? JSON.stringify(data.drawingData) : null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -32,6 +33,7 @@ class PredefinedActivityService {
|
||||
durationText: data.durationText,
|
||||
duration: data.duration,
|
||||
imageLink: data.imageLink,
|
||||
drawingData: data.drawingData ? JSON.stringify(data.drawingData) : null,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,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']]
|
||||
});
|
||||
|
||||
@@ -67,6 +67,16 @@
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<nav class="nav-menu">
|
||||
<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>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<button @click="logout()" class="btn-secondary logout-btn">
|
||||
|
||||
442
frontend/src/components/CourtDrawingRender.vue
Normal file
442
frontend/src/components/CourtDrawingRender.vue
Normal file
@@ -0,0 +1,442 @@
|
||||
<template>
|
||||
<div class="render-container">
|
||||
<canvas
|
||||
ref="canvas"
|
||||
:width="config.canvas.width"
|
||||
:height="config.canvas.height"
|
||||
style="margin:0 auto;"
|
||||
></canvas>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CourtDrawingRender',
|
||||
props: {
|
||||
drawingData: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
width: {
|
||||
type: Number,
|
||||
default: 600
|
||||
},
|
||||
height: {
|
||||
type: Number,
|
||||
default: 400
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
canvas: null,
|
||||
ctx: null,
|
||||
config: {
|
||||
// Mehr Platz links für die drei Startkreise (Rand außerhalb des Tisches)
|
||||
canvas: { width: 640, height: 400 },
|
||||
table: {
|
||||
// Tabelle etwas schmaler und weiter rechts platzieren
|
||||
width: 500,
|
||||
height: 300,
|
||||
color: '#2d5a2d',
|
||||
borderWidth: 2,
|
||||
borderColor: '#000000',
|
||||
outerFrameWidth: 5,
|
||||
outerFrameColor: '#ffffff',
|
||||
outerFrameBorderWidth: 0.5,
|
||||
outerFrameBorderColor: '#000000'
|
||||
},
|
||||
horizontalLine: { color: '#cfd8dc', width: 2, gap: 10, edgeMargin: 10 },
|
||||
net: { color: '#ffffff', width: 4, overhang: 14, borderColor: '#000000', borderWidth: 0.5 },
|
||||
serviceLine: { color: 'rgba(255,255,255,0.0)', width: 0 },
|
||||
startCircles: {
|
||||
radius: 10,
|
||||
// Abstand vom linken Canvas-Rand (größer = weiter links, da wir von tableX abziehen)
|
||||
x: 20,
|
||||
topYOffset: 35,
|
||||
bottomYOffset: 35,
|
||||
selectedColor: '#ff4444',
|
||||
unselectedColor: '#9e9e9e',
|
||||
selectedBorderColor: '#ffffff',
|
||||
unselectedBorderColor: '#555555',
|
||||
selectedBorderWidth: 2,
|
||||
unselectedBorderWidth: 1
|
||||
},
|
||||
arrows: {
|
||||
primaryColor: '#d32f2f', // rechts -> target (rot)
|
||||
secondaryColor: '#1565c0', // zurück (blau)
|
||||
width: 6,
|
||||
headLength: 24,
|
||||
vhOffsetX: 5,
|
||||
vhOffsetY: 8,
|
||||
rhOffsetX: 10,
|
||||
rhOffsetY: 0
|
||||
},
|
||||
targetCircles: {
|
||||
radius: 13,
|
||||
rightXOffset: 20,
|
||||
middleXOffset: 40,
|
||||
topYOffset: 45,
|
||||
bottomYOffset: 45,
|
||||
transparency: 0.25
|
||||
},
|
||||
leftTargetCircles: {
|
||||
radius: 10,
|
||||
leftXOffset: 20,
|
||||
rightXOffset: 40,
|
||||
topYOffset: 40,
|
||||
bottomYOffset: 40
|
||||
},
|
||||
hitMarker: {
|
||||
radius: 10.5,
|
||||
fill: '#ffffff',
|
||||
stroke: '#000000',
|
||||
lineWidth: 1
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
drawingData: {
|
||||
handler() {
|
||||
this.redraw();
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// apply custom dimensions if provided
|
||||
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();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
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
|
||||
this.ctx.clearRect(0, 0, width, height);
|
||||
this.ctx.fillStyle = '#f0f0f0';
|
||||
this.ctx.fillRect(0, 0, width, height);
|
||||
this.drawTable();
|
||||
this.drawStartCircles();
|
||||
this.drawArrowsAndLabels();
|
||||
},
|
||||
drawTable() {
|
||||
const ctx = this.ctx;
|
||||
const { table, horizontalLine, net, outerFrameWidth } = {
|
||||
...this.config,
|
||||
outerFrameWidth: this.config.table.outerFrameWidth
|
||||
};
|
||||
const tableWidth = this.config.table.width;
|
||||
const tableHeight = this.config.table.height;
|
||||
const tableX = (this.config.canvas.width - tableWidth) / 2;
|
||||
const tableY = (this.config.canvas.height - tableHeight) / 2;
|
||||
|
||||
// table fill
|
||||
ctx.fillStyle = table.color;
|
||||
ctx.fillRect(tableX, tableY, tableWidth, tableHeight);
|
||||
|
||||
// table stroke
|
||||
ctx.strokeStyle = table.borderColor;
|
||||
ctx.lineWidth = table.borderWidth;
|
||||
ctx.strokeRect(tableX, tableY, tableWidth, tableHeight);
|
||||
|
||||
// outer white frame
|
||||
ctx.strokeStyle = table.outerFrameColor;
|
||||
ctx.lineWidth = table.outerFrameWidth;
|
||||
ctx.strokeRect(
|
||||
tableX - table.outerFrameWidth,
|
||||
tableY - table.outerFrameWidth,
|
||||
tableWidth + table.outerFrameWidth * 2,
|
||||
tableHeight + table.outerFrameWidth * 2
|
||||
);
|
||||
|
||||
// black thin frame around white
|
||||
ctx.strokeStyle = table.outerFrameBorderColor;
|
||||
ctx.lineWidth = table.outerFrameBorderWidth;
|
||||
ctx.strokeRect(
|
||||
tableX - table.outerFrameWidth - table.outerFrameBorderWidth,
|
||||
tableY - table.outerFrameWidth - table.outerFrameBorderWidth,
|
||||
tableWidth + (table.outerFrameWidth + table.outerFrameBorderWidth) * 2,
|
||||
tableHeight + (table.outerFrameWidth + table.outerFrameBorderWidth) * 2
|
||||
);
|
||||
|
||||
// horizontal split line (two segments)
|
||||
ctx.strokeStyle = horizontalLine.color;
|
||||
ctx.lineWidth = horizontalLine.width;
|
||||
const centerY = tableY + tableHeight / 2;
|
||||
const centerX = tableX + tableWidth / 2;
|
||||
const gap = horizontalLine.gap;
|
||||
const edge = horizontalLine.edgeMargin;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(tableX + edge, centerY);
|
||||
ctx.lineTo(centerX - gap, centerY);
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(centerX + gap, centerY);
|
||||
ctx.lineTo(tableX + tableWidth - edge, centerY);
|
||||
ctx.stroke();
|
||||
|
||||
// net vertical
|
||||
ctx.strokeStyle = net.color;
|
||||
ctx.lineWidth = net.width;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(centerX, tableY - net.overhang);
|
||||
ctx.lineTo(centerX, tableY + tableHeight + net.overhang);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.strokeStyle = net.borderColor;
|
||||
ctx.lineWidth = net.borderWidth;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(centerX, tableY - net.overhang);
|
||||
ctx.lineTo(centerX, tableY + tableHeight + net.overhang);
|
||||
ctx.stroke();
|
||||
},
|
||||
drawStartCircles() {
|
||||
const ctx = this.ctx;
|
||||
const cfg = this.config.startCircles;
|
||||
const tableWidth = this.config.table.width;
|
||||
const tableHeight = this.config.table.height;
|
||||
const tableX = (this.config.canvas.width - tableWidth) / 2;
|
||||
const tableY = (this.config.canvas.height - tableHeight) / 2;
|
||||
|
||||
// Startkreis links VOR dem Tisch positionieren – nur den Kreis zeichnen,
|
||||
// von dem auch der rote Pfeil startet
|
||||
const circleX = tableX - cfg.x; // Links vom Tisch
|
||||
const topY = tableY + cfg.topYOffset;
|
||||
const midY = tableY + tableHeight / 2;
|
||||
const botY = tableY + tableHeight - cfg.bottomYOffset;
|
||||
|
||||
// Mapping und Fallback: bei "AS" auf 'middle'
|
||||
const map = { AS1: 'top', AS2: 'middle', AS3: 'bottom', AS: 'middle' };
|
||||
const selKey = this.drawingData?.selectedStartPosition || 'AS';
|
||||
const selectedPos = map[selKey] || 'middle';
|
||||
const y = selectedPos === 'top' ? topY : selectedPos === 'bottom' ? botY : midY;
|
||||
|
||||
ctx.fillStyle = cfg.selectedColor;
|
||||
ctx.beginPath();
|
||||
ctx.arc(circleX, y, cfg.radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = cfg.selectedBorderColor;
|
||||
ctx.lineWidth = cfg.selectedBorderWidth;
|
||||
ctx.stroke();
|
||||
},
|
||||
computeRightTargetPosition(number) {
|
||||
// Geometrie wie im Tool: nutzt rightXOffset/middleXOffset
|
||||
const tableWidth = this.config.table.width;
|
||||
const tableHeight = this.config.table.height;
|
||||
const tableX = (this.config.canvas.width - tableWidth) / 2;
|
||||
const tableY = (this.config.canvas.height - tableHeight) / 2;
|
||||
const cfg = this.config.targetCircles;
|
||||
|
||||
const x1 = tableX + tableWidth - cfg.rightXOffset; // (1,2,3)
|
||||
const x3 = tableX + tableWidth / 2 + cfg.middleXOffset; // (7,8,9)
|
||||
const xdiff = x3 - x1;
|
||||
const x2 = x3 - xdiff / 2; // (4,5,6)
|
||||
|
||||
const positions = {
|
||||
1: { x: x1, y: tableY + cfg.topYOffset },
|
||||
2: { x: x1, y: tableY + tableHeight / 2 },
|
||||
3: { x: x1, y: tableY + tableHeight - cfg.bottomYOffset },
|
||||
4: { x: x2, y: tableY + cfg.topYOffset },
|
||||
5: { x: x2, y: tableY + tableHeight / 2 },
|
||||
6: { x: x2, y: tableY + tableHeight - cfg.bottomYOffset },
|
||||
7: { x: x3, y: tableY + cfg.topYOffset },
|
||||
8: { x: x3, y: tableY + tableHeight / 2 },
|
||||
9: { x: x3, y: tableY + tableHeight - cfg.bottomYOffset }
|
||||
};
|
||||
return positions[number] || null;
|
||||
},
|
||||
getStartPoint() {
|
||||
// Startpunkt wie im Tool abhängig von Schlagseite (VH/RH)
|
||||
const sc = this.config.startCircles;
|
||||
const ar = this.config.arrows;
|
||||
const tblW = this.config.table.width;
|
||||
const tblH = this.config.table.height;
|
||||
const tableX = (this.config.canvas.width - tblW) / 2;
|
||||
const tableY = (this.config.canvas.height - tblH) / 2;
|
||||
const circleX = tableX - sc.x; // Kreis links vor dem Tisch
|
||||
const map = { AS1: 'top', AS2: 'middle', AS3: 'bottom', AS: 'middle' };
|
||||
const pos = map[this.drawingData?.selectedStartPosition] || 'middle';
|
||||
const y = pos === 'top' ? tableY + sc.topYOffset : pos === 'bottom' ? tableY + tblH - sc.bottomYOffset : tableY + tblH / 2;
|
||||
const isVH = (this.drawingData?.strokeType || 'VH') === 'VH';
|
||||
const startX = isVH
|
||||
? circleX + sc.radius + ar.vhOffsetX
|
||||
: circleX + sc.radius + ar.rhOffsetX;
|
||||
// VH: unterhalb seitlich (circleRadius + vhOffsetY), RH: rechts (rhOffsetY = 0)
|
||||
const startYOffset = isVH ? (sc.radius + ar.vhOffsetY) : ar.rhOffsetY;
|
||||
return { x: startX, y: y + startYOffset };
|
||||
},
|
||||
drawArrow(ctx, from, to, color, label) {
|
||||
const { width, headLength } = this.config.arrows;
|
||||
ctx.strokeStyle = color;
|
||||
ctx.fillStyle = color;
|
||||
ctx.lineWidth = width;
|
||||
// line
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(from.x, from.y);
|
||||
ctx.lineTo(to.x, to.y);
|
||||
ctx.stroke();
|
||||
// arrow head
|
||||
const angle = Math.atan2(to.y - from.y, to.x - from.x);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(to.x, to.y);
|
||||
ctx.lineTo(to.x - headLength * Math.cos(angle - Math.PI / 6), to.y - headLength * Math.sin(angle - Math.PI / 6));
|
||||
ctx.lineTo(to.x - headLength * Math.cos(angle + Math.PI / 6), to.y - headLength * Math.sin(angle + Math.PI / 6));
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
},
|
||||
drawLabelBelow(ctx, text, anchor) {
|
||||
if (!text) return;
|
||||
ctx.font = 'bold 13px Helvetica';
|
||||
const padding = 6;
|
||||
const yOffset = 23; // 5px tiefer
|
||||
const metrics = ctx.measureText(text);
|
||||
const textWidth = metrics.width;
|
||||
let x = anchor.x - textWidth / 2; // zentriert unter dem Punkt
|
||||
let y = anchor.y + yOffset;
|
||||
// clamp innerhalb Canvas
|
||||
x = Math.max(4, Math.min(this.config.canvas.width - textWidth - 4, x));
|
||||
y = Math.max(14, Math.min(this.config.canvas.height - 4, y));
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeText(text, x, y);
|
||||
ctx.fillText(text, x, y);
|
||||
},
|
||||
drawArrowsAndLabels() {
|
||||
if (!this.drawingData) return;
|
||||
const ctx = this.ctx;
|
||||
const from = this.getStartPoint();
|
||||
|
||||
// First arrow: to right target
|
||||
const tp = Number(this.drawingData.targetPosition);
|
||||
if (tp) {
|
||||
const to = this.computeRightTargetPosition(tp);
|
||||
// Zielmarkierung (unter dem Pfeilkopf)
|
||||
this.drawHitMarker(ctx, to);
|
||||
const strokeSide = this.drawingData.strokeType || '';
|
||||
const spinAbbrev = this.abbrevSpin(this.drawingData.spinType);
|
||||
// Text gehört an die Quelle (ohne "target")
|
||||
const sourceLabel = `${strokeSide} ${spinAbbrev}`.trim();
|
||||
const toEnd = { x: to.x - this.config.targetCircles.radius, y: to.y };
|
||||
this.drawArrow(ctx, from, toEnd, this.config.arrows.primaryColor);
|
||||
// Unter dem Startkreis beschriften
|
||||
const startCenter = this.getStartCircleCenter();
|
||||
this.drawLabelBelow(ctx, sourceLabel, startCenter);
|
||||
}
|
||||
|
||||
// Second arrow (optional): from right source to left target
|
||||
const leftTarget = this.drawingData.nextStrokeTargetPosition ? Number(this.drawingData.nextStrokeTargetPosition) : null;
|
||||
if (tp && leftTarget) {
|
||||
// source near previous right target
|
||||
const sourceRightCenter = this.computeRightTargetPosition(tp);
|
||||
// left target mapping: mirror scheme to left half
|
||||
const toLeftCenter = this.computeLeftTargetPosition(leftTarget);
|
||||
// Zielmarkierung links
|
||||
this.drawHitMarker(ctx, toLeftCenter);
|
||||
const side = this.drawingData.nextStrokeSide || '';
|
||||
const type = this.drawingData.nextStrokeType || '';
|
||||
// Text gehört ans Ziel (ohne "extra target")
|
||||
const targetLabel = `${side} ${type}`.trim();
|
||||
const sourceRight = { x: sourceRightCenter.x - this.config.targetCircles.radius, y: sourceRightCenter.y };
|
||||
const toLeft = { x: toLeftCenter.x + this.config.leftTargetCircles.radius, y: toLeftCenter.y };
|
||||
this.drawArrow(ctx, sourceRight, toLeft, this.config.arrows.secondaryColor);
|
||||
// Unter dem rechten Ziel (target der ersten Linie) beschriften
|
||||
this.drawLabelBelow(ctx, targetLabel, sourceRightCenter);
|
||||
}
|
||||
},
|
||||
getStartCircleCenter() {
|
||||
const cfg = this.config.startCircles;
|
||||
const tableWidth = this.config.table.width;
|
||||
const tableHeight = this.config.table.height;
|
||||
const tableX = (this.config.canvas.width - tableWidth) / 2;
|
||||
const tableY = (this.config.canvas.height - tableHeight) / 2;
|
||||
const circleX = tableX - cfg.x;
|
||||
const map = { AS1: 'top', AS2: 'middle', AS3: 'bottom', AS: 'middle' };
|
||||
const selKey = this.drawingData?.selectedStartPosition || 'AS';
|
||||
const selectedPos = map[selKey] || 'middle';
|
||||
const topY = tableY + cfg.topYOffset;
|
||||
const midY = tableY + tableHeight / 2;
|
||||
const botY = tableY + tableHeight - cfg.bottomYOffset;
|
||||
const y = selectedPos === 'top' ? topY : selectedPos === 'bottom' ? botY : midY;
|
||||
return { x: circleX, y };
|
||||
},
|
||||
drawHitMarker(ctx, pos) {
|
||||
if (!pos) return;
|
||||
const mk = this.config.hitMarker;
|
||||
ctx.beginPath();
|
||||
ctx.arc(pos.x, pos.y, mk.radius, 0, Math.PI * 2);
|
||||
ctx.fillStyle = mk.fill;
|
||||
ctx.fill();
|
||||
ctx.lineWidth = mk.lineWidth;
|
||||
ctx.strokeStyle = mk.stroke;
|
||||
ctx.stroke();
|
||||
},
|
||||
computeLeftTargetPosition(number) {
|
||||
// Spiegelung wie im Tool: nutzt leftTargetCircles Offsets
|
||||
const tableWidth = this.config.table.width;
|
||||
const tableHeight = this.config.table.height;
|
||||
const tableX = (this.config.canvas.width - tableWidth) / 2;
|
||||
const tableY = (this.config.canvas.height - tableHeight) / 2;
|
||||
const cfg = this.config.leftTargetCircles;
|
||||
|
||||
const x1 = tableX + cfg.leftXOffset; // linke Spalte (Lang)
|
||||
const x3 = tableX + tableWidth / 2 - cfg.rightXOffset; // nah am Netz (Kurz)
|
||||
const xdiff = x3 - x1;
|
||||
const x2 = x3 - xdiff / 2;
|
||||
|
||||
// Gespiegelte Y-Zuordnung wie im Tool:
|
||||
// 1,4,7 = unten (VH gespiegelt)
|
||||
// 2,5,8 = mitte
|
||||
// 3,6,9 = oben (RH gespiegelt)
|
||||
const positions = {
|
||||
1: { x: x1, y: tableY + tableHeight - cfg.bottomYOffset },
|
||||
2: { x: x1, y: tableY + tableHeight / 2 },
|
||||
3: { x: x1, y: tableY + cfg.topYOffset },
|
||||
4: { x: x2, y: tableY + tableHeight - cfg.bottomYOffset },
|
||||
5: { x: x2, y: tableY + tableHeight / 2 },
|
||||
6: { x: x2, y: tableY + cfg.topYOffset },
|
||||
7: { x: x3, y: tableY + tableHeight - cfg.bottomYOffset },
|
||||
8: { x: x3, y: tableY + tableHeight / 2 },
|
||||
9: { x: x3, y: tableY + cfg.topYOffset }
|
||||
};
|
||||
return positions[number] || null;
|
||||
},
|
||||
abbrevSpin(spin) {
|
||||
if (!spin) return '';
|
||||
const map = {
|
||||
Unterschnitt: 'US',
|
||||
Überschnitt: 'OS',
|
||||
Seitschnitt: 'SR',
|
||||
Seitunterschnitt: 'SU',
|
||||
Gegenläufer: 'GL'
|
||||
};
|
||||
return map[spin] || spin;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.render-container {
|
||||
width: 100%;
|
||||
}
|
||||
canvas { display: block; max-width: 100%; height: auto; }
|
||||
</style>
|
||||
|
||||
|
||||
1618
frontend/src/components/CourtDrawingTool.vue
Normal file
1618
frontend/src/components/CourtDrawingTool.vue
Normal file
File diff suppressed because it is too large
Load Diff
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>
|
||||
|
||||
@@ -13,6 +13,7 @@ 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 Impressum from './views/Impressum.vue';
|
||||
import Datenschutz from './views/Datenschutz.vue';
|
||||
|
||||
@@ -31,6 +32,7 @@ const routes = [
|
||||
{ path: '/training-stats', component: TrainingStatsView },
|
||||
{ path: '/predefined-activities', component: PredefinedActivities },
|
||||
{ path: '/official-tournaments', component: OfficialTournaments },
|
||||
{ path: '/mytischtennis-account', component: MyTischtennisAccount },
|
||||
{ path: '/impressum', component: Impressum },
|
||||
{ path: '/datenschutz', component: Datenschutz },
|
||||
];
|
||||
|
||||
@@ -126,10 +126,11 @@
|
||||
</span>
|
||||
<span v-else @click="startActivityEdit(item)" class="clickable activity-label"
|
||||
:title="item.predefinedActivity && item.predefinedActivity.name ? item.predefinedActivity.name : ''">
|
||||
<span v-if="item.predefinedActivity && item.predefinedActivity.imageLink"
|
||||
@click.stop="showActivityImage(item.predefinedActivity.imageLink)"
|
||||
class="image-icon"
|
||||
title="Bild anzeigen">🖼️</span>
|
||||
<!-- Icon öffnet Rendering (falls vorhanden) oder Bild im Modal -->
|
||||
<span v-if="item.predefinedActivity"
|
||||
@click.stop="openActivityVisual(item.predefinedActivity)"
|
||||
class="image-icon"
|
||||
title="Bild/Zeichnung anzeigen">🖼️</span>
|
||||
{{ (item.predefinedActivity && item.predefinedActivity.code && item.predefinedActivity.code.trim() !== '')
|
||||
? item.predefinedActivity.code
|
||||
: (item.predefinedActivity ? item.predefinedActivity.name : item.activity) }}
|
||||
@@ -161,10 +162,12 @@
|
||||
<td></td>
|
||||
<td>
|
||||
<span class="activity-label" :title="(groupItem.groupPredefinedActivity && groupItem.groupPredefinedActivity.name) ? groupItem.groupPredefinedActivity.name : ''">
|
||||
<span v-if="groupItem.groupPredefinedActivity && groupItem.groupPredefinedActivity.imageLink"
|
||||
@click.stop="showActivityImage(groupItem.groupPredefinedActivity.imageLink)"
|
||||
class="image-icon"
|
||||
title="Bild anzeigen">🖼️</span>
|
||||
<!-- Icon öffnet Rendering (falls vorhanden) oder Bild im Modal -->
|
||||
<span v-if="groupItem.groupPredefinedActivity"
|
||||
@click.stop="openActivityVisual(groupItem.groupPredefinedActivity)"
|
||||
class="image-icon"
|
||||
title="Bild/Zeichnung anzeigen">🖼️</span>
|
||||
|
||||
{{ (groupItem.groupPredefinedActivity && groupItem.groupPredefinedActivity.code && groupItem.groupPredefinedActivity.code.trim() !== '')
|
||||
? groupItem.groupPredefinedActivity.code
|
||||
: groupItem.groupPredefinedActivity.name }}
|
||||
@@ -319,8 +322,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="showImage" class="memberImage">
|
||||
<img :src="imageUrl" @click="closeImage" />
|
||||
<div v-if="showImage || showRenderModal" class="memberImage" @click="closeImage">
|
||||
<template v-if="showImage">
|
||||
<img :src="imageUrl" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<CourtDrawingRender :drawing-data="renderModalData" :width="560" :height="360" />
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="showAccidentForm" class="accidentForm">
|
||||
<form @submit.prevent="submitAccident">
|
||||
@@ -345,7 +353,7 @@
|
||||
+ accident.accident}}</li>
|
||||
</ul>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Schnell hinzufügen Dialog -->
|
||||
<div v-if="showQuickAddDialog" class="modal-overlay" @click.self="closeQuickAddDialog">
|
||||
@@ -397,10 +405,11 @@ import apiClient from '../apiClient.js';
|
||||
import Multiselect from 'vue-multiselect';
|
||||
import Sortable from 'sortablejs';
|
||||
import PDFGenerator from '../components/PDFGenerator.js';
|
||||
import CourtDrawingRender from '../components/CourtDrawingRender.vue';
|
||||
|
||||
export default {
|
||||
name: 'DiaryView',
|
||||
components: { Multiselect },
|
||||
components: { Multiselect, CourtDrawingRender },
|
||||
data() {
|
||||
return {
|
||||
date: null,
|
||||
@@ -436,6 +445,8 @@ export default {
|
||||
showDropdown: false,
|
||||
showImage: false,
|
||||
imageUrl: '',
|
||||
showRenderModal: false,
|
||||
renderModalData: null,
|
||||
groups: [],
|
||||
currentTimeBlockId: null,
|
||||
newGroupName: '',
|
||||
@@ -526,6 +537,36 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
drawingDataFor(pa) {
|
||||
// Zeichnungsdaten können bereits als Objekt vorliegen oder als JSON-String
|
||||
try {
|
||||
if (!pa) return null;
|
||||
if (pa.drawingData && typeof pa.drawingData === 'object') {
|
||||
console.debug('DiaryView: drawingData (object) gefunden für', pa.id);
|
||||
return pa.drawingData;
|
||||
}
|
||||
if (pa.drawingData && typeof pa.drawingData === 'string') {
|
||||
const parsed = JSON.parse(pa.drawingData);
|
||||
console.debug('DiaryView: drawingData (string→parsed) für', pa.id, parsed);
|
||||
return parsed;
|
||||
}
|
||||
// Fallback: falls über images[0].drawingData geliefert wurde
|
||||
if (pa.images && pa.images.length) {
|
||||
// Nimm das erste Bild, das drawingData enthält
|
||||
const withData = pa.images.find(img => !!img.drawingData);
|
||||
if (withData) {
|
||||
const parsedImg = typeof withData.drawingData === 'string'
|
||||
? JSON.parse(withData.drawingData)
|
||||
: withData.drawingData;
|
||||
console.debug('DiaryView: drawingData aus images für', pa.id, 'imageId=', withData.id, parsedImg);
|
||||
return parsedImg;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('DiaryView: drawingData parse error:', e);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
async init() {
|
||||
if (this.isAuthenticated && this.currentClub) {
|
||||
const response = await apiClient.get(`/diary/${this.currentClub}`);
|
||||
@@ -1119,12 +1160,43 @@ export default {
|
||||
},
|
||||
closeImage() {
|
||||
this.showImage = false;
|
||||
this.showRenderModal = false;
|
||||
this.renderModalData = null;
|
||||
this.imageUrl = '';
|
||||
},
|
||||
|
||||
showActivityImage(imageLink) {
|
||||
this.imageUrl = imageLink;
|
||||
this.showImage = true;
|
||||
async showActivityImage(imageLink) {
|
||||
try {
|
||||
// Vorherige Object-URL freigeben
|
||||
if (this.imageUrl && this.imageUrl.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(this.imageUrl);
|
||||
}
|
||||
|
||||
if (imageLink && imageLink.startsWith('/api/')) {
|
||||
// Über API mit Auth und als Blob laden → vermeidet ORB
|
||||
const path = imageLink.replace(/^\/api\//, '');
|
||||
const resp = await apiClient.get(`/${path}`, { responseType: 'blob' });
|
||||
this.imageUrl = URL.createObjectURL(resp.data);
|
||||
} else {
|
||||
this.imageUrl = imageLink;
|
||||
}
|
||||
this.showImage = true;
|
||||
} catch (e) {
|
||||
console.error('Bild laden fehlgeschlagen:', e);
|
||||
alert('Bild konnte nicht geladen werden.');
|
||||
}
|
||||
},
|
||||
async openActivityVisual(pa) {
|
||||
// Bevorzugt Rendering, ansonsten Bild
|
||||
const data = this.drawingDataFor(pa);
|
||||
if (data) {
|
||||
// Erzeuge temporär ein PNG im Client: Canvas offscreen rendern über Renderer-Komponente ist aufwändig.
|
||||
// Einfacher: öffne ein kleines Modal mit der Renderer-Komponente statt imageUrl.
|
||||
this.renderModalData = data;
|
||||
this.showRenderModal = true;
|
||||
} else if (pa.imageLink) {
|
||||
await this.showActivityImage(pa.imageLink);
|
||||
}
|
||||
},
|
||||
async loadMemberImage(member) {
|
||||
try {
|
||||
@@ -1910,6 +1982,12 @@ img {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.memberImage > div, .memberImage canvas {
|
||||
/* falls Komponenten-Inhalt (Renderer) da ist, mittig zeigen */
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.groups {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -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>
|
||||
|
||||
423
frontend/src/views/MyTischtennisAccount.vue
Normal file
423
frontend/src/views/MyTischtennisAccount.vue
Normal file
@@ -0,0 +1,423 @@
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<h1>myTischtennis-Account</h1>
|
||||
|
||||
<div class="account-container">
|
||||
<div v-if="loading" class="loading">Lade...</div>
|
||||
|
||||
<div v-else-if="account" class="account-info">
|
||||
<div class="info-section">
|
||||
<h2>Verknüpfter Account</h2>
|
||||
|
||||
<div class="info-row">
|
||||
<label>E-Mail:</label>
|
||||
<span>{{ account.email }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<label>Passwort gespeichert:</label>
|
||||
<span>{{ account.savePassword ? 'Ja' : 'Nein' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-if="account.clubId">
|
||||
<label>Verein (myTischtennis):</label>
|
||||
<span>{{ account.clubName }} ({{ account.clubId }}{{ account.fedNickname ? ' - ' + account.fedNickname : '' }})</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-if="account.lastLoginSuccess">
|
||||
<label>Letzter erfolgreicher Login:</label>
|
||||
<span>{{ formatDate(account.lastLoginSuccess) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="info-row" v-if="account.lastLoginAttempt">
|
||||
<label>Letzter Login-Versuch:</label>
|
||||
<span>{{ formatDate(account.lastLoginAttempt) }}</span>
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button class="btn-primary" @click="openEditDialog">Account bearbeiten</button>
|
||||
<button class="btn-secondary" @click="testConnection">Verbindung testen</button>
|
||||
<button class="btn-secondary" @click="testLoginFlow">Test: Login-Flow</button>
|
||||
<button class="btn-danger" @click="deleteAccount">Account trennen</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test-Ausgabe -->
|
||||
<div v-if="testResult" class="test-result" :class="testResult.type">
|
||||
<h3>Test-Ergebnis:</h3>
|
||||
<pre>{{ testResult.data }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else class="no-account">
|
||||
<p>Kein myTischtennis-Account verknüpft.</p>
|
||||
<button class="btn-primary" @click="openEditDialog">Account verknüpfen</button>
|
||||
</div>
|
||||
|
||||
<div class="info-box">
|
||||
<h3>Über myTischtennis</h3>
|
||||
<p>Durch die Verknüpfung Ihres myTischtennis-Accounts können Sie:</p>
|
||||
<ul>
|
||||
<li>Automatisch Turnierdaten importieren</li>
|
||||
<li>Spielerergebnisse synchronisieren</li>
|
||||
<li>Wettkampfdaten direkt abrufen</li>
|
||||
</ul>
|
||||
<p><strong>Hinweis:</strong> Das Speichern des Passworts ist optional. Wenn Sie es nicht speichern, werden Sie bei jeder Synchronisation nach dem Passwort gefragt.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Dialog -->
|
||||
<MyTischtennisDialog
|
||||
v-if="showDialog"
|
||||
:account="account"
|
||||
@close="closeDialog"
|
||||
@saved="onAccountSaved"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import apiClient from '../apiClient.js';
|
||||
import MyTischtennisDialog from '../components/MyTischtennisDialog.vue';
|
||||
|
||||
export default {
|
||||
name: 'MyTischtennisAccount',
|
||||
components: {
|
||||
MyTischtennisDialog
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
account: null,
|
||||
showDialog: false,
|
||||
testResult: null
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.loadAccount();
|
||||
},
|
||||
methods: {
|
||||
async loadAccount() {
|
||||
try {
|
||||
this.loading = true;
|
||||
const response = await apiClient.get('/mytischtennis/account');
|
||||
this.account = response.data.account;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden des Accounts:', error);
|
||||
this.$store.dispatch('showMessage', {
|
||||
text: 'Fehler beim Laden des myTischtennis-Accounts',
|
||||
type: 'error'
|
||||
});
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
openEditDialog() {
|
||||
this.showDialog = true;
|
||||
},
|
||||
|
||||
closeDialog() {
|
||||
this.showDialog = false;
|
||||
},
|
||||
|
||||
async onAccountSaved() {
|
||||
this.closeDialog();
|
||||
await this.loadAccount();
|
||||
this.$store.dispatch('showMessage', {
|
||||
text: 'myTischtennis-Account erfolgreich gespeichert',
|
||||
type: 'success'
|
||||
});
|
||||
},
|
||||
|
||||
async testConnection() {
|
||||
this.testResult = null;
|
||||
try {
|
||||
await apiClient.post('/mytischtennis/verify');
|
||||
this.$store.dispatch('showMessage', {
|
||||
text: 'Verbindung erfolgreich! Login funktioniert.',
|
||||
type: 'success'
|
||||
});
|
||||
await this.loadAccount(); // Aktualisiere lastLoginSuccess
|
||||
} catch (error) {
|
||||
const message = error.response?.data?.message || 'Verbindung fehlgeschlagen';
|
||||
|
||||
if (error.response?.status === 400 && message.includes('Kein Passwort gespeichert')) {
|
||||
// Passwort-Dialog öffnen
|
||||
this.showDialog = true;
|
||||
}
|
||||
|
||||
this.$store.dispatch('showMessage', {
|
||||
text: message,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async testLoginFlow() {
|
||||
this.testResult = null;
|
||||
|
||||
try {
|
||||
// 1. Verify Login
|
||||
console.log('Testing login...');
|
||||
const verifyResponse = await apiClient.post('/mytischtennis/verify');
|
||||
console.log('Login successful:', verifyResponse.data);
|
||||
|
||||
// 2. Get Session
|
||||
console.log('Fetching session...');
|
||||
const sessionResponse = await apiClient.get('/mytischtennis/session');
|
||||
console.log('Session data:', sessionResponse.data);
|
||||
|
||||
// 3. Check Status
|
||||
console.log('Checking status...');
|
||||
const statusResponse = await apiClient.get('/mytischtennis/status');
|
||||
console.log('Status:', statusResponse.data);
|
||||
|
||||
this.testResult = {
|
||||
type: 'success',
|
||||
data: {
|
||||
message: 'Alle Tests erfolgreich!',
|
||||
login: {
|
||||
accessToken: verifyResponse.data.accessToken ? '✓ vorhanden' : '✗ fehlt',
|
||||
expiresAt: verifyResponse.data.expiresAt,
|
||||
clubId: verifyResponse.data.clubId || '✗ nicht gefunden',
|
||||
clubName: verifyResponse.data.clubName || '✗ nicht gefunden'
|
||||
},
|
||||
session: {
|
||||
accessToken: sessionResponse.data.session?.accessToken ? '✓ vorhanden' : '✗ fehlt',
|
||||
refreshToken: sessionResponse.data.session?.refreshToken ? '✓ vorhanden' : '✗ fehlt',
|
||||
cookie: sessionResponse.data.session?.cookie ? '✓ vorhanden' : '✗ fehlt',
|
||||
userData: sessionResponse.data.session?.userData ? '✓ vorhanden' : '✗ fehlt',
|
||||
expiresAt: sessionResponse.data.session?.expiresAt
|
||||
},
|
||||
status: statusResponse.data
|
||||
}
|
||||
};
|
||||
|
||||
this.$store.dispatch('showMessage', {
|
||||
text: 'Test erfolgreich! Details siehe unten.',
|
||||
type: 'success'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Test failed:', error);
|
||||
|
||||
this.testResult = {
|
||||
type: 'error',
|
||||
data: {
|
||||
message: 'Test fehlgeschlagen',
|
||||
error: error.response?.data?.message || error.message,
|
||||
status: error.response?.status,
|
||||
details: error.response?.data
|
||||
}
|
||||
};
|
||||
|
||||
this.$store.dispatch('showMessage', {
|
||||
text: `Test fehlgeschlagen: ${error.response?.data?.message || error.message}`,
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async deleteAccount() {
|
||||
if (!confirm('Möchten Sie die Verknüpfung zum myTischtennis-Account wirklich trennen?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await apiClient.delete('/mytischtennis/account');
|
||||
this.account = null;
|
||||
this.$store.dispatch('showMessage', {
|
||||
text: 'myTischtennis-Account erfolgreich getrennt',
|
||||
type: 'success'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Löschen des Accounts:', error);
|
||||
this.$store.dispatch('showMessage', {
|
||||
text: 'Fehler beim Trennen des Accounts',
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
formatDate(dateString) {
|
||||
if (!dateString) return '-';
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: var(--text-color, #333);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.account-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.account-info, .no-account {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.info-section h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--primary-color, #007bff);
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.75rem 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.info-row:last-of-type {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-row label {
|
||||
font-weight: 600;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.info-row span {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 2rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.no-account {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-account p {
|
||||
margin-bottom: 1.5rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
background: #f8f9fa;
|
||||
border-left: 4px solid var(--primary-color, #007bff);
|
||||
padding: 1.5rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.info-box h3 {
|
||||
margin-top: 0;
|
||||
color: var(--primary-color, #007bff);
|
||||
}
|
||||
|
||||
.info-box ul {
|
||||
margin: 1rem 0;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.info-box li {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
.btn-primary, .btn-secondary, .btn-danger {
|
||||
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 {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #545b62;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background-color: #c82333;
|
||||
}
|
||||
|
||||
.test-result {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.test-result.success {
|
||||
border-left: 4px solid #28a745;
|
||||
}
|
||||
|
||||
.test-result.error {
|
||||
border-left: 4px solid #dc3545;
|
||||
}
|
||||
|
||||
.test-result h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.test-result pre {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
overflow-x: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
</label>
|
||||
<div class="image-section">
|
||||
<h4>Bild hinzufügen</h4>
|
||||
<p class="image-help">Du kannst entweder einen Link zu einem Bild eingeben oder ein Bild hochladen:</p>
|
||||
<p class="image-help">Du kannst entweder einen Link zu einem Bild eingeben, ein Bild hochladen oder eine Übungszeichnung erstellen:</p>
|
||||
|
||||
<label>Bild-Link (optional)
|
||||
<input type="text" v-model="editModel.imageLink" placeholder="z.B. https://example.com/bild.jpg oder /api/predefined-activities/:id/image/:imageId" />
|
||||
@@ -74,6 +74,18 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Zeichen-Tool -->
|
||||
<div class="drawing-section">
|
||||
<h5>Übungszeichnung erstellen</h5>
|
||||
<CourtDrawingTool
|
||||
:activity-id="editModel.id"
|
||||
:drawing-data="editModel.drawingData"
|
||||
:allow-image-upload="false"
|
||||
@update-fields="onUpdateFields"
|
||||
@update-drawing-data="onUpdateDrawingData"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="image-list" v-if="images && images.length">
|
||||
<h5>Hochgeladene Bilder:</h5>
|
||||
<div class="image-grid">
|
||||
@@ -97,9 +109,13 @@
|
||||
|
||||
<script>
|
||||
import apiClient from '../apiClient.js';
|
||||
import CourtDrawingTool from '../components/CourtDrawingTool.vue';
|
||||
|
||||
export default {
|
||||
name: 'PredefinedActivities',
|
||||
components: {
|
||||
CourtDrawingTool
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
activities: [],
|
||||
@@ -107,6 +123,7 @@ export default {
|
||||
editModel: null,
|
||||
images: [],
|
||||
selectedFile: null,
|
||||
selectedDrawingData: null,
|
||||
mergeSourceId: '',
|
||||
mergeTargetId: '',
|
||||
};
|
||||
@@ -131,6 +148,15 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
parseDrawingData(value) {
|
||||
if (!value) return null;
|
||||
if (typeof value === 'object') return value;
|
||||
try { return JSON.parse(value); } catch (e) { return null; }
|
||||
},
|
||||
normalizeActivity(activity, images) {
|
||||
const drawingData = this.parseDrawingData(activity && activity.drawingData);
|
||||
return { ...(activity || {}), drawingData };
|
||||
},
|
||||
async reload() {
|
||||
const r = await apiClient.get('/predefined-activities');
|
||||
this.activities = r.data || [];
|
||||
@@ -140,7 +166,24 @@ export default {
|
||||
const r = await apiClient.get(`/predefined-activities/${a.id}`);
|
||||
const { images, ...activity } = r.data;
|
||||
this.images = images || [];
|
||||
this.editModel = { ...activity };
|
||||
// Server-Daten normalisieren und ggf. image-drawingData fallbacken
|
||||
let model = this.normalizeActivity(activity, images);
|
||||
if (!model.drawingData && images && images.length > 0 && images[0].drawingData) {
|
||||
model.drawingData = this.parseDrawingData(images[0].drawingData);
|
||||
}
|
||||
this.editModel = model;
|
||||
},
|
||||
async reloadImages() {
|
||||
if (this.editModel && this.editModel.id) {
|
||||
try {
|
||||
const r = await apiClient.get(`/predefined-activities/${this.editModel.id}`);
|
||||
const { images } = r.data;
|
||||
this.images = images || [];
|
||||
console.log('Images reloaded:', this.images);
|
||||
} catch (error) {
|
||||
console.error('Error reloading images:', error);
|
||||
}
|
||||
}
|
||||
},
|
||||
formatItem(a) {
|
||||
return `${a.code ? '[' + a.code + '] ' : ''}${a.name}`;
|
||||
@@ -164,6 +207,7 @@ export default {
|
||||
duration: null,
|
||||
durationText: '',
|
||||
imageLink: '',
|
||||
drawingData: '',
|
||||
};
|
||||
},
|
||||
cancel() {
|
||||
@@ -173,36 +217,81 @@ export default {
|
||||
},
|
||||
async save() {
|
||||
if (!this.editModel) return;
|
||||
|
||||
console.log('Save: selectedFile =', this.selectedFile);
|
||||
|
||||
if (this.editModel.id) {
|
||||
const { id, ...payload } = this.editModel;
|
||||
if (payload.drawingData && typeof payload.drawingData === 'object') {
|
||||
payload.drawingData = payload.drawingData;
|
||||
}
|
||||
const r = await apiClient.put(`/predefined-activities/${id}`, payload);
|
||||
this.editModel = r.data;
|
||||
this.editModel = this.normalizeActivity(r.data);
|
||||
} else {
|
||||
const r = await apiClient.post('/predefined-activities', this.editModel);
|
||||
this.editModel = r.data;
|
||||
// Nach dem Erstellen einer neuen Aktivität, falls ein Bild ausgewählt wurde, hochladen
|
||||
if (this.selectedFile) {
|
||||
await this.uploadImage();
|
||||
}
|
||||
this.editModel = this.normalizeActivity(r.data);
|
||||
}
|
||||
|
||||
// Nach dem Speichern (sowohl CREATE als auch UPDATE): Bild hochladen falls vorhanden
|
||||
if (this.selectedFile) {
|
||||
console.log('Uploading image after save...');
|
||||
await this.uploadImage();
|
||||
} else {
|
||||
console.log('No selectedFile to upload');
|
||||
}
|
||||
|
||||
await this.reload();
|
||||
},
|
||||
onFileChange(e) {
|
||||
this.selectedFile = e.target.files && e.target.files[0] ? e.target.files[0] : null;
|
||||
},
|
||||
imageUrl(img) {
|
||||
return `/api/predefined-activities/${this.editModel.id}/image/${img.id}`;
|
||||
return `http://localhost:3000/api/predefined-activities/${this.editModel.id}/image/${img.id}`;
|
||||
},
|
||||
async uploadImage() {
|
||||
if (!this.editModel || !this.editModel.id || !this.selectedFile) return;
|
||||
if (!this.editModel || !this.editModel.id || !this.selectedFile) {
|
||||
console.log('Upload skipped: editModel=', this.editModel, 'selectedFile=', this.selectedFile);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Starting image upload...');
|
||||
console.log('editModel:', this.editModel);
|
||||
console.log('selectedActivity:', this.selectedActivity);
|
||||
console.log('Activity ID (editModel.id):', this.editModel.id);
|
||||
console.log('Activity ID (selectedActivity.id):', this.selectedActivity?.id);
|
||||
console.log('File:', this.selectedFile);
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('image', this.selectedFile);
|
||||
await apiClient.post(`/predefined-activities/${this.editModel.id}/image`, fd, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
});
|
||||
// Nach Upload Details neu laden
|
||||
await this.select(this.editModel);
|
||||
this.selectedFile = null;
|
||||
|
||||
// Füge Zeichnungsdaten hinzu, falls vorhanden
|
||||
if (this.selectedDrawingData) {
|
||||
fd.append('drawingData', JSON.stringify(this.selectedDrawingData));
|
||||
console.log('Added drawingData to FormData:', this.selectedDrawingData);
|
||||
}
|
||||
|
||||
// Verwende PUT für Updates, POST für neue Activities
|
||||
const isUpdate = this.selectedActivity && this.selectedActivity.id === this.editModel.id;
|
||||
const method = isUpdate ? 'put' : 'post';
|
||||
|
||||
console.log('Using method:', method);
|
||||
|
||||
try {
|
||||
const response = await apiClient[method](`/predefined-activities/${this.editModel.id}/image`, fd, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
});
|
||||
console.log('Upload successful:', response);
|
||||
|
||||
// Nach Upload Details neu laden
|
||||
await this.select(this.editModel);
|
||||
this.selectedFile = null;
|
||||
|
||||
// Bildliste explizit aktualisieren
|
||||
await this.reloadImages();
|
||||
} catch (error) {
|
||||
console.error('Upload failed:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
async deleteImage(imageId) {
|
||||
if (!this.editModel || !this.editModel.id) return;
|
||||
@@ -220,6 +309,53 @@ export default {
|
||||
if (!confirm('Alle Aktivitäten mit identischem Namen werden zusammengeführt. Fortfahren?')) return;
|
||||
await apiClient.post('/predefined-activities/deduplicate', {});
|
||||
await this.reload();
|
||||
},
|
||||
onDrawingSave(drawingData) {
|
||||
if (this.editModel) {
|
||||
this.editModel.drawingData = drawingData;
|
||||
// Automatisch als Bild-Link setzen, wenn es eine Zeichnung gibt
|
||||
if (drawingData && !this.editModel.imageLink) {
|
||||
this.editModel.imageLink = drawingData;
|
||||
}
|
||||
}
|
||||
// Nicht automatisch speichern, nur wenn User explizit "Speichern" klickt
|
||||
},
|
||||
onUpdateFields(fields) {
|
||||
if (this.editModel) {
|
||||
this.editModel.code = fields.code;
|
||||
this.editModel.name = fields.name;
|
||||
this.editModel.description = fields.description;
|
||||
}
|
||||
},
|
||||
onUpdateDrawingData(data) {
|
||||
if (this.editModel) {
|
||||
this.editModel.drawingData = data;
|
||||
}
|
||||
},
|
||||
|
||||
async onDrawingImageUpload(file, drawingData) {
|
||||
console.log('onDrawingImageUpload called with file:', file);
|
||||
console.log('onDrawingImageUpload called with drawingData:', drawingData);
|
||||
console.log('File type:', file?.type);
|
||||
console.log('File size:', file?.size);
|
||||
console.log('File name:', file?.name);
|
||||
|
||||
// Setze das File und die Zeichnungsdaten für den Upload
|
||||
this.selectedFile = file;
|
||||
this.selectedDrawingData = drawingData;
|
||||
console.log('selectedFile set to:', this.selectedFile);
|
||||
console.log('selectedDrawingData set to:', this.selectedDrawingData);
|
||||
|
||||
// Upload wird erst beim Speichern durchgeführt
|
||||
console.log('File and drawing data ready for upload when saving');
|
||||
},
|
||||
|
||||
async onImageUploaded() {
|
||||
console.log('Image uploaded successfully, refreshing image list...');
|
||||
// Bildliste aktualisieren
|
||||
if (this.editModel && this.editModel.id) {
|
||||
await this.select(this.editModel);
|
||||
}
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
@@ -354,5 +490,19 @@ input[type="text"], input[type="number"], textarea { width: 100%; }
|
||||
.btn-danger:hover {
|
||||
background: #c82333;
|
||||
}
|
||||
|
||||
.drawing-section {
|
||||
margin: 1rem 0;
|
||||
padding: 1rem;
|
||||
background: white;
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.drawing-section h5 {
|
||||
margin: 0 0 1rem 0;
|
||||
color: #333;
|
||||
font-size: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user