refacto : create entities, enums, services

This commit is contained in:
seraph 2020-04-19 14:22:10 +02:00
parent 18a7d4781a
commit e524320457
65 changed files with 1034 additions and 660 deletions

View File

@ -1,3 +1,14 @@
## LIBRARIES USED
| lib name | usage |
| -------------------------------------------------------- | -------------------------------------- |
| [axios](https://github.com/axios/axios) | http client |
| [date-fns](https://date-fns.org) | manipulate dates |
| [locale-enum](https://www.npmjs.com/package/locale-enum) | enum of all locales |
| [uuid](https://www.npmjs.com/package/uuid) | handle client-side generation of uuids |
---
# Framadate # Framadate
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.1. This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.2.1.
@ -27,4 +38,3 @@ Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protrac
Before using ng for the first time in this project, use `npm i` to install needed npm modules. Before using ng for the first time in this project, use `npm i` to install needed npm modules.
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@ -10,7 +10,6 @@
"package": "cat dist/framadate/*.js > dist/framadate/framadate-scripts-bundled.js && ls -l dist/framadate", "package": "cat dist/framadate/*.js > dist/framadate/framadate-scripts-bundled.js && ls -l dist/framadate",
"bld:pkg": "npm run build && npm run package", "bld:pkg": "npm run build && npm run package",
"build:demo": "ng build --crossOrigin=anonymous --extractCss=true --progress=true --prod && npm run package", "build:demo": "ng build --crossOrigin=anonymous --extractCss=true --progress=true --prod && npm run package",
"build:demobliss": "ng build --crossOrigin=anonymous --extractCss=true --baseHref=https://framadate-api.cipherbliss.com --progress=true --prod && npm run package",
"test": "ng test --code-coverage --watch=false", "test": "ng test --code-coverage --watch=false",
"lint": "ng lint", "lint": "ng lint",
"e2e": "ng e2e", "e2e": "ng e2e",
@ -18,8 +17,7 @@
"format:all": "prettier --write \"src/**/*.{js,jsx,ts,tsx,md,html,css,scss}\"", "format:all": "prettier --write \"src/**/*.{js,jsx,ts,tsx,md,html,css,scss}\"",
"trans": "ng xi18n --output-path=src/locale --i18n-locale=fr", "trans": "ng xi18n --output-path=src/locale --i18n-locale=fr",
"storybook": "start-storybook -p 6006", "storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook", "build-storybook": "build-storybook"
"postinstall": "ngcc"
}, },
"private": false, "private": false,
"dependencies": { "dependencies": {
@ -37,12 +35,15 @@
"@ngx-translate/core": "^12.1.2", "@ngx-translate/core": "^12.1.2",
"@ngx-translate/http-loader": "^4.0.0", "@ngx-translate/http-loader": "^4.0.0",
"angular-date-value-accessor": "^1.0.2", "angular-date-value-accessor": "^1.0.2",
"axios": "^0.19.2",
"bulma": "^0.8.2", "bulma": "^0.8.2",
"chart.js": "^2.8.0", "chart.js": "^2.8.0",
"date-fns": "^2.12.0",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"karma-coverage": "^2.0.1", "karma-coverage": "^2.0.1",
"karma-firefox-launcher": "^1.3.0", "karma-firefox-launcher": "^1.3.0",
"karma-phantomjs-launcher": "^1.0.4", "karma-phantomjs-launcher": "^1.0.4",
"locale-enum": "^1.1.0",
"ngx-clipboard": "^13.0.0", "ngx-clipboard": "^13.0.0",
"ngx-markdown": "^9.0.0", "ngx-markdown": "^9.0.0",
"ngx-toaster": "^1.0.1", "ngx-toaster": "^1.0.1",
@ -52,6 +53,7 @@
"rxjs": "^6.5.5", "rxjs": "^6.5.5",
"rxjs-compat": "^6.5.5", "rxjs-compat": "^6.5.5",
"tslib": "^1.11.1", "tslib": "^1.11.1",
"uuid": "^7.0.3",
"zone.js": "^0.10.3" "zone.js": "^0.10.3"
}, },
"devDependencies": { "devDependencies": {
@ -68,6 +70,7 @@
"@types/jasmine": "^3.5.10", "@types/jasmine": "^3.5.10",
"@types/jasminewd2": "~2.0.8", "@types/jasminewd2": "~2.0.8",
"@types/node": "^13.11.1", "@types/node": "^13.11.1",
"@types/uuid": "^7.0.2",
"@typescript-eslint/eslint-plugin": "^2.27.0", "@typescript-eslint/eslint-plugin": "^2.27.0",
"@typescript-eslint/parser": "^2.27.0", "@typescript-eslint/parser": "^2.27.0",
"babel-loader": "^8.1.0", "babel-loader": "^8.1.0",

View File

@ -20,6 +20,7 @@ import { PollGraphicComponent } from './poll-graphic/poll-graphic.component';
const routes: Routes = [ const routes: Routes = [
{ path: '', redirectTo: 'step/creation', pathMatch: 'full' }, { path: '', redirectTo: 'step/creation', pathMatch: 'full' },
{ path: 'admin/:token', component: AdminComponent }, // http://localhost:4200/#/admin/srfdgedsTGETHRYJtujTUjTUkTIUKTK
{ path: 'home', component: HomeComponent }, { path: 'home', component: HomeComponent },
{ path: 'base', component: BaseComponent }, { path: 'base', component: BaseComponent },
{ path: 'step/base', component: BaseComponent }, { path: 'step/base', component: BaseComponent },

View File

@ -49,7 +49,6 @@ import { VotingSummaryComponent } from './pages/voting/voting-summary/voting-sum
import { VotingComponent } from './pages/voting/voting.component'; import { VotingComponent } from './pages/voting/voting.component';
import { PollGraphicComponent } from './poll-graphic/poll-graphic.component'; import { PollGraphicComponent } from './poll-graphic/poll-graphic.component';
import { ConfigService } from './services/config.service'; import { ConfigService } from './services/config.service';
import { PollService } from './services/poll.service';
import { CopyTextComponent } from './ui/copy-text/copy-text.component'; import { CopyTextComponent } from './ui/copy-text/copy-text.component';
import { ResettableInputDirective } from './ui/directives/resettable-input.directive'; import { ResettableInputDirective } from './ui/directives/resettable-input.directive';
import { ErasableInputComponent } from './ui/erasable-input/erasable-input.component'; import { ErasableInputComponent } from './ui/erasable-input/erasable-input.component';
@ -135,7 +134,7 @@ export function HttpLoaderFactory(http: HttpClient): TranslateHttpLoader {
HttpClientModule, HttpClientModule,
FormsModule, FormsModule,
], ],
providers: [TranslateService, ConfigService, PollService, MessageService, ConfirmationService], providers: [TranslateService, ConfigService, MessageService, ConfirmationService],
bootstrap: [AppComponent], bootstrap: [AppComponent],
}) })
export class AppModule {} export class AppModule {}

View File

@ -32,7 +32,7 @@ export class DateUtilities {
}); });
d1.setDate(d1.getDate() + interval); d1.setDate(d1.getDate() + interval);
} }
return dates.slice(0); return [...dates];
} }
/** /**
@ -40,7 +40,7 @@ export class DateUtilities {
* @param d1 * @param d1
* @param d2 * @param d2
*/ */
dayDiff(d1: Date, d2: Date): Number { dayDiff(d1: Date, d2: Date): number {
return Number(d2.getTime() - d1.getTime() / 31536000000); return Number(d2.getTime() - d1.getTime() / 31536000000);
} }

