- HomeController.ts: addes api method for showing number of publications per month for given year
All checks were successful
CI Pipeline / japa-tests (push) Successful in 50s

- adapted command ValidateChecksum.ts: on published files are checked. better information logging
- better LineChart.vue component: showing real statistics
- start/routes/apu.ts: added Route.get('/statistic/:year', 'HomeController.findPublicationsPerMonth');
This commit is contained in:
Kaimbacher 2024-02-02 14:00:54 +01:00
parent 8cef7390d7
commit 68928b5e07
10 changed files with 260 additions and 74 deletions

View File

@ -53,4 +53,91 @@ export default class HomeController {
});
}
}
public async findPublicationsPerMonth({ response }: HttpContextContract) {
const serverState = 'published';
// const year = params.year;
// const from = parseInt(year);
try {
// const datasets = await Database.from('documents as doc')
// .select([Database.raw(`date_part('month', server_date_published) as pub_month`), Database.raw('COUNT(*) as count')])
// .where('server_state', serverState)
// .innerJoin('link_documents_persons as ba', 'doc.id', 'ba.document_id')
// .andWhereRaw(`date_part('year', server_date_published) = ?`, [from])
// .groupBy('pub_month');
// // .orderBy('server_date_published');
const years = [2021, 2022, 2023]; // Add the second year
const result = await Database.from('documents as doc')
.select([
Database.raw(`date_part('year', server_date_published) as pub_year`),
Database.raw(`date_part('month', server_date_published) as pub_month`),
Database.raw('COUNT(*) as count'),
])
.where('server_state', serverState)
// .innerJoin('link_documents_persons as ba', 'doc.id', 'ba.document_id')
// .whereIn('pub_year', years) // Filter by both years
.whereRaw(`date_part('year', server_date_published) IN (${years.join(',')})`) // Filter by both years
.groupBy('pub_year', 'pub_month')
.orderBy('pub_year', 'asc')
.orderBy('pub_month', 'asc');
const labels = Array.from({ length: 12 }, (_, i) => i + 1); // Assuming 12 months
const inputDatasets: Map<string, ChartDataset> = result.reduce((acc, item) => {
const { pub_year, pub_month, count } = item;
if (!acc[pub_year]) {
acc[pub_year] = {
data: Array.from({ length: 12 }).fill(0),
label: pub_year.toString(),
borderColor: this.getRandomHexColor, // pub_year === 2022 ? '#3e95cd' : '#8e5ea2',
fill: false,
};
}
acc[pub_year].data[pub_month - 1] = parseInt(count);
return acc ;
}, {});
const outputDatasets = Object.entries(inputDatasets).map(([year, data]) => ({
data: data.data,
label: year,
borderColor: data.borderColor,
fill: data.fill
}));
const data = {
labels: labels,
datasets: outputDatasets,
};
return response.json(data);
} catch (error) {
return response.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
message: error.message || 'Some error occurred while retrieving datasets.',
});
}
}
private getRandomHexColor() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
}
interface ChartDataset {
data: Array<number>;
label: string;
borderColor: string;
fill: boolean;
}

View File

