diff --git a/app/controllers/super_admins/release_notes_controller.rb b/app/controllers/super_admins/release_notes_controller.rb new file mode 100644 index 000000000..c880618fa --- /dev/null +++ b/app/controllers/super_admins/release_notes_controller.rb @@ -0,0 +1,52 @@ +class SuperAdmins::ReleaseNotesController < ApplicationController + before_action :authenticate_super_admin! + before_action :set_note, only: [:edit, :update] + + def index + @release_notes = ReleaseNote + .order(released_on: :desc, id: :asc) + .with_rich_text_body + end + + def show + # allows refreshing a submitted page in error + redirect_to edit_super_admins_release_note_path(params[:id]) + end + + def new + @release_note = ReleaseNote.new(released_on: Date.current) + end + + def create + @release_note = ReleaseNote.new(release_note_params) + if @release_note.save + redirect_to edit_super_admins_release_note_path(@release_note), notice: t('.success') + else + flash.now[:alert] = [t('.error'), @release_note.errors.full_messages].flatten + render :new + end + end + + def edit + @release_note = ReleaseNote.find(params[:id]) + end + + def update + if @release_note.update(release_note_params) + redirect_to edit_super_admins_release_note_path(@release_note), notice: t('.success') + else + flash.now[:alert] = [t('.error'), @release_note.errors.full_messages].flatten + render :edit + end + end + + private + + def release_note_params + params.require(:release_note).permit(:released_on, :published, :body, categories: []) + end + + def set_note + @release_note = ReleaseNote.find(params[:id]) + end +end diff --git a/app/models/release_note.rb b/app/models/release_note.rb index 8fe5152b2..25bf62a92 100644 --- a/app/models/release_note.rb +++ b/app/models/release_note.rb @@ -1,2 +1,14 @@ class ReleaseNote < ApplicationRecord + has_rich_text :body + + CATEGORIES = [ + 'administrateur', + 'instructeur', + 'expert', + 'usager', + 'api' + ] + + validates :categories, presence: true, inclusion: { in: CATEGORIES } + validates :body, presence: true end diff --git a/app/views/super_admins/release_notes/_form.html.haml b/app/views/super_admins/release_notes/_form.html.haml new file mode 100644 index 000000000..b541cab81 --- /dev/null +++ b/app/views/super_admins/release_notes/_form.html.haml @@ -0,0 +1,30 @@ += form_for [:super_admins, release_note] do |f| + %fieldset#release_notes_fieldset.fr-fieldset{ 'data-controller': 'trix' } + .fr-fieldset__element + = render Dsfr::InputComponent.new(form: f, attribute: :released_on, input_type: :date_field) + + .fr-fieldset__element + .fr-toggle + = f.check_box :published, class: "fr-toggle-input", id: dom_id(release_note, :published) + %label.fr-toggle__label{ for: dom_id(release_note, :published), data: { fr_checked_label: "Publié", fr_unchecked_label: "Brouillon" } } + Publier + + .fr-fieldset__element + %fieldset.fr-fieldset{ "aria-labelledby": dom_id(release_note, :category_legend) } + %legend.fr-fieldset__legend.fr-fieldset__legend--regular{ id: dom_id(release_note, :category_legend) } + Catégories + + - ReleaseNote::CATEGORIES.each do |category| + .fr-fieldset__element.fr-fieldset__element--inline + .fr-checkbox-group + = f.check_box :categories, { multiple: true, include_hidden: false, id: dom_id(release_note, "category_#{category}"), "aria-describedby" => "#{dom_id(release_note, "category_#{category}_messages")}" }, category, nil + %label.fr-label{ for: dom_id(release_note, "category_#{category}") } + = category.humanize + + .fr-fieldset__element + .fr-input-group + = render Dsfr::InputComponent.new(form: f, attribute: :body, input_type: :rich_text_area) + + + .fr-fieldset__element + = f.submit "Valider", class: "fr-btn fr-btn--lg" diff --git a/app/views/super_admins/release_notes/edit.html.haml b/app/views/super_admins/release_notes/edit.html.haml new file mode 100644 index 000000000..6d156bdaf --- /dev/null +++ b/app/views/super_admins/release_notes/edit.html.haml @@ -0,0 +1,6 @@ +.fr-container.fr-my-5w + .fr-grid-row.fr-grid-row--center + .fr-col-lg-10 + %h1.fr-h2 Annonce + = render partial: 'form', object: @release_note, as: :release_note + diff --git a/app/views/super_admins/release_notes/index.html.haml b/app/views/super_admins/release_notes/index.html.haml new file mode 100644 index 000000000..12dca7870 --- /dev/null +++ b/app/views/super_admins/release_notes/index.html.haml @@ -0,0 +1,24 @@ +.fr-container.fr-my-5w + .fr-grid-row.fr-grid-row--center + .fr-col-lg-10 + %h1.fr-h2 Liste des annonces + + = link_to("Créer une annonce", new_super_admins_release_note_path, class: "fr-btn") + + %table.fr-table + %thead + %th Annoncé le + %th Publié? + %th Actions + %tbody + - @release_notes.each do |note| + %tr + %td= l(note.released_on) if note.released_on + %td + - if note.published? + %span.fr-badge.fr-badge--success.fr-badge--no-icon Publié + - else + %span.fr-badge.fr-badge--warning.fr-badge--no-icon Brouillon + %td + = link_to 'Modifier', edit_super_admins_release_note_path(note), class: 'fr-btn fr-btn--secondary' + diff --git a/app/views/super_admins/release_notes/new.html.haml b/app/views/super_admins/release_notes/new.html.haml new file mode 100644 index 000000000..47cd16a99 --- /dev/null +++ b/app/views/super_admins/release_notes/new.html.haml @@ -0,0 +1,5 @@ +.fr-container.fr-my-5w + .fr-grid-row.fr-grid-row--center + .fr-col-lg-10 + %h1.fr-h2 Nouvelle Annonce + = render ReleaseNote::FormComponent.new(release_note: @release_note) diff --git a/config/locales/release_notes.en.yml b/config/locales/release_notes.en.yml new file mode 100644 index 000000000..4eb318d91 --- /dev/null +++ b/config/locales/release_notes.en.yml @@ -0,0 +1,16 @@ +--- +en: + super_admins: + release_notes: + create: &save + success: Release note was successfully saved. + error: Release note was not saved. + update: + <<: *save + activerecord: + attributes: + release_note: + body: Announce + categories: Categories + released_on: Publication date + diff --git a/config/locales/release_notes.fr.yml b/config/locales/release_notes.fr.yml new file mode 100644 index 000000000..87be1b15a --- /dev/null +++ b/config/locales/release_notes.fr.yml @@ -0,0 +1,16 @@ +--- +fr: + super_admins: + release_notes: + create: &save + success: L’annonce a été sauvegardée. + error: L’annonce n’a pas pu être sauvegardée. + update: + <<: *save + activerecord: + attributes: + release_note: + body: Annonce + categories: Catégories + released_on: Date de publication + diff --git a/config/routes.rb b/config/routes.rb index bce765501..5c26b425a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -125,6 +125,10 @@ Rails.application.routes.draw do passwords: 'super_admins/passwords' } + namespace :super_admins do + resources :release_notes, only: [:index, :new, :create, :edit, :update, :show] + end + get 'super_admins/edit_otp', to: 'super_admins#edit_otp', as: 'edit_super_admin_otp' put 'super_admins/enable_otp', to: 'super_admins#enable_otp', as: 'enable_super_admin_otp' diff --git a/db/migrate/20230912155425_create_release_notes.rb b/db/migrate/20230912155425_create_release_notes.rb index 0f05ee7f1..fff30f9c8 100644 --- a/db/migrate/20230912155425_create_release_notes.rb +++ b/db/migrate/20230912155425_create_release_notes.rb @@ -2,7 +2,6 @@ class CreateReleaseNotes < ActiveRecord::Migration[7.0] def change create_table :release_notes do |t| t.date :released_on - t.text :body, default: nil t.boolean :published, default: false, null: false t.string :categories, array: true, default: [] diff --git a/db/schema.rb b/db/schema.rb index 1b8716f21..2eddd4473 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -897,7 +897,6 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_26_161609) do end create_table "release_notes", force: :cascade do |t| - t.text "body" t.string "categories", default: [], array: true t.datetime "created_at", null: false t.boolean "published", default: false, null: false @@ -905,6 +904,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_10_26_161609) do t.datetime "updated_at", null: false t.index ["categories"], name: "index_release_notes_on_categories", using: :gin t.index ["published"], name: "index_release_notes_on_published" + t.index ["released_on"], name: "index_release_notes_on_released_on" end create_table "safe_mailers", force: :cascade do |t| diff --git a/spec/controllers/super_admins/release_notes_controller_spec.rb b/spec/controllers/super_admins/release_notes_controller_spec.rb new file mode 100644 index 000000000..868ad80c8 --- /dev/null +++ b/spec/controllers/super_admins/release_notes_controller_spec.rb @@ -0,0 +1,68 @@ +require 'rails_helper' + +describe SuperAdmins::ReleaseNotesController, type: :controller do + let(:super_admin) { create(:super_admin) } + + before do + sign_in super_admin if super_admin.present? + end + + describe "acl" do + context 'when user is not signed as super admin' do + let(:super_admin) { nil } + let!(:release_note) { create(:release_note, published: false) } + + it 'is not allowed to post' do + expect { post :create, params: { release_note: { released_on: Date.current, published: "0", body: "bam" } } }.not_to change(ReleaseNote, :count) + expect(response.status).to eq(302) + expect(flash[:alert]).to be_present + end + + it 'is not allowed to put' do + expect { + put :update, params: { + id: release_note.id, + release_note: { + released_on: Date.current, + published: "1", + categories: release_note.categories, + body: "hacked body" + } + } + }.not_to change { release_note.reload.body } + expect(response.status).to eq(302) + expect(flash[:alert]).to be_present + end + + it 'is not allowed to index' do + get :index + expect(response.status).to eq(302) + expect(flash[:alert]).to be_present + end + end + + context 'when user is signed as super admin' do + it 'is allowed to post' do + expect { post :create, params: { release_note: { categories: ['api'], released_on: Date.current, published: "0", body: "bam" } } }.to change(ReleaseNote, :count).by(1) + expect(flash[:notice]).to be_present + end + + it 'is allowed to put' do + release_note = create(:release_note, published: false) + put :update, params: { + id: release_note.id, + release_note: { + released_on: Date.current, + published: "1", + categories: release_note.categories, + body: "new body" + } + } + + release_note.reload + expect(release_note.body.to_plain_text).to eq("new body") + expect(release_note.published).to be_truthy + end + end + end +end