2020-04-19 14:22:10 +02:00
|
|
|
import { Injectable } from '@angular/core';
|
2021-11-22 11:51:21 +01:00
|
|
|
import { AxiosInstance, AxiosResponse } from 'axios';
|
2020-04-19 14:22:10 +02:00
|
|
|
import { environment } from 'src/environments/environment';
|
|
|
|
|
2020-06-12 19:17:39 +02:00
|
|
|
import { Answer } from '../enums/answer.enum';
|
2020-04-19 14:22:10 +02:00
|
|
|
import { Poll } from '../models/poll.model';
|
2021-04-28 15:35:08 +02:00
|
|
|
import { HttpClient } from '@angular/common/http';
|
2021-11-22 11:51:21 +01:00
|
|
|
import { Subscription } from 'rxjs';
|
2020-11-13 09:38:42 +01:00
|
|
|
import { ToastService } from './toast.service';
|
|
|
|
import { LoaderService } from './loader.service';
|
2021-04-28 14:33:41 +02:00
|
|
|
import { Stack } from '../models/stack.model';
|
2022-02-14 14:37:42 +01:00
|
|
|
import { Comment, CommentDTO } from '../models/comment.model';
|
2020-04-19 14:22:10 +02:00
|
|
|
|
2020-11-09 12:44:24 +01:00
|
|
|
const apiVersion = environment.api.versionToUse;
|
|
|
|
const currentApiRoutes = environment.api.version[apiVersion];
|
|
|
|
const apiBaseHref = environment.api.version[apiVersion].baseHref;
|
|
|
|
|
|
|
|
const apiEndpoints = environment.api.endpoints;
|
2021-11-22 11:51:21 +01:00
|
|
|
let axios = require('axios');
|
2020-11-09 12:44:24 +01:00
|
|
|
|
2021-11-07 21:08:38 +01:00
|
|
|
class PollDTO {}
|
|
|
|
|
2020-04-19 14:22:10 +02:00
|
|
|
@Injectable({
|
2020-04-21 17:26:25 +02:00
|
|
|
providedIn: 'root',
|
2020-04-19 14:22:10 +02:00
|
|
|
})
|
|
|
|
export class ApiService {
|
2020-05-01 19:10:17 +02:00
|
|
|
private axiosInstance: AxiosInstance;
|
2020-11-09 12:44:24 +01:00
|
|
|
private readonly pollsEndpoint = apiEndpoints.polls.name;
|
|
|
|
private readonly answersEndpoint = apiEndpoints.polls.answers.name;
|
|
|
|
private readonly commentsEndpoint = apiEndpoints.polls.comments.name;
|
|
|
|
private readonly slugsEndpoint = apiEndpoints.polls.slugs.name;
|
|
|
|
private readonly usersEndpoint = apiEndpoints.users.name;
|
|
|
|
private readonly usersPollsEndpoint = apiEndpoints.users.polls.name;
|
|
|
|
private readonly usersPollsSendEmailEndpoint = apiEndpoints.users.polls.sendEmail.name;
|
2021-04-28 14:33:41 +02:00
|
|
|
private baseHref: string;
|
2021-05-20 14:45:19 +02:00
|
|
|
private static loaderService: LoaderService;
|
2020-05-01 19:10:17 +02:00
|
|
|
|
2021-05-21 12:31:42 +02:00
|
|
|
constructor(private http: HttpClient, private toastService: ToastService, private loaderService: LoaderService) {
|
2021-04-28 14:33:41 +02:00
|
|
|
this.baseHref = apiBaseHref;
|
|
|
|
|
2020-11-09 12:44:24 +01:00
|
|
|
this.axiosInstance = axios.create({ baseURL: apiBaseHref });
|
2020-05-12 19:16:23 +02:00
|
|
|
this.axiosInstance.defaults.timeout = 2500;
|
2021-11-16 16:16:30 +01:00
|
|
|
// this.axiosInstance.defaults.headers.post['Content-Type'] = 'application/json';
|
|
|
|
// this.axiosInstance.defaults.headers.post['Accept'] = 'application/json';
|
|
|
|
// this.axiosInstance.defaults.headers.post['Charset'] = 'UTF-8';
|
2021-05-20 14:53:50 +02:00
|
|
|
// this.axiosInstance.defaults.headers.post['Accept-Charset'] = 'UTF-8';
|
2021-11-16 16:16:30 +01:00
|
|
|
// this.axiosInstance.defaults.headers.post['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS';
|
|
|
|
// this.axiosInstance.defaults.headers.post['Referrer-Policy'] = 'origin-when-cross-origin';
|
|
|
|
// this.axiosInstance.defaults.headers.post['Access-Control-Allow-Origin'] = '*';
|
|
|
|
// this.axiosInstance.defaults.headers.post['Control-Allow-Origin'] = '*';
|
|
|
|
// this.axiosInstance.defaults.headers.post['Allow-Origin'] = '*';
|
|
|
|
// this.axiosInstance.defaults.headers.post['Access-Control-Allow-Headers'] =
|
|
|
|
// 'Origin, X-Requested-With, Content-Type, Accept';
|
2021-04-28 15:04:27 +02:00
|
|
|
|
|
|
|
console.log('this.axiosInstance.defaults.headers', this.axiosInstance.defaults.headers);
|
2020-05-01 19:10:17 +02:00
|
|
|
}
|
|
|
|
|
2020-05-12 19:16:23 +02:00
|
|
|
//////////////////////
|
|
|
|
// CREATE OR UPDATE //
|
|
|
|
//////////////////////
|
2020-11-13 09:43:34 +01:00
|
|
|
|
2021-04-25 12:05:17 +02:00
|
|
|
/////////////////////
|
|
|
|
/**
|
|
|
|
* prepare headers like the charset and json type for any call to the backend
|
|
|
|
* @param bodyContent?
|
|
|
|
*/
|
|
|
|
static makeHeaders(bodyContent?: any) {
|
|
|
|
const headerDict = {
|
|
|
|
Charset: 'UTF-8',
|
2021-04-28 15:35:08 +02:00
|
|
|
// 'Content-Type': 'application/json',
|
|
|
|
// Accept: 'application/json',
|
2021-11-22 11:51:21 +01:00
|
|
|
'Access-Control-Allow-Origin': '^https?://(localhost|127.0.0.1)(:[0-9]+)?$',
|
2021-04-25 12:05:17 +02:00
|
|
|
'Content-Type': 'application/json',
|
2021-11-16 16:16:30 +01:00
|
|
|
// mode: 'no-cors',
|
2021-04-25 12:05:17 +02:00
|
|
|
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
2022-02-03 12:26:04 +01:00
|
|
|
'Access-Control-Allow-Headers': 'Accept,Accept-LanguageEnum,Content-LanguageEnum,Content-Type',
|
2021-04-25 12:05:17 +02:00
|
|
|
};
|
2021-11-16 16:16:30 +01:00
|
|
|
const headersAxios = {
|
2021-04-28 15:35:08 +02:00
|
|
|
headers: headerDict,
|
2021-04-25 12:05:17 +02:00
|
|
|
body: bodyContent,
|
|
|
|
};
|
2021-11-16 16:16:30 +01:00
|
|
|
console.log('headersAxios', headersAxios);
|
|
|
|
return headersAxios;
|
2021-04-25 12:05:17 +02:00
|
|
|
}
|
|
|
|
|
2021-11-14 17:02:20 +01:00
|
|
|
/**
|
2021-11-22 11:51:21 +01:00
|
|
|
* create a new poll
|
2021-11-14 17:02:20 +01:00
|
|
|
* @param poll
|
|
|
|
*/
|
2021-11-07 21:08:38 +01:00
|
|
|
public async createPoll(poll: PollDTO): Promise<Subscription> {
|
2021-05-20 14:45:19 +02:00
|
|
|
// this.loaderService.setStatus(true);
|
2021-11-22 11:51:21 +01:00
|
|
|
|
|
|
|
let axiosConf = {
|
|
|
|
method: 'post',
|
|
|
|
url: `${this.baseHref}${currentApiRoutes['api_new_poll']}`,
|
|
|
|
headers: {
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
},
|
|
|
|
data: JSON.stringify(poll),
|
|
|
|
};
|
2021-11-14 17:02:20 +01:00
|
|
|
console.log('apiservice createPoll config', poll);
|
2021-11-22 11:51:21 +01:00
|
|
|
|
|
|
|
return this.axiosInstance.post(`${this.baseHref}${currentApiRoutes['api_new_poll']}`, poll);
|
2020-04-21 17:26:25 +02:00
|
|
|
}
|
|
|
|
|
2021-04-28 14:33:41 +02:00
|
|
|
/**
|
|
|
|
* send a new vote stack
|
|
|
|
* @param vote_stack
|
|
|
|
*/
|
2021-06-07 11:30:10 +02:00
|
|
|
public sendNewVoteStackOfPoll(vote_stack: Stack): Promise<void> {
|
2021-06-10 11:43:17 +02:00
|
|
|
// const headers = ApiService.makeHeaders(vote_stack);
|
2021-06-07 11:30:10 +02:00
|
|
|
const url = `${this.baseHref}/vote-stack/`;
|
2021-04-28 14:33:41 +02:00
|
|
|
|
2021-06-10 11:43:17 +02:00
|
|
|
// const axiosconf = {
|
|
|
|
// url,
|
|
|
|
// method: 'POST',
|
|
|
|
// body: vote_stack,
|
|
|
|
// headers,
|
|
|
|
// };
|
2021-04-28 15:35:08 +02:00
|
|
|
|
2021-05-20 14:23:57 +02:00
|
|
|
return this.axiosInstance.post(url, vote_stack);
|
2021-04-28 14:33:41 +02:00
|
|
|
}
|
|
|
|
|
2021-04-25 12:05:17 +02:00
|
|
|
//////////
|
|
|
|
// READ //
|
|
|
|
|
2020-05-12 19:16:23 +02:00
|
|
|
public async createParticipation(
|
|
|
|
pollId: string,
|
|
|
|
choiceLabel: string,
|
|
|
|
pseudo: string,
|
2020-06-12 19:17:39 +02:00
|
|
|
response: Answer
|
2020-05-12 19:16:23 +02:00
|
|
|
): Promise<string> {
|
2020-04-21 17:26:25 +02:00
|
|
|
try {
|
2020-05-12 19:16:23 +02:00
|
|
|
return await this.axiosInstance.post(`${this.pollsEndpoint}/${pollId}${this.answersEndpoint}`, {
|
|
|
|
choiceLabel,
|
|
|
|
pseudo,
|
|
|
|
response,
|
2020-05-01 19:10:17 +02:00
|
|
|
});
|
2020-04-21 17:26:25 +02:00
|
|
|
} catch (error) {
|
2020-11-09 11:32:12 +01:00
|
|
|
ApiService.handleError(error);
|
2020-04-21 17:26:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-15 12:11:41 +01:00
|
|
|
public createComment(slug: string, comment: Comment | CommentDTO): Promise<string> {
|
2020-04-21 17:26:25 +02:00
|
|
|
try {
|
2022-02-15 12:37:37 +01:00
|
|
|
return this.axiosInstance.post(`${this.baseHref}/comment/poll/${slug}`, comment);
|
2020-04-21 17:26:25 +02:00
|
|
|
} catch (error) {
|
2020-11-09 11:32:12 +01:00
|
|
|
ApiService.handleError(error);
|
2020-04-21 17:26:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-12 15:59:44 +01:00
|
|
|
/**
|
|
|
|
* get all polls published by the API
|
|
|
|
*/
|
2020-06-25 22:42:26 +02:00
|
|
|
public async getAllAvailablePolls(): Promise<Poll[]> {
|
2020-05-12 19:16:23 +02:00
|
|
|
// TODO: used for facilities in DEV, should be removed in production
|
|
|
|
try {
|
2021-11-16 16:16:30 +01:00
|
|
|
this.axiosInstance.options(this.pollsEndpoint);
|
|
|
|
|
2020-05-12 19:16:23 +02:00
|
|
|
const response: AxiosResponse<Poll[]> = await this.axiosInstance.get<Poll[]>(`${this.pollsEndpoint}`);
|
2020-06-25 22:42:26 +02:00
|
|
|
return response?.data;
|
2020-05-12 19:16:23 +02:00
|
|
|
} catch (error) {
|
2020-11-09 11:32:12 +01:00
|
|
|
ApiService.handleError(error);
|
2020-05-12 19:16:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-12 15:59:44 +01:00
|
|
|
/**
|
|
|
|
* get one poll by its admin key
|
|
|
|
* @param admin_key
|
|
|
|
*/
|
|
|
|
public async getPollByAdminKey(admin_key: string): Promise<any | undefined> {
|
2020-05-12 19:16:23 +02:00
|
|
|
try {
|
2021-11-12 15:59:44 +01:00
|
|
|
console.log('fetch API : asking for poll with admin_key=' + admin_key);
|
|
|
|
const response: AxiosResponse<Poll> = await this.axiosInstance.get<any>(
|
|
|
|
`${this.pollsEndpoint}/admin/${admin_key}`
|
|
|
|
);
|
|
|
|
return response && response.data && !Array.isArray(response.data) ? response.data : undefined;
|
|
|
|
} catch (error) {
|
|
|
|
if (error.response?.status === 404) {
|
|
|
|
return undefined;
|
|
|
|
} else {
|
|
|
|
ApiService.handleError(error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async getPollByCustomUrl(custom_url: string): Promise<Poll | undefined> {
|
|
|
|
try {
|
|
|
|
console.log('fetch API : asking for poll with custom_url=' + custom_url);
|
|
|
|
const response: AxiosResponse<Poll> = await this.axiosInstance.get<Poll>(
|
|
|
|
`${this.pollsEndpoint}/${custom_url}`
|
|
|
|
);
|
2020-05-12 19:16:23 +02:00
|
|
|
|
2020-06-18 16:15:26 +02:00
|
|
|
return response && response.data && !Array.isArray(response.data) ? response.data : undefined;
|
2020-05-12 19:16:23 +02:00
|
|
|
} catch (error) {
|
|
|
|
if (error.response?.status === 404) {
|
|
|
|
return undefined;
|
|
|
|
} else {
|
2020-11-09 11:32:12 +01:00
|
|
|
ApiService.handleError(error);
|
2020-05-12 19:16:23 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-12 15:59:44 +01:00
|
|
|
public async getPollByCustomUrlWithHash(custom_url: string, hash: string): Promise<Poll | undefined> {
|
2021-04-21 12:17:05 +02:00
|
|
|
try {
|
|
|
|
const response: AxiosResponse<Poll> = await this.axiosInstance.get<Poll>(
|
2021-11-12 15:59:44 +01:00
|
|
|
`${this.pollsEndpoint}/${custom_url}/pass/${hash}`
|
2021-04-21 12:17:05 +02:00
|
|
|
);
|
2021-11-12 15:59:44 +01:00
|
|
|
console.log('fetch API : asking for poll with custom_url=' + custom_url, { response });
|
2021-04-21 12:17:05 +02:00
|
|
|
|
|
|
|
return response && response.data && !Array.isArray(response.data) ? response.data : undefined;
|
|
|
|
} catch (error) {
|
|
|
|
if (error.response?.status === 404) {
|
|
|
|
return undefined;
|
2021-06-10 12:20:28 +02:00
|
|
|
} else if (error.response?.status === 403) {
|
|
|
|
return error;
|
2021-04-21 12:17:05 +02:00
|
|
|
} else {
|
|
|
|
ApiService.handleError(error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-12 15:59:44 +01:00
|
|
|
public async getSlug(custom_url: string): Promise<boolean> {
|
2020-04-21 17:26:25 +02:00
|
|
|
try {
|
2021-04-30 10:59:46 +02:00
|
|
|
// TODO: scenario should be : if we can get this custom_url, it exists. if not, it doesn't. It's just a GET.
|
2020-05-01 19:10:17 +02:00
|
|
|
const response: AxiosResponse = await this.axiosInstance.get(
|
2021-11-12 15:59:44 +01:00
|
|
|
`${this.pollsEndpoint}${this.slugsEndpoint}/${custom_url}`
|
2020-04-21 17:26:25 +02:00
|
|
|
);
|
2020-05-01 19:10:17 +02:00
|
|
|
if (response?.status !== 404) {
|
|
|
|
return false;
|
|
|
|
}
|
2020-04-21 17:26:25 +02:00
|
|
|
} catch (error) {
|
2020-05-01 19:10:17 +02:00
|
|
|
if (error.response?.status === 404) {
|
|
|
|
return true;
|
|
|
|
} else {
|
2020-11-09 11:32:12 +01:00
|
|
|
ApiService.handleError(error);
|
2020-05-01 19:10:17 +02:00
|
|
|
}
|
2020-04-21 17:26:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-25 12:05:17 +02:00
|
|
|
////////////
|
|
|
|
// UPDATE //
|
2021-11-12 15:59:44 +01:00
|
|
|
////////////
|
2021-04-30 10:59:46 +02:00
|
|
|
public async sendUpdateVoteStack(vote_stack: Stack) {
|
2020-04-21 17:26:25 +02:00
|
|
|
try {
|
2021-06-07 11:30:10 +02:00
|
|
|
return await this.axiosInstance.patch(
|
2021-06-07 12:16:56 +02:00
|
|
|
`${this.baseHref}/vote-stack/${vote_stack.id}/token/${vote_stack.owner.modifier_token}`,
|
2021-06-07 11:30:10 +02:00
|
|
|
vote_stack
|
|
|
|
);
|
2020-04-21 17:26:25 +02:00
|
|
|
} catch (error) {
|
2020-11-09 11:32:12 +01:00
|
|
|
ApiService.handleError(error);
|
2020-04-21 17:26:25 +02:00
|
|
|
}
|
|
|
|
}
|
2021-11-22 11:51:21 +01:00
|
|
|
|
2021-05-21 12:31:42 +02:00
|
|
|
public findMyPollsByEmail(email: string): Promise<any> {
|
|
|
|
return this.axiosInstance.get<any>(`${this.baseHref}/poll/owner/${email}`);
|
2020-04-21 17:26:25 +02:00
|
|
|
}
|
2021-11-22 11:51:21 +01:00
|
|
|
|
2020-06-12 19:17:39 +02:00
|
|
|
public async updateAnswer(slug: string, choiceLabel: string, pseudo: string, answer: Answer): Promise<string> {
|
2020-04-21 17:26:25 +02:00
|
|
|
try {
|
2021-05-21 12:31:42 +02:00
|
|
|
return await this.axiosInstance.patch(`${this.baseHref}/${slug}${this.answersEndpoint}`, {
|
2020-05-12 19:16:23 +02:00
|
|
|
choiceLabel,
|
|
|
|
pseudo,
|
2020-06-12 19:17:39 +02:00
|
|
|
answer,
|
2020-05-01 19:10:17 +02:00
|
|
|
});
|
2020-04-21 17:26:25 +02:00
|
|
|
} catch (error) {
|
2020-11-09 11:32:12 +01:00
|
|
|
ApiService.handleError(error);
|
2020-04-21 17:26:25 +02:00
|
|
|
}
|
|
|
|
}
|
2021-11-22 11:51:21 +01:00
|
|
|
|
2021-05-21 12:31:42 +02:00
|
|
|
////////////
|
|
|
|
// DELETE //
|
|
|
|
|
|
|
|
////////////
|
2020-04-21 17:26:25 +02:00
|
|
|
|
|
|
|
////////////
|
2020-05-12 19:16:23 +02:00
|
|
|
public async deletePoll(slug: string): Promise<boolean> {
|
2020-04-21 17:26:25 +02:00
|
|
|
try {
|
2020-05-12 19:16:23 +02:00
|
|
|
const response: AxiosResponse = await this.axiosInstance.delete(`${this.pollsEndpoint}/${slug}`);
|
|
|
|
return response?.status === 204;
|
2020-04-21 17:26:25 +02:00
|
|
|
} catch (error) {
|
2020-11-09 11:32:12 +01:00
|
|
|
ApiService.handleError(error);
|
2020-04-21 17:26:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-12 19:16:23 +02:00
|
|
|
public async deletePollAnswers(slug: string): Promise<boolean> {
|
2020-04-21 17:26:25 +02:00
|
|
|
try {
|
2020-05-12 19:16:23 +02:00
|
|
|
const response: AxiosResponse = await this.axiosInstance.delete(
|
|
|
|
`${this.pollsEndpoint}/${slug}${this.answersEndpoint}`
|
|
|
|
);
|
|
|
|
return response?.status === 204;
|
2020-04-21 17:26:25 +02:00
|
|
|
} catch (error) {
|
2020-11-09 11:32:12 +01:00
|
|
|
ApiService.handleError(error);
|
2020-04-21 17:26:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-14 14:37:42 +01:00
|
|
|
public async deletePollComments(admin_key: string): Promise<boolean> {
|
2020-04-21 17:26:25 +02:00
|
|
|
try {
|
2022-02-14 14:37:42 +01:00
|
|
|
const response: AxiosResponse = await this.axiosInstance.delete(`${this.pollsEndpoint}/${admin_key}`);
|
2020-05-12 19:16:23 +02:00
|
|
|
return response?.status === 204;
|
2020-04-21 17:26:25 +02:00
|
|
|
} catch (error) {
|
2020-11-09 11:32:12 +01:00
|
|
|
ApiService.handleError(error);
|
2020-04-21 17:26:25 +02:00
|
|
|
}
|
|
|
|
}
|
2021-05-20 14:49:37 +02:00
|
|
|
|
2021-04-30 10:59:46 +02:00
|
|
|
/////////////////////
|
|
|
|
// PRIVATE METHODS //
|
|
|
|
/////////////////////
|
2021-06-07 11:30:10 +02:00
|
|
|
private static handleError(error): void {
|
|
|
|
// this.loaderService.setStatus(true);
|
|
|
|
if (error.response) {
|
|
|
|
// The request was made and the server responded with a status code
|
|
|
|
// that falls out of the range of 2xx
|
|
|
|
console.error('Error response data', error.response.data);
|
|
|
|
console.error('Error response status', error.response.status);
|
|
|
|
console.error('Error response headers', error.response.headers);
|
|
|
|
} else if (error.request) {
|
|
|
|
// The request was made but no response was received
|
|
|
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
|
|
|
// http.ClientRequest in node.js
|
|
|
|
console.log('ErrorRequest', error.request);
|
|
|
|
} else {
|
|
|
|
// Something happened in setting up the request that triggered an Error
|
|
|
|
console.log('Error', error.message);
|
|
|
|
}
|
|
|
|
console.log(error.config);
|
|
|
|
// this.loaderService.setStatus(false);
|
|
|
|
}
|
2021-11-22 11:51:21 +01:00
|
|
|
|
2021-06-07 12:36:49 +02:00
|
|
|
public ousideHandleError(error) {
|
|
|
|
// this.loaderService.setStatus(true);
|
|
|
|
if (error.response) {
|
|
|
|
// The request was made and the server responded with a status code
|
|
|
|
// that falls out of the range of 2xx
|
|
|
|
console.error('Error response data', error.response.data);
|
|
|
|
console.error('Error response status', error.response.status);
|
|
|
|
console.error('Error response headers', error.response.headers);
|
|
|
|
} else if (error.request) {
|
|
|
|
// The request was made but no response was received
|
|
|
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
|
|
|
// http.ClientRequest in node.js
|
|
|
|
console.log('ErrorRequest', error.request);
|
|
|
|
} else {
|
|
|
|
// Something happened in setting up the request that triggered an Error
|
|
|
|
console.log('Error', error.message);
|
|
|
|
}
|
|
|
|
console.log(error.config);
|
|
|
|
// this.loaderService.setStatus(false);
|
|
|
|
}
|
2020-04-19 14:22:10 +02:00
|
|
|
}
|