@ -36,8 +36,12 @@ export default class ValidateChecksum extends BaseCommand {
const { default: File } = await import('App/Models/File');
// const { default: HashValue } = await (await (import ('App/Models/HashValue')));
// query all files from database:
const files = await File.query().preload('hashvalues');
// query all published files from database:
const files = await File.query()
.whereHas('dataset', (dQuery) => {
dQuery.where('server_state', 'published');
})
.preload('hashvalues');
// const logLevel = Config.get('app.logger.level', 'info');
// console.log(this.logger.)
@ -56,7 +60,7 @@ export default class ValidateChecksum extends BaseCommand {
}
if (hashValue['md5'] === calculatedMd5FileHash) {
Logger.info(`File id ${file.id}: stored md5 checksum: ${calculatedMd5FileHash}, control md5 checksum: ${hashValue['md5']}`);
Logger.info(`File id ${file.id} OK: stored md5 checksum: ${calculatedMd5FileHash}, same control md5 checksum: ${hashValue['md5']}`);
} else {
Logger.error(
`File id ${file.id}: stored md5 checksum: ${calculatedMd5FileHash}, control md5 checksum: ${hashValue['md5']}`,

84
package-lock.json generated
View File

@ -916,9 +916,9 @@
}
},
"node_modules/@babel/helper-create-class-features-plugin": {
"version": "7.23.9",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.9.tgz",
"integrity": "sha512-B2L9neXTIyPQoXDm+NtovPvG6VOLWnaXu3BIeVDWwdKFgG30oNa6CqVGiJPDWQwIAK49t9gnQI9c6K6RzabiKw==",
"version": "7.23.10",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.10.tgz",
"integrity": "sha512-2XpP2XhkXzgxecPNEEK8Vz8Asj9aRxt08oKOqtiZoqV2UGZ5T+EkyP9sXQ9nwMxBIG34a7jmasVqoMop7VdPUw==",
"dev": true,
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
@ -4101,9 +4101,9 @@
}
},
"node_modules/@types/geojson": {
"version": "7946.0.13",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz",
"integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==",
"version": "7946.0.14",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz",
"integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==",
"dev": true
},
"node_modules/@types/glob": {
@ -4228,9 +4228,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "20.11.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.10.tgz",
"integrity": "sha512-rZEfe/hJSGYmdfX9tvcPMYeYPW2sNl50nsw4jZmRcaG0HIAb0WYEpsB05GOb53vjqpyE9GUhlDQ4jLSoB5q9kg==",
"version": "20.11.16",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz",
"integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==",
"dependencies": {
"undici-types": "~5.26.4"
}
@ -6368,9 +6368,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001581",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001581.tgz",
"integrity": "sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ==",
"version": "1.0.30001583",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001583.tgz",
"integrity": "sha512-acWTYaha8xfhA/Du/z4sNZjHUWjkiuoAi2LM+T/aL+kemKQgPT1xBb/YKjlQ0Qo8gvbHsGNplrEJ+9G3gL7i4Q==",
"dev": true,
"funding": [
{
@ -7175,9 +7175,9 @@
}
},
"node_modules/css-loader": {
"version": "6.9.1",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.9.1.tgz",
"integrity": "sha512-OzABOh0+26JKFdMzlK6PY1u5Zx8+Ck7CVRlcGNZoY9qwJjdfu2VWFuprTIpPW+Av5TZTVViYWcFQaEEQURLknQ==",
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.10.0.tgz",
"integrity": "sha512-LTSA/jWbwdMlk+rhmElbDR2vbtQoTBPr7fkJE+mxrHj+7ru0hUmHafDRzWIjIHTwpitWVaqY2/UWGRca3yUgRw==",
"dev": true,
"dependencies": {
"icss-utils": "^5.1.0",
@ -7197,7 +7197,16 @@
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"@rspack/core": "0.x || 1.x",
"webpack": "^5.0.0"
},
"peerDependenciesMeta": {
"@rspack/core": {
"optional": true
},
"webpack": {
"optional": true
}
}
},
"node_modules/css-minimizer-webpack-plugin": {
@ -8112,9 +8121,9 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"node_modules/electron-to-chromium": {
"version": "1.4.650",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.650.tgz",
"integrity": "sha512-sYSQhJCJa4aGA1wYol5cMQgekDBlbVfTRavlGZVr3WZpDdOPcp6a6xUnFfrt8TqZhsBYYbDxJZCjGfHuGupCRQ==",
"version": "1.4.655",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.655.tgz",
"integrity": "sha512-2yszojF7vIZ68adIOvzV4bku8OZad9w5H9xF3ZAMZjPuOjBarlflUkjN6DggdV+L71WZuKUfKUhov/34+G5QHg==",
"dev": true
},
"node_modules/emittery": {
@ -8201,9 +8210,9 @@
}
},
"node_modules/envinfo": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz",
"integrity": "sha512-G9/6xF1FPbIw0TtalAMaVPpiq2aDEuKLXM314jPVAO9r2fo2a4BLqMNkmRS7O/xPPZ+COAhGIz3ETvHEV3eUcg==",
"version": "7.11.1",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.1.tgz",
"integrity": "sha512-8PiZgZNIB4q/Lw4AhOvAfB/ityHAd2bli3lESSWmWSzSsl5dKpy5N1d1Rfkd2teq/g9xN90lc6o98DOjMeYHpg==",
"dev": true,
"peer": true,
"bin": {
@ -10195,9 +10204,9 @@
}
},
"node_modules/ignore": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
"integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
"dev": true,
"engines": {
"node": ">= 4"
@ -11573,9 +11582,9 @@
"integrity": "sha512-QS9p+Q20YBxpE0dJBnF6CPURP7p1GUsxnhTxTWH5nG3A1F5w8Rg3T4Xyh5UlrFSbHp88oOciVP/0agsNLhkHdQ=="
},
"node_modules/magic-string": {
"version": "0.30.5",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
"integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
"version": "0.30.6",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.6.tgz",
"integrity": "sha512-n62qCLbPjNjyo+owKtveQxZFZTBm+Ms6YoGD23Wew6Vw337PElFNifQpknPruVRQV57kVShPnLGo9vWxVhpPvA==",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.15"
},
@ -11850,12 +11859,13 @@
}
},
"node_modules/mini-css-extract-plugin": {
"version": "2.7.7",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.7.tgz",
"integrity": "sha512-+0n11YGyRavUR3IlaOzJ0/4Il1avMvJ1VJfhWfCn24ITQXhRr1gghbhhrda6tgtNcpZaWKdSuwKq20Jb7fnlyw==",
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.8.0.tgz",
"integrity": "sha512-CxmUYPFcTgET1zImteG/LZOy/4T5rTojesQXkSNBiquhydn78tfbCE9sjIjnJ/UcjNjOC1bphTCCW5rrS7cXAg==",
"dev": true,
"dependencies": {
"schema-utils": "^4.0.0"
"schema-utils": "^4.0.0",
"tapable": "^2.2.1"
},
"engines": {
"node": ">= 12.13.0"
@ -13070,9 +13080,9 @@
}
},
"node_modules/pinia/node_modules/vue-demi": {
"version": "0.14.6",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
"version": "0.14.7",
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz",
"integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==",
"dev": true,
"hasInstallScript": true,
"bin": {
@ -17191,9 +17201,9 @@
}
},
"node_modules/webpack": {
"version": "5.90.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.0.tgz",
"integrity": "sha512-bdmyXRCXeeNIePv6R6tGPyy20aUobw4Zy8r0LUS2EWO+U+Ke/gYDgsCh7bl5rB6jPpr4r0SZa6dPxBxLooDT3w==",
"version": "5.90.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.1.tgz",
"integrity": "sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==",
"dev": true,
"peer": true,
"dependencies": {

View File

@ -1,7 +1,29 @@
<script setup>
<script lang="ts" setup>
import { ref, watch, computed, onMounted } from 'vue';
import { Chart, LineElement, PointElement, LineController, LinearScale, CategoryScale, Tooltip } from 'chart.js';
import { Chart, LineElement, PointElement, LineController, LinearScale, CategoryScale, Tooltip, registerables } from 'chart.js';
import { Ref } from 'vue';
import { ChartDataCustomTypesPerDataset } from 'chart.js';
// interface Dataset {
// borderColor: string;
// // borderDash:
// // (0) []
// borderDashOffset: number;
// borderWidth: number;
// cubicInterpolationMode: string;
// data: Array<number>;
// fill: boolean;
// pointBackgroundColor: string;
// pointBorderColor: string;
// pointBorderWidth: number;
// pointHoverBackgroundColor: string;
// pointHoverBorderWidth: number;
// pointHoverRadius: number;
// pointRadius: number;
// tension: number;
// }
const props = defineProps({
data: {
type: Object,
@ -9,16 +31,38 @@ const props = defineProps({
},
});
const root = ref(null);
const root: Ref<HTMLCanvasElement | null> = ref<HTMLCanvasElement | null>(null);
let chart;
Chart.register(LineElement, PointElement, LineController, LinearScale, CategoryScale, Tooltip);
onMounted(() => {
Chart.register(...registerables);
// https://gitea.geologie.ac.at/kaiarn/geomon.viewer/src/branch/master/src/common/graphjs/geomon-timeseries-chart/geomon-timeseries-chart.component.ts
chart = new Chart(root.value, {
type: 'line',
data: props.data,
data: props.data as ChartDataCustomTypesPerDataset<"line", number[], string>,
// data: {
// labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
// // labels: [],
// datasets: [
// {
// data: [86, 114, 106, 106, 107, 111, 133, 221, 783, 2478, 2600, 2700],
// label: "Africa",
// borderColor: "#3e95cd",
// fill: false
// }, {
// data: [282, 350, 411, 502, 635, 809, 947, 1402, 3700, 5267, 444, 555],
// label: "Asia",
// borderColor: "#8e5ea2",
// fill: false
// }
// ]
// },
options: {
responsive: true,
maintainAspectRatio: false,
@ -32,11 +76,12 @@ onMounted(() => {
},
plugins: {
legend: {
display: false,
display: true,
},
},
},
});
});
const chartData = computed(() => props.data);

View File

@ -36,7 +36,7 @@ const datasetObject = (color, points) => {
};
};
export const sampleChartData = (points = 9) => {
export const sampleChartData = (points = 12) => {
const labels = [];
for (let i = 1; i <= points; i++) {

View File

@ -34,7 +34,7 @@ export interface Dataset {
| Array<DatasetReference>
| Array<File>
| (Array<number> | Array<Object>);
language: Ref<string>;
language: string;
licenses: Array<number> | Array<Object>;
rights: boolean;
type: string;

View File

@ -14,7 +14,7 @@ import {
mdiChartPie,
} from '@mdi/js';
// import { containerMaxW } from '@/config.js'; // "xl:max-w-6xl xl:mx-auto"
import * as chartConfig from '@/Components/Charts/chart.config.js';
// import * as chartConfig from '@/Components/Charts/chart.config.js';
import LineChart from '@/Components/Charts/LineChart.vue';
import SectionMain from '@/Components/SectionMain.vue';
import CardBoxWidget from '@/Components/CardBoxWidget.vue';
@ -27,14 +27,19 @@ import CardBoxClient from '@/Components/CardBoxClient.vue';
import LayoutAuthenticated from '@/Layouts/LayoutAuthenticated.vue';
import SectionTitleLineWithButton from '@/Components/SectionTitleLineWithButton.vue';
import SectionBannerStarOnGitHub from '@/Components/SectionBannerStarOnGitea.vue';
const chartData = ref();
const fillChartData = () => {
chartData.value = chartConfig.sampleChartData();
const mainService = MainService()
// const chartData = ref();
const fillChartData = async () => {
await mainService.fetchChartData(2022);
// chartData.value = chartConfig.sampleChartData();
// chartData.value = mainService.graphData;
};
onMounted(() => {
fillChartData();
const chartData = computed(() => mainService.graphData);
onMounted(async () => {
await mainService.fetchChartData(2022);
});
const mainService = MainService();
;
/* Fetch sample data */
mainService.fetch('clients');
mainService.fetch('history');
@ -131,7 +136,7 @@ const datasets = computed(() => mainService.datasets);
<SectionBannerStarOnGitHub />
<SectionTitleLineWithButton :icon="mdiChartPie" title="Trends overview (to do publications per year)" />
<SectionTitleLineWithButton :icon="mdiChartPie" title="Trends overview: Publications per month" />
<CardBox title="Performance" :icon="mdiFinance" :header-icon="mdiReload" class="mb-6" @header-icon-click="fillChartData">
<div v-if="chartData">
<line-chart :data="chartData" class="h-96" />

View File

@ -110,13 +110,13 @@ let dataset: Dataset;
if (Object.keys(mainService.dataset).length == 0) {
// language = ref('');
dataset = {
language: language,
language: language.value,
licenses: [],
rights: false,
type: '',
creating_corporation: 'Tethys RDR',
titles: [{ value: '', type: 'Main', language: language }],
descriptions: [{ value: '', type: 'Abstract', language: language }],
titles: [{ value: '', type: 'Main', language: language.value }],
descriptions: [{ value: '', type: 'Abstract', language: language.value }],
authors: [],
contributors: [],
project_id: undefined,
@ -150,11 +150,11 @@ if (Object.keys(mainService.dataset).length == 0) {
// mainService.setDataset(dataset, language);
} else {
// console.log(mainService.dataset);
language = ref(mainService.dataset.language);
language.value = mainService.dataset.language;
// dataset = mainService.dataset;
dataset = {
language: language,
language: mainService.dataset.language,
licenses: mainService.dataset.licenses,
rights: mainService.dataset.rights,
type: mainService.dataset.type,
@ -171,20 +171,23 @@ if (Object.keys(mainService.dataset).length == 0) {
files: mainService.dataset.files,
// upload: mainService.dataset.upload,
};
for (let index in mainService.dataset.titles) {
let title: Title = mainService.dataset.titles[index];
if (title.type == 'Main') {
title.language = language;
}
}
for (let index in mainService.dataset.descriptions) {
let description: Description = mainService.dataset.descriptions[index];
if (description.type == 'Abstract') {
description.language = language;
}
}
// for (let index in mainService.dataset.titles) {
// let title: Title = mainService.dataset.titles[index];
// if (title.type == 'Main') {
// title.language = language;
// }
// }
// for (let index in mainService.dataset.descriptions) {
// let description: Description = mainService.dataset.descriptions[index];
// if (description.type == 'Abstract') {
// description.language = language;
// }
// }
}
// const form = useForm<Dataset>({
// language: language,
// licenses: [],
@ -205,6 +208,21 @@ let form = useForm<Dataset>(dataset as Dataset);
// },
// });
watch(language, (currentValue) => {
if (currentValue != "") {
form.language = currentValue;
const mainTitle = form.titles?.find((title) => title.type === 'Main');
if (mainTitle) {
mainTitle.language = currentValue;
}
const mainAbstract = form.descriptions?.find((desc) => desc.type === 'Abstract');
if (mainAbstract) {
mainAbstract.language = currentValue;
}
mainService.setDataset(form.data());
}
});
let elevation = ref('no_elevation');
watch(elevation, (currentValue) => {
if (currentValue == 'absolut') {
@ -498,7 +516,7 @@ Removes a selected keyword
<div class="flex flex-col md:flex-row">
<FormField label="Language *" help="required: select dataset main language"
:class="{ 'text-red-400': errors.language }" class="w-full mx-2 flex-1">
<FormControl required v-model="form.language" :type="'select'"
<FormControl required v-model="language" :type="'select'"
placeholder="[Enter Language]" :errors="form.errors.language"
:options="{ de: 'de', en: 'en' }">
<div class="text-red-400 text-sm" v-if="form.errors.language">

View File

@ -67,6 +67,8 @@ export const MainService = defineStore('main', {
menu: menu,
totpState: 0,
graphData: {},
}),
actions: {
// payload = authenticated user
@ -165,6 +167,20 @@ export const MainService = defineStore('main', {
this.totpState = state;
},
async fetchChartData(year) {
// sampleDataKey= authors or datasets
axios
.get(`/api/statistic/${year}`)
.then((r) => {
if (r.data) {
this.graphData = r.data;
}
})
.catch((error) => {
alert(error.message);
});
},
// fetchfiles(id) {
// // sampleDataKey= authors or datasets
// axios

View File

@ -16,6 +16,7 @@ Route.group(() => {
Route.get('/dataset/:publish_id', 'DatasetController.findOne').as('dataset.findOne');
Route.get('/sitelinks/:year', 'HomeController.findDocumentsPerYear');
Route.get('/years', 'HomeController.findYears');
Route.get('/statistic/:year', 'HomeController.findPublicationsPerMonth');
Route.get('/download/:id', 'FileController.findOne').as('file.findOne');