Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ gem "leaflet-rails"
gem "libretranslate"#, path: "../libretranslate"
gem "lucide-rails"
gem "metainspector", git: "https://github.com/jaimeiniesta/metainspector.git"
gem "net-ftp"
gem "octokit"
gem "omniauth-saml", "~> 2"
gem "orthotypo"
Expand Down
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,9 @@ GEM
namae (1.2.0)
racc (~> 1.7)
nesty (1.0.2)
net-ftp (0.3.9)
net-protocol
time
net-http (0.9.1)
uri (>= 0.11.1)
net-imap (0.6.6)
Expand Down Expand Up @@ -835,6 +838,7 @@ DEPENDENCIES
lucide-rails
metainspector!
mutex_m (~> 0.3.0)
net-ftp
octokit
omniauth-saml (~> 2)
orthotypo
Expand Down
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ web: bundle exec puma -C config/puma.rb
worker: bundle exec good_job start --queues="default"
miceworker: bundle exec good_job start --queues="mice" --max-threads=2
catsworker: bundle exec good_job start --queues="cats" --max-threads=2
donkeysworker: bundle exec good_job start --queues="donkeys" --max-threads=1
elephantsworker: bundle exec good_job start --queues="elephants" --max-threads=1
whalesworker: bundle exec good_job start --queues="whales" --max-threads=1
unicornsworker: bundle exec good_job start --queues="unicorns" --max-threads=1
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
class Admin::Communication::Library::Files::RedirectionsController < Admin::Communication::Library::Files::ApplicationController
before_action :load_and_authorize

def index
end

# Modifie la propriété `file_server_slug` de la localisation de fichier
# Conceptuellement, ça appartient plutôt à `FilesController`, mais :
# - il est bien encombré déjà
# - c'est utilisé par `FileServerApp.vue`
# - ça doit render l'index pour renvoyer des données à jour (avec le nouvel alias)
def change_server_slug
previous_path = @l10n.file_server_path
@l10n.file_server_slug = params[:file_server_slug]
if @l10n.save(context: :redirection)
# Si une redirection existait avec le nouveau chemin, on la supprime.
# Les permaliens priment toujours sur les redirections.
Communication::File::Redirection.remove(@l10n.university, @l10n.file_server_path)
# Le précédent permalien devient une redirection.
Communication::File::Redirection.add(@l10n, previous_path)
render :index
else
render_error(@l10n)
end
end

def create
@redirection = @l10n.redirections.create(
path_without_extension: params[:path_without_extension],
university: @l10n.university,
)
if @redirection.persisted?
render :index
else
render_error(@redirection)
end
end

def destroy
@redirection = @l10n.redirections.find(params[:id])
if @redirection.destroy
render :index
else
render_error(@redirection)
end
end

def static
@about = @file
render layout: false,
content_type: "text/plain; charset=utf-8"
end

protected

def load_and_authorize
@file = current_university.communication_files.find(params[:file_id])
authorize! :create, @file
@l10n = @file.localization_for(current_language)
end

def render_error(redirection)
render json: {
error: redirection.errors.full_messages.to_sentence,
status: 400
}, status: 400
end
end
6 changes: 5 additions & 1 deletion app/controllers/server/universities_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,13 @@ def university_params
:has_sso, :sso_target_url, :sso_cert, :sso_name_identifier_format, :sso_mapping, :sso_button_label,
:invoice_date, :contribution_amount,
:is_really_a_university,
:default_language_id, language_ids: []
:default_language_id, language_ids: [],
file_server_attributes: [:id, :url, :ftp_host, :ftp_port, :ftp_username, :ftp_password, :ftp_path]
]
attribute_names << :default_github_access_token unless params[:university][:default_github_access_token].blank?
if params[:university][:file_server_attributes]&.[](:ftp_password).blank?
params[:university][:file_server_attributes]&.delete(:ftp_password)
end
params.require(:university).permit(attribute_names)
end

