diff --git a/app/controllers/v1/study_room_presences_controller.rb b/app/controllers/v1/study_room_presences_controller.rb new file mode 100644 index 00000000..ec82c76c --- /dev/null +++ b/app/controllers/v1/study_room_presences_controller.rb @@ -0,0 +1,2 @@ +class V1::StudyRoomPresencesController < V1::ApplicationController +end diff --git a/app/models/study_room_presence.rb b/app/models/study_room_presence.rb new file mode 100644 index 00000000..a6ae4ecc --- /dev/null +++ b/app/models/study_room_presence.rb @@ -0,0 +1,20 @@ +class StudyRoomPresence < ApplicationRecord + belongs_to :user + + validates :start_time, presence: true + validates :end_time, presence: true + validates_datetime :end_time, after: :start_time + validates :status, inclusion: { in: %w[chilling studying banaan] } + + scope :current, (lambda { + where('start_time <= :current_time AND end_time >= :current_time', + current_time: Time.current) + }) + scope :future, (lambda { + where('start_time >= :current_time', current_time: Time.current) + }) + scope :current_and_future, (lambda { + where('(start_time <= :current_time AND end_time >= :current_time) +or (start_time >= :current_time)', current_time: Time.current) + }) +end diff --git a/app/models/user.rb b/app/models/user.rb index 500c4ca9..d5029281 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -16,6 +16,7 @@ class User < ApplicationRecord # rubocop:disable Metrics/ClassLength has_many :user_permissions, through: :permissions_users, source: :permission has_many :article_comments, foreign_key: :author_id has_many :board_room_presences, dependent: :delete_all + has_many :study_room_presences, dependent: :delete_all has_many :photo_comments, foreign_key: :author_id has_many :created_photo_tags, class_name: 'PhotoTag', foreign_key: :author_id, dependent: :delete_all @@ -214,8 +215,8 @@ def allow_tomato_sharing_valid? return unless allow_tomato_sharing_changed?(from: true, to: false) errors.add(:allow_tomato_sharing, - 'before being removed from tomato your credits needs to be zero. - Please ask the board to be removed from tomato.') + 'before being removed from sofia your credits needs to be zero. + Please ask the board to be removed from sofia.') end def generate_ical_secret_key diff --git a/app/policies/study_room_presence_policy.rb b/app/policies/study_room_presence_policy.rb new file mode 100644 index 00000000..bfc78ee2 --- /dev/null +++ b/app/policies/study_room_presence_policy.rb @@ -0,0 +1,2 @@ +class StudyRoomPresencePolicy < ApplicationPolicy +end diff --git a/app/resources/v1/study_room_presence_resource.rb b/app/resources/v1/study_room_presence_resource.rb new file mode 100644 index 00000000..c2e7d00d --- /dev/null +++ b/app/resources/v1/study_room_presence_resource.rb @@ -0,0 +1,17 @@ +class V1::StudyRoomPresenceResource < V1::ApplicationResource + attributes :start_time, :end_time, :status + + has_one :user, always_include_linkage_data: true + + filter :current, apply: ->(records, _value, _options) { records.current } + filter :future, apply: ->(records, _value, _options) { records.future } + filter :current_and_future, apply: ->(records, _value, _options) { records.current_and_future } + + before_create do + @model.user_id = current_user.id + end + + def self.creatable_fields(_context) + %i[start_time end_time status] + end +end diff --git a/config/routes.rb b/config/routes.rb index 20b919f4..d0299850 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,6 +20,7 @@ jsonapi_resources :articles jsonapi_resources :article_comments jsonapi_resources :board_room_presences + jsonapi_resources :study_room_presences jsonapi_resources :books do collection do get :isbn_lookup diff --git a/db/migrate/20241027103012_create_study_room_presence.rb b/db/migrate/20241027103012_create_study_room_presence.rb new file mode 100644 index 00000000..d7133dc2 --- /dev/null +++ b/db/migrate/20241027103012_create_study_room_presence.rb @@ -0,0 +1,17 @@ +class CreateStudyRoomPresence < ActiveRecord::Migration[7.0] + def change + create_table :study_room_presences do |t| + t.datetime :start_time, null: false + t.datetime :end_time, null: false + t.text :status, null: false + t.integer :user_id, null: false + t.datetime :deleted_at + t.timestamps + end + end + + Permission.create(name: 'study_room_presence.create') + Permission.create(name: 'study_room_presence.read') + Permission.create(name: 'study_room_presence.update') + Permission.create(name: 'study_room_presence.destroy') +end diff --git a/db/schema.rb b/db/schema.rb index 428b436a..726dbcaf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -9,8 +9,7 @@ # migrations use external dependencies or application code. # # It's strongly recommended that you check this file into your version control system. - -ActiveRecord::Schema[7.0].define(version: 2024_10_18_155243) do +ActiveRecord::Schema[7.0].define(version: 2024_10_27_103012) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -523,6 +522,16 @@ t.index ["mail_alias_id"], name: "index_stored_mails_on_mail_alias_id" end + create_table "study_room_presences", force: :cascade do |t| + t.datetime "start_time", null: false + t.datetime "end_time", null: false + t.text "status", null: false + t.integer "user_id", null: false + t.datetime "deleted_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "users", id: :serial, force: :cascade do |t| t.string "email" t.string "username" diff --git a/db/seeds/content.rb b/db/seeds/content.rb index 856f65f4..2667adc2 100644 --- a/db/seeds/content.rb +++ b/db/seeds/content.rb @@ -1,6 +1,10 @@ # rubocop:disable Style/CombinableLoops members = Group.find_by(name: 'Leden').users +members.sample(4).each do |user| + FactoryBot.create(:study_room_presence, user:) +end + articles = [] members.sample(15).each do |user| articles << FactoryBot.create(:article, author: user, group: nil) diff --git a/db/seeds/permissions.rb b/db/seeds/permissions.rb index 85bb9579..2ecc1586 100644 --- a/db/seeds/permissions.rb +++ b/db/seeds/permissions.rb @@ -24,6 +24,7 @@ def create_permissions(permission_map) 'article' => %i[create read update destroy], 'article_comment' => %i[create read update destroy], 'board_room_presence' => %i[create read update destroy], + 'study_room_presence' => %i[create read update destroy], 'book' => %i[create read update destroy], 'group' => %i[create read update destroy], 'membership' => %i[create read update destroy], @@ -65,6 +66,7 @@ def create_permissions(permission_map) 'article' => %i[create read], 'article_comment' => %i[create read], 'board_room_presence' => %i[read], + 'study_room_presence' => %i[create read], 'book' => %i[read], 'group' => %i[read], 'membership' => %i[read], @@ -101,6 +103,7 @@ def create_permissions(permission_map) 'article' => %i[read], 'article_comment' => %i[create read], 'board_room_presence' => %i[read], + 'study_room_presence' => %i[read], 'book' => [], 'group' => [], 'poll' => [], diff --git a/spec/factories/study_room_presences.rb b/spec/factories/study_room_presences.rb new file mode 100644 index 00000000..31bc0d4b --- /dev/null +++ b/spec/factories/study_room_presences.rb @@ -0,0 +1,11 @@ +FactoryBot.define do + factory :study_room_presence do + start_time { Faker::Time.between(from: 3.days.ago, to: 5.days.ago) } + end_time { Faker::Time.between(from: 10.days.from_now, to: 5.days.from_now) } + status { %w[chilling studying banaan].sample } + user + + trait(:future) { start_time { Faker::Time.between(from: 1.day.from_now, to: 4.days.from_now) } } + trait(:history) { end_time { Faker::Time.between(from: 2.days.ago, to: 1.day.ago) } } + end +end diff --git a/spec/jobs/user_archive_job_spec.rb b/spec/jobs/user_archive_job_spec.rb index 615a4bcd..9eb639df 100644 --- a/spec/jobs/user_archive_job_spec.rb +++ b/spec/jobs/user_archive_job_spec.rb @@ -44,6 +44,7 @@ describe 'other entities are destroyed' do before do create(:board_room_presence, user:) + create(:study_room_presence, user:) create(:mandate, user:) create(:transaction, user:) create(:mail_alias, user:) @@ -53,6 +54,7 @@ end it { expect { job }.to change(BoardRoomPresence, :count).by(-1) } + it { expect { job }.to change(StudyRoomPresence, :count).by(-1) } it { expect { job }.to change(Debit::Mandate, :count).by(-1) } it { expect { job }.to change(Debit::Transaction, :count).by(-1) } it { expect { job }.to change(MailAlias, :count).by(-1) } diff --git a/spec/models/study_room_presence_spec.rb b/spec/models/study_room_presence_spec.rb new file mode 100644 index 00000000..6367f52d --- /dev/null +++ b/spec/models/study_room_presence_spec.rb @@ -0,0 +1,112 @@ +require 'rails_helper' + +RSpec.describe StudyRoomPresence, type: :model do + subject(:study_room_presence) { build_stubbed(:study_room_presence) } + + describe '#valid?' do + it { expect(study_room_presence).to be_valid } + + context 'when without start time' do + subject(:study_room_presence) do + build_stubbed(:study_room_presence, start_time: nil) + end + + it { expect(study_room_presence).not_to be_valid } + end + + context 'when without end time' do + subject(:study_room_presence) do + build_stubbed(:study_room_presence, end_time: nil) + end + + it { expect(study_room_presence).not_to be_valid } + end + + context 'when end time before start time' do + subject(:study_room_presence) do + build_stubbed(:study_room_presence, + end_time: 1.day.ago, start_time: 1.day.from_now) + end + + it { expect(study_room_presence).not_to be_valid } + end + + context 'when without status' do + subject(:study_room_presence) { build_stubbed(:study_room_presence, status: nil) } + + it { expect(study_room_presence).not_to be_valid } + end + + context 'when without a user' do + subject(:study_room_presence) { build_stubbed(:study_room_presence, user: nil) } + + it { expect(study_room_presence).not_to be_valid } + end + end + + describe '#current' do + context 'when started in the past and ended in the future' do + before { create(:study_room_presence) } + + it { expect(described_class.current.count).to eq 1 } + end + + context 'when not yet started' do + before { create(:study_room_presence, start_time: 1.minute.from_now) } + + it { expect(described_class.current.count).to eq 0 } + end + + context 'when just ended' do + before { create(:study_room_presence, end_time: 1.second.ago) } + + it { expect(described_class.current.count).to eq 0 } + end + end + + describe '#future' do + context 'when started in the past and ended in the future' do + before { create(:study_room_presence) } + + it { expect(described_class.future.count).to eq 0 } + end + + context 'when not yet started' do + before { create(:study_room_presence, start_time: 1.minute.from_now) } + + it { expect(described_class.future.count).to eq 1 } + end + + context 'when just ended' do + before { create(:study_room_presence, end_time: 1.second.ago) } + + it { expect(described_class.future.count).to eq 0 } + end + end + + describe '#current_and_future' do + context 'when started in the past and ended in the future' do + before { create(:study_room_presence) } + + it { expect(described_class.current_and_future.count).to eq 1 } + end + + context 'when not yet started' do + before { create(:study_room_presence, start_time: 1.minute.from_now) } + + it { expect(described_class.current_and_future.count).to eq 1 } + end + + context 'when just ended' do + before { create(:study_room_presence, end_time: 1.second.ago) } + + it { expect(described_class.current_and_future.count).to eq 0 } + end + + context 'when starting in the future' do + before { create(:study_room_presence, start_time: 1.second.from_now) } + + it { expect(described_class.current_and_future.count).to eq 1 } + end + end +end diff --git a/spec/requests/v1/study_room_presence_controller/create_spec.rb b/spec/requests/v1/study_room_presence_controller/create_spec.rb new file mode 100644 index 00000000..862833a9 --- /dev/null +++ b/spec/requests/v1/study_room_presence_controller/create_spec.rb @@ -0,0 +1,12 @@ +require 'rails_helper' + +describe V1::StudyRoomPresencesController do + describe 'POST /study_room_presences/:id', version: 1 do + it_behaves_like 'a creatable and permissible model' do + let(:record) { build(:study_room_presence) } + let(:record_url) { '/v1/study_room_presences' } + let(:record_permission) { 'study_room_presence.create' } + let(:invalid_attributes) { { status: '' } } + end + end +end diff --git a/spec/requests/v1/study_room_presence_controller/destroy_spec.rb b/spec/requests/v1/study_room_presence_controller/destroy_spec.rb new file mode 100644 index 00000000..ef01b7cf --- /dev/null +++ b/spec/requests/v1/study_room_presence_controller/destroy_spec.rb @@ -0,0 +1,11 @@ +require 'rails_helper' + +describe V1::StudyRoomPresencesController do + describe 'DELETE /study_room_presences/:id', version: 1 do + it_behaves_like 'a destroyable and permissible model' do + let(:record) { create(:study_room_presence) } + let(:record_url) { "/v1/study_room_presences/#{record.id}" } + let(:record_permission) { 'study_room_presence.destroy' } + end + end +end diff --git a/spec/requests/v1/study_room_presence_controller/index_spec.rb b/spec/requests/v1/study_room_presence_controller/index_spec.rb new file mode 100644 index 00000000..4c7ae825 --- /dev/null +++ b/spec/requests/v1/study_room_presence_controller/index_spec.rb @@ -0,0 +1,13 @@ +require 'rails_helper' + +describe V1::StudyRoomPresencesController do + describe 'GET /study_room_presences', version: 1 do + let(:records) { create_list(:study_room_presence, 3) } + let(:record_url) { '/v1/study_room_presences' } + let(:record_permission) { 'study_room_presence.read' } + let(:request) { get(record_url) } + + it_behaves_like 'a permissible model' + it_behaves_like 'an indexable model' + end +end diff --git a/spec/requests/v1/study_room_presence_controller/show_spec.rb b/spec/requests/v1/study_room_presence_controller/show_spec.rb new file mode 100644 index 00000000..19342149 --- /dev/null +++ b/spec/requests/v1/study_room_presence_controller/show_spec.rb @@ -0,0 +1,12 @@ +require 'rails_helper' + +describe V1::StudyRoomPresencesController do + describe 'GET /study_room_presences/:id', version: 1 do + it_behaves_like 'a permissible model' do + let(:record) { create(:study_room_presence) } + let(:record_url) { "/v1/study_room_presences/#{record.id}" } + let(:record_permission) { 'study_room_presence.read' } + let(:valid_request) { get(record_url) } + end + end +end diff --git a/spec/requests/v1/study_room_presence_controller/update_spec.rb b/spec/requests/v1/study_room_presence_controller/update_spec.rb new file mode 100644 index 00000000..34b06c95 --- /dev/null +++ b/spec/requests/v1/study_room_presence_controller/update_spec.rb @@ -0,0 +1,12 @@ +require 'rails_helper' + +describe V1::StudyRoomPresencesController do + describe 'PUT /study_room_presences/:id', version: 1 do + it_behaves_like 'an updatable and permissible model' do + let(:record) { create(:study_room_presence) } + let(:record_url) { "/v1/study_room_presences/#{record.id}" } + let(:record_permission) { 'study_room_presence.update' } + let(:invalid_attributes) { { status: '' } } + end + end +end