Add Feedback domain module: public submission flow, admin CRUD, reporting (#7)
Jenkins Production Deployment
Jenkins Production Deployment
Co-authored-by: Patrick Müller <mail@pmueller.me> Reviewed-on: #7 Co-authored-by: Patrick Mueller <patrick@mueller-patrick.tech> Co-committed-by: Patrick Mueller <patrick@mueller-patrick.tech>
This commit was merged in pull request #7.
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import {NachklangFeedbackDB} from '../Feedback.db';
|
||||
import {EventConfig, EventSummary, Question, Song} from '../feedback.interface';
|
||||
|
||||
/**
|
||||
* Returns all events currently eligible to receive feedback:
|
||||
* published, on or after their concert day, and before the deadline.
|
||||
*/
|
||||
export const getEligibleEvents = async (): Promise<EventSummary[]> => {
|
||||
let conn = await NachklangFeedbackDB.getConnection();
|
||||
try {
|
||||
const query = `
|
||||
SELECT slug, name, subtitle, event_date, feedback_deadline, poster_image_url
|
||||
FROM events
|
||||
WHERE is_published = 1 AND event_date <= CURDATE() AND feedback_deadline >= NOW()
|
||||
ORDER BY event_date DESC`;
|
||||
const rows = await conn.query(query);
|
||||
return rows.map((row: any) => ({
|
||||
slug: row.slug,
|
||||
name: row.name,
|
||||
subtitle: row.subtitle,
|
||||
eventDate: row.event_date,
|
||||
feedbackDeadline: row.feedback_deadline,
|
||||
posterImageUrl: row.poster_image_url
|
||||
}));
|
||||
} finally {
|
||||
await conn.end();
|
||||
}
|
||||
};
|
||||
|
||||
export type EventLookupResult =
|
||||
| { status: 'OK'; eventId: number; event: EventConfig }
|
||||
| { status: 'NOT_FOUND' }
|
||||
| { status: 'CLOSED' };
|
||||
|
||||
/**
|
||||
* Resolves a slug to its full public config: meta, ordered setlist, ordered
|
||||
* active questions. Distinguishes "unknown slug" from "known but outside
|
||||
* its feedback window" so callers can respond 404 vs 410. Also used
|
||||
* internally by the submission flow, which additionally needs `eventId`.
|
||||
*/
|
||||
export const getEventConfigBySlug = async (slug: string): Promise<EventLookupResult> => {
|
||||
let conn = await NachklangFeedbackDB.getConnection();
|
||||
try {
|
||||
const eventQuery = `
|
||||
SELECT event_id, slug, name, subtitle, event_date, feedback_deadline, intro_text, poster_image_url, is_published
|
||||
FROM events WHERE slug = ?`;
|
||||
const eventRows = await conn.query(eventQuery, [slug]);
|
||||
if (eventRows.length === 0) {
|
||||
return {status: 'NOT_FOUND'};
|
||||
}
|
||||
const eventRow = eventRows[0];
|
||||
|
||||
const eligibleQuery = `
|
||||
SELECT 1 FROM events
|
||||
WHERE event_id = ? AND is_published = 1 AND event_date <= CURDATE() AND feedback_deadline >= NOW()`;
|
||||
const eligibleRows = await conn.query(eligibleQuery, [eventRow.event_id]);
|
||||
if (eligibleRows.length === 0) {
|
||||
return {status: 'CLOSED'};
|
||||
}
|
||||
|
||||
const songsQuery = 'SELECT song_id, title, composer, position FROM songs WHERE event_id = ? ORDER BY position ASC';
|
||||
const songRows = await conn.query(songsQuery, [eventRow.event_id]);
|
||||
const songs: Song[] = songRows.map((row: any) => ({
|
||||
songId: row.song_id,
|
||||
title: row.title,
|
||||
composer: row.composer,
|
||||
position: row.position
|
||||
}));
|
||||
|
||||
const questionsQuery = `
|
||||
SELECT eq.event_question_id, eq.position, q.question_id, q.question_type, q.label, q.help_text
|
||||
FROM event_questions eq
|
||||
INNER JOIN questions q ON q.question_id = eq.question_id
|
||||
WHERE eq.event_id = ? AND eq.is_active = 1
|
||||
ORDER BY eq.position ASC`;
|
||||
const questionRows = await conn.query(questionsQuery, [eventRow.event_id]);
|
||||
const questions: Question[] = questionRows.map((row: any) => ({
|
||||
eventQuestionId: row.event_question_id,
|
||||
questionId: row.question_id,
|
||||
type: row.question_type,
|
||||
label: row.label,
|
||||
helpText: row.help_text,
|
||||
position: row.position
|
||||
}));
|
||||
|
||||
return {
|
||||
status: 'OK',
|
||||
eventId: eventRow.event_id,
|
||||
event: {
|
||||
slug: eventRow.slug,
|
||||
name: eventRow.name,
|
||||
subtitle: eventRow.subtitle,
|
||||
eventDate: eventRow.event_date,
|
||||
feedbackDeadline: eventRow.feedback_deadline,
|
||||
posterImageUrl: eventRow.poster_image_url,
|
||||
introText: eventRow.intro_text,
|
||||
songs,
|
||||
questions
|
||||
}
|
||||
};
|
||||
} finally {
|
||||
await conn.end();
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user