From eb3c78a61d449718e2702ab7a48ea8bc3577c64e Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 26 Nov 2019 13:44:43 +0100 Subject: [PATCH 01/17] [poc] show google agenda events in the public calendar --- Gemfile | 2 ++ Gemfile.lock | 4 ++++ .../javascripts/controllers/calendar.js | 7 +++++-- app/assets/javascripts/router.js.erb | 1 + app/assets/javascripts/services/ical.js | 5 +++++ app/controllers/api/ical_controller.rb | 19 +++++++++++++++++++ app/views/api/ical/externals.json.jbuilder | 8 ++++++++ config/routes.rb | 2 ++ 8 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/services/ical.js create mode 100644 app/controllers/api/ical_controller.rb create mode 100644 app/views/api/ical/externals.json.jbuilder diff --git a/Gemfile b/Gemfile index b69b1c8f9..af5fd4003 100644 --- a/Gemfile +++ b/Gemfile @@ -152,3 +152,5 @@ gem 'sys-filesystem' gem 'sha3' gem 'repost' + +gem 'icalendar' diff --git a/Gemfile.lock b/Gemfile.lock index eec86b501..db0e0d4e7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -191,6 +191,9 @@ GEM multi_xml (>= 0.5.2) i18n (0.9.5) concurrent-ruby (~> 1.0) + icalendar (2.5.3) + ice_cube (~> 0.16) + ice_cube (0.16.3) ice_nine (0.11.2) jaro_winkler (1.5.1) jbuilder (2.5.0) @@ -497,6 +500,7 @@ DEPENDENCIES forgery friendly_id (~> 5.1.0) has_secure_token + icalendar jbuilder (~> 2.5) jbuilder_cache_multi jquery-rails diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index 0af678931..14515614c 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -16,8 +16,8 @@ * Controller used in the public calendar global */ -Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', - function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise) { +Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', 'externalsPromise', + function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise, externalsPromise) { /* PRIVATE STATIC CONSTANTS */ let currentMachineEvent = null; machinesPromise.forEach(m => m.checked = true); @@ -38,6 +38,9 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ // List of spaces $scope.spaces = spacesPromise.filter(t => !t.disabled); + // External ICS calendars + $scope.externals = externalsPromise; + // add availabilities source to event sources $scope.eventSources = []; diff --git a/app/assets/javascripts/router.js.erb b/app/assets/javascripts/router.js.erb index 12cad9d20..4b020ab3d 100644 --- a/app/assets/javascripts/router.js.erb +++ b/app/assets/javascripts/router.js.erb @@ -638,6 +638,7 @@ angular.module('application.router', ['ui.router']) trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }], machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }], spacesPromise: ['Space', function (Space) { return Space.query().$promise; }], + externalsPromise: ['Ical', function (Ical) { return Ical.get().$promise; }], translations: ['Translations', function (Translations) { return Translations.query(['app.public.calendar']).$promise; }] } }) diff --git a/app/assets/javascripts/services/ical.js b/app/assets/javascripts/services/ical.js new file mode 100644 index 000000000..dec1ed635 --- /dev/null +++ b/app/assets/javascripts/services/ical.js @@ -0,0 +1,5 @@ +'use strict'; + +Application.Services.factory('Ical', ['$resource', function ($resource) { + return $resource('/api/ical/externals'); +}]); diff --git a/app/controllers/api/ical_controller.rb b/app/controllers/api/ical_controller.rb new file mode 100644 index 000000000..dc3307d85 --- /dev/null +++ b/app/controllers/api/ical_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# API Controller for resources of type iCalendar +class API::IcalController < API::ApiController + respond_to :json + + def externals + require 'net/http' + require 'uri' + + ics = Net::HTTP.get(URI.parse('https://calendar.google.com/calendar/ical/sylvain%40sleede.com/public/basic.ics')) + + require 'icalendar' + require 'icalendar/tzinfo' + + cals = Icalendar::Calendar.parse(ics) + @events = cals.first.events + end +end \ No newline at end of file diff --git a/app/views/api/ical/externals.json.jbuilder b/app/views/api/ical/externals.json.jbuilder new file mode 100644 index 000000000..faedb232b --- /dev/null +++ b/app/views/api/ical/externals.json.jbuilder @@ -0,0 +1,8 @@ +json.array!(@events) do |event| + json.id event.uid + json.title event.summary + json.start event.dtstart.iso8601 + json.end event.dtend.iso8601 + json.backgroundColor 'white' + json.borderColor '#214712' +end diff --git a/config/routes.rb b/config/routes.rb index 0609afaeb..9e0b5e095 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -112,6 +112,8 @@ Rails.application.routes.draw do get 'first', action: 'first', on: :collection end + get 'ical/externals' => 'ical#externals' + # for admin resources :trainings do get :availabilities, on: :member From a9b1eabb2cb0585f184779b038d8a004746bd401 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 26 Nov 2019 13:44:43 +0100 Subject: [PATCH 02/17] [poc] show google agenda events in the public calendar --- Gemfile | 2 ++ Gemfile.lock | 4 ++++ .../javascripts/controllers/calendar.js | 7 +++++-- app/assets/javascripts/router.js.erb | 1 + app/assets/javascripts/services/ical.js | 5 +++++ app/controllers/api/ical_controller.rb | 19 +++++++++++++++++++ app/views/api/ical/externals.json.jbuilder | 8 ++++++++ config/routes.rb | 2 ++ 8 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 app/assets/javascripts/services/ical.js create mode 100644 app/controllers/api/ical_controller.rb create mode 100644 app/views/api/ical/externals.json.jbuilder diff --git a/Gemfile b/Gemfile index b69b1c8f9..af5fd4003 100644 --- a/Gemfile +++ b/Gemfile @@ -152,3 +152,5 @@ gem 'sys-filesystem' gem 'sha3' gem 'repost' + +gem 'icalendar' diff --git a/Gemfile.lock b/Gemfile.lock index eec86b501..db0e0d4e7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -191,6 +191,9 @@ GEM multi_xml (>= 0.5.2) i18n (0.9.5) concurrent-ruby (~> 1.0) + icalendar (2.5.3) + ice_cube (~> 0.16) + ice_cube (0.16.3) ice_nine (0.11.2) jaro_winkler (1.5.1) jbuilder (2.5.0) @@ -497,6 +500,7 @@ DEPENDENCIES forgery friendly_id (~> 5.1.0) has_secure_token + icalendar jbuilder (~> 2.5) jbuilder_cache_multi jquery-rails diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index 0af678931..14515614c 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -16,8 +16,8 @@ * Controller used in the public calendar global */ -Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', - function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise) { +Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', 'externalsPromise', + function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise, externalsPromise) { /* PRIVATE STATIC CONSTANTS */ let currentMachineEvent = null; machinesPromise.forEach(m => m.checked = true); @@ -38,6 +38,9 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ // List of spaces $scope.spaces = spacesPromise.filter(t => !t.disabled); + // External ICS calendars + $scope.externals = externalsPromise; + // add availabilities source to event sources $scope.eventSources = []; diff --git a/app/assets/javascripts/router.js.erb b/app/assets/javascripts/router.js.erb index 12cad9d20..4b020ab3d 100644 --- a/app/assets/javascripts/router.js.erb +++ b/app/assets/javascripts/router.js.erb @@ -638,6 +638,7 @@ angular.module('application.router', ['ui.router']) trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }], machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }], spacesPromise: ['Space', function (Space) { return Space.query().$promise; }], + externalsPromise: ['Ical', function (Ical) { return Ical.get().$promise; }], translations: ['Translations', function (Translations) { return Translations.query(['app.public.calendar']).$promise; }] } }) diff --git a/app/assets/javascripts/services/ical.js b/app/assets/javascripts/services/ical.js new file mode 100644 index 000000000..dec1ed635 --- /dev/null +++ b/app/assets/javascripts/services/ical.js @@ -0,0 +1,5 @@ +'use strict'; + +Application.Services.factory('Ical', ['$resource', function ($resource) { + return $resource('/api/ical/externals'); +}]); diff --git a/app/controllers/api/ical_controller.rb b/app/controllers/api/ical_controller.rb new file mode 100644 index 000000000..dc3307d85 --- /dev/null +++ b/app/controllers/api/ical_controller.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# API Controller for resources of type iCalendar +class API::IcalController < API::ApiController + respond_to :json + + def externals + require 'net/http' + require 'uri' + + ics = Net::HTTP.get(URI.parse('https://calendar.google.com/calendar/ical/sylvain%40sleede.com/public/basic.ics')) + + require 'icalendar' + require 'icalendar/tzinfo' + + cals = Icalendar::Calendar.parse(ics) + @events = cals.first.events + end +end \ No newline at end of file diff --git a/app/views/api/ical/externals.json.jbuilder b/app/views/api/ical/externals.json.jbuilder new file mode 100644 index 000000000..faedb232b --- /dev/null +++ b/app/views/api/ical/externals.json.jbuilder @@ -0,0 +1,8 @@ +json.array!(@events) do |event| + json.id event.uid + json.title event.summary + json.start event.dtstart.iso8601 + json.end event.dtend.iso8601 + json.backgroundColor 'white' + json.borderColor '#214712' +end diff --git a/config/routes.rb b/config/routes.rb index 0609afaeb..9e0b5e095 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -112,6 +112,8 @@ Rails.application.routes.draw do get 'first', action: 'first', on: :collection end + get 'ical/externals' => 'ical#externals' + # for admin resources :trainings do get :availabilities, on: :member From bb777227d6a79ce8a26733859cc82675ad7639d7 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 27 Nov 2019 13:59:56 +0100 Subject: [PATCH 03/17] display events of external ics in calendar --- app/assets/javascripts/controllers/calendar.js | 11 ++++++----- app/assets/javascripts/router.js.erb | 1 - app/assets/templates/calendar/calendar.html.erb | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index 14515614c..43f0542a3 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -16,8 +16,8 @@ * Controller used in the public calendar global */ -Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', 'externalsPromise', - function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise, externalsPromise) { +Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', + function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise) { /* PRIVATE STATIC CONSTANTS */ let currentMachineEvent = null; machinesPromise.forEach(m => m.checked = true); @@ -38,11 +38,12 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ // List of spaces $scope.spaces = spacesPromise.filter(t => !t.disabled); - // External ICS calendars - $scope.externals = externalsPromise; - // add availabilities source to event sources $scope.eventSources = []; + $scope.eventSources.push({ + url: '/api/ical/externals', + textColor: 'black' + }); // filter availabilities if have change $scope.filterAvailabilities = function (filter, scope) { diff --git a/app/assets/javascripts/router.js.erb b/app/assets/javascripts/router.js.erb index 4b020ab3d..12cad9d20 100644 --- a/app/assets/javascripts/router.js.erb +++ b/app/assets/javascripts/router.js.erb @@ -638,7 +638,6 @@ angular.module('application.router', ['ui.router']) trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }], machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }], spacesPromise: ['Space', function (Space) { return Space.query().$promise; }], - externalsPromise: ['Ical', function (Ical) { return Ical.get().$promise; }], translations: ['Translations', function (Translations) { return Translations.query(['app.public.calendar']).$promise; }] } }) diff --git a/app/assets/templates/calendar/calendar.html.erb b/app/assets/templates/calendar/calendar.html.erb index bc3c16530..fc2a5c8ae 100644 --- a/app/assets/templates/calendar/calendar.html.erb +++ b/app/assets/templates/calendar/calendar.html.erb @@ -41,7 +41,7 @@

{{ 'calendar.filter_calendar' }}

- + '">
@@ -56,7 +56,7 @@

{{ 'calendar.filter_calendar' }}

From 9e2134c9cfc8d2ee8ce57e552a8a4f5fc535b7b4 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 27 Nov 2019 16:17:32 +0100 Subject: [PATCH 04/17] ics sources configuration interface --- .../controllers/admin/calendar.js.erb | 54 ++++++++++++ .../javascripts/controllers/calendar.js | 2 +- app/assets/javascripts/router.js.erb | 12 +++ app/assets/stylesheets/app.components.scss | 1 + app/assets/stylesheets/app.utilities.scss | 1 + app/assets/stylesheets/application.scss.erb | 6 +- app/assets/stylesheets/modules/icalendar.scss | 10 +++ .../admin/calendar/calendar.html.erb | 12 +-- .../templates/admin/calendar/icalendar.html | 87 +++++++++++++++++++ .../templates/admin/settings/general.html | 4 +- config/locales/app.admin.fr.yml | 14 +++ 11 files changed, 191 insertions(+), 12 deletions(-) create mode 100644 app/assets/stylesheets/modules/icalendar.scss create mode 100644 app/assets/templates/admin/calendar/icalendar.html diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index 5b7979642..569d7a35d 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -755,3 +755,57 @@ Application.Controllers.controller('DeleteRecurrentAvailabilityController', ['$s } } ]); + + + +/** + * Controller used in the iCalendar (ICS) imports management page + */ + +Application.Controllers.controller('AdminICalendarController', ['$scope', + function ($scope) { + // list of ICS sources + $scope.calendars = []; + + // configuration of a new ICS source + $scope.newCalendar = { + color: undefined, + textColor: undefined, + url: undefined, + textHidden: false + }; + + /** + * Save the new iCalendar in database + */ + $scope.save = function () { + $scope.calendars.push(Object.assign({}, $scope.newCalendar)); + $scope.newCalendar.url = undefined; + $scope.newCalendar.color = null; + $scope.newCalendar.textColor = null; + $scope.newCalendar.textHidden = false; + } + + /** + * Return a CSS-like style of the given calendar configuration + * @param calendar + */ + $scope.calendarStyle = function (calendar) { + return { + 'border-color': calendar.color, + 'color': calendar.textColor, + 'width': calendar.textHidden ? '50px' : 'auto', + 'height': calendar.textHidden ? '21px' : 'auto' + }; + } + + /** + * Delete the given calendar from the database + * @param calendar + */ + $scope.delete = function (calendar) { + const idx = $scope.calendars.indexOf(calendar); + $scope.calendars.splice(idx, 1); + } + } +]); diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index 43f0542a3..e46eb8db1 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -170,7 +170,7 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ }; const eventRenderCb = function (event, element) { - if (event.tags.length > 0) { + if (event.tags && event.tags.length > 0) { let html = ''; for (let tag of Array.from(event.tags)) { html += `${tag.name} `; diff --git a/app/assets/javascripts/router.js.erb b/app/assets/javascripts/router.js.erb index 12cad9d20..f0476c79c 100644 --- a/app/assets/javascripts/router.js.erb +++ b/app/assets/javascripts/router.js.erb @@ -659,6 +659,18 @@ angular.module('application.router', ['ui.router']) translations: ['Translations', function (Translations) { return Translations.query('app.admin.calendar').$promise; }] } }) + .state('app.admin.calendar.icalendar', { + url: '/admin/calendar/icalendar', + views: { + 'main@': { + templateUrl: '<%= asset_path "admin/calendar/icalendar.html" %>', + controller: 'AdminICalendarController' + } + }, + resolve: { + translations: ['Translations', function (Translations) { return Translations.query('app.admin.icalendar').$promise; }] + } + }) // project's elements .state('app.admin.project_elements', { diff --git a/app/assets/stylesheets/app.components.scss b/app/assets/stylesheets/app.components.scss index 6a1659507..e550aa8ea 100644 --- a/app/assets/stylesheets/app.components.scss +++ b/app/assets/stylesheets/app.components.scss @@ -670,5 +670,6 @@ padding: 10px; font-size: 10px; padding: 2px; margin-left: 10px; + display: inline-block; } } diff --git a/app/assets/stylesheets/app.utilities.scss b/app/assets/stylesheets/app.utilities.scss index 7b6349c1d..ad1d4241c 100644 --- a/app/assets/stylesheets/app.utilities.scss +++ b/app/assets/stylesheets/app.utilities.scss @@ -102,6 +102,7 @@ p, .widget p { .text-italic { font-style: italic; } +.text-left { text-align: left !important; } .text-center { text-align: center; } .text-right { text-align: right; } diff --git a/app/assets/stylesheets/application.scss.erb b/app/assets/stylesheets/application.scss.erb index ada57aaa4..b87230fd9 100644 --- a/app/assets/stylesheets/application.scss.erb +++ b/app/assets/stylesheets/application.scss.erb @@ -32,11 +32,7 @@ @import "app.buttons"; @import "app.components"; @import "app.plugins"; -@import "modules/invoice"; -@import "modules/signup"; -@import "modules/abuses"; -@import "modules/cookies"; -@import "modules/stripe"; +@import "modules/*"; @import "app.responsive"; diff --git a/app/assets/stylesheets/modules/icalendar.scss b/app/assets/stylesheets/modules/icalendar.scss new file mode 100644 index 000000000..a36dafa74 --- /dev/null +++ b/app/assets/stylesheets/modules/icalendar.scss @@ -0,0 +1,10 @@ +.calendar-form { + margin : 2em; + border: 1px solid #ddd; + border-radius: 3px; + padding: 1em; + + & > .input-group, & > .minicolors { + margin-top: 1em; + } +} diff --git a/app/assets/templates/admin/calendar/calendar.html.erb b/app/assets/templates/admin/calendar/calendar.html.erb index f1666a628..ef3345004 100644 --- a/app/assets/templates/admin/calendar/calendar.html.erb +++ b/app/assets/templates/admin/calendar/calendar.html.erb @@ -5,15 +5,17 @@ -
+

{{ 'admin_calendar.calendar_management' }}

-
-
- +
+
+ + +
@@ -28,7 +30,7 @@

{{ 'admin_calendar.legend' }}

- {{ 'admin_calendar.trainings' }}
+ {{ 'admin_calendar.trainings' }}
{{ 'admin_calendar.machines' }}
{{ 'admin_calendar.spaces' }} {{ 'admin_calendar.events' }} diff --git a/app/assets/templates/admin/calendar/icalendar.html b/app/assets/templates/admin/calendar/icalendar.html new file mode 100644 index 000000000..70faf8f5f --- /dev/null +++ b/app/assets/templates/admin/calendar/icalendar.html @@ -0,0 +1,87 @@ +
+
+
+
+ +
+
+
+
+

{{ 'icalendar.icalendar_import' }}

+
+
+ +
+
+ +
+
+ +
+
+ +
+ +
+
+ {{ 'icalendar.intro' }} +
+
+ + + + + + + + + + + + + + +
{{ 'icalendar.url' }}{{ 'icalendar.display' }}
{{calendar.url}} {{ calendar.textHidden ? '' : 'icalendar.example' }} + + +
+
+

{{ 'icalendar.new_import' }}

+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+
diff --git a/app/assets/templates/admin/settings/general.html b/app/assets/templates/admin/settings/general.html index 2822ad1ba..f4f9c0514 100644 --- a/app/assets/templates/admin/settings/general.html +++ b/app/assets/templates/admin/settings/general.html @@ -341,7 +341,9 @@
-
+
+
+
diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index c55d89754..576cada15 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -88,6 +88,20 @@ fr: view_reservations: "Voir les réservations" legend: "Légende" + icalendar: + icalendar: + icalendar_import: "Import iCalendar" + intro: "Fab-manager vous permet d'importer automatiquement des évènements de calendrier, au format iCalendar RFC 5545, depuis des URL externes. Ces URL seront synchronisée toutes les nuits et les évènements seront affichés dans le calendrier publique." + new_import: "Nouvel import ICS" + color: "Couleur" + text_color: "Couleur du texte" + url: "URL" + example: "Exemple" + display: "Affichage" + hide_text: "Cacher le texte" + hidden: "Caché" + shown: "Affiché" + project_elements: # gestion des éléments constituant les projets project_elements: From baf8cfb4876556bb47055af3253d15697cc2f516 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 27 Nov 2019 17:05:19 +0100 Subject: [PATCH 05/17] iCalendar server api & db model --- .../controllers/admin/calendar.js.erb | 6 +- .../javascripts/controllers/calendar.js | 2 +- app/assets/javascripts/router.js.erb | 1 + app/assets/javascripts/services/ical.js | 5 -- app/assets/javascripts/services/icalendar.js | 12 ++++ app/controllers/api/i_calendar_controller.rb | 55 +++++++++++++++++++ app/controllers/api/ical_controller.rb | 19 ------- app/models/i_calendar.rb | 5 ++ app/policies/i_calendar_policy.rb | 12 ++++ .../api/i_calendar/_i_calendar.json.jbuilder | 3 + .../events.json.jbuilder} | 4 +- app/views/api/i_calendar/index.json.jbuilder | 5 ++ app/views/api/i_calendar/show.json.jbuilder | 3 + config/routes.rb | 4 +- .../20191127153729_create_i_calendars.rb | 16 ++++++ db/schema.rb | 11 +++- test/fixtures/i_calendars.yml | 13 +++++ test/models/i_calendar_test.rb | 7 +++ 18 files changed, 152 insertions(+), 31 deletions(-) delete mode 100644 app/assets/javascripts/services/ical.js create mode 100644 app/assets/javascripts/services/icalendar.js create mode 100644 app/controllers/api/i_calendar_controller.rb delete mode 100644 app/controllers/api/ical_controller.rb create mode 100644 app/models/i_calendar.rb create mode 100644 app/policies/i_calendar_policy.rb create mode 100644 app/views/api/i_calendar/_i_calendar.json.jbuilder rename app/views/api/{ical/externals.json.jbuilder => i_calendar/events.json.jbuilder} (74%) create mode 100644 app/views/api/i_calendar/index.json.jbuilder create mode 100644 app/views/api/i_calendar/show.json.jbuilder create mode 100644 db/migrate/20191127153729_create_i_calendars.rb create mode 100644 test/fixtures/i_calendars.yml create mode 100644 test/models/i_calendar_test.rb diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index 569d7a35d..e41157041 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -762,10 +762,10 @@ Application.Controllers.controller('DeleteRecurrentAvailabilityController', ['$s * Controller used in the iCalendar (ICS) imports management page */ -Application.Controllers.controller('AdminICalendarController', ['$scope', - function ($scope) { +Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCalendars', + function ($scope, iCalendars) { // list of ICS sources - $scope.calendars = []; + $scope.calendars = iCalendars; // configuration of a new ICS source $scope.newCalendar = { diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index e46eb8db1..33d811f55 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -41,7 +41,7 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ // add availabilities source to event sources $scope.eventSources = []; $scope.eventSources.push({ - url: '/api/ical/externals', + url: '/api/i_calendar/events', textColor: 'black' }); diff --git a/app/assets/javascripts/router.js.erb b/app/assets/javascripts/router.js.erb index f0476c79c..62503a9cf 100644 --- a/app/assets/javascripts/router.js.erb +++ b/app/assets/javascripts/router.js.erb @@ -668,6 +668,7 @@ angular.module('application.router', ['ui.router']) } }, resolve: { + iCalendars: ['ICalendar', function (ICalendar) { return ICalendar.query().$promise; }], translations: ['Translations', function (Translations) { return Translations.query('app.admin.icalendar').$promise; }] } }) diff --git a/app/assets/javascripts/services/ical.js b/app/assets/javascripts/services/ical.js deleted file mode 100644 index dec1ed635..000000000 --- a/app/assets/javascripts/services/ical.js +++ /dev/null @@ -1,5 +0,0 @@ -'use strict'; - -Application.Services.factory('Ical', ['$resource', function ($resource) { - return $resource('/api/ical/externals'); -}]); diff --git a/app/assets/javascripts/services/icalendar.js b/app/assets/javascripts/services/icalendar.js new file mode 100644 index 000000000..21d30843e --- /dev/null +++ b/app/assets/javascripts/services/icalendar.js @@ -0,0 +1,12 @@ +'use strict'; + +Application.Services.factory('ICalendar', ['$resource', function ($resource) { + return $resource('/api/i_calendar/:id', + { id: '@id' }, { + events: { + method: 'GET', + url: '/api/i_calendar/events' + } + } + ); +}]); diff --git a/app/controllers/api/i_calendar_controller.rb b/app/controllers/api/i_calendar_controller.rb new file mode 100644 index 000000000..f004a13ab --- /dev/null +++ b/app/controllers/api/i_calendar_controller.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +# API Controller for resources of type iCalendar +class API::ICalendarController < API::ApiController + before_action :authenticate_user!, except: %i[index events] + before_action :set_i_cal, only: [:destroy] + respond_to :json + + def index + @i_cals = ICalendar.all + end + + def create + authorize ICalendar + @i_cal = ICalendar.new(i_calendar_params) + if @i_cal.save + render :show, status: :created, location: @i_cal + else + render json: @i_cal.errors, status: :unprocessable_entity + end + end + + def destroy + authorize ICalendar + @i_cal.destroy + head :no_content + end + + def events + require 'net/http' + require 'uri' + require 'icalendar' + + @events = [] + + @i_cals = ICalendar.all.each do |i_cal| + ics = Net::HTTP.get(URI.parse(i_cal.url)) + cals = Icalendar::Calendar.parse(ics) + + cals.first.events.each do |evt| + @events.push(evt.merge!(color: i_cal.color)) + end + end + end + + private + + def set_i_cal + @i_cal = ICalendar.find(params[:id]) + end + + def i_calendar_params + params.require(:i_calendar).permit(:url, :color, :text_color, :text_hidden) + end +end diff --git a/app/controllers/api/ical_controller.rb b/app/controllers/api/ical_controller.rb deleted file mode 100644 index dc3307d85..000000000 --- a/app/controllers/api/ical_controller.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -# API Controller for resources of type iCalendar -class API::IcalController < API::ApiController - respond_to :json - - def externals - require 'net/http' - require 'uri' - - ics = Net::HTTP.get(URI.parse('https://calendar.google.com/calendar/ical/sylvain%40sleede.com/public/basic.ics')) - - require 'icalendar' - require 'icalendar/tzinfo' - - cals = Icalendar::Calendar.parse(ics) - @events = cals.first.events - end -end \ No newline at end of file diff --git a/app/models/i_calendar.rb b/app/models/i_calendar.rb new file mode 100644 index 000000000..17d630cae --- /dev/null +++ b/app/models/i_calendar.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +# iCalendar (RFC 5545) files, stored by URL and kept with their display configuration +class ICalendar < ActiveRecord::Base +end diff --git a/app/policies/i_calendar_policy.rb b/app/policies/i_calendar_policy.rb new file mode 100644 index 000000000..fd4021814 --- /dev/null +++ b/app/policies/i_calendar_policy.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +# Check the access policies for API::ICalendarController +class ICalendarPolicy < ApplicationPolicy + def create? + user.admin? + end + + def destroy? + user.admin? + end +end diff --git a/app/views/api/i_calendar/_i_calendar.json.jbuilder b/app/views/api/i_calendar/_i_calendar.json.jbuilder new file mode 100644 index 000000000..23b68df1d --- /dev/null +++ b/app/views/api/i_calendar/_i_calendar.json.jbuilder @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +json.extract! i_cal, :id, :url, :color, :text_color, :text_hidden diff --git a/app/views/api/ical/externals.json.jbuilder b/app/views/api/i_calendar/events.json.jbuilder similarity index 74% rename from app/views/api/ical/externals.json.jbuilder rename to app/views/api/i_calendar/events.json.jbuilder index faedb232b..5078729e7 100644 --- a/app/views/api/ical/externals.json.jbuilder +++ b/app/views/api/i_calendar/events.json.jbuilder @@ -1,8 +1,10 @@ +# frozen_string_literal: true + json.array!(@events) do |event| json.id event.uid json.title event.summary json.start event.dtstart.iso8601 json.end event.dtend.iso8601 json.backgroundColor 'white' - json.borderColor '#214712' + json.borderColor event.color end diff --git a/app/views/api/i_calendar/index.json.jbuilder b/app/views/api/i_calendar/index.json.jbuilder new file mode 100644 index 000000000..a517d6997 --- /dev/null +++ b/app/views/api/i_calendar/index.json.jbuilder @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +json.array!(@i_cals) do |i_cal| + json.partial! 'api/i_calendar/i_calendar', i_cal: i_cal +end diff --git a/app/views/api/i_calendar/show.json.jbuilder b/app/views/api/i_calendar/show.json.jbuilder new file mode 100644 index 000000000..8e787f030 --- /dev/null +++ b/app/views/api/i_calendar/show.json.jbuilder @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +json.partial! 'api/i_calendar/i_calendar', i_cal: @i_cal diff --git a/config/routes.rb b/config/routes.rb index 9e0b5e095..bcb92b44c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -112,7 +112,9 @@ Rails.application.routes.draw do get 'first', action: 'first', on: :collection end - get 'ical/externals' => 'ical#externals' + resources :i_calendar, only: %i[index create destroy] do + get 'events', on: :collection + end # for admin resources :trainings do diff --git a/db/migrate/20191127153729_create_i_calendars.rb b/db/migrate/20191127153729_create_i_calendars.rb new file mode 100644 index 000000000..289e93b94 --- /dev/null +++ b/db/migrate/20191127153729_create_i_calendars.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +# From this migration, we store URL to iCalendar files and a piece of configuration about them. +# This allows to display the events of these external calendars in fab-manager +class CreateICalendars < ActiveRecord::Migration + def change + create_table :i_calendars do |t| + t.string :url + t.string :color + t.string :text_color + t.boolean :text_hidden + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 6e2ed6b2c..bd9579e7f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20191113103352) do +ActiveRecord::Schema.define(version: 20191127153729) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -251,6 +251,15 @@ ActiveRecord::Schema.define(version: 20191113103352) do add_index "history_values", ["invoicing_profile_id"], name: "index_history_values_on_invoicing_profile_id", using: :btree add_index "history_values", ["setting_id"], name: "index_history_values_on_setting_id", using: :btree + create_table "i_calendars", force: :cascade do |t| + t.string "url" + t.string "color" + t.string "text_color" + t.boolean "text_hidden" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "imports", force: :cascade do |t| t.integer "user_id" t.string "attachment" diff --git a/test/fixtures/i_calendars.yml b/test/fixtures/i_calendars.yml new file mode 100644 index 000000000..d3406e7cf --- /dev/null +++ b/test/fixtures/i_calendars.yml @@ -0,0 +1,13 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + url: MyString + color: MyString + text_color: MyString + text_hidden: false + +two: + url: MyString + color: MyString + text_color: MyString + text_hidden: false diff --git a/test/models/i_calendar_test.rb b/test/models/i_calendar_test.rb new file mode 100644 index 000000000..0ab530e7c --- /dev/null +++ b/test/models/i_calendar_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class ICalendarTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end From 97d93cd6226ff9ddfbc4e124c7c43144c5a7c080 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 27 Nov 2019 17:39:19 +0100 Subject: [PATCH 06/17] added a name to imported calendar --- .../controllers/admin/calendar.js.erb | 23 +++++++++++++------ app/assets/stylesheets/modules/icalendar.scss | 9 ++++++++ .../templates/admin/calendar/icalendar.html | 12 ++++++++-- app/controllers/api/i_calendar_controller.rb | 2 +- .../api/i_calendar/_i_calendar.json.jbuilder | 2 +- config/locales/app.admin.fr.yml | 2 ++ .../20191127153729_create_i_calendars.rb | 1 + db/schema.rb | 1 + 8 files changed, 41 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index e41157041..85d95c1db 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -762,8 +762,8 @@ Application.Controllers.controller('DeleteRecurrentAvailabilityController', ['$s * Controller used in the iCalendar (ICS) imports management page */ -Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCalendars', - function ($scope, iCalendars) { +Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCalendars', 'ICalendar', 'growl', '_t', + function ($scope, iCalendars, ICalendar, growl, _t) { // list of ICS sources $scope.calendars = iCalendars; @@ -772,6 +772,7 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale color: undefined, textColor: undefined, url: undefined, + name: undefined, textHidden: false }; @@ -779,11 +780,19 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale * Save the new iCalendar in database */ $scope.save = function () { - $scope.calendars.push(Object.assign({}, $scope.newCalendar)); - $scope.newCalendar.url = undefined; - $scope.newCalendar.color = null; - $scope.newCalendar.textColor = null; - $scope.newCalendar.textHidden = false; + ICalendar.save({}, { i_calendar: $scope.newCalendar }, function (data) { + // success + $scope.calendars.push(data); + $scope.newCalendar.url = undefined; + $scope.newCalendar.name = undefined; + $scope.newCalendar.color = null; + $scope.newCalendar.textColor = null; + $scope.newCalendar.textHidden = false; + }, function (error) { + // failed + growl.error(_t('icalendar.create_error')); + console.error(error); + }) } /** diff --git a/app/assets/stylesheets/modules/icalendar.scss b/app/assets/stylesheets/modules/icalendar.scss index a36dafa74..7c852fcd6 100644 --- a/app/assets/stylesheets/modules/icalendar.scss +++ b/app/assets/stylesheets/modules/icalendar.scss @@ -8,3 +8,12 @@ margin-top: 1em; } } + +.calendar-name { + font-weight: 600; + font-style: italic; +} + +.calendar-url { + overflow: hidden; +} diff --git a/app/assets/templates/admin/calendar/icalendar.html b/app/assets/templates/admin/calendar/icalendar.html index 70faf8f5f..fec55b748 100644 --- a/app/assets/templates/admin/calendar/icalendar.html +++ b/app/assets/templates/admin/calendar/icalendar.html @@ -30,14 +30,16 @@ - + + - + +
{{ 'icalendar.url' }}{{ 'icalendar.name' }}{{ 'icalendar.url' }} {{ 'icalendar.display' }}
{{calendar.url}}{{calendar.name}}{{calendar.url}} {{ calendar.textHidden ? '' : 'icalendar.example' }} @@ -47,6 +49,12 @@

{{ 'icalendar.new_import' }}

+
+
+ +
+ +
diff --git a/app/controllers/api/i_calendar_controller.rb b/app/controllers/api/i_calendar_controller.rb index f004a13ab..857ae30d1 100644 --- a/app/controllers/api/i_calendar_controller.rb +++ b/app/controllers/api/i_calendar_controller.rb @@ -50,6 +50,6 @@ class API::ICalendarController < API::ApiController end def i_calendar_params - params.require(:i_calendar).permit(:url, :color, :text_color, :text_hidden) + params.require(:i_calendar).permit(:name, :url, :color, :text_color, :text_hidden) end end diff --git a/app/views/api/i_calendar/_i_calendar.json.jbuilder b/app/views/api/i_calendar/_i_calendar.json.jbuilder index 23b68df1d..dfa627cd3 100644 --- a/app/views/api/i_calendar/_i_calendar.json.jbuilder +++ b/app/views/api/i_calendar/_i_calendar.json.jbuilder @@ -1,3 +1,3 @@ # frozen_string_literal: true -json.extract! i_cal, :id, :url, :color, :text_color, :text_hidden +json.extract! i_cal, :id, :name, :url, :color, :text_color, :text_hidden diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 576cada15..85e75a36a 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -96,11 +96,13 @@ fr: color: "Couleur" text_color: "Couleur du texte" url: "URL" + name: "Nom" example: "Exemple" display: "Affichage" hide_text: "Cacher le texte" hidden: "Caché" shown: "Affiché" + create_error: "Impossible de créer l'import iCalendar. Veuillez réessayer ultérieurement" project_elements: # gestion des éléments constituant les projets diff --git a/db/migrate/20191127153729_create_i_calendars.rb b/db/migrate/20191127153729_create_i_calendars.rb index 289e93b94..b9b558a0f 100644 --- a/db/migrate/20191127153729_create_i_calendars.rb +++ b/db/migrate/20191127153729_create_i_calendars.rb @@ -6,6 +6,7 @@ class CreateICalendars < ActiveRecord::Migration def change create_table :i_calendars do |t| t.string :url + t.string :name t.string :color t.string :text_color t.boolean :text_hidden diff --git a/db/schema.rb b/db/schema.rb index bd9579e7f..30b47937e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -253,6 +253,7 @@ ActiveRecord::Schema.define(version: 20191127153729) do create_table "i_calendars", force: :cascade do |t| t.string "url" + t.string "name" t.string "color" t.string "text_color" t.boolean "text_hidden" From 85d17d62f39df8ae18709bca981e6fb1800e2e90 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 27 Nov 2019 17:44:01 +0100 Subject: [PATCH 07/17] fix events endpoint --- app/controllers/api/i_calendar_controller.rb | 2 +- app/views/api/i_calendar/events.json.jbuilder | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/api/i_calendar_controller.rb b/app/controllers/api/i_calendar_controller.rb index 857ae30d1..902ae04b7 100644 --- a/app/controllers/api/i_calendar_controller.rb +++ b/app/controllers/api/i_calendar_controller.rb @@ -38,7 +38,7 @@ class API::ICalendarController < API::ApiController cals = Icalendar::Calendar.parse(ics) cals.first.events.each do |evt| - @events.push(evt.merge!(color: i_cal.color)) + @events.push(evt) end end end diff --git a/app/views/api/i_calendar/events.json.jbuilder b/app/views/api/i_calendar/events.json.jbuilder index 5078729e7..fc8e89f64 100644 --- a/app/views/api/i_calendar/events.json.jbuilder +++ b/app/views/api/i_calendar/events.json.jbuilder @@ -6,5 +6,4 @@ json.array!(@events) do |event| json.start event.dtstart.iso8601 json.end event.dtend.iso8601 json.backgroundColor 'white' - json.borderColor event.color end From f72ae98109e90c2db797299b7c827df03a1feaa4 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Wed, 27 Nov 2019 17:50:38 +0100 Subject: [PATCH 08/17] do not show title if calendar is configured to hide them --- app/controllers/api/i_calendar_controller.rb | 2 +- app/views/api/i_calendar/events.json.jbuilder | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/api/i_calendar_controller.rb b/app/controllers/api/i_calendar_controller.rb index 902ae04b7..6b798921f 100644 --- a/app/controllers/api/i_calendar_controller.rb +++ b/app/controllers/api/i_calendar_controller.rb @@ -38,7 +38,7 @@ class API::ICalendarController < API::ApiController cals = Icalendar::Calendar.parse(ics) cals.first.events.each do |evt| - @events.push(evt) + @events.push(calendar: i_cal, event: evt) end end end diff --git a/app/views/api/i_calendar/events.json.jbuilder b/app/views/api/i_calendar/events.json.jbuilder index fc8e89f64..2029e8558 100644 --- a/app/views/api/i_calendar/events.json.jbuilder +++ b/app/views/api/i_calendar/events.json.jbuilder @@ -1,9 +1,9 @@ # frozen_string_literal: true json.array!(@events) do |event| - json.id event.uid - json.title event.summary - json.start event.dtstart.iso8601 - json.end event.dtend.iso8601 + json.id event[:event].uid + json.title event[:calendar].text_hidden ? '' : event[:event].summary + json.start event[:event].dtstart.iso8601 + json.end event[:event].dtend.iso8601 json.backgroundColor 'white' end From 36eba998082ec27f97ebe54a6fb145aa59630af9 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 2 Dec 2019 10:39:42 +0100 Subject: [PATCH 09/17] [bug] report errors on admin creation --- CHANGELOG.md | 1 + app/assets/javascripts/controllers/admin/members.js.erb | 3 ++- app/services/user_service.rb | 2 +- config/locales/app.admin.en.yml | 1 + config/locales/app.admin.es.yml | 1 + config/locales/app.admin.fr.yml | 1 + config/locales/app.admin.pt.yml | 1 + 7 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccd19e3a4..e36c9ad9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Updated moment-timezone - Added freeCAD files as default allowed extensions - Fix a bug: unable to remove the picture from a training +- Fix a bug: report errors on admin creation - Fix a security issue: updated loofah to fix [CVE-2019-15587](https://github.com/advisories/GHSA-c3gv-9cxf-6f57) - Fix a security issue: updated angular to 1.7.9 to fix [CVE-2019-10768](https://github.com/advisories/GHSA-89mq-4x47-5v83) - [TODO DEPLOY] add the `SLOT_DURATION` environment variable (see [doc/environment.md](doc/environment.md#SLOT_DURATION) for configuration details) diff --git a/app/assets/javascripts/controllers/admin/members.js.erb b/app/assets/javascripts/controllers/admin/members.js.erb index e9c9f77c9..bacbd58aa 100644 --- a/app/assets/javascripts/controllers/admin/members.js.erb +++ b/app/assets/javascripts/controllers/admin/members.js.erb @@ -754,7 +754,8 @@ Application.Controllers.controller('NewAdminController', ['$state', '$scope', 'A return $state.go('app.admin.members'); } , function (error) { - console.log(error); + growl.error(_t('failed_to_create_admin') + JSON.stringify(error.data ? error.data : error)); + console.error(error); } ); }; diff --git a/app/services/user_service.rb b/app/services/user_service.rb index 3293c57c6..55d05e6fa 100644 --- a/app/services/user_service.rb +++ b/app/services/user_service.rb @@ -41,7 +41,7 @@ class UserService # if the authentication is made through an SSO, generate a migration token admin.generate_auth_migration_token unless AuthProvider.active.providable_type == DatabaseProvider.name - saved = admin.save(validate: false) + saved = admin.save if saved admin.send_confirmation_instructions admin.add_role(:admin) diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 0b1aaa437..960196bbc 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -678,6 +678,7 @@ en: # add a new administrator to the platform add_an_administrator: "Add an administrator" administrator_successfully_created_he_will_receive_his_connection_directives_by_email: "Administrator successfully created. {GENDER, select, female{She} other{He}} receive {GENDER, select, female{her} other{his}} connection directives by e-mail." # messageFormat interpolation + failed_to_create_admin: "Unable to create the administrator:" authentication_new: # add a new authentication provider (SSO) diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index 2f329ba4b..ed24b3296 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -678,6 +678,7 @@ es: # add a new administrator to the platform add_an_administrator: "Agregar un administrador" administrator_successfully_created_he_will_receive_his_connection_directives_by_email: "administrador creado correctamente. {GENDER, select, female{She} other{He}} receive {GENDER, select, female{her} other{his}} directivas de conexión por e-mail." # messageFormat interpolation + failed_to_create_admin: "No se puede crear el administrador :" authentication_new: # add a new authentication provider (SSO) diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index 85e75a36a..ada28f728 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -694,6 +694,7 @@ fr: # ajouter un nouvel administrateur à la plate-forme add_an_administrator: "Ajouter un administrateur" administrator_successfully_created_he_will_receive_his_connection_directives_by_email: "L'administrateur a bien été créé. {GENDER, select, female{Elle} other{Il}} recevra ses instructions de connexion par email." # messageFormat interpolation + failed_to_create_admin: "Impossible de créer l'administrateur :" authentication_new: # ajouter un nouveau fournisseur d'authentification (SSO) diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index 487a45cf2..d6795c2b4 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -678,6 +678,7 @@ pt: # add a new administrator to the platform add_an_administrator: "Adicionar administrador" administrator_successfully_created_he_will_receive_his_connection_directives_by_email: "Administrator criado com sucesso. {GENDER, select, female{Ela} other{Ele}} receberá {GENDER, select, female{sua} other{seu}} diretivas de conexão por e-mail." # messageFormat interpolation + failed_to_create_admin: "Não é possível criar administrador:" authentication_new: # add a new authentication provider (SSO) From 55d2c88134538710e1ed82692df19bf2bba7735b Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 2 Dec 2019 12:19:30 +0100 Subject: [PATCH 10/17] delete & sync ical sources --- .../controllers/admin/calendar.js.erb | 31 +++++++++++++++++-- app/assets/javascripts/services/icalendar.js | 5 +++ .../templates/admin/calendar/icalendar.html | 3 +- app/controllers/api/i_calendar_controller.rb | 5 +++ config/locales/app.admin.fr.yml | 3 ++ config/routes.rb | 1 + 6 files changed, 45 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index 85d95c1db..71fbd47b3 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -813,8 +813,35 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale * @param calendar */ $scope.delete = function (calendar) { - const idx = $scope.calendars.indexOf(calendar); - $scope.calendars.splice(idx, 1); + ICalendar.delete( + { id: calendar.id }, + function () { + // success + const idx = $scope.calendars.indexOf(calendar); + $scope.calendars.splice(idx, 1); + }, function (error) { + // failed + growl.error(_t('icalendar.delete_failed')); + console.error(error); + }); + } + + /** + * Asynchronously re-fetches the events from the given calendar + * @param calendar + */ + $scope.sync = function (calendar) { + ICalendar.sync( + { id: calendar.id }, + function () { + // success + growl.info(_t('icalendar.refresh')); + }, function (error) { + // failed + growl.error(_t('icalendar.sync_failed')); + console.error(error); + } + ) } } ]); diff --git a/app/assets/javascripts/services/icalendar.js b/app/assets/javascripts/services/icalendar.js index 21d30843e..1e01bd5e3 100644 --- a/app/assets/javascripts/services/icalendar.js +++ b/app/assets/javascripts/services/icalendar.js @@ -6,6 +6,11 @@ Application.Services.factory('ICalendar', ['$resource', function ($resource) { events: { method: 'GET', url: '/api/i_calendar/events' + }, + sync: { + method: 'POST', + url: '/api/i_calendar/:id/sync', + params: { id: '@id' } } } ); diff --git a/app/assets/templates/admin/calendar/icalendar.html b/app/assets/templates/admin/calendar/icalendar.html index fec55b748..6c9411d48 100644 --- a/app/assets/templates/admin/calendar/icalendar.html +++ b/app/assets/templates/admin/calendar/icalendar.html @@ -33,7 +33,7 @@ {{ 'icalendar.name' }} {{ 'icalendar.url' }} {{ 'icalendar.display' }} - + @@ -42,6 +42,7 @@ {{calendar.url}} {{ calendar.textHidden ? '' : 'icalendar.example' }} + diff --git a/app/controllers/api/i_calendar_controller.rb b/app/controllers/api/i_calendar_controller.rb index 6b798921f..c506507fe 100644 --- a/app/controllers/api/i_calendar_controller.rb +++ b/app/controllers/api/i_calendar_controller.rb @@ -43,6 +43,11 @@ class API::ICalendarController < API::ApiController end end + def sync + puts '[TODO] run worker' + render json: { processing: true }, status: :created + end + private def set_i_cal diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index ada28f728..e5218afc6 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -103,6 +103,9 @@ fr: hidden: "Caché" shown: "Affiché" create_error: "Impossible de créer l'import iCalendar. Veuillez réessayer ultérieurement" + delete_failed: "Impossible de supprimer l'import iCalendar. Veuillez réessayer ultérieurement" + refresh: "Mise à jour en cours..." + sync_failed: "Impossible de synchroniser l'URL. Veuillez réessayer ultérieurement" project_elements: # gestion des éléments constituant les projets diff --git a/config/routes.rb b/config/routes.rb index bcb92b44c..a8f97dcbc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -114,6 +114,7 @@ Rails.application.routes.draw do resources :i_calendar, only: %i[index create destroy] do get 'events', on: :collection + post 'sync', on: :member end # for admin From cca6b14f58a1ae5cc97a8567af0ccec3b7ca71e9 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 2 Dec 2019 15:53:24 +0100 Subject: [PATCH 11/17] import events asyncronously from icalendar --- app/assets/javascripts/services/icalendar.js | 2 +- app/controllers/api/i_calendar_controller.rb | 20 +++--------- app/models/i_calendar.rb | 10 ++++++ app/models/i_calendar_event.rb | 6 ++++ app/services/i_calendar_import_service.rb | 31 +++++++++++++++++++ app/views/api/i_calendar/events.json.jbuilder | 8 ++--- app/workers/i_calendar_import_worker.rb | 14 +++++++++ config/routes.rb | 2 +- config/schedule.yml | 5 +++ ...20191202135507_create_i_calendar_events.rb | 15 +++++++++ db/schema.rb | 17 +++++++++- test/fixtures/i_calendar_events.yml | 19 ++++++++++++ test/models/i_calendar_test.rb | 7 ----- 13 files changed, 126 insertions(+), 30 deletions(-) create mode 100644 app/models/i_calendar_event.rb create mode 100644 app/services/i_calendar_import_service.rb create mode 100644 app/workers/i_calendar_import_worker.rb create mode 100644 db/migrate/20191202135507_create_i_calendar_events.rb create mode 100644 test/fixtures/i_calendar_events.yml delete mode 100644 test/models/i_calendar_test.rb diff --git a/app/assets/javascripts/services/icalendar.js b/app/assets/javascripts/services/icalendar.js index 1e01bd5e3..93c790dab 100644 --- a/app/assets/javascripts/services/icalendar.js +++ b/app/assets/javascripts/services/icalendar.js @@ -5,7 +5,7 @@ Application.Services.factory('ICalendar', ['$resource', function ($resource) { { id: '@id' }, { events: { method: 'GET', - url: '/api/i_calendar/events' + url: '/api/i_calendar/:id/events' }, sync: { method: 'POST', diff --git a/app/controllers/api/i_calendar_controller.rb b/app/controllers/api/i_calendar_controller.rb index c506507fe..5e17224f6 100644 --- a/app/controllers/api/i_calendar_controller.rb +++ b/app/controllers/api/i_calendar_controller.rb @@ -27,25 +27,13 @@ class API::ICalendarController < API::ApiController end def events - require 'net/http' - require 'uri' - require 'icalendar' - - @events = [] - - @i_cals = ICalendar.all.each do |i_cal| - ics = Net::HTTP.get(URI.parse(i_cal.url)) - cals = Icalendar::Calendar.parse(ics) - - cals.first.events.each do |evt| - @events.push(calendar: i_cal, event: evt) - end - end + @events = ICalendarEvent.where(i_calendar_id: params[:id]).joins(:i_calendar) end def sync - puts '[TODO] run worker' - render json: { processing: true }, status: :created + worker = ICalendarImportWorker.new + worker.perform([params[:id]]) + render json: { processing: [params[:id]] }, status: :created end private diff --git a/app/models/i_calendar.rb b/app/models/i_calendar.rb index 17d630cae..d670e4986 100644 --- a/app/models/i_calendar.rb +++ b/app/models/i_calendar.rb @@ -2,4 +2,14 @@ # iCalendar (RFC 5545) files, stored by URL and kept with their display configuration class ICalendar < ActiveRecord::Base + has_many :i_calendar_events + + after_create sync_events + + private + + def sync_events + worker = ICalendarImportWorker.new + worker.perform([id]) + end end diff --git a/app/models/i_calendar_event.rb b/app/models/i_calendar_event.rb new file mode 100644 index 000000000..b7b4897d0 --- /dev/null +++ b/app/models/i_calendar_event.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# iCalendar (RFC 5545) event, belonging to an ICalendar object (its source) +class ICalendarEvent < ActiveRecord::Base + belongs_to :i_calendar +end diff --git a/app/services/i_calendar_import_service.rb b/app/services/i_calendar_import_service.rb new file mode 100644 index 000000000..06b122bfe --- /dev/null +++ b/app/services/i_calendar_import_service.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +# Import all events from a given remote RFC 5545 iCalendar +class ICalendarImportService + def import(i_calendar_id) + require 'net/http' + require 'uri' + require 'icalendar' + + events = [] + + i_cal = ICalendar.find(i_calendar_id) + ics = Net::HTTP.get(URI.parse(i_cal.url)) + cals = Icalendar::Calendar.parse(ics) + + cals.each do |cal| + cal.events.each do |evt| + events.push( + uid: evt.uid, + dtstart: evt.dtstart, + dtend: evt.dtend, + summary: evt.summary, + description: evt.description, + i_calendar_id: i_calendar_id + ) + end + end + + ICalendarEvent.create!(events) + end +end diff --git a/app/views/api/i_calendar/events.json.jbuilder b/app/views/api/i_calendar/events.json.jbuilder index 2029e8558..725d6f0f0 100644 --- a/app/views/api/i_calendar/events.json.jbuilder +++ b/app/views/api/i_calendar/events.json.jbuilder @@ -1,9 +1,9 @@ # frozen_string_literal: true json.array!(@events) do |event| - json.id event[:event].uid - json.title event[:calendar].text_hidden ? '' : event[:event].summary - json.start event[:event].dtstart.iso8601 - json.end event[:event].dtend.iso8601 + json.id event.uid + json.title event.i_calendar.text_hidden ? '' : event.summary + json.start event.dtstart.iso8601 + json.end event.dtend.iso8601 json.backgroundColor 'white' end diff --git a/app/workers/i_calendar_import_worker.rb b/app/workers/i_calendar_import_worker.rb new file mode 100644 index 000000000..cf571eaba --- /dev/null +++ b/app/workers/i_calendar_import_worker.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +# Periodically import the iCalendar RFC 5545 events from the configured source +class ICalendarImportWorker + include Sidekiq::Worker + + def perform(calendar_ids = ICalendar.all.map(&:id)) + service = ICalendarImportService.new + + calendar_ids.each do |id| + service.import(id) + end + end +end diff --git a/config/routes.rb b/config/routes.rb index a8f97dcbc..f3f505121 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -113,7 +113,7 @@ Rails.application.routes.draw do end resources :i_calendar, only: %i[index create destroy] do - get 'events', on: :collection + get 'events', on: :member post 'sync', on: :member end diff --git a/config/schedule.yml b/config/schedule.yml index f2a8e114e..d89fe27cf 100644 --- a/config/schedule.yml +++ b/config/schedule.yml @@ -15,6 +15,11 @@ generate_statistic: class: "StatisticWorker" queue: default +i_calendar_import: + cron: "0 2 * * *" + class: "ICalendarImportWorker" + queue: default + open_api_trace_calls_count: cron: "0 4 * * 0" # every sunday at 4am class: "OpenAPITraceCallsCountWorker" diff --git a/db/migrate/20191202135507_create_i_calendar_events.rb b/db/migrate/20191202135507_create_i_calendar_events.rb new file mode 100644 index 000000000..748349cd5 --- /dev/null +++ b/db/migrate/20191202135507_create_i_calendar_events.rb @@ -0,0 +1,15 @@ +class CreateICalendarEvents < ActiveRecord::Migration + def change + create_table :i_calendar_events do |t| + t.string :uid + t.datetime :dtstart + t.datetime :dtend + t.string :summary + t.string :description + t.string :attendee + t.belongs_to :i_calendar, index: true, foreign_key: true + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 30b47937e..1d370074c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20191127153729) do +ActiveRecord::Schema.define(version: 20191202135507) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -251,6 +251,20 @@ ActiveRecord::Schema.define(version: 20191127153729) do add_index "history_values", ["invoicing_profile_id"], name: "index_history_values_on_invoicing_profile_id", using: :btree add_index "history_values", ["setting_id"], name: "index_history_values_on_setting_id", using: :btree + create_table "i_calendar_events", force: :cascade do |t| + t.string "uid" + t.datetime "dtstart" + t.datetime "dtend" + t.string "summary" + t.string "description" + t.string "attendee" + t.integer "i_calendar_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "i_calendar_events", ["i_calendar_id"], name: "index_i_calendar_events_on_i_calendar_id", using: :btree + create_table "i_calendars", force: :cascade do |t| t.string "url" t.string "name" @@ -933,6 +947,7 @@ ActiveRecord::Schema.define(version: 20191127153729) do add_foreign_key "exports", "users" add_foreign_key "history_values", "invoicing_profiles" add_foreign_key "history_values", "settings" + add_foreign_key "i_calendar_events", "i_calendars" add_foreign_key "invoices", "coupons" add_foreign_key "invoices", "invoicing_profiles" add_foreign_key "invoices", "invoicing_profiles", column: "operator_profile_id" diff --git a/test/fixtures/i_calendar_events.yml b/test/fixtures/i_calendar_events.yml new file mode 100644 index 000000000..490aa79a1 --- /dev/null +++ b/test/fixtures/i_calendar_events.yml @@ -0,0 +1,19 @@ +# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + uid: MyString + dtstart: 2019-12-02 14:55:07 + dtend: 2019-12-02 14:55:07 + summary: MyString + description: MyString + attendee: MyString + i_calendar_id: + +two: + uid: MyString + dtstart: 2019-12-02 14:55:07 + dtend: 2019-12-02 14:55:07 + summary: MyString + description: MyString + attendee: MyString + i_calendar_id: diff --git a/test/models/i_calendar_test.rb b/test/models/i_calendar_test.rb deleted file mode 100644 index 0ab530e7c..000000000 --- a/test/models/i_calendar_test.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'test_helper' - -class ICalendarTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end -end From 22be9f6a08fa041b09f2d0f6c786df910551991e Mon Sep 17 00:00:00 2001 From: Sylvain Date: Mon, 2 Dec 2019 16:49:20 +0100 Subject: [PATCH 12/17] display external calendars list in public calendar + pull availabilities --- .../javascripts/controllers/calendar.js | 41 ++++++++++++------- app/assets/javascripts/router.js.erb | 1 + app/assets/templates/calendar/filter.html.erb | 11 +++++ app/controllers/api/i_calendar_controller.rb | 7 +++- app/models/i_calendar.rb | 2 +- config/locales/app.public.fr.yml | 1 + 6 files changed, 47 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index 33d811f55..ce0e1298d 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -16,8 +16,8 @@ * Controller used in the public calendar global */ -Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', - function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise) { +Application.Controllers.controller('CalendarController', ['$scope', '$state', '$aside', 'moment', 'Availability', 'Slot', 'Setting', 'growl', 'dialogs', 'bookingWindowStart', 'bookingWindowEnd', '_t', 'uiCalendarConfig', 'CalendarConfig', 'trainingsPromise', 'machinesPromise', 'spacesPromise', 'iCalendarPromise', + function ($scope, $state, $aside, moment, Availability, Slot, Setting, growl, dialogs, bookingWindowStart, bookingWindowEnd, _t, uiCalendarConfig, CalendarConfig, trainingsPromise, machinesPromise, spacesPromise, iCalendarPromise) { /* PRIVATE STATIC CONSTANTS */ let currentMachineEvent = null; machinesPromise.forEach(m => m.checked = true); @@ -38,12 +38,11 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ // List of spaces $scope.spaces = spacesPromise.filter(t => !t.disabled); + // List of external iCalendar sources + $scope.externals = iCalendarPromise; + // add availabilities source to event sources $scope.eventSources = []; - $scope.eventSources.push({ - url: '/api/i_calendar/events', - textColor: 'black' - }); // filter availabilities if have change $scope.filterAvailabilities = function (filter, scope) { @@ -52,10 +51,19 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ trainings: isSelectAll('trainings', scope), machines: isSelectAll('machines', scope), spaces: isSelectAll('spaces', scope), + externals: isSelectAll('externals', scope), evt: filter.evt, dispo: filter.dispo }); - return $scope.calendarConfig.events = availabilitySourceUrl(); + $scope.calendarConfig.events = availabilitySourceUrl(); + $scope.externals.filter(e => e.checked).forEach(e => { + $scope.eventSources.push({ + url: `/api/i_calendar/${e.id}/events`, + textColor: e.textColor, + color: e.color + }); + }); + return uiCalendarConfig.calendars.calendar.fullCalendar('refetchEvents'); }; // a variable for formation/machine/event/dispo checkbox is or not checked @@ -63,6 +71,7 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ trainings: isSelectAll('trainings', $scope), machines: isSelectAll('machines', $scope), spaces: isSelectAll('spaces', $scope), + externals: isSelectAll('externals', $scope), evt: true, dispo: true }; @@ -89,6 +98,9 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ spaces () { return $scope.spaces; }, + externals () { + return $scope.externals; + }, filter () { return $scope.filter; }, @@ -99,10 +111,11 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ return $scope.filterAvailabilities; } }, - controller: ['$scope', '$uibModalInstance', 'trainings', 'machines', 'spaces', 'filter', 'toggleFilter', 'filterAvailabilities', function ($scope, $uibModalInstance, trainings, machines, spaces, filter, toggleFilter, filterAvailabilities) { + controller: ['$scope', '$uibModalInstance', 'trainings', 'machines', 'spaces', 'externals', 'filter', 'toggleFilter', 'filterAvailabilities', function ($scope, $uibModalInstance, trainings, machines, spaces, externals, filter, toggleFilter, filterAvailabilities) { $scope.trainings = trainings; $scope.machines = machines; $scope.spaces = spaces; + $scope.externals = externals; $scope.filter = filter; $scope.toggleFilter = (type, filter) => toggleFilter(type, filter); @@ -124,19 +137,19 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ if (event.available_type === 'machines') { currentMachineEvent = event; calendar.fullCalendar('changeView', 'agendaDay'); - return calendar.fullCalendar('gotoDate', event.start); + calendar.fullCalendar('gotoDate', event.start); } else if (event.available_type === 'space') { calendar.fullCalendar('changeView', 'agendaDay'); - return calendar.fullCalendar('gotoDate', event.start); + calendar.fullCalendar('gotoDate', event.start); } else if (event.available_type === 'event') { - return $state.go('app.public.events_show', { id: event.event_id }); + $state.go('app.public.events_show', { id: event.event_id }); } else if (event.available_type === 'training') { - return $state.go('app.public.training_show', { id: event.training_id }); + $state.go('app.public.training_show', { id: event.training_id }); } else { if (event.machine_id) { - return $state.go('app.public.machines_show', { id: event.machine_id }); + $state.go('app.public.machines_show', { id: event.machine_id }); } else if (event.space_id) { - return $state.go('app.public.space_show', { id: event.space_id }); + $state.go('app.public.space_show', { id: event.space_id }); } } }; diff --git a/app/assets/javascripts/router.js.erb b/app/assets/javascripts/router.js.erb index 62503a9cf..7ebc08af3 100644 --- a/app/assets/javascripts/router.js.erb +++ b/app/assets/javascripts/router.js.erb @@ -638,6 +638,7 @@ angular.module('application.router', ['ui.router']) trainingsPromise: ['Training', function (Training) { return Training.query().$promise; }], machinesPromise: ['Machine', function (Machine) { return Machine.query().$promise; }], spacesPromise: ['Space', function (Space) { return Space.query().$promise; }], + iCalendarPromise: ['ICalendar', function (ICalendar) { return ICalendar.query().$promise; }], translations: ['Translations', function (Translations) { return Translations.query(['app.public.calendar']).$promise; }] } }) diff --git a/app/assets/templates/calendar/filter.html.erb b/app/assets/templates/calendar/filter.html.erb index c275fbeb9..76172a3a5 100644 --- a/app/assets/templates/calendar/filter.html.erb +++ b/app/assets/templates/calendar/filter.html.erb @@ -36,3 +36,14 @@

{{ 'calendar.show_unavailables' }}

+
+
+

{{ 'calendar.externals' }}

+ +
+ +
+ {{::e.name}} + +
+
diff --git a/app/controllers/api/i_calendar_controller.rb b/app/controllers/api/i_calendar_controller.rb index 5e17224f6..b828b709d 100644 --- a/app/controllers/api/i_calendar_controller.rb +++ b/app/controllers/api/i_calendar_controller.rb @@ -27,7 +27,12 @@ class API::ICalendarController < API::ApiController end def events - @events = ICalendarEvent.where(i_calendar_id: params[:id]).joins(:i_calendar) + start_date = ActiveSupport::TimeZone[params[:timezone]]&.parse(params[:start]) + end_date = ActiveSupport::TimeZone[params[:timezone]]&.parse(params[:end])&.end_of_day + + @events = ICalendarEvent.where(i_calendar_id: params[:id]) + .where('dtstart >= ? AND dtend <= ?', start_date, end_date) + .joins(:i_calendar) end def sync diff --git a/app/models/i_calendar.rb b/app/models/i_calendar.rb index d670e4986..1f63f6129 100644 --- a/app/models/i_calendar.rb +++ b/app/models/i_calendar.rb @@ -4,7 +4,7 @@ class ICalendar < ActiveRecord::Base has_many :i_calendar_events - after_create sync_events + after_create :sync_events private diff --git a/config/locales/app.public.fr.yml b/config/locales/app.public.fr.yml index 522562d10..c5493ad5f 100644 --- a/config/locales/app.public.fr.yml +++ b/config/locales/app.public.fr.yml @@ -306,6 +306,7 @@ fr: machines: "Machines" spaces: "Espaces" events: "Évènements" + externals: "Autres calendriers" spaces_list: # liste des espaces From 122ff54cd806aa96c06890809734842ffd832b74 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 3 Dec 2019 10:17:29 +0100 Subject: [PATCH 13/17] fix colors in ics imports --- .../javascripts/controllers/admin/calendar.js.erb | 14 +++++++------- app/assets/javascripts/controllers/calendar.js | 2 +- app/assets/templates/admin/calendar/icalendar.html | 6 +++--- app/models/i_calendar.rb | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index 71fbd47b3..2ae01b869 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -770,10 +770,10 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale // configuration of a new ICS source $scope.newCalendar = { color: undefined, - textColor: undefined, + text_color: undefined, url: undefined, name: undefined, - textHidden: false + text_hidden: false }; /** @@ -786,8 +786,8 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale $scope.newCalendar.url = undefined; $scope.newCalendar.name = undefined; $scope.newCalendar.color = null; - $scope.newCalendar.textColor = null; - $scope.newCalendar.textHidden = false; + $scope.newCalendar.text_color = null; + $scope.newCalendar.text_hidden = false; }, function (error) { // failed growl.error(_t('icalendar.create_error')); @@ -802,9 +802,9 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale $scope.calendarStyle = function (calendar) { return { 'border-color': calendar.color, - 'color': calendar.textColor, - 'width': calendar.textHidden ? '50px' : 'auto', - 'height': calendar.textHidden ? '21px' : 'auto' + 'color': calendar.text_color, + 'width': calendar.text_hidden ? '50px' : 'auto', + 'height': calendar.text_hidden ? '21px' : 'auto' }; } diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index ce0e1298d..fa5670550 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -59,7 +59,7 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ $scope.externals.filter(e => e.checked).forEach(e => { $scope.eventSources.push({ url: `/api/i_calendar/${e.id}/events`, - textColor: e.textColor, + textColor: e.text_color, color: e.color }); }); diff --git a/app/assets/templates/admin/calendar/icalendar.html b/app/assets/templates/admin/calendar/icalendar.html index 6c9411d48..c423e1aac 100644 --- a/app/assets/templates/admin/calendar/icalendar.html +++ b/app/assets/templates/admin/calendar/icalendar.html @@ -33,7 +33,7 @@ {{ 'icalendar.name' }} {{ 'icalendar.url' }} {{ 'icalendar.display' }} - + @@ -72,12 +72,12 @@
- +
Date: Tue, 3 Dec 2019 10:23:19 +0100 Subject: [PATCH 14/17] external calendars legend --- app/assets/javascripts/controllers/calendar.js | 11 +++++++++++ app/assets/stylesheets/modules/icalendar.scss | 5 +++++ app/assets/templates/calendar/filter.html.erb | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index fa5670550..f307a9e35 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -66,6 +66,17 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ return uiCalendarConfig.calendars.calendar.fullCalendar('refetchEvents'); }; + /** + * Return a CSS-like style of the given calendar configuration + * @param calendar + */ + $scope.calendarStyle = function (calendar) { + return { + 'border-color': calendar.color, + 'color': calendar.text_color, + }; + }; + // a variable for formation/machine/event/dispo checkbox is or not checked $scope.filter = { trainings: isSelectAll('trainings', $scope), diff --git a/app/assets/stylesheets/modules/icalendar.scss b/app/assets/stylesheets/modules/icalendar.scss index 7c852fcd6..d97ed60f7 100644 --- a/app/assets/stylesheets/modules/icalendar.scss +++ b/app/assets/stylesheets/modules/icalendar.scss @@ -17,3 +17,8 @@ .calendar-url { overflow: hidden; } + +.external-calendar-legend { + border-left: 3px solid; + border-radius: 3px; +} \ No newline at end of file diff --git a/app/assets/templates/calendar/filter.html.erb b/app/assets/templates/calendar/filter.html.erb index 76172a3a5..8db10fd89 100644 --- a/app/assets/templates/calendar/filter.html.erb +++ b/app/assets/templates/calendar/filter.html.erb @@ -43,7 +43,7 @@
- {{::e.name}} + {{::e.name}}
From 32e7fc390049320b017a0d579cb52e3a81c0bb97 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 3 Dec 2019 10:48:53 +0100 Subject: [PATCH 15/17] confirm delete icalendar imports + fix display + add translations --- .../controllers/admin/calendar.js.erb | 40 +++++++++++++------ .../templates/admin/calendar/icalendar.html | 4 +- app/assets/templates/calendar/filter.html.erb | 2 +- config/locales/app.admin.en.yml | 22 ++++++++++ config/locales/app.admin.es.yml | 22 ++++++++++ config/locales/app.admin.fr.yml | 3 ++ config/locales/app.admin.pt.yml | 22 ++++++++++ 7 files changed, 100 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/controllers/admin/calendar.js.erb b/app/assets/javascripts/controllers/admin/calendar.js.erb index 2ae01b869..220363e4d 100644 --- a/app/assets/javascripts/controllers/admin/calendar.js.erb +++ b/app/assets/javascripts/controllers/admin/calendar.js.erb @@ -762,8 +762,8 @@ Application.Controllers.controller('DeleteRecurrentAvailabilityController', ['$s * Controller used in the iCalendar (ICS) imports management page */ -Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCalendars', 'ICalendar', 'growl', '_t', - function ($scope, iCalendars, ICalendar, growl, _t) { +Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCalendars', 'ICalendar', 'dialogs', 'growl', '_t', + function ($scope, iCalendars, ICalendar, dialogs, growl, _t) { // list of ICS sources $scope.calendars = iCalendars; @@ -813,17 +813,33 @@ Application.Controllers.controller('AdminICalendarController', ['$scope', 'iCale * @param calendar */ $scope.delete = function (calendar) { - ICalendar.delete( - { id: calendar.id }, + dialogs.confirm( + { + resolve: { + object () { + return { + title: _t('icalendar.confirmation_required'), + msg: _t('icalendar.confirm_delete_import') + }; + } + } + }, function () { - // success - const idx = $scope.calendars.indexOf(calendar); - $scope.calendars.splice(idx, 1); - }, function (error) { - // failed - growl.error(_t('icalendar.delete_failed')); - console.error(error); - }); + ICalendar.delete( + { id: calendar.id }, + function () { + // success + const idx = $scope.calendars.indexOf(calendar); + $scope.calendars.splice(idx, 1); + growl.info(_t('icalendar.delete_success')); + }, function (error) { + // failed + growl.error(_t('icalendar.delete_failed')); + console.error(error); + } + ); + } + ) } /** diff --git a/app/assets/templates/admin/calendar/icalendar.html b/app/assets/templates/admin/calendar/icalendar.html index c423e1aac..a91d5090a 100644 --- a/app/assets/templates/admin/calendar/icalendar.html +++ b/app/assets/templates/admin/calendar/icalendar.html @@ -40,7 +40,7 @@ {{calendar.name}} {{calendar.url}} - {{ calendar.textHidden ? '' : 'icalendar.example' }} + {{ calendar.text_hidden ? '' : 'icalendar.example' }} @@ -72,7 +72,7 @@
- +
diff --git a/app/assets/templates/calendar/filter.html.erb b/app/assets/templates/calendar/filter.html.erb index 8db10fd89..7242f3c47 100644 --- a/app/assets/templates/calendar/filter.html.erb +++ b/app/assets/templates/calendar/filter.html.erb @@ -36,7 +36,7 @@

{{ 'calendar.show_unavailables' }}

-
+

{{ 'calendar.externals' }}

diff --git a/config/locales/app.admin.en.yml b/config/locales/app.admin.en.yml index 960196bbc..261408460 100644 --- a/config/locales/app.admin.en.yml +++ b/config/locales/app.admin.en.yml @@ -88,6 +88,28 @@ en: view_reservations: "View reservations" legend: "legend" + icalendar: + icalendar: + icalendar_import: "iCalendar import" + intro: "Fab-manager allows to automatically import calendar events, at RFC 5545 iCalendar format, from external URL. These URL are synchronized every nights and the events are shown in the public calendar." + new_import: "New ICS import" + color: "Colour" + text_color: "Text colour" + url: "URL" + name: "Name" + example: "Example" + display: "Display" + hide_text: "Hide the text" + hidden: "Hidden" + shown: "Shown" + create_error: "Unable to create iCalendar import. Please try again later" + delete_failed: "Unable to delete the iCalendar import. Please try again later" + refresh: "Updating..." + sync_failed: "Unable to synchronize the URL. Please try again later" + confirmation_required: "Confirmation required" + confirm_delete_import: "Do you really want to delete this iCalendar import?" + delete_success: "iCalendar import successfully deleted" + project_elements: # management of the projects' components project_elements: diff --git a/config/locales/app.admin.es.yml b/config/locales/app.admin.es.yml index ed24b3296..fb62110e7 100644 --- a/config/locales/app.admin.es.yml +++ b/config/locales/app.admin.es.yml @@ -88,6 +88,28 @@ es: view_reservations: "Ver reservas" # translation_missing legend: "leyenda" + icalendar: + icalendar: + icalendar_import: "iCalendar import" # translation_missing + intro: "Fab-manager allows to automatically import calendar events, at RFC 5545 iCalendar format, from external URL. These URL are synchronized every nights and the events are shown in the public calendar." # translation_missing + new_import: "New ICS import" # translation_missing + color: "Colour" # translation_missing + text_color: "Text colour" # translation_missing + url: "URL" # translation_missing + name: "Name" # translation_missing + example: "Example" # translation_missing + display: "Display" # translation_missing + hide_text: "Hide the text" # translation_missing + hidden: "Hidden" # translation_missing + shown: "Shown" # translation_missing + create_error: "Unable to create iCalendar import. Please try again later" # translation_missing + delete_failed: "Unable to delete the iCalendar import. Please try again later" # translation_missing + refresh: "Updating..." # translation_missing + sync_failed: "Unable to synchronize the URL. Please try again later" # translation_missing + confirmation_required: "Confirmation required" # translation_missing + confirm_delete_import: "Do you really want to delete this iCalendar import?" # translation_missing + delete_success: "iCalendar import successfully deleted" # translation_missing + project_elements: # management of the projects' components project_elements: diff --git a/config/locales/app.admin.fr.yml b/config/locales/app.admin.fr.yml index e5218afc6..4a5108f15 100644 --- a/config/locales/app.admin.fr.yml +++ b/config/locales/app.admin.fr.yml @@ -106,6 +106,9 @@ fr: delete_failed: "Impossible de supprimer l'import iCalendar. Veuillez réessayer ultérieurement" refresh: "Mise à jour en cours..." sync_failed: "Impossible de synchroniser l'URL. Veuillez réessayer ultérieurement" + confirmation_required: "Confirmation requise" + confirm_delete_import: "Êtes-vous sur de vouloir supprimer cet import iCalendar ?" + delete_success: "L'import iCalendar a bien été supprimé" project_elements: # gestion des éléments constituant les projets diff --git a/config/locales/app.admin.pt.yml b/config/locales/app.admin.pt.yml index d6795c2b4..a908d7da0 100755 --- a/config/locales/app.admin.pt.yml +++ b/config/locales/app.admin.pt.yml @@ -88,6 +88,28 @@ pt: view_reservations: "Ver reservas" # translation_missing legend: "legenda" + icalendar: + icalendar: + icalendar_import: "iCalendar import" # translation_missing + intro: "Fab-manager allows to automatically import calendar events, at RFC 5545 iCalendar format, from external URL. These URL are synchronized every nights and the events are shown in the public calendar." # translation_missing + new_import: "New ICS import" # translation_missing + color: "Colour" # translation_missing + text_color: "Text colour" # translation_missing + url: "URL" # translation_missing + name: "Name" # translation_missing + example: "Example" # translation_missing + display: "Display" # translation_missing + hide_text: "Hide the text" # translation_missing + hidden: "Hidden" # translation_missing + shown: "Shown" # translation_missing + create_error: "Unable to create iCalendar import. Please try again later" # translation_missing + delete_failed: "Unable to delete the iCalendar import. Please try again later" # translation_missing + refresh: "Updating..." # translation_missing + sync_failed: "Unable to synchronize the URL. Please try again later" # translation_missing + confirmation_required: "Confirmation required" # translation_missing + confirm_delete_import: "Do you really want to delete this iCalendar import?" # translation_missing + delete_success: "iCalendar import successfully deleted" # translation_missing + project_elements: # management of the projects' components project_elements: From 538b5cef7839194bad145a5a6818558b341ae837 Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 3 Dec 2019 11:27:20 +0100 Subject: [PATCH 16/17] add/remove ical imports in public agenda also: a little of refacftoring in CalendarController --- .../javascripts/controllers/calendar.js | 111 ++++++++++++------ config/locales/app.public.en.yml | 1 + config/locales/app.public.es.yml | 1 + config/locales/app.public.pt.yml | 1 + 4 files changed, 76 insertions(+), 38 deletions(-) diff --git a/app/assets/javascripts/controllers/calendar.js b/app/assets/javascripts/controllers/calendar.js index f307a9e35..4f60257c5 100644 --- a/app/assets/javascripts/controllers/calendar.js +++ b/app/assets/javascripts/controllers/calendar.js @@ -39,7 +39,7 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ $scope.spaces = spacesPromise.filter(t => !t.disabled); // List of external iCalendar sources - $scope.externals = iCalendarPromise; + $scope.externals = iCalendarPromise.map(e => Object.assign(e, { checked: true })); // add availabilities source to event sources $scope.eventSources = []; @@ -56,14 +56,25 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ dispo: filter.dispo }); $scope.calendarConfig.events = availabilitySourceUrl(); - $scope.externals.filter(e => e.checked).forEach(e => { - $scope.eventSources.push({ - url: `/api/i_calendar/${e.id}/events`, - textColor: e.text_color, - color: e.color - }); + // external iCalendar events sources + $scope.externals.forEach(e => { + if (e.checked) { + if (!$scope.eventSources.some(evt => evt.id === e.id)) { + $scope.eventSources.push({ + id: e.id, + url: `/api/i_calendar/${e.id}/events`, + textColor: e.text_color || '#000', + color: e.color + }); + } + } else { + if ($scope.eventSources.some(evt => evt.id === e.id)) { + const idx = $scope.eventSources.findIndex(evt => evt.id === e.id); + $scope.eventSources.splice(idx, 1); + } + } }); - return uiCalendarConfig.calendars.calendar.fullCalendar('refetchEvents'); + uiCalendarConfig.calendars.calendar.fullCalendar('refetchEventSources'); }; /** @@ -73,7 +84,7 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ $scope.calendarStyle = function (calendar) { return { 'border-color': calendar.color, - 'color': calendar.text_color, + 'color': calendar.text_color }; }; @@ -142,8 +153,49 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ /* PRIVATE SCOPE */ + /** + * Kind of constructor: these actions will be realized first when the controller is loaded + */ + const initialize = () => { + // fullCalendar (v2) configuration + $scope.calendarConfig = CalendarConfig({ + events: availabilitySourceUrl(), + slotEventOverlap: true, + header: { + left: 'month agendaWeek agendaDay', + center: 'title', + right: 'today prev,next' + }, + minTime: moment.duration(moment(bookingWindowStart.setting.value).format('HH:mm:ss')), + maxTime: moment.duration(moment(bookingWindowEnd.setting.value).format('HH:mm:ss')), + defaultView: window.innerWidth <= 480 ? 'agendaDay' : 'agendaWeek', + eventClick (event, jsEvent, view) { + return calendarEventClickCb(event, jsEvent, view); + }, + viewRender (view, element) { + return viewRenderCb(view, element); + }, + eventRender (event, element, view) { + return eventRenderCb(event, element); + } + }); + $scope.externals.forEach(e => { + if (e.checked) { + $scope.eventSources.push({ + id: e.id, + url: `/api/i_calendar/${e.id}/events`, + textColor: e.text_color || '#000', + color: e.color + }); + } + }); + }; + + /** + * Callback triggered when an event object is clicked in the fullCalendar view + */ const calendarEventClickCb = function (event, jsEvent, view) { - // current calendar object + // current calendar object const { calendar } = uiCalendarConfig.calendars; if (event.available_type === 'machines') { currentMachineEvent = event; @@ -168,8 +220,8 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ // agendaDay view: disable slotEventOverlap // agendaWeek view: enable slotEventOverlap const toggleSlotEventOverlap = function (view) { - // set defaultView, because when we change slotEventOverlap - // ui-calendar will trigger rerender calendar + // set defaultView, because when we change slotEventOverlap + // ui-calendar will trigger rerender calendar $scope.calendarConfig.defaultView = view.type; const today = currentMachineEvent ? currentMachineEvent.start : moment().utc().startOf('day'); if ((today > view.intervalStart) && (today < view.intervalEnd) && (today !== view.intervalStart)) { @@ -184,15 +236,22 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ } }; - // function is called when calendar view is rendered or changed + /** + * This function is called when calendar view is rendered or changed + * @see https://fullcalendar.io/docs/v3/viewRender#v2 + */ const viewRenderCb = function (view, element) { toggleSlotEventOverlap(view); if (view.type === 'agendaDay') { - // get availabilties by 1 day for show machine slots + // get availabilties by 1 day for show machine slots return uiCalendarConfig.calendars.calendar.fullCalendar('refetchEvents'); } }; + /** + * Callback triggered by fullCalendar when it is about to render an event. + * @see https://fullcalendar.io/docs/v3/eventRender#v2 + */ const eventRenderCb = function (event, element) { if (event.tags && event.tags.length > 0) { let html = ''; @@ -212,30 +271,6 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$ var availabilitySourceUrl = () => `/api/availabilities/public?${$.param(getFilter())}`; - const initialize = () => - // fullCalendar (v2) configuration - $scope.calendarConfig = CalendarConfig({ - events: availabilitySourceUrl(), - slotEventOverlap: true, - header: { - left: 'month agendaWeek agendaDay', - center: 'title', - right: 'today prev,next' - }, - minTime: moment.duration(moment(bookingWindowStart.setting.value).format('HH:mm:ss')), - maxTime: moment.duration(moment(bookingWindowEnd.setting.value).format('HH:mm:ss')), - defaultView: window.innerWidth <= 480 ? 'agendaDay' : 'agendaWeek', - eventClick (event, jsEvent, view) { - return calendarEventClickCb(event, jsEvent, view); - }, - viewRender (view, element) { - return viewRenderCb(view, element); - }, - eventRender (event, element, view) { - return eventRenderCb(event, element); - } - }); - // !!! MUST BE CALLED AT THE END of the controller return initialize(); } diff --git a/config/locales/app.public.en.yml b/config/locales/app.public.en.yml index ce9a26e9f..75575446a 100644 --- a/config/locales/app.public.en.yml +++ b/config/locales/app.public.en.yml @@ -306,6 +306,7 @@ en: machines: "Machines" spaces: "Spaces" events: "Events" + externals: "Other calendars" spaces_list: # list of spaces diff --git a/config/locales/app.public.es.yml b/config/locales/app.public.es.yml index 75aa29a37..b5bceebfc 100644 --- a/config/locales/app.public.es.yml +++ b/config/locales/app.public.es.yml @@ -306,6 +306,7 @@ es: machines: "Máquinas" spaces: "Espacios" events: "Eventos" + externals: "Otros calendarios" spaces_list: # list of spaces diff --git a/config/locales/app.public.pt.yml b/config/locales/app.public.pt.yml index ff0fb170f..bd8088c68 100755 --- a/config/locales/app.public.pt.yml +++ b/config/locales/app.public.pt.yml @@ -306,6 +306,7 @@ pt: machines: "Máquinas" spaces: "Espaços" events: "Eventos" + externals: "Outras agendas" spaces_list: # list of spaces From 64fe68b2b0ce0d9e8843565e4280fa1896a162ed Mon Sep 17 00:00:00 2001 From: Sylvain Date: Tue, 3 Dec 2019 12:16:07 +0100 Subject: [PATCH 17/17] icalendar source sync: create/update/delete ical events --- app/controllers/api/i_calendar_controller.rb | 3 +-- app/models/i_calendar_event.rb | 6 +++++ app/services/i_calendar_import_service.rb | 24 ++++++++++++-------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/app/controllers/api/i_calendar_controller.rb b/app/controllers/api/i_calendar_controller.rb index b828b709d..76cd5beb1 100644 --- a/app/controllers/api/i_calendar_controller.rb +++ b/app/controllers/api/i_calendar_controller.rb @@ -36,8 +36,7 @@ class API::ICalendarController < API::ApiController end def sync - worker = ICalendarImportWorker.new - worker.perform([params[:id]]) + ICalendarImportWorker.perform_async([params[:id]]) render json: { processing: [params[:id]] }, status: :created end diff --git a/app/models/i_calendar_event.rb b/app/models/i_calendar_event.rb index b7b4897d0..bfa940e9b 100644 --- a/app/models/i_calendar_event.rb +++ b/app/models/i_calendar_event.rb @@ -3,4 +3,10 @@ # iCalendar (RFC 5545) event, belonging to an ICalendar object (its source) class ICalendarEvent < ActiveRecord::Base belongs_to :i_calendar + + def self.update_or_create_by(args, attributes) + obj = find_or_create_by(args) + obj.update(attributes) + obj + end end diff --git a/app/services/i_calendar_import_service.rb b/app/services/i_calendar_import_service.rb index 06b122bfe..2664436bd 100644 --- a/app/services/i_calendar_import_service.rb +++ b/app/services/i_calendar_import_service.rb @@ -7,25 +7,29 @@ class ICalendarImportService require 'uri' require 'icalendar' - events = [] + uids = [] i_cal = ICalendar.find(i_calendar_id) ics = Net::HTTP.get(URI.parse(i_cal.url)) cals = Icalendar::Calendar.parse(ics) + # create new events and update existings cals.each do |cal| cal.events.each do |evt| - events.push( - uid: evt.uid, - dtstart: evt.dtstart, - dtend: evt.dtend, - summary: evt.summary, - description: evt.description, - i_calendar_id: i_calendar_id + uids.push(evt.uid.to_s) + ICalendarEvent.update_or_create_by( + { uid: evt.uid.to_s }, + { + dtstart: evt.dtstart, + dtend: evt.dtend, + summary: evt.summary, + description: evt.description, + i_calendar_id: i_calendar_id + } ) end end - - ICalendarEvent.create!(events) + # remove deleted events + ICalendarEvent.where(i_calendar_id: i_calendar_id).where.not(uid: uids).destroy_all end end