117 Commits

Author SHA1 Message Date
dcaa79245c Merge pull request 'dev' (#124) from dev into master
Reviewed-on: #124
2026-08-06 14:17:49 +00:00
2772e44456 feat: auto fill certificate fields on add licence
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 11m30s
2026-08-06 16:16:47 +02:00
8f0e3c0738 feat: show old member by default 2026-08-06 15:14:30 +02:00
81f6169d41 feat: add licence detail to add log 2026-08-06 15:03:03 +02:00
931c5076b7 feat: update libs 2026-08-06 15:02:44 +02:00
a328e71035 feat: add licence state to membre find result 2026-08-05 16:45:18 +02:00
72f8a27272 Merge pull request 'fix: cache refresh v2' (#123) from dev into master
Reviewed-on: #123
2026-03-23 21:12:00 +00:00
d3805ea0e7 fix: cache refresh v2
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 7m13s
2026-03-23 22:11:42 +01:00
a1e5006e95 Merge pull request 'fix: cache refresh' (#122) from dev into master
Reviewed-on: #122
2026-03-23 15:31:19 +00:00
2d883ca1da fix: cache refresh
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 7m10s
2026-03-23 16:29:11 +01:00
d357328e48 Merge pull request 'fix: add log in getAllClubArray' (#121) from dev into master
Reviewed-on: #121
2026-03-23 15:06:51 +00:00
2501b998fa fix: add log in getAllClubArray
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 7m8s
2026-03-23 16:05:09 +01:00
f9e4b83ad3 Merge pull request 'hot fix: remove save score button 2' (#120) from dev into master
Reviewed-on: #120
2026-03-21 11:09:17 +00:00
a7c89dbd73 hot fix: remove save score button 2
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m55s
2026-03-21 12:08:45 +01:00
ae087084e9 Merge pull request 'hot fix: remove save score button' (#119) from dev into master
Reviewed-on: #119
2026-03-21 11:00:12 +00:00
b2ee06d3b9 hot fix: remove save score button
Some checks failed
Deploy Production Server / if_merged (pull_request) Failing after 44s
2026-03-21 11:59:37 +01:00
5afef2bcab Merge pull request 'fix: change obs lice name location' (#118) from dev into master
Reviewed-on: #118
2026-03-20 15:40:37 +00:00
3ed44bc044 fix: change obs lice name location
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m57s
2026-03-20 16:40:16 +01:00
55d39e02d2 Merge pull request 'fix: clear pub aff when no cat select' (#117) from dev into master
Reviewed-on: #117
2026-03-20 15:11:17 +00:00
d091503f19 fix: clear pub aff when no cat select
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 7m3s
2026-03-20 16:10:59 +01:00
d5c1273e18 Merge pull request 'fix: change methode for move upload file' (#116) from dev into master
Reviewed-on: #116
2026-03-18 12:07:58 +00:00
ea17989a82 fix: change methode for move upload file
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m59s
2026-03-18 13:07:39 +01:00
eda96d5ed2 Merge pull request 'fix: add more log for file copy' (#115) from dev into master
Reviewed-on: #115
2026-03-18 10:07:00 +00:00
e8555f7992 fix: add more log for file copy
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 7m0s
2026-03-18 11:06:40 +01:00
3a4e2f160f Merge pull request 'fix: add log for file copy' (#114) from dev into master
Reviewed-on: #114
2026-03-18 09:48:37 +00:00
fdd42c6a63 fix: add log for file copy
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 7m1s
2026-03-18 10:48:09 +01:00
2b84854ef2 Merge pull request 'fix: obs support wss' (#113) from dev into master
Reviewed-on: #113
2026-03-17 22:16:29 +00:00
ddf530ce4c fix: obs support wss
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 7m8s
2026-03-17 23:16:06 +01:00
fc3bea7f76 Merge pull request 'fix: language detection' (#112) from dev into master
Reviewed-on: #112
2026-03-16 11:19:50 +00:00
286911cf89 fix: language detection
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 7m2s
2026-03-16 12:19:30 +01:00
e67e01640a Merge pull request 'fix: club guest mixing in result' (#111) from dev into master
Reviewed-on: #111
2026-03-10 17:47:33 +00:00
4bfa6e845d fix: club guest mixing in result
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 7m3s
2026-03-10 13:59:35 +01:00
e870ffdda0 Merge pull request 'fix: eslint version' (#110) from dev into master
Reviewed-on: #110
2026-03-09 12:19:27 +00:00
806ead5b98 fix: eslint version
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 7m29s
2026-03-09 13:19:12 +01:00
378a17ba75 Merge pull request 'dev' (#109) from dev into master
Reviewed-on: #109
2026-03-09 12:10:46 +00:00
ee0a7d87e9 feat: cm Add audio encodeur generator
Some checks failed
Deploy Production Server / if_merged (pull_request) Failing after 34s
2026-03-09 13:07:51 +01:00
812d873d5d fix: Match reorder refresh 2026-03-08 21:58:57 +01:00
d6d9f86254 feat: PDF for classement club 2026-02-27 11:10:31 +01:00
a22225ee7a feat: classement club 2026-02-23 12:01:36 +01:00
3cb12826d0 Merge pull request 'dev' (#108) from dev into master
Reviewed-on: #108
2026-02-20 16:59:40 +00:00
2f390b03e2 feat: add podium PDF
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 7m28s
2026-02-19 15:55:46 +01:00
ed5d73c25f feat: improve generation PDF for categories 2026-02-17 21:35:29 +01:00
0a368454c4 fix: duplicate id liceInput1 2026-02-16 22:49:49 +01:00
752f03cba5 feat: PDF for categories 2026-02-16 22:43:14 +01:00
d857fce71f feat: support decimal on weight 2026-02-13 21:42:48 +01:00
2fd09af0ea feat: auto categories full competition 2026-02-13 21:33:19 +01:00
d43cdc1a4e feat: auto categories 2026-02-11 14:08:13 +01:00
8663aa61cf feat: add silver cup png to cm 2026-02-06 18:02:11 +01:00
758e02dc5b feat: remove ^/api$ path log 2026-02-06 17:26:33 +01:00
72b248d09d Merge pull request 'dev' (#107) from dev into master
Reviewed-on: #107
2026-02-06 16:18:39 +00:00
31315c951a feat: auto poule
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 7m28s
2026-02-06 17:17:44 +01:00
ab4d57985c fix: add classement array in competition.js 2026-02-06 13:23:09 +01:00
2e846bf801 feat: use preset in cmt 2026-02-06 13:15:18 +01:00
9954dd002c feat: add config for preset in category + filter on select comb 2026-02-05 23:53:49 +01:00
7dab5b8880 fix: tree detection on cat edit 2026-02-05 22:32:31 +01:00
b37902466c feat: add team card when comb is register 2026-02-05 22:07:14 +01:00
89d9e04a6f feat: add classement match system 2026-02-05 21:29:27 +01:00
e2197d0712 fix: score panel fonction update 2026-02-01 15:25:49 +01:00
34a6911fa0 Merge pull request 'dev' (#106) from dev into master
Reviewed-on: #106
2026-01-30 14:07:08 +00:00
172dcdaa67 fix: ghost selection in PayAndValidateList
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m59s
2026-01-30 15:00:32 +01:00
cdd7221e86 fix: add weight config for all register source 2026-01-30 14:54:44 +01:00
952300d063 feat: card in result view 2026-01-30 14:50:21 +01:00
541d3824f3 feat: update CardModel 2026-01-30 11:58:31 +01:00
4c260b86b9 feat: remove old card system 2026-01-29 22:43:59 +01:00
9018ecef12 feat: card for team 2026-01-29 22:35:54 +01:00
d749dea6f4 feat: add return of table state 2026-01-27 22:11:49 +01:00
189eb135bb wip: competition card system 2026-01-26 22:36:26 +01:00
59ba31ae2d Merge pull request 'dev' (#105) from dev into master
Reviewed-on: #105
2026-01-23 15:49:24 +00:00
197ee0d5b1 feat: register import
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m49s
2026-01-23 16:48:09 +01:00
3d8597869d feat: update register export 2026-01-22 19:04:08 +01:00
4a07eb4ed9 feat: comp register optional weight config 2026-01-22 15:13:48 +01:00
d2a7e6cbac feat: rework CatPreset for add more detail + add preset 2026-01-21 22:05:58 +01:00
78d22b466d feat: add lock picto and hide team on register admin + show registration information on user page 2026-01-20 22:53:47 +01:00
b419e2f58d feat: add category registration system to club and user 2026-01-20 21:49:10 +01:00
a5d3973394 feat: add category registration system to admin 2026-01-20 20:21:34 +01:00
4dff0940c1 feat: add docker healthcheck for api 2026-01-18 21:10:20 +01:00
4b56aa0209 Merge pull request 'dev' (#104) from dev into master
Reviewed-on: #104
2026-01-18 15:19:12 +00:00
eed7a10cfb feat: select comb double clic support and add background to already select
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m43s
2026-01-18 15:26:16 +01:00
1cbbde6506 feat: add team support to result 2026-01-18 15:10:38 +01:00
e8d5d0fa0c feat: add team support to cm 2026-01-17 22:37:48 +01:00
eefa77369a Merge pull request 'feat: update js npm lib' (#103) from dev into master
Reviewed-on: #103
2026-01-16 15:21:11 +00:00
a5bbc41dfd feat: update js npm lib
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m49s
2026-01-16 16:20:51 +01:00
a942798a6c Merge pull request 'feat: add match eq support' (#102) from dev into master
Reviewed-on: #102
2026-01-16 14:37:55 +00:00
01436ac220 feat: add match eq support
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m42s
2026-01-16 15:37:23 +01:00
876545630a Merge pull request 'fix: translate backend asset includes in native' (#101) from dev into master
Reviewed-on: #101
2026-01-16 13:22:09 +00:00
0150c4fac2 fix: translate backend asset includes in native
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m36s
2026-01-16 14:21:46 +01:00
cad6d14ba8 Merge pull request 'fix: translate backend add log' (#100) from dev into master
Reviewed-on: #100
2026-01-16 12:59:33 +00:00
c456fdae8b fix: translate backend add log
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m48s
2026-01-16 13:59:12 +01:00
4bf07cc8f8 Merge pull request 'i18n' (#99) from i18n into master
Reviewed-on: #99
2026-01-16 12:18:53 +00:00
a8356c8663 feat: translate backend
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 7m23s
2026-01-15 16:32:43 +01:00
c7a2215103 feat: translation competition manager part 2026-01-15 14:08:57 +01:00
46f543abde feat: translation competition part 2026-01-14 18:24:26 +01:00
d8b6f1ad25 feat: translation result 2026-01-14 15:51:15 +01:00
9170157dbb feat: init translation 2026-01-13 21:31:05 +01:00
babbc5d585 Merge pull request 'feat: keep showLicenceState in sessionStorage' (#98) from dev into master
Reviewed-on: #98
2026-01-07 21:10:12 +00:00
51c774a21e feat: keep showLicenceState in sessionStorage
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m52s
2026-01-07 22:09:41 +01:00
9d85b9ae45 Merge pull request 'feat: support ha partial config' (#97) from dev into master
Reviewed-on: #97
2026-01-07 15:35:10 +00:00
0ab0731ebd feat: support ha partial config
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m52s
2026-01-07 16:34:57 +01:00
ec48f73d9f Merge pull request 'feat: support ha partial config' (#96) from dev into master
Reviewed-on: #96
2026-01-07 15:24:44 +00:00
f98f1bd1ac feat: support ha partial config
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m59s
2026-01-07 16:24:19 +01:00
b9d752ac55 Merge pull request 'dev' (#95) from dev into master
Reviewed-on: #95
2026-01-05 16:57:01 +00:00
13650b27c1 feat: add selection
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m50s
2026-01-05 17:56:37 +01:00
0757ae7198 feat: allow admin and club admin to get comp result 2026-01-05 14:20:22 +01:00
a871b52006 feat: use blason setting 2026-01-05 11:09:22 +01:00
78959fc485 feat: cma select comb sort by name 2026-01-04 21:29:42 +01:00
0a91e72c29 feat: autorise null mail for admin add 2026-01-04 21:05:20 +01:00
e3a1d1c50b Merge pull request 'dev' (#94) from dev into master
Reviewed-on: #94
2026-01-03 20:50:18 +00:00
3933954e09 feat: add permission setting to admin page
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m44s
2026-01-03 21:49:36 +01:00
959e356fb9 fix: quick add auto complet 2026-01-03 11:22:04 +01:00
3b5955ff31 fix: rm match with cardboard 2026-01-02 23:44:53 +01:00
e8ee5811a0 feat: avoid ws reconnect when user logged out 2026-01-02 23:12:22 +01:00
12326fe122 Merge pull request 'fix: ws timeout' (#93) from dev into master
Reviewed-on: #93
2026-01-02 21:33:40 +00:00
9624ff93f0 fix: ws timeout
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m42s
2026-01-02 22:33:11 +01:00
5444235512 Merge pull request 'dev' (#92) from dev into master
Reviewed-on: #92
2026-01-02 15:00:08 +00:00
b54eaa2549 fix: remove case sensitive sort result
All checks were successful
Deploy Production Server / if_merged (pull_request) Successful in 6m44s
2026-01-02 15:58:59 +01:00
6a7af8aea9 fix: tree order on result api 2026-01-02 15:33:56 +01:00
bc4dbb532f fix: remove extra log on recalculateMatch 2026-01-02 15:13:41 +01:00
178 changed files with 16500 additions and 5455 deletions

View File

@@ -48,7 +48,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
node-version: '22'
cache: 'npm'
cache-dependency-path: src/main/webapp/package-lock.json

View File

@@ -20,6 +20,11 @@ services:
- default
- intra
- nginx
healthcheck:
test: [ "CMD", "curl", "-f", "https://intra.ffsaf.fr/api" ]
interval: 30s
timeout: 5s
retries: 3
ffsaf-db:
image: public.ecr.aws/docker/library/postgres:17.2

View File

@@ -44,7 +44,9 @@ public class FrontendForwardingFilter implements ContainerResponseFilter {
final String path = info.getPath();
final String address = request.remoteAddress().toString();
LOG.infof("Request %s %s from IP %s", method, path, address);
if (!path.equals("/api")) {
LOG.infof("Request %s %s from IP %s", method, path, address);
}
int status = responseContext.getStatus();
if (status != 404 && !(status == 405 && "GET".equals(requestContext.getMethod()))) {

View File

@@ -0,0 +1,48 @@
package fr.titionfire.ffsaf;
import fr.titionfire.ffsaf.domain.service.TradService;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.container.PreMatching;
import jakarta.ws.rs.ext.Provider;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
@Provider
@PreMatching
public class UserInfoProvider implements ContainerRequestFilter {
private static final List<Locale> SUPPORTED_LANGUAGES = Arrays.asList(
Locale.FRENCH,
Locale.ENGLISH
);
@Override
public void filter(ContainerRequestContext requestContext) {
List<Locale> acceptableLanguages = requestContext.getAcceptableLanguages();
Locale selectedLocale = findFirstSupportedLanguage(acceptableLanguages);
if (selectedLocale == null)
selectedLocale = TradService.fallbackLocale;
requestContext.setProperty("userLocale", selectedLocale);
}
private Locale findFirstSupportedLanguage(List<Locale> acceptableLanguages) {
for (Locale acceptableLanguage : acceptableLanguages) {
// Vérifie si la langue est dans la liste des langues supportées
if (SUPPORTED_LANGUAGES.contains(acceptableLanguage)) {
return acceptableLanguage;
}
// Vérifie aussi par tag de langue (ex: "fr-FR" -> "fr")
String languageTag = acceptableLanguage.getLanguage();
for (Locale supportedLanguage : SUPPORTED_LANGUAGES) {
if (supportedLanguage.getLanguage().equals(languageTag)) {
return supportedLanguage;
}
}
}
return null;
}
}

View File

@@ -0,0 +1,64 @@
package fr.titionfire.ffsaf.data.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import java.util.Date;
import java.util.Objects;
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "card")
public class CardModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
Long comb;
Long match;
Long category;
@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "competition", referencedColumnName = "id")
CompetitionModel competition;
@JsonProperty("competition")
Long competitionId;
CardType type;
String reason;
@CreationTimestamp
Date date;
@Column(nullable = false, columnDefinition = "boolean default false")
boolean teamCard = false;
public boolean hasEffect(MatchModel match) {
return switch (this.type) {
case BLUE -> false;
case YELLOW -> Objects.equals(this.match, match.getId());
case RED -> Objects.equals(this.category, match.getCategory().getId())
|| Objects.equals(this.match, match.getId());
case BLACK -> true;
};
}
public enum CardType {
BLUE,
YELLOW,
RED,
BLACK
}
}

View File

@@ -1,42 +0,0 @@
package fr.titionfire.ffsaf.data.model;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "cardboard")
public class CardboardModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "comb", referencedColumnName = "id")
MembreModel comb;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "guest_comb", referencedColumnName = "id")
CompetitionGuestModel guestComb;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "match", referencedColumnName = "id")
MatchModel match;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "compet", referencedColumnName = "id")
CompetitionModel compet;
int red;
int yellow;
}

View File

@@ -0,0 +1,89 @@
package fr.titionfire.ffsaf.data.model;
import fr.titionfire.ffsaf.utils.Categorie;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.*;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "category_preset")
public class CatPresetModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "competition", referencedColumnName = "id")
CompetitionModel competition;
String name = "";
@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "category_preset_catconfig", joinColumns = @JoinColumn(name = "id_preset"))
List<CategorieEmbeddable> categories;
SwordType swordType = SwordType.NONE;
ShieldType shieldType = ShieldType.NONE;
/*
* 1 - 1 - Casque
* 2 - 2 - Gorgerin
* 3 - 4 - Coquille et Protection pelvienne
* 4 - 8 - Gant main(s) armée(s)
* 5 - 16 - Gant main bouclier
* 6 - 32 - Plastron
* 7 - 64 - Protection de bras armé(s)
* 8 - 128 - Protection de bras de bouclier
* 9 - 256 - Protection de jambes
* 10 - 512 - Protection de genoux
* 11 - 1024 - Protection de coudes
* 12 - 2048 - Protection dorsale
* 13 - 4096 - Protection de pieds
*/
int mandatoryProtection1 = 0;
int mandatoryProtection2 = 0;
@ManyToMany(mappedBy = "categoriesInscrites", fetch = FetchType.LAZY)
private List<RegisterModel> registers = new ArrayList<>();
@ManyToMany(mappedBy = "categoriesInscrites", fetch = FetchType.LAZY)
private List<CompetitionGuestModel> guest = new ArrayList<>();
public enum SwordType {
NONE,
ONE_HAND,
TWO_HAND,
SABER
}
public enum ShieldType {
NONE,
STANDARD,
ROUND,
TEARDROP,
BUCKLER
}
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Embeddable
public static class CategorieEmbeddable {
Categorie categorie;
long roundDuration;
long pauseDuration;
}
}

View File

@@ -44,4 +44,14 @@ public class CategoryModel {
Integer type;
String liceName = "1";
@Column(nullable = false, columnDefinition = "boolean default false")
boolean treeAreClassement = false;
@Column(nullable = false, columnDefinition = "boolean default false")
boolean fullClassement = false;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "id_preset", referencedColumnName = "id")
CatPresetModel preset;
}

View File

@@ -0,0 +1,38 @@
package fr.titionfire.ffsaf.data.model;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import java.util.Date;
import java.util.List;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "card_team")
public class ClubCardModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
Long competition;
String teamUuid;
String teamName;
CardModel.CardType type;
String reason;
@CreationTimestamp
Date date;
List<Long> cardIds;
}

View File

@@ -1,9 +1,11 @@
package fr.titionfire.ffsaf.data.model;
import fr.titionfire.ffsaf.utils.Categorie;
import fr.titionfire.ffsaf.utils.ResultPrivacy;
public interface CombModel {
Long getCombId();
String getName();
String getName(MembreModel model, ResultPrivacy privacy);
Categorie getCategorie();
}

View File

@@ -10,6 +10,11 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
@Getter
@Setter
@AllArgsConstructor
@@ -38,7 +43,32 @@ public class CompetitionGuestModel implements CombModel {
String country = "fr";
Integer weight = null;
Float weight = null;
Float weightReal = null;
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
@JoinTable(
name = "groupe_membre",
joinColumns = @JoinColumn(name = "groupe_id"),
inverseJoinColumns = @JoinColumn(name = "membre_id")
)
List<MembreModel> comb = new ArrayList<>();
@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
@JoinTable(
name = "groupe_guest",
joinColumns = @JoinColumn(name = "groupe_id"),
inverseJoinColumns = @JoinColumn(name = "guest_id")
)
List<CompetitionGuestModel> guest = new ArrayList<>();
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@JoinTable(
name = "categories_insc_guest",
joinColumns = @JoinColumn(name = "guest_id"),
inverseJoinColumns = @JoinColumn(name = "category_id")
)
List<CatPresetModel> categoriesInscrites = new ArrayList<>();
public CompetitionGuestModel(String s) {
this.fname = s.substring(0, s.indexOf(" "));
@@ -52,6 +82,8 @@ public class CompetitionGuestModel implements CombModel {
@Override
public String getName() {
if (this.isTeam())
return this.fname;
return this.fname + " " + this.lname;
}
@@ -59,4 +91,25 @@ public class CompetitionGuestModel implements CombModel {
public String getName(MembreModel model, ResultPrivacy privacy) {
return getName();
}
public boolean isTeam() {
return "__team".equals(this.lname);
}
public boolean isInTeam(Object comb_) {
if (!this.isTeam())
return false;
if (comb_ instanceof Long id_) {
if (id_ >= 0)
return comb.stream().anyMatch(membre -> Objects.equals(membre.getId(), id_));
else
return guest.stream().anyMatch(guestModel -> Objects.equals(guestModel.getId(), -id_));
}
return Stream.concat(comb.stream(), guest.stream()).anyMatch(c -> Objects.equals(c, comb_));
}
public Float getWeight2() {
return (this.weightReal != null) ? this.weightReal : this.weight;
}
}

View File

@@ -1,5 +1,6 @@
package fr.titionfire.ffsaf.data.model;
import fr.titionfire.ffsaf.utils.Categorie;
import fr.titionfire.ffsaf.utils.CompetitionSystem;
import fr.titionfire.ffsaf.utils.RegisterMode;
import io.quarkus.runtime.annotations.RegisterForReflection;
@@ -58,6 +59,10 @@ public class CompetitionModel {
@OneToMany(mappedBy = "competition", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
List<CompetitionGuestModel> guests = new ArrayList<>();
@OneToMany(mappedBy = "competition", fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
List<CatPresetModel> catPreset = new ArrayList<>();
List<Categorie> requiredWeight = new ArrayList<>();
List<Long> banMembre = new ArrayList<>();
@@ -67,8 +72,15 @@ public class CompetitionModel {
@Column(name = "table_")
List<String> table = new ArrayList<>();
@Column(columnDefinition = "TEXT")
String data1;
@Column(columnDefinition = "TEXT")
String data2;
@Column(columnDefinition = "TEXT")
String data3;
@Column(columnDefinition = "TEXT")
String data4;
@Column(columnDefinition = "TEXT")
String config;
}

View File

@@ -41,7 +41,7 @@ public class LogModel {
}
public enum ObjectType {
Membre, Affiliation, Licence, Club, Competition, Register
Membre, Affiliation, Licence, Club, Competition, Register, Selection
}
}

View File

@@ -63,10 +63,6 @@ public class MatchModel {
char poule = 'A';
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "match", referencedColumnName = "id")
List<CardboardModel> cardboard = new ArrayList<>();
public String getC1Name(MembreModel model, ResultPrivacy privacy) {
if (c1_id != null)
return c1_id.getName(model, privacy);
@@ -113,7 +109,26 @@ public class MatchModel {
return sum;
}
public CombModel getC1() {
if (this.c1_id != null)
return this.c1_id;
if (this.c1_guest != null)
return this.c1_guest;
return null;
}
public CombModel getC2() {
if (this.c2_id != null)
return this.c2_id;
if (this.c2_guest != null)
return this.c2_guest;
return null;
}
public boolean isC1(Object comb) {
if (this.c1_guest != null && this.c1_guest.isInTeam(comb))
return true;
if (comb instanceof Long id_) {
if (id_ >= 0)
return Objects.equals(this.c1_id != null ? this.c1_id.getId() : null, id_);
@@ -124,6 +139,9 @@ public class MatchModel {
}
public boolean isC2(Object comb) {
if (this.c2_guest != null && this.c2_guest.isInTeam(comb))
return true;
if (comb instanceof Long id_) {
if (id_ >= 0)
return Objects.equals(this.c2_id != null ? this.c2_id.getId() : null, id_);

View File

@@ -72,6 +72,10 @@ public class MembreModel implements LoggableModel, CombModel {
@Schema(description = "Les licences du membre. (optionnel)")
List<LicenceModel> licences;
@OneToMany(mappedBy = "membre", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@Schema(description = "Les séléctions du membre. (optionnel)")
List<SelectionModel> selections;
@Override
public String getObjectName() {
return fname + " " + lname;

View File

@@ -11,6 +11,9 @@ import lombok.Setter;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
@AllArgsConstructor
@@ -34,7 +37,8 @@ public class RegisterModel {
@JoinColumn(name = "id_membre")
MembreModel membre;
Integer weight;
Float weight;
Float weightReal;
int overCategory = 0;
Categorie categorie;
@@ -46,7 +50,18 @@ public class RegisterModel {
@Column(nullable = false, columnDefinition = "boolean default false")
boolean lockEdit = false;
public RegisterModel(CompetitionModel competition, MembreModel membre, Integer weight, int overCategory,
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@JoinTable(
name = "categories_insc_comb",
joinColumns = {
@JoinColumn(name = "id_competition", referencedColumnName = "id_competition"),
@JoinColumn(name = "id_membre", referencedColumnName = "id_membre")
},
inverseJoinColumns = @JoinColumn(name = "category_id")
)
List<CatPresetModel> categoriesInscrites = new ArrayList<>();
public RegisterModel(CompetitionModel competition, MembreModel membre, Float weight, int overCategory,
Categorie categorie, ClubModel club) {
this.id = new RegisterId(competition.getId(), membre.getId());
this.competition = competition;
@@ -75,4 +90,10 @@ public class RegisterModel {
return null;
return Categorie.values()[Math.min(tmp.ordinal() + this.overCategory, Categorie.values().length - 1)];
}
public Float getWeight2() {
if (weightReal != null)
return weightReal;
return weight;
}
}

View File

@@ -0,0 +1,44 @@
package fr.titionfire.ffsaf.data.model;
import fr.titionfire.ffsaf.utils.Categorie;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.persistence.*;
import lombok.*;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
@Entity
@Table(name = "selection")
public class SelectionModel implements LoggableModel {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Schema(description = "L'identifiant de la séléction.")
Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "membre", referencedColumnName = "id")
@Schema(description = "Le membre de la séléction. (optionnel)")
MembreModel membre;
@Schema(description = "La saison de la séléction.", examples = "2025")
int saison;
@Schema(description = "Catégorie de la séléction.")
Categorie categorie;
@Override
public String getObjectName() {
return "selection " + id.toString();
}
@Override
public LogModel.ObjectType getObjectType() {
return LogModel.ObjectType.Selection;
}
}

View File

@@ -40,6 +40,14 @@ public class TreeModel {
@JoinColumn(referencedColumnName = "id")
TreeModel right;
public TreeModel(Long category, Integer level, MatchModel match) {
this.category = category;
this.level = level;
this.match = match;
this.left = null;
this.right = null;
}
public List<TreeModel> flat() {
List<TreeModel> out = new ArrayList<>();
this.flat(out);
@@ -55,4 +63,44 @@ public class TreeModel {
if (this.left != null)
this.left.flat(out);
}
public int death() {
int dg = 0;
int dd = 0;
if (this.right != null)
dg = this.right.death();
if (this.left != null)
dg = this.left.death();
return 1 + Math.max(dg, dd);
}
public int getMaxChildrenAtDepth(int death, int current) {
if (current == death)
return 1;
int tmp = 0;
if (this.right != null)
tmp += this.right.getMaxChildrenAtDepth(death, current + 1);
if (this.left != null)
tmp += this.left.getMaxChildrenAtDepth(death, current + 1);
return tmp;
}
public void getChildrenAtDepth (int death, int current, List<TreeModel> out) {
if (current == death) {
out.add(this);
return;
}
if (this.right != null)
this.right.getChildrenAtDepth(death, current + 1, out);
if (this.left != null)
this.left.getChildrenAtDepth(death, current + 1, out);
}
}

View File

@@ -0,0 +1,10 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.CardModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class CardRepository implements PanacheRepositoryBase<CardModel, Long> {
}

View File

@@ -1,9 +1,9 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.CardboardModel;
import fr.titionfire.ffsaf.data.model.CatPresetModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class CardboardRepository implements PanacheRepositoryBase<CardboardModel, Long> {
public class CatPresetRepository implements PanacheRepositoryBase<CatPresetModel, Long> {
}

View File

@@ -0,0 +1,10 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.ClubCardModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class ClubCardRepository implements PanacheRepositoryBase<ClubCardModel, Long> {
}

View File

@@ -20,6 +20,9 @@ public class MatchRepository implements PanacheRepositoryBase<MatchModel, Long>
}
public Uni<Void> create(List<MatchModel> matchModel) {
if (matchModel.isEmpty())
return Uni.createFrom().voidItem();
matchModel.forEach(model -> model.setSystem(CompetitionSystem.INTERNAL));
return Panache.withTransaction(() -> this.persist(matchModel)
.call(__ -> this.flush())

View File

@@ -0,0 +1,9 @@
package fr.titionfire.ffsaf.data.repository;
import fr.titionfire.ffsaf.data.model.SelectionModel;
import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class SelectionRepository implements PanacheRepositoryBase<SelectionModel, Long> {
}

View File

@@ -1,26 +0,0 @@
package fr.titionfire.ffsaf.domain.entity;
import fr.titionfire.ffsaf.data.model.CardboardModel;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
@RegisterForReflection
public class CardboardEntity {
long comb_id;
long match_id;
long compet_id;
int red;
int yellow;
public static CardboardEntity fromModel(CardboardModel model) {
return new CardboardEntity(
model.getComb() != null ? model.getComb().getId() : model.getGuestComb().getId() * -1,
model.getMatch().getId(),
model.getCompet().getId(),
model.getRed(), model.getYellow());
}
}

View File

@@ -1,5 +1,6 @@
package fr.titionfire.ffsaf.domain.entity;
import fr.titionfire.ffsaf.data.model.CatPresetModel;
import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.data.model.RegisterModel;
@@ -9,6 +10,10 @@ import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
@Data
@AllArgsConstructor
@RegisterForReflection
@@ -22,7 +27,9 @@ public class CombEntity {
Genre genre;
String country;
int overCategory;
Integer weight;
Float weight;
List<CombEntity> teamMembers;
List<Long> categoriesInscrites;
public static CombEntity fromModel(MembreModel model) {
if (model == null)
@@ -31,7 +38,7 @@ public class CombEntity {
return new CombEntity(model.getId(), model.getLname(), model.getFname(), model.getCategorie(),
model.getClub() == null ? null : model.getClub().getClubId(),
model.getClub() == null ? "Sans club" : model.getClub().getName(), model.getGenre(), model.getCountry(),
0, null);
0, null, new ArrayList<>(), new ArrayList<>());
}
@@ -40,7 +47,18 @@ public class CombEntity {
return null;
return new CombEntity(model.getId() * -1, model.getLname(), model.getFname(), model.getCategorie(), null,
model.getClub(), model.getGenre(), model.getCountry(), 0, model.getWeight());
model.getClub(), model.getGenre(), model.getCountry(), 0,
model.getWeight2() != null ? model.getWeight2() : model.getWeight(),
Stream.concat(model.getComb().stream().map(CombEntity::fromModel),
model.getGuest().stream().map(CombEntity::fromModel)).toList(),
new ArrayList<>());
}
public CombEntity addCategoriesInscrites(List<CatPresetModel> categoriesInscrites) {
if (categoriesInscrites == null)
return this;
this.categoriesInscrites = categoriesInscrites.stream().map(CatPresetModel::getId).toList();
return this;
}
public static CombEntity fromModel(RegisterModel registerModel) {
@@ -51,6 +69,8 @@ public class CombEntity {
return new CombEntity(model.getId(), model.getLname(), model.getFname(), registerModel.getCategorie(),
registerModel.getClub2() == null ? null : registerModel.getClub2().getClubId(),
registerModel.getClub2() == null ? "Sans club" : registerModel.getClub2().getName(), model.getGenre(),
model.getCountry(), registerModel.getOverCategory(), registerModel.getWeight());
model.getCountry(), registerModel.getOverCategory(),
registerModel.getWeight2() != null ? registerModel.getWeight2() : registerModel.getWeight(),
new ArrayList<>(), new ArrayList<>());
}
}

View File

@@ -6,7 +6,6 @@ import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@@ -23,7 +22,6 @@ public class MatchEntity {
private Date date;
private List<ScoreEmbeddable> scores;
private char poule;
private List<CardboardEntity> cardboard;
public static MatchEntity fromModel(MatchModel model) {
if (model == null)
@@ -35,22 +33,6 @@ public class MatchEntity {
model.getC2_id()),
model.getCategory_ord(), model.isEnd(), model.getCategory().getId(), model.getDate(),
model.getScores(),
model.getPoule(),
(model.getCardboard() == null) ? new ArrayList<>() : model.getCardboard().stream()
.map(CardboardEntity::fromModel).toList());
}
public int win() {
int sum = 0;
for (ScoreEmbeddable score : scores) {
if (score.getS1() == -1000 || score.getS2() == -1000)
continue;
if (score.getS1() > score.getS2())
sum++;
else if (score.getS1() < score.getS2())
sum--;
}
return sum;
model.getPoule());
}
}

View File

@@ -0,0 +1,184 @@
package fr.titionfire.ffsaf.domain.entity;
import fr.titionfire.ffsaf.data.model.*;
import fr.titionfire.ffsaf.utils.ResultPrivacy;
import fr.titionfire.ffsaf.utils.ScoreEmbeddable;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Getter;
import java.util.*;
@RegisterForReflection
public class MatchModelExtend {
final MatchModel match;
@Getter
boolean isEnd = false;
@Getter
List<ScoreEmbeddable> scoresToPrint = new ArrayList<>();
@Getter
List<ScoreEmbeddable> scoresToCompute = new ArrayList<>();
@Getter
int win = 0;
public MatchModelExtend(MatchModel match, List<CardModel> cards) {
this.match = match;
List<Long> combIds = extractCombIds(match);
List<CardModel> cards2 = cards.stream().filter(c -> combIds.contains(c.getComb()) && c.hasEffect(match))
.sorted(Comparator.comparing(CardModel::getType).reversed()).toList();
for (ScoreEmbeddable score : match.getScores()) {
if (score.getS1() == -1000 || score.getS2() == -1000)
continue;
this.scoresToCompute.add(virtualScore(score, cards2, false));
}
calc_win_end(cards2);
for (ScoreEmbeddable score : match.getScores()) {
if (score.getS1() == -1000 || score.getS2() == -1000)
continue;
this.scoresToPrint.add(virtualScore(score, cards2, true));
}
if (this.isEnd && this.scoresToPrint.isEmpty()) {
this.scoresToPrint.add(virtualScore(new ScoreEmbeddable(0, 0, 0), cards2, true));
}
}
private ScoreEmbeddable virtualScore(ScoreEmbeddable score, List<CardModel> cards2, boolean toPrint) {
if (cards2.size() > 1) {
if (!Objects.equals(cards2.get(0).getComb(), cards2.get(1).getComb()))
return new ScoreEmbeddable(score.getN_round(), toPrint ? -997 : 0, toPrint ? -997 : 0);
}
if (!cards2.isEmpty()) {
if (isC1(cards2.get(0).getComb()))
return new ScoreEmbeddable(score.getN_round(), toPrint ? -997 : 0, 10);
else
return new ScoreEmbeddable(score.getN_round(), 10, toPrint ? -997 : 0);
}
if (score.getS1() < -900 && score.getS2() < -900)
return new ScoreEmbeddable(score.getN_round(), toPrint ? score.getS1() : 0, toPrint ? score.getS2() : 0);
else if (score.getS1() < -900)
return new ScoreEmbeddable(score.getN_round(), toPrint ? score.getS1() : 0, 10);
else if (score.getS2() < -900)
return new ScoreEmbeddable(score.getN_round(), 10, toPrint ? score.getS2() : 0);
return new ScoreEmbeddable(score.getN_round(), score.getS1(), score.getS2());
}
private void calc_win_end(List<CardModel> cards2) {
if (cards2.size() > 1) {
if (!Objects.equals(cards2.get(0).getComb(), cards2.get(1).getComb())) {
this.win = 0;
this.isEnd = true;
return;
}
}
if (!cards2.isEmpty()) {
if (match.isC1(cards2.get(0).getComb())) {
this.win = -1;
} else if (match.isC2(cards2.get(0).getComb())) {
this.win = 1;
}
this.isEnd = true;
return;
}
for (ScoreEmbeddable score : this.scoresToCompute) {
if (score.getS1() > score.getS2())
win++;
else if (score.getS1() < score.getS2())
win--;
}
this.isEnd = match.isEnd();
}
private List<Long> extractCombIds(MatchModel match) {
List<Long> ids = new ArrayList<>();
if (match.getC1_id() != null)
ids.add(match.getC1_id().getId());
if (match.getC2_id() != null)
ids.add(match.getC2_id().getId());
if (match.getC1_guest() != null)
ids.add(match.getC1_guest().getId() * -1);
if (match.getC2_guest() != null)
ids.add(match.getC2_guest().getId() * -1);
return ids;
}
//--------------- Delegation methods to MatchModel ---------------
public Long getId() {
return match.getId();
}
public MembreModel getC1_id() {
return match.getC1_id();
}
public CompetitionGuestModel getC1_guest() {
return match.getC1_guest();
}
public MembreModel getC2_id() {
return match.getC2_id();
}
public CompetitionGuestModel getC2_guest() {
return match.getC2_guest();
}
public CategoryModel getCategory() {
return match.getCategory();
}
public long getCategory_ord() {
return match.getCategory_ord();
}
public Date getDate() {
return match.getDate();
}
public char getPoule() {
return match.getPoule();
}
public String getC1Name(MembreModel model, ResultPrivacy privacy) {
return match.getC1Name(model, privacy);
}
public String getC2Name(MembreModel model, ResultPrivacy privacy) {
return match.getC2Name(model, privacy);
}
public String getC2Name() {
return match.getC2Name();
}
public String getC1Name() {
return match.getC1Name();
}
public boolean isC1(Object comb) {
return match.isC1(comb);
}
public boolean isC2(Object comb) {
return match.isC2(comb);
}
public CombModel getC1() {
return match.getC1();
}
public CombModel getC2() {
return match.getC2();
}
}

View File

@@ -79,6 +79,9 @@ public class AffiliationService {
@ConfigProperty(name = "notif.affRequest.mail")
List<String> mails;
@Inject
TradService trad;
public Uni<List<AffiliationRequestModel>> getAllReq() {
return repositoryRequest.listAll();
}
@@ -92,7 +95,7 @@ public class AffiliationService {
return Uni.createFrom().item(affModel)
.invoke(Unchecked.consumer(model -> {
if (model.getSaison() != currentSaison && model.getSaison() != currentSaison + 1) {
throw new DBadRequestException("Saison non valid");
throw new DBadRequestException(trad.t("saison.non.valid"));
}
}))
.chain(() -> ((affModel.getState_id().charAt(0) == 'W') ? stateIdService.get_rna(
@@ -110,7 +113,7 @@ public class AffiliationService {
out, affModel.getSaison()))
.onItem().invoke(Unchecked.consumer(count -> {
if (count != 0 && unique) {
throw new DBadRequestException("Demande d'affiliation déjà existante");
throw new DBadRequestException(trad.t("demande.d.affiliation.deja.existante"));
}
}))
)
@@ -118,28 +121,28 @@ public class AffiliationService {
repository.count("club = ?1 and saison = ?2", club, affModel.getSaison())))
.onItem().invoke(Unchecked.consumer(count -> {
if (count != 0) {
throw new DBadRequestException("Affiliation déjà existante");
throw new DBadRequestException(trad.t("affiliation.deja.existante"));
}
}))
.map(o -> affModel)
.call(model -> ((model.getM1_lincence() != -1) ? combRepository.find("licence",
model.getM1_lincence()).count().invoke(Unchecked.consumer(count -> {
if (count == 0) {
throw new DBadRequestException("Licence membre n°1 inconnue");
throw new DBadRequestException(trad.t("licence.membre.n.1.inconnue"));
}
})) : Uni.createFrom().nullItem())
)
.call(model -> ((model.getM2_lincence() != -1) ? combRepository.find("licence",
model.getM2_lincence()).count().invoke(Unchecked.consumer(count -> {
if (count == 0) {
throw new DBadRequestException("Licence membre n°2 inconnue");
throw new DBadRequestException(trad.t("licence.membre.n.2.inconnue"));
}
})) : Uni.createFrom().nullItem())
)
.call(model -> ((model.getM3_lincence() != -1) ? combRepository.find("licence",
model.getM3_lincence()).count().invoke(Unchecked.consumer(count -> {
if (count == 0) {
throw new DBadRequestException("Licence membre n°3 inconnue");
throw new DBadRequestException(trad.t("licence.membre.n.3.inconnue"));
}
})) : Uni.createFrom().nullItem())
);
@@ -148,7 +151,7 @@ public class AffiliationService {
public Uni<?> saveEdit(AffiliationRequestForm form) {
return pre_save(form, false)
.chain(model -> repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.onItem().ifNull().failWith(new DNotFoundException(trad.t("demande.d.affiliation.non.trouve")))
.chain(origine -> {
origine.setName(model.getName());
origine.setAddress(model.getAddress());
@@ -201,7 +204,7 @@ public class AffiliationService {
LOGGER.debug(form.toString());
return repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.onItem().ifNull().failWith(new DNotFoundException(trad.t("demande.d.affiliation.non.trouve")))
.map(model -> {
model.setName(form.getName());
model.setState_id(form.getState_id());
@@ -306,7 +309,7 @@ public class AffiliationService {
LOGGER.debug(form.toString());
return repositoryRequest.findById(form.getId())
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.onItem().ifNull().failWith(new DNotFoundException(trad.t("demande.d.affiliation.non.trouve")))
.chain(req ->
clubRepository.find("StateId = ?1", form.getState_id()).firstResult()
.chain(model -> (model == null) ? acceptNew(form, req) : acceptOld(form, req, model))
@@ -398,7 +401,7 @@ public class AffiliationService {
public Uni<SimpleReqAffiliation> getRequest(long id) {
return repositoryRequest.findById(id).map(SimpleReqAffiliation::fromModel)
.onItem().ifNull().failWith(new DNotFoundException("Demande d'affiliation non trouvé"))
.onItem().ifNull().failWith(new DNotFoundException(trad.t("demande.d.affiliation.non.trouve")))
.call(out -> clubRepository.find("StateId = ?1", out.getStateId()).firstResult().invoke(c -> {
if (c != null) {
out.setClub(c.getId());
@@ -422,7 +425,7 @@ public class AffiliationService {
public Uni<List<SimpleAffiliation>> getAffiliation(long id) {
return clubRepository.findById(id)
.onItem().ifNull().failWith(new DNotFoundException("Club non trouvé"))
.onItem().ifNull().failWith(new DNotFoundException(trad.t("club.non.trouve")))
.call(model -> Mutiny.fetch(model.getAffiliations()))
.chain(model -> repositoryRequest.list("state_id = ?1", model.getStateId())
.map(reqs -> reqs.stream().map(req ->
@@ -434,11 +437,11 @@ public class AffiliationService {
public Uni<SimpleAffiliation> setAffiliation(long id, int saison) {
return clubRepository.findById(id)
.onItem().ifNull().failWith(new DNotFoundException("Club non trouvé"))
.onItem().ifNull().failWith(new DNotFoundException(trad.t("club.non.trouve")))
.call(model -> Mutiny.fetch(model.getAffiliations()))
.invoke(Unchecked.consumer(club -> {
if (club.getAffiliations().stream().anyMatch(affiliation -> affiliation.getSaison() == saison)) {
throw new DBadRequestException("Affiliation déjà existante");
throw new DBadRequestException(trad.t("affiliation.deja.existante"));
}
}))
.chain(club ->

View File

@@ -0,0 +1,227 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.CardModel;
import fr.titionfire.ffsaf.data.model.ClubCardModel;
import fr.titionfire.ffsaf.data.model.CompetitionModel;
import fr.titionfire.ffsaf.data.model.MatchModel;
import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.ws.recv.RCard;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.panache.common.Sort;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@WithSession
@ApplicationScoped
public class CardService {
@Inject
CardRepository cardRepository;
@Inject
ClubCardRepository clubCardRepository;
@Inject
RegisterRepository registerRepository;
@Inject
CompetitionGuestRepository competitionGuestRepository;
@Inject
MatchRepository matchRepository;
@Inject
TradService trad;
private static final List<CardModel.CardType> COMPETITION_LEVEL_CARDS = List.of(
CardModel.CardType.YELLOW,
CardModel.CardType.RED,
CardModel.CardType.BLACK
);
private List<Long> extractCombIds(MatchModel match) {
List<Long> ids = new ArrayList<>();
if (match.getC1_id() != null)
ids.add(match.getC1_id().getId());
if (match.getC2_id() != null)
ids.add(match.getC2_id().getId());
if (match.getC1_guest() != null)
ids.add(match.getC1_guest().getId() * -1);
if (match.getC2_guest() != null)
ids.add(match.getC2_guest().getId() * -1);
return ids;
}
public Uni<List<CardModel>> getForMatch(MatchModel match) {
return cardRepository.list(
"competition = ?1 AND (type IN ?2 OR (type = CardType.BLUE AND category = ?4)) AND comb IN ?3",
match.getCategory().getCompet(), COMPETITION_LEVEL_CARDS,
extractCombIds(match), match.getCategory().getId());
}
public Uni<List<CardModel>> getAll(CompetitionModel competition) {
return cardRepository.list("competition = ?1", competition);
}
public Uni<RCard.SendCardAdd> checkCanBeAdded(RCard.SendCardAdd card, MatchModel matchModel) {
return cardRepository.find("competition = ?1 AND comb = ?2",
Sort.descending("type"),
matchModel.getCategory().getCompet(), card.combId())
.firstResult()
.map(card_ -> {
if (card.type() == CardModel.CardType.BLUE) {
return card_ == null || (card_.getType() == CardModel.CardType.BLUE
&& !Objects.equals(card_.getCategory(), matchModel.getCategory().getId()));
}
if (card.type() == CardModel.CardType.BLACK) {
return card_ != null && card_.getType() == CardModel.CardType.RED;
}
return card_ == null || card_.getType().ordinal() < card.type().ordinal();
})
.chain(b -> {
if (b)
return Uni.createFrom().item(card);
else
return Uni.createFrom().failure(new DBadRequestException(trad.t("card.cannot.be.added")));
});
}
public Uni<List<CardModel>> addTeamCartToNewComb(Long combId, String teamUuid, String teamName,
CompetitionModel competition) {
return clubCardRepository.list("competition = ?1 AND (teamUuid = ?2 OR teamName = ?3)",
Sort.ascending("type"), competition.getId(), teamUuid, teamName)
.chain(clubCards -> {
Uni<?> queue = Uni.createFrom().voidItem();
List<CardModel> addCards = new ArrayList<>();
for (ClubCardModel clubCard : clubCards) {
CardModel model = new CardModel();
model.setCompetition(competition);
model.setCompetitionId(competition.getId());
model.setComb(combId);
model.setTeamCard(true);
model.setType(clubCard.getType());
model.setDate(clubCard.getDate());
queue = queue.call(__ -> Panache.withTransaction(() -> cardRepository.persist(model))
.invoke(addCards::add)
.call(() -> {
clubCard.getCardIds().add(model.getId());
return Panache.withTransaction(() -> clubCardRepository.persist(clubCard));
}));
}
return queue.replaceWith(addCards);
});
}
public Uni<Void> rmTeamCardFromComb(Long combId, String uuid) {
return cardRepository.delete("comb = ?1 AND competition.uuid = ?2 AND teamCard = True", combId, uuid)
.replaceWithVoid();
}
public Uni<List<CardModel>> addTeamCard(CompetitionModel competition, String teamUuid, String teamName,
CardModel.CardType type, String reason) {
return clubCardRepository.find("competition = ?1 AND (teamUuid = ?2 OR teamName = ?3)",
Sort.descending("type"), competition.getId(), teamUuid, teamName)
.firstResult()
.map(card_ -> {
if (type == CardModel.CardType.BLACK) {
return card_ != null && card_.getType() == CardModel.CardType.RED;
}
return card_ == null || card_.getType().ordinal() < type.ordinal();
})
.chain(b -> {
if (!b)
return Uni.createFrom().failure(new DBadRequestException(trad.t("card.cannot.be.added")));
if (teamUuid != null) {
return registerRepository.list("competition = ?1 AND club.clubId = ?2", competition, teamUuid)
.map(l -> l.stream().map(r -> r.getMembre().getId()).toList());
} else {
return competitionGuestRepository.list("competition = ?1 AND club = ?2", competition,
teamName)
.map(l -> l.stream().map(r -> r.getId() * -1).toList());
}
})
.chain(combIds -> cardRepository.list("competition = ?1 AND comb IN ?2", competition, combIds)
.map(cards -> {
List<CardModel> newCards = new ArrayList<>();
for (Long id : combIds) {
Optional<CardModel> optional = cards.stream()
.filter(c -> id.equals(c.getComb()) && c.getType() == type).findAny();
CardModel model = new CardModel();
model.setCompetition(competition);
model.setCompetitionId(competition.getId());
model.setComb(id);
model.setTeamCard(true);
if (optional.isEmpty()) {
model.setType(type);
} else {
model.setType(
CardModel.CardType.values()[Math.min(optional.get().getType().ordinal() + 1,
CardModel.CardType.BLACK.ordinal())]);
}
newCards.add(model);
}
return newCards;
})
)
.call(newCards -> Panache.withTransaction(() -> cardRepository.persist(newCards)))
.call(newCards -> {
ClubCardModel model = new ClubCardModel();
model.setCompetition(competition.getId());
model.setTeamUuid(teamUuid);
model.setTeamName(teamName);
model.setType(type);
model.setReason(reason);
model.setCardIds(newCards.stream().map(CardModel::getId).toList());
return Panache.withTransaction(() -> clubCardRepository.persist(model));
});
}
public Uni<List<CardModel>> recvReturnState(CompetitionModel competition, RCard.SendTeamCardReturnState state) {
return clubCardRepository.find("competition = ?1 AND (teamUuid = ?2 OR teamName = ?3) AND type = ?4",
competition.getId(), state.teamUuid(), state.teamName(), state.type())
.firstResult()
.chain(o -> cardRepository.list("id IN ?1", o.getCardIds()))
.call(cards -> matchRepository.list("category.compet = ?1 AND category.id IN ?2", competition,
state.selectedCategory())
.invoke(matches -> {
for (CardModel card : cards) {
for (MatchModel m : matches.stream()
.filter(m -> extractCombIds(m).contains(card.getComb())).toList()) {
if (state.state() == 1) {
card.setCategory(m.getCategory().getId());
} else if (state.state() == 2) {
card.setCategory(m.getCategory().getId());
if (Objects.equals(m.getId(), state.selectedMatch()))
card.setMatch(m.getId());
}
}
}
})
.chain(() -> Panache.withTransaction(() -> cardRepository.persist(cards))));
}
public Uni<List<Long>> rmTeamCard(CompetitionModel competition, String teamUuid, String teamName,
CardModel.CardType type) {
return clubCardRepository.find("competition = ?1 AND (teamUuid = ?2 OR teamName = ?3) AND type = ?4",
competition.getId(), teamUuid, teamName, type)
.firstResult()
.chain(card -> Uni.createFrom().item(card.getCardIds())
.call(() -> Panache.withTransaction(() -> cardRepository.delete("id IN ?1", card.getCardIds())))
.call(() -> Panache.withTransaction(() -> clubCardRepository.delete(card))));
}
}

View File

@@ -45,10 +45,13 @@ public class CategoryService {
@Inject
CompetitionGuestRepository competitionGuestRepository;
@Inject
TradService trad;
public Uni<CategoryData> getByIdAdmin(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
return repository.find("systemId = ?1 AND system = ?2", id, system)
.firstResult()
.onItem().ifNull().failWith(() -> new RuntimeException("Category not found"))
.onItem().ifNull().failWith(() -> new RuntimeException(trad.t("categorie.non.trouver")))
.call(data -> permService.hasAdminViewPerm(securityCtx, data.getCompet()))
.map(CategoryData::fromModel);
}
@@ -64,7 +67,7 @@ public class CategoryService {
.chain(o -> {
if (o == null) {
return competRepository.findById(data.getCompet())
.onItem().ifNull().failWith(() -> new RuntimeException("Competition not found"))
.onItem().ifNull().failWith(() -> new RuntimeException(trad.t("competition.not.found")))
.call(o2 -> permService.hasEditPerm(securityCtx, o2))
.chain(competitionModel -> {
CategoryModel model = new CategoryModel();
@@ -139,7 +142,7 @@ public class CategoryService {
.onItem().ifNotNull().call(o2 -> permService.hasEditPerm(securityCtx, o2.getCompet()))
.onItem().ifNull().switchTo(
() -> competRepository.findById(data.getCompet())
.onItem().ifNull().failWith(() -> new RuntimeException("Compet not found"))
.onItem().ifNull().failWith(() -> new RuntimeException(trad.t("competition.not.found")))
.call(o -> permService.hasEditPerm(securityCtx, o))
.map(o -> {
CategoryModel model = new CategoryModel();
@@ -256,7 +259,7 @@ public class CategoryService {
public Uni<?> delete(SecurityCtx securityCtx, CompetitionSystem system, Long id) {
return repository.find("systemId = ?1 AND system = ?2", id, system).firstResult()
.onItem().ifNull().failWith(() -> new RuntimeException("Category not found"))
.onItem().ifNull().failWith(() -> new RuntimeException(trad.t("categorie.non.trouver")))
.call(o -> permService.hasEditPerm(securityCtx, o.getCompet()))
.call(o -> Mutiny.fetch(o.getTree())
.call(o2 -> o2.isEmpty() ? Uni.createFrom().nullItem() :

View File

@@ -63,6 +63,9 @@ public class ClubService {
@Inject
LoggerService ls;
@Inject
TradService trad;
public SimpleClubModel findByIdOptionalClub(long id) throws Throwable {
return VertxContextSupport.subscribeAndAwait(
() -> Panache.withTransaction(() -> repository.findById(id).map(SimpleClubModel::fromModel)));
@@ -130,7 +133,7 @@ public class ClubService {
return combRepository.find("userId = ?1", securityCtx.getSubject()).firstResult()
.invoke(Unchecked.consumer(m -> {
if (m == null || m.getClub() == null)
throw new DNotFoundException("Club non trouvé");
throw new DNotFoundException(trad.t("club.non.trouve"));
}))
.map(MembreModel::getClub)
.call(club -> Mutiny.fetch(club.getContact()));
@@ -150,7 +153,7 @@ public class ClubService {
return combRepository.find("userId = ?1", securityCtx.getSubject()).firstResult()
.invoke(Unchecked.consumer(m -> {
if (m == null || m.getClub() == null)
throw new DNotFoundException("Club non trouvé");
throw new DNotFoundException(trad.t("club.non.trouve"));
if (!securityCtx.isInClubGroup(m.getClub().getId()))
throw new DForbiddenException();
}))
@@ -166,7 +169,7 @@ public class ClubService {
return combRepository.find("userId = ?1", securityCtx.getSubject()).firstResult()
.invoke(Unchecked.consumer(m -> {
if (m == null || m.getClub() == null)
throw new DNotFoundException("Club non trouvé");
throw new DNotFoundException(trad.t("club.non.trouve"));
if (!securityCtx.isInClubGroup(m.getClub().getId()))
throw new DForbiddenException();
}))
@@ -183,7 +186,7 @@ public class ClubService {
ls.logUpdate("Contact(s)...", club);
club.setContact(MAPPER.readValue(form.getContact(), typeRef));
} catch (JsonProcessingException e) {
throw new DBadRequestException("Erreur de format des contacts");
throw new DBadRequestException(trad.t("erreur.de.format.des.contacts"));
}
ls.logChange("Lieux d'entrainements", club.getTraining_location(), form.getTraining_location(),
@@ -233,7 +236,7 @@ public class ClubService {
ls.logUpdate("Contact(s)...", m);
m.setContact(MAPPER.readValue(input.getContact(), typeRef));
} catch (JsonProcessingException e) {
throw new DBadRequestException("Erreur de format des contacts");
throw new DBadRequestException(trad.t("erreur.de.format.des.contacts"));
}
}
return Panache.withTransaction(() -> repository.persist(m)).call(() -> ls.append());

View File

@@ -98,12 +98,14 @@ public class CompetPermService {
map.putIfAbsent(model.getId(), "owner");
else if (securityCtx.roleHas("federation_admin"))
map.putIfAbsent(model.getId(), "admin");
else if (securityCtx.isInClubGroup(model.getClub().getId()) && (securityCtx.roleHas(
"club_president")
|| securityCtx.roleHas("club_respo_intra") || securityCtx.roleHas(
"club_secretaire")
|| securityCtx.roleHas("club_tresorier")))
else if (securityCtx.isInClubGroup(
model.getClub().getId()) && (securityCtx.isClubAdmin()))
map.putIfAbsent(model.getId(), "admin");
else if (model.getAdmin().contains(securityCtx.getSubject()))
map.putIfAbsent(model.getId(), "admin");
else if (model.getTable().contains(securityCtx.getSubject()))
map.putIfAbsent(model.getId(), "table");
}
return map;
}));
@@ -182,12 +184,14 @@ public class CompetPermService {
if (o.getSystem() == CompetitionSystem.SAFCA)
return hasSafcaViewPerm(securityCtx, o.getId());
if (o.getAdmin().contains(securityCtx.getSubject()))
return Uni.createFrom().nullItem();
if (!securityCtx.isInClubGroup(o.getClub().getId())) // Only membre club pass here
throw new DForbiddenException();
if (o.getSystem() == CompetitionSystem.INTERNAL)
if (securityCtx.roleHas("club_president") || securityCtx.roleHas("club_respo_intra")
|| securityCtx.roleHas("club_secretaire") || securityCtx.roleHas("club_tresorier"))
if (securityCtx.isClubAdmin())
return Uni.createFrom().nullItem();
throw new DForbiddenException();

View File

@@ -7,10 +7,7 @@ import fr.titionfire.ffsaf.net2.data.SimpleCompet;
import fr.titionfire.ffsaf.net2.request.SReqCompet;
import fr.titionfire.ffsaf.net2.request.SReqRegister;
import fr.titionfire.ffsaf.rest.client.dto.NotificationData;
import fr.titionfire.ffsaf.rest.data.CompetitionData;
import fr.titionfire.ffsaf.rest.data.RegisterRequestData;
import fr.titionfire.ffsaf.rest.data.SimpleCompetData;
import fr.titionfire.ffsaf.rest.data.SimpleRegisterComb;
import fr.titionfire.ffsaf.rest.data.*;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
@@ -63,6 +60,9 @@ public class CompetitionService {
@Inject
CompetitionGuestRepository competitionGuestRepository;
@Inject
CatPresetRepository catPresetRepository;
@Inject
ServerCustom serverCustom;
@@ -97,21 +97,29 @@ public class CompetitionService {
@CacheName("have-access")
Cache cacheNoneAccess;
@Inject
TradService trad;
public Uni<CompetitionData> getById(SecurityCtx securityCtx, Long id) {
return permService.hasViewPerm(securityCtx, id).map(CompetitionData::fromModelLight);
return permService.hasViewPerm(securityCtx, id).map(cm -> {
CompetitionData out = CompetitionData.fromModelLight(cm);
if (cm.getAdmin() != null) {
out.setCanEditRegisters(cm.getAdmin().stream().anyMatch(u -> u.equals(securityCtx.getSubject())));
}
return out;
});
}
public Uni<CompetitionData> getByIdAdmin(SecurityCtx securityCtx, Long id) {
if (id == 0) {
return Uni.createFrom()
.item(new CompetitionData(null, "", "", "", "", new Date(), new Date(),
CompetitionSystem.INTERNAL, RegisterMode.FREE, new Date(), new Date(), true,
null, "", "", null, true, "", "", "", ""));
return Uni.createFrom().item(new CompetitionData());
}
return permService.hasAdminViewPerm(securityCtx, id)
.call(competitionModel -> Mutiny.fetch(competitionModel.getCatPreset()))
.chain(competitionModel -> Mutiny.fetch(competitionModel.getInsc())
.chain(insc -> Mutiny.fetch(competitionModel.getGuests())
.map(guest -> CompetitionData.fromModel(competitionModel).addInsc(insc, guest))))
.map(guest -> CompetitionData.fromModel(competitionModel).addInsc(insc, guest)
.addPresets(competitionModel.getCatPreset()))))
.chain(data ->
vertx.getOrCreateContext().executeBlocking(() -> {
keycloakService.getUser(UUID.fromString(data.getOwner()))
@@ -129,6 +137,14 @@ public class CompetitionService {
out.addAll(cm.stream().map(CompetitionData::fromModelLight).toList());
out.forEach(competition -> competition.setCanEdit(true));
}))
.chain(ids -> repository.list("id NOT IN ?1 AND ?2 IN admin", ids, securityCtx.getSubject())
.map(cm -> {
out.addAll(cm.stream().map(CompetitionData::fromModelLight).toList());
out.forEach(competition -> competition.setCanEditRegisters(true));
List<Long> ids2 = new ArrayList<>(ids);
ids2.addAll(cm.stream().map(CompetitionModel::getId).toList());
return ids2;
}))
.call(ids ->
repository.list("id NOT IN ?1 AND (publicVisible = TRUE OR registerMode IN ?2)", ids,
securityCtx.isClubAdmin() ? List.of(RegisterMode.FREE, RegisterMode.HELLOASSO,
@@ -165,8 +181,9 @@ public class CompetitionService {
public Uni<List<CompetitionData>> getAllSystemTable(SecurityCtx securityCtx,
CompetitionSystem system) {
return repository.list("system = ?1", system)
.chain(l -> Uni.join().all(l.stream().map(cm -> permService.hasTablePerm(securityCtx, cm)).toList())
.andCollectFailures())
.chain(l -> Uni.join().all(l.stream().map(cm ->
permService.hasTablePerm(securityCtx, cm).onFailure().recoverWithNull()
).toList()).andCollectFailures())
.map(l -> l.stream().filter(Objects::nonNull).map(CompetitionData::fromModel).toList());
}
@@ -176,7 +193,7 @@ public class CompetitionService {
.invoke(Unchecked.consumer(combModel -> {
if (!securityCtx.getRoles().contains("create_compet") && !securityCtx.getRoles()
.contains("federation_admin"))
throw new DForbiddenException("Vous ne pouvez pas créer de compétition");
throw new DForbiddenException(trad.t("vous.ne.pouvez.pas.creer.de.competition"));
}))
.map(MembreModel::getClub)
.chain(clubModel -> {
@@ -189,17 +206,21 @@ public class CompetitionService {
model.setGuests(new ArrayList<>());
model.setUuid(UUID.randomUUID().toString());
model.setOwner(securityCtx.getSubject());
model.setCatPreset(new ArrayList<>());
copyData(data, model);
return Panache.withTransaction(() -> repository.persist(model));
}).map(CompetitionData::fromModel)
})
.call(model -> syncPreset(data, model))
.map(CompetitionData::fromModel)
.call(c -> (c.getSystem() == CompetitionSystem.SAFCA) ? cacheAccess.invalidate(
securityCtx.getSubject()) : Uni.createFrom().nullItem())
.call(c -> (c.getSystem() == CompetitionSystem.INTERNAL) ? cacheNoneAccess.invalidate(
securityCtx.getSubject()) : Uni.createFrom().nullItem());
} else {
return permService.hasEditPerm(securityCtx, data.getId())
.call(model -> Mutiny.fetch(model.getCatPreset()))
.chain(model -> {
copyData(data, model);
@@ -207,7 +228,8 @@ public class CompetitionService {
keycloakService.getUser(data.getOwner()).map(UserRepresentation::getId).orElse(null))
.invoke(Unchecked.consumer(newOwner -> {
if (newOwner == null)
throw new DBadRequestException("User " + data.getOwner() + " not found");
throw new DBadRequestException(
String.format(trad.t("user.not.found"), data.getOwner()));
if (!newOwner.equals(model.getOwner())) {
if (!securityCtx.roleHas("federation_admin")
&& !securityCtx.roleHas("safca_super_admin")
@@ -217,7 +239,9 @@ public class CompetitionService {
}
}))
.chain(__ -> Panache.withTransaction(() -> repository.persist(model)));
}).map(CompetitionData::fromModel)
})
.call(model -> syncPreset(data, model))
.map(model -> CompetitionData.fromModel(model).addPresets(model.getCatPreset()))
.call(c -> (c.getSystem() == CompetitionSystem.SAFCA) ? cacheAccess.invalidate(
securityCtx.getSubject()) : Uni.createFrom().nullItem())
.call(c -> (c.getSystem() == CompetitionSystem.INTERNAL) ? cacheNoneAccess.invalidate(
@@ -225,6 +249,45 @@ public class CompetitionService {
}
}
private Uni<?> syncPreset(CompetitionData data, CompetitionModel model) {
List<Long> toRemoveId = model.getCatPreset().stream()
.map(CatPresetModel::getId)
.filter(id -> data.getPresets().stream().noneMatch(preset -> Objects.equals(preset.getId(), id)))
.toList();
for (PresetData preset : data.getPresets()) {
CatPresetModel presetModel;
if (preset.getId() != null && preset.getId() > 0) {
presetModel = model.getCatPreset().stream()
.filter(p -> p.getId().equals(preset.getId()))
.findFirst()
.orElse(new CatPresetModel());
} else {
presetModel = new CatPresetModel();
model.getCatPreset().add(presetModel);
}
presetModel.setCompetition(model);
presetModel.setName(preset.getName());
presetModel.setSwordType(preset.getSword());
presetModel.setShieldType(preset.getShield());
presetModel.setCategories(preset.getCategories());
presetModel.setMandatoryProtection1(preset.getMandatoryProtection1());
presetModel.setMandatoryProtection2(preset.getMandatoryProtection2());
}
// Remove deleted presets
model.getCatPreset().removeIf(presetModel -> toRemoveId.contains(presetModel.getId()));
return Panache.withTransaction(() -> repository.persist(model)
.call(__ -> {
if (!toRemoveId.isEmpty()) {
return catPresetRepository.delete("id IN ?1", toRemoveId);
}
return Uni.createFrom().nullItem();
}));
}
private void copyData(CompetitionData data, CompetitionModel model) {
if (model.getBanMembre() == null)
model.setBanMembre(new ArrayList<>());
@@ -238,6 +301,7 @@ public class CompetitionService {
model.setStartRegister(data.getStartRegister());
model.setEndRegister(data.getEndRegister());
model.setRegisterMode(data.getRegisterMode());
model.setRequiredWeight(data.getRequiredWeight());
model.setData1(data.getData1());
model.setData2(data.getData2());
model.setData3(data.getData3());
@@ -251,11 +315,18 @@ public class CompetitionService {
Uni<List<SimpleRegisterComb>> uni = Mutiny.fetch(c.getInsc())
.onItem().transformToMulti(Multi.createFrom()::iterable)
.onItem().call(combModel -> Mutiny.fetch(combModel.getMembre().getLicences()))
.map(cm -> SimpleRegisterComb.fromModel(cm, cm.getMembre().getLicences()))
.onItem().call(combModel -> Mutiny.fetch(combModel.getCategoriesInscrites()))
.map(cm -> SimpleRegisterComb.fromModel(cm, cm.getMembre().getLicences())
.setCategorieInscrite(cm.getCategoriesInscrites()))
.collect().asList();
return uni
.call(l -> Mutiny.fetch(c.getGuests())
.map(guest -> guest.stream().map(SimpleRegisterComb::fromModel).toList())
.onItem().transformToMulti(Multi.createFrom()::iterable)
.filter(g -> !g.isTeam())
.onItem().call(guest -> Mutiny.fetch(guest.getCategoriesInscrites()))
.map(guest -> SimpleRegisterComb.fromModel(guest)
.setCategorieInscrite(guest.getCategoriesInscrites()))
.collect().asList()
.invoke(l::addAll));
});
@@ -270,12 +341,17 @@ public class CompetitionService {
model.getClub()))
.onItem().transformToMulti(Multi.createFrom()::iterable)
.onItem().call(combModel -> Mutiny.fetch(combModel.getMembre().getLicences()))
.map(combModel -> SimpleRegisterComb.fromModel(combModel, combModel.getMembre().getLicences()))
.onItem().call(combModel -> Mutiny.fetch(combModel.getCategoriesInscrites()))
.map(combModel -> SimpleRegisterComb.fromModel(combModel, combModel.getMembre().getLicences())
.setCategorieInscrite(combModel.getCategoriesInscrites()))
.collect().asList();
return membreService.getByAccountId(securityCtx.getSubject())
.chain(model -> registerRepository.find("competition.id = ?1 AND membre = ?2", id, model).firstResult()
.map(rm -> rm == null ? List.of() : List.of(SimpleRegisterComb.fromModel(rm, List.of()))));
.call(rm -> rm == null ? Uni.createFrom().voidItem() :
Mutiny.fetch(rm.getCategoriesInscrites()))
.map(rm -> rm == null ? List.of() : List.of(SimpleRegisterComb.fromModel(rm, List.of())
.setCategorieInscrite(rm.getCategoriesInscrites()))));
}
public Uni<SimpleRegisterComb> addRegisterComb(SecurityCtx securityCtx, Long id, RegisterRequestData data,
@@ -291,8 +367,12 @@ public class CompetitionService {
c.getBanMembre().remove(combModel.getId());
return Panache.withTransaction(() -> repository.persist(c));
})
.chain(combModel -> updateRegister(data, c, combModel, true)))
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences()));
.chain(combModel -> updateRegister(data, c, combModel, true, false)))
.call(r -> r.getCompetition().getSystem() == CompetitionSystem.INTERNAL ?
sRegister.sendRegisterNoFetch(r.getCompetition().getUuid(), r) : Uni.createFrom()
.voidItem())
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())
.setCategorieInscrite(r.getCategoriesInscrites()));
} else {
return permService.hasEditPerm(securityCtx, id)
.chain(c -> competitionGuestRepository.findById(data.getId() * -1)
@@ -303,21 +383,35 @@ public class CompetitionService {
model.setCompetition(c);
return model;
}))
.chain(model -> {
.invoke(model -> {
model.setFname(data.getFname());
model.setLname(data.getLname());
if (data.getLname().equals("__team"))
model.setLname("_team");
else
model.setLname(data.getLname());
model.setGenre(data.getGenre());
model.setClub(data.getClub());
model.setCountry(data.getCountry());
model.setWeight(data.getWeight());
model.setWeightReal(data.getWeightReal());
model.setCategorie(data.getCategorie());
return Panache.withTransaction(() -> competitionGuestRepository.persist(model))
.call(r -> model.getCompetition().getSystem() == CompetitionSystem.INTERNAL ?
sRegister.sendRegister(model.getCompetition().getUuid(),
r) : Uni.createFrom().voidItem());
if (model.getCompetition().getRequiredWeight().contains(model.getCategorie()))
model.setWeight(data.getWeight());
})
.map(SimpleRegisterComb::fromModel);
.call(g -> Mutiny.fetch(g.getCategoriesInscrites()))
.call(g -> catPresetRepository.list("competition = ?1 AND id IN ?2", g.getCompetition(),
data.getCategoriesInscrites())
.invoke(cats -> {
g.getCategoriesInscrites().clear();
g.getCategoriesInscrites().addAll(cats);
g.getCategoriesInscrites()
.removeIf(cat -> cat.getCategories().stream()
.noneMatch(e -> e.getCategorie().equals(g.getCategorie())));
}))
.chain(model -> Panache.withTransaction(() -> competitionGuestRepository.persist(model))
.call(r -> model.getCompetition().getSystem() == CompetitionSystem.INTERNAL ?
sRegister.sendRegisterNoFetch(model.getCompetition().getUuid(), r)
: Uni.createFrom().voidItem()))
.map(g -> SimpleRegisterComb.fromModel(g).setCategorieInscrite(g.getCategoriesInscrites()));
}
if ("club".equals(source))
return repository.findById(id)
@@ -326,7 +420,7 @@ public class CompetitionService {
|| !securityCtx.isClubAdmin())
throw new DForbiddenException();
if (new Date().before(cm.getStartRegister()) || new Date().after(cm.getEndRegister()))
throw new DBadRequestException("Inscription fermée");
throw new DBadRequestException(trad.t("inscription.fermee"));
}))
.chain(c -> findComb(data.getLicence(), data.getFname(), data.getLname())
.call(combModel -> Mutiny.fetch(combModel.getLicences()))
@@ -334,40 +428,123 @@ public class CompetitionService {
if (!securityCtx.isInClubGroup(model.getClub().getId()))
throw new DForbiddenException();
if (c.getBanMembre().contains(model.getId()))
throw new DForbiddenException(
"Vous n'avez pas le droit d'inscrire ce membre (par décision de l'administrateur de la compétition)");
throw new DForbiddenException(trad.t("insc.err1"));
}))
.chain(combModel -> updateRegister(data, c, combModel, false)))
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences()));
.chain(combModel -> updateRegister(data, c, combModel, false, false)))
.call(r -> r.getCompetition().getSystem() == CompetitionSystem.INTERNAL ?
sRegister.sendRegisterNoFetch(r.getCompetition().getUuid(), r) : Uni.createFrom()
.voidItem())
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())
.setCategorieInscrite(r.getCategoriesInscrites()));
return repository.findById(id)
.invoke(Unchecked.consumer(cm -> {
if (cm.getRegisterMode() != RegisterMode.FREE)
throw new DForbiddenException();
if (new Date().before(cm.getStartRegister()) || new Date().after(cm.getEndRegister()))
throw new DBadRequestException("Inscription fermée");
throw new DBadRequestException(trad.t("inscription.fermee"));
}))
.chain(c -> membreService.getByAccountId(securityCtx.getSubject())
.invoke(Unchecked.consumer(model -> {
if (c.getBanMembre().contains(model.getId()))
throw new DForbiddenException(
"Vous n'avez pas le droit de vous inscrire (par décision de l'administrateur de la compétition)");
throw new DForbiddenException(trad.t("insc.err2"));
}))
.chain(combModel -> updateRegister(data, c, combModel, false)))
.map(r -> SimpleRegisterComb.fromModel(r, List.of()));
.chain(combModel -> updateRegister(data, c, combModel, false, false)))
.call(r -> r.getCompetition().getSystem() == CompetitionSystem.INTERNAL ?
sRegister.sendRegisterNoFetch(r.getCompetition().getUuid(), r) : Uni.createFrom().voidItem())
.map(r -> SimpleRegisterComb.fromModel(r, List.of()).setCategorieInscrite(r.getCategoriesInscrites()));
}
public Uni<List<SimpleRegisterComb>> addRegistersComb(SecurityCtx securityCtx, Long id,
List<RegisterRequestData> datas,
String source) {
if (!"admin".equals(source))
return Uni.createFrom().failure(new DForbiddenException());
return permService.hasEditPerm(securityCtx, id)
.chain(cm -> Multi.createFrom().iterable(datas).onItem().transformToUni(data ->
makeImportUpdate(cm, data).onFailure().recoverWithItem(t -> {
SimpleRegisterComb errorComb = new SimpleRegisterComb();
errorComb.setLicence(-42);
errorComb.setFname("ERROR");
errorComb.setLname(t.getMessage());
return errorComb;
})).concatenate().collect().asList());
}
@WithSession
public Uni<SimpleRegisterComb> makeImportUpdate(CompetitionModel c, RegisterRequestData data) {
if (data.getLicence() == null || data.getLicence() != -1) { // not a guest
return findComb(data.getLicence(), data.getFname(), data.getLname())
.call(combModel -> Mutiny.fetch(combModel.getLicences()))
.call(combModel -> {
if (c.getBanMembre() == null)
c.setBanMembre(new ArrayList<>());
c.getBanMembre().remove(combModel.getId());
return Panache.withTransaction(() -> repository.persist(c));
})
.chain(combModel -> updateRegister(data, c, combModel, true, true))
.map(r -> SimpleRegisterComb.fromModel(r, r.getMembre().getLicences())
.setCategorieInscrite(r.getCategoriesInscrites()));
} else {
return findGuestOrInit(data.getFname(), data.getLname(), c)
.invoke(Unchecked.consumer(model -> {
if (data.getCategorie() == null)
throw new DBadRequestException(trad.t("categorie.requise"));
model.setCategorie(data.getCategorie());
if (data.getGenre() == null) {
if (model.getGenre() == null)
data.setGenre(Genre.NA);
} else
model.setGenre(data.getGenre());
if (data.getClub() == null) {
if (model.getClub() == null)
data.setClub("");
} else
model.setClub(data.getClub());
if (data.getCountry() == null) {
if (model.getCountry() == null)
data.setCountry("FR");
} else
model.setCountry(data.getCountry());
if (c.getRequiredWeight().contains(model.getCategorie())) {
if (data.getCountry() != null)
model.setWeight(data.getWeight());
}
}))
.call(g -> Mutiny.fetch(g.getCategoriesInscrites()))
.call(g -> catPresetRepository.list("competition = ?1 AND id IN ?2", c,
data.getCategoriesInscrites())
.invoke(cats -> {
g.getCategoriesInscrites().clear();
g.getCategoriesInscrites().addAll(cats);
g.getCategoriesInscrites().removeIf(cat -> cat.getCategories().stream()
.noneMatch(e -> e.getCategorie().equals(g.getCategorie())));
}))
.chain(model -> Panache.withTransaction(() -> competitionGuestRepository.persist(model))
.call(r -> c.getSystem() == CompetitionSystem.INTERNAL ?
sRegister.sendRegister(c.getUuid(), r) : Uni.createFrom().voidItem()))
.map(g -> SimpleRegisterComb.fromModel(g).setCategorieInscrite(g.getCategoriesInscrites()));
}
}
private Uni<RegisterModel> updateRegister(RegisterRequestData data, CompetitionModel c,
MembreModel combModel, boolean admin) {
MembreModel combModel, boolean admin, boolean append) {
return registerRepository.find("competition = ?1 AND membre = ?2", c, combModel).firstResult()
.onFailure().recoverWithNull()
.map(Unchecked.function(r -> {
if (r != null) {
if (!admin && r.isLockEdit())
throw new DForbiddenException(
"Modification bloquée par l'administrateur de la compétition");
r.setWeight(data.getWeight());
r.setOverCategory(data.getOverCategory());
throw new DForbiddenException(trad.t("insc.err3"));
if (data.getOverCategory() != null || !append)
if (data.getOverCategory() == null)
r.setOverCategory(0);
else
r.setOverCategory(data.getOverCategory());
r.setCategorie(
(combModel.getBirth_date() == null) ? combModel.getCategorie() :
Utils.getCategoryFormBirthDate(combModel.getBirth_date(),
@@ -375,50 +552,91 @@ public class CompetitionService {
int days = Utils.getDaysBeforeCompetition(c.getDate());
if (days > -7)
r.setClub(combModel.getClub());
if (admin)
if (c.getRequiredWeight().contains(r.getCategorie2()))
if (data.getCountry() != null || !append)
r.setWeight(data.getWeight());
if (admin) {
r.setWeightReal(data.getWeightReal());
r.setLockEdit(data.isLockEdit());
}
} else {
r = new RegisterModel(c, combModel, data.getWeight(), data.getOverCategory(),
(combModel.getBirth_date() == null) ? combModel.getCategorie() :
Utils.getCategoryFormBirthDate(combModel.getBirth_date(),
c.getDate()),
(combModel.getClub() == null) ? null : combModel.getClub());
if (admin)
if (admin) {
r.setWeightReal(data.getWeightReal());
r.setLockEdit(data.isLockEdit());
else
} else
r.setLockEdit(false);
}
if (c.getSystem() == CompetitionSystem.SAFCA) {
SReqRegister.sendIfNeed(serverCustom.clients,
new CompetitionData.SimpleRegister(r.getMembre().getId(),
r.getOverCategory(), r.getWeight(), r.getCategorie(),
r.getOverCategory(), r.getWeight2(), r.getCategorie(),
(r.getClub() == null) ? null : r.getClub().getId(),
(r.getClub() == null) ? null : r.getClub().getName()), c.getId());
}
return r;
}))
.call(r -> Mutiny.fetch(r.getCategoriesInscrites()).chain(__ ->
catPresetRepository.list("competition = ?1 AND id IN ?2", c, data.getCategoriesInscrites())
.invoke(cats -> {
if (data.isQuick()) {
cats.removeIf(cat -> r.getCategoriesInscrites().stream()
.anyMatch(cp -> cp.equals(cat)));
} else {
r.getCategoriesInscrites().clear();
}
r.getCategoriesInscrites().addAll(cats);
r.getCategoriesInscrites()
.removeIf(cat -> cat.getCategories().stream()
.noneMatch(e -> e.getCategorie().equals(r.getCategorie2())));
})))
.chain(r -> Panache.withTransaction(() -> registerRepository.persist(r)))
.call(r -> c.getSystem() == CompetitionSystem.INTERNAL ?
sRegister.sendRegister(c.getUuid(), r) : Uni.createFrom().voidItem());
}
private Uni<CompetitionGuestModel> findGuestOrInit(String fname, String lname, CompetitionModel competition) {
if (fname == null || lname == null)
return Uni.createFrom().failure(new DBadRequestException(trad.t("nom.et.prenom.requis")));
return competitionGuestRepository.find(
"unaccent(lname) ILIKE unaccent(?1) AND unaccent(fname) ILIKE unaccent(?2) AND competition = ?3",
lname, fname, competition).firstResult()
.map(guestModel -> {
if (guestModel == null) {
CompetitionGuestModel model = new CompetitionGuestModel();
model.setFname(fname);
if (lname.equals("__team"))
model.setLname("_team");
else
model.setLname(lname);
model.setCompetition(competition);
return model;
}
return guestModel;
});
}
private Uni<MembreModel> findComb(Long licence, String fname, String lname) {
if (licence != null && licence > 0) {
return combRepository.find("licence = ?1", licence).firstResult()
.invoke(Unchecked.consumer(combModel -> {
if (combModel == null)
throw new DForbiddenException("Licence " + licence + " non trouvé");
throw new DForbiddenException(String.format(trad.t("licence.non.trouve"), licence));
}));
} else {
if (fname == null || lname == null)
return Uni.createFrom().failure(new DBadRequestException("Nom et prénom requis"));
return Uni.createFrom().failure(new DBadRequestException(trad.t("nom.et.prenom.requis")));
return combRepository.find("unaccent(lname) ILIKE unaccent(?1) AND unaccent(fname) ILIKE unaccent(?2)",
lname,
fname).firstResult()
.invoke(Unchecked.consumer(combModel -> {
if (combModel == null)
throw new DForbiddenException("Combattant " + fname + " " + lname + " non trouvé");
throw new DForbiddenException(String.format(trad.t("combattant.non.trouve"), fname, lname));
}));
}
}
@@ -445,12 +663,13 @@ public class CompetitionService {
|| !securityCtx.isClubAdmin())
throw new DForbiddenException();
if (new Date().before(cm.getStartRegister()) || new Date().after(cm.getEndRegister()))
throw new DBadRequestException("Inscription fermée");
throw new DBadRequestException(trad.t("inscription.fermee"));
}))
.call(cm -> membreService.getById(combId)
.invoke(Unchecked.consumer(model -> {
if (model == null)
throw new DNotFoundException("Membre " + combId + " n'existe pas");
throw new DNotFoundException(
String.format(trad.t("le.membre.n.existe.pas"), combId));
if (!securityCtx.isInClubGroup(model.getClub().getId()))
throw new DForbiddenException();
})))
@@ -462,7 +681,7 @@ public class CompetitionService {
if (cm.getRegisterMode() != RegisterMode.FREE || !Objects.equals(model.getId(), combId))
throw new DForbiddenException();
if (new Date().before(cm.getStartRegister()) || new Date().after(cm.getEndRegister()))
throw new DBadRequestException("Inscription fermée");
throw new DBadRequestException(trad.t("inscription.fermee"));
})))
.chain(c -> deleteRegister(combId, c, false));
}
@@ -470,7 +689,7 @@ public class CompetitionService {
private Uni<Void> deleteRegister(Long combId, CompetitionModel c, boolean admin) {
if (admin && combId < 0) {
return competitionGuestRepository.find("competition = ?1 AND id = ?2", c, combId * -1).firstResult()
.onFailure().transform(t -> new DBadRequestException("Combattant non inscrit"))
.onFailure().transform(t -> new DBadRequestException(trad.t("combattant.non.inscrit")))
.call(Unchecked.function(
model -> Panache.withTransaction(() -> competitionGuestRepository.delete(model))
.call(r -> c.getSystem() == CompetitionSystem.INTERNAL ?
@@ -479,10 +698,10 @@ public class CompetitionService {
.replaceWithVoid();
}
return registerRepository.find("competition = ?1 AND membre.id = ?2", c, combId).firstResult()
.onFailure().transform(t -> new DBadRequestException("Combattant non inscrit"))
.onFailure().transform(t -> new DBadRequestException(trad.t("combattant.non.inscrit")))
.call(Unchecked.function(registerModel -> {
if (!admin && registerModel.isLockEdit())
throw new DForbiddenException("Modification bloquée par l'administrateur de la compétition");
throw new DForbiddenException(trad.t("insc.err3"));
return Panache.withTransaction(() -> registerRepository.delete(registerModel))
.call(r -> c.getSystem() == CompetitionSystem.INTERNAL ?
sRegister.sendRegisterRemove(c.getUuid(), combId) : Uni.createFrom().voidItem());
@@ -513,6 +732,64 @@ public class CompetitionService {
.call(__ -> cache.invalidate(id));
}
public Uni<SimpleCompetData> getInternalData(SecurityCtx securityCtx, Long id) {
return permService.hasEditPerm(securityCtx, id)
.invoke(Unchecked.consumer(cm -> {
if (cm.getSystem() != CompetitionSystem.INTERNAL)
throw new DBadRequestException(trad.t("competition.is.not.internal"));
}))
.chain(competitionModel -> {
SimpleCompetData data = SimpleCompetData.fromModel(competitionModel);
return vertx.getOrCreateContext().executeBlocking(() -> {
if (competitionModel.getAdmin() != null)
data.setAdmin(
competitionModel.getAdmin().stream().map(uuid -> keycloakService.getUserById(uuid))
.filter(Optional::isPresent)
.map(user -> user.get().getUsername())
.toList());
if (competitionModel.getTable() != null)
data.setTable(
competitionModel.getTable().stream().map(uuid -> keycloakService.getUserById(uuid))
.filter(Optional::isPresent)
.map(user -> user.get().getUsername())
.toList());
return data;
});
});
}
public Uni<Void> setInternalData(SecurityCtx securityCtx, SimpleCompetData data) {
return permService.hasEditPerm(securityCtx, data.getId())
.invoke(Unchecked.consumer(cm -> {
if (cm.getSystem() != CompetitionSystem.INTERNAL)
throw new DBadRequestException(trad.t("competition.is.not.internal"));
}))
.chain(cm -> vertx.getOrCreateContext().executeBlocking(() -> {
ArrayList<String> admin = new ArrayList<>();
ArrayList<String> table = new ArrayList<>();
for (String username : data.getAdmin()) {
Optional<UserRepresentation> opt = keycloakService.getUser(username);
if (opt.isEmpty())
throw new DBadRequestException(String.format(trad.t("user.not.found"), username));
admin.add(opt.get().getId());
}
for (String username : data.getTable()) {
Optional<UserRepresentation> opt = keycloakService.getUser(username);
if (opt.isEmpty())
throw new DBadRequestException(String.format(trad.t("user.not.found"), username));
table.add(opt.get().getId());
}
cm.setAdmin(admin);
cm.setTable(table);
cm.setConfig(data.getConfigForInternal());
return cm;
}))
.chain(cm -> Panache.withTransaction(() -> repository.persist(cm)))
.replaceWithVoid();
}
public Uni<SimpleCompetData> getSafcaData(SecurityCtx securityCtx, Long id) {
return permService.getSafcaConfig(id)
.call(Unchecked.function(o -> {
@@ -549,13 +826,13 @@ public class CompetitionService {
for (String username : data.getAdmin()) {
Optional<UserRepresentation> opt = keycloakService.getUser(username);
if (opt.isEmpty())
throw new DBadRequestException("User " + username + " not found");
throw new DBadRequestException(String.format(trad.t("user.not.found"), username));
admin.add(UUID.fromString(opt.get().getId()));
}
for (String username : data.getTable()) {
Optional<UserRepresentation> opt = keycloakService.getUser(username);
if (opt.isEmpty())
throw new DBadRequestException("User " + username + " not found");
throw new DBadRequestException(String.format(trad.t("user.not.found"), username));
table.add(UUID.fromString(opt.get().getId()));
}
@@ -581,6 +858,12 @@ public class CompetitionService {
.call(__ -> cache.invalidate(data.getId()));
}
public Uni<List<PresetData>> getPresetsForCompetition(SecurityCtx securityCtx, Long id) {
return permService.hasViewPerm(securityCtx, id)
.chain(cm -> Mutiny.fetch(cm.getCatPreset()))
.map(p -> p.stream().map(PresetData::fromModel).toList());
}
public Uni<Response> unregisterHelloAsso(NotificationData data) {
if (!data.getState().equals("Refunded"))
return Uni.createFrom().item(Response.ok().build());
@@ -614,8 +897,8 @@ public class CompetitionService {
public Uni<Response> registerHelloAsso(NotificationData data) {
String organizationSlug = data.getOrganizationSlug();
String formSlug = data.getFormSlug();
RegisterRequestData req = new RegisterRequestData(null, "", "", null, 0, false, null, Categorie.CADET, Genre.NA,
null, "fr");
RegisterRequestData req = new RegisterRequestData(null, "", "", null, null, 0, false, new ArrayList<>(), null,
Categorie.CADET, Genre.NA, null, "fr", false);
return repository.find("data1 = ?1 AND data2 = ?2", organizationSlug, formSlug).firstResult()
.onFailure().recoverWithNull()
@@ -649,7 +932,7 @@ public class CompetitionService {
.call(m -> Panache.withTransaction(() ->
helloAssoRepository.persist(
new HelloAssoRegisterModel(cm, m, data.getId()))))
.chain(m -> updateRegister(req, cm, m, true)))
.chain(m -> updateRegister(req, cm, m, true, true)))
.onFailure().recoverWithItem(throwable -> {
fail.add("%s %s - licence n°%d".formatted(item.getUser().getLastName(),
item.getUser().getFirstName(), optional.get()));

View File

@@ -25,6 +25,11 @@ public class HelloAssoTokenService {
// Récupère un token valide (en le rafraîchissant si nécessaire)
public Uni<String> getValidAccessToken() {
if (clientId == null || clientSecret == null || clientId.isEmpty() || clientSecret.isEmpty() || clientId.equalsIgnoreCase(
"null") || clientSecret.equalsIgnoreCase("null")) {
LOG.error("Client ID ou Client Secret non configuré pour HelloAsso");
return Uni.createFrom().failure(new IllegalStateException("HelloAsso client credentials not configured"));
}
if (currentToken == null || currentToken.isExpired()) {
return fetchNewToken(clientId, clientSecret);
}

View File

@@ -377,11 +377,19 @@ public class KeycloakService {
public Optional<UserRepresentation> getUser(UUID userId) {
UserResource user = keycloak.realm(realm).users().get(userId.toString());
if (user == null)
return getUserById(userId.toString());
}
public Optional<UserRepresentation> getUserById(String userId) {
try {
UserResource user = keycloak.realm(realm).users().get(userId);
if (user == null)
return Optional.empty();
else
return Optional.of(user.toRepresentation());
} catch (Exception e) {
return Optional.empty();
else
return Optional.of(user.toRepresentation());
}
}
private String makeLogin(MembreModel model) {

View File

@@ -47,6 +47,9 @@ public class LicenceService {
@Inject
CheckoutService checkoutService;
@Inject
TradService trad;
public Uni<List<LicenceModel>> getLicence(long id, Consumer<MembreModel> checkPerm) {
return combRepository.findById(id).invoke(checkPerm)
.chain(combRepository -> Mutiny.fetch(combRepository.getLicences()));
@@ -119,7 +122,9 @@ public class LicenceService {
.call(genLicenceNumberAndAccountIfNeed())
: Uni.createFrom().nullItem()
))
.call(licenceModel -> ls.logA(LogModel.ActionType.ADD, membreModel.getObjectName(),
.call(licenceModel -> ls.logA(LogModel.ActionType.ADD,
"%s (valid=%b, pay=%b, %s)" .formatted(membreModel.getObjectName(), model.isValidate(),
model.isPay(), model.getCertificate()),
licenceModel));
});
} else {
@@ -157,7 +162,7 @@ public class LicenceService {
return repository.list("membre.id IN ?1 AND saison = ?2 AND pay = FALSE", ids, Utils.getSaison())
.invoke(Unchecked.consumer(models -> {
if (models.size() != ids.size())
throw new DBadRequestException("Erreur lors de lalection des membres");
throw new DBadRequestException(trad.t("erreur.lors.de.la.selection.des.membres"));
}))
.call(models -> {
Uni<?> uni = Uni.createFrom().nullItem();
@@ -174,7 +179,7 @@ public class LicenceService {
.call(__ -> checkoutService.canDeleteLicence(id)
.invoke(Unchecked.consumer(b -> {
if (!b) throw new DBadRequestException(
"Impossible de supprimer une licence pour laquelle un paiement est en cours");
trad.t("licence.rm.err1"));
})))
.call(model -> ls.logADelete(model))
.chain(model -> Panache.withTransaction(() -> repository.delete(model)));
@@ -186,7 +191,7 @@ public class LicenceService {
return repository.find("saison = ?1 AND membre = ?2", Utils.getSaison(), membreModel).count()
.invoke(Unchecked.consumer(count -> {
if (count > 0)
throw new DBadRequestException("Licence déjà demandée");
throw new DBadRequestException(trad.t("licence.deja.demandee"));
})).chain(__ -> combRepository.findById(id).chain(membreModel2 -> {
LicenceModel model = new LicenceModel();
model.setClub_id((membreModel2.getClub() == null) ? null : membreModel2.getClub().getId());
@@ -215,13 +220,13 @@ public class LicenceService {
.call(__ -> checkoutService.canDeleteLicence(id)
.invoke(Unchecked.consumer(b -> {
if (!b) throw new DBadRequestException(
"Impossible de supprimer une licence pour laquelle un paiement est en cours");
trad.t("licence.rm.err1"));
})))
.invoke(Unchecked.consumer(licenceModel -> {
if (licenceModel.isValidate())
throw new DBadRequestException("Impossible de supprimer une licence déjà validée");
throw new DBadRequestException(trad.t("impossible.de.supprimer.une.licence.deja.validee"));
if (licenceModel.isPay())
throw new DBadRequestException("Impossible de supprimer une licence déjà payée");
throw new DBadRequestException(trad.t("impossible.de.supprimer.une.licence.deja.payee"));
}))
.call(model -> ls.logADelete(model))
.chain(__ -> Panache.withTransaction(() -> repository.deleteById(id)));

View File

@@ -7,10 +7,7 @@ import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.net2.ServerCustom;
import fr.titionfire.ffsaf.net2.data.SimpleCombModel;
import fr.titionfire.ffsaf.net2.request.SReqComb;
import fr.titionfire.ffsaf.rest.data.MeData;
import fr.titionfire.ffsaf.rest.data.SimpleLicence;
import fr.titionfire.ffsaf.rest.data.SimpleMembre;
import fr.titionfire.ffsaf.rest.data.SimpleMembreInOutData;
import fr.titionfire.ffsaf.rest.data.*;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.exception.DInternalError;
@@ -65,6 +62,9 @@ public class MembreService {
@Inject
LoggerService ls;
@Inject
TradService trad;
public SimpleCombModel find(int licence, String np) throws Throwable {
return VertxContextSupport.subscribeAndAwait(() -> Panache.withTransaction(() ->
repository.find(
@@ -180,15 +180,21 @@ public class MembreService {
Sort sort = getSort(order);
if (sort == null)
return Uni.createFrom().failure(new DInternalError("Erreur lors calcul du trie"));
return Uni.createFrom().failure(new DInternalError(trad.t("erreur.lors.calcul.du.trie")));
String finalSearch = search;
return getLicenceListe(licenceRequest, payState)
.map(l -> l.stream().map(l2 -> l2.getMembre().getId()).toList())
.chain(ids -> {
.chain(l -> {
PanacheQuery<MembreModel> query;
query = repository.find(queryStr, sort, finalSearch, ids, club).page(Page.ofSize(limit));
return getPageResult(query, limit, page);
query = repository.find(queryStr, sort, finalSearch,
l.stream().map(l2 -> l2.getMembre().getId()).toList(), club).page(Page.ofSize(limit));
return getPageResult(query, limit, page)
.call(r -> (r.getResult().isEmpty() || !l.isEmpty() ?
Uni.createFrom().item(l) :
licenceRepository.list("saison = ?1 AND membre.id IN ?2", Utils.getSaison(),
r.getResult().stream().map(SimpleMembre::getId).toList()))
.invoke(l2 -> r.setAdditionalData(l2.stream().map(SimpleLicence::fromModel)))
);
});
}
@@ -199,7 +205,7 @@ public class MembreService {
.call(result -> query.count().invoke(result::setResult_count))
.call(result -> query.pageCount()
.invoke(Unchecked.consumer(pages -> {
if (page > pages) throw new DBadRequestException("Page out of range");
if (page > pages) throw new DBadRequestException(trad.t("page.out.of.range"));
}))
.invoke(result::setPage_count))
.call(result -> query.page(Page.of(page, limit)).list()
@@ -210,7 +216,7 @@ public class MembreService {
public Uni<List<SimpleMembreInOutData>> getAllExport(String subject) {
return repository.find("userId = ?1", subject).firstResult()
.chain(membreModel -> repository.list("club = ?1", membreModel.getClub()))
.chain(membres -> licenceRepository.list("saison = ?1 AND membre IN ?2", Utils.getSaison(), membres)
.chain(membres -> licenceRepository.list("membre IN ?1", membres)
.map(l -> membres.stream().map(m -> SimpleMembreInOutData.fromModel(m, l)).toList()));
}
@@ -244,8 +250,8 @@ public class MembreService {
for (MembreModel membreModel : membres) {
if (!Objects.equals(membreModel.getClub(), clubModel.get())) {
LOGGER.info("Similar membres found: " + membreModel);
throw new DForbiddenException(
"Le membre n°" + membreModel.getLicence() + " n'appartient pas à votre club");
throw new DForbiddenException(String.format(trad.t("le.membre.appartient.pas.a.votre.club"),
membreModel.getLicence()));
}
}
Uni<Void> uniResult = Uni.createFrom().voidItem();
@@ -272,7 +278,8 @@ public class MembreService {
if (model.getLicence() != null && !model.getLicence().equals(dataIn.getLicence())) {
LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException(
"Email '" + model.getEmail() + "' déja utilisé par " + model.getFname() + " " + model.getFname());
String.format(trad.t("email.deja.utilise.par"), model.getEmail(),
model.getFname(), model.getLname()));
}
if (StringSimilarity.similarity(model.getLname().toUpperCase(),
@@ -280,7 +287,8 @@ public class MembreService {
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3) {
LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException(
"Email '" + model.getEmail() + "' déja utilisé par " + model.getFname() + " " + model.getFname());
String.format(trad.t("email.deja.utilise.par"), model.getEmail(),
model.getFname(), model.getLname()));
}
}
@@ -291,8 +299,8 @@ public class MembreService {
model.getFname().toUpperCase(), dataIn.getPrenom().toUpperCase()) > 3)) {
LOGGER.info("Similar membres found: " + model);
throw new DBadRequestException(
"Pour enregistrer un nouveau membre, veuillez laisser le champ licence vide. (tentative de changement non-autotiser de nom sur la licence "
+ model.getLicence() + " pour " + model.getFname() + " " + model.getFname() + ")");
String.format(trad.t("try.edit.licence"), model.getLicence(), model.getFname(),
model.getFname()));
}
ls.logChange("Nom", model.getLname(), dataIn.getNom().toUpperCase(), model);
@@ -366,7 +374,7 @@ public class MembreService {
.call(__ -> repository.count("email LIKE ?1 AND id != ?2", membre.getEmail(), id)
.invoke(Unchecked.consumer(c -> {
if (c > 0 && !membre.getEmail().isBlank())
throw new DBadRequestException("Email déjà utilisé");
throw new DBadRequestException(trad.t("email.deja.utilise"));
})))
.chain(membreModel -> clubRepository.findById(membre.getClub())
.map(club -> new Pair<>(membreModel, club)))
@@ -388,7 +396,7 @@ public class MembreService {
.call(__ -> repository.count("email LIKE ?1 AND id != ?2", membre.getEmail(), id)
.invoke(Unchecked.consumer(c -> {
if (c > 0 && !membre.getEmail().isBlank())
throw new DBadRequestException("Email déjà utilisé");
throw new DBadRequestException(trad.t("email.deja.utilise"));
})))
.invoke(Unchecked.consumer(membreModel -> {
if (!securityCtx.isInClubGroup(membreModel.getClub().getId()))
@@ -397,7 +405,7 @@ public class MembreService {
membre.getLname().toUpperCase()) > 3 || StringSimilarity.similarity(
membreModel.getFname().toUpperCase(), membre.getFname().toUpperCase()) > 3) {
throw new DBadRequestException(
"Pour enregistrer un nouveau membre, veuillez utilisez le bouton prévue a cette effet.");
trad.t("regiter.new.membre"));
}
}))
.invoke(Unchecked.consumer(membreModel -> {
@@ -406,7 +414,7 @@ public class MembreService {
else if (securityCtx.roleHas("club_secretaire")) source = RoleAsso.SECRETAIRE;
else if (securityCtx.roleHas("club_respo_intra")) source = RoleAsso.MEMBREBUREAU;
if (!membre.getRole().equals(membreModel.getRole()) && membre.getRole().level >= source.level)
throw new DForbiddenException("Permission insuffisante");
throw new DForbiddenException(trad.t("permission.insuffisante"));
}))
.onItem().transform(target -> {
if (!securityCtx.getSubject().equals(target.getUserId())) {
@@ -479,7 +487,8 @@ public class MembreService {
return clubRepository.findById(input.getClub())
.call(__ -> repository.count("email LIKE ?1", input.getEmail())
.invoke(Unchecked.consumer(c -> {
if (c > 0) throw new DBadRequestException("Email déjà utilisé");
if (c > 0 && input.getEmail() != null && !input.getEmail().isBlank())
throw new DBadRequestException(trad.t("email.deja.utilise"));
})))
.chain(clubModel -> {
MembreModel model = getMembreModel(input, clubModel);
@@ -495,7 +504,7 @@ public class MembreService {
return repository.find("userId = ?1", subject).firstResult()
.call(__ -> repository.count("email LIKE ?1", input.getEmail())
.invoke(Unchecked.consumer(c -> {
if (c > 0) throw new DBadRequestException("Email déjà utilisé");
if (c > 0) throw new DBadRequestException(trad.t("email.deja.utilise"));
})))
.call(membreModel ->
repository.count(
@@ -503,7 +512,7 @@ public class MembreService {
input.getLname(), input.getFname(), membreModel.getClub())
.invoke(Unchecked.consumer(c -> {
if (c > 0)
throw new DBadRequestException("Membre déjà existent");
throw new DBadRequestException(trad.t("membre.deja.existent"));
})))
.chain(membreModel -> {
MembreModel model = getMembreModel(input, membreModel.getClub());
@@ -536,13 +545,13 @@ public class MembreService {
.invoke(Unchecked.consumer(membreModel -> {
if (membreModel.getLicence() != null) {
throw new DBadRequestException(
"Impossible de supprimer un membre qui a déjà un numéro de licence");
trad.t("membre.rm.err1"));
}
}))
.call(membreModel -> licenceRepository.find("membre = ?1", membreModel).count()
.invoke(Unchecked.consumer(l -> {
if (l > 0)
throw new DBadRequestException("Impossible de supprimer un membre avec des licences");
throw new DBadRequestException(trad.t("membre.rm.err2"));
})))
.call(membreModel -> (membreModel.getUserId() != null) ?
keycloakService.removeAccount(membreModel.getUserId()) : Uni.createFrom().nullItem())
@@ -588,10 +597,13 @@ public class MembreService {
public Uni<MeData> getMembre(String subject) {
MeData meData = new MeData();
return repository.find("userId = ?1", subject).firstResult()
.invoke(meData::setMembre)
.chain(membreModel -> Mutiny.fetch(membreModel.getLicences()))
.map(licences -> licences.stream().map(SimpleLicence::fromModel).toList())
.invoke(meData::setLicences)
.invoke(m -> meData.setMembre(m, trad))
.call(membreModel -> Mutiny.fetch(membreModel.getLicences())
.map(licences -> licences.stream().map(SimpleLicence::fromModel).toList())
.invoke(meData::setLicences))
.call(membreModel -> Mutiny.fetch(membreModel.getSelections())
.map(licences -> licences.stream().map(SimpleSelection::fromModel).toList())
.invoke(meData::setSelections))
.map(__ -> meData);
}

View File

@@ -46,6 +46,9 @@ public class PDFService {
@ConfigProperty(name = "pdf-maker.sign-file")
String sign_file;
@Inject
TradService trad;
public Uni<Response> getLicencePdf(String subject) {
return getLicencePdf(combRepository.find("userId = ?1", subject).firstResult()
@@ -58,7 +61,7 @@ public class PDFService {
LicenceModel licence = m.getLicences().stream()
.filter(licenceModel -> licenceModel.getSaison() == Utils.getSaison() && licenceModel.isValidate())
.findFirst()
.orElseThrow(() -> new DNotFoundException("Pas de licence pour la saison en cours"));
.orElseThrow(() -> new DNotFoundException(trad.t("pas.de.licence.pour.la.saison.en.cours")));
try {
byte[] buff = make_pdf(m, licence);
@@ -119,7 +122,7 @@ public class PDFService {
combRepository.find("userId = ?1", subject).firstResult()
.invoke(Unchecked.consumer(m -> {
if (m == null || m.getClub() == null)
throw new DNotFoundException("Club non trouvé");
throw new DNotFoundException(trad.t("club.non.trouve"));
}))
.map(MembreModel::getClub)
.call(m -> Mutiny.fetch(m.getAffiliations())));
@@ -130,7 +133,7 @@ public class PDFService {
clubRepository.findById(id)
.invoke(Unchecked.consumer(m -> {
if (m == null)
throw new DNotFoundException("Club non trouvé");
throw new DNotFoundException(trad.t("club.non.trouve"));
}))
.call(m -> Mutiny.fetch(m.getAffiliations())));
}
@@ -141,7 +144,7 @@ public class PDFService {
.map(Unchecked.function(m -> {
if (m.getAffiliations().stream()
.noneMatch(licenceModel -> licenceModel.getSaison() == Utils.getSaison()))
throw new DNotFoundException("Pas d'affiliation pour la saison en cours");
throw new DNotFoundException(trad.t("pas.d.affiliation.pour.la.saison.en.cours"));
try {
byte[] buff = make_pdf(m);

View File

@@ -2,10 +2,13 @@ package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.*;
import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.domain.entity.MatchModelExtend;
import fr.titionfire.ffsaf.rest.data.ResultCategoryData;
import fr.titionfire.ffsaf.rest.exception.DBadRequestException;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.utils.*;
import io.quarkus.cache.Cache;
import io.quarkus.cache.CacheName;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.runtime.annotations.RegisterForReflection;
import io.smallrye.mutiny.Multi;
@@ -14,6 +17,7 @@ import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.Builder;
import lombok.Data;
import org.hibernate.reactive.mutiny.Mutiny;
import java.util.*;
@@ -45,7 +49,16 @@ public class ResultService {
@Inject
MatchRepository matchRepository;
private static final ResourceBundle BUNDLE = ResourceBundle.getBundle("lang.String");
@Inject
CardRepository cardRepository;
@Inject
TradService trad;
@Inject
@CacheName("club-classement")
Cache cacheClubClassement;
private static final HashMap<String, Long> cacheClubClassementExp = new HashMap<>();
private static final HashMap<Long, String> combTempIds = new HashMap<>();
@@ -93,12 +106,21 @@ public class ResultService {
public Uni<List<Object[]>> getList(SecurityCtx securityCtx) {
return membreService.getByAccountId(securityCtx.getSubject())
.chain(m -> registerRepository.list("membre = ?1", m))
.chain(m -> registerRepository.list(
"membre = ?1 OR (TRUE = ?2 AND membre.club = ?3)",
m, securityCtx.isClubAdmin(), m.getClub()))
.onItem().transformToMulti(Multi.createFrom()::iterable)
.onItem().call(r -> Mutiny.fetch(r.getCompetition()))
.onItem().transform(r -> new Object[]{r.getCompetition().getUuid(), r.getCompetition().getName(),
r.getCompetition().getDate()})
.collect().asList();
.onItem().transform(RegisterModel::getCompetition)
.collect().asList()
.chain(l -> compRepository.list("owner = ?1 OR ?1 IN admin", securityCtx.getSubject())
.map(l2 -> Stream.concat(l.stream(), l2.stream()).distinct()
.map(c -> new Object[]{c.getUuid(), c.getName(), c.getDate()}).toList())
);
}
public Uni<HashMap<String, Long>> getCategoryList(String uuid, SecurityCtx securityCtx) {
return hasAccess(uuid, securityCtx).chain(__ -> getCategoryList(uuid));
}
public Uni<HashMap<String, Long>> getCategoryList(String uuid) {
@@ -113,39 +135,44 @@ public class ResultService {
}
public Uni<ResultCategoryData> getCategory(String uuid, long poule, SecurityCtx securityCtx) {
return hasAccess(uuid, securityCtx).chain(r ->
matchRepository.list("category.compet.uuid = ?1 AND category.id = ?2", uuid, poule)
.call(list -> list.isEmpty() ? Uni.createFrom().voidItem() :
Mutiny.fetch(list.get(0).getCategory().getTree()))
.map(list -> getData(list, r.getMembre())));
return hasAccess(uuid, securityCtx).chain(membreModel -> getData(uuid, poule, membreModel));
}
public Uni<ResultCategoryData> getCategory(String uuid, long poule) {
return getData(uuid, poule, null);
}
private Uni<ResultCategoryData> getData(String uuid, long poule, MembreModel membreModel) {
List<CardModel> cards = new ArrayList<>();
return matchRepository.list("category.compet.uuid = ?1 AND category.id = ?2", uuid, poule)
.call(list -> list.isEmpty() ? Uni.createFrom().voidItem() :
Mutiny.fetch(list.get(0).getCategory().getTree()))
.map(list -> getData(list, null));
.chain(list -> cardRepository.list("competition.uuid = ?1", uuid).invoke(cards::addAll)
.map(c -> list.stream().map(m -> new MatchModelExtend(m, c)).toList()))
.map(matchModels -> {
ResultCategoryData out = new ResultCategoryData();
CategoryModel categoryModel = matchModels.get(0).getCategory();
out.setName(categoryModel.getName());
out.setType(categoryModel.getType());
out.setLiceName(categoryModel.getLiceName() == null ? new String[]{} : categoryModel.getLiceName()
.split(";"));
out.setGenTime(System.currentTimeMillis());
out.setTreeIsClassement(categoryModel.isTreeAreClassement());
getArray2(matchModels, membreModel, out);
getTree(categoryModel.getTree(), membreModel, cards, out);
getClassementArray(categoryModel, membreModel, cards, out);
return out;
});
}
private ResultCategoryData getData(List<MatchModel> matchModels, MembreModel membreModel) {
ResultCategoryData out = new ResultCategoryData();
public void getArray2(List<MatchModelExtend> matchModels_, MembreModel membreModel, ResultCategoryData out) {
List<MatchModelExtend> matchModels = matchModels_.stream().filter(o -> o.getCategory_ord() >= 0).toList();
CategoryModel categoryModel = matchModels.get(0).getCategory();
out.setName(categoryModel.getName());
out.setType(categoryModel.getType());
out.setLiceName(categoryModel.getLiceName() == null ? new String[]{} : categoryModel.getLiceName().split(";"));
out.setGenTime(System.currentTimeMillis());
getArray2(matchModels, membreModel, out);
getTree(categoryModel.getTree(), membreModel, out);
return out;
}
private void getArray2(List<MatchModel> matchModels_, MembreModel membreModel, ResultCategoryData out) {
List<MatchModel> matchModels = matchModels_.stream().filter(o -> o.getCategory_ord() >= 0).toList();
HashMap<Character, List<MatchModel>> matchMap = new HashMap<>();
for (MatchModel model : matchModels) {
HashMap<Character, List<MatchModelExtend>> matchMap = new HashMap<>();
for (MatchModelExtend model : matchModels) {
char g = model.getPoule();
if (!matchMap.containsKey(g))
matchMap.put(g, new ArrayList<>());
@@ -154,7 +181,7 @@ public class ResultService {
matchMap.forEach((c, matchEntities) -> {
List<ResultCategoryData.PouleArrayData> matchs = matchEntities.stream()
.sorted(Comparator.comparing(MatchModel::getCategory_ord))
.sorted(Comparator.comparing(MatchModelExtend::getCategory_ord))
.map(o -> ResultCategoryData.PouleArrayData.fromModel(o, membreModel,
ResultPrivacy.REGISTERED_ONLY_NO_DETAILS))
.toList();
@@ -164,46 +191,26 @@ public class ResultService {
.distinct()
.filter(Objects::nonNull)
.map(comb -> {
AtomicInteger w = new AtomicInteger(0);
AtomicInteger pointMake = new AtomicInteger(0);
AtomicInteger pointTake = new AtomicInteger(0);
matchEntities.stream()
.filter(m -> m.isEnd() && (m.isC1(comb) || m.isC2(comb)))
.forEach(matchModel -> {
int win = matchModel.win();
if ((matchModel.isC1(comb) && win > 0) || matchModel.isC2(comb) && win < 0)
w.getAndIncrement();
for (ScoreEmbeddable score : matchModel.getScores()) {
if (score.getS1() <= -900 || score.getS2() <= -900)
continue;
if (matchModel.isC1(comb)) {
pointMake.addAndGet(score.getS1());
pointTake.addAndGet(score.getS2());
} else {
pointMake.addAndGet(score.getS2());
pointTake.addAndGet(score.getS1());
}
}
});
float pointRate = (pointTake.get() == 0) ? pointMake.get() : (float) pointMake.get() / pointTake.get();
return new ResultCategoryData.RankArray(0,
comb.getName(membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS), w.get(),
pointMake.get(), pointTake.get(), pointRate);
CombStat stat = makeStat(matchEntities, comb);
return new ResultCategoryData.RankArray(0, comb,
comb.getName(membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS), stat.score, stat.w,
stat.pointMake, stat.pointTake, stat.getPointRate());
})
.filter(r -> r.getPointMake() > 0 || r.getPointTake() > 0)
.sorted(Comparator
.comparing(ResultCategoryData.RankArray::getWin)
.comparing(ResultCategoryData.RankArray::getScore)
.thenComparing(ResultCategoryData.RankArray::getWin)
.thenComparing(ResultCategoryData.RankArray::getPointRate).reversed())
.toList();
out.getMatchs().put(c, matchs);
int lastScore = -1;
int lastWin = -1;
float pointRate = 0;
int rank = 0;
for (ResultCategoryData.RankArray rankArray1 : rankArray) {
if (rankArray1.getWin() != lastWin || pointRate != rankArray1.getPointRate()) {
if (rankArray1.getScore() != lastScore || rankArray1.getWin() != lastWin || pointRate != rankArray1.getPointRate()) {
lastScore = rankArray1.getScore();
lastWin = rankArray1.getWin();
pointRate = rankArray1.getPointRate();
rank++;
@@ -214,32 +221,94 @@ public class ResultService {
});
}
private static void convertTree(TreeModel src, TreeNode<ResultCategoryData.TreeData> dst, MembreModel membreModel,
ResultPrivacy privacy) {
dst.setData(ResultCategoryData.TreeData.from(src.getMatch(), membreModel, privacy));
if (src.getLeft() != null) {
dst.setLeft(new TreeNode<>());
convertTree(src.getLeft(), dst.getLeft(), membreModel, privacy);
}
if (src.getRight() != null) {
dst.setRight(new TreeNode<>());
convertTree(src.getRight(), dst.getRight(), membreModel, privacy);
public void getClassementArray(CategoryModel categoryModel, MembreModel membreModel, List<CardModel> cards,
ResultCategoryData out) {
if ((categoryModel.getType() & 2) != 0) {
AtomicInteger rank = new AtomicInteger(0);
categoryModel.getTree().stream()
.filter(t -> t.getLevel() > 0)
.sorted(Comparator.comparing(TreeModel::getLevel))
.forEach(treeModel -> makeClassementRow(membreModel,
new MatchModelExtend(treeModel.getMatch(), cards), out, rank));
categoryModel.getTree().stream()
.filter(t -> t.getLevel() <= -10)
.sorted(Comparator.comparing(TreeModel::getLevel).reversed())
.forEach(treeModel -> makeClassementRow(membreModel,
new MatchModelExtend(treeModel.getMatch(), cards), out, rank));
} else {
for (List<ResultCategoryData.RankArray> list : out.getRankArray().values()) {
for (ResultCategoryData.RankArray r : list) {
out.getClassement()
.add(new ResultCategoryData.ClassementData(r.getRank(), r.getComb(), r.getName()));
}
}
}
}
private void getTree(List<TreeModel> treeModels, MembreModel membreModel, ResultCategoryData out) {
private static void makeClassementRow(MembreModel membreModel, MatchModelExtend m, ResultCategoryData out,
AtomicInteger rank) {
if (m.isEnd()) {
if (m.getWin() > 0) {
out.getClassement()
.add(new ResultCategoryData.ClassementData(rank.incrementAndGet(), m.getC1(),
m.getC1Name(membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS)));
out.getClassement()
.add(new ResultCategoryData.ClassementData(rank.incrementAndGet(), m.getC2(),
m.getC2Name(membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS)));
} else if (m.getWin() < 0) {
out.getClassement()
.add(new ResultCategoryData.ClassementData(rank.incrementAndGet(), m.getC2(),
m.getC2Name(membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS)));
out.getClassement()
.add(new ResultCategoryData.ClassementData(rank.incrementAndGet(), m.getC1(),
m.getC1Name(membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS)));
} else {
out.getClassement()
.add(new ResultCategoryData.ClassementData(rank.incrementAndGet(), m.getC1(),
m.getC1Name(membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS)));
out.getClassement()
.add(new ResultCategoryData.ClassementData(rank.get(), m.getC2(),
m.getC2Name(membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS)));
}
} else {
out.getClassement().add(new ResultCategoryData.ClassementData(rank.incrementAndGet(), null, null));
out.getClassement().add(new ResultCategoryData.ClassementData(rank.incrementAndGet(), null, null));
}
}
private static void convertTree(TreeModel src, TreeNode<ResultCategoryData.TreeData> dst, MembreModel membreModel,
ResultPrivacy privacy, List<CardModel> cards) {
dst.setData(
ResultCategoryData.TreeData.from(new MatchModelExtend(src.getMatch(), cards), src.getLevel(),
membreModel, privacy));
if (src.getLeft() != null) {
dst.setLeft(new TreeNode<>());
convertTree(src.getLeft(), dst.getLeft(), membreModel, privacy, cards);
}
if (src.getRight() != null) {
dst.setRight(new TreeNode<>());
convertTree(src.getRight(), dst.getRight(), membreModel, privacy, cards);
}
}
private void getTree(List<TreeModel> treeModels, MembreModel membreModel, List<CardModel> cards,
ResultCategoryData out) {
ArrayList<TreeNode<ResultCategoryData.TreeData>> trees = new ArrayList<>();
treeModels.stream().filter(t -> t.getLevel() != 0).forEach(treeModel -> {
TreeNode<ResultCategoryData.TreeData> root = new TreeNode<>();
convertTree(treeModel, root, membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS);
trees.add(root);
});
treeModels.stream()
.filter(t -> t.getLevel() != 0)
.sorted(Comparator.comparing(TreeModel::getLevel))
.forEach(treeModel -> {
TreeNode<ResultCategoryData.TreeData> root = new TreeNode<>();
convertTree(treeModel, root, membreModel, ResultPrivacy.REGISTERED_ONLY_NO_DETAILS, cards);
trees.add(root);
});
out.setTrees(trees);
}
public Uni<CombsArrayData> getAllCombArray(String uuid, SecurityCtx securityCtx) {
return hasAccess(uuid, securityCtx)
.chain(r -> getAllCombArray_(uuid, r.getMembre()));
.chain(membreModel -> getAllCombArray_(uuid, membreModel));
}
public Uni<CombsArrayData> getAllCombArrayPublic(String uuid) {
@@ -249,10 +318,13 @@ public class ResultService {
private Uni<CombsArrayData> getAllCombArray_(String uuid, MembreModel membreModel) {
return registerRepository.list("competition.uuid = ?1", uuid)
.chain(registers -> matchRepository.list("category.compet.uuid = ?1", uuid)
.map(matchModels -> new Pair<>(registers, matchModels)))
.chain(matchModels -> cardRepository.list("competition.uuid = ?1", uuid)
.map(cards -> new Pair<>(registers,
matchModels.stream().map(m -> new MatchModelExtend(m, cards)).toList()))))
.map(pair -> {
List<RegisterModel> registers = pair.getKey();
List<MatchModel> matchModels = pair.getValue();
List<MatchModelExtend> matchModels = pair.getValue();
CombsArrayData.CombsArrayDataBuilder builder = CombsArrayData.builder();
@@ -262,12 +334,7 @@ public class ResultService {
.distinct()
.map(comb -> {
var builder2 = CombsArrayData.CombsData.builder();
AtomicInteger w = new AtomicInteger(0);
AtomicInteger l = new AtomicInteger(0);
AtomicInteger pointMake = new AtomicInteger();
AtomicInteger pointTake = new AtomicInteger();
makeStat(matchModels, comb, w, l, pointMake, pointTake);
CombStat stat = makeStat(matchModels, comb);
Categorie categorie = null;
String clubName = null;
@@ -277,26 +344,25 @@ public class ResultService {
if (register.isPresent()) {
categorie = register.get().getCategorie2();
ClubModel club = register.get().getClub2();
clubName = (club == null) ? BUNDLE.getString("no.licence") : club.getName();
clubName = (club == null) ? trad.t("no.licence") : club.getName();
} else if (comb instanceof CompetitionGuestModel guestModel) {
categorie = guestModel.getCategorie();
clubName = guestModel.getClub();
} else if (comb instanceof MembreModel model) {
categorie = model.getCategorie();
clubName = (model.getClub() == null) ? BUNDLE.getString(
"no.licence") : model.getClub().getName();
clubName = (model.getClub() == null) ? trad.t("no.licence")
: model.getClub().getName();
}
builder2.cat((categorie == null) ? "---" : categorie.getName(BUNDLE));
builder2.cat((categorie == null) ? "---" : categorie.getName(trad));
builder2.name(comb.getName(membreModel, ResultPrivacy.REGISTERED_ONLY));
builder2.w(w.get());
builder2.l(l.get());
builder2.ratioVictoire((l.get() == 0) ? w.get() : (float) w.get() / l.get());
builder2.w(stat.w);
builder2.l(stat.l);
builder2.ratioVictoire((stat.l == 0) ? stat.w : (float) stat.w / stat.l);
builder2.club(clubName);
builder2.pointMake(pointMake.get());
builder2.pointTake(pointTake.get());
builder2.ratioPoint(
(pointTake.get() == 0) ? pointMake.get() : (float) pointMake.get() / pointTake.get());
builder2.pointMake(stat.pointMake);
builder2.pointTake(stat.pointTake);
builder2.ratioPoint(stat.getPointRate());
return builder2.build();
})
@@ -304,71 +370,102 @@ public class ResultService {
.toList();
builder.nb_insc(combs.size());
builder.tt_match((int) matchModels.stream().filter(MatchModel::isEnd).count());
builder.point(combs.stream().mapToInt(CombsArrayData.CombsData::pointMake).sum());
builder.tt_match((int) matchModels.stream().filter(MatchModelExtend::isEnd).count());
builder.point(matchModels.stream()
.filter(MatchModelExtend::isEnd)
.flatMap(m -> m.getScoresToCompute().stream())
.mapToInt(s -> s.getS1() + s.getS2()).sum());
builder.combs(combs);
return builder.build();
});
}
public Uni<HashMap<String, String>> getCombList(String uuid, ResultPrivacy privacy) {
public Uni<HashMap<String, String>> getCombList(String uuid, SecurityCtx securityCtx) {
return hasAccess(uuid, securityCtx)
.chain(membreModel -> getCombList(uuid, ResultPrivacy.REGISTERED_ONLY));
}
public Uni<HashMap<String, String>> getCombList(String uuid) {
return getCombList(uuid, ResultPrivacy.PUBLIC);
}
private Uni<HashMap<String, String>> getCombList(String uuid, ResultPrivacy privacy) {
return registerRepository.list("competition.uuid = ?1 AND membre.resultPrivacy <= ?2", uuid, privacy)
.map(models -> {
HashMap<String, String> map = new HashMap<>();
models.forEach(
r -> map.put(Utils.getFullName(r.getMembre()), getCombTempId(r.getMembre().getId())));
r -> map.put(r.getMembre().getName(), getCombTempId(r.getMembre().getId())));
return map;
})
.chain(map -> competitionGuestRepository.list("competition.uuid = ?1", uuid)
.chain(map -> competitionGuestRepository.list("competition.uuid = ?1 AND lname != \"__team\"", uuid)
.map(models -> {
models.forEach(guestModel -> map.put(Utils.getFullName(guestModel),
models.forEach(guestModel -> map.put(guestModel.getName(),
getCombTempId(guestModel.getId() * -1)));
return map;
})
);
}
public Uni<?> getCombArrayPublic(String uuid, String combTempId, ResultPrivacy privacy) {
public Uni<?> getCombArrayPublic(String uuid, String combTempId, SecurityCtx securityCtx) {
return hasAccess(uuid, securityCtx)
.chain(membreModel -> getCombArrayPublic(uuid, combTempId, ResultPrivacy.REGISTERED_ONLY));
}
public Uni<?> getCombArrayPublic(String uuid, String combTempId) {
return getCombArrayPublic(uuid, combTempId, ResultPrivacy.PUBLIC);
}
private Uni<?> getCombArrayPublic(String uuid, String combTempId, ResultPrivacy privacy) {
CombArrayData.CombArrayDataBuilder builder = CombArrayData.builder();
Long id = getCombTempId(combTempId);
if (id == null) {
return Uni.createFrom().failure(new DForbiddenException("Comb not found"));
return Uni.createFrom().failure(new DForbiddenException(trad.t("comb.not.found")));
}
Uni<List<MatchModel>> uni;
Uni<List<MatchModelExtend>> uni;
if (id >= 0) {
uni = registerRepository.find("membre.id = ?1 AND competition.uuid = ?2 AND membre.resultPrivacy <= ?3", id,
uuid, privacy).firstResult()
.chain(Unchecked.function(registerModel -> {
if (registerModel == null)
throw new DBadRequestException("Combattant non inscrit");
throw new DBadRequestException(trad.t("combattant.non.inscrit"));
builder.name(Utils.getFullName(registerModel.getMembre()));
builder.club((registerModel.getClub2() == null) ? BUNDLE.getString(
"no.licence") : registerModel.getClub2().getName());
builder.cat((registerModel.getCategorie2() == null) ? "---" : registerModel.getCategorie2()
.getName(BUNDLE));
builder.club((registerModel.getClub2() == null) ? trad.t("no.licence") :
registerModel.getClub2().getName());
builder.cat((registerModel.getCategorie2() == null) ? "---" :
registerModel.getCategorie2().getName(trad));
return matchRepository.list("category.compet.uuid = ?1 AND (c1_id = ?2 OR c2_id = ?2)", uuid,
registerModel.getMembre());
return matchRepository.list(
"SELECT DISTINCT m FROM MatchModel m LEFT JOIN m.c1_guest.comb c1g LEFT JOIN m.c2_guest.comb c2g " +
"WHERE m.category.compet.uuid = ?1 AND (m.c1_id = ?2 OR m.c2_id = ?2 OR c1g = ?2 OR c2g = ?2)",
uuid, registerModel.getMembre())
.chain(matchModels -> cardRepository.list("competition.uuid = ?1", uuid)
.map(cards -> matchModels.stream().map(m -> new MatchModelExtend(m, cards))
.toList()));
}));
} else {
uni = competitionGuestRepository.find("id = ?1 AND competition.uuid = ?2", -id, uuid).firstResult()
.chain(guestModel -> {
builder.name(Utils.getFullName(guestModel));
builder.club(guestModel.getClub());
builder.cat((guestModel.getCategorie() == null) ? "---" : guestModel.getCategorie()
.getName(BUNDLE));
builder.cat(
(guestModel.getCategorie() == null) ? "---" : guestModel.getCategorie().getName(trad));
return matchRepository.list("category.compet.uuid = ?1 AND (c1_guest = ?2 OR c2_guest = ?2)",
uuid, guestModel);
return matchRepository.list(
"SELECT DISTINCT m FROM MatchModel m LEFT JOIN m.c1_guest.guest c1g LEFT JOIN m.c2_guest.guest c2g " +
"WHERE m.category.compet.uuid = ?1 AND (m.c1_guest = ?2 OR m.c2_guest = ?2 OR c1g = ?2 OR c2g = ?2)",
uuid, guestModel)
.chain(matchModels -> cardRepository.list("competition.uuid = ?1", uuid)
.map(cards -> matchModels.stream().map(m -> new MatchModelExtend(m, cards))
.toList()));
});
}
return uni.invoke(matchModels -> {
List<CategoryModel> pouleModels = matchModels.stream().map(MatchModel::getCategory).distinct()
List<CategoryModel> pouleModels = matchModels.stream().map(MatchModelExtend::getCategory).distinct()
.toList();
List<CombArrayData.MatchsData> matchs = new ArrayList<>();
@@ -376,7 +473,7 @@ public class ResultService {
AtomicInteger sumPointMake = new AtomicInteger(0);
AtomicInteger sumPointTake = new AtomicInteger(0);
for (MatchModel matchModel : matchModels) {
for (MatchModelExtend matchModel : matchModels) {
if ((matchModel.getC1_id() == null && matchModel.getC1_guest() == null) ||
(matchModel.getC2_id() == null && matchModel.getC2_guest() == null))
continue;
@@ -385,42 +482,41 @@ public class ResultService {
builder2.date(matchModel.getDate());
builder2.poule(pouleModels.stream().filter(p -> p.equals(matchModel.getCategory()))
.map(CategoryModel::getName).findFirst().orElse(""));
builder2.end(matchModel.isEnd());
AtomicInteger pointMake = new AtomicInteger();
AtomicInteger pointTake = new AtomicInteger();
if (matchModel.isC1(id)) {
builder2.adv(Utils.getFullName(matchModel.getC2_id(), matchModel.getC2_guest()));
builder2.adv(matchModel.getC2Name());
if (matchModel.isEnd()) {
matchModel.getScores().stream()
.filter(s -> s.getS1() > -900 && s.getS2() > -900)
matchModel.getScoresToCompute()
.forEach(scoreEntity -> {
pointMake.addAndGet(scoreEntity.getS1());
pointTake.addAndGet(scoreEntity.getS2());
});
builder2.score(matchModel.getScores().stream()
builder2.score(matchModel.getScoresToPrint().stream()
.map(s -> new Integer[]{s.getS1(), s.getS2()}).toList());
} else {
builder2.score(new ArrayList<>());
}
builder2.win(matchModel.win() > 0);
builder2.win(matchModel.isEnd() && matchModel.getWin() > 0);
} else {
builder2.adv(Utils.getFullName(matchModel.getC1_id(), matchModel.getC1_guest()));
builder2.adv(matchModel.getC1Name());
if (matchModel.isEnd()) {
matchModel.getScores().stream()
.filter(s -> s.getS1() > -900 && s.getS2() > -900)
matchModel.getScoresToCompute()
.forEach(scoreEntity -> {
pointMake.addAndGet(scoreEntity.getS2());
pointTake.addAndGet(scoreEntity.getS1());
});
builder2.score(matchModel.getScores().stream()
builder2.score(matchModel.getScoresToPrint().stream()
.map(s -> new Integer[]{s.getS2(), s.getS1()}).toList());
} else {
builder2.score(new ArrayList<>());
}
builder2.win(matchModel.win() < 0);
builder2.win(matchModel.isEnd() && matchModel.getWin() < 0);
}
builder2.eq(matchModel.isEnd() && matchModel.getWin() == 0);
builder2.ratio(
(pointTake.get() == 0) ? pointMake.get() : (float) pointMake.get() / pointTake.get());
@@ -464,10 +560,14 @@ public class ResultService {
@Builder
@RegisterForReflection
public static record MatchsData(Date date, String poule, String adv, List<Integer[]> score, float ratio,
boolean win) {
boolean win, boolean eq, boolean end) {
}
}
public Uni<HashMap<String, Long>> getClubList(String uuid, SecurityCtx securityCtx) {
return hasAccess(uuid, securityCtx).chain(__ -> getClubList(uuid));
}
public Uni<HashMap<String, Long>> getClubList(String uuid) {
return registerRepository.list("competition.uuid = ?1", uuid)
.map(registers -> {
@@ -488,55 +588,76 @@ public class ResultService {
}
public Uni<ClubArrayData> getClubArray(String uuid, Long id, SecurityCtx securityCtx) {
return hasAccess(uuid, securityCtx).chain(cm_register -> getClubArray2(uuid, id, cm_register.getMembre()));
return hasAccess(uuid, securityCtx).chain(membreModel -> getClubArray2(uuid, id, membreModel));
}
public Uni<ClubArrayData> getClubArray2(String uuid, Long id, MembreModel membreModel) {
if (id < 0) {
String clubName = getClubTempId(id);
if (clubName == null) {
return Uni.createFrom().failure(new DForbiddenException("Club not found"));
return Uni.createFrom().failure(new DForbiddenException(trad.t("club.not.found")));
}
return competitionGuestRepository.list("competition.uuid = ?1 AND club = ?2", uuid, clubName)
.call(list -> {
if (list.isEmpty())
return Uni.createFrom().failure(new DBadRequestException("Club not found"));
return Uni.createFrom().failure(new DBadRequestException(trad.t("club.not.found")));
return Uni.createFrom().voidItem();
})
.chain(guests -> matchRepository.list(
"category.compet.uuid = ?1 AND (c1_guest IN ?2 OR c2_guest IN ?2)", uuid, guests)
.map(matchModels ->
getClubArray2(clubName, guests.stream().map(o -> (CombModel) o).toList(),
matchModels, new ArrayList<>(), membreModel)));
"SELECT DISTINCT m FROM MatchModel m LEFT JOIN m.c1_guest.guest c1g LEFT JOIN m.c2_guest.guest c2g " +
"WHERE m.category.compet.uuid = ?1 AND (m.c1_guest IN ?2 OR m.c2_guest IN ?2 OR c1g IN ?2 OR c2g IN ?2)",
uuid, guests)
.chain(mm -> cardRepository.list("competition.uuid = ?1", uuid)
.map(cards ->
getClubArray2(clubName, guests.stream().map(o -> (CombModel) o).toList(),
mm.stream().map(m -> new MatchModelExtend(m, cards)).toList(),
new ArrayList<>(), membreModel)
)));
} else {
return clubRepository.findById(id).chain(clubModel ->
registerRepository.list("competition.uuid = ?1 AND membre.club = ?2", uuid, clubModel)
.chain(registers -> matchRepository.list("category.compet.uuid = ?1", uuid)
.map(matchModels ->
getClubArray2(clubModel.getName(),
registers.stream().map(o -> (CombModel) o.getMembre()).toList(),
matchModels, registers, membreModel))));
return cardRepository.list("competition.uuid = ?1", uuid)
.chain(cards -> clubRepository.findById(id).chain(clubModel ->
registerRepository.list("competition.uuid = ?1 AND membre.club = ?2", uuid, clubModel)
.chain(registers -> matchRepository.list(
"SELECT DISTINCT m FROM MatchModel m LEFT JOIN m.c1_guest.comb c1g LEFT JOIN m.c2_guest.comb c2g " +
"WHERE m.category.compet.uuid = ?1 AND (m.c1_id IN ?2 OR m.c2_id IN ?2 OR c1g IN ?2 OR c2g IN ?2)",
uuid, registers.stream().map(RegisterModel::getMembre).toList())
.map(matchModels -> matchModels.stream()
.map(m -> new MatchModelExtend(m, cards)))
.chain(s -> competitionGuestRepository.list(
"competition.uuid = ?1 AND club = ?2", uuid, clubModel.getName())
.chain(guests -> matchRepository.list(
"SELECT DISTINCT m FROM MatchModel m LEFT JOIN m.c1_guest.guest c1g LEFT JOIN m.c2_guest.guest c2g " +
"WHERE m.category.compet.uuid = ?1 AND (m.c1_guest IN ?2 OR m.c2_guest IN ?2 OR c1g IN ?2 OR c2g IN ?2)",
uuid, guests)
.map(mm -> new Pair<>(guests, Stream.concat(s, mm.stream()
.map(m -> new MatchModelExtend(m, cards)))
.distinct().toList()))
))
.map(p ->
getClubArray2(clubModel.getName(),
Stream.concat(
registers.stream().map(RegisterModel::getMembre),
p.getKey().stream()
).toList(),
p.getValue(), registers, membreModel)
))));
}
}
private ClubArrayData getClubArray2(String name, List<CombModel> combs, List<MatchModel> matchModels,
private ClubArrayData getClubArray2(String name, List<CombModel> combs, List<MatchModelExtend> matchModels,
List<RegisterModel> registers, MembreModel membreModel) {
ClubArrayData.ClubArrayDataBuilder builder = ClubArrayData.builder();
builder.name(name);
builder.nb_insc(combs.size());
AtomicInteger tt_win = new AtomicInteger(0);
AtomicInteger tt_match = new AtomicInteger(0);
ArrayList<Long> win_ids = new ArrayList<>();
ArrayList<Long> match_ids = new ArrayList<>();
List<ClubArrayData.CombData> combData = combs.stream().map(comb -> {
var builder2 = ClubArrayData.CombData.builder();
AtomicInteger w = new AtomicInteger(0);
AtomicInteger l = new AtomicInteger(0);
AtomicInteger pointMake = new AtomicInteger();
AtomicInteger pointTake = new AtomicInteger();
makeStat(matchModels, comb, w, l, pointMake, pointTake);
CombStat stat = makeStat(matchModels, comb);
Categorie categorie = null;
@@ -550,25 +671,25 @@ public class ResultService {
categorie = model.getCategorie();
}
builder2.cat((categorie == null) ? "---" : categorie.getName(BUNDLE));
builder2.cat((categorie == null) ? "---" : categorie.getName(trad));
builder2.name(comb.getName(membreModel, ResultPrivacy.REGISTERED_ONLY));
builder2.w(w.get());
builder2.l(l.get());
builder2.ratioVictoire((l.get() == 0) ? w.get() : (float) w.get() / l.get());
builder2.pointMake(pointMake.get());
builder2.pointTake(pointTake.get());
builder2.ratioPoint((pointTake.get() == 0) ? pointMake.get() : (float) pointMake.get() / pointTake.get());
builder2.w(stat.w);
builder2.l(stat.l);
builder2.ratioVictoire((stat.l == 0) ? stat.w : (float) stat.w / stat.l);
builder2.pointMake(stat.pointMake);
builder2.pointTake(stat.pointTake);
builder2.ratioPoint(stat.getPointRate());
tt_win.addAndGet(w.get());
tt_match.addAndGet(w.get() + l.get());
win_ids.addAll(stat.win_ids);
match_ids.addAll(stat.match_ids);
return builder2.build();
})
.sorted(Comparator.comparing(ClubArrayData.CombData::name))
.toList();
builder.nb_match(tt_match.get());
builder.match_w(tt_win.get());
builder.nb_match((int) match_ids.stream().distinct().count());
builder.match_w((int) win_ids.stream().distinct().count());
builder.ratioVictoire((float) combData.stream().filter(c -> c.l + c.w != 0)
.mapToDouble(ClubArrayData.CombData::ratioVictoire).average().orElse(0L));
builder.pointMake(combData.stream().mapToInt(ClubArrayData.CombData::pointMake).sum());
@@ -580,30 +701,36 @@ public class ResultService {
return builder.build();
}
private static void makeStat(List<MatchModel> matchModels, CombModel comb, AtomicInteger w, AtomicInteger l,
AtomicInteger pointMake, AtomicInteger pointTake) {
private static CombStat makeStat(List<MatchModelExtend> matchModels, CombModel comb) {
CombStat stat = new CombStat();
matchModels.stream()
.filter(m -> m.isEnd() && (m.isC1(comb) || m.isC2(comb)))
.forEach(matchModel -> {
int win = matchModel.win();
if ((matchModel.isC1(comb) && win > 0) || matchModel.isC2(comb) && win < 0) {
w.getAndIncrement();
stat.match_ids.add(matchModel.getId());
int win = matchModel.getWin();
if (win == 0) {
stat.score += 1;
} else if ((matchModel.isC1(comb) && win > 0) || matchModel.isC2(comb) && win < 0) {
stat.w++;
stat.win_ids.add(matchModel.getId());
stat.score += 2;
} else {
l.getAndIncrement();
stat.l++;
}
matchModel.getScores().stream()
.filter(s -> s.getS1() > -900 && s.getS2() > -900)
matchModel.getScoresToCompute()
.forEach(score -> {
if (matchModel.isC1(comb)) {
pointMake.addAndGet(score.getS1());
pointTake.addAndGet(score.getS2());
stat.pointMake += score.getS1();
stat.pointTake += score.getS2();
} else {
pointMake.addAndGet(score.getS2());
pointTake.addAndGet(score.getS1());
stat.pointMake += score.getS2();
stat.pointTake += score.getS1();
}
});
});
return stat;
}
@Builder
@@ -617,21 +744,166 @@ public class ResultService {
}
}
private Uni<RegisterModel> hasAccess(String uuid, SecurityCtx securityCtx) {
return registerRepository.find("membre.userId = ?1 AND competition.uuid = ?2", securityCtx.getSubject(), uuid)
.firstResult()
.invoke(Unchecked.consumer(o -> {
if (o == null)
throw new DForbiddenException("Access denied");
}));
public Uni<List<ClubClassement>> getAllClubArray(String uuid) {
return getAllClubArray(uuid, true);
}
private Uni<RegisterModel> hasAccess(Long compId, SecurityCtx securityCtx) {
return registerRepository.find("membre.userId = ?1 AND competition.id = ?2", securityCtx.getSubject(), compId)
public Uni<List<ClubClassement>> getAllClubArray(String uuid, SecurityCtx securityCtx) {
return hasAccess(uuid, securityCtx).chain(membreModel -> getAllClubArray(uuid, true));
}
public Uni<List<ClubClassement>> getAllClubArray(String uuid, boolean cache) {
List<CardModel> cards = new java.util.ArrayList<>();
return (!cache || cacheClubClassementExp.getOrDefault(uuid, 0L) < System.currentTimeMillis() ?
cacheClubClassement.invalidate(uuid).invoke(() -> cacheClubClassementExp.remove(uuid)) :
Uni.createFrom().voidItem()
).chain(o -> cacheClubClassement.getAsync(uuid, k -> cardRepository.list("competition.uuid = ?1", uuid)
.invoke(__ -> System.out.println("Cache miss for club classement with uuid " + uuid))
.invoke(cards::addAll)
.chain(__ -> matchRepository.list("category.compet.uuid = ?1", uuid))
.chain(matchs -> {
HashMap<CategoryModel, List<MatchModel>> map = new HashMap<>();
for (MatchModel match : matchs) {
if (!map.containsKey(match.getCategory()))
map.put(match.getCategory(), new java.util.ArrayList<>());
map.get(match.getCategory()).add(match);
}
return Multi.createFrom().iterable(map.entrySet())
.onItem().call(entry -> Mutiny.fetch(entry.getKey().getTree()))
.map(entry -> {
ResultCategoryData tmp = new ResultCategoryData();
getArray2(entry.getValue().stream().map(m -> new MatchModelExtend(m, cards))
.toList(),
null, tmp);
getClassementArray(entry.getKey(), null, cards, tmp);
return tmp;
})
.collect().asList();
})
.map(categoryData -> {
HashMap<String, ClubClassement> clubMap = new HashMap<>();
categoryData.forEach(
c -> c.getClassement().stream().map(ResultCategoryData.ClassementData::comb)
.filter(Objects::nonNull)
.distinct()
.map(comb -> {
if (comb instanceof MembreModel membreModel2) {
return (membreModel2.getClub() != null) ? membreModel2.getClub()
.getName() : "";
} else if (comb instanceof CompetitionGuestModel guestModel) {
return guestModel.getClub();
}
return "";
})
.filter(s -> s != null && !s.isBlank() && !s.equals("Team"))
.distinct()
.forEach(clubName -> clubMap.putIfAbsent(clubName,
new ClubClassement(clubName))));
categoryData.forEach(c -> c.getClassement().forEach(classementData -> {
if (classementData.rank() > 3)
return;
if (classementData.comb() != null) {
String clubName = "";
if (classementData.comb() instanceof MembreModel membreModel2) {
clubName = (membreModel2.getClub() != null) ? membreModel2.getClub()
.getName() : "";
} else if (classementData.comb() instanceof CompetitionGuestModel guestModel) {
clubName = guestModel.getClub();
}
if (clubName != null && !clubName.isBlank()
&& !clubName.equals("Team") && clubMap.containsKey(clubName)) {
ClubClassement entity = clubMap.get(clubName);
entity.score[classementData.rank() - 1]++;
entity.tt_score += 4 - classementData.rank();
}
}
}));
return clubMap.values().stream()
.sorted(Comparator.comparingInt((ClubClassement c) -> c.tt_score)
.thenComparingInt(c -> c.score[0])
.thenComparingInt(c -> c.score[1])
.thenComparingInt(c -> c.score[2]).reversed())
.toList();
})
.invoke(__ -> cacheClubClassementExp.put(uuid, System.currentTimeMillis() + 60 * 1000L))
));
}
@Data
@RegisterForReflection
public static class ClubClassement {
String name;
Integer[] score = new Integer[]{0, 0, 0};
int tt_score = 0;
public ClubClassement(String name) {
this.name = name;
}
}
@RegisterForReflection
public static class CombStat {
public int w;
public int l;
public int score;
public int pointMake;
public int pointTake;
public ArrayList<Long> win_ids = new ArrayList<>();
public ArrayList<Long> match_ids = new ArrayList<>();
public CombStat() {
this.w = 0;
this.l = 0;
this.score = 0;
this.pointMake = 0;
this.pointTake = 0;
}
public float getPointRate() {
return (pointTake == 0) ? pointMake : (float) pointMake / pointTake;
}
}
private Uni<MembreModel> hasAccess(String uuid, SecurityCtx securityCtx) {
return registerRepository.find("membre.userId = ?1 AND competition.uuid = ?2", securityCtx.getSubject(), uuid)
.firstResult()
.invoke(Unchecked.consumer(o -> {
if (o == null)
throw new DForbiddenException("Access denied");
.chain(Unchecked.function(o -> {
if (o != null)
return Uni.createFrom().item(o.getMembre());
return membreService.getByAccountId(securityCtx.getSubject()).chain(m -> {
if (securityCtx.isClubAdmin()) {
return registerRepository.count("membre.club = ?2 AND competition.uuid = ?1",
uuid, m.getClub()).chain(c -> {
if (c > 0) return Uni.createFrom().item(m);
return compRepository.count("uuid = ?1 AND (owner = ?2 OR ?2 IN admin)",
uuid, securityCtx.getSubject())
.chain(c2 -> {
if (c2 > 0) return Uni.createFrom().item(m);
return Uni.createFrom().failure(new DForbiddenException(
trad.t("access.denied")));
});
});
} else {
return compRepository.count("uuid = ?1 AND (owner = ?2 OR ?2 IN admin)", uuid,
securityCtx.getSubject())
.chain(c2 -> {
if (c2 > 0) return Uni.createFrom().item(m);
return Uni.createFrom()
.failure(new DForbiddenException(trad.t("access.denied")));
});
}
});
}));
}
}

View File

@@ -0,0 +1,64 @@
package fr.titionfire.ffsaf.domain.service;
import fr.titionfire.ffsaf.data.model.LogModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.data.model.SelectionModel;
import fr.titionfire.ffsaf.data.repository.CombRepository;
import fr.titionfire.ffsaf.data.repository.SelectionRepository;
import fr.titionfire.ffsaf.rest.data.SimpleSelection;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.hibernate.reactive.mutiny.Mutiny;
import java.util.List;
import java.util.function.Consumer;
@WithSession
@ApplicationScoped
public class SelectionService {
@Inject
CombRepository combRepository;
@Inject
SelectionRepository repository;
@Inject
LoggerService ls;
public Uni<List<SelectionModel>> getSelection(long id, Consumer<MembreModel> checkPerm) {
return combRepository.findById(id).invoke(checkPerm)
.chain(combRepository -> Mutiny.fetch(combRepository.getSelections()));
}
public Uni<SelectionModel> setSelection(long id, SimpleSelection data) {
if (data.getId() == -1) {
return combRepository.findById(id).chain(membreModel -> {
SelectionModel model = new SelectionModel();
model.setMembre(membreModel);
model.setSaison(data.getSaison());
model.setCategorie(data.getCategorie());
return Panache.withTransaction(() -> repository.persist(model))
.call(licenceModel -> ls.logA(LogModel.ActionType.ADD, membreModel.getObjectName(),
licenceModel));
});
} else {
return repository.findById(data.getId()).chain(model -> {
ls.logChange("Catégorie", model.getCategorie(), data.getCategorie(), model);
model.setCategorie(data.getCategorie());
return Panache.withTransaction(() -> repository.persist(model))
.call(__ -> ls.append());
});
}
}
public Uni<Void> deleteSelection(long id) {
return repository.findById(id)
.call(model -> ls.logADelete(model))
.chain(model -> Panache.withTransaction(() -> repository.delete(model)));
}
}

View File

@@ -0,0 +1,52 @@
package fr.titionfire.ffsaf.domain.service;
import io.quarkus.websockets.next.WebSocketConnection;
import jakarta.enterprise.context.RequestScoped;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;
import jakarta.ws.rs.container.ContainerRequestContext;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
@RequestScoped
public class TradService {
@Inject
Instance<ContainerRequestContext> requestContextInstance;
public static final Locale fallbackLocale = Locale.FRANCE;
public String t(String key) {
return translate(key);
}
public String t(String key, WebSocketConnection connection) {
List<String> lang = connection.handshakeRequest().headers().get("Accept-Language");
Locale userLocale = lang != null && !lang.isEmpty() ? Locale.forLanguageTag(lang.get(0)) : fallbackLocale;
return translate(key, userLocale);
}
public String translate(String key) {
ContainerRequestContext requestContext = requestContextInstance.get();
Locale userLocale = (Locale) requestContext.getProperty("userLocale");
return translate(key, userLocale);
}
public String translate(String key, Locale userLocale) {
try {
ResourceBundle messages = ResourceBundle.getBundle("lang.messages", userLocale);
return messages.getString(key);
} catch (MissingResourceException e) {
try {
ResourceBundle fallbackMessages = ResourceBundle.getBundle("lang.messages", fallbackLocale);
return fallbackMessages.getString(key);
} catch (MissingResourceException ex) {
return key;
}
}
}
}

View File

@@ -1,10 +1,12 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.TradService;
import fr.titionfire.ffsaf.rest.client.SirenService;
import fr.titionfire.ffsaf.rest.client.StateIdService;
import fr.titionfire.ffsaf.rest.data.AssoData;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
@@ -19,6 +21,9 @@ public class AssoEndpoints {
@RestClient
SirenService sirenService;
@Inject
TradService trad;
@GET
@Path("state_id/{stateId}")
@Produces(MediaType.APPLICATION_JSON)
@@ -28,9 +33,9 @@ public class AssoEndpoints {
stateId).chain(stateIdService::getAssoDataFromUnit)).onFailure().transform(throwable -> {
if (throwable instanceof WebApplicationException exception) {
if (exception.getResponse().getStatus() == 404)
return new DNotFoundException("Service momentanément indisponible");
return new DNotFoundException(trad.t("service.momentanement.indisponible"));
if (exception.getResponse().getStatus() == 400)
return new DNotFoundException("Asso introuvable");
return new DNotFoundException(trad.t("asso.introuvable"));
}
return throwable;
});

View File

@@ -3,6 +3,7 @@ package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.data.model.ClubModel;
import fr.titionfire.ffsaf.domain.service.ClubService;
import fr.titionfire.ffsaf.domain.service.PDFService;
import fr.titionfire.ffsaf.domain.service.TradService;
import fr.titionfire.ffsaf.domain.service.VirusScannerService;
import fr.titionfire.ffsaf.net2.data.SimpleClubModel;
import fr.titionfire.ffsaf.rest.data.*;
@@ -53,6 +54,9 @@ public class ClubEndpoints {
@ConfigProperty(name = "upload_dir")
String media;
@Inject
TradService trad;
Consumer<ClubModel> checkPerm = Unchecked.consumer(clubModel -> {
if (!securityCtx.roleHas("federation_admin") && !securityCtx.isInClubGroup(clubModel.getId()))
throw new DForbiddenException();
@@ -84,7 +88,7 @@ public class ClubEndpoints {
@Operation(summary = "Renvoie les types de contacts pour les clubs", description = "Renvoie la liste des types de " +
"contacts possibles pour les clubs")
public Uni<HashMap<String, String>> getConcatType() {
return Uni.createFrom().item(Contact.toSite());
return Uni.createFrom().item(Contact.toSite(trad));
}
@GET
@@ -125,7 +129,7 @@ public class ClubEndpoints {
public Uni<SimpleClub> getById(
@Parameter(description = "Identifiant de club") @PathParam("id") long id) {
return clubService.getById(id).onItem().invoke(checkPerm).map(SimpleClub::fromModel)
.invoke(m -> m.setContactMap(Contact.toSite()));
.invoke(m -> m.setContactMap(Contact.toSite(trad)));
}
@PUT
@@ -218,7 +222,7 @@ public class ClubEndpoints {
})
public Uni<SimpleClub> getOfUser() {
return clubService.getOfUser(securityCtx).map(SimpleClub::fromModel)
.invoke(m -> m.setContactMap(Contact.toSite()));
.invoke(m -> m.setContactMap(Contact.toSite(trad)));
}
@PUT

View File

@@ -1,10 +1,7 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.CompetitionService;
import fr.titionfire.ffsaf.rest.data.CompetitionData;
import fr.titionfire.ffsaf.rest.data.RegisterRequestData;
import fr.titionfire.ffsaf.rest.data.SimpleCompetData;
import fr.titionfire.ffsaf.rest.data.SimpleRegisterComb;
import fr.titionfire.ffsaf.rest.data.*;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import io.quarkus.security.Authenticated;
import io.smallrye.mutiny.Uni;
@@ -53,6 +50,16 @@ public class CompetitionEndpoints {
return service.addRegisterComb(securityCtx, id, data, source);
}
@POST
@Path("{id}/registers/{source}")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
@Operation(hidden = true)
public Uni<List<SimpleRegisterComb>> addRegistersComb(@PathParam("id") Long id, @PathParam("source") String source,
List<RegisterRequestData> data) {
return service.addRegistersComb(securityCtx, id, data, source);
}
@DELETE
@Path("{id}/register/{comb_id}/{source}")
@Authenticated
@@ -71,6 +78,21 @@ public class CompetitionEndpoints {
return service.getSafcaData(securityCtx, id);
}
@GET
@Path("{id}/internalData")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<SimpleCompetData> getInternalData(@PathParam("id") Long id) {
return service.getInternalData(securityCtx, id);
}
@GET
@Path("{id}/categories")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<PresetData>> getPresetsForCompetition(@PathParam("id") Long id) {
return service.getPresetsForCompetition(securityCtx, id);
}
@GET
@Path("all")
@@ -95,6 +117,14 @@ public class CompetitionEndpoints {
return service.setSafcaData(securityCtx, data);
}
@POST
@Path("/internalData")
@Authenticated
@Produces(MediaType.APPLICATION_JSON)
public Uni<?> setInternalData(SimpleCompetData data) {
return service.setInternalData(securityCtx, data);
}
@DELETE
@Path("{id}")
@Authenticated

View File

@@ -2,13 +2,13 @@ package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.ResultService;
import fr.titionfire.ffsaf.domain.service.UpdateService;
import fr.titionfire.ffsaf.utils.ResultPrivacy;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import java.util.HashMap;
import java.util.List;
@Path("api/public/result/{id}")
public class ExternalResultEndpoints {
@@ -47,7 +47,7 @@ public class ExternalResultEndpoints {
@Path("/comb/list")
@Produces(MediaType.APPLICATION_JSON)
public Uni<HashMap<String, String>> combList() {
return resultService.getCombList(id, ResultPrivacy.PUBLIC);
return resultService.getCombList(id);
}
@GET
@@ -56,7 +56,7 @@ public class ExternalResultEndpoints {
public Uni<?> getArray(@QueryParam("comb") String comb) {
if (comb.equals("0"))
return Uni.createFrom().item("");
return resultService.getCombArrayPublic(id, comb, ResultPrivacy.PUBLIC);
return resultService.getCombArrayPublic(id, comb);
}
@GET
@@ -74,6 +74,13 @@ public class ExternalResultEndpoints {
return resultService.getClubList(id);
}
@GET
@Path("/club/classement")
@Produces(MediaType.APPLICATION_JSON)
public Uni<List<ResultService.ClubClassement>> clubClassement() {
return resultService.getAllClubArray(id);
}
@GET
@Path("/club/data")
@Produces(MediaType.APPLICATION_JSON)

View File

@@ -101,7 +101,7 @@ public class LicenceEndpoints {
@RolesAllowed("federation_admin")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Operation(summary = "Créer une licence", description = "Créer unr licence en fonction de son identifiant et des " +
@Operation(summary = "Créer une licence", description = "Créer une licence en fonction de son identifiant et des " +
"informations fournies dans le formulaire (pour les administrateurs)")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "La licence a été mise à jour avec succès"),

View File

@@ -2,7 +2,6 @@ package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.domain.service.ResultService;
import fr.titionfire.ffsaf.rest.data.ResultCategoryData;
import fr.titionfire.ffsaf.utils.ResultPrivacy;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import io.quarkus.security.Authenticated;
import io.smallrye.mutiny.Uni;
@@ -33,7 +32,7 @@ public class ResultEndpoints {
@GET
@Path("{uuid}/category/list")
public Uni<HashMap<String, Long>> getCategoryList(@PathParam("uuid") String uuid) {
return resultService.getCategoryList(uuid);
return resultService.getCategoryList(uuid, securityCtx);
}
@GET
@@ -45,7 +44,13 @@ public class ResultEndpoints {
@GET
@Path("{uuid}/club/list")
public Uni<HashMap<String, Long>> getClubList(@PathParam("uuid") String uuid) {
return resultService.getClubList(uuid);
return resultService.getClubList(uuid, securityCtx);
}
@GET
@Path("{uuid}/club/classement")
public Uni<List<ResultService.ClubClassement>> getClubClassement(@PathParam("uuid") String uuid) {
return resultService.getAllClubArray(uuid, securityCtx);
}
@GET
@@ -57,13 +62,13 @@ public class ResultEndpoints {
@GET
@Path("{uuid}/comb/list")
public Uni<HashMap<String, String>> getCombList(@PathParam("uuid") String uuid) {
return resultService.getCombList(uuid, ResultPrivacy.REGISTERED_ONLY);
return resultService.getCombList(uuid, securityCtx);
}
@GET
@Path("{uuid}/comb/{id}")
public Uni<?> getCombList(@PathParam("uuid") String uuid, @PathParam("id") String id) {
return resultService.getCombArrayPublic(uuid, id, ResultPrivacy.REGISTERED_ONLY);
return resultService.getCombArrayPublic(uuid, id, securityCtx);
}
@GET

View File

@@ -0,0 +1,85 @@
package fr.titionfire.ffsaf.rest;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.domain.service.SelectionService;
import fr.titionfire.ffsaf.rest.data.SimpleSelection;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponses;
import java.util.List;
import java.util.function.Consumer;
@Path("api/selection")
public class SelectionEndpoints {
@Inject
SelectionService selectionService;
@Inject
SecurityCtx securityCtx;
Consumer<MembreModel> checkPerm = Unchecked.consumer(membreModel -> {
if (!securityCtx.roleHas("federation_admin") && !securityCtx.isInClubGroup(membreModel.getClub().getId()))
throw new DForbiddenException();
});
@GET
@Path("{id}")
@RolesAllowed({"federation_admin", "club_president", "club_secretaire", "club_tresorier", "club_respo_intra",
"ffsaf_selectionneur"})
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Renvoie les séléctions d'un membre", description = "Renvoie les séléctions d'un membre en fonction " +
"de son identifiant")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "La liste des séléctions du membre"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "Le membre n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<List<SimpleSelection>> getSelection(@PathParam("id") long id) {
return selectionService.getSelection(id, checkPerm)
.map(selectionModels -> selectionModels.stream().map(SimpleSelection::fromModel).toList());
}
@POST
@Path("{id}")
@RolesAllowed({"federation_admin", "ffsaf_selectionneur"})
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@Operation(summary = "Créer une séléction", description = "Créer une séléction en fonction de son identifiant et des " +
"informations fournies dans le formulaire (pour les administrateurs)")
@APIResponses(value = {
@APIResponse(responseCode = "200", description = "La séléction a été mise à jour avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "La séléction n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<SimpleSelection> setSelection(@PathParam("id") long id, SimpleSelection data) {
return selectionService.setSelection(id, data).map(SimpleSelection::fromModel);
}
@DELETE
@Path("{id}")
@RolesAllowed({"federation_admin", "ffsaf_selectionneur"})
@Produces(MediaType.TEXT_PLAIN)
@Operation(summary = "Supprime une séléction", description = "Supprime une séléction en fonction de son identifiant " +
"(pour les administrateurs)")
@APIResponses(value = {
@APIResponse(responseCode = "204", description = "La séléction a été supprimée avec succès"),
@APIResponse(responseCode = "403", description = "Accès refusé"),
@APIResponse(responseCode = "404", description = "La séléction n'existe pas"),
@APIResponse(responseCode = "500", description = "Erreur interne du serveur")
})
public Uni<?> deleteSelection(@PathParam("id") long id) {
return selectionService.deleteSelection(id);
}
}

View File

@@ -1,5 +1,6 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.CatPresetModel;
import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
import fr.titionfire.ffsaf.data.model.CompetitionModel;
import fr.titionfire.ffsaf.data.model.RegisterModel;
@@ -10,6 +11,7 @@ import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Stream;
@@ -33,12 +35,23 @@ public class CompetitionData {
private Long club;
private String clubName;
private String owner;
private List<SimpleRegister> registers;
private List<SimpleRegister> registers; // for SAFCA
private boolean canEdit;
private boolean canEditRegisters;
private String data1;
private String data2;
private String data3;
private String data4;
private String config;
private List<PresetData> presets;
private List<Categorie> requiredWeight;
public CompetitionData() {
this(null, "", "", "", "", new Date(), new Date(),
CompetitionSystem.INTERNAL, RegisterMode.FREE, new Date(), new Date(), true,
null, "", "", null, true, true,
"", "", "", "", "{}", new ArrayList<>(), new ArrayList<>());
}
public static CompetitionData fromModel(CompetitionModel model) {
if (model == null)
@@ -47,8 +60,9 @@ public class CompetitionData {
return new CompetitionData(model.getId(), model.getName(), model.getDescription(), model.getAdresse(),
model.getUuid(), model.getDate(), model.getTodate(), model.getSystem(),
model.getRegisterMode(), model.getStartRegister(), model.getEndRegister(), model.isPublicVisible(),
model.getClub().getId(), model.getClub().getName(), model.getOwner(), null, false,
model.getData1(), model.getData2(), model.getData3(), model.getData4());
model.getClub().getId(), model.getClub().getName(), model.getOwner(), null, false, false,
model.getData1(), model.getData2(), model.getData3(), model.getData4(), model.getConfig(),
new ArrayList<>(), model.getRequiredWeight());
}
public static CompetitionData fromModelLight(CompetitionModel model) {
@@ -58,36 +72,42 @@ public class CompetitionData {
CompetitionData out = new CompetitionData(model.getId(), model.getName(), model.getDescription(),
model.getAdresse(), "", model.getDate(), model.getTodate(), null,
model.getRegisterMode(), model.getStartRegister(), model.getEndRegister(), model.isPublicVisible(),
null, model.getClub().getName(), "", null, false,
"", "", "", "");
null, model.getClub().getName(), "", null, false, false,
"", "", "", "", "{}", new ArrayList<>(), model.getRequiredWeight());
if (model.getRegisterMode() == RegisterMode.HELLOASSO) {
out.setData1(model.getData1());
out.setData2(model.getData2());
}
return out;
}
public CompetitionData addInsc(List<RegisterModel> insc, List<CompetitionGuestModel> guests) {
this.registers = Stream.concat(
insc.stream()
.map(i -> new SimpleRegister(i.getMembre().getId(), i.getOverCategory(), i.getWeight(),
.map(i -> new SimpleRegister(i.getMembre().getId(), i.getOverCategory(), i.getWeight2(),
i.getCategorie(), (i.getClub() == null) ? null : i.getClub().getId(),
(i.getClub() == null) ? null : i.getClub().getName())),
guests.stream()
.map(i -> new SimpleRegister(i.getId() * -1, 0, i.getWeight(),
.map(i -> new SimpleRegister(i.getId() * -1, 0, i.getWeight2(),
i.getCategorie(), null, i.getClub()))).toList();
return this;
}
public CompetitionData addPresets(List<CatPresetModel> presets) {
this.presets = presets.stream().map(PresetData::fromModel).toList();
return this;
}
@Data
@AllArgsConstructor
@RegisterForReflection
public static class SimpleRegister {
long id;
int overCategory;
Integer weight;
Float weight;
Categorie categorie;
Long club;
String club_str;

View File

@@ -1,6 +1,7 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.domain.service.TradService;
import fr.titionfire.ffsaf.utils.ResultPrivacy;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.Data;
@@ -44,20 +45,22 @@ public class MeData {
private ResultPrivacy resultPrivacy;
@Schema(description = "La liste des licences du membre.")
private List<SimpleLicence> licences;
@Schema(description = "La liste des séléctions du membre.")
private List<SimpleSelection> selections;
public void setMembre(MembreModel membreModel) {
public void setMembre(MembreModel membreModel, TradService trad) {
this.id = membreModel.getId();
this.lname = membreModel.getLname();
this.fname = membreModel.getFname();
this.categorie = membreModel.getCategorie() == null ? "catégorie inconnue" : membreModel.getCategorie().getName();
this.club = membreModel.getClub() == null ? "Sans club" : membreModel.getClub().getName();
this.genre = membreModel.getGenre().str;
this.categorie = membreModel.getCategorie() == null ? trad.t("categorie.inconnue") : membreModel.getCategorie().getName(trad);
this.club = membreModel.getClub() == null ? trad.t("sans.club") : membreModel.getClub().getName();
this.genre = membreModel.getGenre().getString(trad);
this.licence = membreModel.getLicence();
this.country = membreModel.getCountry();
this.birth_date = membreModel.getBirth_date();
this.email = membreModel.getEmail();
this.role = membreModel.getRole().str;
this.grade_arbitrage = membreModel.getGrade_arbitrage().str;
this.role = membreModel.getRole().getString(trad);
this.grade_arbitrage = membreModel.getGrade_arbitrage().getString(trad);
this.resultPrivacy = membreModel.getResultPrivacy();
}
}

View File

@@ -0,0 +1,31 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.CatPresetModel;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
@RegisterForReflection
public class PresetData {
private Long id;
private String name;
private CatPresetModel.SwordType sword;
private CatPresetModel.ShieldType shield;
private List<CatPresetModel.CategorieEmbeddable> categories;
private int mandatoryProtection1;
private int mandatoryProtection2;
public static PresetData fromModel(CatPresetModel model) {
if (model == null)
return null;
return new PresetData(model.getId(), model.getName(), model.getSwordType(), model.getShieldType(),
model.getCategories(), model.getMandatoryProtection1(), model.getMandatoryProtection2());
}
}

View File

@@ -7,6 +7,8 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@@ -16,9 +18,11 @@ public class RegisterRequestData {
private String fname;
private String lname;
private Integer weight;
private int overCategory;
private Float weight;
private Float weightReal;
private Integer overCategory;
private boolean lockEdit = false;
private List<Long> categoriesInscrites;
// for guest registration only
private Long id = null;
@@ -26,4 +30,6 @@ public class RegisterRequestData {
private Genre genre = Genre.NA;
private String club = null;
private String country = null;
private boolean quick = false;
}

View File

@@ -1,7 +1,9 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.MatchModel;
import com.fasterxml.jackson.annotation.JsonIgnore;
import fr.titionfire.ffsaf.data.model.CombModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.domain.entity.MatchModelExtend;
import fr.titionfire.ffsaf.utils.ResultPrivacy;
import fr.titionfire.ffsaf.utils.ScoreEmbeddable;
import fr.titionfire.ffsaf.utils.TreeNode;
@@ -11,6 +13,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -20,9 +23,11 @@ import java.util.List;
public class ResultCategoryData {
int type;
String name;
boolean treeIsClassement;
HashMap<Character, List<PouleArrayData>> matchs = new HashMap<>();
HashMap<Character, List<RankArray>> rankArray = new HashMap<>();
ArrayList<TreeNode<TreeData>> trees;
List<ClassementData> classement = new ArrayList<>();
String[] liceName;
long genTime;
@@ -31,7 +36,10 @@ public class ResultCategoryData {
@RegisterForReflection
public static class RankArray {
int rank;
@JsonIgnore
CombModel comb;
String name;
int score;
int win;
int pointMake;
int pointTake;
@@ -39,25 +47,37 @@ public class ResultCategoryData {
}
@RegisterForReflection
public record PouleArrayData(String red, boolean red_w, List<Integer[]> score, boolean blue_w, String blue, boolean end) {
public static PouleArrayData fromModel(MatchModel matchModel, MembreModel membreModel, ResultPrivacy privacy) {
public record PouleArrayData(String red, boolean red_w, List<Integer[]> score, boolean blue_w, String blue,
boolean eq, boolean end, Date date) {
public static PouleArrayData fromModel(MatchModelExtend matchModel, MembreModel membreModel,
ResultPrivacy privacy) {
return new PouleArrayData(
matchModel.getC1Name(membreModel, privacy),
matchModel.isEnd() && matchModel.win() > 0,
matchModel.isEnd() && matchModel.getWin() > 0,
matchModel.isEnd() ?
matchModel.getScores().stream().map(s -> new Integer[]{s.getS1(), s.getS2()}).toList()
matchModel.getScoresToPrint().stream().map(s -> new Integer[]{s.getS1(), s.getS2()})
.toList()
: new ArrayList<>(),
matchModel.isEnd() && matchModel.win() < 0,
matchModel.isEnd() && matchModel.getWin() < 0,
matchModel.getC2Name(membreModel, privacy),
matchModel.isEnd());
matchModel.isEnd() && matchModel.getWin() == 0,
matchModel.isEnd(),
matchModel.getDate());
}
}
@RegisterForReflection
public static record TreeData(long id, String c1FullName, String c2FullName, List<ScoreEmbeddable> scores,
boolean end) {
public static TreeData from(MatchModel match, MembreModel membreModel, ResultPrivacy privacy) {
return new TreeData(match.getId(), match.getC1Name(membreModel, privacy), match.getC2Name(membreModel, privacy), match.getScores(), match.isEnd());
boolean end, int win, int level, Date date) {
public static TreeData from(MatchModelExtend match, int level, MembreModel membreModel, ResultPrivacy privacy) {
return new TreeData(match.getId(), match.getC1Name(membreModel, privacy),
match.getC2Name(membreModel, privacy), match.getScoresToPrint(), match.isEnd(), match.getWin(),
level, match.getDate());
}
}
@RegisterForReflection
public static record ClassementData(int rank, @JsonIgnore CombModel comb, String name) {
}
}

View File

@@ -1,5 +1,9 @@
package fr.titionfire.ffsaf.rest.data;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import fr.titionfire.ffsaf.data.model.CompetitionModel;
import fr.titionfire.ffsaf.net2.data.SimpleCompet;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
@@ -25,4 +29,41 @@ public class SimpleCompetData {
return new SimpleCompetData(compet.id(), compet.show_blason(), compet.show_flag(),
new ArrayList<>(), new ArrayList<>());
}
public static SimpleCompetData fromModel(CompetitionModel competitionModel) {
if (competitionModel == null)
return null;
boolean show_blason = true;
boolean show_flag = false;
if (competitionModel.getConfig() != null) {
try {
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.readTree(competitionModel.getConfig());
if (rootNode.has("show_blason"))
show_blason = rootNode.get("show_blason").asBoolean();
if (rootNode.has("show_flag"))
show_flag = rootNode.get("show_flag").asBoolean();
} catch (JsonProcessingException ignored) {
}
}
return new SimpleCompetData(competitionModel.getId(), show_blason, show_flag,
new ArrayList<>(), new ArrayList<>());
}
public String getConfigForInternal(){
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.createObjectNode()
.put("show_blason", this.show_blason)
.put("show_flag", this.show_flag);
try {
return objectMapper.writeValueAsString(rootNode);
} catch (JsonProcessingException e) {
return "{}";
}
}
}

View File

@@ -2,10 +2,14 @@ package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.LicenceModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
@@ -24,7 +28,27 @@ public class SimpleMembreInOutData {
public static SimpleMembreInOutData fromModel(MembreModel membreModel, List<LicenceModel> lc) {
LicenceModel currentLicence = lc.stream().filter(l -> l.getMembre().getId().equals(membreModel.getId()))
.findFirst().orElse(null);
.max(Comparator.comparingInt(LicenceModel::getSaison)).orElse(null);
String certif = null;
if (currentLicence != null && currentLicence.getCertificate() != null) {
String[] strings = currentLicence.getCertificate().split("¤");
if (currentLicence.getSaison() == Utils.getSaison()) {
certif = currentLicence.getCertificate();
} else if (strings.length > 1) {
try {
Date date = new SimpleDateFormat("yyyy-MM-dd").parse(strings[1]);
Calendar max = Utils.getFirstDateOfSaison();
max.add(Calendar.YEAR, -2);
if (max.getTime().compareTo(date) < 0) {
certif = currentLicence.getCertificate();
}
} catch (Exception ignored) {
}
}
}
return new SimpleMembreInOutData(
membreModel.getLicence(),
@@ -33,8 +57,8 @@ public class SimpleMembreInOutData {
membreModel.getEmail(),
membreModel.getGenre().str,
membreModel.getBirth_date(),
currentLicence != null,
currentLicence == null ? null : currentLicence.getCertificate()
currentLicence != null && currentLicence.getSaison() == Utils.getSaison(),
certif
);
}
}

View File

@@ -1,9 +1,6 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
import fr.titionfire.ffsaf.data.model.LicenceModel;
import fr.titionfire.ffsaf.data.model.MembreModel;
import fr.titionfire.ffsaf.data.model.RegisterModel;
import fr.titionfire.ffsaf.data.model.*;
import fr.titionfire.ffsaf.net2.data.SimpleClubModel;
import fr.titionfire.ffsaf.utils.Categorie;
import fr.titionfire.ffsaf.utils.Genre;
@@ -11,11 +8,14 @@ import fr.titionfire.ffsaf.utils.Utils;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.ArrayList;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection
public class SimpleRegisterComb {
private long id;
@@ -26,10 +26,12 @@ public class SimpleRegisterComb {
private Categorie categorie;
private SimpleClubModel club;
private Integer licence;
private Integer weight;
private Float weight;
private Float weightReal;
private int overCategory;
private boolean hasLicenceActive;
private boolean lockEdit;
private List<Long> categoriesInscrites;
public static SimpleRegisterComb fromModel(RegisterModel register, List<LicenceModel> licences) {
MembreModel membreModel = register.getMembre();
@@ -37,15 +39,21 @@ public class SimpleRegisterComb {
membreModel.getGenre(), membreModel.getCountry(),
(register.getCategorie() == null) ? null : register.getCategorie(),
SimpleClubModel.fromModel(register.getClub()), membreModel.getLicence(), register.getWeight(),
register.getOverCategory(),
register.getWeightReal(), register.getOverCategory(),
licences.stream().anyMatch(l -> l.isValidate() && l.getSaison() == Utils.getSaison()),
register.isLockEdit());
register.isLockEdit(), new ArrayList<>());
}
public static SimpleRegisterComb fromModel(CompetitionGuestModel guest) {
return new SimpleRegisterComb(guest.getId() * -1, guest.getFname(), guest.getLname(),
guest.getGenre(), guest.getCountry(), guest.getCategorie(),
new SimpleClubModel(null, guest.getClub(), "fr", null),
null, guest.getWeight(), 0, false, false);
null, guest.getWeight(), guest.getWeightReal(), 0, false, false,
new ArrayList<>());
}
public SimpleRegisterComb setCategorieInscrite(List<CatPresetModel> presets) {
this.categoriesInscrites = presets.stream().map(CatPresetModel::getId).toList();
return this;
}
}

View File

@@ -0,0 +1,36 @@
package fr.titionfire.ffsaf.rest.data;
import fr.titionfire.ffsaf.data.model.SelectionModel;
import fr.titionfire.ffsaf.utils.Categorie;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
@Data
@Builder
@AllArgsConstructor
@RegisterForReflection
public class SimpleSelection {
@Schema(description = "ID de la séléction", examples = "1")
Long id;
@Schema(description = "ID du membre", examples = "1")
Long membre;
@Schema(description = "Saison de la séléction", examples = "2024")
int saison;
@Schema(description = "Catégorie de la séléction", examples = "JUNIOR")
Categorie categorie;
public static SimpleSelection fromModel(SelectionModel model) {
if (model == null)
return null;
return new SimpleSelection.SimpleSelectionBuilder()
.id(model.getId())
.membre(model.getMembre().getId())
.saison(model.getSaison())
.categorie(model.getCategorie())
.build();
}
}

View File

@@ -1,5 +1,6 @@
package fr.titionfire.ffsaf.utils;
import fr.titionfire.ffsaf.domain.service.TradService;
import io.quarkus.runtime.annotations.RegisterForReflection;
import java.util.ResourceBundle;
@@ -19,7 +20,7 @@ public enum Categorie {
VETERAN2;
public String getName(ResourceBundle BUNDLE) {
return switch (this){
return switch (this) {
case SUPER_MINI -> BUNDLE.getString("Cat.SUPER_MINI");
case MINI_POUSSIN -> BUNDLE.getString("Cat.MINI_POUSSIN");
case POUSSIN -> BUNDLE.getString("Cat.POUSSIN");
@@ -34,8 +35,24 @@ public enum Categorie {
};
}
public String getName(TradService tradService) {
return switch (this) {
case SUPER_MINI -> tradService.translate("Cat.SUPER_MINI");
case MINI_POUSSIN -> tradService.translate("Cat.MINI_POUSSIN");
case POUSSIN -> tradService.translate("Cat.POUSSIN");
case BENJAMIN -> tradService.translate("Cat.BENJAMIN");
case MINIME -> tradService.translate("Cat.MINIME");
case CADET -> tradService.translate("Cat.CADET");
case JUNIOR -> tradService.translate("Cat.JUNIOR");
case SENIOR1 -> tradService.translate("Cat.SENIOR1");
case SENIOR2 -> tradService.translate("Cat.SENIOR2");
case VETERAN1 -> tradService.translate("Cat.VETERAN1");
case VETERAN2 -> tradService.translate("Cat.VETERAN2");
};
}
public String getName() {
return switch (this){
return switch (this) {
case SUPER_MINI -> "Super Mini";
case MINI_POUSSIN -> "Mini Poussin";
case POUSSIN -> "Poussin";

View File

@@ -1,32 +1,34 @@
package fr.titionfire.ffsaf.utils;
import fr.titionfire.ffsaf.domain.service.TradService;
import io.quarkus.runtime.annotations.RegisterForReflection;
import java.util.HashMap;
@RegisterForReflection
public enum Contact {
COURRIEL("Courriel"),
TELEPHONE("Téléphone"),
SITE("Site web"),
FACEBOOK("Facebook"),
INSTAGRAM("Instagram"),
AUTRE("Autre");
COURRIEL,
TELEPHONE,
SITE,
FACEBOOK,
INSTAGRAM,
AUTRE;
public String name;
Contact(String name) {
this.name = name;
public String getName(TradService trad) {
return switch (this) {
case COURRIEL -> trad.translate("Contact.COURRIEL");
case TELEPHONE -> trad.translate("Contact.TELEPHONE");
case SITE -> trad.translate("Contact.SITE");
case FACEBOOK -> trad.translate("Contact.FACEBOOK");
case INSTAGRAM -> trad.translate("Contact.INSTAGRAM");
case AUTRE -> trad.translate("Contact.AUTRE");
};
}
public void setName(String name) {
this.name = name;
}
public static HashMap<String, String> toSite() {
public static HashMap<String, String> toSite(TradService trad) {
HashMap<String, String> map = new HashMap<>();
for (Contact contact : Contact.values()) {
map.put(contact.toString(), contact.name);
map.put(contact.toString(), contact.getName(trad));
}
return map;
}

View File

@@ -1,5 +1,6 @@
package fr.titionfire.ffsaf.utils;
import fr.titionfire.ffsaf.domain.service.TradService;
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
@@ -27,8 +28,17 @@ public enum Genre {
}
}
public String getString(TradService trad) {
return switch (this) {
case H -> trad.translate("Genre.Homme");
case F -> trad.translate("Genre.Femme");
case NA -> trad.translate("Genre.NA");
};
}
@Override
public String toString() {
return str;
}
}

View File

@@ -1,5 +1,6 @@
package fr.titionfire.ffsaf.utils;
import fr.titionfire.ffsaf.domain.service.TradService;
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
@@ -14,6 +15,14 @@ public enum GradeArbitrage {
this.str = name;
}
public String getString(TradService trad) {
return switch (this) {
case NA -> trad.translate("GradeArbitrage.NA");
case ASSESSEUR -> trad.translate("GradeArbitrage.ASSESSEUR");
case ARBITRE -> trad.translate("GradeArbitrage.ARBITRE");
};
}
@Override
public String toString() {
return str;

View File

@@ -10,13 +10,14 @@ import java.util.List;
@Data
@RegisterForReflection
public class PageResult<T> {
@Schema(description = "Le numéro de la page courante.", example = "1")
@Schema(description = "Le numéro de la page courante.", examples = "1")
private int page;
@Schema(description = "Le nombre d'éléments par page.", example = "10")
@Schema(description = "Le nombre d'éléments par page.", examples = "10")
private int page_size;
@Schema(description = "Le nombre total de pages.", example = "5")
@Schema(description = "Le nombre total de pages.", examples = "5")
private int page_count;
@Schema(description = "Le nombre total d'éléments.", example = "47")
@Schema(description = "Le nombre total d'éléments.", examples = "47")
private long result_count;
private List<T> result = new ArrayList<>();
private Object additionalData;
}

View File

@@ -1,5 +1,6 @@
package fr.titionfire.ffsaf.utils;
import fr.titionfire.ffsaf.domain.service.TradService;
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
@@ -21,6 +22,19 @@ public enum RoleAsso {
this.level = level;
}
public String getString(TradService trad) {
return switch (this){
case MEMBRE -> trad.translate("RoleAsso.MEMBRE");
case PRESIDENT -> trad.translate("RoleAsso.PRESIDENT");
case VPRESIDENT -> trad.translate("RoleAsso.VPRESIDENT");
case SECRETAIRE -> trad.translate("RoleAsso.SECRETAIRE");
case VSECRETAIRE -> trad.translate("RoleAsso.VSECRETAIRE");
case TRESORIER -> trad.translate("RoleAsso.TRESORIER");
case VTRESORIER -> trad.translate("RoleAsso.VTRESORIER");
case MEMBREBUREAU -> trad.translate("RoleAsso.MEMBREBUREAU");
};
}
@Override
public String toString() {
return str;

View File

@@ -20,6 +20,8 @@ import java.io.*;
import java.net.URISyntaxException;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.CompletableFuture;
@@ -117,6 +119,15 @@ public class Utils {
}
}
public static Calendar getFirstDateOfSaison() {
Calendar calendar = Calendar.getInstance();
calendar.set(getSaison(), Calendar.SEPTEMBER, 1, 0, 0, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar;
}
public static Calendar toCalendar(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
@@ -244,15 +255,16 @@ public class Utils {
FilenameFilter filter = (directory, filename) -> filename.startsWith(id + ".");
File[] files = dirFile.listFiles(filter);
if (files != null) {
for (File f : files) {
for (File f2 : files) {
//noinspection ResultOfMethodCallIgnored
f.delete();
f2.delete();
}
}
File f = file.filePath().toFile();
//noinspection ResultOfMethodCallIgnored
f.renameTo(new File(dirFile, id + "." + detectedExtensions[0]));
Path f = file.filePath();
System.out.println(f + " -> " + new File(dirFile, id + "." + detectedExtensions[0]));
Files.copy(f, new File(dirFile, id + "." + detectedExtensions[0]).toPath(),
StandardCopyOption.REPLACE_EXISTING);
return "ok";
})));
}

View File

@@ -4,6 +4,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
import fr.titionfire.ffsaf.domain.service.CompetPermService;
import fr.titionfire.ffsaf.net2.MessageType;
import fr.titionfire.ffsaf.rest.data.SimpleCompetData;
import fr.titionfire.ffsaf.utils.SecurityCtx;
import fr.titionfire.ffsaf.ws.data.WelcomeInfo;
import fr.titionfire.ffsaf.ws.recv.*;
@@ -16,6 +17,7 @@ import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.annotation.PostConstruct;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.ws.rs.ForbiddenException;
import org.jboss.logging.Logger;
@@ -23,6 +25,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.Executor;
import static fr.titionfire.ffsaf.net2.Client_Thread.MAPPER;
@@ -40,10 +43,19 @@ public class CompetitionWS {
RCategorie rCategorie;
@Inject
RRegister rRegister;
RRegister rRegister;
@Inject
RCardboard rCardboard;
RCard rCard;
@Inject
RTeam rTeam;
@Inject
RState rState;
@Inject
RPDF rpdf;
@Inject
SecurityCtx securityCtx;
@@ -51,6 +63,15 @@ public class CompetitionWS {
@Inject
CompetPermService competPermService;
@SuppressWarnings("CdiInjectionPointsInspection")
@Inject
OpenConnections connections;
@Inject
@Named("notify-executor")
Executor notifyExecutor;
private static Executor executor;
@Inject
CompetitionRepository competitionRepository;
@@ -78,7 +99,12 @@ public class CompetitionWS {
getWSReceiverMethods(RMatch.class, rMatch);
getWSReceiverMethods(RCategorie.class, rCategorie);
getWSReceiverMethods(RRegister.class, rRegister);
getWSReceiverMethods(RCardboard.class, rCardboard);
getWSReceiverMethods(RCard.class, rCard);
getWSReceiverMethods(RTeam.class, rTeam);
getWSReceiverMethods(RState.class, rState);
getWSReceiverMethods(RPDF.class, rpdf);
executor = notifyExecutor;
}
@OnOpen
@@ -95,8 +121,8 @@ public class CompetitionWS {
.call(cm -> competPermService.hasEditPerm(securityCtx, cm).map(__ -> PermLevel.ADMIN)
.onFailure()
.recoverWithUni(competPermService.hasTablePerm(securityCtx, cm).map(__ -> PermLevel.TABLE))
.onFailure()
.recoverWithUni(competPermService.hasViewPerm(securityCtx, cm).map(__ -> PermLevel.VIEW))
//.onFailure()
//.recoverWithUni(competPermService.hasViewPerm(securityCtx, cm).map(__ -> PermLevel.VIEW))
.invoke(prem -> connection.userData().put(UserData.TypedKey.forString("prem"), prem.toString()))
.invoke(prem -> LOGGER.infof("Connection permission: %s", prem))
.onFailure().transform(t -> new ForbiddenException()))
@@ -105,10 +131,13 @@ public class CompetitionWS {
waitingResponse.put(connection, new HashMap<>());
})
.map(cm -> {
SimpleCompetData data = SimpleCompetData.fromModel(cm);
WelcomeInfo welcomeInfo = new WelcomeInfo();
welcomeInfo.setName(cm.getName());
welcomeInfo.setPerm(connection.userData().get(UserData.TypedKey.forString("prem")));
welcomeInfo.setShow_blason(data.isShow_blason());
welcomeInfo.setShow_flag(data.isShow_flag());
return new MessageOut(UUID.randomUUID(), "welcomeInfo", MessageType.NOTIFY, welcomeInfo);
});
@@ -120,6 +149,7 @@ public class CompetitionWS {
LOGGER.debugf("Active connections: %d", connection.getOpenConnections().size());
waitingResponse.remove(connection);
rState.removeConnection(connection);
}
private MessageOut makeReply(MessageIn message, Object data) {
@@ -186,7 +216,7 @@ public class CompetitionWS {
// return Uni.createFrom().item(new Message<>(message.uuid(), message.code(), MessageType.REPLY, "ko"));
}
public static Uni<Void> sendNotifyToOtherEditor(WebSocketConnection connection, String code, Object data) {
public static void sendNotifyToOtherEditor(WebSocketConnection connection, String code, Object data) {
String uuid = connection.pathParam("uuid");
List<Uni<Void>> queue = new ArrayList<>();
@@ -198,7 +228,39 @@ public class CompetitionWS {
}
});
return Uni.join().all(queue).andCollectFailures().onFailure().recoverWithNull().replaceWithVoid();
Uni.join().all(queue)
.andCollectFailures()
.runSubscriptionOn(executor)
.subscribeAsCompletionStage()
.whenComplete((v, t) -> {
if (t != null) {
LOGGER.error("Error sending ws_out message", t);
}
});
}
public static void sendNotifyState(WebSocketConnection connection, String code, Object data) {
String uuid = connection.pathParam("uuid");
List<Uni<Void>> queue = new ArrayList<>();
queue.add(Uni.createFrom().voidItem()); // For avoid empty queue
connection.getOpenConnections().forEach(c -> {
Boolean s = c.userData().get(UserData.TypedKey.forBoolean("needState"));
if (uuid.equals(c.pathParam("uuid")) && s != null && s) {
queue.add(c.sendText(new MessageOut(UUID.randomUUID(), code, MessageType.NOTIFY, data)));
}
});
Uni.join().all(queue)
.andCollectFailures()
.runSubscriptionOn(executor)
.subscribeAsCompletionStage()
.whenComplete((v, t) -> {
if (t != null) {
LOGGER.error("Error sending ws_out message", t);
}
});
}
@OnError

View File

@@ -0,0 +1,19 @@
package fr.titionfire.ffsaf.ws;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@Singleton
public class ExecutorProducer {
@Produces
@Named("notify-executor")
public Executor produceNotifyExecutor() {
// Créez un pool de threads avec une taille fixe (par exemple, 10 threads)
return Executors.newFixedThreadPool(10);
}
}

View File

@@ -8,4 +8,6 @@ import lombok.Data;
public class WelcomeInfo {
private String name;
private String perm;
private boolean show_blason;
private boolean show_flag;
}

View File

@@ -0,0 +1,159 @@
package fr.titionfire.ffsaf.ws.recv;
import fr.titionfire.ffsaf.data.model.CardModel;
import fr.titionfire.ffsaf.data.model.MatchModel;
import fr.titionfire.ffsaf.data.repository.CardRepository;
import fr.titionfire.ffsaf.data.repository.ClubCardRepository;
import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
import fr.titionfire.ffsaf.data.repository.MatchRepository;
import fr.titionfire.ffsaf.domain.service.CardService;
import fr.titionfire.ffsaf.domain.service.TradService;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import fr.titionfire.ffsaf.ws.PermLevel;
import fr.titionfire.ffsaf.ws.send.SSCard;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.runtime.annotations.RegisterForReflection;
import io.quarkus.websockets.next.WebSocketConnection;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.Date;
import java.util.List;
@WithSession
@ApplicationScoped
@RegisterForReflection
public class RCard {
@Inject
MatchRepository matchRepository;
@Inject
ClubCardRepository clubCardRepository;
@Inject
CardRepository cardRepository;
@Inject
CardService cardService;
@Inject
CompetitionRepository competitionRepository;
@Inject
TradService trad;
private Uni<MatchModel> getById(long id, WebSocketConnection connection) {
return matchRepository.findById(id)
.invoke(Unchecked.consumer(o -> {
if (o == null)
throw new DNotFoundException(trad.t("matche.non.trouver", connection));
if (!o.getCategory().getCompet().getUuid().equals(connection.pathParam("uuid")))
throw new DForbiddenException(trad.t("permission.denied", connection));
}));
}
@WSReceiver(code = "getCardForMatch", permission = PermLevel.VIEW)
public Uni<List<CardModel>> getCardForMatch(WebSocketConnection connection, Long matchId) {
if (matchId == null)
return Uni.createFrom().nullItem();
return getById(matchId, connection).chain(matchModel -> cardService.getForMatch(matchModel));
}
@WSReceiver(code = "getAllForTeamNoDetail", permission = PermLevel.VIEW)
public Uni<List<SendTeamCards>> getAllForTeamNoDetail(WebSocketConnection connection, Object o) {
return competitionRepository.find("uuid", connection.pathParam("uuid")).firstResult()
.chain(c -> clubCardRepository.list("competition = ?1", c.getId()))
.map(cards -> cards.stream()
.map(card -> new SendTeamCards(card.getTeamUuid(), card.getTeamName(), List.of(),
card.getType(), card.getReason(), card.getDate()))
.toList());
}
@WSReceiver(code = "sendCardAdd", permission = PermLevel.TABLE)
public Uni<Void> sendCardAdd(WebSocketConnection connection, SendCardAdd card) {
return getById(card.matchId(), connection)
.chain(matchModel -> cardService.checkCanBeAdded(card, matchModel)
.chain(c -> {
CardModel model = new CardModel();
model.setComb(card.combId());
model.setMatch(card.matchId());
model.setCategory(matchModel.getCategory().getId());
model.setCompetition(matchModel.getCategory().getCompet());
model.setCompetitionId(matchModel.getCategory().getCompet().getId());
model.setType(card.type());
model.setReason(card.reason());
return Panache.withTransaction(() -> cardRepository.persist(model));
})
)
.invoke(cardModel -> SSCard.sendCards(connection, List.of(cardModel)))
.replaceWithVoid();
}
@WSReceiver(code = "sendCardRm", permission = PermLevel.ADMIN)
public Uni<Void> sendCardRm(WebSocketConnection connection, SendCardAdd card) {
return competitionRepository.find("uuid", connection.pathParam("uuid")).firstResult()
.chain(competition -> cardRepository.find(
"match IS NULL AND comb = ?1 AND type = ?2 AND competition = ?3 AND match " + (card.matchId() == null ? "IS NULL" : "= " + card.matchId()),
card.combId(), card.type(), competition)
.firstResult()
.invoke(Unchecked.consumer(o -> {
if (o == null)
throw new DNotFoundException(trad.t("carton.non.trouver", connection));
SSCard.sendRmCards(connection, List.of(o.getId()));
}))
.chain(cardModel -> Panache.withTransaction(() -> cardRepository.delete(cardModel)))
)
.replaceWithVoid();
}
@WSReceiver(code = "applyTeamCards", permission = PermLevel.TABLE)
public Uni<Void> applyTeamCards(WebSocketConnection connection, SendTeamCards teamCards) {
return competitionRepository.find("uuid", connection.pathParam("uuid")).firstResult()
.chain(c -> cardService.addTeamCard(c, teamCards.teamUuid(), teamCards.teamName(), teamCards.type,
teamCards.reason()))
.invoke(cards -> SSCard.sendTeamCard(connection,
new SendTeamCards(teamCards.teamUuid(), teamCards.teamName(), cards, teamCards.type(),
teamCards.reason(), new Date())))
.replaceWithVoid();
}
@WSReceiver(code = "sendTeamCardReturnState", permission = PermLevel.TABLE)
public Uni<Void> sendTeamCardReturnState(WebSocketConnection connection, SendTeamCardReturnState state) {
if (state.state <= 0 || state.state > 2)
return Uni.createFrom().voidItem();
return competitionRepository.find("uuid", connection.pathParam("uuid")).firstResult()
.chain(c -> cardService.recvReturnState(c, state))
.invoke(cards -> SSCard.sendCards(connection, cards))
.replaceWithVoid();
}
@WSReceiver(code = "removeTeamCards", permission = PermLevel.TABLE)
public Uni<Void> removeTeamCards(WebSocketConnection connection, SendTeamCards teamCards) {
return competitionRepository.find("uuid", connection.pathParam("uuid")).firstResult()
.chain(c -> cardService.rmTeamCard(c, teamCards.teamUuid(), teamCards.teamName(), teamCards.type))
.invoke(cards -> SSCard.sendRmCards(connection, cards))
.invoke(__ -> SSCard.rmTeamCard(connection, teamCards))
.replaceWithVoid();
}
@RegisterForReflection
public record SendCardAdd(Long matchId, long combId, CardModel.CardType type, String reason) {
}
@RegisterForReflection
public record SendTeamCards(String teamUuid, String teamName, List<CardModel> cards, CardModel.CardType type,
String reason, Date date) {
}
@RegisterForReflection
public record SendTeamCardReturnState(String teamUuid, String teamName, CardModel.CardType type,
int state, Long selectedCategory, Long selectedMatch) {
}
}

View File

@@ -1,128 +0,0 @@
package fr.titionfire.ffsaf.ws.recv;
import fr.titionfire.ffsaf.data.model.CardboardModel;
import fr.titionfire.ffsaf.data.model.MatchModel;
import fr.titionfire.ffsaf.data.repository.CardboardRepository;
import fr.titionfire.ffsaf.data.repository.MatchRepository;
import fr.titionfire.ffsaf.domain.entity.CardboardEntity;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import fr.titionfire.ffsaf.ws.PermLevel;
import fr.titionfire.ffsaf.ws.send.SSCardboard;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.runtime.annotations.RegisterForReflection;
import io.quarkus.websockets.next.WebSocketConnection;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import lombok.Data;
import java.util.Objects;
@WithSession
@ApplicationScoped
@RegisterForReflection
public class RCardboard {
@Inject
MatchRepository matchRepository;
@Inject
CardboardRepository cardboardRepository;
private Uni<MatchModel> getById(long id, WebSocketConnection connection) {
return matchRepository.findById(id)
.invoke(Unchecked.consumer(o -> {
if (o == null)
throw new DNotFoundException("Matche non trouver");
if (!o.getCategory().getCompet().getUuid().equals(connection.pathParam("uuid")))
throw new DForbiddenException("Permission denied");
}));
}
@WSReceiver(code = "sendCardboardChange", permission = PermLevel.TABLE)
public Uni<Void> sendCardboardChange(WebSocketConnection connection, SendCardboard card) {
return getById(card.matchId, connection)
.chain(matchModel -> cardboardRepository.find("(comb.id = ?1 OR guestComb.id = ?2) AND match.id = ?3",
card.combId, card.combId * -1, card.matchId).firstResult()
.chain(model -> {
if (model != null) {
model.setRed(model.getRed() + card.red);
model.setYellow(model.getYellow() + card.yellow);
return Panache.withTransaction(() -> cardboardRepository.persist(model));
}
CardboardModel cardboardModel = new CardboardModel();
cardboardModel.setCompet(matchModel.getCategory().getCompet());
cardboardModel.setMatch(matchModel);
cardboardModel.setRed(card.red);
cardboardModel.setYellow(card.yellow);
cardboardModel.setComb(null);
cardboardModel.setGuestComb(null);
if (card.combId >= 0) {
if (matchModel.getC1_id() != null && matchModel.getC1_id().getId() == card.combId)
cardboardModel.setComb(matchModel.getC1_id());
if (matchModel.getC2_id() != null && matchModel.getC2_id().getId() == card.combId)
cardboardModel.setComb(matchModel.getC2_id());
} else {
if (matchModel.getC1_guest() != null && matchModel.getC1_guest()
.getId() == card.combId * -1)
cardboardModel.setGuestComb(matchModel.getC1_guest());
if (matchModel.getC2_guest() != null && matchModel.getC2_guest()
.getId() == card.combId * -1)
cardboardModel.setGuestComb(matchModel.getC2_guest());
}
if (cardboardModel.getComb() == null && cardboardModel.getGuestComb() == null)
return Uni.createFrom().nullItem();
return Panache.withTransaction(() -> cardboardRepository.persist(cardboardModel));
}))
.call(model -> SSCardboard.sendCardboard(connection, CardboardEntity.fromModel(model)))
.replaceWithVoid();
}
@WSReceiver(code = "getCardboardWithoutThis", permission = PermLevel.VIEW)
public Uni<CardboardAllMatch> getCardboardWithoutThis(WebSocketConnection connection, Long matchId) {
return getById(matchId, connection)
.chain(matchModel -> cardboardRepository.list("compet = ?1 AND match != ?2", matchModel.getCategory().getCompet(), matchModel)
.map(models -> {
CardboardAllMatch out = new CardboardAllMatch();
models.stream().filter(c -> (matchModel.getC1_id() != null
&& Objects.equals(c.getComb(), matchModel.getC1_id()))
|| (matchModel.getC1_guest() != null
&& Objects.equals(c.getGuestComb(), matchModel.getC1_guest())))
.forEach(c -> {
out.c1_yellow += c.getYellow();
out.c1_red += c.getRed();
});
models.stream().filter(c -> (matchModel.getC2_id() != null
&& Objects.equals(c.getComb(), matchModel.getC2_id()))
|| (matchModel.getC2_guest() != null
&& Objects.equals(c.getGuestComb(), matchModel.getC2_guest())))
.forEach(c -> {
out.c2_yellow += c.getYellow();
out.c2_red += c.getRed();
});
return out;
}));
}
@RegisterForReflection
public record SendCardboard(long matchId, long combId, int yellow, int red) {
}
@Data
@RegisterForReflection
public static class CardboardAllMatch {
int c1_yellow = 0;
int c1_red = 0;
int c2_yellow = 0;
int c2_red = 0;
}
}

View File

@@ -1,22 +1,25 @@
package fr.titionfire.ffsaf.ws.recv;
import fr.titionfire.ffsaf.data.model.CategoryModel;
import fr.titionfire.ffsaf.data.model.MatchModel;
import fr.titionfire.ffsaf.data.model.TreeModel;
import fr.titionfire.ffsaf.data.repository.CategoryRepository;
import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
import fr.titionfire.ffsaf.data.repository.MatchRepository;
import fr.titionfire.ffsaf.data.repository.TreeRepository;
import fr.titionfire.ffsaf.data.model.*;
import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.domain.entity.MatchEntity;
import fr.titionfire.ffsaf.domain.entity.MatchModelExtend;
import fr.titionfire.ffsaf.domain.entity.TreeEntity;
import fr.titionfire.ffsaf.domain.service.CardService;
import fr.titionfire.ffsaf.domain.service.ResultService;
import fr.titionfire.ffsaf.domain.service.TradService;
import fr.titionfire.ffsaf.rest.data.PresetData;
import fr.titionfire.ffsaf.rest.data.ResultCategoryData;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import fr.titionfire.ffsaf.utils.TreeNode;
import fr.titionfire.ffsaf.ws.PermLevel;
import fr.titionfire.ffsaf.ws.send.SSCategorie;
import fr.titionfire.ffsaf.ws.send.SSMatch;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.runtime.annotations.RegisterForReflection;
import io.quarkus.websockets.next.UserData;
import io.quarkus.websockets.next.WebSocketConnection;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.unchecked.Unchecked;
@@ -25,8 +28,9 @@ import jakarta.inject.Inject;
import lombok.Data;
import org.hibernate.reactive.mutiny.Mutiny;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@WithSession
@ApplicationScoped
@@ -46,13 +50,28 @@ public class RCategorie {
@Inject
TreeRepository treeRepository;
@Inject
CardService cardService;
@Inject
CardRepository cardRepository;
@Inject
CatPresetRepository catPresetRepository;
@Inject
ResultService resultService;
@Inject
TradService trad;
private Uni<CategoryModel> getById(long id, WebSocketConnection connection) {
return categoryRepository.findById(id)
.invoke(Unchecked.consumer(o -> {
if (o == null)
throw new DNotFoundException("Catégorie non trouver");
throw new DNotFoundException(trad.t("categorie.non.trouver", connection));
if (!o.getCompet().getUuid().equals(connection.pathParam("uuid")))
throw new DForbiddenException("Permission denied");
throw new DForbiddenException(trad.t("permission.denied", connection));
}));
}
@@ -72,6 +91,9 @@ public class RCategorie {
fullCategory.setName(cat.getName());
fullCategory.setLiceName(cat.getLiceName());
fullCategory.setType(cat.getType());
fullCategory.setTreeAreClassement(cat.isTreeAreClassement());
fullCategory.setFullClassement(cat.isFullClassement());
fullCategory.setPreset(PresetData.fromModel(cat.getPreset()));
})
.call(cat -> Mutiny.fetch(cat.getMatchs())
.map(matchModels -> matchModels.stream().filter(o -> o.getCategory_ord() >= 0)
@@ -80,6 +102,8 @@ public class RCategorie {
.call(cat -> treeRepository.list("category = ?1 AND level != 0", cat.getId())
.map(treeModels -> treeModels.stream().map(TreeEntity::fromModel).toList())
.invoke(fullCategory::setTrees))
.call(cat -> cardService.getAll(cat.getCompet())
.invoke(fullCategory::setCards))
.map(__ -> fullCategory);
}
@@ -94,20 +118,67 @@ public class RCategorie {
categoryModel.setTree(new ArrayList<>());
categoryModel.setType(categorie.type);
categoryModel.setLiceName(categorie.liceName);
categoryModel.setTreeAreClassement(categorie.treeAreClassement);
categoryModel.setFullClassement(categorie.fullClassement);
if (categorie.preset() != null)
return catPresetRepository.findById(categorie.preset().getId())
.invoke(categoryModel::setPreset)
.chain(__ -> categoryRepository.create(categoryModel));
return categoryRepository.create(categoryModel);
})
.call(cat -> SSCategorie.sendAddCategory(connection, cat))
.invoke(cat -> SSCategorie.sendAddCategory(connection, cat))
.map(CategoryModel::getId);
}
@WSReceiver(code = "createOrReplaceCategory", permission = PermLevel.ADMIN)
public Uni<Long> createOrReplaceCategory(WebSocketConnection connection, JustCategorie categorie) {
return matchRepository.list("category.compet.uuid = ?1 AND category.name = ?2", connection.pathParam("uuid"),
categorie.name)
.chain(existing -> {
if (existing.isEmpty())
return createCategory(connection, categorie);
Map<Long, List<MatchModel>> matchesByCategory = existing.stream()
.filter(m -> m.getCategory() != null)
.collect(Collectors.groupingBy(m -> m.getCategory().getId()));
for (Map.Entry<Long, List<MatchModel>> entry : matchesByCategory.entrySet()) {
Long categoryId = entry.getKey();
List<MatchModel> matches = entry.getValue();
if (matches.stream().noneMatch(m -> !m.getScores().isEmpty() || m.isEnd()))
return Panache.withTransaction(() -> updateCategory(connection, categorie, categoryId)
.call(__ -> treeRepository.delete("category = ?1", categoryId))
.call(__ -> matchRepository.delete("category.id = ?1", categoryId)))
.replaceWith(categoryId);
}
return createCategory(connection, categorie);
});
}
@WSReceiver(code = "updateCategory", permission = PermLevel.ADMIN)
public Uni<Void> updateCategory(WebSocketConnection connection, JustCategorie categorie) {
return getById(categorie.id, connection)
return updateCategory(connection, categorie, categorie.id);
}
private Uni<Void> updateCategory(WebSocketConnection connection, JustCategorie categorie, Long id) {
return getById(id, connection)
.call(cat -> {
if (categorie.preset() == null) {
cat.setPreset(null);
return Uni.createFrom().item(cat);
} else {
return catPresetRepository.findById(categorie.preset().getId())
.invoke(cat::setPreset);
}
})
.chain(cat -> {
cat.setName(categorie.name);
cat.setLiceName(categorie.liceName);
cat.setType(categorie.type);
cat.setTreeAreClassement(categorie.treeAreClassement);
cat.setFullClassement(categorie.fullClassement);
return Panache.withTransaction(() -> categoryRepository.persist(cat));
})
.call(cat -> {
@@ -121,7 +192,7 @@ public class RCategorie {
Uni<Long> finalUni = uni;
return Panache.withTransaction(() -> finalUni);
})
.call(cat -> SSCategorie.sendCategory(connection, cat))
.invoke(cat -> SSCategorie.sendCategory(connection, cat))
.replaceWithVoid();
}
@@ -202,7 +273,7 @@ public class RCategorie {
.call(__ -> treeRepository.flush())
.call(cat -> treeRepository.list("category = ?1 AND level != 0", cat.getId())
.map(treeModels -> treeModels.stream().map(TreeEntity::fromModel).toList())
.chain(trees -> SSCategorie.sendTreeCategory(connection, trees)))
.invoke(trees -> SSCategorie.sendTreeCategory(connection, trees)))
.replaceWithVoid();
}
@@ -212,14 +283,159 @@ public class RCategorie {
.call(cat -> Panache.withTransaction(() -> treeRepository.delete("category = ?1", cat.getId())
.call(__ -> matchRepository.delete("category = ?1", cat))))
.chain(cat -> Panache.withTransaction(() -> categoryRepository.delete(cat)))
.call(__ -> SSCategorie.sendDelCategory(connection, id))
.invoke(__ -> SSCategorie.sendDelCategory(connection, id))
.replaceWithVoid();
}
@WSReceiver(code = "listPreset", permission = PermLevel.VIEW)
public Uni<List<PresetData>> listPreset(WebSocketConnection connection, Object o) {
return catPresetRepository.list("competition.uuid", connection.pathParam("uuid"))
.map(presets -> presets.stream().map(PresetData::fromModel).toList());
}
@WSReceiver(code = "createClassementMatchs", permission = PermLevel.TABLE)
public Uni<Void> createClassementMatchs(WebSocketConnection connection, Long categoryId) {
return getById(categoryId, connection)
.call(cat -> {
PermLevel perm = PermLevel.valueOf(connection.userData().get(UserData.TypedKey.forString("prem")));
if (perm == PermLevel.TABLE) {
return matchRepository.list("category = ?1 AND category_ord = -42", cat.getId())
.chain(l -> l.stream().anyMatch(MatchModel::isEnd) ?
Uni.createFrom().failure(
new DForbiddenException(trad.t("err.match.termine", connection))) :
Uni.createFrom().voidItem());
}
return Uni.createFrom().voidItem();
})
.call(cat -> treeRepository.list("category = ?1 AND level <= -10", cat.getId())
.map(l -> l.stream().map(o -> o.getMatch().getId()).toList())
.call(__ -> treeRepository.delete("category = ?1 AND level <= -10", cat.getId()))
.call(ids -> matchRepository.delete("id IN ?1", ids)))
.call(cat -> Mutiny.fetch(cat.getTree()))
.call(cat -> {
List<MatchModel> toSave = new ArrayList<>();
if (!cat.getTree().isEmpty()) {
for (TreeModel treeModel : cat.getTree())
cleanTree(treeModel, toSave);
}
return Panache.withTransaction(() -> matchRepository.persist(toSave))
.invoke(__ -> SSMatch.sendMatch(connection,
toSave.stream().map(MatchEntity::fromModel).toList()));
})
.chain(cat -> Mutiny.fetch(cat.getMatchs())
.chain(list -> cardRepository.list("competition = ?1", cat.getCompet())
.map(c -> list.stream().map(m -> new MatchModelExtend(m, c)).toList()))
.map(matchModels -> {
ResultCategoryData out = new ResultCategoryData();
resultService.getArray2(matchModels, null, out);
out.getRankArray().remove('-');
return out;
})
.invoke(Unchecked.consumer(result -> {
if (result.getRankArray().size() != 2) {
throw new DForbiddenException(trad.t("configuration.non.supportee", connection));
}
}))
.chain(result -> {
List<MatchModel> toSave = new ArrayList<>();
List<MatchModel> toCreate = new ArrayList<>();
List<TreeModel> toSaveTree = new ArrayList<>();
int treeSize = 0;
List<TreeModel> lastNode = new ArrayList<>();
Optional<TreeModel> tree = cat.getTree().stream().filter(t -> t.getLevel() > 0)
.min(Comparator.comparing(TreeModel::getLevel));
if (tree.isPresent()) {
tree.get().getChildrenAtDepth(tree.get().death() - 1, 0, lastNode);
treeSize = lastNode.size();
}
Iterator<List<ResultCategoryData.RankArray>> iterator = result.getRankArray().values()
.iterator();
List<ResultCategoryData.RankArray> poule1 = iterator.next();
List<ResultCategoryData.RankArray> poule2 = iterator.next();
int maxToMixFill = Math.min(poule1.size(), poule2.size());
int maxToMixTreeFill = Math.min(treeSize, maxToMixFill);
for (int i = 0; i < maxToMixTreeFill; i++) {
CombModel comb1 = poule1.get(i).getComb();
CombModel comb2 = poule2.get(treeSize - i - 1).getComb();
fillMatchComb(lastNode.get(i).getMatch(), comb1, comb2);
toSave.add(lastNode.get(i).getMatch());
}
if (cat.isFullClassement()) {
for (int i = maxToMixTreeFill; i < maxToMixFill; i++) {
MatchModel match = new MatchModel();
match.setCategory(cat);
match.setCategory_ord(-42);
match.setEnd(false);
CombModel comb1 = poule1.get(i).getComb();
CombModel comb2 = poule2.get(i).getComb();
fillMatchComb(match, comb1, comb2);
toCreate.add(match);
toSaveTree.add(new TreeModel(cat.getId(), -10 - i, match));
}
}
return Panache.withTransaction(() -> matchRepository.persist(toSave)
.call(__ -> matchRepository.create(toCreate)))
.call(__ -> toSaveTree.isEmpty() ? Uni.createFrom().voidItem()
: Panache.withTransaction(() -> treeRepository.persist(toSaveTree)))
.map(__ -> Stream.concat(toSave.stream(), toCreate.stream())
.map(MatchEntity::fromModel).toList());
})
)
.onFailure().invoke(t -> System.out.println("error: " + t.getMessage()))
.invoke(matchEntities -> SSMatch.sendMatch(connection, matchEntities))
.call(__ -> treeRepository.list("category = ?1 AND level != 0", categoryId)
.map(treeModels -> treeModels.stream().map(TreeEntity::fromModel).toList())
.invoke(trees -> SSCategorie.sendTreeCategory(connection, trees)))
.replaceWithVoid();
}
private void cleanTree(TreeModel treeModel, List<MatchModel> toSave) {
MatchModel model = treeModel.getMatch();
if (model != null) {
model.setC1_id(null);
model.setC1_guest(null);
model.setC2_id(null);
model.setC2_guest(null);
model.setEnd(false);
model.setDate(null);
model.getScores().clear();
toSave.add(model);
}
if (treeModel.getLeft() != null)
cleanTree(treeModel.getLeft(), toSave);
if (treeModel.getRight() != null)
cleanTree(treeModel.getRight(), toSave);
}
private void fillMatchComb(MatchModel match, CombModel comb1, CombModel comb2) {
if (comb1 instanceof MembreModel m)
match.setC1_id(m);
else if (comb1 instanceof CompetitionGuestModel g)
match.setC1_guest(g);
if (comb2 instanceof MembreModel m)
match.setC2_id(m);
else if (comb2 instanceof CompetitionGuestModel g)
match.setC2_guest(g);
}
@RegisterForReflection
public record JustCategorie(long id, String name, int type, String liceName) {
public record JustCategorie(long id, String name, int type, String liceName, boolean treeAreClassement,
boolean fullClassement, PresetData preset) {
public static JustCategorie from(CategoryModel m) {
return new JustCategorie(m.getId(), m.getName(), m.getType(), m.getLiceName());
return new JustCategorie(m.getId(), m.getName(), m.getType(), m.getLiceName(), m.isTreeAreClassement(),
m.isFullClassement(), PresetData.fromModel(m.getPreset()));
}
}
@@ -234,7 +450,11 @@ public class RCategorie {
String name;
int type;
String liceName;
boolean treeAreClassement = false;
boolean fullClassement = false;
PresetData preset;
List<TreeEntity> trees = null;
List<MatchEntity> matches;
List<CardModel> cards;
}
}

View File

@@ -4,6 +4,7 @@ import fr.titionfire.ffsaf.data.model.*;
import fr.titionfire.ffsaf.data.repository.*;
import fr.titionfire.ffsaf.domain.entity.MatchEntity;
import fr.titionfire.ffsaf.domain.entity.TreeEntity;
import fr.titionfire.ffsaf.domain.service.TradService;
import fr.titionfire.ffsaf.rest.exception.DForbiddenException;
import fr.titionfire.ffsaf.rest.exception.DNotFoundException;
import fr.titionfire.ffsaf.utils.ScoreEmbeddable;
@@ -44,13 +45,19 @@ public class RMatch {
@Inject
CompetitionGuestRepository competitionGuestRepository;
@Inject
TradService trad;
@Inject
RState rState;
private Uni<MatchModel> getById(long id, WebSocketConnection connection) {
return matchRepository.findById(id)
.invoke(Unchecked.consumer(o -> {
if (o == null)
throw new DNotFoundException("Matche non trouver");
throw new DNotFoundException(trad.t("matche.non.trouver", connection));
if (!o.getCategory().getCompet().getUuid().equals(connection.pathParam("uuid")))
throw new DForbiddenException("Permission denied");
throw new DForbiddenException(trad.t("permission.denied", connection));
}));
}
@@ -78,13 +85,13 @@ public class RMatch {
return categoryRepository.findById(m.categorie)
.invoke(Unchecked.consumer(o -> {
if (o == null)
throw new DNotFoundException("Catégorie non trouver");
throw new DNotFoundException(trad.t("categorie.non.trouver", connection));
if (!o.getCompet().getUuid().equals(connection.pathParam("uuid")))
throw new DForbiddenException("Permission denied");
throw new DForbiddenException(trad.t("permission.denied", connection));
}))
.chain(categoryModel -> creatMatch(categoryModel, m))
.chain(mm -> Panache.withTransaction(() -> matchRepository.create(mm)))
.call(mm -> SSMatch.sendMatch(connection, MatchEntity.fromModel(mm)))
.invoke(mm -> SSMatch.sendMatch(connection, MatchEntity.fromModel(mm)))
.replaceWithVoid();
}
@@ -121,7 +128,7 @@ public class RMatch {
mm.setC2_guest(null);
}))
.chain(mm -> Panache.withTransaction(() -> matchRepository.persist(mm)))
.call(mm -> SSMatch.sendMatch(connection, MatchEntity.fromModel(mm)))
.invoke(mm -> SSMatch.sendMatch(connection, MatchEntity.fromModel(mm)))
.replaceWithVoid();
}
@@ -136,7 +143,7 @@ public class RMatch {
m.getCategory_ord()))
.invoke(m -> m.setCategory_ord(order.pos))
.call(m -> Panache.withTransaction(() -> matchRepository.persist(m)))
.call(mm -> SSMatch.sendMatchOrder(connection, order))
.invoke(mm -> SSMatch.sendMatchOrder(connection, order))
.replaceWithVoid();
}
@@ -163,12 +170,12 @@ public class RMatch {
.call(mm -> {
if (mm.isEnd() && mm.win() != old_win && mm.getCategory_ord() == -42) {
return updateEndAndTree(mm, new ArrayList<>())
.call(l -> SSMatch.sendMatch(connection, l));
.invoke(l -> SSMatch.sendMatch(connection, l));
}
return Uni.createFrom().nullItem();
});
})
.call(mm -> SSMatch.sendMatch(connection, MatchEntity.fromModel(mm)))
.invoke(mm -> SSMatch.sendMatch(connection, MatchEntity.fromModel(mm)))
.replaceWithVoid();
}
@@ -188,8 +195,9 @@ public class RMatch {
return Panache.withTransaction(() -> matchRepository.persist(mm));
})
.invoke(mm -> toSend.add(MatchEntity.fromModel(mm)))
.invoke(mm -> rState.setMatchEnd(connection, matchEnd))
.chain(mm -> updateEndAndTree(mm, toSend))
.call(__ -> SSMatch.sendMatch(connection, toSend))
.invoke(__ -> SSMatch.sendMatch(connection, toSend))
.replaceWithVoid();
}
@@ -277,8 +285,9 @@ public class RMatch {
@WSReceiver(code = "deleteMatch", permission = PermLevel.ADMIN)
public Uni<Void> deleteMatch(WebSocketConnection connection, Long idMatch) {
return getById(idMatch, connection)
.chain(matchModel -> Panache.withTransaction(() -> matchRepository.delete(matchModel)))
.call(__ -> SSMatch.sendDeleteMatch(connection, idMatch))
.map(__ -> idMatch)
.chain(l -> Panache.withTransaction(() -> matchRepository.delete("id = ?1", l)))
.invoke(__ -> SSMatch.sendDeleteMatch(connection, idMatch))
.replaceWithVoid();
}
@@ -288,34 +297,28 @@ public class RMatch {
return categoryRepository.findById(data.categorie)
.invoke(Unchecked.consumer(o -> {
if (o == null)
throw new DNotFoundException("Catégorie non trouver");
throw new DNotFoundException(trad.t("categorie.non.trouver", connection));
if (!o.getCompet().getUuid().equals(connection.pathParam("uuid")))
throw new DForbiddenException("Permission denied");
throw new DForbiddenException(trad.t("permission.denied", connection));
}))
.invoke(m -> System.out.println("A00"))
.call(cm -> data.matchesToRemove.isEmpty() ? Uni.createFrom().voidItem() :
(Panache.withTransaction(
() -> matchRepository.delete("id IN ?1 AND category = ?2", data.matchesToRemove, cm))
.call(__ -> SSMatch.sendDeleteMatch(connection, data.matchesToRemove))))
.invoke(m -> System.out.println("A0"))
.invoke(__ -> SSMatch.sendDeleteMatch(connection, data.matchesToRemove))))
.call(cm -> Panache.withSession(() -> matchRepository.list("id IN ?1 AND category = ?2",
Stream.concat(data.matchOrderToUpdate.keySet().stream(),
data.matchPouleToUpdate.keySet().stream())
.distinct().toList(), cm)
.invoke(m -> System.out.println("A " + m.size()))
.invoke(matchModels -> matchModels.forEach(model -> {
if (data.matchPouleToUpdate.containsKey(model.getId()))
model.setPoule(data.matchPouleToUpdate.get(model.getId()));
if (data.matchOrderToUpdate.containsKey(model.getId()))
model.setCategory_ord(data.matchOrderToUpdate.get(model.getId()));
}))
.invoke(m -> System.out.println("B " + m.size()))
.call(mm -> mm.isEmpty() ? Uni.createFrom().voidItem() :
Panache.withTransaction(() -> matchRepository.persist(mm)))
.invoke(m -> System.out.println("C"))
.invoke(mm -> matches.addAll(mm.stream().map(MatchEntity::fromModel).toList())))
)
.invoke(m -> System.out.println("D"))
.chain(categoryModel -> {
Uni<List<MatchModel>> uni = Uni.createFrom().item(new ArrayList<>());
for (AddMatch match : data.newMatch)
@@ -324,12 +327,10 @@ public class RMatch {
return Panache.withSession(() -> finalUni);
}
)
.invoke(m -> System.out.println("E"))
.chain(mm -> mm.isEmpty() ? Uni.createFrom().voidItem() :
Panache.withTransaction(() -> matchRepository.create(mm))
.invoke(__ -> matches.addAll(mm.stream().map(MatchEntity::fromModel).toList())))
.invoke(m -> System.out.println("F"))
.call(__ -> SSMatch.sendMatch(connection, matches))
.invoke(__ -> SSMatch.sendMatch(connection, matches))
.replaceWithVoid();
}

View File

@@ -0,0 +1,112 @@
package fr.titionfire.ffsaf.ws.recv;
import fr.titionfire.ffsaf.data.model.CardModel;
import fr.titionfire.ffsaf.data.model.CategoryModel;
import fr.titionfire.ffsaf.data.model.MatchModel;
import fr.titionfire.ffsaf.data.repository.CardRepository;
import fr.titionfire.ffsaf.data.repository.CategoryRepository;
import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
import fr.titionfire.ffsaf.data.repository.MatchRepository;
import fr.titionfire.ffsaf.domain.entity.MatchModelExtend;
import fr.titionfire.ffsaf.domain.service.ResultService;
import fr.titionfire.ffsaf.domain.service.TradService;
import fr.titionfire.ffsaf.rest.data.ResultCategoryData;
import fr.titionfire.ffsaf.utils.Categorie;
import fr.titionfire.ffsaf.ws.PermLevel;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.runtime.annotations.RegisterForReflection;
import io.quarkus.websockets.next.WebSocketConnection;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.transaction.Transactional;
import org.hibernate.reactive.mutiny.Mutiny;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Stream;
@WithSession
@ApplicationScoped
@RegisterForReflection
public class RPDF {
@Inject
CompetitionRepository competitionRepository;
@Inject
MatchRepository matchRepository;
@Inject
CardRepository cardRepository;
@Inject
CategoryRepository categoryRepository;
@Inject
ResultService resultService;
@Inject
TradService trad;
@Transactional
@WSReceiver(code = "getPodium", permission = PermLevel.VIEW)
public Uni<List<PodiumEntity>> getPodium(WebSocketConnection connection, Object o) {
List<CardModel> cards = new java.util.ArrayList<>();
return cardRepository.list("competition.uuid = ?1", connection.pathParam("uuid"))
.invoke(cards::addAll)
.chain(__ -> matchRepository.list("category.compet.uuid = ?1", connection.pathParam("uuid")))
.chain(matchs -> {
HashMap<CategoryModel, List<MatchModel>> map = new HashMap<>();
for (MatchModel match : matchs) {
if (!map.containsKey(match.getCategory()))
map.put(match.getCategory(), new java.util.ArrayList<>());
map.get(match.getCategory()).add(match);
}
return Multi.createFrom().iterable(map.entrySet())
.onItem().call(entry -> Mutiny.fetch(entry.getKey().getTree()))
.map(entry -> {
ResultCategoryData tmp = new ResultCategoryData();
double cmoy = entry.getValue().stream().flatMap(m -> Stream.of(m.getC1(), m.getC2()))
.filter(c -> c != null && c.getCategorie() != null)
.mapToInt(c -> c.getCategorie().ordinal())
.average().orElse(0);
Categorie categorie_moy = Categorie.values()[(int) Math.ceil(cmoy)];
resultService.getArray2(
entry.getValue().stream().map(m -> new MatchModelExtend(m, cards)).toList(),
null, tmp);
resultService.getClassementArray(entry.getKey(), null, cards, tmp);
String source = "";
if ((entry.getKey().getType() & 2) != 0) {
if (entry.getKey().isTreeAreClassement())
source = trad.t("podium.source.classement", connection);
else
source = trad.t("podium.source.tree", connection);
} else if ((entry.getKey().getType() & 1) != 0)
source = trad.t("podium.source.poule", connection);
return new PodiumEntity(entry.getKey().getName(), source, categorie_moy,
tmp.getClassement());
})
.collect().asList();
});
}
@WSReceiver(code = "getPodiumClub", permission = PermLevel.VIEW)
public Uni<List<ResultService.ClubClassement>> getPodiumClub(WebSocketConnection connection, Object o) {
return resultService.getAllClubArray(connection.pathParam("uuid"), false);
}
@RegisterForReflection
public static record PodiumEntity(String poule_name, String source, Categorie categorie,
List<ResultCategoryData.ClassementData> podium) {
}
}

View File

@@ -6,6 +6,7 @@ import fr.titionfire.ffsaf.ws.PermLevel;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.runtime.annotations.RegisterForReflection;
import io.quarkus.websockets.next.WebSocketConnection;
import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@@ -24,14 +25,20 @@ public class RRegister {
@WSReceiver(code = "getRegister", permission = PermLevel.TABLE)
public Uni<List<CombEntity>> getRegister(WebSocketConnection connection, Object o) {
ArrayList<CombEntity> combEntities = new ArrayList<>();
return competitionRepository.find("uuid", connection.pathParam("uuid")).firstResult()
.call(cm -> Mutiny.fetch(cm.getInsc()))
.call(cm -> Mutiny.fetch(cm.getGuests()))
.map(cm -> {
ArrayList<CombEntity> combEntities = new ArrayList<>();
combEntities.addAll(cm.getInsc().stream().map(CombEntity::fromModel).toList());
combEntities.addAll(cm.getGuests().stream().map(CombEntity::fromModel).toList());
return combEntities;
});
.call(cm -> Mutiny.fetch(cm.getInsc())
.onItem().transformToMulti(Multi.createFrom()::iterable)
.call(r -> Mutiny.fetch(r.getCategoriesInscrites()))
.map(r -> CombEntity.fromModel(r).addCategoriesInscrites(r.getCategoriesInscrites()))
.collect().asList()
.invoke(combEntities::addAll))
.call(cm -> Mutiny.fetch(cm.getGuests())
.onItem().transformToMulti(Multi.createFrom()::iterable)
.call(r -> Mutiny.fetch(r.getCategoriesInscrites()))
.map(r -> CombEntity.fromModel(r).addCategoriesInscrites(r.getCategoriesInscrites()))
.collect().asList()
.invoke(combEntities::addAll))
.replaceWith(combEntities);
}
}

View File

@@ -0,0 +1,149 @@
package fr.titionfire.ffsaf.ws.recv;
import fr.titionfire.ffsaf.ws.PermLevel;
import fr.titionfire.ffsaf.ws.send.SSState;
import io.quarkus.runtime.annotations.RegisterForReflection;
import io.quarkus.websockets.next.UserData;
import io.quarkus.websockets.next.WebSocketConnection;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import lombok.Data;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
@ApplicationScoped
@RegisterForReflection
public class RState {
private static final HashMap<WebSocketConnection, TableState> tableStates = new HashMap<>();
@WSReceiver(code = "subscribeToState", permission = PermLevel.VIEW)
public Uni<List<TableState>> sendCurrentScore(WebSocketConnection connection, Boolean subscribe) {
connection.userData().put(UserData.TypedKey.forBoolean("needState"), subscribe);
if (subscribe) {
String uuid = connection.pathParam("uuid");
return Uni.createFrom().item(() ->
tableStates.values().stream().filter(s -> s.getCompetitionUuid().equals(uuid)).toList()
);
}
return Uni.createFrom().nullItem();
}
@WSReceiver(code = "sendState", permission = PermLevel.TABLE)
public Uni<Void> sendState(WebSocketConnection connection, TableState tableState) {
tableState.setCompetitionUuid(connection.pathParam("uuid"));
if (tableStates.containsKey(connection))
tableState.setId(tableStates.get(connection).getId());
if (tableState.getChronoState().isRunning() && tableState.getChronoState().state == 0)
tableState.setState(MatchState.IN_PROGRESS);
tableStates.put(connection, tableState);
SSState.sendStateFull(connection, tableState);
return Uni.createFrom().voidItem();
}
@WSReceiver(code = "sendSelectCategory", permission = PermLevel.TABLE)
public Uni<Void> sendSelectCategory(WebSocketConnection connection, Long catId) {
TableState tableState = tableStates.get(connection);
if (tableState != null) {
tableState.setSelectedCategory(catId);
tableState.setState(MatchState.NOT_STARTED);
SSState.sendStateFull(connection, tableState);
}
return Uni.createFrom().voidItem();
}
@WSReceiver(code = "sendSelectMatch", permission = PermLevel.TABLE)
public Uni<Void> sendSelectMatch(WebSocketConnection connection, Long matchId) {
TableState tableState = tableStates.get(connection);
if (tableState != null) {
tableState.setSelectedMatch(matchId);
tableState.setState(MatchState.NOT_STARTED);
SSState.sendStateFull(connection, tableState);
}
return Uni.createFrom().voidItem();
}
@WSReceiver(code = "sendCurentChrono", permission = PermLevel.TABLE)
public Uni<Void> sendCurentChrono(WebSocketConnection connection, ChronoState chronoState) {
TableState tableState = tableStates.get(connection);
if (tableState != null) {
tableState.setChronoState(chronoState);
if (chronoState.isRunning())
tableState.setState(MatchState.IN_PROGRESS);
SSState.sendStateFull(connection, tableState);
}
return Uni.createFrom().voidItem();
}
@WSReceiver(code = "sendLicenceName", permission = PermLevel.TABLE)
public Uni<Void> sendCurrentScore(WebSocketConnection connection, String name) {
TableState tableState = tableStates.get(connection);
if (tableState != null) {
tableState.setLiceName(name);
SSState.sendStateFull(connection, tableState);
}
return Uni.createFrom().voidItem();
}
@WSReceiver(code = "sendCurrentScore", permission = PermLevel.TABLE)
public Uni<Void> sendCurrentScore(WebSocketConnection connection, ScoreState scoreState) {
TableState tableState = tableStates.get(connection);
if (tableState != null) {
tableState.setScoreState(scoreState);
SSState.sendStateFull(connection, tableState);
}
return Uni.createFrom().voidItem();
}
public void removeConnection(WebSocketConnection connection) {
if (tableStates.containsKey(connection)) {
SSState.sendRmStateFull(connection, tableStates.get(connection).getId());
tableStates.remove(connection);
}
}
public void setMatchEnd(WebSocketConnection connection, RMatch.MatchEnd matchEnd) {
if (tableStates.containsKey(connection)) {
TableState tableState = tableStates.get(connection);
if (matchEnd.end())
tableState.setState(MatchState.ENDED);
else
tableState.setState(MatchState.IN_PROGRESS);
SSState.sendStateFull(connection, tableState);
}
}
@RegisterForReflection
public record ChronoState(long time, long startTime, long configTime, long configPause, int state) {
public boolean isRunning() {
return startTime != 0 || state != 0;
}
}
@RegisterForReflection
public record ScoreState(int scoreRouge, int scoreBleu) {
}
@Data
@RegisterForReflection
public static class TableState {
UUID id = UUID.randomUUID();
String competitionUuid;
Long selectedCategory;
Long selectedMatch;
ChronoState chronoState;
ScoreState scoreState;
String liceName = "???";
MatchState state = MatchState.NOT_STARTED;
}
public enum MatchState {
NOT_STARTED,
IN_PROGRESS,
ENDED
}
}

View File

@@ -0,0 +1,137 @@
package fr.titionfire.ffsaf.ws.recv;
import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
import fr.titionfire.ffsaf.data.model.RegisterModel;
import fr.titionfire.ffsaf.data.repository.CompetitionGuestRepository;
import fr.titionfire.ffsaf.data.repository.CompetitionRepository;
import fr.titionfire.ffsaf.data.repository.RegisterRepository;
import fr.titionfire.ffsaf.domain.entity.CombEntity;
import fr.titionfire.ffsaf.domain.service.TradService;
import fr.titionfire.ffsaf.utils.Categorie;
import fr.titionfire.ffsaf.utils.Genre;
import fr.titionfire.ffsaf.utils.Pair;
import fr.titionfire.ffsaf.ws.PermLevel;
import fr.titionfire.ffsaf.ws.send.SSRegister;
import io.quarkus.hibernate.reactive.panache.Panache;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.runtime.annotations.RegisterForReflection;
import io.quarkus.websockets.next.WebSocketConnection;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@WithSession
@ApplicationScoped
@RegisterForReflection
public class RTeam {
@Inject
TradService trad;
@Inject
CompetitionRepository competitionRepository;
@Inject
RegisterRepository registerRepository;
@Inject
CompetitionGuestRepository competitionGuestRepository;
@WSReceiver(code = "setTeam", permission = PermLevel.ADMIN)
public Uni<CombEntity> setTeam(WebSocketConnection connection, TeamData data) {
return competitionRepository.find("uuid", connection.pathParam("uuid")).firstResult()
.chain(cm -> registerRepository.list("membre.id IN ?1 AND competition = ?2",
data.members.stream().filter(id -> id >= 0).toList(), cm)
.chain(l -> competitionGuestRepository.list("id IN ?1",
data.members.stream().filter(id -> id < 0).map(i -> i * -1).toList())
.map(l2 -> new Pair<>(l, l2)))
.chain(pair ->
competitionGuestRepository.find("fname = ?1 AND lname = ?2 AND competition = ?3",
data.name, "__team", cm).firstResult()
.chain(team -> {
if (pair.getKey().isEmpty() && pair.getValue().isEmpty()) {
if (team != null) {
CompetitionGuestModel finalTeam1 = team;
SSRegister.sendRegisterRemove(connection, finalTeam1.getId() * -1);
return Panache.withTransaction(
() -> competitionGuestRepository.delete(finalTeam1))
.replaceWith((CombEntity) null);
} else
return Uni.createFrom().item((CombEntity) null);
}
if (team == null) {
// Create new team
team = new CompetitionGuestModel();
team.setFname(data.name);
team.setLname("__team");
team.setCompetition(cm);
team.setClub("Team");
team.setGenre(Genre.NA);
} else {
team.getComb().clear();
team.getGuest().clear();
}
team.setCategorie(Stream.concat(
pair.getKey().stream().map(RegisterModel::getCategorie2),
pair.getValue().stream().map(CompetitionGuestModel::getCategorie))
.map(Enum::ordinal)
.max(Integer::compareTo)
.map(i -> Categorie.values()[i]).orElse(Categorie.SENIOR1));
List<Float> s = Stream.concat(
pair.getKey().stream().map(RegisterModel::getWeight),
pair.getValue().stream().map(CompetitionGuestModel::getWeight))
.filter(Objects::nonNull).toList();
if (s.isEmpty()) {
team.setWeight(null);
} else if (s.size() == 1) {
team.setWeight(s.get(0));
} else {
team.setWeight((float) s.stream().mapToDouble(Float::doubleValue)
.average()
.orElse(0));
}
team.setCountry(Stream.concat(
pair.getKey().stream().map(m -> m.getMembre().getCountry()),
pair.getValue().stream().map(CompetitionGuestModel::getCountry))
.filter(Objects::nonNull)
.map(String::toUpperCase)
.collect(Collectors.groupingBy(
e -> e, // Classer par élément
Collectors.counting() // Compter les occurrences
))
.entrySet()
.stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElse("FR"));
team.getComb().addAll(
pair.getKey().stream().map(RegisterModel::getMembre).toList());
team.getGuest().addAll(pair.getValue());
CompetitionGuestModel finalTeam = team;
return Panache.withTransaction(
() -> competitionGuestRepository.persistAndFlush(finalTeam))
.map(CombEntity::fromModel);
}))
)
.invoke(combEntity -> {
if (combEntity != null)
SSRegister.sendRegister(connection, combEntity);
});
}
@RegisterForReflection
public record TeamData(List<Long> members, String name) {
}
}

View File

@@ -3,6 +3,7 @@ package fr.titionfire.ffsaf.ws.send;
import fr.titionfire.ffsaf.data.model.CompetitionGuestModel;
import fr.titionfire.ffsaf.data.model.RegisterModel;
import fr.titionfire.ffsaf.domain.entity.CombEntity;
import fr.titionfire.ffsaf.domain.service.CardService;
import fr.titionfire.ffsaf.net2.MessageType;
import fr.titionfire.ffsaf.ws.CompetitionWS;
import fr.titionfire.ffsaf.ws.MessageOut;
@@ -13,6 +14,7 @@ import io.quarkus.websockets.next.UserData;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.hibernate.reactive.mutiny.Mutiny;
import java.util.List;
import java.util.UUID;
@@ -25,16 +27,48 @@ public class SRegister {
@Inject
OpenConnections connections;
@Inject
CardService cardService;
public Uni<Void> sendRegister(String uuid, RegisterModel registerModel) {
return send(uuid, "sendRegister", CombEntity.fromModel(registerModel));
return Mutiny.fetch(registerModel.getCategoriesInscrites()).chain(o ->
send(uuid, "sendRegister", CombEntity.fromModel(registerModel).addCategoriesInscrites(o))
.call(__ -> registerModel.getClub2() == null ? Uni.createFrom().voidItem() :
cardService.addTeamCartToNewComb(registerModel.getMembre().getId(),
registerModel.getClub2().getClubId(), registerModel.getClub2().getName(),
registerModel.getCompetition())
.chain(cardModels -> send(uuid, "sendCards", cardModels))));
}
public Uni<Void> sendRegisterNoFetch(String uuid, RegisterModel registerModel) {
return send(uuid, "sendRegister",
CombEntity.fromModel(registerModel).addCategoriesInscrites(registerModel.getCategoriesInscrites()))
.call(__ -> registerModel.getClub2() == null ? Uni.createFrom().voidItem() :
cardService.addTeamCartToNewComb(registerModel.getMembre().getId(),
registerModel.getClub2().getClubId(), registerModel.getClub2().getName(),
registerModel.getCompetition())
.chain(cardModels -> send(uuid, "sendCards", cardModels)));
}
public Uni<Void> sendRegister(String uuid, CompetitionGuestModel model) {
return send(uuid, "sendRegister", CombEntity.fromModel(model));
return Mutiny.fetch(model.getCategoriesInscrites()).chain(o ->
send(uuid, "sendRegister", CombEntity.fromModel(model).addCategoriesInscrites(o))
.call(__ -> cardService.addTeamCartToNewComb(model.getId() * -1,
null, model.getClub(), model.getCompetition())
.chain(cardModels -> send(uuid, "sendCards", cardModels))));
}
public Uni<Void> sendRegisterNoFetch(String uuid, CompetitionGuestModel model) {
return send(uuid, "sendRegister",
CombEntity.fromModel(model).addCategoriesInscrites(model.getCategoriesInscrites()))
.call(__ -> cardService.addTeamCartToNewComb(model.getId() * -1,
null, model.getClub(), model.getCompetition())
.chain(cardModels -> send(uuid, "sendCards", cardModels)));
}
public Uni<Void> sendRegisterRemove(String uuid, Long combId) {
return send(uuid, "sendRegisterRemove", combId);
return send(uuid, "sendRegisterRemove", combId)
.call(__ -> cardService.rmTeamCardFromComb(combId, uuid));
}
public Uni<Void> send(String uuid, String code, Object data) {

View File

@@ -0,0 +1,27 @@
package fr.titionfire.ffsaf.ws.send;
import fr.titionfire.ffsaf.data.model.CardModel;
import fr.titionfire.ffsaf.ws.CompetitionWS;
import fr.titionfire.ffsaf.ws.recv.RCard;
import io.quarkus.websockets.next.WebSocketConnection;
import java.util.List;
public class SSCard {
public static void sendCards(WebSocketConnection connection, List<CardModel> cardModel) {
CompetitionWS.sendNotifyToOtherEditor(connection, "sendCards", cardModel);
}
public static void sendRmCards(WebSocketConnection connection, List<Long> ids) {
CompetitionWS.sendNotifyToOtherEditor(connection, "rmCards", ids);
}
public static void sendTeamCard(WebSocketConnection connection, RCard.SendTeamCards teamCards) {
CompetitionWS.sendNotifyToOtherEditor(connection, "sendTeamCard", teamCards);
}
public static void rmTeamCard(WebSocketConnection connection, RCard.SendTeamCards teamCards) {
CompetitionWS.sendNotifyToOtherEditor(connection, "rmTeamCard", teamCards);
}
}

View File

@@ -1,13 +0,0 @@
package fr.titionfire.ffsaf.ws.send;
import fr.titionfire.ffsaf.domain.entity.CardboardEntity;
import fr.titionfire.ffsaf.ws.CompetitionWS;
import io.quarkus.websockets.next.WebSocketConnection;
import io.smallrye.mutiny.Uni;
public class SSCardboard {
public static Uni<Void> sendCardboard(WebSocketConnection connection, CardboardEntity cardboardEntity) {
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendCardboard", cardboardEntity);
}
}

View File

@@ -5,33 +5,32 @@ import fr.titionfire.ffsaf.domain.entity.TreeEntity;
import fr.titionfire.ffsaf.ws.CompetitionWS;
import fr.titionfire.ffsaf.ws.recv.RCategorie;
import io.quarkus.websockets.next.WebSocketConnection;
import io.smallrye.mutiny.Uni;
import java.util.List;
public class SSCategorie {
public static Uni<Void> sendAddCategory(WebSocketConnection connection, CategoryModel category) {
return SSCategorie.sendAddCategory(connection, RCategorie.JustCategorie.from(category));
public static void sendAddCategory(WebSocketConnection connection, CategoryModel category) {
SSCategorie.sendAddCategory(connection, RCategorie.JustCategorie.from(category));
}
public static Uni<Void> sendAddCategory(WebSocketConnection connection, RCategorie.JustCategorie justCategorie) {
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendAddCategory", justCategorie);
public static void sendAddCategory(WebSocketConnection connection, RCategorie.JustCategorie justCategorie) {
CompetitionWS.sendNotifyToOtherEditor(connection, "sendAddCategory", justCategorie);
}
public static Uni<Void> sendCategory(WebSocketConnection connection, CategoryModel category) {
return SSCategorie.sendCategory(connection, RCategorie.JustCategorie.from(category));
public static void sendCategory(WebSocketConnection connection, CategoryModel category) {
SSCategorie.sendCategory(connection, RCategorie.JustCategorie.from(category));
}
public static Uni<Void> sendCategory(WebSocketConnection connection, RCategorie.JustCategorie justCategorie) {
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendCategory", justCategorie);
public static void sendCategory(WebSocketConnection connection, RCategorie.JustCategorie justCategorie) {
CompetitionWS.sendNotifyToOtherEditor(connection, "sendCategory", justCategorie);
}
public static Uni<Void> sendTreeCategory(WebSocketConnection connection, List<TreeEntity> treeEntities) {
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendTreeCategory", treeEntities);
public static void sendTreeCategory(WebSocketConnection connection, List<TreeEntity> treeEntities) {
CompetitionWS.sendNotifyToOtherEditor(connection, "sendTreeCategory", treeEntities);
}
public static Uni<?> sendDelCategory(WebSocketConnection connection, Long id) {
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendDelCategory", id);
public static void sendDelCategory(WebSocketConnection connection, Long id) {
CompetitionWS.sendNotifyToOtherEditor(connection, "sendDelCategory", id);
}
}

View File

@@ -4,29 +4,28 @@ import fr.titionfire.ffsaf.domain.entity.MatchEntity;
import fr.titionfire.ffsaf.ws.CompetitionWS;
import fr.titionfire.ffsaf.ws.recv.RMatch;
import io.quarkus.websockets.next.WebSocketConnection;
import io.smallrye.mutiny.Uni;
import java.util.List;
public class SSMatch {
public static Uni<Void> sendMatch(WebSocketConnection connection, MatchEntity matchEntity) {
return SSMatch.sendMatch(connection, List.of(matchEntity));
public static void sendMatch(WebSocketConnection connection, MatchEntity matchEntity) {
SSMatch.sendMatch(connection, List.of(matchEntity));
}
public static Uni<Void> sendMatch(WebSocketConnection connection, List<MatchEntity> matchEntities) {
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendMatch", matchEntities);
public static void sendMatch(WebSocketConnection connection, List<MatchEntity> matchEntities) {
CompetitionWS.sendNotifyToOtherEditor(connection, "sendMatch", matchEntities);
}
public static Uni<Void> sendMatchOrder(WebSocketConnection connection, RMatch.MatchOrder matchOrder) {
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendMatchOrder", matchOrder);
public static void sendMatchOrder(WebSocketConnection connection, RMatch.MatchOrder matchOrder) {
CompetitionWS.sendNotifyToOtherEditor(connection, "sendMatchOrder", matchOrder);
}
public static Uni<Void> sendDeleteMatch(WebSocketConnection connection, Long l) {
return SSMatch.sendDeleteMatch(connection, List.of(l));
public static void sendDeleteMatch(WebSocketConnection connection, Long l) {
SSMatch.sendDeleteMatch(connection, List.of(l));
}
public static Uni<Void> sendDeleteMatch(WebSocketConnection connection, List<Long> longs) {
return CompetitionWS.sendNotifyToOtherEditor(connection, "sendDeleteMatch", longs);
public static void sendDeleteMatch(WebSocketConnection connection, List<Long> longs) {
CompetitionWS.sendNotifyToOtherEditor(connection, "sendDeleteMatch", longs);
}
}

View File

@@ -0,0 +1,16 @@
package fr.titionfire.ffsaf.ws.send;
import fr.titionfire.ffsaf.domain.entity.CombEntity;
import fr.titionfire.ffsaf.ws.CompetitionWS;
import io.quarkus.websockets.next.WebSocketConnection;
public class SSRegister {
public static void sendRegister(WebSocketConnection connection, CombEntity combEntity) {
CompetitionWS.sendNotifyToOtherEditor(connection, "sendRegister", combEntity);
}
public static void sendRegisterRemove(WebSocketConnection connection, Long combId) {
CompetitionWS.sendNotifyToOtherEditor(connection, "sendRegisterRemove", combId);
}
}

View File

@@ -0,0 +1,19 @@
package fr.titionfire.ffsaf.ws.send;
import fr.titionfire.ffsaf.ws.CompetitionWS;
import fr.titionfire.ffsaf.ws.recv.RState;
import io.quarkus.websockets.next.WebSocketConnection;
import java.util.UUID;
public class SSState {
public static void sendStateFull(WebSocketConnection connection, RState.TableState state) {
CompetitionWS.sendNotifyState(connection, "sendStateFull", state);
}
public static void sendRmStateFull(WebSocketConnection connection, UUID id) {
CompetitionWS.sendNotifyState(connection, "rmStateFull", id);
}
}

View File

@@ -8,6 +8,7 @@ quarkus.hibernate-orm.database.generation=update
quarkus.hibernate-orm.physical-naming-strategy=fr.titionfire.ffsaf.data.SafcaNamingStrategy
quarkus.hibernate-orm.dialect=fr.titionfire.ffsaf.data.CustomPostgreSQLDialect
quarkus.datasource.jdbc.reactive.max-size=40
quarkus.http.cors.enabled=true
quarkus.quartz.start-mode=forced
@@ -67,7 +68,7 @@ quarkus.http.auth.permission.public.policy=permit
quarkus.keycloak.admin-client.server-url=https://auth.safca.fr
quarkus.native.resources.includes=asset/**
quarkus.native.resources.includes=asset/**,lang/**
# HelloAsso Connector
helloasso.api=https://api.helloasso.com

View File

@@ -1,16 +0,0 @@
filtre.all=--tout--
no.licence=Non licenci\u00E9
# Categories
Cat.SUPER_MINI=Super Mini
Cat.MINI_POUSSIN=Mini Poussin
Cat.POUSSIN= Poussin
Cat.BENJAMIN=Benjamin
Cat.MINIME=Minime
Cat.CADET=Cadet
Cat.JUNIOR=Junior
Cat.SENIOR1=Senior 1
Cat.SENIOR2=Senior 2
Cat.VETERAN1=V\u00E9t\u00E9ran 1
Cat.VETERAN2=V\u00E9t\u00E9ran 2

View File

@@ -0,0 +1,94 @@
filtre.all=--all--
no.licence=Not licensed
Cat.SUPER_MINI=Super Mini
Cat.MINI_POUSSIN=Mini Chick
Cat.POUSSIN=Chick
Cat.BENJAMIN=Benjamin
Cat.MINIME=Minime
Cat.CADET=Cadet
Cat.JUNIOR=Junior
Cat.SENIOR1=Senior 1
Cat.SENIOR2=Senior 2
Cat.VETERAN1=Veteran 1
Cat.VETERAN2=Veteran 2
access.denied=Access denied
club.not.found=Club not found
combattant.non.inscrit=Fighter not registered
comb.not.found=Fighter not found
matche.non.trouver=Match not found
permission.denied=Permission denied
categorie.non.trouver=Category not found
RoleAsso.MEMBRE=Member
RoleAsso.PRESIDENT=President
RoleAsso.VPRESIDENT=Vice-President
RoleAsso.SECRETAIRE=Secretary
RoleAsso.VSECRETAIRE=Vice-Secretary
RoleAsso.TRESORIER=Treasurer
RoleAsso.VTRESORIER=Vice-Treasurer
RoleAsso.MEMBREBUREAU=Board Member
sans.club=No club
categorie.inconnue=Unknown category
GradeArbitrage.NA=N/A
GradeArbitrage.ASSESSEUR=Assessor
GradeArbitrage.ARBITRE=Referee
Genre.Homme=Male
Genre.Femme=Female
Genre.NA=Not defined
Contact.COURRIEL=Email
Contact.TELEPHONE=Phone
Contact.SITE=Website
Contact.FACEBOOK=Facebook
Contact.INSTAGRAM=Instagram
Contact.AUTRE=Other
service.momentanement.indisponible=Service temporarily unavailable
asso.introuvable=Association not found
erreur.lors.calcul.du.trie=Error during sorting calculation
page.out.of.range=Page out of range
le.membre.appartient.pas.a.votre.club=Member no. %d does not belong to your club
email.deja.utilise.par=Email '%s' already used by %s %s
try.edit.licence=To register a new member, please leave the license field blank. (Unauthorized attempt to change the name on license %d for %s %s)
email.deja.utilise=Email already in use
regiter.new.membre=To register a new member, please use the dedicated button.
permission.insuffisante=Insufficient permission
membre.deja.existent=Member already exists
membre.rm.err1=Cannot delete a member who already has a license number
membre.rm.err2=Cannot delete a member with licenses
pas.de.licence.pour.la.saison.en.cours=No license for the current season
club.non.trouve=Club not found
pas.d.affiliation.pour.la.saison.en.cours=No affiliation for the current season
erreur.lors.de.la.selection.des.membres=Error during member selection
licence.rm.err1=Cannot delete a license for which payment is in progress
licence.deja.demandee=License already requested
impossible.de.supprimer.une.licence.deja.validee=Cannot delete an already validated license
impossible.de.supprimer.une.licence.deja.payee=Cannot delete an already paid license
vous.ne.pouvez.pas.creer.de.competition=You cannot create a competition
user.not.found=User %s not found
inscription.fermee=Registration closed
insc.err1=You do not have the right to register this member (by decision of the competition administrator)
insc.err2=You do not have the right to register (by decision of the competition administrator)
insc.err3=Modification blocked by the competition administrator
licence.non.trouve=License %s not found
nom.et.prenom.requis=Last name and first name required
combattant.non.trouve=Fighter %s %s not found
le.membre.n.existe.pas=Member %d does not exist
competition.is.not.internal=Competition is not INTERNAL
erreur.de.format.des.contacts=Contact format error
competition.not.found=Competition not found
saison.non.valid=Invalid season
demande.d.affiliation.deja.existante=Affiliation request already exists
affiliation.deja.existante=Affiliation already exists
licence.membre.n.1.inconnue=License member no. 1 unknown
licence.membre.n.2.inconnue=License member no. 2 unknown
licence.membre.n.3.inconnue=License member no. 3 unknown
demande.d.affiliation.non.trouve=Affiliation request not found
carton.non.trouver=Card not found
card.cannot.be.added=Unable to add the card
configuration.non.supportee=Unsupported configuration
err.match.termine=Error, a placement match has already been played
podium.source.classement=Ranking
podium.source.tree=Tournaments
podium.source.poule=Pool

View File

@@ -0,0 +1,90 @@
filtre.all=--Tous--
no.licence=Non licencié
Cat.SUPER_MINI=Super Mini
Cat.MINI_POUSSIN=Mini-Poussin
Cat.POUSSIN=Poussin
Cat.BENJAMIN=Benjamin
Cat.MINIME=Minime
Cat.CADET=Cadet
Cat.JUNIOR=Junior
Cat.SENIOR1=Senior 1
Cat.SENIOR2=Senior 2
Cat.VETERAN1=Vétéran 1
Cat.VETERAN2=Vétéran 2
access.denied=Accès refusé
club.not.found=Club introuvable
combattant.non.inscrit=Combattant non inscrit
comb.not.found=Combat introuvable
matche.non.trouver=Match introuvable
permission.denied=Permission refusée
categorie.non.trouver=Catégorie introuvable
RoleAsso.MEMBRE=Membre
RoleAsso.PRESIDENT=Président
RoleAsso.VPRESIDENT=Vice-Président
RoleAsso.SECRETAIRE=Secrétaire
RoleAsso.VSECRETAIRE=Vice-Secrétaire
RoleAsso.TRESORIER=Trésorier
RoleAsso.VTRESORIER=Vice-Trésorier
RoleAsso.MEMBREBUREAU=Membre bureau
sans.club=Sans club
categorie.inconnue=catégorie inconnue
GradeArbitrage.NA=N/A
GradeArbitrage.ASSESSEUR=Assesseur
GradeArbitrage.ARBITRE=Arbitre
Genre.Homme=Homme
Genre.Femme=Femme
Genre.NA=Non défini
Contact.COURRIEL=Courriel
Contact.TELEPHONE=Téléphone
Contact.SITE=Site web
Contact.FACEBOOK=Facebook
Contact.INSTAGRAM=Instagram
Contact.AUTRE=Autre
service.momentanement.indisponible=Service momentanément indisponible
asso.introuvable=Association introuvable
erreur.lors.calcul.du.trie=Erreur lors du calcul du tri
page.out.of.range=Page out of range
le.membre.appartient.pas.a.votre.club=Le membre n°%d n'appartient pas à votre club
email.deja.utilise.par=L'adresse e-mail '%s' est déjà utilisée par %s %s
try.edit.licence=Pour enregistrer un nouveau membre, veuillez laisser le champ licence vide. (Tentative de modification non autorisée du nom sur la licence %d pour %s %s)
email.deja.utilise=Adresse e-mail déjà utilisée
regiter.new.membre=Pour enregistrer un nouveau membre, veuillez utiliser le bouton prévu à cet effet.
permission.insuffisante=Permissions insuffisantes
membre.deja.existent=Membre déjà existant
membre.rm.err1=Impossible de supprimer un membre qui possède déjà un numéro de licence
membre.rm.err2=Impossible de supprimer un membre ayant des licences actives
pas.de.licence.pour.la.saison.en.cours=Aucune licence pour la saison en cours
club.non.trouve=Club introuvable
pas.d.affiliation.pour.la.saison.en.cours=Aucune affiliation pour la saison en cours
erreur.lors.de.la.selection.des.membres=Erreur lors de la sélection des membres
licence.rm.err1=Impossible de supprimer une licence pour laquelle un paiement est en cours
licence.deja.demandee=Licence déjà demandée
impossible.de.supprimer.une.licence.deja.validee=Impossible de supprimer une licence déjà validée
impossible.de.supprimer.une.licence.deja.payee=Impossible de supprimer une licence déjà payée
vous.ne.pouvez.pas.creer.de.competition=Vous n'êtes pas autorisé à créer une compétition
user.not.found=Utilisateur %s introuvable
inscription.fermee=Inscription fermée
insc.err1=Vous n'êtes pas autorisé à inscrire ce membre (décision de l'administrateur de la compétition)
insc.err2=Vous n'êtes pas autorisé à vous inscrire (décision de l'administrateur de la compétition)
insc.err3=Modification bloquée par l'administrateur de la compétition
licence.non.trouve=Licence %s introuvable
nom.et.prenom.requis=Nom et prénom obligatoires
combattant.non.trouve=Combattant %s %s introuvable
le.membre.n.existe.pas=Le membre n°%d n'existe pas
competition.is.not.internal=Competition is not INTERNAL
erreur.de.format.des.contacts=Format des contacts invalide
competition.not.found=Compétition introuvable
saison.non.valid=Saison invalide
demande.d.affiliation.deja.existante=Une demande d'affiliation existe déjà
affiliation.deja.existante=Affiliation déjà existante
licence.membre.n.1.inconnue=Licence du membre n°1 inconnue
licence.membre.n.2.inconnue=Licence du membre n°2 inconnue
licence.membre.n.3.inconnue=Licence du membre n°3 inconnue
demande.d.affiliation.non.trouve=Demande d'affiliation introuvable
carton.non.trouver=Carton introuvable
card.cannot.be.added=Impossible d'ajouter le carton
configuration.non.supportee=Configuration non supportée
err.match.termine=Erreur, un match de classement a déjà été joué
podium.source.classement=Classement
podium.source.tree=Tournois
podium.source.poule=Poule

File diff suppressed because it is too large Load Diff

View File

@@ -13,36 +13,42 @@
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-brands-svg-icons": "^6.5.1",
"@fortawesome/free-regular-svg-icons": "^6.5.1",
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/react-fontawesome": "^0.2.0",
"axios": "^1.6.5",
"@fortawesome/fontawesome-svg-core": "^7.3.1",
"@fortawesome/free-brands-svg-icons": "^7.3.1",
"@fortawesome/free-regular-svg-icons": "^7.3.1",
"@fortawesome/free-solid-svg-icons": "^7.3.1",
"@fortawesome/react-fontawesome": "^3.5.0",
"axios": "1.18.0",
"browser-image-compression": "^2.0.2",
"i18next": "^26.3.6",
"i18next-browser-languagedetector": "^8.2.1",
"i18next-http-backend": "^4.0.1",
"jspdf": "4.2.1",
"jspdf-autotable": "^5.0.8",
"jszip": "^3.10.1",
"leaflet": "^1.9.4",
"obs-websocket-js": "^5.0.7",
"proj4": "^2.11.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-is": "^19.0.0",
"react-leaflet": "^4.2.1",
"react-loader-spinner": "^6.1.6",
"react-router-dom": "^6.21.2",
"react-toastify": "^10.0.4",
"recharts": "^2.15.1",
"xlsx": "^0.18.5",
"obs-websocket-js": "^5.0.8",
"proj4": "^2.21.0",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"react-i18next": "^17.0.11",
"react-is": "^19.2.8",
"react-leaflet": "^5.0.0",
"react-loader-spinner": "^8.0.2",
"react-router-dom": "^7.18.2",
"react-toastify": "^11.1.0",
"recharts": "^3.10.1",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz",
"xlsx-js-style": "^1.2.0"
},
"devDependencies": {
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.55.0",
"eslint-plugin-react": "^7.33.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"vite": "^5.0.8"
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
"@vitejs/plugin-react": "^6.0.5",
"eslint": "^9.39.5",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.3",
"vite": "8.0.16"
}
}

View File

@@ -1,11 +1,16 @@
import i18next from 'https://cdn.jsdelivr.net/npm/i18next@25.7.4/+esm';
import i18nextHttpBackend from 'https://cdn.jsdelivr.net/npm/i18next-http-backend@3.0.2/+esm'
import i18nextBrowserLanguagedetector from 'https://cdn.jsdelivr.net/npm/i18next-browser-languagedetector@8.2.0/+esm'
let apiUrlRoot = "";
const rootDiv = document.getElementById("safca_api_data");
const header = `<h4>Résultat de la compétition :</h4>`
const backButton = `<a onclick="setSubPage('home')" href="javascript:void(0);">Retour</a>`
const cupImg = `<img decoding="async" loading="lazy" width="16" height="16" class="wp-image-1635"
style="width: 16px;" src="https://intra.ffsaf.fr/img/171891.png"
alt="">`
const cupImg2 = `<img decoding="async" loading="lazy" width="16" height="16" class="wp-image-1635"
style="width: 16px;" src="https://intra.ffsaf.fr/img/171892.png"
alt="">`
const voidFunction = () => {
}
@@ -13,7 +18,7 @@ let lastRf = 0;
let rfFonction = voidFunction;
setInterval(() => {
rfFonction();
// rfFonction();
}, 15000);
function setSubPage(name) {
@@ -34,6 +39,9 @@ function setSubPage(name) {
case 'club':
clubPage(location);
break;
case 'clubRank':
clubRankPage();
break;
case 'all':
combsPage();
break;
@@ -41,18 +49,25 @@ function setSubPage(name) {
}
function homePage() {
rootDiv.innerHTML = header;
rootDiv.innerHTML = `<h4>${i18next.t('résultatDeLaCompétition')} :</h4>`;
let content = document.createElement('div');
content.innerHTML = `
<ul>
<li><a onclick="setSubPage('poule')" href="javascript:void(0);">Par poule</a></li>
<li><a onclick="setSubPage('comb')" href="javascript:void(0);">Par combattant</a></li>
<li><a onclick="setSubPage('club')" href="javascript:void(0);">Par club</a></li>
<li><a onclick="setSubPage('all')" href="javascript:void(0);">Tous les combattants</a></li>
<li><a id="pouleLink" href="javascript:void(0);">${i18next.t('parCatégorie')}</a></li>
<li><a id="combLink" href="javascript:void(0);">${i18next.t('parCombattant')}</a></li>
<li><a id="clubLink" href="javascript:void(0);">${i18next.t('parClub')}</a></li>
<li><a id="clubClassement" href="javascript:void(0);">${i18next.t('classementClub')}</a></li>
<li><a id="allLink" href="javascript:void(0);">${i18next.t('tousLesCombattants')}</a></li>
</ul>
`
rootDiv.append(content)
document.getElementById('pouleLink').addEventListener('click', () => setSubPage('poule'));
document.getElementById('combLink').addEventListener('click', () => setSubPage('comb'));
document.getElementById('clubLink').addEventListener('click', () => setSubPage('club'));
document.getElementById('clubClassement').addEventListener('click', () => setSubPage('clubRank'));
document.getElementById('allLink').addEventListener('click', () => setSubPage('all'));
}
let loadingAnimationStep = 0;
@@ -63,7 +78,7 @@ function startLoading(root) {
element.id = id;
const anim = setInterval(() => {
let str = "Chargement";
let str = i18next.t('chargement');
for (let i = 0; i < loadingAnimationStep; i++) {
str += ".";
}
@@ -80,23 +95,27 @@ function stopLoading(loading) {
loading['root'].removeChild(loading['element']);
}
function scoreToString(score) {
const scorePrint = (s1) => {
switch (s1) {
case -997:
return "disc.";
case -998:
return "abs.";
case -999:
return "for.";
case -1000:
return "";
default:
return String(s1);
}
function scorePrint(s1) {
switch (s1) {
case -997:
return i18next.t('disc.');
case -998:
return i18next.t('abs.');
case -999:
return i18next.t('for.');
case -1000:
return "";
default:
return String(s1);
}
}
return score.map(o => scorePrint(o.at(0)) + "-" + scorePrint(o.at(1))).join(" | ");
function scoreToString(score) {
if (score.length === 0)
return "";
if (score.at(0) instanceof Array)
return score.map(s => scorePrint(s.at(0)) + "-" + scorePrint(s.at(1))).join(" | ");
return score.map(o => scorePrint(o.s1) + "-" + scorePrint(o.s2)).join(" | ");
}
function dateToString(date) {
@@ -111,16 +130,16 @@ function dateToString(date) {
let d = Math.floor((current - date_2) / (1000 * 60 * 60 * 24));
if (d === 0)
return "Aujourd'hui à " + date_.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"});
return i18next.t('aujourdhuià', {time: date_.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"})});
else if (d === 1)
return "Hier à " + date_.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"});
return i18next.t('hierà', {time: date_.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"})});
else if (d === 2)
return "Avant-hier à " + date_.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"});
return i18next.t('avanthierà', {time: date_.toLocaleTimeString([], {hour: "2-digit", minute: "2-digit"})});
else
return date_.toLocaleDateString();
}
function buildPouleMenu(isPoule, change_view) {
function buildPouleMenu(isPoule, change_view, isClassement = false) {
const menuDiv = document.createElement('div');
menuDiv.id = 'menu';
menuDiv.style.borderBottom = '1px solid #9EA0A1';
@@ -175,11 +194,11 @@ function buildPouleMenu(isPoule, change_view) {
return li;
}
const li1 = createTab('Poule', isPoule, function () {
const li1 = createTab(i18next.t('poule'), isPoule, function () {
change_view(true);
});
ul.appendChild(li1);
const li2 = createTab('Tournois', !isPoule, function () {
const li2 = createTab(isClassement ? i18next.t('classement') : i18next.t('tournois'), !isPoule, function () {
change_view(false);
});
ul.appendChild(li2);
@@ -195,23 +214,23 @@ function buildMatchArray(matchs) {
<figure class="wp-block-table is-style-stripes" style="font-size: 16px">
<table style="width: 800px;overflow: auto"><thead>
<tr>
<th class="has-text-align-center" data-align="center">Rouge</th>
<th class="has-text-align-center" data-align="center">${i18next.t('rouge')}</th>
<th class="has-text-align-center" data-align="center"></th>
<th class="has-text-align-center" data-align="center">Scores</th>
<th class="has-text-align-center" data-align="center">${i18next.t('scores')}</th>
<th class="has-text-align-center" data-align="center"></th>
<th class="has-text-align-center" data-align="center">Bleu</th>
<th class="has-text-align-center" data-align="center">Date</th>
<th class="has-text-align-center" data-align="center">${i18next.t('bleu')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('date')}</th>
</tr>
</thead><tbody>`
for (const match of matchs) {
arrayContent += `
<tr>
<td class="has-text-align-right" data-align="right">${match.red}</td>
<td class="has-text-align-center" data-align="center">${match.red_w ? cupImg : ""}</td>
<td class="has-text-align-center" data-align="center">${match.red_w ? cupImg : (match.eq ? cupImg2 : "")}</td>
<td class="has-text-align-center" data-align="center">${scoreToString(match.score)}</td>
<td class="has-text-align-center" data-align="center">${match.blue_w ? cupImg : ""}</td>
<td class="has-text-align-center" data-align="center">${match.blue_w ? cupImg : (match.eq ? cupImg2 : "")}</td>
<td class="has-text-align-left" data-align="left">${match.blue}</td>
<td class="has-text-align-center" data-align="center">${dateToString((match.red_w || match.blue_w) ? match.date : null)}</td>
<td class="has-text-align-center" data-align="center">${dateToString((match.end) ? match.date : null)}</td>
</tr>`
}
arrayContent += `</tbody></table></figure>`
@@ -225,12 +244,13 @@ function buildRankArray(rankArray) {
<table style="width: 600px;overflow: auto">
<thead>
<tr>
<th class="has-text-align-center" data-align="center">Place</th>
<th class="has-text-align-center" data-align="center">Nom</th>
<th class="has-text-align-center" data-align="center">Victoire</th>
<th class="has-text-align-center" data-align="center">Ratio</th>
<th class="has-text-align-center" data-align="center">Points marqués</th>
<th class="has-text-align-center" data-align="center">Points reçus</th>
<th class="has-text-align-center" data-align="center">${i18next.t('place')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('nom')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('score')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('victoire')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('ratio')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('pointsMarqués')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('pointsReçus')}</th>
</tr>
</thead><tbody>`
for (const row of rankArray) {
@@ -238,6 +258,7 @@ function buildRankArray(rankArray) {
<tr>
<td class="has-text-align-center" data-align="center">${row.rank}</td>
<td class="has-text-align-left" data-align="left">${row.name}</td>
<td class="has-text-align-center" data-align="center">${row.score}</td>
<td class="has-text-align-center" data-align="center">${row.win}</td>
<td class="has-text-align-center" data-align="center">${row.pointRate.toFixed(3)}</td>
<td class="has-text-align-center" data-align="center">${row.pointMake}</td>
@@ -251,14 +272,45 @@ function buildRankArray(rankArray) {
}
function buildTree(treeData) {
return drawGraph(initTree(treeData))
return drawGraph(initTree(treeData.filter(d => d.data.level >= 0)))
}
function buildClassementArray(classement) {
const classement2 = classement.sort((a, b) => {
if (a.rank === b.rank)
return a.name.localeCompare(b.name);
return a.rank - b.rank;
})
const arrayDiv = document.createElement('div');
let arrayContent = `<figure class="wp-block-table is-style-stripes" style="font-size: 16px; margin-top: 2em">
<table style="width: 600px;overflow: auto">
<thead>
<tr>
<th class="has-text-align-center" data-align="center">${i18next.t('place')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('nom')}</th>
</tr>
</thead><tbody>`
for (const row of classement2) {
arrayContent += `
<tr>
<td class="has-text-align-center" data-align="center">${row.rank}</td>
<td class="has-text-align-left" data-align="left">${row.name}</td>
</tr>`
}
arrayContent += `</tbody></table></figure>`
arrayDiv.innerHTML = arrayContent;
return arrayDiv;
}
function poulePage(location) {
rootDiv.innerHTML = header + backButton;
rootDiv.innerHTML = `<h4>${i18next.t('résultatDeLaCompétition')} :</h4><a id="homeLink" href="javascript:void(0);">${i18next.t('back')}</a>`;
document.getElementById('homeLink').addEventListener('click', () => setSubPage('home'));
const content = document.createElement('div');
content.style.marginTop = '1em';
content.innerHTML = '<h4>Recherche par poule</h4>';
content.innerHTML = `<h4>${i18next.t('rechercheParCatégorie')}</h4>`;
const dataContainer = document.createElement('div');
dataContainer.id = 'data-container';
@@ -286,7 +338,7 @@ function poulePage(location) {
for (const g in poule.matchs) {
if (Object.keys(poule.matchs).length > 1) {
const text = document.createElement('h4');
text.textContent = `Groupe ${g}`;
text.textContent = `${i18next.t('poule')} ${g}`;
text.style.marginTop = '2em';
dataContainer.append(text);
}
@@ -297,13 +349,13 @@ function poulePage(location) {
dataContainer.append(buildTree(poule['trees']));
} else {
const change_view = (isPoule) => {
dataContainer.replaceChildren(buildPouleMenu(isPoule, change_view));
dataContainer.replaceChildren(buildPouleMenu(isPoule, change_view, poule['treeIsClassement']));
if (isPoule) {
for (const g in poule.matchs) {
if (Object.keys(poule.matchs).length > 1) {
const text = document.createElement('h4');
text.textContent = `Groupe ${g}`;
text.textContent = `${i18next.t('poule')} ${g}`;
text.style.marginTop = '2em';
dataContainer.append(text);
}
@@ -312,6 +364,22 @@ function poulePage(location) {
}
} else {
dataContainer.append(buildTree(poule['trees']));
if (poule['treeIsClassement'] && poule['trees'].some(d => d.data.level <= -10)) {
dataContainer.append(buildMatchArray(
poule['trees'].filter(d => d.data.level < 0).reverse().map(d => ({
red: d.data.c1FullName,
blue: d.data.c2FullName,
score: d.data.scores,
end: d.data.end,
red_w: d.data.win > 0,
blue_w: d.data.win < 0,
eq: d.data.win === 0,
date: d.data.date,
}))));
}
if (poule['treeIsClassement']){
dataContainer.append(buildClassementArray(poule['classement']));
}
}
location[2] = isPoule ? 1 : 2;
@@ -323,7 +391,7 @@ function poulePage(location) {
})
.catch(e => {
console.error(e);
dataContainer.replaceChildren(new Text("Erreur de chargement de la poule"));
dataContainer.replaceChildren(new Text(i18next.t('erreurDeChargementDeLaPoule')));
})
.finally(() => stopLoading(loading));
}
@@ -334,8 +402,8 @@ function poulePage(location) {
.then(poule => {
const select = document.createElement('select');
select.setAttribute('id', poule.id);
select.innerHTML = `<option value="0">--Sélectionner une poule--</option>`;
for (const pouleKey of Object.keys(poule).sort()) {
select.innerHTML = `<option value="0">${i18next.t('--sélectionnerUneCatégorie--')}</option>`;
for (const pouleKey of Object.keys(poule).sort((a, b) => a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase()))) {
select.innerHTML += `<option value="${poule[pouleKey]}">${pouleKey}</option>`;
}
select.addEventListener('change', e => {
@@ -356,7 +424,7 @@ function poulePage(location) {
loadPoule();
}
})
.catch(() => rootDiv.append(new Text("Erreur de chargement des poules")))
.catch(() => rootDiv.append(new Text(i18next.t('erreurDeChargementDesCatégories'))))
.finally(() => stopLoading(loading));
rfFonction = () => {
@@ -372,28 +440,33 @@ function buildCombView(comb) {
let arrayContent = `
<h3>Info :</h3>
<ul>
<li>Nom Pr&eacute;nom : ${comb.name}</li>
<li>Club : ${comb.club}</li>
<li>Cat&eacute;gorie : ${comb.cat}</li>
<li>${i18next.t('nomPrénom')} : ${comb.name}</li>
<li>${i18next.t('club')} : ${comb.club}</li>
<li>${i18next.t('catégorie')} : ${comb.cat}</li>
</ul>
<h3>Statistique :</h3>
<h3>${i18next.t('statistique')} :</h3>
<ul>
<li>Taux de victoire : ${comb.matchs.length === 0 ? "---" : (comb.totalWin / comb.matchs.length * 100).toFixed(0)}% (${comb.totalWin} sur ${comb.matchs.length})</li>
<li>Points marqués : ${comb.pointMake}</li>
<li>Points reçus : ${comb.pointTake}</li>
<li>Ratio du score (point marqu&eacute; / point re&ccedil;u): ${comb.pointRatio.toFixed(3)}</li>
<li>${i18next.t('tauxDeVictoire2', {
nb: comb.matchs.length === 0 ? "---" : (comb.totalWin / comb.matchs.filter(m => m.end).length * 100).toFixed(0),
victoires: comb.totalWin,
matchs: comb.matchs.filter(m => m.end).length
})}
</li>
<li>${i18next.t('pointsMarqués2', {nb: comb.pointMake})}</li>
<li>${i18next.t('pointsReçus2', {nb: comb.pointTake})}</li>
<li>${i18next.t('ratioDuScore2', {nb: comb.pointRatio.toFixed(3)})}</li>
</ul>
<h3>Liste des matchs:</h3>
<h3>${i18next.t('listeDesMatchs')}:</h3>
<figure class="wp-block-table is-style-stripes" style="font-size: 16px">
<table style="width: 700px;overflow: auto"><thead>
<tr>
<th class="has-text-align-center" data-align="center">Date</th>
<th class="has-text-align-center" data-align="center">Poule</th>
<th class="has-text-align-center" data-align="center">Adversaire</th>
<th class="has-text-align-center" data-align="center">Scores</th>
<th class="has-text-align-center" data-align="center">Ratio</th>
<th class="has-text-align-center" data-align="center">${i18next.t('date')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('poule')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('adversaire')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('scores')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('ratio')}</th>
<th class="has-text-align-center" data-align="center"></th>
</tr>
</thead><tbody>`
@@ -404,8 +477,8 @@ function buildCombView(comb) {
<td class="has-text-align-center" data-align="center">${match.poule}</td>
<td class="has-text-align-center" data-align="center">${match.adv}</td>
<td class="has-text-align-center" data-align="center">${scoreToString(match.score)}</td>
<td class="has-text-align-center" data-align="center">${match.ratio.toFixed(3)}</td>
<td class="has-text-align-center" data-align="center">${match.win ? cupImg : ""}</td>
<td class="has-text-align-center" data-align="center">${match.end ? match.ratio.toFixed(3) : ""}</td>
<td class="has-text-align-center" data-align="center">${match.win ? cupImg : (match.eq ? cupImg2 : "")}</td>
</tr>`
}
arrayContent += `</tbody></table></figure>`
@@ -414,10 +487,12 @@ function buildCombView(comb) {
}
function combPage(location) {
rootDiv.innerHTML = header + backButton;
rootDiv.innerHTML = `<h4>${i18next.t('résultatDeLaCompétition')} :</h4><a id="homeLink" href="javascript:void(0);">${i18next.t('back')}</a>`;
document.getElementById('homeLink').addEventListener('click', () => setSubPage('home'));
const content = document.createElement('div');
content.style.marginTop = '1em';
content.innerHTML = '<h4>Recherche par combattant</h4>';
content.innerHTML = `<h4>${i18next.t('rechercheParCombattant')}</h4>`;
const dataContainer = document.createElement('div');
dataContainer.id = 'data-container';
@@ -430,7 +505,7 @@ function combPage(location) {
console.log(comb);
dataContainer.replaceChildren(buildCombView(comb));
})
.catch(() => dataContainer.replaceChildren(new Text("Erreur de chargement du combattant")))
.catch(() => dataContainer.replaceChildren(new Text(i18next.t('erreurDeChargementDuCombattant'))))
.finally(() => stopLoading(loading));
}
@@ -439,7 +514,7 @@ function combPage(location) {
.then(response => response.json())
.then(combs => {
const select = document.createElement('select');
select.innerHTML = `<option value="0">--Sélectionner un combattant--</option>`;
select.innerHTML = `<option value="0">${i18next.t('--sélectionnerUnCombattant--')}</option>`;
for (const comb of Object.keys(combs).sort()) {
select.innerHTML += `<option value="${combs[comb]}">${comb}</option>`;
}
@@ -458,7 +533,7 @@ function combPage(location) {
loadComb(tmp);
}
})
.catch(() => rootDiv.append(new Text("Erreur de chargement des combattants")))
.catch(() => rootDiv.append(new Text(i18next.t('erreurDeChargementDesCombattants'))))
.finally(() => stopLoading(loading));
rootDiv.append(content)
@@ -467,34 +542,34 @@ function combPage(location) {
function buildClubView(club) {
const pouleDiv = document.createElement('div');
let arrayContent = `
<h3>Info :</h3>
<h3>${i18next.t('info')} :</h3>
<ul>
<li>Nom : ${club.name}</li>
<li>Nombre d'inscris : ${club.nb_insc}</li>
<li>${i18next.t('nom')} : ${club.name}</li>
<li>${i18next.t('nombreDinscris')} : ${club.nb_insc}</li>
</ul>
<h3>Statistique :</h3>
<h3>${i18next.t('statistique')} :</h3>
<ul>
<li>Nombre de match disput&eacute; : ${club.nb_match}</li>
<li>Nombre de victoires : ${club.match_w}</li>
<li>Ratio de victoires moyen : ${club.ratioVictoire.toFixed(3)}</li>
<li>Points marqués : ${club.pointMake}</li>
<li>Points reçus : ${club.pointTake}</li>
<li>Ratio de points moyen : ${club.ratioPoint.toFixed(3)}</li>
<li>${i18next.t('nombreDeMatchDisputé2', {nb: club.nb_match})}</li>
<li>${i18next.t('nombreDeVictoires2', {nb: club.match_w})}</li>
<li>${i18next.t('ratioDeVictoiresMoyen2', {nb: club.ratioVictoire.toFixed(3)})}</li>
<li>${i18next.t('pointsMarqués2', {nb: club.pointMake})}</li>
<li>${i18next.t('pointsReçus2', {nb: club.pointTake})}</li>
<li>${i18next.t('ratioDePointsMoyen2', {nb: club.ratioPoint.toFixed(3)})}</li>
</ul>
<h3>Liste des menbres :</h3>
<h3>${i18next.t('listeDesMenbres')} :</h3>
<figure class="wp-block-table is-style-stripes" style="font-size: 16px">
<table style="width: 800px;overflow: auto"><thead>
<tr>
<th class="has-text-align-center" data-align="center">Cat&eacute;gorie</th>
<th class="has-text-align-center" data-align="center">Nom</th>
<th class="has-text-align-center" data-align="center">Victoires</th>
<th class="has-text-align-center" data-align="center">D&eacute;faites</th>
<th class="has-text-align-center" data-align="center">Ratio victoires</th>
<th class="has-text-align-center" data-align="center">Points marqués</th>
<th class="has-text-align-center" data-align="center">Points reçus</th>
<th class="has-text-align-center" data-align="center">Ratio points</th>
<th class="has-text-align-center" data-align="center">${i18next.t('catégorie')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('nom')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('victoires')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('défaites')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('ratioVictoires')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('pointsMarqués')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('pointsReçus')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('ratioPoints')}</th>
</tr>
</thead><tbody>`
for (const comb of club.combs) {
@@ -516,10 +591,12 @@ function buildClubView(club) {
}
function clubPage(location) {
rootDiv.innerHTML = header + backButton;
rootDiv.innerHTML = `<h4>${i18next.t('résultatDeLaCompétition')} :</h4><a id="homeLink" href="javascript:void(0);">${i18next.t('back')}</a>`;
document.getElementById('homeLink').addEventListener('click', () => setSubPage('home'));
const content = document.createElement('div');
content.style.marginTop = '1em';
content.innerHTML = '<h4>Recherche par club</h4>';
content.innerHTML = `<h4>${i18next.t('rechercheParClub')}</h4>`;
const dataContainer = document.createElement('div');
dataContainer.id = 'data-container';
@@ -532,7 +609,7 @@ function clubPage(location) {
console.log(club);
dataContainer.replaceChildren(buildClubView(club));
})
.catch(() => dataContainer.replaceChildren(new Text("Erreur de chargement du club")))
.catch(() => dataContainer.replaceChildren(new Text(i18next.t('erreurDeChargementDuClub'))))
.finally(() => stopLoading(loading));
}
@@ -541,7 +618,7 @@ function clubPage(location) {
.then(response => response.json())
.then(clubs => {
const select = document.createElement('select');
select.innerHTML = `<option value="0">--Sélectionner un club--</option>`;
select.innerHTML = `<option value="0">${i18next.t('--sélectionnerUnClub--')}</option>`;
for (const club of Object.keys(clubs).sort()) {
select.innerHTML += `<option value="${clubs[club]}">${club}</option>`;
}
@@ -562,36 +639,90 @@ function clubPage(location) {
loadComb(tmp);
}
})
.catch(() => rootDiv.append(new Text("Erreur de chargement des clubs")))
.catch(() => rootDiv.append(new Text(i18next.t('erreurDeChargementDesClubs'))))
.finally(() => stopLoading(loading));
rootDiv.append(content)
}
function buildClubsView(clubs) {
const pouleDiv = document.createElement('div');
let arrayContent = `
<h3>${i18next.t('classementDesClub')} :</h3>
<figure class="wp-block-table is-style-stripes" style="font-size: 16px">
<table style="width: 1200px;overflow: auto"><thead>
<tr>
<th class="has-text-align-center" data-align="center">${i18next.t('club')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('1er')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('2eme')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('3eme')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('scores')}</th>
</tr>
</thead><tbody>`
for (const club of clubs) {
arrayContent += `
<tr>
<td class="has-text-align-center" data-align="center">${club.name}</td>
<td class="has-text-align-center" data-align="center">${club.score[0]}</td>
<td class="has-text-align-center" data-align="center">${club.score[1]}</td>
<td class="has-text-align-center" data-align="center">${club.score[2]}</td>
<td class="has-text-align-center" data-align="center">${club.tt_score}</td>
</tr>`
}
arrayContent += `</tbody></table></figure>`
pouleDiv.innerHTML = arrayContent;
return pouleDiv;
}
function clubRankPage() {
rootDiv.innerHTML = `<h4>${i18next.t('résultatDeLaCompétition')} :</h4><a id="homeLink" href="javascript:void(0);">${i18next.t('back')}</a>`;
document.getElementById('homeLink').addEventListener('click', () => setSubPage('home'));
const content = document.createElement('div');
content.style.marginTop = '1em';
const dataContainer = document.createElement('div');
dataContainer.id = 'data-container';
const loading = startLoading(content);
fetch(`${apiUrlRoot}/club/classement`)
.then(response => response.json())
.then(clubs => {
console.log(clubs);
dataContainer.replaceChildren(buildClubsView(clubs));
})
.catch(() => dataContainer.replaceChildren(new Text(i18next.t('erreurDeChargementDeLaListe'))))
.finally(() => stopLoading(loading));
content.append(dataContainer);
rootDiv.append(content)
}
function buildCombsView(combs) {
const pouleDiv = document.createElement('div');
let arrayContent = `
<h3>Statistique :</h3>
<h3>${i18next.t('statistique')} :</h3>
<ul>
<li>Nombre d'inscris : ${combs.nb_insc}</li>
<li>Nombre de match disput&eacute; : ${combs.tt_match}</li>
<li>Points marqués : ${combs.point}</li>
<li>${i18next.t('nombreDinscris2', {nb: combs.nb_insc})}</li>
<li>${i18next.t('nombreDeMatchDisputé2', {nb: combs.tt_match})}</li>
<li>${i18next.t('pointsMarqués2', {nb: combs.point})}</li>
</ul>
<h3>Liste des combattants :</h3>
<h3>${i18next.t('listeDesCombattants')} :</h3>
<figure class="wp-block-table is-style-stripes" style="font-size: 16px">
<table style="width: 1200px;overflow: auto"><thead>
<tr>
<th class="has-text-align-center" data-align="center">Cat&eacute;gorie</th>
<th class="has-text-align-center" data-align="center">Club</th>
<th class="has-text-align-center" data-align="center">Nom</th>
<th class="has-text-align-center" data-align="center">Victoires</th>
<th class="has-text-align-center" data-align="center">D&eacute;faites</th>
<th class="has-text-align-center" data-align="center">Ratio victoires</th>
<th class="has-text-align-center" data-align="center">Points marqués</th>
<th class="has-text-align-center" data-align="center">Points reçus</th>
<th class="has-text-align-center" data-align="center">Ratios points</th>
<th class="has-text-align-center" data-align="center">${i18next.t('catégorie')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('club')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('nom')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('victoires')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('défaites')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('ratioVictoires')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('pointsMarqués')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('pointsReçus')}</th>
<th class="has-text-align-center" data-align="center">${i18next.t('ratiosPoints')}</th>
</tr>
</thead><tbody>`
for (const comb of combs.combs) {
@@ -614,7 +745,9 @@ function buildCombsView(combs) {
}
function combsPage() {
rootDiv.innerHTML = header + backButton;
rootDiv.innerHTML = `<h4>${i18next.t('résultatDeLaCompétition')} :</h4><a id="homeLink" href="javascript:void(0);">${i18next.t('back')}</a>`;
document.getElementById('homeLink').addEventListener('click', () => setSubPage('home'));
const content = document.createElement('div');
content.style.marginTop = '1em';
@@ -628,26 +761,50 @@ function combsPage() {
console.log(combs);
dataContainer.replaceChildren(buildCombsView(combs));
})
.catch(() => dataContainer.replaceChildren(new Text("Erreur de chargement de la liste")))
.catch(() => dataContainer.replaceChildren(new Text(i18next.t('erreurDeChargementDeLaListe'))))
.finally(() => stopLoading(loading));
content.append(dataContainer);
rootDiv.append(content)
}
window.addEventListener("load", () => {
let path = document.getElementById('safca_api_script').src;
const urlParams = new URLSearchParams(new URL(path).search);
apiUrlRoot = path.substring(0, path.lastIndexOf('/')) + "/api/public/result/" + urlParams.get("id");
export async function initCompetitionApi(apiUrlRoot_, host) {
apiUrlRoot = apiUrlRoot_;
const options = {
order: ['querystring', 'cookie', 'localStorage', 'sessionStorage', 'navigator', 'htmlTag'],
caches: [],
}
const backend = {
loadPath: `${host}/locales/{{lng}}/{{ns}}.json`,
}
await i18next
.use(i18nextHttpBackend)
.use(i18nextBrowserLanguagedetector)
.init({
supportedLngs: ['fr', 'en'],
fallbackLng: 'fr',
debug: host.startsWith('http://localhost'),
interpolation: {
escapeValue: true,
},
detection: options,
backend: backend,
ns: ['result'],
defaultNS: 'result',
})
console.log("apiUrlRoot:", apiUrlRoot)
console.log("FFSAF Competition API initialized.")
let hash = window.location.hash.substring(1);
if (hash.length === 0)
setSubPage('home');
else
setSubPage(hash);
});
}
class TreeNode {
constructor(data) {
@@ -671,7 +828,7 @@ class TreeNode {
}
function initTree(data_in) {
out = [];
let out = [];
for (const din of data_in) {
out.push(parseTree(din));
}
@@ -818,7 +975,7 @@ function drawGraph(root = []) {
ctx.textBaseline = 'top';
for (let i = 0; i < scores.length; i++) {
const score = scores[i].s1+"-"+scores[i].s2;
const score = scorePrint(scores[i].s1) + "-" + scorePrint(scores[i].s2);
const div = (scores.length <= 2) ? 2 : (scores.length >= 4) ? 4 : 3;
const text = ctx.measureText(score);
let dx = (size * 2 - text.width) / 2;
@@ -896,20 +1053,6 @@ function drawGraph(root = []) {
if (tree.right != null) drawNode(tree.right, px - size * 2 - size * 8, py + size * 2 * death);
}
function win(scores) {
let sum = 0;
for (const score of scores) {
if (score.s1 === -1000 || score.s2 === -1000)
continue;
if (score.s1 > score.s2)
sum++;
else if (score.s1 < score.s2)
sum--;
}
return sum;
}
let px = max_x;
let py;
let max_y
@@ -922,7 +1065,7 @@ function drawGraph(root = []) {
for (const node of root) {
let win_name = "";
if (node.data.end) {
if (win(node.data.scores) > 0)
if (node.data.win > 0)
win_name = (node.data.c1FullName === null) ? "???" : node.data.c1FullName;
else
win_name = (node.data.c2FullName === null) ? "???" : node.data.c2FullName;

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,213 @@
{
"--SélectionnerUnCombattant--": "-- Select a fighter --",
"--Tous--": "-- All --",
"PourLéquipe": "for the team",
"actuel": "Current",
"administration": "Administration",
"adresseDuServeur": "Server address",
"afficher": "Show",
"ajoutAutomatique": "Automatic addition",
"ajouter": "Add",
"ajouterDesCombattants": "Add fighters",
"ajouterUn": "Add one",
"ajouterUneTeam": "Add team",
"attention": "Warning",
"aucuneConfigurationObs": "No OBS configuration found, please import one",
"avertissement": "Warning",
"bleu": "Blue",
"blue": "Blue",
"cardAdded": "Card added",
"cardRemoved": "Card removed",
"carton": "Card",
"cartonDéquipe": "Team's card",
"cartonJaune": "Yellow card",
"cartonNoir": "Black card",
"cartonRouge": "Red card",
"catégorie": "Category",
"catégorieDâgeMoyenne": "Middle-aged category",
"catégorieSélectionnée": "Selected category",
"catégoriesVontêtreCréées": "weight categories will be created",
"ceCartonEstIssuDunCartonDéquipe": "This card comes from a team card, do you really want to delete it?",
"certainsCombattantsNontPasDePoidsRenseigné": "Some fighters do not have a weight listed; they will NOT be included in the categories.",
"chrono.+/-...S": "+/- ... s",
"chrono.+10S": "+10 s",
"chrono.+1S": "+1 s",
"chrono.-10S": "-10 s",
"chrono.-1S": "-1 s",
"chrono.arrêter": "Stop",
"chrono.définirLeTemps": "Set time",
"chrono.démarrer": "Start",
"chrono.editionTemps": "Time editing",
"chrono.entrezLeTempsEnS": "Enter time in seconds",
"chrono.recapTemps": "Time: {{temps}}, pause: {{pause}}",
"chronomètre": "Stopwatch",
"classement": "Ranking",
"club": "Club",
"combattantsCorrespondentAuxSélectionnés": "fighter(s) match the selections above",
"compétition": "Competition",
"compétitionManager": "Competition manager",
"config.obs.dossierDesResources": "Resources folder",
"config.obs.motDePasseDuServeur": "Server password",
"config.obs.warn1": "/! The password will be stored in plain text; it is recommended to use it only on OBS WebSocket and to change it between each competition",
"config.obs.ws": "ws://",
"configurationDuNomDeLaZone": "Zone name configuration",
"configurationObs": "OBS Configuration",
"confirm1": "This match already has results; are you sure you want to delete it?",
"confirm2.msg": "Do you really want to change the tournament tree size or the loser matches? This will modify existing matches (including possible deletions)!",
"confirm2.title": "Tournament tree change",
"confirm3.msg": "Do you really want to remove the {{typeStr}} part from the category? This will delete the matches contained in this part!",
"confirm3.title": "Change category type",
"confirm4.msg": "Do you really want to delete the category {{name}}. This will delete all associated matches!",
"confirm4.title": "Delete category",
"confirmer": "Confirm",
"conserverUniquementLesMatchsTerminés": "Keep only finished matches",
"contre": "vs",
"couleur": "Color",
"créationDeLaLesCatégories": "Creating the category(ies)",
"créerLaPhaseFinaleSilYADesPoules": "Create the final phase if there are groups.",
"créerLesMatchesDeClassement": "Create the ranking matches",
"créerLesMatchesDeClassement.msg": "Ranking matches have already been set up/played; recreating these matches will delete them all (you will therefore lose any results). Please note down any information you wish to keep.",
"créerLesMatchs": "Create matches",
"créerToutesLesCatégories": "Create all categories",
"date": "Date",
"demi-finalesEtFinales": "Semi-finals and finals",
"depuisUneCatégoriePrédéfinie": "From a predefined category",
"duréePause": "Pause duration",
"duréeRound": "Round duration",
"editionDeLaCatégorie": "Edit category",
"editionDuMatch": "Match edition",
"enregister": "Save",
"enregistrer": "Save",
"epéeBouclier": "Sword and shield",
"err1": "A fighter cannot fight themselves!",
"err2": "The combat zone name format is invalid. Please separate names with ';'.",
"err3": "At least one type (pool or tournament) must be selected.",
"erreurLorsDeLaCopieDansLePresse": "Error while copying to clipboard: ",
"erreurLorsDeLaCréationDesMatchs": "Error while creating matches: ",
"etatDesTablesDeMarque": "State of marque tables",
"exporter": "Export",
"fermer": "Close",
"feuilleVierge": "Blank sheet",
"finalesUniquement": "Finals only",
"genre": "Gender",
"genre.f": "F",
"genre.h": "M",
"genre.na": "NA",
"imprimer": "Print",
"individuelle": "Individual",
"informationCatégorie": "Category information",
"inscrit": "Registered",
"jusquauRang": "Up to the rank",
"leTournoiServiraDePhaseFinaleAuxPoules": "The tournament will serve as the final phase for the group stage.",
"lesCombattantsEnDehors": "Fighters not participating in the tournament will have a ranking match.",
"lesCombattantsEnDehors2": "Fighters outside the ranking tournament will have a ranking match",
"listeDesCartons": "List of cards",
"manche": "Round",
"matchPourLesPerdantsDuTournoi": "Match for tournament losers:",
"matchTerminé": "Match over",
"matches": "Matches",
"modeDeCréation": "Creation method",
"modifier": "Edit",
"msg1": "There are already matches in this pool; what do you want to do with them?",
"neRienConserver": "Keep nothing",
"no": "No.",
"nom": "Name",
"nomDeLaZone": "Area name",
"nomDeLéquipe": "team name",
"nomDesZonesDeCombat": "Combat zone names <1>(separated by ';')</1>",
"nombreDeCombattants": "Number of fighters",
"nouvelle...": "New...",
"obs.préfixDesSources": "Source prefix",
"pays": "Country",
"personnaliser": "Personalize",
"podium": "Podium",
"podiumDesClubs": "Club podium",
"poids": "Weight",
"poule": "Pool",
"poulePour": "Pool for: ",
"préparation...": "Preparing...",
"quoiImprimer?": "What print?",
"remplacer": "Replace",
"rouge": "Red",
"réinitialiser": "Reset",
"résultat": "Result",
"sansPoule": "No pool",
"sauvegarde": "Backup",
"sauvegarder": "Save",
"score": "Score",
"score.err1": "Cannot end a tournament match in a draw.",
"score.spe": "Special scores: <br/>-997: disqualified <br/>-998: absent <br/>-999: forfeit",
"scores": "Scores",
"secrétariatsDeLice": "Ring secretariats",
"select.aucunCombattantDisponible": "No fighters available",
"select.aucunCombattantSélectionné": "No fighters selected",
"select.msg1": "(0 = disabled)",
"select.recherche": "Search",
"select.sélectionnerDesCombatants": "Select fighters",
"select.à": "to",
"serveur": "Server",
"source": "Source",
"suivant": "Next",
"supprimer": "Delete",
"supprimerUn": "Delete one",
"sélectionneLesModesDaffichage": "Select display modes",
"sélectionner": "Select",
"taille": "Size",
"team": "Team",
"terminé": "Finished",
"texteCopiéDansLePresse": "Text copied to clipboard! Paste it into an HTML tag on your WordPress.",
"toast.card.team.error": "Error while editing team card",
"toast.card.team.pending": "Editing team card...",
"toast.card.team.success": "Team card edited!",
"toast.createCategory.error": "Error while creating the category",
"toast.createCategory.pending": "Creating category...",
"toast.createCategory.success": "Category created!",
"toast.deleteCategory.error": "Error while deleting the category",
"toast.deleteCategory.pending": "Deleting category...",
"toast.deleteCategory.success": "Category deleted!",
"toast.matchs.classement.create.error": "Error while creating ranking matches.",
"toast.matchs.classement.create.pending": "Creating ranking matches in progress...",
"toast.matchs.classement.create.success": "Ranking matches created successfully.",
"toast.matchs.create.error": "Error while creating matches.",
"toast.matchs.create.pending": "Creating matches in progress...",
"toast.matchs.create.success": "Matches created successfully.",
"toast.print.error": "Error while preparing print",
"toast.print.pending": "Preparing print...",
"toast.print.success": "Print ready!",
"toast.team.update.error": "Error while updating team",
"toast.team.update.pending": "Updating team...",
"toast.team.update.success": "Team updated!",
"toast.updateCategory.error": "Error while updating the category",
"toast.updateCategory.pending": "Updating category...",
"toast.updateCategory.success": "Category updated!",
"toast.updateMatchScore.error": "Error while updating match score",
"toast.updateMatchScore.pending": "Updating match score...",
"toast.updateMatchScore.success": "Match score updated!",
"toast.updateTrees.error": "Error while updating trees",
"toast.updateTrees.init.error": "Error while creating trees",
"toast.updateTrees.init.pending": "Creating tournament trees...",
"toast.updateTrees.init.success": "Trees created!",
"toast.updateTrees.pending": "Updating tournament trees...",
"toast.updateTrees.success": "Trees updated!",
"tournoi": "Tournament",
"tournois": "Tournaments",
"tousLesMatchs": "All matches",
"toutConserver": "Keep all",
"touteLaCatégorie": "The entire category",
"toutesLesCatégories": "All categories",
"ttm.admin.obs": "Short click: Download resources. Long click: Create OBS configuration",
"ttm.admin.scripte": "Copy integration script",
"ttm.table.inverserLaPosition": "Reverse fighter positions on this screen",
"ttm.table.obs": "Short click: Load configuration and connect.",
"ttm.table.pub_aff": "Open public display",
"ttm.table.pub_score": "Show scores on public display",
"type": "Type",
"téléchargement": "Download",
"téléchargementEnCours": "Downloading...",
"téléchargementTerminé!": "Download completed!",
"uneCatégorie": "a category",
"uneCatégorieNePeutContenirPlusDe10Combattants": "A category cannot contain more than 10 fighters, please create weight categories.",
"valider": "Validate",
"zone": "Zone",
"zoneDeCombat": "Combat zone"
}

View File

@@ -0,0 +1,664 @@
{
"(optionnelle)": "(optional)",
"---SansClub---": "--- no club ---",
"---TousLesAges---": "--- all ages ---",
"---ToutLesClubs---": "--- all clubs ---",
"---ToutLesPays---": "--- all countries ---",
"---TouteLesCatégories---": "--- all categories ---",
"--NonLicencier--": "-- Not licensed --",
"--SélectionnerCatégorie--": "-- Select category --",
"1Catégorie": "+1 category",
"2Catégorie": "+2 categories",
"LesModificationsNontEnregistrer": "/!\\ The changes have not yet been saved, click save /!\\",
"activer": "Activate",
"admin": "Administration",
"administrateur": "Administrator",
"adresse": "Address",
"adresseAdministrative": "Administrative address",
"aff.ancienNom": "Former name: {{name}}",
"aff.byMembreSim": "By similar member",
"aff.byNewMenbre": "By new member",
"aff.byNoLicence": "By license number",
"aff.info1": "This club has already been affiliated (affiliation no. {{no}})",
"aff.membreNo": "Member no. {{no}}",
"aff.nomDuClub": "Club name",
"aff.raisonDuRefus": "Reason for refusal",
"aff.raisonDuRefus.msg": "Please indicate the reason for refusal",
"aff.refusConfirm": "Are you sure you want to refuse this request?",
"aff.refuserLaDemande": "Refuse the request",
"aff.refuserLaDemande.detail": "Are you sure you want to refuse this request?",
"aff.submit.error1": "Please enter a valid license number for member {{id}}",
"aff.submit.error2": "Please enter a valid license number for member {{id}}",
"aff.submit.error3": "Please enter a valid email for member {{id}}",
"aff.toast.accept.error": "Failed to accept affiliation",
"aff.toast.accept.pending": "Accepting affiliation in progress",
"aff.toast.accept.success": "Affiliation accepted successfully 🎉",
"aff.toast.del.error": "Failed to delete affiliation request",
"aff.toast.del.pending": "Deleting affiliation request in progress",
"aff.toast.del.success": "Affiliation request deleted successfully 🎉",
"aff.toast.del2.error": "Failed to delete affiliation",
"aff.toast.del2.pending": "Deleting affiliation in progress",
"aff.toast.del2.success": "Affiliation deleted successfully 🎉",
"aff.toast.save.error": "Failed to save affiliation request",
"aff.toast.save.pending": "Saving affiliation request in progress",
"aff.toast.save.success": "Affiliation request saved successfully 🎉",
"aff.toast.save2.error": "Failed to save affiliation",
"aff.toast.save2.pending": "Saving affiliation in progress",
"aff.toast.save2.success": "Affiliation saved successfully 🎉",
"aff_req.appuyerSurRechercher": "Press search to complete",
"aff_req.association": "The association",
"aff_req.button.cancel": "Cancel my request",
"aff_req.button.confirm": "Confirm my affiliation request",
"aff_req.button.save": "Save changes",
"aff_req.denomination": "Denomination",
"aff_req.disposeLicence": "Already has a license",
"aff_req.error1": "The role of member {{i}} is required",
"aff_req.error2": "The SIRET/RNA format is invalid",
"aff_req.nomDeLassociation": "Association name",
"aff_req.text1": "Affiliation is annual and valid for one sports season: from September 1st to August 31st of the following year.",
"aff_req.text2": "To affiliate, a sports association must meet the following conditions:",
"aff_req.text2.li": [
"Have its headquarters in France or the Principality of Monaco",
"Be constituted in accordance with Chapter 1 of Title II of Book 1 of the Sports Code",
"Pursue a purpose that falls within the definition of Article 1 of the Federation's statutes",
"Have statutes compatible with the principles of organization and operation of the Federation",
"Ensure freedom of opinion and respect for the rights of defense within it, and prohibit any discrimination",
"Comply with the rules of supervision, hygiene, and safety established by the Federation's regulations"
],
"aff_req.text3": "After validation of your request, you will receive a temporary identifier and password to access your FFSAF space",
"aff_req.text4": "Note that to finalize your affiliation, you will need to:",
"aff_req.text4.li1": "Have at least three licensed members, including the president",
"aff_req.text4.li2": "Have paid the fees provided for by the federal regulations",
"aff_req.text5": "You can later add publicly visible addresses for your training locations",
"aff_req.text6": "Leave blank to make no changes. (If a coat of arms has already been sent with this request, it will be used; otherwise, we will use the one from the previous affiliation)",
"aff_req.text7": "Affiliation request sent successfully",
"aff_req.text8": "Once your request is validated, you will receive a temporary identifier and password to access your FFSAF space",
"aff_req.toast.undo.error": "Failed to cancel affiliation request",
"aff_req.toast.undo.pending": "Cancelling affiliation request in progress",
"aff_req.toast.undo.success": "Affiliation request cancelled successfully 🎉",
"afficherLesCombattantsNonPesés": "Show unweighed fighters",
"afficherLétatDesAffiliation": "Display affiliation status",
"affiliation": "Affiliation",
"affiliationNo": "Affiliation no. {{no}}",
"ajout": "Addition",
"ajouterUnClub": "Add a club",
"ajouterUnMembre": "Add a member",
"all_season": "--- all seasons ---",
"ans": "years",
"arme": "Weapon",
"au": "to",
"aucun": "None",
"aucunMembreSélectionné": "No member selected",
"aucuneCatégorieDisponible": "No categories available at this time.",
"back": "« back",
"blason": "Coat of arms",
"bouclier": "Shield",
"bureau": "Board",
"button.accepter": "Accept",
"button.ajouter": "Add",
"button.annuler": "Cancel",
"button.appliquer": "Apply",
"button.confirmer": "Confirm",
"button.créer": "Create",
"button.enregistrer": "Save",
"button.fermer": "Close",
"button.modifier": "Edit",
"button.refuser": "Refuse",
"button.seDésinscrire": "Unsubscribe",
"button.suivant": "Next",
"button.supprimer": "Delete",
"casque": "Helmet",
"cat.benjamin": "Benjamin",
"cat.cadet": "Cadet",
"cat.catégorieInconnue": "Unknown category",
"cat.junior": "Junior",
"cat.miniPoussin": "Mini Chick",
"cat.minime": "Minime",
"cat.poussin": "Chick",
"cat.senior1": "Senior 1",
"cat.senior2": "Senior 2",
"cat.superMini": "Super Mini",
"cat.vétéran1": "Veteran 1",
"cat.vétéran2": "Veteran 2",
"categorie": "category",
"catégorie": "Category",
"catégorieàAjouter": "Category to add",
"certificatMédical": "Medical certificate",
"champAttendu": "Expected field",
"chargement...": "Loading...",
"chargerLexcel": "Load Excel",
"chargerLexcel.msg": "Please use the file above as a template; do not rename the columns or modify the license numbers.",
"choisir...": "Choose...",
"club.aff_renew.msg": "Please select 0 to 3 board members to fill out the pre-request. (If a non-board member will become one next year, you can enter them in the next step)",
"club.change.status": "To modify the above information, please contact the FFSAF by email.",
"club.contact.tt": {
"AUTRE": "Other club contact",
"COURRIEL": "Club email address<br>Example: contact@ffsaf.fr",
"FACEBOOK": "Club Facebook page starting with 'https://www.facebook.com/'<br>Example: https://www.facebook.com/ffmsf",
"INSTAGRAM": "Club Instagram account starting with 'https://www.instagram.com/'<br>Example: https://www.instagram.com/ff_msf",
"SITE": "Club website with or without 'https://'<br>Example: ffsaf.fr<br>Or https://ffsaf.fr",
"TELEPHONE": "Club phone number<br>Example: 06 12 13 78 55"
},
"club.toast.aff.error": "Failed to load affiliations",
"club.toast.aff.pending": "Loading affiliations in progress",
"club.toast.aff.success": "Affiliations loaded successfully 🎉",
"club.toast.del.error": "Failed to delete club",
"club.toast.del.pending": "Deleting club in progress",
"club.toast.del.success": "Club deleted successfully 🎉",
"club.toast.new.error": "Failed to create club",
"club.toast.new.pending": "Creating club in progress",
"club.toast.new.success": "Club created successfully 🎉",
"club.toast.save.error": "Failed to save club",
"club.toast.save.pending": "Saving club in progress",
"club.toast.save.success": "Club saved successfully 🎉",
"clubExterne": "External club",
"club_one": "Club",
"club_other": "Clubs",
"club_zero": "No club",
"colonneDansLeFichier": "Column in the file",
"combattant": "fighter",
"comp.aff.blason": "Display the club's coat of arms on screens",
"comp.aff.flag": "Display the fighter's country on screens",
"comp.ajoutRapide": "Quick add",
"comp.ajouterUnCombattant": "Add a fighter",
"comp.ajouterUnInvité": "Add a guest",
"comp.billetterie": "Ticketing",
"comp.billetterieHelloasso": "HelloAsso Ticketing",
"comp.catégorieNormalisée": "Standardized category",
"comp.combattantNonTrouvé": "Fighter not found",
"comp.combattantsInscrits": "Registered fighters",
"comp.compétitionFuture": "Future competition",
"comp.compétitionPassée": "Past competition",
"comp.créationCompétition": "Competition creation",
"comp.dateDinscription": "Registration date",
"comp.editionCompétition": "Competition edition",
"comp.error1": "The end date must be after the start date.",
"comp.error2": "Please enter the start and end dates of registration.",
"comp.error3": "The end date of registration must be after the start date of registration.",
"comp.exporterLesInscription": "Export registrations",
"comp.ha.emailDeRéceptionDesInscriptionséchoué": "Email for receiving failed registrations",
"comp.ha.error1": "Please enter the HelloAsso ticketing URL and associated rates.",
"comp.ha.error2": "The HelloAsso ticketing URL is invalid. Please check the URL format.",
"comp.ha.error3": "Please enter the email for receiving failed registrations.",
"comp.ha.tarifsHelloasso": "HelloAsso rates",
"comp.ha.text1": "To ensure good interconnection with HelloAsso, please follow these instructions:",
"comp.ha.text2": "<strong>Configure the notification URL:</strong> In order for us to receive a notification for each registration, it is necessary to configure the notification URL of your HelloAsso account to redirect to \"https://intra.ffsaf.fr/api/webhook/ha\". To do this, from the home page of your association on HelloAsso, go to <strong>My account</strong> > <strong>Settings</strong> > <strong>Integrations and API</strong> section Notification and copy-paste <strong>https://intra.ffsaf.fr/api/webhook/ha</strong> in the <strong>My callback URL</strong> field and save.",
"comp.ha.text3": "<strong>Copy-paste the exact name of the rates</strong> <em>-separated by semicolons-</em> that will result in automatic registration. All these rates must imperatively require the license number as a mandatory field. To do this, during the configuration of your ticketing at step 3, click on <strong>+ Add information</strong>, enter the exact title <strong>License number</strong>, in <strong>Type of response desired</strong> enter Number, select the rates entered previously and make the information mandatory.",
"comp.ha.text4": "<strong>Copy-paste the URL of your ticketing</strong> in the field below. It should look like this: https://www.helloasso.com/associations/__asso-name-on-helloasso__/events/__ticketing-name__",
"comp.ha.text5": "HelloAsso ticketing URL",
"comp.ha.text6": "If for any reason the automatic registration fails, an email will be sent to this address to inform you",
"comp.informationsGénéralesSurLaCompétition": "General information about the competition",
"comp.informationsSurLeModeDinscription": "Information on the registration mode",
"comp.informationsTechniques": "Technical information",
"comp.inscription": "Registration",
"comp.inscriptionModeAdministrateur": "Registration - administrator mode",
"comp.inscriptionsLibres": "Free registrations",
"comp.inscriptionsParLesAdministrateursDeLaCompétition": "Registrations by competition administrators",
"comp.inscriptionsParLesResponsablesDeClub": "Registrations by club managers",
"comp.inscriptionsSurLaBilletterieHelloasso": "Registrations on the HelloAsso ticketing",
"comp.modal.annoncé": "Announced",
"comp.modal.information": "Information",
"comp.modal.pesé": "Weighed",
"comp.modal.poids": "Weight (in kg)",
"comp.modal.recherche": "Search*",
"comp.modal.surclassement": "Overclassification",
"comp.modal.text1": "Guests are reserved for members not licensed by the federation. Fighters registered via this form will not be able to see their results from their profile.",
"comp.modal.text2": "Prevent members/clubs from modifying this registration",
"comp.modifierLesParticipants": "View/Edit participants",
"comp.monInscription": "My registration",
"comp.noDeLicence": "License number",
"comp.nouvelleCompétition": "New competition",
"comp.organisateur": "Organizer",
"comp.quiPeutInscrire": "Who can register",
"comp.reg.libres": "Free",
"comp.reg.parLesAdministrateursDeLaCompétition": "By competition administrators",
"comp.reg.parLesResponsablesDeClub": "By club managers",
"comp.reg.surLaBilletterieHelloasso": "On the HelloAsso ticketing",
"comp.responsablesEtBureauxDesAssociations": "Managers and boards of associations",
"comp.sinscrire": "Register",
"comp.supprimerLaCompétition": "Delete competition",
"comp.supprimerLaCompétition.msg": "Are you sure you want to delete this competition and all associated results?",
"comp.surclassement_one": "{{cat}} with 1 overclassification",
"comp.surclassement_other": "{{cat}} with {{count}} overclassifications",
"comp.surclassement_zero": "{{cat}}",
"comp.text2": "Visible to the public (appears in the list of competitions)",
"comp.text3": "If not checked, the competition will only be visible to people who can register participants.",
"comp.tips": "Tip 1: It is possible to ban a fighter, which will prevent them from being re-registered by any means other than by an administrator of this competition. To do this, click on the small <1/> next to their name.<br/>Tip 2: It is also possible to lock the modifications of their registration from their file, which will prevent it from being modified/deleted by themselves and/or a club manager.",
"comp.toast.del.error": "Failed to delete competition",
"comp.toast.del.pending": "Deleting competition in progress",
"comp.toast.del.success": "Competition deleted successfully 🎉",
"comp.toast.params.error": "Failed to update competition parameters",
"comp.toast.params.pending": "Updating competition parameters in progress...",
"comp.toast.params.success": "Competition parameters updated successfully 🎉",
"comp.toast.register.add.error": "Fighter not found",
"comp.toast.register.add.pending": "Search in progress",
"comp.toast.register.add.success": "Fighter found and added/updated",
"comp.toast.register.addMultiple.success_one": "Successful import for 1 fighter",
"comp.toast.register.addMultiple.success_other": "Successful import for {{count}} fighters",
"comp.toast.register.ban.error": "Error",
"comp.toast.register.ban.pending": "Unregistration in progress",
"comp.toast.register.ban.success": "Fighter unregistered and banned",
"comp.toast.register.del.error": "Error",
"comp.toast.register.del.pending": "Unregistration in progress",
"comp.toast.register.del.success": "Fighter unregistered",
"comp.toast.register.self.add.error": "Error during registration",
"comp.toast.register.self.add.pending": "Registration in progress",
"comp.toast.register.self.add.success": "Registration completed 🎉",
"comp.toast.register.self.del.error": "Error during unregistration",
"comp.toast.register.self.del.pending": "Unregistration in progress",
"comp.toast.register.self.del.success": "Unregistration completed",
"comp.toast.registers.addMultiple.error": "Import failed",
"comp.toast.registers.addMultiple.pending": "Import in progress",
"comp.toast.registers.addMultiple.success": "Import completed successfully 🎉",
"comp.toast.save.error": "Failed to save competition",
"comp.toast.save.pending": "Saving competition in progress",
"comp.toast.save.success": "Competition saved successfully 🎉",
"comp.tousLesMembresDeLaFfsaf": "All FFSAF members",
"comp.typeDinscription": "Registration type",
"comp.uniquementLesAdministrateursDeLaCompétition": "Only competition administrators",
"comp.warn1": "Are you sure you want to unregister and ban this fighter from the competition?\n(You can re-register them later)",
"comp.warn2": "Are you sure you want to unregister this fighter?\nThis will not unregister them from the HelloAsso ticketing and will not refund them.",
"comp.warn3": "Are you sure you want to unregister this fighter?",
"comp.warn4": "Are you sure you want to unregister yourself?",
"comp_manage": "Competitions Manager",
"competition_one": "Competition",
"competition_other": "Competitions",
"compte": "Account",
"compétition": "Competition",
"configuration": "Configuration",
"configurationDeLaCatégorie": "Category configuration",
"conserverLancienEmail": "Keep the old email",
"contactAdministratif": "Administrative contact",
"contactInterne": "Internal contact",
"contact_one": "Contact",
"contact_other": "Contacts",
"coquilleProtectionPelvienne": "Shell / Pelvic protection",
"date": "Date",
"dateDeNaissance": "Date of birth",
"days": [
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
],
"de": "of",
"demandeDaffiliationEnCours": "Affiliation request in progress",
"demandeDeLicence": "License request",
"demander": "Request",
"description": "Description",
"dlAff": "Download affiliation certificate",
"donnéesAdministratives": "Administrative data",
"du": "From",
"dun": "of a",
"duréePause": "Pause duration",
"duréeRound": "Round duration",
"définirLidDuCompte": "Define account ID",
"editionDeL'affiliation": "Editing affiliation",
"editionDeLaDemande": "Editing request",
"editionDeLaLicence": "Editing license",
"editionDeLaSéléction": "Editing selection",
"editionDeLadresse": "Editing address",
"email": "Email",
"en": "in",
"enAttente": "Pending",
"erreurDePaiement": "Payment error😕",
"erreurDePaiement.detail": "Error message:",
"erreurDePaiement.msg": "An error occurred while processing your payment. Please try again later.",
"erreurPourLinscription": "Registration error",
"espaceAdministration": "Administration space",
"f": "F",
"faitPar": "Done by",
"femme": "Female",
"fileImport.variants": {
"categorie": [
"category",
"catégorie",
"weight category",
"age category"
],
"club": [
"club",
"club name",
"association",
"association name"
],
"genre": [
"gender",
"genre",
"sex",
"civility"
],
"licence": [
"license",
"licence",
"license number",
"license ID",
"ID license",
"licence no"
],
"nom": [
"last name",
"nom",
"family name",
"surname",
"lastname"
],
"overCategory": [
"over category",
"surclassement",
"category override",
"over classification"
],
"pays": [
"country",
"pays",
"country of residence",
"origin country"
],
"prenom": [
"first name",
"prénom",
"given name",
"first given name"
],
"weight": [
"weight",
"poids",
"weight (kg)",
"actual weight",
"mass"
]
},
"filtre": "Filter",
"gantMainBouclier": "Shield hand glove",
"gantMainsArmées": "Armed hand(s) glove(s)",
"gants": "Gloves",
"genre": "Gender",
"gestionGroupée": "Group management",
"gorgerin": "Gorgerin",
"gradeDarbitrage": "Refereeing grade",
"h": "M",
"home": {
"header1": "For licensed members",
"header2": "For clubs",
"text1": "Here you will find all your information as well as the status of your registration with the federation. You can also download your registration certificate, register for competitions, and consult your results, provided that the organizing club has entered them.<br/><br/>During your first registration, you will receive an email containing your login information; this email will be sent once your license is validated by the secretariat.",
"text2": "This is where you can take out federal licenses for your members, where you can request or renew your affiliation, enter your schedules, training locations, and social networks, which will then be displayed on the ffsaf.fr website.<br/>You will also have the possibility to publish registration forms for your competitions and to record the results.<br/><br/>Not yet affiliated with the federation? Click <1>here</1> to make your first request.",
"welcome_message": "Welcome to the intranet of the Fédération France Soft Armored Fighting"
},
"homme": "Male",
"horairesD'entraînements": "Training schedules",
"importationDuFichier": "Importing the file",
"importerDesCombattants": "Import fighters",
"importerDesInvités": "Import guests",
"information": "Information",
"invité": "guest",
"keepEmpty": "Leave blank to make no changes.",
"le": "the",
"licence": "License",
"licenceNo": "License no. {{no}}",
"lieu": "Place",
"lieuxDentraînements": "Training locations",
"ligneIgnorée1": "Line ignored: missing name, first name or category.",
"ligneIgnorée2": "Line ignored: missing first name or license.",
"loading": "Loading...",
"me": {
"result": {
"PRIVATE": "Private (visible only to me)",
"PUBLIC": "Public (visible to all)",
"REGISTERED_ONLY": "Logged-in members (visible to federation members)",
"REGISTERED_ONLY_NO_DETAILS": "Logged-in members - hide details (visible to federation members)"
},
"toast.settings.error": "Failed to update settings 😕",
"toast.settings.pending": "Updating settings in progress...",
"toast.settings.success": "Settings updated successfully 🎉"
},
"me.changerMonMotDePasse": "Change my password",
"me.formationDarbitrage": "Refereeing training",
"me.paramètresDuCompte": "Account settings",
"me.rôleAuSienDuClub": "Role within the club",
"me.visibilitéDesRésultats": "Visibility of results",
"member_one": "Member",
"member_other": "Members",
"membre.emailVideàLaLigne": "Empty email on line {{no}}",
"membre.emailVérifié": "Email verified",
"membre.filtre.inactif": "Show inactive fighters",
"membre.filtre.licence": "Show license status",
"membre.filtre.licences": [
"No request or valid license",
"With request or valid license",
"Request in progress",
"License validated",
"All license statuses",
"Complete request",
"Incomplete request"
],
"membre.filtre.payement": [
"Without payment",
"With payment",
"All payment statuses"
],
"membre.identifiant": "Identifier",
"membre.import.err1": "Invalid medical certificate date format on line {{no}}",
"membre.import.err2": "Invalid medical certificate date format on line {{no}}",
"membre.import.err3": "Empty date of birth on line {{no}}",
"membre.import.err4": "Invalid date of birth format on line {{no}}",
"membre.import.err5": "Invalid email on line {{no}}",
"membre.import.errTT_one": "{{count}} error in the file, operation cancelled",
"membre.import.errTT_other": "{{count}} errors in the file, operation cancelled",
"membre.import.warn_one": "{{count}} medical certificate not filled",
"membre.import.warn_other": "{{count}} medical certificates not filled",
"membre.info.emailInfo": "The email is used to create an account to log in to the site and must be unique.<br/>For minors, the parents' email can be used multiple times using the following syntax: {'email.parent+<alphanumeric characters>@example.com'}.<br/>Examples: mail.parent+1@example.com, mail.parent+titouan@example.com, mail.parent+cedrique@example.com",
"membre.info.error1": "Please select a valid country 😕",
"membre.info.error2": "Please select a valid club 😕",
"membre.initaccount": "Initialize account",
"membre.initaccount.text1": "Enter the account UUID",
"membre.initaccount.text2": "Warning: only change a member's ID if you are sure of what you are doing...",
"membre.noAccount": "This member does not have an account...",
"membre.noAccount.clubMsg": "An account will be created by the federation when their first license is validated",
"membre.nomVideàLaLigne": "Empty last name on line {{no}}",
"membre.prénomVideàLaLigne": "Empty first name on line {{no}}",
"membre.toast.compte.created": "Account created successfully 🎉",
"membre.toast.compte.error": "Failed to create account",
"membre.toast.compte.pending": "Creating account in progress",
"membre.toast.del.error": "Failed to delete account",
"membre.toast.del.pending": "Deleting account in progress",
"membre.toast.del.success": "Account deleted successfully 🎉",
"membre.toast.id.error": "Failed to define identifier",
"membre.toast.id.pending": "Defining identifier in progress",
"membre.toast.id.success": "Identifier defined successfully 🎉",
"membre.toast.licence.ask.del.error": "Failed to delete license request",
"membre.toast.licence.ask.del.pending": "Deleting license request in progress",
"membre.toast.licence.ask.del.success": "License request deleted successfully 🎉",
"membre.toast.licence.ask.error": "Failed to request license",
"membre.toast.licence.ask.pending": "Saving license request in progress",
"membre.toast.licence.ask.success": "License request saved successfully 🎉",
"membre.toast.licence.del.error": "Failed to delete license",
"membre.toast.licence.del.pending": "Deleting license in progress",
"membre.toast.licence.del.success": "License deleted successfully 🎉",
"membre.toast.licence.save.error": "Failed to save license",
"membre.toast.licence.save.pending": "Saving license in progress",
"membre.toast.licence.save.success": "License saved successfully 🎉",
"membre.toast.licences.export.error": "Failed to export licenses",
"membre.toast.licences.export.pending": "Exporting licenses in progress",
"membre.toast.licences.export.success": "Licenses exported successfully 🎉",
"membre.toast.licences.import.error": "Failed to send changes",
"membre.toast.licences.import.pending": "Sending changes in progress",
"membre.toast.licences.import.success": "Changes sent successfully 🎉",
"membre.toast.licences.load.error": "Failed to load licenses",
"membre.toast.licences.load.pending": "Loading licenses in progress",
"membre.toast.licences.load.success": "Licenses loaded successfully 🎉",
"membre.toast.perm.error": "Failed to update permissions 😕",
"membre.toast.perm.pending": "Updating permissions in progress...",
"membre.toast.perm.success": "Permissions updated successfully 🎉",
"membre.toast.save.error": "Failed to update profile 😕",
"membre.toast.save.pending": "Updating profile in progress...",
"membre.toast.save.success": "Profile updated successfully 🎉",
"membre.toast.select.del.error": "Failed to delete selection",
"membre.toast.select.del.pending": "Deleting selection in progress",
"membre.toast.select.del.success": "Selection deleted successfully 🎉",
"membre.toast.select.save.error": "Failed to save selection",
"membre.toast.select.save.pending": "Saving selection in progress",
"membre.toast.select.save.success": "Selection saved successfully 🎉",
"mettreàJours": "Update",
"modification": "Modification",
"nationalité": "Nationality",
"nav": {
"account": "My account",
"aff_request": "Affiliation request",
"club": {
"my": "My club"
},
"competitions": {
"results": "My results"
},
"home": "Home",
"login": "Login",
"logout": "Logout",
"space": "My space",
"title": "FFSAF Intranet"
},
"noLicence": "License no.",
"noSiretOuRna": "SIRET or RNA no.",
"nom": "Last name",
"nombreDeLicences": "Number of licenses",
"nombreDeLicencesParCatégorie": "Number of licenses by category for {{saison}}",
"non": "No",
"nonDéfinie": "Not defined",
"nonValidée": "Not validated",
"nouveauClub": "New club",
"nouveauMembre": "New member",
"nouvelEmail": "New email",
"numéroDeLaLigneDentête": "Header line number",
"numéroDeLigne": "Line number",
"ou": "or",
"oui": "Yes",
"outdated_session": {
"login_button": "Log in again",
"message": "Your session has expired, please log in again to continue using the application.",
"title": "Session expired"
},
"pageClub": "Club page",
"pageMembre": "Member page",
"pageNouveauClub": "New club page",
"page_info_full": "Line {{line}} to {{tt_line}} (page {{page}} of {{tt_page}})",
"page_info_ligne": "{{show}} line(s) displayed out of {{total}}",
"paiementDeLaLicence": "License payment",
"paiementDesLicences": "License payments",
"par": "by",
"pasDeLicence": "No license",
"payment.ha.info": "HelloAsso's solidarity model guarantees that 100% of your payment will be transferred to the chosen association. You can support the help they provide to associations by leaving a voluntary contribution to HelloAsso at the time of your payment.",
"payment.info": "About HelloAsso",
"payment.paiementSécurisé": "Secure payment",
"payment.payerAvec": "Pay with",
"payment.recap": "{{count}} license(s) selected<br/>Total amount to pay: {{total}} €",
"paymentDesLicences": "License payments",
"paymentDesLicences.msg_one": "Are you sure you want to mark the license as paid?",
"paymentDesLicences.msg_other": "Are you sure you want to mark the {{count}} licenses as paid?",
"paymentDesLicences.msg_zero": "$t(paymentDesLicences.msg_other)",
"paymentOk": "🎉Your payment has been processed successfully.🎉",
"paymentOk.msg": "Thank you for your payment. The licenses should be activated within the next hour, provided that the medical certificate is completed.",
"pays": "Country",
"perm.administrateurDeLaFédération": "Federation administrator",
"perm.créerDesCompétion": "Create competitions",
"perm.ffsafIntra": "FFSAF intra",
"permission": "Permission",
"peutSinscrire": "Can register?",
"photos": "Photos",
"plastron": "Breastplate",
"poids": "Weight",
"poidsDemandéPour": "Weight required for",
"prenom": "First name",
"protectionDeBras": "Arm protection",
"protectionDeBrasArmé": "Protection of armed arm(s)",
"protectionDeBrasDeBouclier": "Shield arm protection",
"protectionDeCoudes": "Elbow protection",
"protectionDeGenoux": "Knee protection",
"protectionDeJambes": "Leg protection",
"protectionDePieds": "Foot protection",
"protectionDorsale": "Back protector",
"protectionObligatoire": "Mandatory protection",
"prénomEtNom": "First and last name",
"rechercher": "Search",
"rechercher...": "Search...",
"registration_one": "Registration",
"registration_other": "Registrations",
"renouveler": "Renew",
"renouvellementDeLaffiliation": "Affiliation renewal",
"result_one": "Result",
"result_other": "Results",
"retouràLaListeDeMembres": "Back to member list",
"role": "Role",
"role.membre": "Member",
"role.membreDuBureau": "Board member",
"role.président": "President",
"role.secrétaire": "Secretary",
"role.trésorier": "Treasurer",
"role.vise-président": "Vice-President",
"role.vise-secrétaire": "Vice-Secretary",
"role.vise-trésorier": "Vice-Treasurer",
"saison": "Season",
"sans": "Without",
"secrétariatsDeLice": "Ring secretariats",
"selectionner...": "Select...",
"shield.buckler": "Buckler",
"shield.none": "$t(sans) / $t(nonDéfinie)",
"shield.round": "Round",
"shield.standard": "Standard",
"shield.teardrop": "Teardrop",
"siDisponiblePourLaCatégorieDages": "If available for the age category",
"siretOuRna": "SIRET or RNA",
"stats": "Statistics",
"statue": "Statue",
"status": "Status",
"statuts": "Statutes",
"supprimerLeClub": "Delete club",
"supprimerLeClub.msg": "Are you sure you want to delete this club?",
"supprimerLeCompte": "Delete account",
"supprimerLeCompte.msg": "Are you sure you want to delete this account?",
"sword.none": "$t(sans) / $t(nonDéfinie)",
"sword.oneHand": "One hand sword",
"sword.saber": "Saber",
"sword.twoHand": "Two hands sword",
"sélectionEnéquipeDeFrance": "Selection in the French team",
"sélectionner...": "Select...",
"toast.edit.error": "Failed to save changes",
"toast.edit.pending": "Saving changes in progress",
"toast.edit.success": "Changes saved successfully 🎉",
"toast.licence.bulk.pay.error": "Failed to mark licenses as paid",
"toast.licence.bulk.pay.pending": "Marking licenses as paid in progress",
"toast.licence.bulk.pay.success": "Licenses marked as paid successfully 🎉",
"toast.licence.bulk.valid.error": "Failed to validate licenses",
"toast.licence.bulk.valid.pending": "Validating licenses in progress",
"toast.licence.bulk.valid.success": "Licenses validated successfully 🎉",
"toast.licence.order.error": "Failed to create order",
"toast.licence.order.pending": "Creating order in progress",
"toast.licence.order.success": "Order created successfully 🎉",
"trie": "Sort",
"téléchargerLexcelDesMembres": "Download members' Excel",
"téléchargerLexcelDesMembres.info": "To be used as a template to update information",
"téléchargéeLaLicence": "Download license",
"validationDeLaLicence": "License validation",
"validationDesLicences": "License validations",
"validerDesLicences": "Validate licenses",
"validerLePayement_one": "Validate payment for the selected license",
"validerLePayement_other": "Validate payment for the {{count}} selected licenses",
"validerLePayement_zero": "$t(validerLePayement_other)",
"validerLicence.msg_one": "Are you sure you want to validate the license?",
"validerLicence.msg_other": "Are you sure you want to validate the {{count}} licenses?",
"validerLicence.msg_zero": "$t(validerLicence.msg_other)",
"validerLicence_one": "Validate the selected license",
"validerLicence_other": "Validate the {{count}} selected licenses",
"validerLicence_zero": "$t(validerLicence_other)",
"validée": "Validated",
"veuillezAssocierChaqueChampàUneColonneDuFichier": "Please associate each field with a column in the file",
"veuillezIndiqueràQuelle": "Please indicate on which line the headers are located in the file",
"veuillezMapperLesColonnesSuivantes": "Please map the following columns",
"voir/modifierLesParticipants": "View/Edit participants",
"voirLesStatues": "View statues",
"vousNêtesPasEncoreInscrit": "You are not yet registered or your registration has not yet been entered on the intranet",
"à": "at",
"étatDeLaDemande": "Request status"
}

View File

@@ -0,0 +1,75 @@
{
"--sélectionnerUnClub--": "--Select a club--",
"--sélectionnerUnCombattant--": "--Select a fighter--",
"--sélectionnerUneCatégorie--": "--Select a category--",
"1er": "Gold medal",
"2eme": "Silver medal",
"3eme": "Bronze medal",
"abs.": "abs.",
"adversaire": "Opponent",
"aujourdhuià": "Today at {{time}}",
"avanthierà": "Day before yesterday at {{time}}",
"back": "« back",
"bleu": "Blue",
"catégorie": "Category",
"chargement": "Loading",
"classement": "Ranking",
"classementClub": "Club ranking",
"classementDesClub": "Clubs ranking",
"classementFinal": "Final ranking",
"club": "Club",
"combattant": "Fighter",
"combattants": "Fighters",
"date": "Date",
"disc.": "DQ",
"défaites": "Losses",
"erreurDeChargementDeLaListe": "Error loading the list",
"erreurDeChargementDeLaPoule": "Error loading the pool",
"erreurDeChargementDesCatégories": "Error loading categories",
"erreurDeChargementDesClubs": "Error loading clubs",
"erreurDeChargementDesCombattants": "Error loading fighters",
"erreurDeChargementDuClub": "Error loading club",
"erreurDeChargementDuCombattant": "Error loading fighter",
"for.": "forf.",
"hierà": "Yesterday at {{time}}",
"info": "Info",
"listeDesCombattants": "List of fighters",
"listeDesMatchs": "List of matches",
"listeDesMembres": "List of members",
"listeDesMenbres": "List of members",
"nom": "Last name",
"nomPrénom": "Last name First name",
"nombreDeMatchDisputé2": "Number of matches played: {{nb}}",
"nombreDeVictoires2": "Number of wins: {{nb}}",
"nombreDinscris": "Number of registered",
"nombreDinscris2": "Number of registered: {{nb}}",
"parCatégorie": "By category",
"parClub": "By club",
"parCombattant": "By fighter",
"place": "Place",
"pointsMarqués": "Points scored",
"pointsMarqués2": "Points scored: {{nb}}",
"pointsReçus": "Points received",
"pointsReçus2": "Points received: {{nb}}",
"poule": "Pool",
"ratio": "Ratio",
"ratioDePointsMoyen2": "Average points ratio: {{nb}}",
"ratioDeVictoiresMoyen2": "Average win ratio: {{nb}}",
"ratioDuScore2": "Score ratio (points scored / points received): {{nb}}",
"ratioPoints": "Points ratio",
"ratioVictoires": "Win ratio",
"ratiosPoints": "Points ratios",
"rechercheParCatégorie": "Search by category",
"rechercheParClub": "Search by club",
"rechercheParCombattant": "Search by fighter",
"rouge": "Red",
"résultatDeLaCompétition": "Competition result",
"score": "Score",
"scores": "Scores",
"statistique": "Statistics",
"tauxDeVictoire2": "Win rate: {{nb}}% ({{victoires}} out of {{matchs}})",
"tournois": "Tournaments",
"tousLesCombattants": "All fighters",
"victoire": "Win",
"victoires": "Wins"
}

View File

@@ -0,0 +1,213 @@
{
"--SélectionnerUnCombattant--": "-- Sélectionner un combattant --",
"--Tous--": "-- Tous --",
"PourLéquipe": " pour l'équipe",
"actuel": "Actuel",
"administration": "Administration",
"adresseDuServeur": "Adresse du serveur",
"afficher": "Afficher",
"ajoutAutomatique": "Ajout automatique",
"ajouter": "Ajouter",
"ajouterDesCombattants": "Ajouter des combattants",
"ajouterUn": "Ajouter un ",
"ajouterUneTeam": "Ajouter une équipe",
"attention": "Attention",
"aucuneConfigurationObs": "Aucune configuration OBS trouvée, veuillez en importer une",
"avertissement": "Avertissement",
"bleu": "Bleu",
"blue": "Blue",
"cardAdded": "Carton ajouté",
"cardRemoved": "Carton retiré",
"carton": "Carton",
"cartonDéquipe": "Carton d'équipe",
"cartonJaune": "Carton jaune",
"cartonNoir": "Carton noir",
"cartonRouge": "Carton rouge",
"catégorie": "Catégorie",
"catégorieDâgeMoyenne": "Catégorie d'âge moyenne",
"catégorieSélectionnée": "Catégorie sélectionnée",
"catégoriesVontêtreCréées": "catégories de poids vont être créées",
"ceCartonEstIssuDunCartonDéquipe": "Ce carton est issu d'un carton d'équipe, voulez-vous vraiment le supprimer ?",
"certainsCombattantsNontPasDePoidsRenseigné": "Certains combattants n'ont pas de poids renseigné, ils ne seront PAS insert dans les catégories",
"chrono.+/-...S": "+/- ... s",
"chrono.+10S": "+10 s",
"chrono.+1S": "+1 s",
"chrono.-10S": "-10 s",
"chrono.-1S": "-1 s",
"chrono.arrêter": "Arrêter",
"chrono.définirLeTemps": "Définir le temps",
"chrono.démarrer": "Démarrer",
"chrono.editionTemps": "Edition temps",
"chrono.entrezLeTempsEnS": "Entrez le temps en s",
"chrono.recapTemps": "Temps: {{temps}}, pause: {{pause}}",
"chronomètre": "Chronomètre",
"classement": "Classement",
"club": "Club",
"combattantsCorrespondentAuxSélectionnés": "combattant(s) correspondent aux sélections ci-dessus.",
"compétition": "Compétition",
"compétitionManager": "Compétition manager",
"config.obs.dossierDesResources": "Dossier des resources",
"config.obs.motDePasseDuServeur": "Mot de passe du serveur",
"config.obs.warn1": "/! Le mot de passe va être stoker en claire, il est recommandé de ne l'utiliser que sur obs websocket et d'en changer entre chaque compétition",
"config.obs.ws": "ws://",
"configurationDuNomDeLaZone": "Configuration du nom de la zone",
"configurationObs": "Configuration OBS",
"confirm1": "Ce match a déjà des résultats, êtes-vous sûr de vouloir le supprimer ?",
"confirm2.msg": "Voulez-vous vraiment changer la taille de l'arbre du tournoi ou les matchs pour les perdants ? Cela va modifier les matchs existants (incluant des possibles suppressions)!",
"confirm2.title": "Changement de l'arbre du tournoi",
"confirm3.msg": "Voulez-vous vraiment enlever la partie {{typeStr}} de la catégorie. Cela va supprimer les matchs contenus dans cette partie !",
"confirm3.title": "Changement de type de catégorie",
"confirm4.msg": "Voulez-vous vraiment supprimer la catégorie {{name}}. Cela va supprimer tous les matchs associés !",
"confirm4.title": "Suppression de la catégorie",
"confirmer": "Confirmer",
"conserverUniquementLesMatchsTerminés": "Conserver uniquement les matchs terminés",
"contre": "contre",
"couleur": "Couleur",
"créationDeLaLesCatégories": "Création de la/les catégories",
"créerLaPhaseFinaleSilYADesPoules": "Créer la phase finale s'il y a des poules",
"créerLesMatchesDeClassement": "Créer les matches de classement",
"créerLesMatchesDeClassement.msg": "Des matches de classement ont déjà été configurer/jouer, la recréation de ces matches vont tous les supprimer (vous perdre donc les résultats s'il y en a). Mercie de noter de votre côté les informations que vous voulez conserver.",
"créerLesMatchs": "Créer les matchs",
"créerToutesLesCatégories": "Créer toutes les catégories",
"date": "Date",
"demi-finalesEtFinales": "Demi-finales et finales",
"depuisUneCatégoriePrédéfinie": "Depuis une catégorie prédéfinie",
"duréePause": "Durée pause",
"duréeRound": "Durée round",
"editionDeLaCatégorie": "Edition de la catégorie",
"editionDuMatch": "Edition du match",
"enregister": "Enregister",
"enregistrer": "Enregistrer",
"epéeBouclier": "Epée bouclier",
"err1": "Un combattant ne peut pas s'affronter lui-même !",
"err2": "Le format du nom des zones de combat est invalide. Veuillez séparer les noms par des ';'.",
"err3": "Au moins un type (poule ou tournoi) doit être sélectionné.",
"erreurLorsDeLaCopieDansLePresse": "Erreur lors de la copie dans le presse-papier : ",
"erreurLorsDeLaCréationDesMatchs": "Erreur lors de la création des matchs: ",
"etatDesTablesDeMarque": "Etat des tables de marque",
"exporter": "Exporter",
"fermer": "Fermer",
"feuilleVierge": "Feuille vierge",
"finalesUniquement": "Finales uniquement",
"genre": "Genre",
"genre.f": "F",
"genre.h": "H",
"genre.na": "NA",
"imprimer": "Imprimer",
"individuelle": "Individuelle",
"informationCatégorie": "Information catégorie",
"inscrit": "Inscrit",
"jusquauRang": "Jusqu'au rang",
"leTournoiServiraDePhaseFinaleAuxPoules": "Le tournoi servira de phase finale aux poules",
"lesCombattantsEnDehors": "Les combattants en dehors du tournoi auront un match de classement",
"lesCombattantsEnDehors2": "Les combattants en dehors du tournoi de classement auront un match de classement",
"listeDesCartons": "Liste des cartons",
"manche": "Manche",
"matchPourLesPerdantsDuTournoi": "Match pour les perdants du tournoi:",
"matchTerminé": "Match terminé",
"matches": "Matches",
"modeDeCréation": "Mode de création",
"modifier": "Modifier",
"msg1": "Il y a déjà des matchs dans cette poule, que voulez-vous faire avec ?",
"neRienConserver": "Ne rien conserver",
"no": "N°",
"nom": "Nom",
"nomDeLaZone": "Nom de la zone",
"nomDeLéquipe": "Nom de l'équipe",
"nomDesZonesDeCombat": "Nom des zones de combat <1>(séparée par des ';')</1>",
"nombreDeCombattants": "Nombre de combattants",
"nouvelle...": "Nouvelle...",
"obs.préfixDesSources": "Préfix des sources",
"pays": "Pays",
"personnaliser": "Personnaliser",
"podium": "Podium",
"podiumDesClubs": "Podium des clubs",
"poids": "Poids",
"poule": "Poule",
"poulePour": "Poule pour: ",
"préparation...": "Préparation...",
"quoiImprimer?": "Quoi imprimer ?",
"remplacer": "Remplacer",
"rouge": "Rouge",
"réinitialiser": "Réinitialiser",
"résultat": "Résultat",
"sansPoule": "Sans poule",
"sauvegarde": "Sauvegarde",
"sauvegarder": "Sauvegarder",
"score": "Score",
"score.err1": "Impossible de terminer un match nul en tournois.",
"score.spe": "Score speciaux : <br/>-997 : disqualifié <br/>-998 : absent <br/>-999 : forfait",
"scores": "Scores",
"secrétariatsDeLice": "Secrétariats de lice",
"select.aucunCombattantDisponible": "Aucun combattant disponible",
"select.aucunCombattantSélectionné": "Aucun combattant sélectionné",
"select.msg1": "(0 = désactivé)",
"select.recherche": "Recherche",
"select.sélectionnerDesCombatants": "Sélectionner des combatants",
"select.à": "à",
"serveur": "Serveur",
"source": "Source",
"suivant": "Suivant",
"supprimer": "Supprimer",
"supprimerUn": "Supprimer un",
"sélectionneLesModesDaffichage": "Sélectionne les modes d'affichage",
"sélectionner": "Sélectionner",
"taille": "Taille",
"team": "Équipe",
"terminé": "Terminé",
"texteCopiéDansLePresse": "Texte copié dans le presse-papier ! Collez-le dans une balise HTML sur votre WordPress.",
"toast.card.team.error": "Erreur lors de la modification du carton d'équipe",
"toast.card.team.pending": "Modification du carton d'équipe...",
"toast.card.team.success": "Carton d'équipe modifié !",
"toast.createCategory.error": "Erreur lors de la création de la catégorie",
"toast.createCategory.pending": "Création de la catégorie...",
"toast.createCategory.success": "Catégorie créée !",
"toast.deleteCategory.error": "Erreur lors de la suppression de la catégorie",
"toast.deleteCategory.pending": "Suppression de la catégorie...",
"toast.deleteCategory.success": "Catégorie supprimée !",
"toast.matchs.classement.create.error": "Erreur lors de la création des matchs de classement.",
"toast.matchs.classement.create.pending": "Création des matchs de classement en cours...",
"toast.matchs.classement.create.success": "Matchs de classement créés avec succès.",
"toast.matchs.create.error": "Erreur lors de la création des matchs.",
"toast.matchs.create.pending": "Création des matchs en cours...",
"toast.matchs.create.success": "Matchs créés avec succès.",
"toast.print.error": "Erreur lors de la génération du PDF",
"toast.print.pending": "Génération du PDF en cours...",
"toast.print.success": "PDF généré !",
"toast.team.update.error": "Erreur lors de la mise à jour de l'équipe",
"toast.team.update.pending": "Mise à jour de l'équipe...",
"toast.team.update.success": "Équipe mise à jour !",
"toast.updateCategory.error": "Erreur lors de la mise à jour de la catégorie",
"toast.updateCategory.pending": "Mise à jour de la catégorie...",
"toast.updateCategory.success": "Catégorie mise à jour !",
"toast.updateMatchScore.error": "Erreur lors de la mise à jour du score du match",
"toast.updateMatchScore.pending": "Mise à jour du score du match...",
"toast.updateMatchScore.success": "Score du match mis à jour !",
"toast.updateTrees.error": "Erreur lors de la mise à jour des arbres",
"toast.updateTrees.init.error": "Erreur lors de la création des arbres",
"toast.updateTrees.init.pending": "Création des arbres du tournoi...",
"toast.updateTrees.init.success": "Arbres créés !",
"toast.updateTrees.pending": "Mise à jour des arbres du tournoi...",
"toast.updateTrees.success": "Arbres mis à jour !",
"tournoi": "Tournoi",
"tournois": "Tournois",
"tousLesMatchs": "Tous les matchs",
"toutConserver": "Tout conserver",
"touteLaCatégorie": "Toute la catégorie",
"toutesLesCatégories": "Toutes les catégories",
"ttm.admin.obs": "Clique court : Télécharger les ressources. Clique long : Créer la configuration obs",
"ttm.admin.scripte": "Copier le scripte d'intégration",
"ttm.table.inverserLaPosition": "Inverser la position des combattants sur cette écran",
"ttm.table.obs": "Clique court : Charger la configuration et se connecter.",
"ttm.table.pub_aff": "Ouvrir l'affichage public",
"ttm.table.pub_score": "Afficher les scores sur l'affichage public",
"type": "Type",
"téléchargement": "Téléchargement",
"téléchargementEnCours": "Téléchargement en cours...",
"téléchargementTerminé!": "Téléchargement terminé !",
"uneCatégorie": "une catégorie",
"uneCatégorieNePeutContenirPlusDe10Combattants": "Une catégorie ne peut contenir plus de 10 combattants, veuillez créer des catégories de poids.",
"valider": "Valider",
"zone": "Zone",
"zoneDeCombat": "Zone de combat"
}

View File

@@ -0,0 +1,668 @@
{
"(optionnelle)": "(optionnelle)",
"---SansClub---": "--- sans club ---",
"---TousLesAges---": "--- tous les ages ---",
"---ToutLesClubs---": "--- tous les clubs ---",
"---ToutLesPays---": "--- tout les pays ---",
"---TouteLesCatégories---": "--- toute les catégories ---",
"--NonLicencier--": "-- Non licencier --",
"--SélectionnerCatégorie--": "-- Sélectionner catégorie --",
"1Catégorie": "+1 catégorie",
"2Catégorie": "+2 catégorie",
"LesModificationsNontEnregistrer": "/!\\ Les modifications n'ont pas encore été enregistré, cliqué sur enregistrer /!\\",
"activer": "Activer",
"admin": "Administration",
"administrateur": "Administrateur",
"adresse": "Adresse",
"adresseAdministrative": "Adresse administrative",
"aff.ancienNom": "Ancien nom: {{name}}",
"aff.byMembreSim": "Par Membre similaire",
"aff.byNewMenbre": "Par Nouveau membre",
"aff.byNoLicence": "Par n° de licence",
"aff.info1": "Ce club a déjà été affilié (affiliation n°{{no}})",
"aff.membreNo": "Membre n°{{no}}",
"aff.nomDuClub": "Nom du club",
"aff.raisonDuRefus": "Raison du refus",
"aff.raisonDuRefus.msg": "Veuillez indiquer la raison du refus",
"aff.refusConfirm": "Êtes-vous sûr de vouloir refuser cette demande ?",
"aff.refuserLaDemande": "Refuser la demande",
"aff.refuserLaDemande.detail": "Êtes-vous sûr de vouloir refuser cette demande ?",
"aff.submit.error1": "Veuillez saisir un numéro de licence valide pour le membre {{id}}",
"aff.submit.error2": "Veuillez saisir un numéro de licence valide pour le membre {{id}}",
"aff.submit.error3": "Veuillez saisir un email valide pour le membre {{id}}",
"aff.toast.accept.error": "Échec de l'acceptation de l'affiliation",
"aff.toast.accept.pending": "Acceptation de l'affiliation en cours",
"aff.toast.accept.success": "Affiliation acceptée avec succès 🎉",
"aff.toast.del.error": "Échec de la suppression de la demande d'affiliation",
"aff.toast.del.pending": "Suppression de la demande d'affiliation en cours",
"aff.toast.del.success": "Demande d'affiliation supprimée avec succès 🎉",
"aff.toast.del2.error": "Échec de la suppression de l'affiliation",
"aff.toast.del2.pending": "Suppression de l'affiliation en cours",
"aff.toast.del2.success": "Affiliation supprimée avec succès 🎉",
"aff.toast.save.error": "Échec de l'enregistrement de la demande d'affiliation",
"aff.toast.save.pending": "Enregistrement de la demande d'affiliation en cours",
"aff.toast.save.success": "Demande d'affiliation enregistrée avec succès 🎉",
"aff.toast.save2.error": "Échec de l'enregistrement de l'affiliation",
"aff.toast.save2.pending": "Enregistrement de l'affiliation en cours",
"aff.toast.save2.success": "Affiliation enregistrée avec succès 🎉",
"aff_req.appuyerSurRechercher": "Appuyer sur rechercher pour compléter",
"aff_req.association": "L'association",
"aff_req.button.cancel": "Annuler ma demande",
"aff_req.button.confirm": "Confirmer ma demande d'affiliation",
"aff_req.button.save": "Enregistrer les modifications",
"aff_req.denomination": "Dénomination",
"aff_req.disposeLicence": "Dispose déjà d'une licence",
"aff_req.error1": "Le rôle du membre {{i}} est obligatoire",
"aff_req.error2": "Le format du SIRET/RNA est invalide",
"aff_req.nomDeLassociation": "Nom de l'association",
"aff_req.text1": "L'affiliation est annuelle et valable pour une saison sportive : du 1er septembre au 31 août de lannée suivante.",
"aff_req.text2": "Pour saffilier, une association sportive doit réunir les conditions suivantes :",
"aff_req.text2.li": [
"Avoir son siège social en France ou Principauté de Monaco",
"Être constituée conformément au chapitre 1er du titre II du livre 1er du Code du Sport",
"Poursuivre un objet social entrant dans la définition de larticle 1 des statuts de la Fédération",
"Disposer de statuts compatibles avec les principes dorganisation et de fonctionnement de la Fédération",
"Assurer en son sein la liberté dopinion et le respect des droits de la défense, et sinterdire toute discrimination",
"Respecter les règles dencadrement, dhygiène et de sécurité établies par les règlements de la Fédération"
],
"aff_req.text3": "Après validation de votre demande, vous recevrez un identifiant et mot de passe provisoire pour accéder à votre espace FFSAF",
"aff_req.text4": "Notez que pour finaliser votre affiliation, il vous faudra :",
"aff_req.text4.li1": "Disposer dau moins trois membres licenciés, dont le président",
"aff_req.text4.li2": "S'être acquitté des cotisations prévues par les règlements fédéraux",
"aff_req.text5": "Vous pourrez par la suite, ajouter des adresses visibles publiquement pour vos lieux d'entrainement",
"aff_req.text6": "Laissez vide pour ne rien changer. (Si un blason a déjà été envoyé lors de cette demande, il sera utilisé, sinon nous utiliserons celui de la précédant affiliation)",
"aff_req.text7": "Demande d'affiliation envoyée avec succès",
"aff_req.text8": "Une fois votre demande validée, vous recevrez un identifiant et mot de passe provisoire pour accéder à votre espace FFSAF",
"aff_req.toast.undo.error": "Échec de l'annulation de la demande d'affiliation",
"aff_req.toast.undo.pending": "Annulation de la demande d'affiliation en cours",
"aff_req.toast.undo.success": "Demande d'affiliation annulée avec succès 🎉",
"afficherLesCombattantsNonPesés": "Afficher les combattants non pesés",
"afficherLétatDesAffiliation": "Afficher l'état des affiliation",
"affiliation": "Affiliation",
"affiliationNo": "Affiliation n°{{no}}",
"ajout": "Ajout",
"ajouterUnClub": "Ajouter un club",
"ajouterUnMembre": "Ajouter un membre",
"all_season": "--- tout les saisons ---",
"ans": "ans",
"arme": "Arme",
"au": "au",
"aucun": "Aucun",
"aucunMembreSélectionné": "Aucun membre sélectionné",
"aucuneCatégorieDisponible": "Aucune catégorie disponible pour le moment.",
"back": "« retour",
"blason": "Blason",
"bouclier": "Bouclier",
"bureau": "Bureau",
"button.accepter": "Accepter",
"button.ajouter": "Ajouter",
"button.annuler": "Annuler",
"button.appliquer": "Appliquer",
"button.confirmer": "Confirmer",
"button.créer": "Créer",
"button.enregistrer": "Enregistrer",
"button.fermer": "Fermer",
"button.modifier": "Modifier",
"button.refuser": "Refuser",
"button.seDésinscrire": "Se désinscrire",
"button.suivant": "Suivant",
"button.supprimer": "Supprimer",
"casque": "Casque",
"cat.benjamin": "Benjamin",
"cat.cadet": "Cadet",
"cat.catégorieInconnue": "Catégorie inconnue",
"cat.junior": "Junior",
"cat.miniPoussin": "Mini Poussin",
"cat.minime": "Minime",
"cat.poussin": "Poussin",
"cat.senior1": "Senior 1",
"cat.senior2": "Senior 2",
"cat.superMini": "Super Mini",
"cat.vétéran1": "Vétéran 1",
"cat.vétéran2": "Vétéran 2",
"categorie": "categorie",
"catégorie": "Catégorie",
"catégorieàAjouter": "Catégorie à ajouter",
"certificatMédical": "Certificat médical",
"champAttendu": "Champ attendu",
"chargement...": "Chargement...",
"chargerLexcel": "Charger l'Excel",
"chargerLexcel.msg": "Merci d'utiliser le fichier ci-dessus comme base, ne pas renommer les colonnes ni modifier les n° de licences.",
"choisir...": "Choisir...",
"club.aff_renew.msg": "Veuillez sélectionner 0 à 3 membres du bureau pour remplir la pré-demande. (Si un membre non-bureau va le devenir l'an prochain, vous pourrez les renseigner à la prochaine étape)",
"club.change.status": "Pour modifier les informations ci-dessus, merci de contacter la FFSAF par mail.",
"club.contact.tt": {
"AUTRE": "Autre contact du club",
"COURRIEL": "Adresse e-mail du club<br>Exemple: contact@ffsaf.fr",
"FACEBOOK": "Page Facebook du club débutant par 'https://www.facebook.com/'</br>Exemple: https://www.facebook.com/ffmsf",
"INSTAGRAM": "Compte Instagram du club débutant par 'https://www.instagram.com/'</br>Exemple: https://www.instagram.com/ff_msf",
"SITE": "Site web du club avec ou sans le 'https://'</br>Exemple: ffsaf.fr</br>Ou https://ffsaf.fr",
"TELEPHONE": "Numéro de téléphone du club<br>Exemple: 06 12 13 78 55"
},
"club.toast.aff.error": "Impossible de charger les affiliations",
"club.toast.aff.pending": "Chargement des affiliations en cours",
"club.toast.aff.success": "Affiliations chargées avec succès 🎉",
"club.toast.del.error": "Échec de la suppression du club",
"club.toast.del.pending": "Suppression du club en cours",
"club.toast.del.success": "Club supprimé avec succès 🎉",
"club.toast.new.error": "Échec de la création du club",
"club.toast.new.pending": "Création du club en cours",
"club.toast.new.success": "Club créé avec succès 🎉",
"club.toast.save.error": "Échec de l'enregistrement du club",
"club.toast.save.pending": "Enregistrement du club en cours",
"club.toast.save.success": "Club enregistré avec succès 🎉",
"clubExterne": "Club externe",
"club_one": "Club",
"club_other": "Clubs",
"club_zero": "Sans club",
"colonneDansLeFichier": "Colonne dans le fichier",
"combattant": "combattant",
"comp.aff.blason": "Afficher le blason du club sur les écrans",
"comp.aff.flag": "Afficher le pays du combattant sur les écrans",
"comp.ajoutRapide": "Ajout rapide",
"comp.ajouterUnCombattant": "Ajouter un combattant",
"comp.ajouterUnInvité": "Ajouter un invité",
"comp.billetterie": "Billetterie",
"comp.billetterieHelloasso": "Billetterie HelloAsso",
"comp.catégorieNormalisée": "Catégorie normalisée",
"comp.combattantNonTrouvé": "Combattant non trouvé",
"comp.combattantsInscrits": "Combattants inscrits",
"comp.compétitionFuture": "Compétition future",
"comp.compétitionPassée": "Compétition passée",
"comp.créationCompétition": "Création compétition",
"comp.dateDinscription": "Date d'inscription",
"comp.editionCompétition": "Edition compétition",
"comp.error1": "La date de fin doit être postérieure à la date de début.",
"comp.error2": "Veuillez renseigner les dates de début et de fin d'inscription.",
"comp.error3": "La date de fin d'inscription doit être postérieure à la date de début d'inscription.",
"comp.exporterLesInscription": "Exporter les inscriptions",
"comp.ha.emailDeRéceptionDesInscriptionséchoué": "Email de réception des inscriptions échoué",
"comp.ha.error1": "Veuillez renseigner l'URL de la billetterie HelloAsso et les tarifs associés.",
"comp.ha.error2": "L'URL de la billetterie HelloAsso n'est pas valide. Veuillez vérifier le format de l'URL.",
"comp.ha.error3": "Veuillez renseigner l'email de réception des inscriptions échouées.",
"comp.ha.tarifsHelloasso": "Tarifs HelloAsso",
"comp.ha.text1": "Afin de permettre une bonne interconnexion avec HelloAsso, merci de suivre les instructions suivantes :",
"comp.ha.text2": "<strong>Configurer l'url de notification : </strong>afin que nous puissions recevoir une notification à chaque inscription, il est nécessaire de configurer l'url de notification de votre compte HelloAsso pour qu'il redirige vers \"https://intra.ffsaf.fr/api/webhook/ha\". Pour ce faire, depuis la page d'accueil de votre association sur HelloAsso, allez dans <strong>Mon compte</strong> > <strong>Paramètres</strong> > <strong> Intégrations et API</strong> section Notification et copier-coller <strong>https://intra.ffsaf.fr/api/webhook/ha </strong> dans le champ <strong>Mon URL de callback</strong> et enregister.",
"comp.ha.text3": "<strong>Copier-coller le nom exacte des tarifs</strong> <em>-sépare par des point-virgules-</em> qui donneront lieux à une inscription automatique. Tous ces tarifs doivent impérativement demander le numéro de licence en champs obligatoire. Pour ce faire, lors de la configuration de votre billetterie à l'étape n°3, cliquer sur <strong>+ Ajouter une information</strong>, saisissez l'intituler exact suivant <strong> Numéro de licence</strong>, dans <strong>Type de réponse souhaitée</strong> rentrer Nombre, sélectionner les tarifs entrés plus précédemment et rendre l'information obligatoire.",
"comp.ha.text4": "<strong>Copier-coller l'url de votre billetterie</strong> dans le champs si dessous. Il devrais avoir la forme suivante: https://www.helloasso.com/associations/__nom-asso-sur-helloasso__/evenements/__nom-billetterie__",
"comp.ha.text5": "Url de la billetterie HelloAsso",
"comp.ha.text6": "Si pour une raison quelconque l'inscription automatique échoue, un email sera envoyé à cette adresse pour vous en informer",
"comp.informationsGénéralesSurLaCompétition": "Informations générales sur la compétition",
"comp.informationsSurLeModeDinscription": "Informations sur le mode d'inscription",
"comp.informationsTechniques": "Informations techniques",
"comp.inscription": "Inscription",
"comp.inscriptionModeAdministrateur": "Inscription - mode administrateur",
"comp.inscriptionsLibres": "Inscriptions libres",
"comp.inscriptionsParLesAdministrateursDeLaCompétition": "Inscriptions par les administrateurs de la compétition",
"comp.inscriptionsParLesResponsablesDeClub": "Inscriptions par les responsables de club",
"comp.inscriptionsSurLaBilletterieHelloasso": "Inscriptions sur la billetterie HelloAsso",
"comp.modal.annoncé": "Annoncé",
"comp.modal.information": "Information",
"comp.modal.pesé": "Pesé",
"comp.modal.poids": "Poids (en kg)",
"comp.modal.recherche": "Recherche*",
"comp.modal.surclassement": "Surclassement",
"comp.modal.text1": "Les invités sont réservés aux membres non licenciés par la fédération. Les combattants inscrits via ce formulaire ne pourront pas voir leur résultat depuis leur profil.",
"comp.modal.text2": "Empêcher les membres/club de modifier cette inscription",
"comp.modifierLesParticipants": "Voir/Modifier les participants",
"comp.monInscription": "Mon inscription",
"comp.noDeLicence": "N° de licence",
"comp.nouvelleCompétition": "Nouvelle compétition",
"comp.organisateur": "Organisateur",
"comp.quiPeutInscrire": "Qui peut inscrire",
"comp.reg.libres": "Libres",
"comp.reg.parLesAdministrateursDeLaCompétition": "Par les administrateurs de la compétition",
"comp.reg.parLesResponsablesDeClub": "Par les responsables de club",
"comp.reg.surLaBilletterieHelloasso": "Sur la billetterie HelloAsso",
"comp.responsablesEtBureauxDesAssociations": "Responsables et bureaux des associations",
"comp.sinscrire": "S'inscrire",
"comp.supprimerLaCompétition": "Supprimer la compétition",
"comp.supprimerLaCompétition.msg": "Êtes-vous sûr de vouloir supprimer cette compétition est tout les resultat associer?",
"comp.surclassement_one": "{{cat}} avec 1 de surclassement",
"comp.surclassement_other": "{{cat}} avec {{count}} de surclassements",
"comp.surclassement_zero": "{{cat}}",
"comp.text2": "Visible par le public (Apparaît dans la liste des compétitions)",
"comp.text3": "Si non coché, la compétition ne sera visible que par les personnes pouvant y inscrire des participants.",
"comp.tips": "Tips 1: Il est possible de bannir un combattant, ce qui l'empêchera d'être réinscrit par un autre moyen que par un administrateur de cette compétition. Pour cela, cliquez sur la petite <1/> à côté de son nom.<br/>Tips 2: Il est aussi possible de verrouiller les modifications de son inscription depuis sa fiche, ce qui l'empêchera d'être modifié/supprimé par lui-même et/ou un responsable de club.",
"comp.toast.del.error": "Échec de la suppression de la compétition",
"comp.toast.del.pending": "Suppression de la compétition en cours",
"comp.toast.del.success": "Compétition supprimée avec succès 🎉",
"comp.toast.params.error": "Échec de la mise à jours des paramètres de la compétition",
"comp.toast.params.pending": "Mise à jours des paramètres de la compétition en cours...",
"comp.toast.params.success": "Paramètres de la compétition mis à jours avec succès 🎉",
"comp.toast.register.add.error": "Combattant non trouvé",
"comp.toast.register.add.pending": "Recherche en cours",
"comp.toast.register.add.success": "Combattant trouvé et ajouté/mis à jour",
"comp.toast.register.addMultiple.success_one": "Importation réussie pour 1 combattant",
"comp.toast.register.addMultiple.success_other": "Importation réussie pour {{count}} combattants",
"comp.toast.register.ban.error": "Erreur",
"comp.toast.register.ban.pending": "Désinscription en cours",
"comp.toast.register.ban.success": "Combattant désinscrit et bannie",
"comp.toast.register.del.error": "Erreur",
"comp.toast.register.del.pending": "Désinscription en cours",
"comp.toast.register.del.success": "Combattant désinscrit",
"comp.toast.register.self.add.error": "Erreur lors de l'inscription",
"comp.toast.register.self.add.pending": "Inscription en cours",
"comp.toast.register.self.add.success": "Inscription réalisée 🎉",
"comp.toast.register.self.del.error": "Erreur lors de la désinscription",
"comp.toast.register.self.del.pending": "Désinscription en cours",
"comp.toast.register.self.del.success": "Désinscription réalisée",
"comp.toast.registers.addMultiple.error": "Erreur lors de l'importation des combattants",
"comp.toast.registers.addMultiple.pending": "Importation des combattants en cours...",
"comp.toast.registers.addMultiple.success": "Importation des combattants réussie 🎉",
"comp.toast.save.error": "Échec de l'enregistrement de la compétition",
"comp.toast.save.pending": "Enregistrement de la compétition en cours",
"comp.toast.save.success": "Compétition enregistrée avec succès 🎉",
"comp.tousLesMembresDeLaFfsaf": "Tous les membres de la FFSAF",
"comp.typeDinscription": "Type d'inscription",
"comp.uniquementLesAdministrateursDeLaCompétition": "Uniquement les administrateurs de la compétition",
"comp.warn1": "Êtes-vous sûr de vouloir désinscrire et bannir ce combattant de la compétition?\n(Vous pouvez le réinscrire plus tard)\"",
"comp.warn2": "Êtes-vous sûr de vouloir désinscrire ce combattant ?\nCela ne le désinscrira pas de la billetterie HelloAsso et ne le remboursera pas.",
"comp.warn3": "Êtes-vous sûr de vouloir désinscrire ce combattant ?",
"comp.warn4": "Êtes-vous sûr de vouloir vous désinscrire ?",
"comp_manage": "Compétitions Manager",
"competition_one": "Compétition",
"competition_other": "Compétitions",
"compte": "Compte",
"compétition": "Compétition",
"configuration": "Configuration",
"configurationDeLaCatégorie": "Configuration de la catégorie",
"conserverLancienEmail": "Conserver l'ancien email",
"contactAdministratif": "Contact administratif",
"contactInterne": "Contact interne",
"contact_one": "Contact",
"contact_other": "Contacts",
"coquilleProtectionPelvienne": "Coquille / Protection pelvienne",
"date": "Date",
"dateDeNaissance": "Date de naissance",
"days": [
"Lundi",
"Mardi",
"Mercredi",
"Jeudi",
"Vendredi",
"Samedi",
"Dimanche"
],
"de": "de",
"demandeDaffiliationEnCours": "Demande d'affiliation en cours",
"demandeDeLicence": "Demande de licence ",
"demander": "Demander",
"description": "Description",
"dlAff": "Téléchargée l'attestation d'affiliation",
"donnéesAdministratives": "Données administratives",
"du": "Du",
"dun": "d'un",
"duréePause": "Durée pause",
"duréeRound": "Durée round",
"définirLidDuCompte": "Définir l'id du compte",
"editionDeL'affiliation": "Edition de l'affiliation",
"editionDeLaDemande": "Edition de la demande ",
"editionDeLaLicence": "Edition de la licence",
"editionDeLaSéléction": "Edition de la séléction",
"editionDeLadresse": "Edition de l'adresse",
"email": "Email",
"en": "en",
"enAttente": "En attente",
"erreurDePaiement": "Erreur de paiement😕",
"erreurDePaiement.detail": "Message d'erreur :",
"erreurDePaiement.msg": "Une erreur est survenue lors du traitement de votre paiement. Veuillez réessayer plus tard.",
"erreurPourLinscription": "Erreur pour l'inscription",
"espaceAdministration": "Espace administration",
"f": "F",
"faitPar": "Fait par",
"femme": "Femme",
"fileImport.variants": {
"categorie": [
"catégorie",
"category",
"catégorie de poids",
"weight category",
"catégorie d'âge"
],
"club": [
"club",
"nom du club",
"club name",
"association",
"nom de l'association"
],
"genre": [
"genre",
"sexe",
"gender",
"sex",
"civilité"
],
"licence": [
"licence",
"n° licence",
"num licence",
"id licence",
"license",
"licence id"
],
"nom": [
"nom",
"nom de famille",
"lastname",
"family name",
"nom complet"
],
"overCategory": [
"surclassement",
"over category",
"surcatégorie",
"surclassement de catégorie"
],
"pays": [
"pays",
"country",
"pays de résidence",
"pays d'origine"
],
"prenom": [
"prénom",
"prenom",
"first name",
"given name",
"prénom usuel"
],
"weight": [
"poids",
"weight",
"poids (kg)",
"poids réel",
"masse"
]
},
"filtre": "Filtre",
"gantMainBouclier": "Gant main de bouclier",
"gantMainsArmées": "Gant main(s) armée(s)",
"gants": "Gants",
"genre": "Genre",
"gestionGroupée": "Gestion groupée",
"gorgerin": "Gorgerin",
"gradeDarbitrage": "Grade d'arbitrage",
"h": "H",
"home": {
"header1": "Pour les licenciés",
"header2": "Pour les clubs",
"text1": "Vous y retrouverez toutes vos informations ainsi que l'état de votre inscription à la fédération. Vous pouvez également télécharger votre attestation d'inscription, vous inscrire aux compétitions ainsi que consulter vos résultats sous réserve que le club organisateur les ait renseignés.<br/><br/>Lors de votre première inscription, vous recevrez un email contenant vos informations d'identification, ce mail sera envoyé une fois votre licence validée par le secrétariat.",
"text2": "C'est ici que vous pouvez prendre les licences fédérales pour vos adhérents, que vous pouvez demander ou renouveler votre affiliation, renseigner vos horaires, lieux d'entraînement et réseaux sociaux qui seront par la suite affichés sur le site ffsaf.fr.<br/>Vous aurez par ailleurs la possibilité de publier des formulaires d'inscriptions pour vos compétitions ainsi que d'enregistrer les résultats.<br/><br/> Vous n'êtes pas encore affilié à la fédération ? Cliquez <1>içi</1> pour faire votre première demande.",
"welcome_message": "Bienvenue sur lintranet de la Fédération France Soft Armored Fighting"
},
"homme": "Homme",
"horairesD'entraînements": "Horaires d'entraînements",
"importationDuFichier": "Importation du fichier",
"importerDesCombattants": "Importer des combattants",
"importerDesInvités": "Importer des invités",
"information": "Information",
"invité": "invité",
"keepEmpty": "Laissez vide pour ne rien changer.",
"le": "le",
"licence": "Licence",
"licenceNo": "Licence n°{{no}}",
"lieu": "Lieu",
"lieuxDentraînements": "Lieux d'entraînements",
"ligneIgnorée1": "Ligne ignorée : nom, prénom ou catégorie manquante.",
"ligneIgnorée2": "Ligne ignorée : nom prénom ou licence manquante.",
"loading": "Chargement...",
"me": {
"result": {
"PRIVATE": "Privé (visible uniquement par moi)",
"PUBLIC": "Public (visible par tous)",
"REGISTERED_ONLY": "Membres connectés (visibles par les membres de la fédération)",
"REGISTERED_ONLY_NO_DETAILS": "Membres connectés - masquer les détails (visibles par les membres de la fédération)"
},
"toast.settings.error": "Échec de la mise à jours des paramètres 😕",
"toast.settings.pending": "Mise à jours des paramètres en cours...",
"toast.settings.success": "Paramètres mis à jours avec succès 🎉"
},
"me.changerMonMotDePasse": "Changer mon mot de passe",
"me.formationDarbitrage": "Formation d'arbitrage",
"me.paramètresDuCompte": "Paramètres du compte",
"me.rôleAuSienDuClub": "Rôle au sien du club",
"me.visibilitéDesRésultats": "Visibilité des résultats",
"member_one": "Membre",
"member_other": "Membres",
"membre.emailVideàLaLigne": "Email vide à la ligne {{no}}",
"membre.emailVérifié": "Email vérifié",
"membre.filtre.inactif": "Afficher les combattants inactifs",
"membre.filtre.licence": "Afficher l'état des licences",
"membre.filtre.licences": [
"Sans demande ni licence validée",
"Avec demande ou licence validée",
"Demande en cours",
"Licence validée",
"Tout les états de licences",
"Demande complet",
"Demande incomplet"
],
"membre.filtre.payement": [
"Sans paiement",
"Avec paiement",
"Tout les états de paiement"
],
"membre.identifiant": "Identifiant",
"membre.import.err1": "Format de la date de certificat invalide à la ligne {{no}}",
"membre.import.err2": "Format de la date de certificat invalide à la ligne {{no}}",
"membre.import.err3": "Date de naissance vide à la ligne {{no}}",
"membre.import.err4": "Format de la date de naissance invalide à la ligne {{no}}",
"membre.import.err5": "Email invalide à la ligne {{no}}",
"membre.import.errTT_one": "{{count}} erreur dans le fichier, opération annulée",
"membre.import.errTT_other": "{{count}} erreurs dans le fichier, opération annulée",
"membre.import.warn_one": "{{count}} certificat médical non rempli",
"membre.import.warn_other": "{{count}} certificats médicaux non remplis",
"membre.info.emailInfo": "L'email sert à la création de compte pour se connecter au site et doit être unique.<br/>Pour les mineurs, l'email des parents peut être utilisé plusieurs fois grâce à la syntaxe suivante : {'email.parent+<caractères alphanumériques>@exemple.com'}.<br/>Exemples : mail.parent+1@exemple.com, mail.parent+titouan@exemple.com, mail.parent+cedrique@exemple.com",
"membre.info.error1": "Veuillez sélectionner un pays valide 😕",
"membre.info.error2": "Veuillez sélectionner un club valide 😕",
"membre.initaccount": "Initialiser le compte",
"membre.initaccount.text1": "Entré l'UUID du compte",
"membre.initaccount.text2": "Attention ne changée l'id d'un membre que si vous êtes sûr de ce que vos faites...",
"membre.noAccount": "Ce membre ne dispose pas de compte...",
"membre.noAccount.clubMsg": "Un compte sera créé par la fédération lors de la validation de sa première licence",
"membre.nomVideàLaLigne": "Nom vide à la ligne {{no}}",
"membre.prénomVideàLaLigne": "Prénom vide à la ligne {{no}}",
"membre.toast.compte.created": "Compte créé avec succès 🎉",
"membre.toast.compte.error": "Échec de la création du compte",
"membre.toast.compte.pending": "Création du compte en cours",
"membre.toast.del.error": "Échec de la suppression du compte",
"membre.toast.del.pending": "Suppression du compte en cours",
"membre.toast.del.success": "Compte supprimé avec succès 🎉",
"membre.toast.id.error": "Échec de la définition de l'identifient",
"membre.toast.id.pending": "Définition de l'identifient en cours",
"membre.toast.id.success": "Identifient défini avec succès 🎉",
"membre.toast.licence.ask.del.error": "Échec de la suppression de la demande de licence",
"membre.toast.licence.ask.del.pending": "Suppression de la demande de licence en cours",
"membre.toast.licence.ask.del.success": "Demande de licence supprimée avec succès 🎉",
"membre.toast.licence.ask.error": "Échec de la demande de licence",
"membre.toast.licence.ask.pending": "Enregistrement de la demande de licence en cours",
"membre.toast.licence.ask.success": "Demande de licence enregistrée avec succès 🎉",
"membre.toast.licence.del.error": "Échec de la suppression de la licence",
"membre.toast.licence.del.pending": "Suppression de la licence en cours",
"membre.toast.licence.del.success": "Licence supprimée avec succès 🎉",
"membre.toast.licence.save.error": "Échec de l'enregistrement de la licence",
"membre.toast.licence.save.pending": "Enregistrement de la licence en cours",
"membre.toast.licence.save.success": "Licence enregistrée avec succès 🎉",
"membre.toast.licences.export.error": "Échec de l'export des licences",
"membre.toast.licences.export.pending": "Export des licences en cours",
"membre.toast.licences.export.success": "Licences exportées avec succès 🎉",
"membre.toast.licences.import.error": "Échec de l'envoie des changements",
"membre.toast.licences.import.pending": "Envoie des changements en cours",
"membre.toast.licences.import.success": "Changements envoyés avec succès 🎉",
"membre.toast.licences.load.error": "Impossible de charger les licences",
"membre.toast.licences.load.pending": "Chargement des licences en cours",
"membre.toast.licences.load.success": "Licences chargées avec succès 🎉",
"membre.toast.perm.error": "Échec de la mise à jours des permissions 😕",
"membre.toast.perm.pending": "Mise à jours des permissions en cours...",
"membre.toast.perm.success": "Permission mise à jours avec succès 🎉",
"membre.toast.save.error": "Échec de la mise à jours du profil 😕",
"membre.toast.save.pending": "Mise à jours du profil en cours...",
"membre.toast.save.success": "Profil mis à jours avec succès 🎉",
"membre.toast.select.del.error": "Échec de la suppression de la séléction",
"membre.toast.select.del.pending": "Suppression de la séléction en cours",
"membre.toast.select.del.success": "Séléction supprimée avec succès 🎉",
"membre.toast.select.save.error": "Échec de l'enregistrement de la séléction",
"membre.toast.select.save.pending": "Enregistrement de la séléction en cours",
"membre.toast.select.save.success": "Séléction enregistrée avec succès 🎉",
"mettreàJours": "Mettre à jours",
"modification": "Modification",
"nationalité": "Nationalité",
"nav": {
"account": "Mon compte",
"aff_request": "Demande d'affiliation",
"club": {
"my": "Mon club"
},
"competitions": {
"results": "Mes résultats"
},
"home": "Accueil",
"login": "Connexion",
"logout": "Déconnexion",
"space": "Mon espace",
"title": "FFSAF Intranet"
},
"noLicence": "N° Licence",
"noSiretOuRna": "N° $t(siretOuRna)",
"nom": "Nom",
"nombreDeLicences": "Nombre de licences",
"nombreDeLicencesParCatégorie": "Nombre de licences par catégorie pour {{saison}}",
"non": "Non",
"nonDéfinie": "Non définie",
"nonValidée": "Non validée",
"nouveauClub": "Nouveau club",
"nouveauMembre": "Nouveau membre",
"nouvelEmail": "Nouvel email",
"numéroDeLaLigneDentête": "Numéro de la ligne d'en-tête",
"numéroDeLigne": "Numéro de ligne",
"ou": "Ou",
"oui": "Oui",
"outdated_session": {
"login_button": "Se reconnecter",
"message": "Votre session a expirée, veuillez vous reconnecter pour continuer à utiliser l'application.",
"title": "Session expirée"
},
"pageClub": "Page club",
"pageMembre": "Page membre",
"pageNouveauClub": "Page nouveau club",
"page_info_full": "Ligne {{line}} à {{tt_line}} (page {{page}} sur {{tt_page}})",
"page_info_ligne": "{{show}} ligne(s) affichée(s) sur {{total}}",
"paiementDeLaLicence": "Paiement de la licence",
"paiementDesLicences": "Paiement des licences",
"par": "par",
"pasDeLicence": "Pas de licence",
"payment.ha.info": "Le modèle solidaire de HelloAsso garantit que 100% de votre paiement sera versé à lassociation choisie. Vous pouvez soutenir laide quils apportent aux associations en laissant une contribution volontaire à HelloAsso au moment de votre paiement.",
"payment.info": "A propos de HelloAsso",
"payment.paiementSécurisé": "Paiement sécurisé",
"payment.payerAvec": "Payer avec",
"payment.recap": "{{count}} licence(s) sélectionnée<br/>Total à régler : {{total}} €",
"paymentDesLicences": "Payment des licences",
"paymentDesLicences.msg_one": "Êtes-vous sûr de vouloir marquer comme payées la licence ?",
"paymentDesLicences.msg_other": "Êtes-vous sûr de vouloir marquer comme payées les {{count}} licences ?",
"paymentDesLicences.msg_zero": "$t(paymentDesLicences.msg_other)",
"paymentOk": "🎉Votre paiement a été traité avec succès.🎉",
"paymentOk.msg": "Merci pour votre paiement. Les licences devraient être activées dans l'heure qui vient, à condition que le certificat médical soit rempli.",
"pays": "Pays",
"perm.administrateurDeLaFédération": "Administrateur de la fédération",
"perm.créerDesCompétion": "Créer des compétion",
"perm.ffsafIntra": "FFSAF intra",
"permission": "Permission",
"peutSinscrire": "Peut s'inscrire?",
"photos": "Photos",
"plastron": "Plastron",
"poids": "Poids",
"poidsDemandéPour": "Poids demandé pour",
"prenom": "Prénom",
"protectionDeBras": "Protection de bras",
"protectionDeBrasArmé": "Protection de bras armé(s)",
"protectionDeBrasDeBouclier": "Protection de bras de bouclier",
"protectionDeCoudes": "Protection de coudes",
"protectionDeGenoux": "Protection de genoux",
"protectionDeJambes": "Protection de jambes",
"protectionDePieds": "Protection de pieds",
"protectionDorsale": "Protection dorsale",
"protectionObligatoire": "Protection obligatoire",
"prénomEtNom": "Prénom et nom",
"rechercher": "Rechercher",
"rechercher...": "Rechercher...",
"registration_one": "Inscription",
"registration_other": "Inscriptions",
"renouveler": "Renouveler",
"renouvellementDeLaffiliation": "Renouvellement de l'affiliation",
"result_one": "Résultat",
"result_other": "Résultats",
"retouràLaListeDeMembres": "Retour à la liste de membres",
"role": "Rôle",
"role.membre": "Membre",
"role.membreDuBureau": "Membre du bureau",
"role.président": "Président",
"role.secrétaire": "Secrétaire",
"role.trésorier": "Trésorier",
"role.vise-président": "Vise-Président",
"role.vise-secrétaire": "Vise-Secrétaire",
"role.vise-trésorier": "Vise-Trésorier",
"saison": "Saison",
"sans": "Sans",
"secrétariatsDeLice": "Secrétariats de lice",
"selectionner...": "Sélectionner...",
"shield.buckler": "Bocle",
"shield.none": "$t(sans) / $t(nonDéfinie)",
"shield.round": "Rond",
"shield.standard": "Standard",
"shield.teardrop": "Larme",
"siDisponiblePourLaCatégorieDages": "Si disponible pour la catégorie d'ages",
"siretOuRna": "SIRET ou RNA",
"stats": "Statistiques",
"statue": "Statue",
"status": "Status",
"statuts": "Statuts",
"supprimerLeClub": "Supprimer le club",
"supprimerLeClub.msg": "Êtes-vous sûr de vouloir supprimer ce club ?",
"supprimerLeCompte": "Supprimer le compte",
"supprimerLeCompte.msg": "Êtes-vous sûr de vouloir supprimer ce compte ?",
"sword.none": "$t(sans) / $t(nonDéfinie)",
"sword.oneHand": "Épée une main",
"sword.saber": "Sabre",
"sword.twoHand": "Épée deux mains",
"sélectionEnéquipeDeFrance": "Sélection en équipe de France",
"sélectionner...": "Sélectionner...",
"toast.edit.error": "Échec de l'enregistrement des modifications",
"toast.edit.pending": "Enregistrement des modifications en cours",
"toast.edit.success": "Modifications enregistrées avec succès 🎉",
"toast.licence.bulk.pay.error": "Échec du marquage des licences comme payées",
"toast.licence.bulk.pay.pending": "Marquage des licences comme payées en cours",
"toast.licence.bulk.pay.success": "Licences marquées comme payées avec succès 🎉",
"toast.licence.bulk.valid.error": "Échec de la validation des licences",
"toast.licence.bulk.valid.pending": "Validation des licences en cours",
"toast.licence.bulk.valid.success": "Licences validées avec succès 🎉",
"toast.licence.order.error": "Échec de le création de la commande",
"toast.licence.order.pending": "Création de la commande en cours",
"toast.licence.order.success": "Commande créée avec succès 🎉",
"trie": "Trie",
"téléchargerLexcelDesMembres": "Télécharger l'Excel des membres",
"téléchargerLexcelDesMembres.info": "À utiliser comme template pour mettre à jour les informations",
"téléchargéeLaLicence": "Téléchargée la licence",
"validationDeLaLicence": "Validation de la licence",
"validationDesLicences": "Validation des licences",
"validerDesLicences": "Valider des licences",
"validerLePayement_one": "Valider le payement de la licence sélectionnée",
"validerLePayement_other": "Valider le payement des {{count}} licences sélectionnées",
"validerLePayement_zero": "$t(validerLePayement_other)",
"validerLicence.msg_one": "Êtes-vous sûr de vouloir valider la licence ?",
"validerLicence.msg_other": "Êtes-vous sûr de vouloir valider les {{count}} licences ?",
"validerLicence.msg_zero": "$t(validerLicence.msg_other)",
"validerLicence_one": "Valider la licence sélectionnée",
"validerLicence_other": "Valider les {{count}} licences sélectionnées",
"validerLicence_zero": "$t(validerLicence_other)",
"validée": "Validée",
"veuillezAssocierChaqueChampàUneColonneDuFichier": "Veuillez associer chaque champ à une colonne du fichier",
"veuillezIndiqueràQuelle": "Veuillez indiquer à quelle ligne se trouvent les en-têtes dans le fichier",
"veuillezMapperLesColonnesSuivantes": "Veuillez mapper les colonnes suivantes",
"voir/modifierLesParticipants": "Voir/Modifier les participants",
"voirLesStatues": "Voir les statues",
"vousNêtesPasEncoreInscrit": "Vous n'êtes pas encore inscrit ou votre inscription n'a pas encore été rentrée sur l'intranet",
"à": "à",
"étatDeLaDemande": "État de la demande"
}

View File

@@ -0,0 +1,75 @@
{
"--sélectionnerUnClub--": "--Sélectionner un club--",
"--sélectionnerUnCombattant--": "--Sélectionner un combattant--",
"--sélectionnerUneCatégorie--": "--Sélectionner une catégorie--",
"1er": "Médaille d'or",
"2eme": "Médaille d'argent",
"3eme": "Médaille de bronze",
"abs.": "abs.",
"adversaire": "Adversaire",
"aujourdhuià": "Aujourd'hui à {{time}}",
"avanthierà": "Avant-hier à {{time}}",
"back": "« retour",
"bleu": "Bleu",
"catégorie": "Catégorie",
"chargement": "Chargement",
"classement": "Classement",
"classementClub": "Classement club",
"classementDesClub": "Classement des clubs",
"classementFinal": "Classement final",
"club": "Club",
"combattant": "Combattant",
"combattants": "Combattants",
"date": "Date",
"disc.": "disc.",
"défaites": "Défaites",
"erreurDeChargementDeLaListe": "Erreur de chargement de la liste",
"erreurDeChargementDeLaPoule": "Erreur de chargement de la poule",
"erreurDeChargementDesCatégories": "Erreur de chargement des catégories",
"erreurDeChargementDesClubs": "Erreur de chargement des clubs",
"erreurDeChargementDesCombattants": "Erreur de chargement des combattants",
"erreurDeChargementDuClub": "Erreur de chargement du club",
"erreurDeChargementDuCombattant": "Erreur de chargement du combattant",
"for.": "for.",
"hierà": "Hier à {{time}}",
"info": "Info",
"listeDesCombattants": "Liste des combattants",
"listeDesMatchs": "Liste des matchs",
"listeDesMembres": "Liste des membres",
"listeDesMenbres": "Liste des menbres",
"nom": "Nom",
"nomPrénom": "Nom Prénom",
"nombreDeMatchDisputé2": "Nombre de match disputé : {{nb}}",
"nombreDeVictoires2": "Nombre de victoires : {{nb}} ",
"nombreDinscris": "Nombre d'inscris",
"nombreDinscris2": "Nombre d'inscris : {{nb}}",
"parCatégorie": "Par catégorie",
"parClub": "Par club",
"parCombattant": "Par combattant",
"place": "Place",
"pointsMarqués": "Points marqués",
"pointsMarqués2": "Points marqués : {{nb}}",
"pointsReçus": "Points reçus",
"pointsReçus2": "Points reçus : {{nb}}",
"poule": "Poule",
"ratio": "Ratio",
"ratioDePointsMoyen2": "Ratio de points moyen : {{nb}}",
"ratioDeVictoiresMoyen2": "Ratio de victoires moyen : {{nb}}",
"ratioDuScore2": "Ratio du score (point marqué / point reçu): {{nb}}",
"ratioPoints": "Ratio points",
"ratioVictoires": "Ratio victoires",
"ratiosPoints": "Ratios points",
"rechercheParCatégorie": "Recherche par catégorie",
"rechercheParClub": "Recherche par club",
"rechercheParCombattant": "Recherche par combattant",
"rouge": "Rouge",
"résultatDeLaCompétition": "Résultat de la compétition",
"score": "Score",
"scores": "Scores",
"statistique": "Statistique",
"tauxDeVictoire2": "Taux de victoire : {{nb}}% ({{victoires}} sur {{matchs}})",
"tournois": "Tournois",
"tousLesCombattants": "Tous les combattants",
"victoire": "Victoire",
"victoires": "Victoires"
}

View File

@@ -0,0 +1,84 @@
class ProcessorDTMF extends AudioWorkletProcessor {
constructor() {
super();
this.sampleRate = sampleRate;
this.symbolDuration = 0.03; // 50 ms par symbole
this.samplesPerSymbol = Math.floor(this.sampleRate * this.symbolDuration);
this.encodeLowPrio = [];
this.symbolSamples = [];
this.lastBlackStep = 0;
this.port.onmessage = (e) => {
if (e.data.type === 'encode') {
this.symbolSamples.push(...this.encodeSymbols(e.data.symbols));
this.symbolSamples.push(...this.encodeBlack(this.sampleRate * 0.02));
}
if (e.data.type === 'encodeLowPrio') {
this.encodeLowPrio.push(e.data.symbols);
}
};
}
dtmfFrequencies = [
[697, 770, 852, 941], // Fréquences basses
[1209, 1336, 1477, 1633] // Fréquences hautes
];
encodeSymbols(symbols) {
const samples = [];
for (const symbol of symbols) {
const lf = this.dtmfFrequencies[0][symbol % 4]; // Fréquence basse
const hf = this.dtmfFrequencies[1][Math.floor(symbol / 4)]; // Fréquence haute
// console.log(`Symbol: ${symbol}, LF: ${lf} Hz, HF: ${hf} Hz`);
for (let i = 0; i < this.samplesPerSymbol; i++) {
const t = i / this.sampleRate;
const t2 = (this.lastBlackStep + i) / this.sampleRate;
samples.push(0.5 * Math.sin(2 * Math.PI * lf * t) + 0.5 * Math.sin(2 * Math.PI * hf * t) // Signal DTMF
+ Math.sin(2 * Math.PI * 150 * t2) * (0.0625 * (Math.sin(2 * Math.PI * 0.5 * t2) + 1))); // Ajouter un signal à 150 Hz pour le "black"
}
this.lastBlackStep += this.samplesPerSymbol;
// ajouter un silence de 10 ms entre les symboles
samples.push(...this.encodeBlack(this.sampleRate * 0.01)); // Silence
}
return samples;
}
encodeBlack(size) {
const samples = [];
for (let i = 0; i < size; i++) {
const t = (this.lastBlackStep + i) / this.sampleRate;
samples.push(Math.sin(2 * Math.PI * 150 * t) * (0.0625 * (Math.sin(2 * Math.PI * 0.5 * t) + 1))); // Signal à 350 Hz pour le "black"
}
this.lastBlackStep += size;
this.lastBlackStep %= this.sampleRate * 2; // Réinitialiser tous les 2 secondes pour éviter les débordements
return samples;
}
process(inputs, outputs, parameters) {
const output = outputs[0]; // output est un tableau de canaux (ex: [Float32Array, ...])
const channelData = output[0]; // Accéder au premier canal (mono)
if (this.symbolSamples.length === 0 && this.encodeLowPrio.length > 0) {
this.symbolSamples.push(...this.encodeSymbols(this.encodeLowPrio.shift()));
this.symbolSamples.push(...this.encodeBlack(this.sampleRate * 0.02));
}
if (this.symbolSamples.length === 0) {
const samples = this.encodeBlack(channelData.length)
for (let i = 0; i < channelData.length; i++) {
channelData[i] = samples[i] || 0;
}
return true;
}
for (let i = 0; i < channelData.length; i++) {
channelData[i] = this.symbolSamples.shift() || 0; // Prendre le prochain échantillon ou 0 si vide
}
return true;
}
}
registerProcessor('dtmf-processor', ProcessorDTMF);

View File

@@ -14,8 +14,8 @@ import {ClubRoot, getClubChildren} from "./pages/club/ClubRoot.jsx";
import {DemandeAff, DemandeAffOk} from "./pages/DemandeAff.jsx";
import {MePage} from "./pages/MePage.jsx";
import {CompetitionRoot, getCompetitionChildren} from "./pages/competition/CompetitionRoot.jsx";
import {FallingLines} from "react-loader-spinner";
import {getResultChildren, ResultRoot} from "./pages/result/ResultRoot.jsx";
import {useTranslation} from "react-i18next";
const router = createBrowserRouter([
{
@@ -79,10 +79,12 @@ const router = createBrowserRouter([
function GetCompetitionMangerLazy() {
const CMLazy = lazy(() => import('./pages/competition/editor/CompetitionManagerRoot.jsx'))
const {t} = useTranslation();
return <Suspense
fallback={<div>
<h1>Compétition manager</h1>
<p>Chargement...</p>
<h1>{t("comp_manage")}</h1>
<p>{t("loading")}</p>
</div>}>
<CMLazy/>
</Suspense>
@@ -144,6 +146,7 @@ function Root() {
function ReAuthMsg() {
const {is_authenticated} = useAuth()
const location = useLocation()
const {t} = useTranslation();
const notAuthPaths = [
/^\/$/s,
@@ -161,15 +164,14 @@ function ReAuthMsg() {
}}>
<div className="card">
<div className="card-header">
<h5>Session expirée</h5>
<h5>{t("outdated_session.title")}</h5>
</div>
<div className="card-body">
<p className="card-text">Votre session a expirée, veuillez vous reconnecter pour continuer à
utiliser l'application.</p>
<p className="card-text">{t("outdated_session.message")}</p>
</div>
<div className="card-footer">
<button className="btn btn-primary" onClick={() => login()} style={{marginRight: "0.5em"}}>Se reconnecter</button>
<a className="btn btn-secondary" href="/">Accueil</a>
<button className="btn btn-primary" onClick={() => login()} style={{marginRight: "0.5em"}}>{t("outdated_session.login_button")}</button>
<a className="btn btn-secondary" href="/">{t("nav.home")}</a>
</div>
</div>
</div>

View File

@@ -1,14 +0,0 @@
import Keycloak from "keycloak-js";
const vite_url = import.meta.env.VITE_URL;
const client_id = import.meta.env.VITE_CLIENT_ID;
const keycloak = new Keycloak({
url: `${vite_url}/auth-api`,
realm: "safca",
clientId: client_id,
});
export default keycloak;

View File

@@ -0,0 +1,115 @@
const CategoryPreset = [
{
name: "Épée",
sword: "ONE_HAND",
shield: "NONE",
categories: [
{categorie: "MINI_POUSSIN", roundDuration: 30000, pauseDuration: 60000},
{categorie: "POUSSIN", roundDuration: 30000, pauseDuration: 60000},
{categorie: "BENJAMIN", roundDuration: 45000, pauseDuration: 60000},
{categorie: "MINIME", roundDuration: 45000, pauseDuration: 60000},
{categorie: "CADET", roundDuration: 60000, pauseDuration: 60000},
],
mandatoryProtection1: 45,
mandatoryProtection2: 13
},
{
name: "Épée Bouclier",
sword: "ONE_HAND",
shield: "STANDARD",
categories: [
{categorie: "SUPER_MINI", roundDuration: 30000, pauseDuration: 60000},
{categorie: "MINI_POUSSIN", roundDuration: 30000, pauseDuration: 60000},
{categorie: "POUSSIN", roundDuration: 45000, pauseDuration: 60000},
{categorie: "BENJAMIN", roundDuration: 45000, pauseDuration: 60000},
{categorie: "MINIME", roundDuration: 60000, pauseDuration: 60000},
{categorie: "CADET", roundDuration: 60000, pauseDuration: 60000},
{categorie: "JUNIOR", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR2", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN2", roundDuration: 60000, pauseDuration: 60000},
],
mandatoryProtection1: 45,
mandatoryProtection2: 13
},
{
name: "Épée Bocle",
sword: "ONE_HAND",
shield: "BUCKLER",
categories: [
{categorie: "POUSSIN", roundDuration: 45000, pauseDuration: 60000},
{categorie: "BENJAMIN", roundDuration: 45000, pauseDuration: 60000},
{categorie: "MINIME", roundDuration: 60000, pauseDuration: 60000},
{categorie: "CADET", roundDuration: 60000, pauseDuration: 60000},
{categorie: "JUNIOR", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR2", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN2", roundDuration: 60000, pauseDuration: 60000},
],
mandatoryProtection1: 45,
mandatoryProtection2: 13
},
{
name: "Épée Longue",
sword: "TWO_HAND",
shield: "NONE",
categories: [
{categorie: "JUNIOR", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR2", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN2", roundDuration: 60000, pauseDuration: 60000},
],
mandatoryProtection1: 61,
mandatoryProtection2: 29
},
{
name: "Sabre",
sword: "SABER",
shield: "NONE",
categories: [
{categorie: "MINIME", roundDuration: 60000, pauseDuration: 60000},
{categorie: "CADET", roundDuration: 60000, pauseDuration: 60000},
{categorie: "JUNIOR", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR2", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN2", roundDuration: 60000, pauseDuration: 60000},
],
mandatoryProtection1: 47,
mandatoryProtection2: 47
},
{
name: "Sabre Bocle",
sword: "SABER",
shield: "BUCKLER",
categories: [
{categorie: "MINIME", roundDuration: 60000, pauseDuration: 60000},
{categorie: "CADET", roundDuration: 60000, pauseDuration: 60000},
{categorie: "JUNIOR", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "SENIOR2", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN1", roundDuration: 60000, pauseDuration: 60000},
{categorie: "VETERAN2", roundDuration: 60000, pauseDuration: 60000},
],
mandatoryProtection1: 47,
mandatoryProtection2: 47
},
{
name: "Profight Léger",
sword: "ONE_HAND",
shield: "TEARDROP",
categories: [
{categorie: "SENIOR1", roundDuration: 120000, pauseDuration: 60000},
{categorie: "SENIOR2", roundDuration: 120000, pauseDuration: 60000},
{categorie: "VETERAN1", roundDuration: 120000, pauseDuration: 60000},
{categorie: "VETERAN2", roundDuration: 120000, pauseDuration: 60000},
],
mandatoryProtection1: 3647,
mandatoryProtection2: 3647
},
]
export default CategoryPreset;

Some files were not shown because too many files have changed in this diff Show More