diff --git a/back-end/src/handlers/contests.ts b/back-end/src/handlers/contests.ts index bd5f969..71d2bd4 100644 --- a/back-end/src/handlers/contests.ts +++ b/back-end/src/handlers/contests.ts @@ -12,6 +12,7 @@ import { User } from '../models/user.model'; /// const PROJECT = process.env.PROJECT; +const STAGE = process.env.STAGE; const DDB_TABLES = { users: process.env.DDB_TABLE_users, contests: process.env.DDB_TABLE_contests }; const ddb = new DynamoDB(); @@ -27,6 +28,7 @@ class ContestsRC extends ResourceController { constructor(event: any, callback: any) { super(event, callback, { resourceId: 'contestId' }); + if (STAGE === 'prod') this.silentLambdaLogs(); // to make the vote anonymous } protected async checkAuthBeforeRequest(): Promise { @@ -115,10 +117,11 @@ class ContestsRC extends ResourceController { await ddb.transactWrites([{ Update: markUserContestVoted }, { Update: addUserVoteToContest }]); } private async publishResults(): Promise { + if (!this.user.permissions.canManageContents) throw new HandledError('Unauthorized'); + if (this.contest.publishedResults) throw new HandledError('Already public'); - if (!this.contest.voteEndsAt || new Date().toISOString() <= this.contest.voteEndsAt) - throw new HandledError('Vote is not done'); + if (!this.contest.isVoteEnded()) throw new HandledError('Vote is not done'); await ddb.update({ TableName: DDB_TABLES.contests, @@ -155,7 +158,7 @@ class ContestsRC extends ResourceController { if (!this.user.permissions.canManageContents) { contests = contests.filter(c => c.enabled); contests.forEach(contest => { - if (contest.publishedResults) delete contest.results; + if (!contest.publishedResults) delete contest.results; }); } diff --git a/back-end/src/models/contest.model.ts b/back-end/src/models/contest.model.ts index 543db80..5921cb3 100644 --- a/back-end/src/models/contest.model.ts +++ b/back-end/src/models/contest.model.ts @@ -1,7 +1,7 @@ import { Resource, epochISOString } from 'idea-toolbox'; /** - * A contest to which people can vote in. + * A contest to which users can vote in. */ export class Contest extends Resource { /** @@ -13,11 +13,11 @@ export class Contest extends Resource { */ createdAt: epochISOString; /** - * Whether the contest is enabled and therefore shown in the menu. + * Whether the contest is enabled and therefore shown in the menu to everyone. */ enabled: boolean; /** - * If set, the vote is active (users can vote) and ends at the configured timestamp. + * If set, the vote is active (users can vote), and it ends at the configured timestamp. */ voteEndsAt?: epochISOString; /** @@ -38,7 +38,7 @@ export class Contest extends Resource { candidates: ContestCandidate[]; /** * The count of votes for each of the sorted candidates. - * Note: the order of the candidates list must not change after a vote is open. + * Note: the order of the candidates list must not change after the vote is open. * This attribute is not accessible to non-admin users until `publishedResults` is true. */ results?: number[]; @@ -66,9 +66,10 @@ export class Contest extends Resource { safeLoad(newData: any, safeData: any): void { super.safeLoad(newData, safeData); this.contestId = safeData.contestId; - this.results = safeData.results; + this.createdAt = safeData.createdAt; if (safeData.isVoteStarted()) { this.candidates = safeData.candidates; + this.results = safeData.results; } } @@ -110,7 +111,7 @@ export class ContestCandidate extends Resource { * The country of the candidate. * This is particularly important beacuse, if set, users can't vote for candidates of their own countries. */ - country: string; + country: string | null; load(x: any): void { super.load(x); diff --git a/front-end/src/app/tabs/contests/contest.page.ts b/front-end/src/app/tabs/contests/contest.page.ts index 1f37233..965cf20 100644 --- a/front-end/src/app/tabs/contests/contest.page.ts +++ b/front-end/src/app/tabs/contests/contest.page.ts @@ -49,11 +49,11 @@ import { Contest } from '@models/contest.model'; {{ contest.name }} @if(contest.isVoteStarted()) { @if(contest.isVoteEnded()) { @if(contest.publishedResults) { - {{ 'CONTESTS.RESULTS' | translate }} + {{ 'CONTESTS.RESULTS' | translate }} } @else { {{ 'CONTESTS.VOTE_ENDED' | translate }} } } @else { - + {{ 'CONTESTS.VOTE_NOW_UNTIL' | translate : { deadline: (contest.voteEndsAt | dateLocale : 'short') } }} } } @else { @@ -85,12 +85,12 @@ import { Contest } from '@models/contest.model'; - } @if(contest.publishedResults) { @if( isCandidateWinnerByIndex($index)) { - + } @if(contest.publishedResults) { @if(isCandidateWinnerByIndex($index)) { + } {{ contest.results[$index] ?? 0 }} {{ 'CONTESTS.VOTES' | translate | lowercase }} @@ -161,7 +161,7 @@ export class ContestPage implements OnInit { } catch (err) { this._message.error('COMMON.NOT_FOUND'); } finally { - await this._loading.hide(); + this._loading.hide(); } } @@ -208,7 +208,7 @@ export class ContestPage implements OnInit { } catch (err) { this._message.error('COMMON.OPERATION_FAILED'); } finally { - await this._loading.hide(); + this._loading.hide(); } }; diff --git a/front-end/src/app/tabs/contests/contests.page.ts b/front-end/src/app/tabs/contests/contests.page.ts index 8f00b1d..a8ef07e 100644 --- a/front-end/src/app/tabs/contests/contests.page.ts +++ b/front-end/src/app/tabs/contests/contests.page.ts @@ -27,11 +27,11 @@ import { Contest } from '@models/contest.model'; {{ contest.name }} @if(contest.isVoteStarted()) { @if(contest.isVoteEnded()) { @if(contest.publishedResults) { - {{ 'CONTESTS.RESULTS' | translate }} + {{ 'CONTESTS.RESULTS' | translate }} } @else { {{ 'CONTESTS.VOTE_ENDED' | translate }} } } @else { - {{ 'CONTESTS.VOTE_NOW' | translate }} + {{ 'CONTESTS.VOTE_NOW' | translate }} } } } @empty { @if(contests) { diff --git a/front-end/src/app/tabs/contests/manageContest.component.ts b/front-end/src/app/tabs/contests/manageContest.component.ts index 9fbea0b..e4e6d0d 100644 --- a/front-end/src/app/tabs/contests/manageContest.component.ts +++ b/front-end/src/app/tabs/contests/manageContest.component.ts @@ -108,7 +108,7 @@ import { Contest, ContestCandidate } from '@models/contest.model'; {{ 'CONTESTS.CANDIDATE_URL' | translate }} - + + @for(country of _app.configurations.sectionCountries; track $index) { {{ country }} } @@ -154,7 +155,7 @@ import { Contest, ContestCandidate } from '@models/contest.model'; } {{ contest.results[$index] ?? 0 }} {{ 'CONTESTS.VOTES' | translate | lowercase }} @@ -281,7 +282,8 @@ export class ManageContestComponent implements OnInit { this.contest.candidates.push(new ContestCandidate()); } removeCandidate(candidate: ContestCandidate): void { - this.contest.candidates.splice(this.contest.candidates.indexOf(candidate), 1); + const candidateIndex = this.contest.candidates.indexOf(candidate); + if (candidateIndex !== -1) this.contest.candidates.splice(candidateIndex, 1); } isCandidateWinnerByIndex(candidateIndex: number): boolean { @@ -300,7 +302,7 @@ export class ManageContestComponent implements OnInit { } catch (err) { this._message.error('COMMON.OPERATION_FAILED'); } finally { - await this._loading.hide(); + this._loading.hide(); } };