feat: allow custom layouts when creating tours

Courses without scraped layouts can now be used in tours by entering
a layout name and par manually. The layout is saved to the database
for reuse. All courses are shown in the dropdown, not just those with
existing layouts.
This commit is contained in:
Samuel Enocsson
2026-03-20 07:26:31 +01:00
parent 2ccb018bdf
commit 38cc93bc1c
4 changed files with 161 additions and 28 deletions
+23
View File
@@ -69,10 +69,33 @@ function updateLayoutRating(courseId, layoutName, par, meanRating, ratingCount,
});
}
function getOrCreateLayout(courseId, name, par) {
return new Promise((resolve, reject) => {
db.get(
'SELECT id FROM layouts WHERE course_id = ? AND name = ? AND par = ?',
[courseId, name, par],
(err, row) => {
if (err) return reject(err);
if (row) return resolve(row.id);
db.run(
'INSERT INTO layouts (course_id, name, par) VALUES (?, ?, ?)',
[courseId, name, par],
function(err) {
if (err) reject(err);
else resolve(this.lastID);
}
);
}
);
});
}
module.exports = {
saveCourseToDB,
getAllCoursesFromDB,
saveLayoutToDB,
getLayoutsForCourse,
getOrCreateLayout,
updateLayoutRating
};
+17 -8
View File
@@ -1,6 +1,6 @@
const express = require('express');
const router = express.Router();
const { getAllCoursesFromDB, getLayoutsForCourse } = require('../models/course');
const { getAllCoursesFromDB, getLayoutsForCourse, getOrCreateLayout } = require('../models/course');
const {
createTour, getTourByCode, addCourseToTour, getTourCourses,
getTourCourseById, joinTour, getPlayerInTour, recordResult
@@ -25,7 +25,18 @@ router.post('/api/tours', async (req, res) => {
const tourId = await createTour(name, code, startDate, endDate);
for (const course of courses) {
await addCourseToTour(tourId, course.courseId, course.layoutId);
let layoutId = course.layoutId;
// Create new layout if name and par provided instead of layoutId
if (!layoutId && course.layoutName && course.par) {
layoutId = await getOrCreateLayout(course.courseId, course.layoutName, parseInt(course.par));
}
if (!layoutId) {
return res.status(400).json({ error: 'Each course needs a layout (existing or new with name and par)' });
}
await addCourseToTour(tourId, course.courseId, layoutId);
}
logger.info(`Tour created: "${name}" (${code}) with ${courses.length} courses`);
@@ -121,20 +132,18 @@ router.get('/partials/tour-leaderboard/:code', async (req, res) => {
}
});
// Get courses and layouts for tour creation form
// Get all courses with their layouts (if any) for tour creation form
router.get('/api/tours/courses-with-layouts', async (req, res) => {
try {
const courses = await getAllCoursesFromDB();
const coursesWithLayouts = [];
const result = [];
for (const course of courses) {
const layouts = await getLayoutsForCourse(course.id);
if (layouts.length > 0) {
coursesWithLayouts.push({ ...course, layouts });
}
result.push({ ...course, layouts });
}
res.json(coursesWithLayouts);
res.json(result);
} catch (error) {
logger.error('Error fetching courses with layouts:', error.message);
res.status(500).json({ error: 'Failed to fetch courses' });