View File

@ -57,8 +57,8 @@ export class PollConfig {
customUrl = ''; // custom slug in the url, must be unique customUrl = ''; // custom slug in the url, must be unique
customUrlIsUnique = null; // given by the backend customUrlIsUnique = null; // given by the backend
urlSlugPublic = null; urlSlugPublic = null;
urlPublic = environment.production ? '' : environment.baseHref + '/#/poll/id/4'; urlPublic = environment.production ? '' : window.location.origin + '/#/poll/id/4';
urlAdmin = environment.baseHref + '/#/admin/d65es45fd45sdf45sd345f312sdf31sgfd345'; urlAdmin = window.location.origin + '/#/admin/d65es45fd45sdf45sd345f312sdf31sgfd345';
adminKey = ''; // key to change config of the poll adminKey = ''; // key to change config of the poll
owner_modifier_token = ''; // key to change a vote stack owner_modifier_token = ''; // key to change a vote stack
canModifyAnswers = true; // bool for the frontend selector canModifyAnswers = true; // bool for the frontend selector

View File

@ -1,42 +0,0 @@
import { AdminComponent } from '../pages/admin/admin.component';
import { AnswersComponent } from '../pages/answers/answers.component';
import { BaseComponent } from '../pages/base-page/base.component';
import { CreateOrRetrieveComponent } from '../pages/create-or-retrieve/create-or-retrieve.component';
import { DatesComponent } from '../pages/dates/dates.component';
import { EndConfirmationComponent } from '../pages/end-confirmation/end-confirmation.component';
import { HomeComponent } from '../pages/home/home.component';
import { KindComponent } from '../pages/kind/kind.component';
import { PasswordComponent } from '../pages/password/password.component';
import { PicturesComponent } from '../pages/pictures/pictures.component';
import { PollDisplayComponent } from '../pages/poll-display/poll-display.component';
import { ResumeComponent } from '../pages/resume/resume.component';
import { VisibilityComponent } from '../pages/visibility/visibility.component';
import { VotingChoiceComponent } from '../pages/voting/voting-choice/voting-choice.component';
import { VotingComponent } from '../pages/voting/voting.component';
import { PollGraphicComponent } from '../poll-graphic/poll-graphic.component';
/**
* each step in the form is a component
*/
export const Routes = [
{ path: '', redirectTo: 'step/creation', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'base', component: BaseComponent },
{ path: 'step/base', component: BaseComponent },
{ path: 'step/creation', component: CreateOrRetrieveComponent },
{ path: 'step/date', component: DatesComponent },
{ path: 'step/kind', component: KindComponent },
{ path: 'step/answers', component: AnswersComponent },
{ path: 'step/admin', component: AdminComponent },
{ path: 'step/pictures', component: PicturesComponent },
{ path: 'step/visibility', component: VisibilityComponent },
{ path: 'step/resume', component: ResumeComponent },
{ path: 'step/end', component: EndConfirmationComponent },
{ path: 'graphic/:poll', component: PollGraphicComponent },
{ path: 'vote/poll/id/:poll', component: PollDisplayComponent },
{ path: 'vote/poll/slug/:pollSlug', component: PollDisplayComponent },
{ path: 'votingchoice', component: VotingChoiceComponent },
{ path: 'voting', component: VotingComponent },
{ path: 'step/password', component: PasswordComponent },
{ path: '**', redirectTo: '/home', pathMatch: 'full' },
];

View File

@ -1,24 +0,0 @@
export var graphOptions = {
legend: { display: false },
scales: {
xAxes: [
{
gridLines: { drawBorder: false, display: false },
display: false,
stacked: true,
ticks: {
beginAtZero: true,
maxRotation: 0,
minRotation: 0,
},
},
],
yAxes: [
{
gridLines: { drawBorder: true, display: false },
display: true,
stacked: true,
},
],
},
};

View File

@ -1,10 +1,10 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { BaseComponent } from '../base-page/base.component'; import { BaseComponent } from '../base-page/base.component';
import { ConfigService } from '../../services/config.service'; import { ConfigService } from '../../services/config.service';
import { mockComments } from '../../config/mocks/mock-comments'; import { mockComments } from '../../mocks/mock-comments';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import { mockPoll3 } from '../../config/mocks/mock-poll3'; import { mockPoll3 } from '../../mocks/mock-poll3';
@Component({ @Component({
selector: 'framadate-poll-display', selector: 'framadate-poll-display',

View File

@ -103,7 +103,7 @@
</sub> </sub>
<div class="preview-url"> <div class="preview-url">
<a [href]="'/#/vote/poll/slug/' + config.customUrl"> <a [href]="'/#/vote/poll/slug/' + config.customUrl">
{{ environment.baseHref + '#/vote/poll/slug/' + config.customUrl }} {{ window.location.origin + '#/vote/poll/slug/' + config.customUrl }}
</a> </a>
<framadate-copy-text [textToCopy]="config.urlPublic"></framadate-copy-text> <framadate-copy-text [textToCopy]="config.urlPublic"></framadate-copy-text>
</div> </div>

View File

@ -11,7 +11,7 @@ import { PollUtilities } from '../../config/PollUtilities';
}) })
export class VisibilityComponent extends BaseComponent implements OnInit { export class VisibilityComponent extends BaseComponent implements OnInit {
showCustomPassword = false; showCustomPassword = false;
baseUrl = environment.baseApiHref; baseUrl = environment.api.baseHref;
environment = environment; environment = environment;
constructor(public config: ConfigService, public utils: PollUtilities) { constructor(public config: ConfigService, public utils: PollUtilities) {

View File

@ -7,8 +7,8 @@ import { ConfirmationService, MessageService } from 'primeng';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { ConfigService } from '../../../services/config.service'; import { ConfigService } from '../../../services/config.service';
import { VotingChoiceComponent } from './voting-choice.component'; import { VotingChoiceComponent } from './voting-choice.component';
import { mockChoice } from '../../../config/mocks/choice'; import { mockChoice } from '../../../mocks/choice';
import { mockPoll3 } from '../../../config/mocks/mock-poll3'; import { mockPoll3 } from '../../../mocks/mock-poll3';
const routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']); const routerSpy = jasmine.createSpyObj('Router', ['navigateByUrl']);

View File

@ -1,5 +1,5 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { mockComments } from '../../../config/mocks/mock-comments'; import { mockComments } from '../../../mocks/mock-comments';
@Component({ @Component({
selector: 'framadate-voting-comment', selector: 'framadate-voting-comment',

View File

@ -110,7 +110,7 @@
<button <button
*ngIf="config.isAdmin" *ngIf="config.isAdmin"
type="button" type="button"
(click)="showModalDialog()" (click)="toggleModalDialogVisibility()"
pButton pButton
icon="pi pi-external-link" icon="pi pi-external-link"
label="Show" label="Show"
@ -118,7 +118,7 @@
show admin confirmation modal show admin confirmation modal
</button> </button>
<p-dialog <p-dialog
[(visible)]="config.displayConfirmVoteModalAdmin" [visible]="displayConfirmVoteModalAdmin"
[modal]="true" [modal]="true"
[baseZIndex]="10000" [baseZIndex]="10000"
[draggable]="false" [draggable]="false"
@ -144,7 +144,7 @@
type="button btn--large btn btn--block" type="button btn--large btn btn--block"
pButton pButton
icon="fa fa-check" icon="fa fa-check"
(click)="config.displayConfirmVoteModalAdmin = false" (click)="toggleModalDialogVisibility()"
label="Revenir au sondage" label="Revenir au sondage"
class="btn btn--primary btn--default btn--purple btn--black-text" class="btn btn--primary btn--default btn--purple btn--black-text"
></button> ></button>

View File

@ -1,6 +1,6 @@
import { Component, Input, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { ConfigService } from '../../../services/config.service'; import { ConfigService } from '../../../services/config.service';
import { mockPoll3 } from '../../../config/mocks/mock-poll3'; import { mockPoll3 } from '../../../mocks/mock-poll3';
@Component({ @Component({
selector: 'framadate-voting-summary', selector: 'framadate-voting-summary',
@ -8,14 +8,15 @@ import { mockPoll3 } from '../../../config/mocks/mock-poll3';
styleUrls: ['./voting-summary.component.scss'], styleUrls: ['./voting-summary.component.scss'],
}) })
export class VotingSummaryComponent implements OnInit { export class VotingSummaryComponent implements OnInit {
preferred: string = 'rien'; public displayConfirmVoteModalAdmin = false;
severalPreferred: boolean = false; public preferred = 'rien';
public severalPreferred = false;
@Input() pollconfig = mockPoll3; @Input() pollconfig = mockPoll3;
constructor(public config: ConfigService) {} constructor(public config: ConfigService) {}
ngOnInit() { ngOnInit(): void {
this.computePreferred(); this.computePreferred();
} }
@ -44,7 +45,7 @@ export class VotingSummaryComponent implements OnInit {
}); });
} }
showModalDialog() { toggleModalDialogVisibility(): void {
this.config.displayConfirmVoteModalAdmin = true; this.displayConfirmVoteModalAdmin = !this.displayConfirmVoteModalAdmin;
} }
} }

View File

@ -0,0 +1,8 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
@NgModule({
declarations: [],
imports: [CommonModule],
})
export class PollAdministrationModule {}

View File

@ -1,10 +1,9 @@
import { Component, Inject, OnInit } from '@angular/core'; import { Component, Inject, OnInit } from '@angular/core';
import { Chart } from 'chart.js'; import { Chart } from 'chart.js';
import { DOCUMENT } from '@angular/common'; import { DOCUMENT } from '@angular/common';
import { mockGraphConfig } from '../config/mocks/mock-graph'; import { mockGraphConfig } from '../mocks/mock-graph';
import { graphOptions } from '../config/graph-canevas-options';
import { ConfigService } from '../services/config.service'; import { ConfigService } from '../services/config.service';
import { mockPoll3 } from '../config/mocks/mock-poll3'; import { mockPoll3 } from '../mocks/mock-poll3';
@Component({ @Component({
selector: 'framadate-poll-graphic', selector: 'framadate-poll-graphic',
@ -12,19 +11,19 @@ import { mockPoll3 } from '../config/mocks/mock-poll3';
styleUrls: ['./poll-graphic.component.scss'], styleUrls: ['./poll-graphic.component.scss'],
}) })
export class PollGraphicComponent implements OnInit { export class PollGraphicComponent implements OnInit {
isColorblind: boolean = false; isColorblind = false;
pollConfigRetrieved: any = mockPoll3; pollConfigRetrieved: any = mockPoll3;
graphicConfig: any = mockGraphConfig; graphicConfig: any = mockGraphConfig;
preferred: any = 'rien'; preferred: any = 'rien';
yesList: number[] = []; yesList: number[] = [];
maybeList: number[] = []; maybeList: number[] = [];
noList: number[] = []; noList: number[] = [];
nbPoll: number = 0; nbPoll = 0;
dateList: string[] = []; dateList: string[] = [];
constructor(@Inject(DOCUMENT) private document: any, private config: ConfigService) {} constructor(@Inject(DOCUMENT) private document: any, private config: ConfigService) {}
ngOnInit() { ngOnInit(): void {
this.formatDataAnswers(this.graphicConfig); this.formatDataAnswers(this.graphicConfig);
this.isColorblind = false; this.isColorblind = false;
this.pollConfigRetrieved = new Chart(this.document.getElementById('graph'), { this.pollConfigRetrieved = new Chart(this.document.getElementById('graph'), {
@ -52,15 +51,15 @@ export class PollGraphicComponent implements OnInit {
}, },
], ],
}, },
options: graphOptions, options: this.getSettedGraphOptions(),
}); });
} }
toggleColorblind() { public toggleColorblind(): void {
this.isColorblind = !this.isColorblind; this.isColorblind = !this.isColorblind;
} }
formatDataAnswers(poll) { public formatDataAnswers(poll): void {
// if (poll && poll.pollType === "date") { // if (poll && poll.pollType === "date") {
this.initPollCounter(); this.initPollCounter();
poll.answers.forEach((response) => { poll.answers.forEach((response) => {
@ -79,11 +78,35 @@ export class PollGraphicComponent implements OnInit {
// } // }
} }
initPollCounter() { public initPollCounter(): void {
this.nbPoll++; this.nbPoll++;
this.dateList[this.nbPoll - 1] = 'jeudi'; this.dateList[this.nbPoll - 1] = 'jeudi';
this.maybeList[this.nbPoll - 1] = 0; this.maybeList[this.nbPoll - 1] = 0;
this.yesList[this.nbPoll - 1] = 0; this.yesList[this.nbPoll - 1] = 0;
this.noList[this.nbPoll - 1] = 0; this.noList[this.nbPoll - 1] = 0;
} }
private getSettedGraphOptions(): any {
// TODO: create interfaces, or find another way to work. "any" return type should be removed.
return {
legend: { display: false },
scales: {
xAxes: [
{
gridLines: { drawBorder: false, display: false },
display: false,
stacked: true,
ticks: { beginAtZero: true, maxRotation: 0, minRotation: 0 },
},
],
yAxes: [
{
gridLines: { drawBorder: true, display: false },
display: true,
stacked: true,
},
],
},
};
}
} }

View File

@ -0,0 +1,8 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
@NgModule({
declarations: [],
imports: [CommonModule],
})
export class PollParticipationModule {}

View File

@ -4,9 +4,9 @@ import { HttpClient } from '@angular/common/http';
import { environment } from '../../environments/environment'; import { environment } from '../../environments/environment';
import { ConfirmationService, MessageService } from 'primeng/api'; import { ConfirmationService, MessageService } from 'primeng/api';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { mockMyPolls } from '../config/mocks/mockmypolls'; import { mockMyPolls } from '../mocks/mockmypolls';
import { mockPoll3 } from '../config/mocks/mock-poll3'; import { mockPoll3 } from '../mocks/mock-poll3';
import { mockSuccessVote } from '../config/mocks/mock-success-vote'; import { mockSuccessVote } from '../mocks/mock-success-vote';
import { PollUtilities } from '../config/PollUtilities'; import { PollUtilities } from '../config/PollUtilities';
const LocalstoragePreferences = { const LocalstoragePreferences = {
@ -25,7 +25,7 @@ const LocalstoragePreferences = {
export class ConfigService extends PollConfig { export class ConfigService extends PollConfig {
preferences: any = LocalstoragePreferences; // user specific preferences, stored in localstorage, used for theme. preferences: any = LocalstoragePreferences; // user specific preferences, stored in localstorage, used for theme.
loading = false; loading = false;
baseHref: any = environment.baseApiHref; apiBaseHref: any = environment.api.baseHref;
constructor( constructor(
private http: HttpClient, private http: HttpClient,
@ -138,7 +138,7 @@ export class ConfigService extends PollConfig {
return jsonConfig; return jsonConfig;
} }
checkIfSlugIsUniqueInDatabase(slug: string = '') { checkIfSlugIsUniqueInDatabase(slug = '') {
this.customUrlIsUnique = null; this.customUrlIsUnique = null;
if (!slug) { if (!slug) {
slug = this.utils.makeSlug(this); slug = this.utils.makeSlug(this);
@ -148,7 +148,7 @@ export class ConfigService extends PollConfig {
// TODO // TODO
this.todo('check slug is unique'); this.todo('check slug is unique');
this.http this.http
.get(`${this.baseHref}/check-slug-is-uniq/${slug}`, this.utils.makeHeaders({ slug: this.customUrl })) .get(`${this.apiBaseHref}/check-slug-is-uniq/${slug}`, this.utils.makeHeaders({ slug: this.customUrl }))
.subscribe( .subscribe(
(res: any) => { (res: any) => {
this.customUrlIsUnique = res.poll.isUnique; this.customUrlIsUnique = res.poll.isUnique;
@ -170,7 +170,7 @@ export class ConfigService extends PollConfig {
this.todo('send email for real : TODO'); this.todo('send email for real : TODO');
this.loading = true; this.loading = true;
this.http.get(`${this.baseHref}/send-polls-to-user/${this.myEmail}`, this.utils.makeHeaders()).subscribe( this.http.get(`${this.apiBaseHref}/send-polls-to-user/${this.myEmail}`, this.utils.makeHeaders()).subscribe(
(res) => { (res) => {
// message: 'Trouvé! Allez voir votre boite email', // message: 'Trouvé! Allez voir votre boite email',
this.myPolls = res; this.myPolls = res;
@ -211,7 +211,7 @@ export class ConfigService extends PollConfig {
*/ */
getPollByURL(url: string) { getPollByURL(url: string) {
this.todo(); this.todo();
return this.http.get(`${this.baseHref}/poll/slug/${url}`, this.utils.makeHeaders()); return this.http.get(`${this.apiBaseHref}/poll/slug/${url}`, this.utils.makeHeaders());
} }
/** /**
@ -220,7 +220,7 @@ export class ConfigService extends PollConfig {
* @param id * @param id
*/ */
getPollById(id: string, password?: string) { getPollById(id: string, password?: string) {
return this.http.get(`${this.baseHref}/poll/${id}`, this.utils.makeHeaders({ body: password })); return this.http.get(`${this.apiBaseHref}/poll/${id}`, this.utils.makeHeaders({ body: password }));
} }
fetchPollFromRoute(event) { fetchPollFromRoute(event) {
@ -233,7 +233,7 @@ export class ConfigService extends PollConfig {
* @param ownerEmail * @param ownerEmail
*/ */
getMyPolls(ownerEmail: string) { getMyPolls(ownerEmail: string) {
this.http.get(`${this.baseHref}/my-polls`, this.utils.makeHeaders({ ownerEmail })).subscribe( this.http.get(`${this.apiBaseHref}/my-polls`, this.utils.makeHeaders({ ownerEmail })).subscribe(
(res: any) => { (res: any) => {
// this.myPolls = res.poll; // this.myPolls = res.poll;
}, },
@ -256,16 +256,16 @@ export class ConfigService extends PollConfig {
this.currentPoll = res; this.currentPoll = res;
this.pollId = res.poll.id; this.pollId = res.poll.id;
this.owner_modifier_token = res.owner_modifier_token; this.owner_modifier_token = res.owner_modifier_token;
this.urlPublic = this.baseHref + '#/vote/poll/id/' + res.poll.id; this.urlPublic = this.apiBaseHref + '#/vote/poll/id/' + res.poll.id;
this.urlSlugPublic = this.baseHref + '#/vote/poll/slug/' + res.poll.id; this.urlSlugPublic = this.apiBaseHref + '#/vote/poll/slug/' + res.poll.id;
if (res.poll.customUrl) { if (res.poll.customUrl) {
this.urlSlugPublic = this.baseHref + '#/vote/poll/id/' + res.poll.customUrl; this.urlSlugPublic = this.apiBaseHref + '#/vote/poll/id/' + res.poll.customUrl;
} }
if (res.vote_stack) { if (res.vote_stack) {
this.loadVoteStack(res.vote_stack); this.loadVoteStack(res.vote_stack);
} }
this.adminKey = res.admin_key; this.adminKey = res.admin_key;
this.urlAdmin = this.baseHref + '#/admin/' + res.admin_key; this.urlAdmin = this.apiBaseHref + '#/admin/' + res.admin_key;
} }
resetCurrentChoicesAnswers() { resetCurrentChoicesAnswers() {
@ -312,7 +312,7 @@ export class ConfigService extends PollConfig {
createPollFromConfig(config: any) { createPollFromConfig(config: any) {
this.loading = true; this.loading = true;
console.log('config', config); console.log('config', config);
return this.http.post(`${this.baseHref}/poll`, config, this.utils.makeHeaders()).subscribe( return this.http.post(`${this.apiBaseHref}/poll`, config, this.utils.makeHeaders()).subscribe(
(res: any) => { (res: any) => {
// redirect to the page to administrate the new poll // redirect to the page to administrate the new poll
this.messageService.add({ severity: 'success', summary: 'Sondage Créé' }); this.messageService.add({ severity: 'success', summary: 'Sondage Créé' });
@ -372,7 +372,7 @@ export class ConfigService extends PollConfig {
this.handleVoteAdded(mockSuccessVote); this.handleVoteAdded(mockSuccessVote);
return; return;
} }
this.http.post(`${this.baseHref}/poll/${this.pollId}/vote`, voteStack, this.utils.makeHeaders()).subscribe( this.http.post(`${this.apiBaseHref}/poll/${this.pollId}/vote`, voteStack, this.utils.makeHeaders()).subscribe(
(res: any) => { (res: any) => {
this.handleVoteAdded(res); this.handleVoteAdded(res);
}, },
@ -384,7 +384,8 @@ export class ConfigService extends PollConfig {
handleVoteAdded(res) { handleVoteAdded(res) {
if (this.isAdmin) { if (this.isAdmin) {
this.displayConfirmVoteModalAdmin = true; // TODO : REFACTO, displayConfirmVoteModalAdmin exists only in voting-summary.component
// this.displayConfirmVoteModalAdmin = true;
} }
// save modifier token // save modifier token
this.myVoteStack.modifier_token = res.modifier_token; this.myVoteStack.modifier_token = res.modifier_token;
@ -405,7 +406,7 @@ export class ConfigService extends PollConfig {
} }
this.http this.http
.patch( .patch(
`${this.baseHref}/vote-stack/${voteStack.id}/token/${this.owner_modifier_token}`, `${this.apiBaseHref}/vote-stack/${voteStack.id}/token/${this.owner_modifier_token}`,
voteStack, voteStack,
this.utils.makeHeaders() this.utils.makeHeaders()
) )
@ -436,7 +437,7 @@ export class ConfigService extends PollConfig {
}; };
} }
console.log('comment', comment); console.log('comment', comment);
this.http.post(`${this.baseHref}/poll/${this.pollId}/comment`, comment, this.utils.makeHeaders()).subscribe( this.http.post(`${this.apiBaseHref}/poll/${this.pollId}/comment`, comment, this.utils.makeHeaders()).subscribe(
(res: any) => { (res: any) => {
this.messageService.add({ this.messageService.add({
severity: 'success', severity: 'success',
@ -468,18 +469,20 @@ export class ConfigService extends PollConfig {
this.title + this.title +
') permanentely?', ') permanentely?',
accept: () => { accept: () => {
this.http.delete(`${this.baseHref}/poll/${this.pollId}/comments`, this.utils.makeHeaders()).subscribe( this.http
(res: any) => { .delete(`${this.apiBaseHref}/poll/${this.pollId}/comments`, this.utils.makeHeaders())
this.messageService.add({ .subscribe(
severity: 'success', (res: any) => {
summary: 'Commentaires bien supprimés', this.messageService.add({
detail: 'Commentaires du sondage "' + this.title + '" supprimé', severity: 'success',
}); summary: 'Commentaires bien supprimés',
}, detail: 'Commentaires du sondage "' + this.title + '" supprimé',
(e) => { });
this.handleError(e); },
} (e) => {
); this.handleError(e);
}
);
}, },
}); });
} }
@ -492,7 +495,7 @@ export class ConfigService extends PollConfig {
this.title + this.title +
') permanentely?', ') permanentely?',
accept: () => { accept: () => {
this.http.delete(`${this.baseHref}/poll/${this.pollId}/votes`, this.utils.makeHeaders()).subscribe( this.http.delete(`${this.apiBaseHref}/poll/${this.pollId}/votes`, this.utils.makeHeaders()).subscribe(
(res: any) => { (res: any) => {
this.messageService.add({ this.messageService.add({
severity: 'success', severity: 'success',
@ -524,7 +527,7 @@ export class ConfigService extends PollConfig {
self.title + self.title +
') and all is data permanentely?', ') and all is data permanentely?',
accept: () => { accept: () => {
this.http.delete(`${this.baseHref}/poll/${this.pollId}`, this.utils.makeHeaders()).subscribe( this.http.delete(`${this.apiBaseHref}/poll/${this.pollId}`, this.utils.makeHeaders()).subscribe(
(res: any) => { (res: any) => {
this.messageService.add({ this.messageService.add({
severity: 'success', severity: 'success',
@ -549,7 +552,7 @@ export class ConfigService extends PollConfig {
* TODO * TODO
*/ */
updatePoll(voteStack: any) { updatePoll(voteStack: any) {
this.http.put(`${this.baseHref}/poll/${this.pollId}`, voteStack, this.utils.makeHeaders()).subscribe( this.http.put(`${this.apiBaseHref}/poll/${this.pollId}`, voteStack, this.utils.makeHeaders()).subscribe(
(res: any) => { (res: any) => {
this.messageService.add({ this.messageService.add({
severity: 'success', severity: 'success',
@ -645,7 +648,7 @@ export class ConfigService extends PollConfig {
} }
download(filename, text) { download(filename, text) {
var element = document.createElement('a'); const element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename); element.setAttribute('download', filename);

View File

@ -1,12 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { PollService } from './poll.service';
describe('PollServiceService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: PollService = TestBed.get(PollService);
expect(service).toBeTruthy();
});
});

View File

@ -1,11 +0,0 @@
import { Injectable } from '@angular/core';
import { environment } from '../../environments/environment';
@Injectable({
providedIn: 'root',
})
export class PollService {
private baseHref: string = environment.baseApiHref;
constructor() {}
}

View File

@ -1,12 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { ConfigService } from './config.service';
describe('ConfigService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: ConfigService = TestBed.get(ConfigService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,4 @@
export enum AnswerGranularity {
BASIC = 'BASIC',
COMPLEX = 'COMPLEX',
}

View File

@ -0,0 +1,5 @@
export enum AnswerType {
YES = 'YES',
NO = 'NO',
MAYBE = 'MAYBE',
}

View File

@ -0,0 +1,4 @@
export enum PollType {
CLASSIC = 'CLASSIC',
DATES = 'DATES',
}

View File

@ -0,0 +1,5 @@
export enum Theme {
LIGHT = 'LIGHT',
DARK = 'DARK',
RED = 'RED',
}

View File

@ -0,0 +1,5 @@
export interface DateOption {
timeList: any;
literal: string;
date_object?: object;
}

View File

@ -0,0 +1,6 @@
import { AnswerType } from '../enums/answer-type.enum';
import { PollOption } from './poll-options.model';
export class Answer {
constructor(public pollOption: PollOption, public type: AnswerType, public userPseudo: string) {}
}

View File

@ -0,0 +1,13 @@
import { AnswerGranularity } from '../enums/answer-granularity.enum';
export class PollConfig {
constructor(
public allowSeveralHours = true,
public isVisibleToAnyoneWithTheLink: boolean = true,
public answerType: AnswerGranularity = AnswerGranularity.BASIC,
public creationDate: Date = new Date(),
public expirationDate?: Date,
public canVotersModifyTheirAnswers = true,
public isProtectedByPassword: boolean = false
) {}
}

View File

@ -0,0 +1,9 @@
import { isValid } from 'date-fns';
export class PollOption {
constructor(public label: string, public url?: string, public subOptions?: PollOption[]) {}
public isDatePoll(): boolean {
return isValid(this.label);
}
}

View File

@ -0,0 +1,19 @@
import { PollType } from '../enums/poll-type.enum';
import { Answer } from './answer.model';
import { PollConfig } from './poll-config.model';
import { PollOption } from './poll-options.model';
import { User } from './user.model';
export class Poll {
constructor(
public id: string,
public slug: string,
public type: PollType,
public title: string,
public description: string,
public owner: User,
public config: PollConfig,
public options: PollOption[] = [],
public answers: Answer[] = []
) {}
}

View File

@ -0,0 +1,5 @@
import { Theme } from '../enums/theme.enum';
export class UIConfig {
constructor(public isMenuVisible = true, public theme: Theme.LIGHT) {}
}

View File

@ -0,0 +1,10 @@
import { Poll } from './poll.model';
export class User {
constructor(
public isOwner: boolean = false,
public pseudo?: string,
public email?: string,
public polls?: Poll[]
) {}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { ApiService } from './api.service';
describe('ApiService', () => {
let service: ApiService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ApiService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,195 @@
import { Injectable } from '@angular/core';
import axios, { AxiosResponse } from 'axios';
import { environment } from 'src/environments/environment';
import { Poll } from '../models/poll.model';
import { User } from '../models/user.model';
@Injectable({
providedIn: 'root',
})
export class ApiService {
////////////
// CREATE //
////////////
public async savePoll(poll: Poll): Promise<void> {
try {
await axios.post(`${environment.api.baseHref}${environment.api.endpoints.poll.root}`, {
params: { config: poll.config },
});
} catch (error) {
this.handleError(error);
}
}
public async saveVote(poll: Poll): Promise<void> {
try {
// TODO: add the votestack in the params
await axios.post(
`${environment.api.baseHref}${environment.api.endpoints.poll.root}/${poll.id}${environment.api.endpoints.vote}`,
{ params: { voteStack: {} } }
);
} catch (error) {
this.handleError(error);
}
}
public async saveComment(poll: Poll, comment: string): Promise<void> {
try {
// TODO: add the comment in the params
await axios.post(
`${environment.api.baseHref}${environment.api.endpoints.poll.root}/${poll.id}${environment.api.endpoints.comment}`,
{ params: { comment } }
);
} catch (error) {
this.handleError(error);
}
}
//////////
// READ //
//////////
public async isSlugAvailable(slug: string): Promise<boolean> {
try {
// TODO: what is the return of the API ? could it be changed to an object { isAvailable: true } ?
const response: AxiosResponse = await axios.get(
`${environment.api.baseHref}${environment.api.endpoints.slug.isAvailable}/${slug}`
);
if (response) {
const { isAvailable } = response.data;
return isAvailable;
}
} catch (error) {
this.handleError(error);
}
}
public async sendUserPollsByEmail(email: string): Promise<Poll[]> {
// If user is not authenticated: the list of polls is send to user's email by the backend.
try {
const response: AxiosResponse<Poll[]> = await axios.get<Poll[]>(
`${environment.api.baseHref}${environment.api.endpoints.poll.byEmail.sendEmail}/${email}`
);
return response ? response.data : [];
} catch (error) {
this.handleError(error);
}
}
public async getUserPollsByEmail(user: User): Promise<Poll[]> {
// If user is authenticated : retrieve polls & display directly in frontend.
// TODO: Backend should handle this case. Actually the endpoint doesn't exist in backend.
try {
const response: AxiosResponse<Poll[]> = await axios.get<Poll[]>(
`${environment.api.baseHref}${environment.api.endpoints.poll.byEmail.retrieve}/${user.email}`
);
return response ? response.data : [];
} catch (error) {
this.handleError(error);
}
}
public async getPollsByUrl(url: string): Promise<Poll[]> {
try {
const response: AxiosResponse<Poll[]> = await axios.get<Poll[]>(
`${environment.api.baseHref}${environment.api.endpoints.poll.byUrl}/${url}`
);
return response ? response.data : [];
} catch (error) {
this.handleError(error);
}
}
public async getPollById(id: string, password?: string): Promise<Poll[]> {
try {
const response: AxiosResponse<Poll[]> = await axios.get<Poll[]>(
`${environment.api.baseHref}${environment.api.endpoints.poll.root}/${id}`,
password ? { params: { password } } : {}
);
return response ? response.data : [];
} catch (error) {
this.handleError(error);
}
}
////////////
// UPDATE //
////////////
public async updatePoll(poll: Poll): Promise<void> {
try {
// TODO: implement the params when entities are finalized.
await axios.put(`${environment.api.baseHref}${environment.api.endpoints.poll.root}/${poll.id}`, {
params: { voteStack: {}, token: '' },
});
} catch (error) {
this.handleError(error);
}
}
public async updateVote(voteStack: any): Promise<void> {
try {
// TODO: implement the params when entities are finalized.
await axios.patch(`${environment.api.baseHref}${environment.api.endpoints.vote.root}/${voteStack.id}`, {
params: { voteStack: {}, token: '' },
});
} catch (error) {
this.handleError(error);
}
}
////////////
// DELETE //
////////////
public async deletePoll(poll: Poll): Promise<void> {
try {
await axios.delete(`${environment.api.baseHref}${environment.api.endpoints.poll.root}${poll.id}`, {});
} catch (error) {
this.handleError(error);
}
}
public async deletePollVotes(poll: Poll): Promise<void> {
try {
// TODO: update endpoint in Backend
await axios.delete(
`${environment.api.baseHref}${environment.api.endpoints.poll.root}${poll.id}${environment.api.endpoints.vote.root}`
);
} catch (error) {
this.handleError(error);
}
}
public async deletePollComments(poll: Poll): Promise<void> {
try {
// TODO: modify endpoint in Backend
await axios.delete(
`${environment.api.baseHref}${environment.api.endpoints.poll.root}${poll.id}${environment.api.endpoints.comment.root}`
);
} catch (error) {
this.handleError(error);
}
}
/////////////////////
// PRIVATE METHODS //
/////////////////////
private handleError(error): void {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.log(error.response.data);
console.log(error.response.status);
console.log(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(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Error', error.message);
}
console.log(error.config);
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { CommentService } from './comment.service';
describe('CommentService', () => {
let service: CommentService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(CommentService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Injectable } from '@angular/core';
import { Poll } from '../models/poll.model';
import { ApiService } from './api.service';
@Injectable({
providedIn: 'root',
})
export class CommentService {
constructor(private apiService: ApiService) {}
public saveComment(poll: Poll, comment: string): void {
this.apiService.saveComment(poll, comment);
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { DateUtilsService } from './date-utils.service';
describe('DateUtilsService', () => {
let service: DateUtilsService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(DateUtilsService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,50 @@
import { Injectable } from '@angular/core';
import { addDays, differenceInDays, format } from 'date-fns';
@Injectable({
providedIn: 'root',
})
export class DateUtilsService {
public static addDaysToDate(days: number, date: Date): Date {
return addDays(date, days);
}
public static diffInDays(dateLeft: Date, dateRight: Date): number {
return differenceInDays(dateLeft, dateRight);
}
public static formatDate(date): string {
return format(date, 'yyyy-MM-dd');
}
public static orderDates(): Date[] {
// TODO: to implement
const datesOrdered: Date[] = [];
return datesOrdered;
}
public static getDatesInRange(d1: Date, d2: Date, interval: number): Date[] {
// TODO: refacto this
d1 = new Date(d1);
d2 = new Date(d2);
const dates = [];
while (+d1 < +d2) {
dates.push({
literal: this.formateDate(d1),
date_object: d1,
});
d1.setDate(d1.getDate() + interval);
}
return [...dates];
}
public static getDoubleDigits(str: string): string {
// TODO: ça sert à quoi ça ?
// Parce que ajouter 2 caractère à une string et ensuite slicer à partir du 2ème caractère, euh…
return ('00' + str).slice(-2);
}
private isInChronologicalOrder(date1: Date, date2: Date): boolean {
return date1 < date2;
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { PollUtilsService } from './poll-utils.service';
describe('PollUtilsService', () => {
let service: PollUtilsService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(PollUtilsService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import { v4 as uuidv4 } from 'uuid';
@Injectable({
providedIn: 'root',
})
export class PollUtilsService {
public makeUuid(): string {
// TODO: how to be sure the uuid generated in front is available in backend ?
// It could be a better way to generate uuids in backend.
return uuidv4();
}
/**
* make a uniq slug for the current poll creation
* @param str
*/
public makeSlug(config: PollConfig): string {
let str = '';
str =
config.creationDate.getFullYear() +
'_' +
(config.creationDate.getMonth() + 1) +
'_' +
config.creationDate.getDate() +
'_' +
config.myName +
'_' +
config.title;
str = str.replace(/^\s+|\s+$/g, ''); // trim
str = str.toLowerCase();
// remove accents, swap ñ for n, etc
const from = 'àáäâèéëêìíïîòóöôùúüûñç·/_,:;';
const to = 'aaaaeeeeiiiioooouuuunc------';
for (let i = 0, l = from.length; i < l; i++) {
str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
}
str = str
.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
.replace(/\s+/g, '-') // collapse whitespace and replace by -
.replace(/-+/g, '-'); // collapse dashes
return str;
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { PollService } from './poll.service';
describe('PollService', () => {
let service: PollService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(PollService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,42 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { Poll } from '../models/poll.model';
import { ApiService } from './api.service';
@Injectable({
providedIn: 'root',
})
export class PollService {
private _poll: BehaviorSubject<Poll | undefined> = new BehaviorSubject<Poll | undefined>(undefined);
constructor(private apiService: ApiService) {}
public get poll(): Observable<Poll | undefined> {
return this._poll.asObservable();
}
public updateCurrentPoll(poll: Poll): void {
this._poll.next(poll);
}
public savePoll(poll: Poll): void {
this.apiService.savePoll(poll);
}
public saveVote(poll: Poll): void {
this.apiService.saveVote(poll);
}
public saveComment(poll: Poll, comment: string): void {
this.apiService.saveComment(poll, comment);
}
public deletePollVotes(poll: Poll): void {
this.apiService.deletePollVotes(poll);
}
public deletePollComments(poll: Poll): void {
this.apiService.deletePollComments(poll);
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { ThemeService } from './theme.service';
describe('ThemeService', () => {
let service: ThemeService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(ThemeService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,8 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class ThemeService {
constructor() {}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { UserService } from './user.service';
describe('UserService', () => {
let service: UserService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(UserService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,19 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { User } from '../models/user.model';
@Injectable({
providedIn: 'root',
})
export class UserService {
private _user: BehaviorSubject<User | undefined> = new BehaviorSubject<User | undefined>(undefined);
public get user(): Observable<User | undefined> {
return this._user.asObservable();
}
public updateUser(user: User): void {
this._user.next(user);
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { VoteService } from './vote.service';
describe('VoteService', () => {
let service: VoteService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(VoteService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,17 @@
import { Injectable } from '@angular/core';
import { ApiService } from './api.service';
@Injectable({
providedIn: 'root',
})
export class VoteService {
constructor(private apiService: ApiService) {}
public saveVote(vote: any): void {
this.apiService.saveVote(vote);
}
public updateVote(vote: any): void {
this.apiService.updateVote(vote);
}
}

View File

@ -0,0 +1,8 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
declarations: [],
imports: [CommonModule],
})
export class SharedModule {}

View File

@ -1,5 +1,18 @@
export const environment = { export const environment = {
production: true, production: true,
baseHref: 'https://framadate-api.cipherbliss.com/', api: {
baseApiHref: 'https://framadate-api.cipherbliss.com/api/v1', baseHref: 'https://framadate-api.cipherbliss.com/api/v1',
endpoints: {
isSlugAvailable: '/check-slug-is-uniq',
},
},
poll: {
defaultConfig: {
expiracyInDays: 60,
expiracyAfterLastModificationInDays: 180,
},
},
localStorage: {
key: 'framadate-storage',
},
}; };

View File

@ -1,18 +1,47 @@
// This file can be replaced during build by using the `fileReplacements` array. // This file can be replaced during build by using the `fileReplacements` array.
// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`. // The list of file replacements can be found in `angular.json`.
let baseURL = 'http://localhost:8000/';
const baseURLProd = 'https://framadate.org/'; const backendApiUrlsInDev = {
const baseURLDemo = 'https://framadate-api.cipherbliss.com/'; local: 'http://localhost:8000/',
const apiVersion = 1; remote: 'https://framadate-api.cipherbliss.com/',
const testOnDemo = 0; };
if (testOnDemo) {
baseURL = baseURLDemo;
}
export const environment = { export const environment = {
production: false, production: false,
baseHref: baseURL, api: {
baseApiHref: baseURL + 'api/v' + apiVersion, baseHref: backendApiUrlsInDev.local + 'api/v1',
endpoints: {
slug: {
root: '/slug',
isAvailable: '/check-slug-is-uniq',
},
poll: {
root: '/poll',
byEmail: {
sendEmail: '/send-polls-to-user',
retrieve: '/my-polls',
},
byUrl: '/poll/slug',
byId: '/poll/slug',
},
vote: {
root: '/vote',
},
comment: {
root: '/comment',
},
},
},
poll: {
defaultConfig: {
expiracyInDays: 60,
expiracyAfterLastModificationInDays: 180,
},
},
localStorage: {
key: 'framadate-storage',
},
}; };
/* /*
@ -22,4 +51,4 @@ export const environment = {
* This import should be commented out in production mode because it will have a negative impact * This import should be commented out in production mode because it will have a negative impact
* on performance if an error is thrown. * on performance if an error is thrown.
*/ */
// import 'zone.js/dist/zone-error'; // Included with Angular CLI. import 'zone.js/dist/zone-error'; // Included with Angular CLI.

View File

@ -6,4 +6,4 @@
}, },
"files": ["src/test.ts", "src/polyfills.ts"], "files": ["src/test.ts", "src/polyfills.ts"],
"include": ["src/**/*.spec.ts", "src/**/*.d.ts"] "include": ["src/**/*.spec.ts", "src/**/*.d.ts"]
} }

711
yarn.lock

File diff suppressed because it is too large Load Diff