Add posterImageUrl to events
Concert posters already live on the public website, so events store a URL to an existing image rather than hosting an uploaded file.
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
-- Nachklang e.V. Feedback module — adds a per-event poster image URL.
|
||||
-- Apply manually against the FEEDBACK_DB database, after 001_init.sql:
|
||||
-- mysql -h <DB_HOST> -u <DB_USER> -p <FEEDBACK_DB> < 002_add_poster_image_url.sql
|
||||
--
|
||||
-- Stores a URL only (e.g. an existing nachklang.art poster image) rather
|
||||
-- than an uploaded file — the concert posters already live on the public
|
||||
-- website, so there is no need for the feedback app to host its own copy.
|
||||
ALTER TABLE events
|
||||
ADD COLUMN poster_image_url VARCHAR(500) NULL AFTER intro_text;
|
||||
@@ -33,6 +33,9 @@
|
||||
* feedbackDeadline:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* posterImageUrl:
|
||||
* type: string
|
||||
* nullable: true
|
||||
* isPublished:
|
||||
* type: boolean
|
||||
* submissionCount:
|
||||
@@ -87,6 +90,7 @@ export interface EventAdminSummary {
|
||||
subtitle: string | null;
|
||||
eventDate: string;
|
||||
feedbackDeadline: string;
|
||||
posterImageUrl: string | null;
|
||||
isPublished: boolean;
|
||||
submissionCount: number;
|
||||
}
|
||||
@@ -110,6 +114,7 @@ export interface CreateEventInput {
|
||||
eventDate: string;
|
||||
feedbackDeadline?: string;
|
||||
introText?: string;
|
||||
posterImageUrl?: string;
|
||||
}
|
||||
|
||||
export interface UpdateEventInput {
|
||||
@@ -119,6 +124,7 @@ export interface UpdateEventInput {
|
||||
feedbackDeadline?: string;
|
||||
isPublished?: boolean;
|
||||
introText?: string;
|
||||
posterImageUrl?: string;
|
||||
}
|
||||
|
||||
export interface AdminQuestion {
|
||||
|
||||
@@ -59,6 +59,8 @@ export const eventsAdminRouter = express.Router();
|
||||
* format: date-time
|
||||
* introText:
|
||||
* type: string
|
||||
* posterImageUrl:
|
||||
* type: string
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Created
|
||||
@@ -77,13 +79,13 @@ eventsAdminRouter.get('/', async (req: Request, res: Response) => {
|
||||
|
||||
eventsAdminRouter.post('/', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const {name, subtitle, eventDate, feedbackDeadline, introText} = req.body || {};
|
||||
const {name, subtitle, eventDate, feedbackDeadline, introText, posterImageUrl} = req.body || {};
|
||||
if (!name || !eventDate) {
|
||||
res.status(400).send({status: 'BAD_REQUEST', message: 'name and eventDate are required'});
|
||||
return;
|
||||
}
|
||||
const eventId = await EventsAdminService.createEvent(
|
||||
{name, subtitle, eventDate, feedbackDeadline, introText},
|
||||
{name, subtitle, eventDate, feedbackDeadline, introText, posterImageUrl},
|
||||
res.locals.admin.email
|
||||
);
|
||||
res.status(201).send({eventId});
|
||||
|
||||
@@ -38,6 +38,7 @@ const mapSummaryRow = (row: any): EventAdminSummary => ({
|
||||
subtitle: row.subtitle,
|
||||
eventDate: row.event_date,
|
||||
feedbackDeadline: row.feedback_deadline,
|
||||
posterImageUrl: row.poster_image_url,
|
||||
isPublished: !!row.is_published,
|
||||
submissionCount: Number(row.submission_count)
|
||||
});
|
||||
@@ -94,11 +95,11 @@ export const createEvent = async (input: CreateEventInput, createdByEmail: strin
|
||||
: computeDefaultDeadline(input.eventDate);
|
||||
|
||||
const query = `
|
||||
INSERT INTO events (slug, name, subtitle, event_date, feedback_deadline, intro_text, created_by_email)
|
||||
VALUES (?,?,?,?,?,?,?) RETURNING event_id`;
|
||||
INSERT INTO events (slug, name, subtitle, event_date, feedback_deadline, intro_text, poster_image_url, created_by_email)
|
||||
VALUES (?,?,?,?,?,?,?,?) RETURNING event_id`;
|
||||
const res = await conn.query(query, [
|
||||
slug, input.name, input.subtitle || null, input.eventDate, formatDatetime(deadline),
|
||||
input.introText || null, createdByEmail
|
||||
input.introText || null, input.posterImageUrl || null, createdByEmail
|
||||
]);
|
||||
await conn.commit();
|
||||
return res[0].event_id;
|
||||
@@ -154,6 +155,7 @@ export const updateEvent = async (eventId: number, input: UpdateEventInput): Pro
|
||||
if (input.feedbackDeadline !== undefined) { fields.push('feedback_deadline = ?'); values.push(input.feedbackDeadline); }
|
||||
if (input.isPublished !== undefined) { fields.push('is_published = ?'); values.push(input.isPublished ? 1 : 0); }
|
||||
if (input.introText !== undefined) { fields.push('intro_text = ?'); values.push(input.introText); }
|
||||
if (input.posterImageUrl !== undefined) { fields.push('poster_image_url = ?'); values.push(input.posterImageUrl || null); }
|
||||
|
||||
if (fields.length === 0) return true;
|
||||
|
||||
|
||||
@@ -62,6 +62,10 @@
|
||||
* feedbackDeadline:
|
||||
* type: string
|
||||
* format: date-time
|
||||
* posterImageUrl:
|
||||
* type: string
|
||||
* nullable: true
|
||||
* example: "https://www.nachklang.art/img/nk/image-20260727-214855-851.jpeg"
|
||||
* EventConfig:
|
||||
* allOf:
|
||||
* - $ref: '#/components/schemas/EventSummary'
|
||||
@@ -116,6 +120,7 @@ export interface EventSummary {
|
||||
subtitle: string | null;
|
||||
eventDate: string;
|
||||
feedbackDeadline: string;
|
||||
posterImageUrl: string | null;
|
||||
}
|
||||
|
||||
export interface EventConfig extends EventSummary {
|
||||
|
||||
@@ -9,7 +9,7 @@ export const getEligibleEvents = async (): Promise<EventSummary[]> => {
|
||||
let conn = await NachklangFeedbackDB.getConnection();
|
||||
try {
|
||||
const query = `
|
||||
SELECT slug, name, subtitle, event_date, feedback_deadline
|
||||
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`;
|
||||
@@ -19,7 +19,8 @@ export const getEligibleEvents = async (): Promise<EventSummary[]> => {
|
||||
name: row.name,
|
||||
subtitle: row.subtitle,
|
||||
eventDate: row.event_date,
|
||||
feedbackDeadline: row.feedback_deadline
|
||||
feedbackDeadline: row.feedback_deadline,
|
||||
posterImageUrl: row.poster_image_url
|
||||
}));
|
||||
} finally {
|
||||
await conn.end();
|
||||
@@ -41,7 +42,7 @@ export const getEventConfigBySlug = async (slug: string): Promise<EventLookupRes
|
||||
let conn = await NachklangFeedbackDB.getConnection();
|
||||
try {
|
||||
const eventQuery = `
|
||||
SELECT event_id, slug, name, subtitle, event_date, feedback_deadline, intro_text, is_published
|
||||
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) {
|
||||
@@ -91,6 +92,7 @@ export const getEventConfigBySlug = async (slug: string): Promise<EventLookupRes
|
||||
subtitle: eventRow.subtitle,
|
||||
eventDate: eventRow.event_date,
|
||||
feedbackDeadline: eventRow.feedback_deadline,
|
||||
posterImageUrl: eventRow.poster_image_url,
|
||||
introText: eventRow.intro_text,
|
||||
songs,
|
||||
questions
|
||||
|
||||
Reference in New Issue
Block a user