diff --git a/app/controllers/admin_controller.rb b/app/controllers/admin_controller.rb index 529fcd6..b0d91a0 100644 --- a/app/controllers/admin_controller.rb +++ b/app/controllers/admin_controller.rb @@ -334,6 +334,28 @@ def update_creator_status render json: user, root: :data, status: :ok, meta: { message: "Creator status updated!" } end + def feature_course + course = Course.non_deleted_courses.find(feature_course_params[:course_id]) + + unless course.publish_status_published? && course.course_status_active? && !course.private? + raise Errors::BaseError.new(message: "Only published, active, public courses can be featured", status: 400) + end + + # Adding an already-featured course just refreshes its featured date (bumping it up the list) + course.update!(featured_at: Time.now) + + render json: course, root: :data, status: :ok, meta: { message: "Course added to featured list!" } + end + + def unfeature_course + course = Course.non_deleted_courses.find(feature_course_params[:course_id]) + + # Idempotent: removing a course that isn't featured is a no-op + course.update!(featured_at: nil) + + render json: course, root: :data, status: :ok, meta: { message: "Course removed from featured list!" } + end + def dummy_course_toggle course = Course.find(dummy_course_toggle_params[:course_id]) @@ -426,4 +448,8 @@ def update_creator_status_params def dummy_course_toggle_params params.permit(:course_id, :undo_dummy_status) end + + def feature_course_params + params.permit(:course_id) + end end diff --git a/app/controllers/auth_controller.rb b/app/controllers/auth_controller.rb index daa39ef..7fbf0be 100644 --- a/app/controllers/auth_controller.rb +++ b/app/controllers/auth_controller.rb @@ -226,7 +226,7 @@ def login_creator # If the user is not a creator or admin, they cannot login here if user.creator_status_none? && user.user_type != :admin - raise Errors::AuthenticationError.new(message: "You are not an approved creator, please use the usual StudyRound login") + raise Errors::AuthenticationError.new(message: "You are not a creator. Please visit https://app.studyround.com to onboard as a creator.") end if user && user.authenticate(login_params[:password]) diff --git a/app/controllers/courses_controller.rb b/app/controllers/courses_controller.rb index 6db4079..ce3f5c4 100644 --- a/app/controllers/courses_controller.rb +++ b/app/controllers/courses_controller.rb @@ -4,7 +4,7 @@ class CoursesController < ApplicationController include CardTransactionHelper before_action :default_12_page_size, only: [:index, :per_category, :enrolled_courses, :search, :my_courses, :tests, :purchased_courses, :purchased_tests, :created_courses] - skip_before_action :authorize!, only: [:index, :show, :categorised, :similar_courses, :top_courses, :trending_courses, :search, :dummy_courses] + skip_before_action :authorize!, only: [:index, :show, :categorised, :similar_courses, :top_courses, :trending_courses, :search, :dummy_courses, :featured_courses] before_action :check_creators_consent, only: [:create] before_action :load_creators_course, only: [:update, :publish, :destroy] @@ -57,7 +57,7 @@ def create raise Errors::ForbiddenError.new(message: "You have reached the maximum number of courses you can create") end else - # Do nothing + raise Errors::ForbiddenError.new(message: "You are not a creator. Please visit https://app.studyround.com to onboard as a creator.") end course_params = prepare_received_course_params(create_course_params) @@ -387,6 +387,14 @@ def dummy_courses render json: paginated_courses, root: :data, meta: paginated_meta(paginated_courses) end + def featured_courses + # Curated courses for the dashboard, ordered by most recently featured + courses = Course.featured_courses + + paginated_courses = paginate(courses, params) + render json: paginated_courses, root: :data, meta: paginated_meta(paginated_courses) + end + private def load_creators_course diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb index e8adc0d..725cb6e 100644 --- a/app/controllers/questions_controller.rb +++ b/app/controllers/questions_controller.rb @@ -116,7 +116,7 @@ def create raise Errors::ForbiddenError.new(message: "You have reached the maximum number of questions for this course") end else - # Do nothing + raise Errors::ForbiddenError.new(message: "You are not a creator. Please visit https://app.studyround.com to onboard as a creator.") end draft = create_draft(create_question_params) diff --git a/app/models/course.rb b/app/models/course.rb index a6ff9a5..4a4f14d 100644 --- a/app/models/course.rb +++ b/app/models/course.rb @@ -40,6 +40,9 @@ class Course < ApplicationRecord scope :published_active_or_dummy_courses, -> { published_active_courses.or(dummy_courses) } + # Curated, admin-featured courses for the dashboard. Ordered by most recently featured first. + scope :featured_courses, -> { published_active_courses.where.not(featured_at: nil).order(featured_at: :desc) } + scope :filtered_by_search, -> (search) { where('title ILIKE ?', "%#{search}%") } # This doesn't provide unique results, so we're using the other one. Using distinct also fails on some queries. diff --git a/config/routes.rb b/config/routes.rb index 9cd4804..b39d860 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -31,6 +31,7 @@ end get '/courses/categorised', to: "courses#categorised" + get '/courses/featured', to: "courses#featured_courses" get '/courses/top', to: "courses#top_courses" get '/courses/trending', to: "courses#trending_courses" get '/courses/recent', to: "courses#recent_courses" @@ -155,6 +156,8 @@ patch '/admin/update-result', to: "admin#update_result" patch '/admin/update-creator-status', to: "admin#update_creator_status" patch '/admin/dummy-course-toggle', to: "admin#dummy_course_toggle" + post '/admin/feature-course', to: "admin#feature_course" + delete '/admin/feature-course', to: "admin#unfeature_course" post '/automation/assign-course', to: "automation#assign_course" post '/automation/create-course', to: "automation#create_course" diff --git a/db/migrate/20260607175212_add_featured_at_to_courses.rb b/db/migrate/20260607175212_add_featured_at_to_courses.rb new file mode 100644 index 0000000..a5abd67 --- /dev/null +++ b/db/migrate/20260607175212_add_featured_at_to_courses.rb @@ -0,0 +1,5 @@ +class AddFeaturedAtToCourses < ActiveRecord::Migration[5.2] + def change + add_column :courses, :featured_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 318e260..6190551 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2026_03_08_142256) do +ActiveRecord::Schema.define(version: 2026_06_07_175212) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -136,6 +136,7 @@ t.datetime "last_publish_date" t.integer "rating_count", default: 0 t.boolean "invite_only", default: false + t.datetime "featured_at" t.index ["creator_id"], name: "index_courses_on_creator_id" t.index ["title"], name: "index_courses_on_title" end