Add last scheduler executions endpoint and member quick update functions
Implemented a new endpoint in ApiLogController to retrieve the last execution information for scheduler jobs. Added quick update functions in MemberService and corresponding routes for updating test membership status and marking member forms as handed over. Enhanced the MembersView to support quick actions for managing test memberships and form statuses, improving user experience and operational efficiency.
This commit is contained in:
@@ -62,6 +62,23 @@ class ApiLogController {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/logs/scheduler/last-executions
|
||||
* Get last execution info for scheduler jobs
|
||||
*/
|
||||
async getLastSchedulerExecutions(req, res, next) {
|
||||
try {
|
||||
const { clubId } = req.query;
|
||||
const results = await apiLogService.getLastSchedulerExecutions(clubId ? parseInt(clubId) : null);
|
||||
res.json({
|
||||
success: true,
|
||||
data: results
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new ApiLogController();
|
||||
|
||||
@@ -100,6 +100,30 @@ const rotateMemberImage = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
const quickUpdateTestMembership = async (req, res) => {
|
||||
try {
|
||||
const { clubId, memberId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const result = await MemberService.quickUpdateTestMembership(userToken, clubId, memberId);
|
||||
res.status(result.status).json(result.response);
|
||||
} catch (error) {
|
||||
console.error('[quickUpdateTestMembership] - Error:', error);
|
||||
res.status(500).json({ error: 'Failed to update test membership' });
|
||||
}
|
||||
};
|
||||
|
||||
const quickUpdateMemberFormHandedOver = async (req, res) => {
|
||||
try {
|
||||
const { clubId, memberId } = req.params;
|
||||
const { authcode: userToken } = req.headers;
|
||||
const result = await MemberService.quickUpdateMemberFormHandedOver(userToken, clubId, memberId);
|
||||
res.status(result.status).json(result.response);
|
||||
} catch (error) {
|
||||
console.error('[quickUpdateMemberFormHandedOver] - Error:', error);
|
||||
res.status(500).json({ error: 'Failed to update member form status' });
|
||||
}
|
||||
};
|
||||
|
||||
const transferMembers = async (req, res) => {
|
||||
try {
|
||||
const { id: clubId } = req.params;
|
||||
@@ -132,4 +156,4 @@ const transferMembers = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
export { getClubMembers, getWaitingApprovals, setClubMembers, uploadMemberImage, getMemberImage, updateRatingsFromMyTischtennis, rotateMemberImage, transferMembers };
|
||||
export { getClubMembers, getWaitingApprovals, setClubMembers, uploadMemberImage, getMemberImage, updateRatingsFromMyTischtennis, rotateMemberImage, transferMembers, quickUpdateTestMembership, quickUpdateMemberFormHandedOver };
|
||||
@@ -11,6 +11,9 @@ router.use(authenticate);
|
||||
// Get logs - requires permissions or admin
|
||||
router.get('/', apiLogController.getLogs);
|
||||
|
||||
// Get last scheduler executions
|
||||
router.get('/scheduler/last-executions', apiLogController.getLastSchedulerExecutions);
|
||||
|
||||
// Get single log by ID
|
||||
router.get('/:id', apiLogController.getLogById);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getClubMembers, getWaitingApprovals, setClubMembers, uploadMemberImage, getMemberImage, updateRatingsFromMyTischtennis, rotateMemberImage, transferMembers } from '../controllers/memberController.js';
|
||||
import { getClubMembers, getWaitingApprovals, setClubMembers, uploadMemberImage, getMemberImage, updateRatingsFromMyTischtennis, rotateMemberImage, transferMembers, quickUpdateTestMembership, quickUpdateMemberFormHandedOver } from '../controllers/memberController.js';
|
||||
import express from 'express';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
import { authorize } from '../middleware/authorizationMiddleware.js';
|
||||
@@ -17,5 +17,7 @@ router.get('/notapproved/:id', authenticate, authorize('members', 'read'), getWa
|
||||
router.post('/update-ratings/:id', authenticate, authorize('mytischtennis', 'write'), updateRatingsFromMyTischtennis);
|
||||
router.post('/rotate-image/:clubId/:memberId', authenticate, authorize('members', 'write'), rotateMemberImage);
|
||||
router.post('/transfer/:id', authenticate, authorize('members', 'write'), transferMembers);
|
||||
router.post('/quick-update-test-membership/:clubId/:memberId', authenticate, authorize('members', 'write'), quickUpdateTestMembership);
|
||||
router.post('/quick-update-member-form/:clubId/:memberId', authenticate, authorize('members', 'write'), quickUpdateMemberFormHandedOver);
|
||||
|
||||
export default router;
|
||||
|
||||
@@ -184,6 +184,130 @@ class ApiLogService {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get last execution info for scheduler jobs
|
||||
*/
|
||||
async getLastSchedulerExecutions(clubId = null) {
|
||||
try {
|
||||
const jobTypes = ['rating_updates', 'match_results'];
|
||||
const results = {};
|
||||
|
||||
for (const jobType of jobTypes) {
|
||||
const lastExecution = await ApiLog.findOne({
|
||||
where: {
|
||||
logType: 'scheduler',
|
||||
schedulerJobType: jobType
|
||||
},
|
||||
order: [['createdAt', 'DESC']],
|
||||
attributes: ['id', 'createdAt', 'statusCode', 'responseBody', 'executionTime', 'errorMessage']
|
||||
});
|
||||
|
||||
if (lastExecution) {
|
||||
let parsedResponse = null;
|
||||
let updatedCount = null;
|
||||
let fetchedCount = null;
|
||||
let teamDetails = [];
|
||||
|
||||
// Parse responseBody to extract counts
|
||||
try {
|
||||
if (lastExecution.responseBody) {
|
||||
parsedResponse = JSON.parse(lastExecution.responseBody);
|
||||
// Extract counts from response
|
||||
if (parsedResponse.totalUpdated !== undefined) {
|
||||
updatedCount = parsedResponse.totalUpdated;
|
||||
} else if (parsedResponse.updatedCount !== undefined) {
|
||||
updatedCount = parsedResponse.updatedCount;
|
||||
}
|
||||
if (parsedResponse.totalFetched !== undefined) {
|
||||
fetchedCount = parsedResponse.totalFetched;
|
||||
} else if (parsedResponse.fetchedCount !== undefined) {
|
||||
fetchedCount = parsedResponse.fetchedCount;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore parsing errors
|
||||
}
|
||||
|
||||
// For match_results, try to get team-specific details from API logs
|
||||
if (jobType === 'match_results') {
|
||||
try {
|
||||
// Find all API logs created around the same time as the scheduler execution
|
||||
// (within 5 minutes before and after)
|
||||
const timeWindowStart = new Date(lastExecution.createdAt);
|
||||
timeWindowStart.setMinutes(timeWindowStart.getMinutes() - 5);
|
||||
const timeWindowEnd = new Date(lastExecution.createdAt);
|
||||
timeWindowEnd.setMinutes(timeWindowEnd.getMinutes() + 5);
|
||||
|
||||
const teamLogs = await ApiLog.findAll({
|
||||
where: {
|
||||
logType: 'api_request',
|
||||
schedulerJobType: 'mytischtennis_fetch',
|
||||
createdAt: {
|
||||
[Op.between]: [timeWindowStart, timeWindowEnd]
|
||||
}
|
||||
},
|
||||
order: [['createdAt', 'DESC']],
|
||||
attributes: ['id', 'requestBody', 'statusCode', 'createdAt']
|
||||
});
|
||||
|
||||
// Extract team information from requestBody
|
||||
const teamMap = new Map();
|
||||
for (const log of teamLogs) {
|
||||
try {
|
||||
if (log.requestBody) {
|
||||
const requestData = JSON.parse(log.requestBody);
|
||||
if (requestData.clubTeamId && requestData.teamName) {
|
||||
const teamId = requestData.clubTeamId;
|
||||
if (!teamMap.has(teamId)) {
|
||||
teamMap.set(teamId, {
|
||||
clubTeamId: teamId,
|
||||
teamName: requestData.teamName,
|
||||
success: log.statusCode === 200,
|
||||
lastRun: log.createdAt
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore parsing errors for individual logs
|
||||
}
|
||||
}
|
||||
|
||||
teamDetails = Array.from(teamMap.values());
|
||||
} catch (e) {
|
||||
console.error('Error extracting team details:', e);
|
||||
}
|
||||
}
|
||||
|
||||
results[jobType] = {
|
||||
lastRun: lastExecution.createdAt,
|
||||
success: lastExecution.statusCode === 200,
|
||||
executionTime: lastExecution.executionTime,
|
||||
updatedCount: updatedCount,
|
||||
fetchedCount: fetchedCount,
|
||||
errorMessage: lastExecution.errorMessage,
|
||||
teamDetails: teamDetails
|
||||
};
|
||||
} else {
|
||||
results[jobType] = {
|
||||
lastRun: null,
|
||||
success: null,
|
||||
executionTime: null,
|
||||
updatedCount: null,
|
||||
fetchedCount: null,
|
||||
errorMessage: null,
|
||||
teamDetails: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
console.error('Error getting last scheduler executions:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new ApiLogService();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import UserClub from "../models/UserClub.js";
|
||||
import { checkAccess, getUserByToken, hasUserClubAccess } from "../utils/userUtils.js";
|
||||
import Member from "../models/Member.js";
|
||||
import Participant from "../models/Participant.js";
|
||||
import DiaryDate from "../models/DiaryDates.js";
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import sharp from 'sharp';
|
||||
@@ -492,6 +494,56 @@ class MemberService {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async quickUpdateTestMembership(userToken, clubId, memberId) {
|
||||
try {
|
||||
await checkAccess(userToken, clubId);
|
||||
const member = await Member.findOne({ where: { id: memberId, clubId: clubId } });
|
||||
if (!member) {
|
||||
return { status: 404, response: { error: 'Member not found in this club' } };
|
||||
}
|
||||
|
||||
if (!member.testMembership) {
|
||||
return { status: 400, response: { error: 'Member is not a test member' } };
|
||||
}
|
||||
|
||||
member.testMembership = false;
|
||||
await member.save();
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
response: { success: true, message: 'Testmitgliedschaft entfernt' }
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[quickUpdateTestMembership] - Error:', error);
|
||||
return { status: 500, response: { error: 'Failed to update test membership' } };
|
||||
}
|
||||
}
|
||||
|
||||
async quickUpdateMemberFormHandedOver(userToken, clubId, memberId) {
|
||||
try {
|
||||
await checkAccess(userToken, clubId);
|
||||
const member = await Member.findOne({ where: { id: memberId, clubId: clubId } });
|
||||
if (!member) {
|
||||
return { status: 404, response: { error: 'Member not found in this club' } };
|
||||
}
|
||||
|
||||
if (member.memberFormHandedOver) {
|
||||
return { status: 400, response: { error: 'Member form already handed over' } };
|
||||
}
|
||||
|
||||
member.memberFormHandedOver = true;
|
||||
await member.save();
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
response: { success: true, message: 'Mitgliedsformular als ausgehändigt markiert' }
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[quickUpdateMemberFormHandedOver] - Error:', error);
|
||||
return { status: 500, response: { error: 'Failed to update member form status' } };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new MemberService();
|
||||
Reference in New Issue
Block a user