Compare commits

..
7 Commits
Author SHA1 Message Date
joshuaandClaude Opus 5.5 7e661abd24 Bump to 1.10.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:46:36 +02:00
joshuaandClaude Opus 5.5 1f59916efa Show in-progress events on home and events pages
Filter by endDate instead of startDate so events stay listed until they finish.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 14:46:04 +02:00
joshuaandClaude Sonnet 5 e728a06b9a Bump to 1.10.4
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PAwumFeBwx6XSsQXp8Nfqt
2026-09-01 09:04:31 +02:00
joshuaandClaude Sonnet 5 1d9b87500d Fix "Add to calendar" 400 when FRONTEND_URL lacks a scheme
The ics library's url validator requires a scheme (https://...), but
production's FRONTEND_URL/APP_BASE_URL was configured without one, so
createEvent() rejected every request before generating a .ics file.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PAwumFeBwx6XSsQXp8Nfqt
2026-09-01 09:00:17 +02:00
joshua 97faff1c89 Merge pull request 'Log event creation to admin audit log, add back button, bump to 1.10.3' (#3) from fix/event-create-audit-log-and-back-button into main 2026-08-28 14:49:24 +02:00
joshuaandClaude Sonnet 5 5b2183677d Also log staff cancellations via PUT /api/registrations/:id
Registrations can be cancelled two ways: DELETE /:id (owner or admin,
already logged registration_cancelled) and PUT /:id (staff+, status
change endpoint) — the latter was silently unlogged. Since PUT /:id
is staff-only, any transition into 'cancelled' there is inherently a
staff-initiated cancellation, so it's now logged the same way.

Audited all six categories promised in the 1.10.0 changelog entry
(refunds, donation assign/unassign, manual registrations,
staff-initiated cancellations, event create/update/delete, settings
changes) against their actual logAdminAction call sites and route
wiring — this was the only other gap found.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSWFWQsjTc9GyffPiXEDQT
2026-08-28 14:48:17 +02:00
joshuaandClaude Sonnet 5 af6dffe534 Log event creation to admin audit log, add back button, bump to 1.10.3
createEvent never called logAdminAction, even though 1.10.0 already
listed event_created as a filterable action on the audit-log page —
only updateEvent/deleteEvent actually logged. Event creation is now
logged the same way, at every return path including the legacy
pre-migration retry branches.

Also adds a "Back to dashboard" link to Admin -> Audit log, matching
the existing back-link pattern on the Cashup page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CSWFWQsjTc9GyffPiXEDQT
2026-08-28 14:44:23 +02:00
12 changed files with 70 additions and 13 deletions
+20
View File
@@ -7,6 +7,26 @@ and this project follows [Semantic Versioning](https://semver.org/).
## [Unreleased]
## [1.10.5] - 2026-10-01
### Fixed
- Events that have already started but not yet finished now show on the home page and the Events page. Both pages were hiding an event as soon as its start time passed; they now keep it listed until its end time, which matches what `GET /api/events` already returns.
## [1.10.4] - 2026-09-01
### Fixed
- "Add to calendar" (`GET /api/events/:id/ics`) always failed with a 400 in production: the `ics` library's `url` field requires a scheme (`https://...`), but `FRONTEND_URL`/`APP_BASE_URL` was configured without one, so `createEvent()` rejected it before generating any output. A missing scheme is now assumed to be `https://` instead of failing.
## [1.10.3] - 2026-08-28
### Fixed
- Creating an event was never recorded in the admin audit log — 1.10.0 added `event_created` as a logged action on the frontend's filter list, but the backend's `createEvent` never actually called `logAdminAction`, only `updateEvent`/`deleteEvent` did. Event creation is now logged the same way.
- Staff cancelling a registration via `PUT /api/registrations/:id` (the staff status-change endpoint, separate from the owner-facing `DELETE /:id` cancel route) wasn't logged at all — only the `DELETE` path logged `registration_cancelled`. Both paths now log it, verified against all six audit categories promised in 1.10.0 (refunds, donation assign/unassign, manual registrations, staff-initiated cancellations, event create/update/delete, settings changes) with no other gaps found.
- Added a "Back to dashboard" link to Admin → Audit log, matching the back-link pattern already used on the Cashup page.
## [1.10.2] - 2026-08-28
### Fixed
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "event-management-backend",
"version": "1.10.2",
"version": "1.10.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "event-management-backend",
"version": "1.10.2",
"version": "1.10.5",
"hasInstallScript": true,
"dependencies": {
"@prisma/client": "^5.4.2",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "event-management-backend",
"version": "1.10.2",
"version": "1.10.5",
"description": "Event Management System Backend",
"main": "src/index.js",
"scripts": {
+21 -1
View File
@@ -68,8 +68,19 @@ const createEvent = async (req, res) => {
location: location || null,
};
const logEventCreate = (createdEvent) => logAdminAction({
actorId: req.user?.id,
actorRole: req.user?.role,
action: 'event_created',
targetType: 'Event',
targetId: createdEvent.id,
metadata: { title: createdEvent.title },
ip: getClientIp(req),
});
try {
const event = await prisma.event.create({ data });
logEventCreate(event);
// Automatically create a main ticket (event option) with the event price — contact-only
// events have no bookable options, so there's nothing to auto-create for them.
@@ -133,24 +144,28 @@ const createEvent = async (req, res) => {
// @ts-ignore delete field and retry
delete data.registrationDeadline;
const event = await prisma.event.create({ data });
logEventCreate(event);
return res.status(201).json(event);
}
if (msg.includes('Unknown argument `goLiveAt`')) {
// @ts-ignore delete field and retry
delete data.goLiveAt;
const event = await prisma.event.create({ data });
logEventCreate(event);
return res.status(201).json(event);
}
if (msg.includes('Unknown argument `createdById`')) {
// @ts-ignore delete field and retry
delete data.createdById;
const event = await prisma.event.create({ data });
logEventCreate(event);
return res.status(201).json(event);
}
if (msg.includes('Unknown argument `redirectUrl`')) {
// @ts-ignore delete field and retry
delete data.redirectUrl;
const event = await prisma.event.create({ data });
logEventCreate(event);
return res.status(201).json(event);
}
throw err;
@@ -478,7 +493,12 @@ const getEventIcs = async (req, res) => {
}
const { buildEventIcs } = require('../utils/icsUtils');
const frontendUrl = (process.env.FRONTEND_URL || process.env.APP_BASE_URL || 'http://localhost:3001').replace(/\/$/, '');
let frontendUrl = (process.env.FRONTEND_URL || process.env.APP_BASE_URL || 'http://localhost:3001').replace(/\/$/, '');
// The ics library's url validator requires a scheme; guard against FRONTEND_URL/APP_BASE_URL
// being configured without one (e.g. "example.com" instead of "https://example.com").
if (!/^[a-z0-9+.-]+:\/\//i.test(frontendUrl)) {
frontendUrl = `https://${frontendUrl}`;
}
const ics = buildEventIcs(event, `${frontendUrl}/events/${event.id}`);
res.setHeader('Content-Type', 'text/calendar; charset=utf-8');
@@ -626,6 +626,22 @@ const updateRegistrationStatus = async (req, res) => {
}
});
// This is the staff-only status-change endpoint (separate from the owner-facing
// DELETE /:id cancel route), so any transition into 'cancelled' here is always a
// staff-initiated cancellation — log it the same way DELETE /:id does, so both
// paths land under the one 'registration_cancelled' filter in the audit log.
if (status === 'cancelled' && registration.status !== 'cancelled') {
logAdminAction({
actorId: req.user.id,
actorRole: req.user.role,
action: 'registration_cancelled',
targetType: 'Registration',
targetId: req.params.id,
metadata: { registrationOwnerId: registration.userId, previousStatus: registration.status },
ip: getClientIp(req),
});
}
// Generate tickets and email them when status is manually set to 'paid' by staff
if (status === 'paid') {
(async () => {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "hope-events-frontend",
"version": "1.10.2",
"version": "1.10.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "hope-events-frontend",
"version": "1.10.2",
"version": "1.10.5",
"dependencies": {
"@hookform/resolvers": "^5.2.1",
"@radix-ui/react-accordion": "^1.2.11",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hope-events-frontend",
"version": "1.10.2",
"version": "1.10.5",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
@@ -113,7 +113,8 @@ export default function AdminAuditLogPage() {
<History className="w-5 h-5 text-brand-600" />
</div>
<div>
<h1 className="text-2xl font-semibold text-gray-900">Admin Audit Log</h1>
<button className="text-xs text-brand-600 hover:underline" onClick={() => router.push("/dashboard/admin")}>← Back to dashboard</button>
<h1 className="text-2xl font-semibold text-gray-900 mt-0.5">Admin Audit Log</h1>
<p className="text-sm text-gray-500">{total} action{total !== 1 ? "s" : ""} recorded</p>
</div>
</div>
+1 -1
View File
@@ -31,7 +31,7 @@ export default async function EventsPage() {
const now = Date.now();
const upcoming = (events || []).filter(e => {
if (e.cashupStatus === "closed") return false;
const t = new Date(e.startDate).getTime();
const t = new Date(e.endDate || e.startDate).getTime();
return !isNaN(t) && t > now;
});
const sorted = [...upcoming].sort((a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime());
+1 -1
View File
@@ -45,7 +45,7 @@ export default async function HomePage() {
]);
const now = Date.now();
const upcoming = (events || []).filter(e => {
const t = new Date(e.startDate).getTime();
const t = new Date(e.endDate || e.startDate).getTime();
return !isNaN(t) && t > now;
});
const sorted = [...upcoming].sort((a, b) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime());
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "hope-events",
"version": "1.10.2",
"version": "1.10.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "hope-events",
"version": "1.10.2",
"version": "1.10.5",
"license": "ISC",
"devDependencies": {
"concurrently": "^9.2.1"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "hope-events",
"version": "1.10.2",
"version": "1.10.5",
"main": "index.js",
"scripts": {
"dev:backend": "cd backend && npm run dev",