diff --git a/backend/controllers/diaryController.js b/backend/controllers/diaryController.js index b2686f6..98c9452 100644 --- a/backend/controllers/diaryController.js +++ b/backend/controllers/diaryController.js @@ -116,3 +116,15 @@ const deleteTagFromDiaryDate = async (req, res) => { export { getDatesForClub, createDateForClub, updateTrainingTimes, addDiaryNote, deleteDiaryNote, addDiaryTag, addTagToDiaryDate, deleteTagFromDiaryDate }; + +export const deleteDateForClub = async (req, res) => { + try { + const { clubId, dateId } = req.params; + const { authcode: userToken } = req.headers; + const result = await diaryService.removeDateForClub(userToken, clubId, dateId); + res.status(200).json(result); + } catch (error) { + console.error('[deleteDateForClub] - Error:', error); + res.status(error.statusCode || 500).json({ error: error.message || 'systemerror' }); + } +}; diff --git a/backend/package.json b/backend/package.json index 51a13f6..2f6e4c4 100644 --- a/backend/package.json +++ b/backend/package.json @@ -5,7 +5,8 @@ "type": "module", "scripts": { "postinstall": "cd ../frontend && npm install && npm run build", - "dev": "nodemon server.js" + "dev": "nodemon server.js", + "cleanup:usertoken": "node ./scripts/cleanupUserTokenKeys.js" }, "keywords": [], "author": "", diff --git a/backend/routes/diaryRoutes.js b/backend/routes/diaryRoutes.js index cc17bc9..dacfce2 100644 --- a/backend/routes/diaryRoutes.js +++ b/backend/routes/diaryRoutes.js @@ -8,7 +8,8 @@ import { deleteDiaryNote, addDiaryTag, addTagToDiaryDate, - deleteTagFromDiaryDate + deleteTagFromDiaryDate, + deleteDateForClub, } from '../controllers/diaryController.js'; const router = express.Router(); @@ -21,5 +22,6 @@ router.delete('/:clubId/tag', authenticate, deleteTagFromDiaryDate); router.get('/:clubId', authenticate, getDatesForClub); router.post('/:clubId', authenticate, createDateForClub); router.put('/:clubId', authenticate, updateTrainingTimes); +router.delete('/:clubId/:dateId', authenticate, deleteDateForClub); export default router; diff --git a/backend/scripts/cleanupUserTokenKeys.js b/backend/scripts/cleanupUserTokenKeys.js new file mode 100644 index 0000000..c822c59 --- /dev/null +++ b/backend/scripts/cleanupUserTokenKeys.js @@ -0,0 +1,90 @@ +import mysql from 'mysql2/promise'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const dbConfig = { + host: process.env.DB_HOST || 'localhost', + user: process.env.DB_USER || 'root', + password: process.env.DB_PASSWORD || '', + database: process.env.DB_NAME || 'trainingdiary', +}; + +async function getIndexSummary(connection, table) { + const [rows] = await connection.execute(`SHOW INDEX FROM \`${table}\``); + const summary = rows.reduce((acc, r) => { + const key = r.Key_name; + acc[key] = acc[key] || { unique: r.Non_unique === 0, columns: [] }; + acc[key].columns.push(r.Column_name); + return acc; + }, {}); + return summary; +} + +async function cleanupUserTokenKeys() { + let connection; + const table = 'UserToken'; + + try { + console.log('Connecting to DB:', dbConfig); + connection = await mysql.createConnection(dbConfig); + + console.log(`\nBefore cleanup (indexes on ${table}):`); + let before = await getIndexSummary(connection, table); + Object.entries(before).forEach(([name, info]) => { + console.log(` - ${name} ${info.unique ? '(UNIQUE)' : ''} -> [${info.columns.join(', ')}]`); + }); + + // Drop all non-PRIMARY indexes on UserToken + const [indexes] = await connection.execute(`SHOW INDEX FROM \`${table}\``); + const keyNames = Array.from(new Set(indexes.map(i => i.Key_name))).filter(k => k !== 'PRIMARY'); + + for (const keyName of keyNames) { + try { + await connection.execute(`DROP INDEX \`${keyName}\` ON \`${table}\``); + console.log(`Dropped index: ${keyName}`); + } catch (err) { + console.warn(`Could not drop ${keyName}: ${err.code || err.message}`); + } + } + + // Re-create minimal, deterministic indexes + // Unique on token (column is 'token') + try { + await connection.execute(`CREATE UNIQUE INDEX \`uniq_UserToken_token\` ON \`${table}\` (\`token\`)`); + console.log('Created UNIQUE index: uniq_UserToken_token (token)'); + } catch (err) { + console.warn('Could not create uniq_UserToken_token:', err.code || err.message); + } + + // Helpful index on user_id if column exists + try { + const [cols] = await connection.execute(`SHOW COLUMNS FROM \`${table}\` LIKE 'user_id'`); + if (cols && cols.length > 0) { + await connection.execute(`CREATE INDEX \`idx_UserToken_user_id\` ON \`${table}\` (\`user_id\`)`); + console.log('Created INDEX: idx_UserToken_user_id (user_id)'); + } else { + console.log('Column user_id not found, skip creating idx_UserToken_user_id'); + } + } catch (err) { + console.warn('Could not create idx_UserToken_user_id:', err.code || err.message); + } + + console.log(`\nAfter cleanup (indexes on ${table}):`); + const after = await getIndexSummary(connection, table); + Object.entries(after).forEach(([name, info]) => { + console.log(` - ${name} ${info.unique ? '(UNIQUE)' : ''} -> [${info.columns.join(', ')}]`); + }); + + console.log('\nDone.'); + } catch (err) { + console.error('Cleanup failed:', err); + process.exitCode = 1; + } finally { + if (connection) await connection.end(); + } +} + +cleanupUserTokenKeys(); + + diff --git a/backend/server.js b/backend/server.js index 4dcbdce..03f073b 100644 --- a/backend/server.js +++ b/backend/server.js @@ -151,7 +151,7 @@ app.get('*', (req, res) => { await TournamentMatch.sync({ alter: true }); await TournamentResult.sync({ alter: true }); await Accident.sync({ alter: true }); - await UserToken.sync({ alter: true }); + await UserToken.sync(); app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`); diff --git a/backend/services/diaryDateActivityService.js b/backend/services/diaryDateActivityService.js index 5e66b98..2b1d70d 100644 --- a/backend/services/diaryDateActivityService.js +++ b/backend/services/diaryDateActivityService.js @@ -20,7 +20,7 @@ class DiaryDateActivityService { duration: data.duration }); } - restData.predefinedActivityId = predefinedActivity.id; + restData.predefinedActivityId = predefinedActivity.id; const maxOrderId = await DiaryDateActivity.max('orderId', { where: { diaryDateId: data.diaryDateId } }); diff --git a/backend/services/diaryService.js b/backend/services/diaryService.js index c0f236d..9ab8cc7 100644 --- a/backend/services/diaryService.js +++ b/backend/services/diaryService.js @@ -1,4 +1,5 @@ import DiaryDate from '../models/DiaryDates.js'; +import DiaryDateActivity from '../models/DiaryDateActivity.js'; import Club from '../models/Club.js'; import DiaryNote from '../models/DiaryNote.js'; import { DiaryTag } from '../models/DiaryTag.js'; @@ -151,6 +152,23 @@ class DiaryService { await DiaryDateTag.destroy({ where: { tagId } }); } + async removeDateForClub(userToken, clubId, dateId) { + console.log('[DiaryService::removeDateForClub] - Check user access'); + await checkAccess(userToken, clubId); + console.log('[DiaryService::removeDateForClub] - Validate date'); + const diaryDate = await DiaryDate.findOne({ where: { id: dateId, clubId } }); + if (!diaryDate) { + throw new HttpError('Diary entry not found', 404); + } + console.log('[DiaryService::removeDateForClub] - Check for activities'); + const activityCount = await DiaryDateActivity.count({ where: { diaryDateId: dateId } }); + if (activityCount > 0) { + throw new HttpError('Cannot delete date with activities', 409); + } + console.log('[DiaryService::removeDateForClub] - Delete diary date'); + await diaryDate.destroy(); + return { ok: true }; + } } export default new DiaryService(); diff --git a/frontend/src/views/DiaryView.vue b/frontend/src/views/DiaryView.vue index 4b0bdd1..c286c2a 100644 --- a/frontend/src/views/DiaryView.vue +++ b/frontend/src/views/DiaryView.vue @@ -8,6 +8,7 @@ +