Skip to content

Commit

Permalink
Merge pull request #262 from softwaremagico/260-download-all-generate…
Browse files Browse the repository at this point in the history
…d-info-in-a-zip-file

260 download all generated info in a zip file
  • Loading branch information
softwaremagico authored Nov 15, 2023
2 parents 4f8a4a4 + 63a4f4f commit 7f74f54
Show file tree
Hide file tree
Showing 19 changed files with 245 additions and 20 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
[![GitHub commit activity](https://img.shields.io/github/commit-activity/y/softwaremagico/KendoTournamentManager)](https://github.com/softwaremagico/KendoTournamentManager)
[![GitHub last commit](https://img.shields.io/github/last-commit/softwaremagico/KendoTournamentManager)](https://github.com/softwaremagico/KendoTournamentManager)
[![CircleCI](https://circleci.com/gh/softwaremagico/KendoTournamentManager.svg?style=shield)](https://circleci.com/gh/softwaremagico/KendoTournamentManager)
[![Time](https://img.shields.io/badge/development-516.5h-blueviolet.svg)]()
[![Time](https://img.shields.io/badge/development-518h-blueviolet.svg)]()

[![Powered by](https://img.shields.io/badge/powered%20by%20java-orange.svg?logo=OpenJDK&logoColor=white)]()
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=kendo-tournament-backend&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=kendo-tournament-backend)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.softwaremagico.kt.html.controller;

public class ZipContent {
private final String name;
private final String extension;

private final byte[] content;

ZipContent(String name, String extension, byte[] content) {
this.name = name;
this.extension = extension;
this.content = content;
}

public String getName() {
return name;
}

public String getExtension() {
return extension;
}

public byte[] getContent() {
return content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.softwaremagico.kt.html.controller;

import com.softwaremagico.kt.core.controller.RankingController;
import com.softwaremagico.kt.core.controller.models.ScoreOfCompetitorDTO;
import com.softwaremagico.kt.core.controller.models.ScoreOfTeamDTO;
import com.softwaremagico.kt.core.controller.models.TournamentDTO;
import com.softwaremagico.kt.logger.KendoTournamentLogger;
import com.softwaremagico.kt.pdf.EmptyPdfBodyException;
import com.softwaremagico.kt.pdf.InvalidXmlElementException;
import com.softwaremagico.kt.pdf.controller.PdfController;
import org.springframework.stereotype.Controller;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Controller
public class ZipController {

private final PdfController pdfController;
private final HtmlController htmlController;

private final RankingController rankingController;

public ZipController(PdfController pdfController, HtmlController htmlController, RankingController rankingController) {
this.pdfController = pdfController;
this.htmlController = htmlController;
this.rankingController = rankingController;
}


public byte[] createZipData(Locale locale, TournamentDTO tournament) throws IOException {
final List<ZipContent> content = new ArrayList<>();
//Role List
try {
content.add(new ZipContent("Role List - " + tournament.getName(), "pdf",
pdfController.generateClubList(locale, tournament).generate()));
} catch (EmptyPdfBodyException | InvalidXmlElementException e) {
KendoTournamentLogger.errorMessage(this.getClass(), e);
}
//Team List
try {
content.add(new ZipContent("Team List - " + tournament.getName(), "pdf",
pdfController.generateTeamList(tournament).generate()));
} catch (EmptyPdfBodyException | InvalidXmlElementException e) {
KendoTournamentLogger.errorMessage(this.getClass(), e);
}
//Fight List
try {
content.add(new ZipContent("Fight List - " + tournament.getName(), "pdf",
pdfController.generateFightsSummaryList(locale, tournament).generate()));
} catch (EmptyPdfBodyException | InvalidXmlElementException e) {
KendoTournamentLogger.errorMessage(this.getClass(), e);
}
//Team Ranking
try {
final List<ScoreOfTeamDTO> scores = rankingController.getTeamsScoreRanking(tournament);
content.add(new ZipContent("Team Ranking - " + tournament.getName(), "pdf",
pdfController.generateTeamsScoreList(locale, tournament, scores).generate()));
} catch (EmptyPdfBodyException | InvalidXmlElementException e) {
KendoTournamentLogger.errorMessage(this.getClass(), e);
}
//Competitors Ranking
try {
final List<ScoreOfCompetitorDTO> scores = rankingController.getCompetitorsScoreRanking(tournament);
content.add(new ZipContent("Competitors Ranking - " + tournament.getName(), "pdf",
pdfController.generateCompetitorsScoreList(locale, tournament, scores).generate()));
} catch (EmptyPdfBodyException | InvalidXmlElementException e) {
KendoTournamentLogger.errorMessage(this.getClass(), e);
}
//BlogCode
content.add(new ZipContent("Wordpress code - " + tournament.getName(), "txt",
htmlController.generateBlogCode(locale, tournament).getWordpressFormat().getBytes(StandardCharsets.UTF_8)));
return createZipData(content);
}


public byte[] createZipData(List<ZipContent> content) throws IOException {
if (content == null || content.isEmpty()) {
return null;
}
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
final ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream);
for (ZipContent zipContent : content) {
try {
final ZipEntry entry = new ZipEntry(zipContent.getName() + "." + zipContent.getExtension());
zipOutputStream.putNextEntry(entry);
final byte[] data = zipContent.getContent();
zipOutputStream.write(data, 0, data.length);
zipOutputStream.closeEntry();
} catch (IOException e) {
KendoTournamentLogger.errorMessage(this.getClass(), e);
}
}
zipOutputStream.close();
return byteArrayOutputStream.toByteArray();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.softwaremagico.kt.core.controller.models.TournamentDTO;
import com.softwaremagico.kt.core.score.CompetitorRanking;
import com.softwaremagico.kt.html.controller.HtmlController;
import com.softwaremagico.kt.html.controller.ZipController;
import com.softwaremagico.kt.logger.RestServerLogger;
import com.softwaremagico.kt.pdf.EmptyPdfBodyException;
import com.softwaremagico.kt.pdf.InvalidXmlElementException;
Expand All @@ -46,13 +47,15 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -75,14 +78,18 @@ public class RankingServices {

private final GroupController groupController;

private final ZipController zipController;

public RankingServices(RankingController rankingController, PdfController pdfController, TournamentController tournamentController,
ParticipantController participantController, HtmlController htmlController, GroupController groupController) {
ParticipantController participantController, HtmlController htmlController, GroupController groupController,
ZipController zipController) {
this.rankingController = rankingController;
this.tournamentController = tournamentController;
this.pdfController = pdfController;
this.participantController = participantController;
this.htmlController = htmlController;
this.groupController = groupController;
this.zipController = zipController;
}

@PreAuthorize("hasAnyRole('ROLE_VIEWER', 'ROLE_EDITOR', 'ROLE_ADMIN')")
Expand Down Expand Up @@ -224,4 +231,17 @@ public byte[] getTournamentsSummaryAsHtml(@Parameter(description = "Id of an exi
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString());
return htmlController.generateBlogCode(locale, tournament).getWordpressFormat().getBytes(StandardCharsets.UTF_8);
}

@PreAuthorize("hasAnyRole('ROLE_VIEWER', 'ROLE_EDITOR', 'ROLE_ADMIN')")
@Operation(summary = "Download all files as a zip", security = @SecurityRequirement(name = "bearerAuth"))
@GetMapping(value = "/tournament/{tournamentId}/zip")
public byte[] startByEmail(@Parameter(description = "Id of an existing tournament", required = true)
@PathVariable("tournamentId") Integer tournamentId, Locale locale,
Authentication authentication, HttpServletResponse response, HttpServletRequest request) throws IOException {
final TournamentDTO tournament = tournamentController.get(tournamentId);
final ContentDisposition contentDisposition = ContentDisposition.builder("attachment")
.filename(tournament.getName() + ".zip").build();
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString());
return zipController.createZipData(locale, tournament);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import java.util.Comparator;
import java.util.List;
import java.util.Locale;

@RestController
Expand All @@ -72,6 +74,15 @@ public TournamentServices(TournamentController tournamentController, PdfControll
this.pdfController = pdfController;
}

@PreAuthorize("hasAnyRole('ROLE_VIEWER', 'ROLE_EDITOR', 'ROLE_ADMIN')")
@Operation(summary = "Gets all", security = @SecurityRequirement(name = "bearerAuth"))
@GetMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE)
public List<TournamentDTO> getAll(HttpServletRequest request) {
final List<TournamentDTO> tournaments = super.getAll(request);
tournaments.sort(Comparator.comparing(TournamentDTO::getCreatedAt).reversed());
return tournaments;
}

@PreAuthorize("hasAnyRole('ROLE_EDITOR', 'ROLE_ADMIN')")
@Operation(summary = "Creates a tournament with some basic information.", security = @SecurityRequirement(name = "bearerAuth"))
@ResponseStatus(HttpStatus.CREATED)
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/app/components/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ export class IconModule {
.addSvgIcon("brackets", this.setPath(`${this.path}/brackets.svg`))
.addSvgIcon("unfinish", this.setPath(`${this.path}/unfinish.svg`))
.addSvgIcon("clone", this.setPath(`${this.path}/clone.svg`))
.addSvgIcon("oneWinner", this.setPath(`${this.path}/oneWinner.svg`))
.addSvgIcon("twoWinners", this.setPath(`${this.path}/twoWinners.svg`));
.addSvgIcon("one-winner", this.setPath(`${this.path}/one-winner.svg`))
.addSvgIcon("two-winners", this.setPath(`${this.path}/two-winners.svg`))
.addSvgIcon("zip-file", this.setPath(`${this.path}/zip-file.svg`));
}

private setPath(url: string): SafeResourceUrl {
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/app/services/ranking.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,21 @@ export class RankingService {
);
}

getAllListAsZip(tournamentId: number): Observable<Blob> {
this.systemOverloadService.isBusy.next(true);
const url: string = `${this.baseUrl}` + '/tournament/' + tournamentId + '/zip';
return this.http.get<Blob>(url, {
responseType: 'blob' as 'json', observe: 'body', headers: new HttpHeaders({
'Content-Type': 'application/json'
})
}).pipe(
tap({
next: () => this.loggerService.info(`getting tournament lists as zip`),
error: () => this.systemOverloadService.isBusy.next(false),
complete: () => this.systemOverloadService.isBusy.next(false),
}),
catchError(this.messageService.handleError<Blob>(`getting tournament lists as zip`))
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
[matTooltipShowDelay]="500"
color="primary" mat-button
matTooltip="{{'winnerNumber' | translate}}">
<mat-icon class="kendo-icon" svgIcon="oneWinner"></mat-icon>
<mat-icon class="kendo-icon" svgIcon="one-winner"></mat-icon>
</button>
<button (click)="deleteGroup()" *ngIf="(RbacActivity.DELETE_GROUP | rbac : this.rbacService.activities)"
[disabled]="groupsDisabled || (tournament && tournament.locked)"
Expand All @@ -38,7 +38,7 @@
[matTooltipShowDelay]="500"
color="primary" mat-button
matTooltip="{{'winnerNumber' | translate}}">
<mat-icon class="kendo-icon" svgIcon="twoWinners"></mat-icon>
<mat-icon class="kendo-icon" svgIcon="two-winners"></mat-icon>
</button>
<mat-divider [vertical]="true"></mat-divider>
<button (click)="askToRemoveAllTeams()"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@
matTooltip="{{'titleStatistics' | translate}}">
<mat-icon>bar_chart</mat-icon>
</button>
<button (click)="downloadZip()" *ngIf="(RbacActivity.READ_ALL_RANKINGS | rbac : this.rbacService.activities)"
[disabled]="!basicTableData.selectedElement"
[matTooltipShowDelay]="500"
color="primary" mat-button
matTooltip="{{'tournamentListAsZip' | translate}}">
<mat-icon class="kendo-icon" svgIcon="zip-file"></mat-icon>
</button>
<button (click)="downloadBlogCode()" *ngIf="(RbacActivity.READ_ALL_RANKINGS | rbac : this.rbacService.activities)"
[disabled]="!basicTableData.selectedElement" [matTooltipShowDelay]="500"
color="primary" mat-button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export class TournamentListComponent extends RbacBasedComponent implements OnIni
}
}

openDialog(title: string, action: Action, tournament: Tournament) {
openDialog(title: string, action: Action, tournament: Tournament): void {
const dialogRef = this.dialog.open(TournamentDialogBoxComponent, {
width: '600px',
data: {
Expand All @@ -111,7 +111,7 @@ export class TournamentListComponent extends RbacBasedComponent implements OnIni
});
}

addRowData(tournament: Tournament) {
addRowData(tournament: Tournament): void {
this.tournamentService.add(tournament).subscribe(_tournament => {
this.basicTableData.dataSource.data.push(_tournament);
this.basicTableData.dataSource._updateChangeSubscription();
Expand Down Expand Up @@ -164,9 +164,9 @@ export class TournamentListComponent extends RbacBasedComponent implements OnIni
}
}

downloadBlogCode() {
downloadBlogCode(): void {
if (this.basicTableData.selectedElement?.id) {
this.rankingService.getTournamentSummaryAsHtml(this.basicTableData.selectedElement.id).subscribe((html: Blob) => {
this.rankingService.getTournamentSummaryAsHtml(this.basicTableData.selectedElement.id).subscribe((html: Blob): void => {
const blob: Blob = new Blob([html], {type: 'txt/plain'});
const downloadURL: string = window.URL.createObjectURL(blob);

Expand All @@ -178,7 +178,7 @@ export class TournamentListComponent extends RbacBasedComponent implements OnIni
}
}

downloadAccreditations() {
downloadAccreditations(): void {
if (this.basicTableData.selectedElement) {
const dialogRef: MatDialogRef<RoleSelectorDialogBoxComponent> = this.dialog.open(RoleSelectorDialogBoxComponent, {
data: {
Expand Down Expand Up @@ -208,7 +208,7 @@ export class TournamentListComponent extends RbacBasedComponent implements OnIni
}
}

downloadDiplomas() {
downloadDiplomas(): void {
if (this.basicTableData.selectedElement) {
const dialogRef: MatDialogRef<RoleSelectorDialogBoxComponent> = this.dialog.open(RoleSelectorDialogBoxComponent, {
data: {
Expand Down Expand Up @@ -258,7 +258,7 @@ export class TournamentListComponent extends RbacBasedComponent implements OnIni
return (argument as Tournament).locked;
}

openStatistics() {
openStatistics(): void {
if (this.basicTableData.selectedElement) {
this.userSessionService.setSelectedTournament(this.basicTableData.selectedElement.id + "");
this.router.navigate(['/tournaments/statistics'], {state: {tournamentId: this.basicTableData.selectedElement.id}});
Expand All @@ -280,7 +280,7 @@ export class TournamentListComponent extends RbacBasedComponent implements OnIni
}
}

cloneElement() {
cloneElement(): void {
const tournamentId: number = this.basicTableData.selectedElement?.id!;
this.basicTableData.selectedElement = undefined;
this.tournamentService.clone(tournamentId).subscribe((_tournament: Tournament): void => {
Expand All @@ -290,4 +290,18 @@ export class TournamentListComponent extends RbacBasedComponent implements OnIni
this.messageService.infoMessage('infoTournamentStored');
});
}

downloadZip(): void {
if (this.basicTableData.selectedElement?.id) {
this.rankingService.getAllListAsZip(this.basicTableData.selectedElement.id).subscribe((html: Blob): void => {
const blob: Blob = new Blob([html], {type: 'application/zip'});
const downloadURL: string = window.URL.createObjectURL(blob);

const anchor = document.createElement("a");
anchor.download = this.basicTableData.selectedElement!.name + ".zip";
anchor.href = downloadURL;
anchor.click();
});
}
}
}
3 changes: 2 additions & 1 deletion frontend/src/assets/i18n/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,8 @@
"leagueFifo": "El primer equip serà el primer en sortir en generar el següent partit.",
"leagueFifoHint": "En generar els partits, al primer se selecciona l'equip que és reemplaçat. Si s'activa, el primer equip serà substituït pel tercer. Si es desactiva, el segon serà reemplaçat pel tercer. Per exemple, per als equips 1, 2 i 3, si se selecciona els combats seran: '1-2, 3-2, 3-1'. Però si no, serà: '1-2, 1-3, 2-3.",
"tournamentProperties": "Propietats del torneig",
"tournamentCloneWarning": "Duplicaràs el torneig. Estàs segur de continuar?"
"tournamentCloneWarning": "Duplicaràs el torneig. Estàs segur de continuar?",
"tournamentListAsZip": "Baixar totes les llistes PDF en un fitxer zip."
}


3 changes: 2 additions & 1 deletion frontend/src/assets/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,8 @@
"leagueFifo": "Bei der Generierung des nächsten Spiels ist die erste Mannschaft die erste, die ausscheidet.",
"leagueFifoHint": "Beim Generieren der Spiele wird im ersten Spiel die Mannschaft ausgewählt, die ersetzt wird. Bei Aktivierung wird die erste Mannschaft durch die dritte ersetzt. Bei Deaktivierung wird die zweite durch die dritte ersetzt. Wenn diese Option ausgewählt ist, lauten die Spiele für die Mannschaften 1, 2 und 3 beispielsweise wie folgt: '1-2, 3-2, 3-1'. Aber wenn nicht, lautet es: '1-2, 1-3, 2-3.'",
"tournamentProperties": "Turniereigenschaften",
"tournamentCloneWarning": "Sie werden das Turnier verdoppeln. Sind Sie sicher, dass Sie fortfahren?"
"tournamentCloneWarning": "Sie werden das Turnier verdoppeln. Sind Sie sicher, dass Sie fortfahren?",
"tournamentListAsZip": "Laden Sie alle PDF-Listen in einer ZIP-Datei herunter."
}


Loading

0 comments on commit 7f74f54

Please sign in to comment.