fixing verification for both a team registration and a indivual turnament registration
This commit is contained in:
parent
06ba7fe85a
commit
1a4d1cf73c
4 changed files with 415 additions and 164 deletions
|
|
@ -177,8 +177,8 @@ impl TournamentSignupConfig {
|
|||
}
|
||||
|
||||
self.entry_fields = normalize_signup_fields(self.entry_fields);
|
||||
ensure_attendance_id_field(&mut self.entry_fields);
|
||||
self.participant_fields = normalize_signup_fields(self.participant_fields);
|
||||
ensure_attendance_field_for_mode(&mut self);
|
||||
|
||||
self
|
||||
}
|
||||
|
|
@ -218,26 +218,53 @@ fn normalize_signup_fields(mut fields: Vec<TournamentSignupField>) -> Vec<Tourna
|
|||
fields
|
||||
}
|
||||
|
||||
fn ensure_attendance_id_field(fields: &mut Vec<TournamentSignupField>) {
|
||||
fn remove_attendance_id_field(
|
||||
fields: &mut Vec<TournamentSignupField>,
|
||||
) -> Option<TournamentSignupField> {
|
||||
let mut attendance_index = None;
|
||||
for (index, field) in fields.iter_mut().enumerate() {
|
||||
for (index, field) in fields.iter().enumerate() {
|
||||
if field.id == ATTENDANCE_ID_FIELD_ID {
|
||||
attendance_index = Some(index);
|
||||
sanitize_attendance_id_field(field);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match attendance_index {
|
||||
Some(index) => {
|
||||
if index != 0 {
|
||||
let field = fields.remove(index);
|
||||
attendance_index.map(|index| {
|
||||
let mut field = fields.remove(index);
|
||||
sanitize_attendance_id_field(&mut field);
|
||||
field
|
||||
})
|
||||
}
|
||||
|
||||
fn insert_attendance_field_front(
|
||||
fields: &mut Vec<TournamentSignupField>,
|
||||
mut field: TournamentSignupField,
|
||||
) {
|
||||
sanitize_attendance_id_field(&mut field);
|
||||
fields.insert(0, field);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
fields.insert(0, default_attendance_id_field());
|
||||
}
|
||||
|
||||
fn ensure_attendance_field_for_mode(config: &mut TournamentSignupConfig) {
|
||||
let mut attendance = remove_attendance_id_field(&mut config.entry_fields)
|
||||
.or_else(|| remove_attendance_id_field(&mut config.participant_fields))
|
||||
.unwrap_or_else(default_attendance_id_field);
|
||||
|
||||
if config.mode == "team" {
|
||||
config
|
||||
.entry_fields
|
||||
.retain(|field| field.id != ATTENDANCE_ID_FIELD_ID);
|
||||
config
|
||||
.participant_fields
|
||||
.retain(|field| field.id != ATTENDANCE_ID_FIELD_ID);
|
||||
insert_attendance_field_front(&mut config.participant_fields, attendance);
|
||||
} else {
|
||||
config
|
||||
.entry_fields
|
||||
.retain(|field| field.id != ATTENDANCE_ID_FIELD_ID);
|
||||
config
|
||||
.participant_fields
|
||||
.retain(|field| field.id != ATTENDANCE_ID_FIELD_ID);
|
||||
insert_attendance_field_front(&mut config.entry_fields, attendance);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use rocket::serde::json::Json;
|
|||
use rocket::Route;
|
||||
use serde_json::{Map, Value};
|
||||
use sqlx::{Postgres, Transaction};
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
pub fn routes() -> Vec<Route> {
|
||||
rocket::routes![
|
||||
|
|
@ -1178,6 +1178,76 @@ pub async fn create_registration_by_slug(
|
|||
));
|
||||
}
|
||||
|
||||
let is_team = config.mode == "team";
|
||||
let mut lead_participant: Option<(i32, Person)> = None;
|
||||
|
||||
if is_team {
|
||||
entry_values.remove(ATTENDANCE_ID_FIELD_ID);
|
||||
let mut seen_attendance_ids: HashSet<i32> = HashSet::new();
|
||||
|
||||
for (index, values) in participant_values.iter_mut().enumerate() {
|
||||
let attendance_raw = values
|
||||
.get(ATTENDANCE_ID_FIELD_ID)
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.ok_or_else(|| {
|
||||
ApiError::bad_request(format!(
|
||||
"Spelare #{number}: ange deltagar-ID från närvarolistan.",
|
||||
number = index + 1
|
||||
))
|
||||
})?;
|
||||
|
||||
let attendance_id: i32 = attendance_raw.parse().map_err(|_| {
|
||||
ApiError::bad_request(format!(
|
||||
"Spelare #{number}: deltagar-ID måste vara ett heltal från närvarolistan.",
|
||||
number = index + 1
|
||||
))
|
||||
})?;
|
||||
|
||||
if !seen_attendance_ids.insert(attendance_id) {
|
||||
return Err(ApiError::bad_request(format!(
|
||||
"Spelare #{number}: deltagar-ID används redan av en annan spelare i laget.",
|
||||
number = index + 1
|
||||
)));
|
||||
}
|
||||
|
||||
let person = sqlx::query_as::<_, Person>(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
first_name,
|
||||
last_name,
|
||||
grade,
|
||||
parent_name,
|
||||
parent_phone_number,
|
||||
checked_in,
|
||||
inside,
|
||||
visitor,
|
||||
sleeping_spot
|
||||
FROM persons
|
||||
WHERE id = $1
|
||||
"#,
|
||||
)
|
||||
.bind(attendance_id)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::bad_request(format!(
|
||||
"Spelare #{number}: deltagar-ID:t finns inte i närvarolistan.",
|
||||
number = index + 1
|
||||
))
|
||||
})?;
|
||||
|
||||
values.insert(
|
||||
ATTENDANCE_ID_FIELD_ID.to_string(),
|
||||
attendance_id.to_string(),
|
||||
);
|
||||
|
||||
if lead_participant.is_none() {
|
||||
lead_participant = Some((attendance_id, person.clone()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let attendance_id_value = entry_values
|
||||
.get(ATTENDANCE_ID_FIELD_ID)
|
||||
.map(|value| value.trim().to_string())
|
||||
|
|
@ -1212,13 +1282,18 @@ pub async fn create_registration_by_slug(
|
|||
ApiError::bad_request("Det angivna deltagar-ID:t finns inte i närvarolistan.")
|
||||
})?;
|
||||
|
||||
let canonical_attendance = attendance_id.to_string();
|
||||
entry_values.insert(
|
||||
ATTENDANCE_ID_FIELD_ID.to_string(),
|
||||
canonical_attendance.clone(),
|
||||
attendance_id.to_string(),
|
||||
);
|
||||
lead_participant = Some((attendance_id, person));
|
||||
}
|
||||
|
||||
let entry_label = build_entry_label(attendance_id, &person, &config, &entry_values);
|
||||
let (lead_attendance_id, lead_person) = lead_participant.ok_or_else(|| {
|
||||
ApiError::bad_request("Minst en deltagare med deltagar-ID krävs för anmälan.")
|
||||
})?;
|
||||
|
||||
let entry_label = build_entry_label(lead_attendance_id, &lead_person, &config, &entry_values);
|
||||
|
||||
for field in &config.entry_fields {
|
||||
if !field.unique {
|
||||
|
|
@ -1656,6 +1731,76 @@ pub async fn update_registration_by_slug(
|
|||
));
|
||||
}
|
||||
|
||||
let is_team = config.mode == "team";
|
||||
let mut lead_participant: Option<(i32, Person)> = None;
|
||||
|
||||
if is_team {
|
||||
entry_values.remove(ATTENDANCE_ID_FIELD_ID);
|
||||
let mut seen_attendance_ids: HashSet<i32> = HashSet::new();
|
||||
|
||||
for (index, values) in participant_values.iter_mut().enumerate() {
|
||||
let attendance_raw = values
|
||||
.get(ATTENDANCE_ID_FIELD_ID)
|
||||
.map(|value| value.trim().to_string())
|
||||
.filter(|value| !value.is_empty())
|
||||
.ok_or_else(|| {
|
||||
ApiError::bad_request(format!(
|
||||
"Spelare #{number}: ange deltagar-ID från närvarolistan.",
|
||||
number = index + 1
|
||||
))
|
||||
})?;
|
||||
|
||||
let attendance_id: i32 = attendance_raw.parse().map_err(|_| {
|
||||
ApiError::bad_request(format!(
|
||||
"Spelare #{number}: deltagar-ID måste vara ett heltal från närvarolistan.",
|
||||
number = index + 1
|
||||
))
|
||||
})?;
|
||||
|
||||
if !seen_attendance_ids.insert(attendance_id) {
|
||||
return Err(ApiError::bad_request(format!(
|
||||
"Spelare #{number}: deltagar-ID används redan av en annan spelare i laget.",
|
||||
number = index + 1
|
||||
)));
|
||||
}
|
||||
|
||||
let person = sqlx::query_as::<_, Person>(
|
||||
r#"
|
||||
SELECT
|
||||
id,
|
||||
first_name,
|
||||
last_name,
|
||||
grade,
|
||||
parent_name,
|
||||
parent_phone_number,
|
||||
checked_in,
|
||||
inside,
|
||||
visitor,
|
||||
sleeping_spot
|
||||
FROM persons
|
||||
WHERE id = $1
|
||||
"#,
|
||||
)
|
||||
.bind(attendance_id)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
ApiError::bad_request(format!(
|
||||
"Spelare #{number}: deltagar-ID:t finns inte i närvarolistan.",
|
||||
number = index + 1
|
||||
))
|
||||
})?;
|
||||
|
||||
values.insert(
|
||||
ATTENDANCE_ID_FIELD_ID.to_string(),
|
||||
attendance_id.to_string(),
|
||||
);
|
||||
|
||||
if lead_participant.is_none() {
|
||||
lead_participant = Some((attendance_id, person.clone()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let attendance_id_value = entry_values
|
||||
.get(ATTENDANCE_ID_FIELD_ID)
|
||||
.map(|value| value.trim().to_string())
|
||||
|
|
@ -1694,8 +1839,14 @@ pub async fn update_registration_by_slug(
|
|||
ATTENDANCE_ID_FIELD_ID.to_string(),
|
||||
attendance_id.to_string(),
|
||||
);
|
||||
lead_participant = Some((attendance_id, person));
|
||||
}
|
||||
|
||||
let entry_label = build_entry_label(attendance_id, &person, &config, &entry_values);
|
||||
let (lead_attendance_id, lead_person) = lead_participant.ok_or_else(|| {
|
||||
ApiError::bad_request("Minst en deltagare med deltagar-ID krävs för anmälan.")
|
||||
})?;
|
||||
|
||||
let entry_label = build_entry_label(lead_attendance_id, &lead_person, &config, &entry_values);
|
||||
|
||||
for field in &config.entry_fields {
|
||||
if !field.unique {
|
||||
|
|
|
|||
|
|
@ -130,15 +130,10 @@
|
|||
};
|
||||
}
|
||||
|
||||
function ensureAttendanceField(fields: SignupFieldForm[]): SignupFieldForm[] {
|
||||
let attendanceField: SignupFieldForm | null = null;
|
||||
const others: SignupFieldForm[] = [];
|
||||
|
||||
for (const field of fields) {
|
||||
if (isAttendanceField(field)) {
|
||||
const label = field.label.trim() || ATTENDANCE_FIELD_LABEL;
|
||||
const placeholder = field.placeholder.trim() || ATTENDANCE_FIELD_PLACEHOLDER;
|
||||
attendanceField = {
|
||||
function sanitizeAttendanceField(field?: SignupFieldForm | null): SignupFieldForm {
|
||||
const label = field?.label?.trim() || ATTENDANCE_FIELD_LABEL;
|
||||
const placeholder = field?.placeholder?.trim() || ATTENDANCE_FIELD_PLACEHOLDER;
|
||||
return {
|
||||
id: ATTENDANCE_FIELD_ID,
|
||||
label,
|
||||
field_type: 'text',
|
||||
|
|
@ -146,53 +141,96 @@
|
|||
placeholder,
|
||||
unique: true
|
||||
};
|
||||
}
|
||||
|
||||
function removeAttendanceField(fields: SignupFieldForm[]): {
|
||||
fields: SignupFieldForm[];
|
||||
attendance: SignupFieldForm | null;
|
||||
} {
|
||||
const index = fields.findIndex(isAttendanceField);
|
||||
if (index === -1) {
|
||||
return { fields: [...fields], attendance: null };
|
||||
}
|
||||
|
||||
const next = [...fields];
|
||||
const [field] = next.splice(index, 1);
|
||||
return { fields: next, attendance: sanitizeAttendanceField(field) };
|
||||
}
|
||||
|
||||
function ensureAttendancePlacement(signup: SignupConfigForm) {
|
||||
const entryResult = removeAttendanceField(signup.entry_fields);
|
||||
const participantResult = removeAttendanceField(signup.participant_fields);
|
||||
let attendance =
|
||||
entryResult.attendance ?? participantResult.attendance ?? createAttendanceField();
|
||||
|
||||
if (signup.mode === 'team') {
|
||||
const participantFields = [...participantResult.fields];
|
||||
if (!participantFields.some((field) => !isAttendanceField(field))) {
|
||||
participantFields.push(createSignupField('Spelarinfo'));
|
||||
}
|
||||
signup.entry_fields = [...entryResult.fields];
|
||||
signup.participant_fields = [sanitizeAttendanceField(attendance), ...participantFields];
|
||||
} else {
|
||||
others.push(field);
|
||||
const entryFields = [...entryResult.fields];
|
||||
if (!entryFields.some((field) => !isAttendanceField(field))) {
|
||||
entryFields.push(createSignupField('Spelarnamn'));
|
||||
}
|
||||
signup.entry_fields = [sanitizeAttendanceField(attendance), ...entryFields];
|
||||
signup.participant_fields = participantResult.fields.filter(
|
||||
(field) => !isAttendanceField(field)
|
||||
);
|
||||
}
|
||||
|
||||
if (!attendanceField) {
|
||||
attendanceField = createAttendanceField();
|
||||
const firstEntry = signup.entry_fields.find((field) => !isAttendanceField(field));
|
||||
if (firstEntry) {
|
||||
const trimmed = firstEntry.placeholder.trim();
|
||||
firstEntry.placeholder = trimmed || (signup.mode === 'team' ? 'Lag namn' : 'Spelarnamn');
|
||||
if (signup.mode === 'team' && firstEntry.label.trim().length === 0) {
|
||||
firstEntry.label = 'Lag';
|
||||
}
|
||||
}
|
||||
|
||||
return [attendanceField, ...others];
|
||||
}
|
||||
|
||||
function createDefaultSignup(): SignupConfigForm {
|
||||
return {
|
||||
const signup: SignupConfigForm = {
|
||||
mode: 'solo',
|
||||
team_size: { min: 1, max: 1 },
|
||||
entry_fields: ensureAttendanceField([createSignupField('Lag / spelarnamn')]),
|
||||
entry_fields: [createSignupField('Spelarnamn')],
|
||||
participant_fields: []
|
||||
};
|
||||
ensureAttendancePlacement(signup);
|
||||
return signup;
|
||||
}
|
||||
|
||||
function cloneSignupConfig(config: TournamentSignupConfig | null | undefined): SignupConfigForm {
|
||||
if (!config) return createDefaultSignup();
|
||||
let entry_fields = ensureAttendanceField((config.entry_fields ?? []).map(cloneSignupField));
|
||||
let participant_fields = (config.participant_fields ?? []).map(cloneSignupField);
|
||||
if (entry_fields.length === 1) {
|
||||
entry_fields = ensureAttendanceField([
|
||||
...entry_fields,
|
||||
createSignupField('Lag / spelarnamn')
|
||||
]);
|
||||
}
|
||||
if (participant_fields.length === 0) {
|
||||
participant_fields.push(createSignupField('Spelare'));
|
||||
}
|
||||
const mode = config.mode === 'team' ? 'team' : 'solo';
|
||||
if (mode === 'solo') {
|
||||
participant_fields = [];
|
||||
}
|
||||
return {
|
||||
mode,
|
||||
|
||||
const signup: SignupConfigForm = {
|
||||
mode: config.mode === 'team' ? 'team' : 'solo',
|
||||
team_size: {
|
||||
min: config.team_size?.min ?? 1,
|
||||
max: config.team_size?.max ?? 1
|
||||
},
|
||||
entry_fields,
|
||||
participant_fields
|
||||
entry_fields: (config.entry_fields ?? []).map(cloneSignupField),
|
||||
participant_fields: (config.participant_fields ?? []).map(cloneSignupField)
|
||||
};
|
||||
|
||||
ensureAttendancePlacement(signup);
|
||||
|
||||
if (
|
||||
signup.mode === 'team' &&
|
||||
!signup.participant_fields.some((field) => !isAttendanceField(field))
|
||||
) {
|
||||
signup.participant_fields = [...signup.participant_fields, createSignupField('Spelarinfo')];
|
||||
ensureAttendancePlacement(signup);
|
||||
}
|
||||
|
||||
if (signup.mode !== 'team' && !signup.entry_fields.some((field) => !isAttendanceField(field))) {
|
||||
signup.entry_fields = [...signup.entry_fields, createSignupField('Spelarnamn')];
|
||||
ensureAttendancePlacement(signup);
|
||||
}
|
||||
|
||||
return signup;
|
||||
}
|
||||
|
||||
function signupFieldKey(index: number, field?: SignupFieldForm) {
|
||||
|
|
@ -201,17 +239,15 @@
|
|||
}
|
||||
|
||||
function addEntryField(form: TournamentForm) {
|
||||
form.signup.entry_fields = ensureAttendanceField([
|
||||
...form.signup.entry_fields,
|
||||
createSignupField('Nytt fält')
|
||||
]);
|
||||
form.signup.entry_fields = [...form.signup.entry_fields, createSignupField('Nytt fält')];
|
||||
ensureAttendancePlacement(form.signup);
|
||||
}
|
||||
|
||||
function removeEntryField(form: TournamentForm, index: number) {
|
||||
const target = form.signup.entry_fields[index];
|
||||
if (!target || isAttendanceField(target)) return;
|
||||
const remaining = form.signup.entry_fields.filter((_, idx) => idx !== index);
|
||||
form.signup.entry_fields = ensureAttendanceField(remaining);
|
||||
form.signup.entry_fields = form.signup.entry_fields.filter((_, idx) => idx !== index);
|
||||
ensureAttendancePlacement(form.signup);
|
||||
}
|
||||
|
||||
function addParticipantField(form: TournamentForm) {
|
||||
|
|
@ -219,13 +255,16 @@
|
|||
...form.signup.participant_fields,
|
||||
createSignupField('Spelarinfo')
|
||||
];
|
||||
ensureAttendancePlacement(form.signup);
|
||||
}
|
||||
|
||||
function removeParticipantField(form: TournamentForm, index: number) {
|
||||
if (form.signup.participant_fields.length <= 1) return;
|
||||
const target = form.signup.participant_fields[index];
|
||||
if (!target || isAttendanceField(target)) return;
|
||||
form.signup.participant_fields = form.signup.participant_fields.filter(
|
||||
(_, idx) => idx !== index
|
||||
);
|
||||
ensureAttendancePlacement(form.signup);
|
||||
}
|
||||
|
||||
function setSignupMode(form: TournamentForm, mode: 'solo' | 'team') {
|
||||
|
|
@ -243,6 +282,7 @@
|
|||
form.signup.participant_fields = [createSignupField('Spelare')];
|
||||
}
|
||||
}
|
||||
ensureAttendancePlacement(form.signup);
|
||||
}
|
||||
|
||||
function setTeamSize(form: TournamentForm, key: 'min' | 'max', value: string) {
|
||||
|
|
@ -362,8 +402,17 @@
|
|||
max = 1;
|
||||
}
|
||||
|
||||
const entry_fields = ensureAttendanceField(signup.entry_fields).map(normalizeField);
|
||||
const participant_fields = mode === 'team' ? signup.participant_fields.map(normalizeField) : [];
|
||||
const draft: SignupConfigForm = {
|
||||
mode,
|
||||
team_size: { min, max },
|
||||
entry_fields: signup.entry_fields.map((field) => ({ ...field })),
|
||||
participant_fields: signup.participant_fields.map((field) => ({ ...field }))
|
||||
};
|
||||
|
||||
ensureAttendancePlacement(draft);
|
||||
|
||||
const entry_fields = draft.entry_fields.map(normalizeField);
|
||||
const participant_fields = mode === 'team' ? draft.participant_fields.map(normalizeField) : [];
|
||||
|
||||
return {
|
||||
mode,
|
||||
|
|
|
|||
|
|
@ -188,6 +188,36 @@
|
|||
signup.error = '';
|
||||
signup.success = '';
|
||||
|
||||
const isTeam = signupConfig.mode === 'team';
|
||||
|
||||
if (isTeam) {
|
||||
if (signup.participants.length < minParticipants) {
|
||||
signup.error =
|
||||
minParticipants === 1
|
||||
? 'Lägg till minst en spelare.'
|
||||
: `Lägg till minst ${minParticipants} spelare.`;
|
||||
return;
|
||||
}
|
||||
|
||||
for (let index = 0; index < signup.participants.length; index += 1) {
|
||||
const participant = signup.participants[index];
|
||||
const raw = (participant[ATTENDANCE_FIELD_ID] ?? '').trim();
|
||||
if (!raw) {
|
||||
signup.error = `Spelare ${index + 1}: ange deltagar-ID från närvarolistan.`;
|
||||
return;
|
||||
}
|
||||
if (!/^\d+$/.test(raw)) {
|
||||
signup.error = `Spelare ${index + 1}: deltagar-ID får endast innehålla siffror.`;
|
||||
return;
|
||||
}
|
||||
const numeric = Number.parseInt(raw, 10);
|
||||
if (!Number.isFinite(numeric) || numeric <= 0) {
|
||||
signup.error = `Spelare ${index + 1}: ange ett giltigt deltagar-ID.`;
|
||||
return;
|
||||
}
|
||||
participant[ATTENDANCE_FIELD_ID] = String(numeric);
|
||||
}
|
||||
} else {
|
||||
const attendanceValue = (signup.entry[ATTENDANCE_FIELD_ID] ?? '').trim();
|
||||
if (!attendanceValue) {
|
||||
signup.error = 'Ange ditt deltagar-ID från närvarolistan.';
|
||||
|
|
@ -203,19 +233,13 @@
|
|||
return;
|
||||
}
|
||||
signup.entry[ATTENDANCE_FIELD_ID] = String(attendanceNumeric);
|
||||
}
|
||||
|
||||
if (signup.participants.length === 0) {
|
||||
signup.error = 'Lägg till minst en spelare.';
|
||||
return;
|
||||
}
|
||||
|
||||
if (signupConfig.mode === 'team') {
|
||||
if (signup.participants.length < minParticipants) {
|
||||
signup.error = `Lägg till minst ${minParticipants} spelare.`;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
signup.submitting = true;
|
||||
try {
|
||||
const payload = buildSignupPayload();
|
||||
|
|
|
|||
Loading…
Reference in a new issue