diff --git a/backend/src/db/migrations/20260217000001-make-courses-public.js b/backend/src/db/migrations/20260217000001-make-courses-public.js new file mode 100644 index 0000000..30c2b2c --- /dev/null +++ b/backend/src/db/migrations/20260217000001-make-courses-public.js @@ -0,0 +1,71 @@ + +module.exports = { + async up(queryInterface, Sequelize) { + const [publicRole] = await queryInterface.sequelize.query( + "SELECT id FROM roles WHERE name = 'Public' LIMIT 1;", + { type: Sequelize.QueryTypes.SELECT } + ); + const [readCoursesPermission] = await queryInterface.sequelize.query( + "SELECT id FROM permissions WHERE name = 'READ_COURSES' LIMIT 1;", + { type: Sequelize.QueryTypes.SELECT } + ); + const [readCourseCategoriesPermission] = await queryInterface.sequelize.query( + "SELECT id FROM permissions WHERE name = 'READ_COURSE_CATEGORIES' LIMIT 1;", + { type: Sequelize.QueryTypes.SELECT } + ); + + if (publicRole) { + const permissionIds = [ + readCoursesPermission?.id, + readCourseCategoriesPermission?.id, + ].filter(Boolean); + + for (const permissionId of permissionIds) { + const [existing] = await queryInterface.sequelize.query( + `SELECT * FROM "rolesPermissionsPermissions" WHERE "roles_permissionsId" = '${publicRole.id}' AND "permissionId" = '${permissionId}' LIMIT 1;`, + { type: Sequelize.QueryTypes.SELECT } + ); + + if (!existing) { + await queryInterface.bulkInsert('rolesPermissionsPermissions', [ + { + createdAt: new Date(), + updatedAt: new Date(), + roles_permissionsId: publicRole.id, + permissionId, + }, + ]); + } + } + } + }, + + async down(queryInterface, Sequelize) { + const [publicRole] = await queryInterface.sequelize.query( + "SELECT id FROM roles WHERE name = 'Public' LIMIT 1;", + { type: Sequelize.QueryTypes.SELECT } + ); + const [readCoursesPermission] = await queryInterface.sequelize.query( + "SELECT id FROM permissions WHERE name = 'READ_COURSES' LIMIT 1;", + { type: Sequelize.QueryTypes.SELECT } + ); + const [readCourseCategoriesPermission] = await queryInterface.sequelize.query( + "SELECT id FROM permissions WHERE name = 'READ_COURSE_CATEGORIES' LIMIT 1;", + { type: Sequelize.QueryTypes.SELECT } + ); + + if (publicRole) { + const permissionIds = [ + readCoursesPermission?.id, + readCourseCategoriesPermission?.id, + ].filter(Boolean); + + if (permissionIds.length > 0) { + await queryInterface.bulkDelete('rolesPermissionsPermissions', { + roles_permissionsId: publicRole.id, + permissionId: permissionIds, + }); + } + } + } +}; diff --git a/backend/src/index.js b/backend/src/index.js index 9862e5b..5d477a7 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -123,9 +123,15 @@ app.use('/api/roles', passport.authenticate('jwt', {session: false}), rolesRoute app.use('/api/permissions', passport.authenticate('jwt', {session: false}), permissionsRoutes); -app.use('/api/courses', passport.authenticate('jwt', {session: false}), coursesRoutes); +app.use('/api/courses', (req, res, next) => { + if (req.method === 'GET') return next(); + return passport.authenticate('jwt', { session: false })(req, res, next); +}, coursesRoutes); -app.use('/api/course_categories', passport.authenticate('jwt', {session: false}), course_categoriesRoutes); +app.use('/api/course_categories', (req, res, next) => { + if (req.method === 'GET') return next(); + return passport.authenticate('jwt', { session: false })(req, res, next); +}, course_categoriesRoutes); app.use('/api/lessons', passport.authenticate('jwt', {session: false}), lessonsRoutes); diff --git a/backend/src/middlewares/check-permissions.js b/backend/src/middlewares/check-permissions.js index 77740c7..fab42f6 100644 --- a/backend/src/middlewares/check-permissions.js +++ b/backend/src/middlewares/check-permissions.js @@ -1,37 +1,5 @@ const ValidationError = require('../services/notifications/errors/validation'); -const RolesDBApi = require('../db/api/roles'); - -// Cache for the 'Public' role object -let publicRoleCache = null; - -// Function to asynchronously fetch and cache the 'Public' role -async function fetchAndCachePublicRole() { - try { - // Use RolesDBApi to find the role by name 'Public' - publicRoleCache = await RolesDBApi.findBy({ name: 'Public' }); - - if (!publicRoleCache) { - console.error("WARNING: Role 'Public' not found in database during middleware startup. Check your migrations."); - // The system might not function correctly without this role. May need to throw an error or use a fallback stub. - } else { - console.log("'Public' role successfully loaded and cached."); - } - } catch (error) { - console.error("Error fetching 'Public' role during middleware startup:", error); - // Handle the error during startup fetch - throw error; // Important to know if the app can proceed without the Public role - } -} - -// Trigger the role fetching when the check-permissions.js module is imported/loaded -// This should happen during application startup when routes are being configured. -fetchAndCachePublicRole().catch(error => { - // Handle the case where the fetchAndCachePublicRole promise is rejected - console.error("Critical error during permissions middleware initialization:", error); - // Decide here if the process should exit if the Public role is essential. - // process.exit(1); -}); /** * Middleware creator to check if the current user (or Public role) has a specific permission. @@ -61,34 +29,13 @@ function checkPermissions(permission) { } } - // 3. Determine the "effective" role for permission check - let effectiveRole = null; - try { - if (currentUser && currentUser.app_role) { - // User is authenticated and has an assigned role - effectiveRole = currentUser.app_role; - } else { - // User is NOT authenticated OR is authenticated but has no role - // Use the cached 'Public' role - if (!publicRoleCache) { - // If the cache is unexpectedly empty (e.g., startup error caught), - // we can try fetching the role again synchronously (less ideal) or just deny access. - console.error("Public role cache is empty. Attempting synchronous fetch..."); - // Less efficient fallback option: - effectiveRole = await RolesDBApi.findBy({ name: 'Public' }); // Could be slow - if (!effectiveRole) { - // If even the synchronous attempt failed - return next(new Error("Internal Server Error: Public role missing and cannot be fetched.")); - } - } else { - effectiveRole = publicRoleCache; // Use the cached object - } - } + // 3. Determine the authenticated role for permission check + const effectiveRole = currentUser && currentUser.app_role ? currentUser.app_role : null; + if (!effectiveRole) { + return next(new ValidationError('auth.forbidden', 'Role missing for permission check.')); + } - // Check if we got a valid role object - if (!effectiveRole) { - return next(new Error("Internal Server Error: Could not determine effective role.")); - } + try { // 4. Check Permissions on the "effective" role // Assume the effectiveRole object (from app_role or RolesDBApi) has a getPermissions() method @@ -146,4 +93,3 @@ module.exports = { checkPermissions, checkCrudPermissions, }; - diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 502a3a5..ea7e223 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -3,164 +3,268 @@ import React, { useEffect, useState } from 'react'; import type { ReactElement } from 'react'; import Head from 'next/head'; import Link from 'next/link'; +import axios from 'axios'; +import { mdiBookOpenVariant, mdiAccountGroup, mdiStar, mdiChevronRight } from '@mdi/js'; import BaseButton from '../components/BaseButton'; -import CardBox from '../components/CardBox'; -import SectionFullScreen from '../components/SectionFullScreen'; +import BaseIcon from '../components/BaseIcon'; import LayoutGuest from '../layouts/Guest'; -import BaseDivider from '../components/BaseDivider'; -import BaseButtons from '../components/BaseButtons'; import { getPageTitle } from '../config'; import { useAppSelector } from '../stores/hooks'; -import CardBoxComponentTitle from "../components/CardBoxComponentTitle"; -import { getPexelsImage, getPexelsVideo } from '../helpers/pexels'; +import { getMultiplePexelsImages } from '../helpers/pexels'; +export default function LandingPage() { + const [courses, setCourses] = useState([]); + const [loading, setLoading] = useState(true); + const [images, setImages] = useState([]); + const textColor = useAppSelector((state) => state.style.linkColor); + const { currentUser } = useAppSelector((state) => state.auth); -export default function Starter() { - const [illustrationImage, setIllustrationImage] = useState({ - src: undefined, - photographer: undefined, - photographer_url: undefined, - }) - const [illustrationVideo, setIllustrationVideo] = useState({video_files: []}) - const [contentType, setContentType] = useState('image'); - const [contentPosition, setContentPosition] = useState('left'); - const textColor = useAppSelector((state) => state.style.linkColor); - - const title = 'Course LMS' - - // Fetch Pexels image/video - useEffect(() => { - async function fetchData() { - const image = await getPexelsImage(); - const video = await getPexelsVideo(); - setIllustrationImage(image); - setIllustrationVideo(video); - } - fetchData(); - }, []); - - const imageBlock = (image) => ( -
- ); - - const videoBlock = (video) => { - if (video?.video_files?.length > 0) { - return ( -This is a React.js/Node.js app generated by the Flatlogic Web App Generator
-For guides and documentation please check - your local README.md and the Flatlogic documentation
-+ Master new skills with our professional courses led by industry experts. Join thousands of students already learning today. +
+© 2026 {title}. All rights reserved
- - Privacy Policy - -Courses
+Students
+Rating
+Discover our most popular courses and start your journey today.
+{course.description || course.subtitle}
+We're working on adding new content. Check back soon!
+ {currentUser && currentUser.app_role?.name === 'Instructor' && ( +Learn from professionals who are active in their fields and passionate about teaching.
+Access your courses anytime, anywhere. Learn at your own pace on any device.
+Once you enroll, the content is yours forever. Stay updated with free course updates.
+Join thousands of students and transform your career today with our expert-led courses.
+