Expand Down
198 changes: 198 additions & 0 deletions app/javascript/apps/file-server/FileServerApp.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
<script>
export default {
data() {
return {
data: {},
permalinkChanged: false,
redirection: '',
};
},
methods: {
async load() {
await this.request(this.endpoint);
},
blockInvalidPermalinkChars(event) {
// Empêche l'insertion avant qu'elle n'atteigne le DOM, pour éviter
// le clignotement d'un caractère invalide le temps que `parsePermalink`
// (déclenché après coup par @input) le retire.
if (event.data && /[^a-zA-Z0-9-]/.test(event.data)) {
event.preventDefault();
}
},
parsePermalink(event) {
this.data.file_server.slug = event.target.value
.toLowerCase()
.replace(/[^a-z0-9-]/g, '');
this.permalinkChanged = true;
},
async savePermalink() {
const json = await this.request(this.data.file_server.endpoint, {
method: 'PATCH',
body: { file_server_slug: this.data.file_server.slug },
}, this.$t('fileServer.permalink.changed'));
if (json) this.permalinkChanged = false;
},
parseRedirection(event) {
// TODO filtre
this.redirection = event.target.value;
},
async addRedirection() {
await this.request(this.data.redirections.endpoint, {
method: 'POST',
body: { path_without_extension: this.redirection },
}, this.$t('fileServer.redirections.added'));
},
async removeRedirection(redirection) {
await this.request(redirection.endpoint, {
method: 'DELETE',
}, this.$t('fileServer.redirections.removed'));
},
async request(url, options = {}, successMessage = null) {
try {
const response = await fetch(url, {
method: options.method || 'GET',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': this.csrfToken,
},
body: options.body ? JSON.stringify(options.body) : undefined,
});
const json = await response.json();
if (!response.ok) {
this.notify(json.error, 'error');
return null;
}
this.data = json;
if (successMessage) this.notify(successMessage, 'success');
return json;
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
this.notify(this.$t('fileServer.error'), 'error');
return null;
}
},
notify(message, type) {
const notyf = new Notyf();
notyf.open({
message: message,
type: type,
position: { x: 'left', y: 'bottom' },
duration: 9000,
ripple: true,
dismissible: true,
});
},
},
beforeMount() {
this.csrfToken = document.querySelector('[name="csrf-token"]').content;
this.dataset = document.getElementById('file-server-app').dataset;
this.endpoint = this.dataset.endpoint;
},
mounted() {
this.load();
},
};
</script>

<template>
<section class="mb-5">
<h2>{{ $t('fileServer.permalink.title') }}</h2>
<div class="card card--horizontal mt-2">
<div class="card-body">
<div class="d-lg-flex align-items-center">
<span class="me-1 text-muted">
{{ data.file_server?.base_url }}
</span>
<input
type="text"
class="form-control"
:value="data.file_server?.slug"
@beforeinput="blockInvalidPermalinkChars"
@input="parsePermalink"
/>
<span class="ms-2 me-5 text-muted">
{{ data.file_server?.extension }}
</span>
</div>
</div>
<div class="card-footer">
<button
type="button"
class="btn btn-light text-nowrap me-2"
:disabled="!this.permalinkChanged"
@click.prevent="savePermalink"
>
{{ $t('fileServer.permalink.save') }}
</button>
<a
class="btn btn-light text-nowrap"
:href="data.file_server?.url"
target="_blank"
>
{{ $t('fileServer.permalink.open') }}
</a>
</div>
</div>

<div class="mt-4">
{{ $t('fileServer.redirections.title') }}
<div class="row g-2">
<div v-for="redirection in data.redirections?.list">
<div class="card card--horizontal">
<div class="card-body">
<p class="my-2">
{{ redirection.url }}
</p>
</div>
<div class="card-footer">
<button
type="button"
class="btn btn-danger text-nowrap me-2"
@click.prevent="removeRedirection(redirection)"
>
{{ $t('fileServer.redirections.remove') }}
</button>
<a
class="btn btn-light text-nowrap"
:href="redirection.url"
target="_blank"
>
{{ $t('fileServer.redirections.open') }}
</a>
</div>
</div>
</div>
<div>
<div class="card card--horizontal">
<div class="card-body">
<div class="button-group d-lg-flex align-items-center">
<span class="me-1 text-muted">
{{ data.redirections?.base_url }}
</span>
<input
type="text"
class="form-control"
@input="parseRedirection"
/>
<span class="ms-2 me-5 text-muted">
{{ data.file_server?.extension }}
</span>
<div class="card-footer p-0">
<button
type="button"
class="btn btn-light text-nowrap"
:disabled="this.redirection == ''"
@click.prevent="addRedirection"
>
{{ $t('fileServer.redirections.add') }}
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</template>
2 changes: 2 additions & 0 deletions app/javascript/apps/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createApp } from 'vue';
import { getI18n } from './i18n';
import BlocksEditorApp from './blocks-editor/BlocksEditorApp.vue';
import DownloadableSummaryApp from './downloadable-summary/DownloadableSummaryApp.vue';
import FileServerApp from './file-server/FileServerApp.vue';
import FeaturedMediaApp from './featured-media/FeaturedMediaApp.vue';
import PickerTestApp from './picker/PickerTestApp.vue';
import SsoMappingApp from './sso-mapping/SsoMappingApp.vue';
Expand All @@ -19,6 +20,7 @@ async function boot() {
mount(BlocksEditorApp, '#blocks-editor-app');
mount(DownloadableSummaryApp, '#downloadable-summary-app');
mount(FeaturedMediaApp, '#featured-media-app');
mount(FileServerApp, '#file-server-app');
mount(PickerTestApp, '#picker-test-app');
mount(SsoMappingApp, '#sso-mapping-app');
mount(TimeSlotsApp, '#time-slots-app');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Communication::File::SynchronizeWithFileServerJob < ApplicationJob
queue_as :donkeys

def perform(file_localization)
file_localization.sync_to_file_server_safely
end
end
Loading
Loading