From 11141c8667a5c471048bae79b1bcb1cb0e43d0c9 Mon Sep 17 00:00:00 2001 From: Kevin Matthews Date: Wed, 6 Dec 2023 12:21:48 -0800 Subject: [PATCH 1/6] prevent accepting too many registrations at once --- .../components/RegistrationActions.jsx | 25 ++++++++++++------- .../RegistrationAdministrationList.jsx | 11 ++++---- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/Frontend/src/pages/registration_administration/components/RegistrationActions.jsx b/Frontend/src/pages/registration_administration/components/RegistrationActions.jsx index a86d3dfd..920c8836 100644 --- a/Frontend/src/pages/registration_administration/components/RegistrationActions.jsx +++ b/Frontend/src/pages/registration_administration/components/RegistrationActions.jsx @@ -31,6 +31,7 @@ export default function RegistrationActions({ partitionedSelected, refresh, registrations, + spotsRemaining, }) { const { competitionInfo } = useContext(CompetitionContext) const { isOrganizerOrDelegate } = useContext(PermissionsContext) @@ -57,6 +58,20 @@ export default function RegistrationActions({ }, }) + const attemptToApprove = () => { + const idsToAccept = [...pending, ...cancelled, ...waiting] + if (idsToAccept.length > spotsRemaining) { + setMessage( + `Accepting all these registrations would go over the competitor limit by ${ + idsToAccept.length - spotsRemaining + }`, + 'negative' + ) + } else { + changeStatus(idsToAccept, 'accepted') + } + } + const changeStatus = async (attendees, status) => { attendees.forEach((attendee) => { updateRegistrationMutation( @@ -112,15 +127,7 @@ export default function RegistrationActions({ {isOrganizerOrDelegate && ( <> {anyApprovable && ( - )} diff --git a/Frontend/src/pages/registration_administration/components/RegistrationAdministrationList.jsx b/Frontend/src/pages/registration_administration/components/RegistrationAdministrationList.jsx index 103bf814..64ac9e35 100644 --- a/Frontend/src/pages/registration_administration/components/RegistrationAdministrationList.jsx +++ b/Frontend/src/pages/registration_administration/components/RegistrationAdministrationList.jsx @@ -120,9 +120,9 @@ export default function RegistrationAdministrationList() { // some sticky/floating bar somewhere with totals/info would be better // than putting this in the table headers which scroll out of sight - const spotsRemaining = `; ${ - competitionInfo?.competitor_limit - accepted?.length - } spot(s) remaining` + const spotsRemaining = + (competitionInfo.competitor_limit ?? Infinity) - accepted.length + const spotsRemainingText = `; ${spotsRemaining} spot(s) remaining` return isLoading ? ( @@ -143,7 +143,7 @@ export default function RegistrationAdministrationList() { {competitionInfo.competitor_limit && ( <> {`/${competitionInfo.competitor_limit}`} - {spotsRemaining} + {spotsRemainingText} )} ) @@ -158,7 +158,7 @@ export default function RegistrationAdministrationList() {
Waitlisted registrations ({waiting.length} - {competitionInfo.competitor_limit && spotsRemaining}) + {competitionInfo.competitor_limit && spotsRemainingText})
) From c2f4545ddf0e44e0286879414b94dd2c45b5587c Mon Sep 17 00:00:00 2001 From: Finn Ickler Date: Thu, 7 Dec 2023 15:26:14 +0100 Subject: [PATCH 2/6] fix backend check? --- app/controllers/registration_controller.rb | 12 +++++++++++- app/lib/redis.rb | 17 +++++++++++++++++ app/models/registration.rb | 19 ++++++++++++++++++- app/services/registration_checker.rb | 2 +- spec/models/registration_spec.rb | 4 ++-- 5 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 app/lib/redis.rb diff --git a/app/controllers/registration_controller.rb b/app/controllers/registration_controller.rb index ff5c2ef4..a1ba3725 100644 --- a/app/controllers/registration_controller.rb +++ b/app/controllers/registration_controller.rb @@ -93,7 +93,13 @@ def update begin registration = Registration.find("#{@competition_id}-#{@user_id}") + old_status = registration.competing_status updated_registration = registration.update_competing_lane!({ status: status, comment: comment, event_ids: event_ids, admin_comment: admin_comment, guests: guests }) + if old_status == "accepted" && status != "accepted" + Registration.decrement_competitors_count(@competition_id) + elsif old_status != "accepted" && status == "accepted" + Registration.increment_competitors_count(@competition_id) + end render json: { status: 'ok', registration: { user_id: updated_registration['user_id'], guests: updated_registration.guests, @@ -194,12 +200,16 @@ def list_admin def import file = params.require(:csv_data) + competition_id = params.require(:competition_id) content = File.read(file) if CsvImport.valid?(content) registrations = CSV.parse(File.read(file), headers: true).map do |row| - CsvImport.parse_row_to_registration(row.to_h, params[:competition_id]) + CsvImport.parse_row_to_registration(row.to_h, competition_id) end Registration.import(registrations) + + Rails.cache.invalidate("#{competition_id}-accepted-count") + render json: { status: 'Successfully imported registration' } else render json: { error: 'Invalid csv' }, status: :internal_server_error diff --git a/app/lib/redis.rb b/app/lib/redis.rb new file mode 100644 index 00000000..d1be7b77 --- /dev/null +++ b/app/lib/redis.rb @@ -0,0 +1,17 @@ +module Redis + def increment_or_initialize(key, &block) + if Rails.cache.exist?(key) + Rails.cache.increment(key) + else + Rails.cache.write(key, block.call, expires_in: 60.minutes, raw: true) + end + end + + def decrement_or_initialize(key, &block) + if Rails.cache.exist?(key) + Rails.cache.decrement(key) + else + Rails.cache.write(key, block.call, expires_in: 60.minutes, raw: true) + end + end +end \ No newline at end of file diff --git a/app/models/registration.rb b/app/models/registration.rb index 54a143ec..5b78e281 100644 --- a/app/models/registration.rb +++ b/app/models/registration.rb @@ -17,11 +17,28 @@ class Registration # Validations validate :is_competing_consistency - # NOTE: There are more efficient ways to do this, see: https://github.com/thewca/wca-registration/issues/330 def self.accepted_competitors(competition_id) where(competition_id: competition_id, is_competing: true).count end + def self.accepted_competitors_count(competition_id) + Rails.cache.fetch("#{competition_id}-accepted-count", expires_in: 60.minutes, raw: true) do + self.accepted_competitors(competition_id) + end + end + + def self.decrement_competitors_count(competition_id) + decrement_or_initialize("#{competition_id}-accepted-count") do + self.accepted_competitors(competition_id) + end + end + + def self.increment_competitors_count(competition_id) + increment_or_initialize("#{competition_id}-accepted-count") do + self.accepted_competitors(competition_id) + end + end + # Returns all event ids irrespective of registration status def event_ids lanes.filter_map { |x| x.lane_details['event_details'].pluck('event_id') if x.lane_name == 'competing' }[0] diff --git a/app/services/registration_checker.rb b/app/services/registration_checker.rb index 16f39df5..0c370470 100644 --- a/app/services/registration_checker.rb +++ b/app/services/registration_checker.rb @@ -107,7 +107,7 @@ def validate_update_status! raise RegistrationError.new(:unprocessable_entity, ErrorCodes::INVALID_REQUEST_DATA) unless Registration::REGISTRATION_STATES.include?(new_status) raise RegistrationError.new(:forbidden, ErrorCodes::COMPETITOR_LIMIT_REACHED) if - new_status == 'accepted' && Registration.accepted_competitors(@competition_info.competition_id) >= @competition_info.competitor_limit + new_status == 'accepted' && Registration.accepted_competitors_count(@competition_info.competition_id) == @competition_info.competitor_limit # Organizers can make any status change they want to - no checks performed diff --git a/spec/models/registration_spec.rb b/spec/models/registration_spec.rb index dd7de803..e38a75d6 100644 --- a/spec/models/registration_spec.rb +++ b/spec/models/registration_spec.rb @@ -56,7 +56,7 @@ FactoryBot.create_list(:registration, 3, registration_status: 'accepted') FactoryBot.create_list(:registration, 3, registration_status: 'accepted', competition_id: target_comp) - comp_registration_count = Registration.accepted_competitors(target_comp) + comp_registration_count = Registration.accepted_competitors_count(target_comp) expect(comp_registration_count).to eq(3) end @@ -67,7 +67,7 @@ FactoryBot.create_list(:registration, 3, registration_status: 'accepted', competition_id: target_comp) FactoryBot.create_list(:registration, 3, registration_status: 'cancelled', competition_id: target_comp) - comp_registration_count = Registration.accepted_competitors(target_comp) + comp_registration_count = Registration.accepted_competitors_count(target_comp) expect(comp_registration_count).to eq(3) end From 07a5f349b4440df2543e51138d1ede5f39e8583a Mon Sep 17 00:00:00 2001 From: Finn Ickler Date: Thu, 7 Dec 2023 19:02:18 +0100 Subject: [PATCH 3/6] run rubocop --- app/controllers/registration_controller.rb | 4 ++-- app/lib/redis.rb | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/controllers/registration_controller.rb b/app/controllers/registration_controller.rb index a1ba3725..7a1a12d0 100644 --- a/app/controllers/registration_controller.rb +++ b/app/controllers/registration_controller.rb @@ -95,9 +95,9 @@ def update registration = Registration.find("#{@competition_id}-#{@user_id}") old_status = registration.competing_status updated_registration = registration.update_competing_lane!({ status: status, comment: comment, event_ids: event_ids, admin_comment: admin_comment, guests: guests }) - if old_status == "accepted" && status != "accepted" + if old_status == 'accepted' && status != 'accepted' Registration.decrement_competitors_count(@competition_id) - elsif old_status != "accepted" && status == "accepted" + elsif old_status != 'accepted' && status == 'accepted' Registration.increment_competitors_count(@competition_id) end render json: { status: 'ok', registration: { diff --git a/app/lib/redis.rb b/app/lib/redis.rb index d1be7b77..9296b8e9 100644 --- a/app/lib/redis.rb +++ b/app/lib/redis.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Redis def increment_or_initialize(key, &block) if Rails.cache.exist?(key) @@ -14,4 +16,4 @@ def decrement_or_initialize(key, &block) Rails.cache.write(key, block.call, expires_in: 60.minutes, raw: true) end end -end \ No newline at end of file +end From a91d39c7ce5dffb82aa132b464e742291bff35b1 Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 11 Dec 2023 14:00:59 +0100 Subject: [PATCH 4/6] Fix Redis counter --- app/lib/{redis.rb => redis_helper.rb} | 6 +++--- app/models/registration.rb | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) rename app/lib/{redis.rb => redis_helper.rb} (75%) diff --git a/app/lib/redis.rb b/app/lib/redis_helper.rb similarity index 75% rename from app/lib/redis.rb rename to app/lib/redis_helper.rb index 9296b8e9..429de80b 100644 --- a/app/lib/redis.rb +++ b/app/lib/redis_helper.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -module Redis - def increment_or_initialize(key, &block) +module RedisHelper + def self.increment_or_initialize(key, &block) if Rails.cache.exist?(key) Rails.cache.increment(key) else @@ -9,7 +9,7 @@ def increment_or_initialize(key, &block) end end - def decrement_or_initialize(key, &block) + def self.decrement_or_initialize(key, &block) if Rails.cache.exist?(key) Rails.cache.decrement(key) else diff --git a/app/models/registration.rb b/app/models/registration.rb index 5b78e281..9762cc3e 100644 --- a/app/models/registration.rb +++ b/app/models/registration.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative 'lane' +require_relative '../lib/redis_helper' require 'time' class Registration include Dynamoid::Document @@ -28,13 +29,13 @@ def self.accepted_competitors_count(competition_id) end def self.decrement_competitors_count(competition_id) - decrement_or_initialize("#{competition_id}-accepted-count") do + RedisHelper::decrement_or_initialize("#{competition_id}-accepted-count") do self.accepted_competitors(competition_id) end end def self.increment_competitors_count(competition_id) - increment_or_initialize("#{competition_id}-accepted-count") do + RedisHelper::increment_or_initialize("#{competition_id}-accepted-count") do self.accepted_competitors(competition_id) end end From de7638b850897414f21e37b5783f8ba00d2b9a8c Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 11 Dec 2023 14:07:42 +0100 Subject: [PATCH 5/6] fix rubocop --- app/models/registration.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/registration.rb b/app/models/registration.rb index 9762cc3e..bb33cf2d 100644 --- a/app/models/registration.rb +++ b/app/models/registration.rb @@ -29,13 +29,13 @@ def self.accepted_competitors_count(competition_id) end def self.decrement_competitors_count(competition_id) - RedisHelper::decrement_or_initialize("#{competition_id}-accepted-count") do + RedisHelper.decrement_or_initialize("#{competition_id}-accepted-count") do self.accepted_competitors(competition_id) end end def self.increment_competitors_count(competition_id) - RedisHelper::increment_or_initialize("#{competition_id}-accepted-count") do + RedisHelper.increment_or_initialize("#{competition_id}-accepted-count") do self.accepted_competitors(competition_id) end end From b385315727874d95b78e6beddab1091ebd4648aa Mon Sep 17 00:00:00 2001 From: FinnIckler Date: Mon, 11 Dec 2023 14:48:46 +0100 Subject: [PATCH 6/6] Added Low Limit Test competition --- Frontend/src/api/mocks/fixtures.ts | 154 ++++++++++++++++++ .../src/api/mocks/get_competition_info.ts | 4 + Frontend/src/api/mocks/get_permissions.ts | 2 +- Frontend/src/ui/Header.jsx | 6 + app/helpers/mocks.rb | 79 ++++++++- 5 files changed, 239 insertions(+), 6 deletions(-) diff --git a/Frontend/src/api/mocks/fixtures.ts b/Frontend/src/api/mocks/fixtures.ts index b573e068..5993994a 100644 --- a/Frontend/src/api/mocks/fixtures.ts +++ b/Frontend/src/api/mocks/fixtures.ts @@ -1429,6 +1429,160 @@ export const OPEN_COMPETITION: CompetitionInfo = { ], 'class': 'competition', } + +export const LOW_COMPETITOR_LIMIT: CompetitionInfo = { + 'id': 'LowLimit2023', + 'name': 'Low Limit 2023', + 'information': 'This Competition has a low Limit', + 'venue': 'Pfarrheim St. Engelbert', + 'contact': + '[Kölner Kubing 2023 Orgateam](mailto:koelnerkubing@googlegroups.com)', + 'registration_open': dateFromNow(-1, -1), + 'registration_close': dateFromNow(2), + 'use_wca_registration': true, + 'announced_at': '2023-08-22T17:10:32.000Z', + 'base_entry_fee_lowest_denomination': 1600, + 'currency_code': 'EUR', + 'start_date': dateFromNow(2), + 'end_date': dateFromNow(2, 1), + 'enable_donations': true, + 'competitor_limit': 3, + 'extra_registration_requirements': + "### Deutsch:\r\n\r\n**1. Erstelle einen WCA-Account**\r\n(Dieser Schritt gilt NUR für Newcomer und Teilnehmer ohne WCA-Account): Erstelle [hier](https://www.worldcubeassociation.org/users/sign_up) einen WCA-Account.\r\n**2. Zahle die Anmeldegebühr**\r\nZahle die Anmeldegebühr in Höhe von 16 Euro via PayPal [*über diesen Link*](https://www.paypal.com/paypalme/FrederikHutfless/16eur) (https://www.paypal.com/paypalme/FrederikHutfless/16eur). \r\n**3. Fülle das Anmeldeformular aus**\r\nFülle das Anmeldeformular aus und schicke es ab: [klicke hier und scrolle ganz nach unten ans Ende der Seite](https://www.worldcubeassociation.org/competitions/KoelnerKubing2023/register).\r\n\r\n**Wichtig:** \r\n* Bitte aktiviere **nicht** den optionalen Käuferschutz, da hierfür eine Gebühr vom gezahlten Eintrittspreis abgezogen wird. \r\n* Falls der Name bei der Zahlung und der Name im Anmeldeformular nicht identisch sind, gib bitte den Namen des Teilnehmers im Anmeldeformular im letzten Schritt der Zahlungsprozedur an oder kontaktiere uns. *Eine Zahlung gilt erst dann als geleistet, wenn wir diese eindeutig zuordnen können.* \r\n* **Ohne (eindeutig zugeordnete) Zahlung gilt die Anmeldung als nicht vollständig und wird nicht bestätigt.**\r\n\r\nWenn das Teilnehmerlimit bereits erreicht wurde, erhältst du nach Anmeldung und Zahlung einen Platz auf der Warteliste. Danach erhältst du eine E-Mail, sobald ein Platz für dich frei werden sollte. Falls du keinen freien Teilnehmerplatz mehr erlangen solltest, wird die Anmeldegebühr selbstverständlich erstattet.\r\n\r\nSolltest du nicht mehr teilnehmen können und deine Anmeldung daher stornieren wollen, bitten wir dich darum, uns zu informieren. Dadurch kann ein Teilnehmer auf der Warteliste nachrücken! Du erhältst eine Erstattung der gesamten Anmeldegebühr (100%), wenn du deine Anmeldung vor dem 03. November 2023, 23:59 MESZ stornierst.\r\n\r\nWenn absehbar ist, dass die Warteliste bereits so lang ist, dass weitere Neuanmeldungen nicht mehr angenommen werden, behalten wir uns vor, die Anmeldung früher als angekündigt zu schließen.\r\n\r\nWir haben [häufig gestellte Fragen und Antworten hier](https://www.worldcubeassociation.org/competitions/KoelnerKubing2023#31967-faq) zusammengestellt.\r\n\r\n#### Für Gäste:\r\nJeder Gast zahlt 3€ (Euro) Eintitt vor Ort\r\n### English:\r\n\r\n**1. Create an WCA account**\r\n(This step is ONLY for newcomers and competitors without a WCA account:) Create a WCA-account [here](https://www.worldcubeassociation.org/users/sign_up).\r\n**2. Pay the registration fee**\r\nPay the registration fee of 16 Euro by following [*this link*](https://www.paypal.com/paypalme/FrederikHutfless/16eur) (https://www.paypal.com/paypalme/FrederikHutfless/16eur) and proceed with the payment via Paypal.\r\n**3. Fill in the registration form**\r\nFill and submit the registration form here: [click here and scroll all the way down to the bottom of the page](https://www.worldcubeassociation.org/competitions/KoelnerKubing2023/register).\r\n\r\n**Important:**\r\n* Please do **not** activate the optional buyer protection as this will be deducted as a fee from the amount you pay. \r\n* If the name of the payment and the name in the registration form are not identical, please enter the name of the competitor from on the registration form in the final step of the payment procedure or contact us. *A payment is only considered to be made once we can clearly match it.*\r\n* **The registration is not considered complete and will not be confirmed until a (clearly matched) payment is made.**\r\n\r\nIf you have registered and paid but the competitor limit has been reached, you will receive a spot on the waiting list. You will be notified via email once a spot for you becomes available. If you do not move up from the waiting list until registration closes, you will get a full refund.\r\n\r\nIf you find that you can not attend the competition anymore, please inform us via e-mail. That way, another competitor can fill your spot! You will receive a full refund (100%) of the entrance fee if you cancel your registration before November 03, 2023, 11:59 PM GMT+2.\r\n\r\nOnce the waiting list is long enough for us to anticipate that new registrations will likely not move up to the competitor's list, we may close the registration earlier than announced. \r\n\r\nWe compiled a set of [frequently asked questions and answers here](https://www.worldcubeassociation.org/competitions/KoelnerKubing2023#31967-faq).\r\n\r\n#### For guests:\r\nEach guest pays 3€ (Euro) entrance fee", + 'on_the_spot_registration': false, + 'on_the_spot_entry_fee_lowest_denomination': undefined, + 'refund_policy_percent': 100, + 'refund_policy_limit_date': dateFromNow(2), + 'guests_entry_fee_lowest_denomination': 300, + 'qualification_results': false, + 'external_registration_page': '', + 'event_restrictions': false, + 'cancelled_at': undefined, + 'waiting_list_deadline_date': dateFromNow(2, -1), + 'event_change_deadline_date': dateFromNow(2, -1), + 'guest_entry_status': 'free', + 'allow_registration_edits': true, + 'allow_registration_self_delete_after_acceptance': false, + 'allow_registration_without_qualification': false, + 'guests_per_registration_limit': undefined, + 'force_comment_in_registration': false, + 'url': 'https://www.worldcubeassociation.org/competitions/KoelnerKubing2023', + 'website': + 'https://www.worldcubeassociation.org/competitions/KoelnerKubing2023', + 'short_name': 'Kölner Kubing 2023', + 'city': 'Köln', + 'venue_address': 'Pfarrer-Moll-Str. 54, 51105 Cologne, Germany', + 'venue_details': 'Obergeschoss // upstairs', + 'latitude_degrees': 50.929833, + 'longitude_degrees': 6.995431, + 'country_iso2': 'DE', + 'event_ids': [ + '333', + '222', + '444', + '555', + '666', + '777', + '333fm', + '333oh', + 'clock', + 'pyram', + 'skewb', + ], + 'registration_opened?': true, + 'main_event_id': '333', + 'number_of_bookmarks': 5, + 'using_stripe_payments?': undefined, + 'uses_qualification?': false, + 'uses_cutoff?': true, + 'delegates': [ + { + id: 2, + created_at: '2012-07-25T05:42:29.000Z', + updated_at: '2023-10-25T17:31:40.000Z', + name: 'Sébastien Auroux', + delegate_status: 'delegate', + wca_id: '2008AURO01', + gender: 'm', + country_iso2: 'DE', + url: 'https://www.worldcubeassociation.org/persons/2008AURO01', + country: { + id: 'Germany', + name: 'Germany', + continentId: '_Europe', + iso2: 'DE', + }, + email: 'sauroux@worldcubeassociation.org', + location: 'Germany', + senior_delegate_id: 454, + class: 'user', + teams: [ + { + id: 190, + friendly_id: 'wrt', + leader: true, + name: 'Sébastien Auroux', + senior_member: false, + wca_id: '2008AURO01', + avatar: { + url: 'https://avatars.worldcubeassociation.org/uploads/user/avatar/2008AURO01/1630621356.jpg', + thumb: { + url: 'https://avatars.worldcubeassociation.org/uploads/user/avatar/2008AURO01/1630621356_thumb.jpg', + }, + }, + }, + ], + avatar: { + url: 'https://avatars.worldcubeassociation.org/uploads/user/avatar/2008AURO01/1630621356.jpg', + pending_url: + 'https://www.worldcubeassociation.org/assets/missing_avatar_thumb-d77f478a307a91a9d4a083ad197012a391d5410f6dd26cb0b0e3118a5de71438.png', + thumb_url: + 'https://avatars.worldcubeassociation.org/uploads/user/avatar/2008AURO01/1630621356_thumb.jpg', + is_default: false, + }, + }, + ], + 'organizers': [ + { + id: 80119, + created_at: '2017-11-04T21:20:56.000Z', + updated_at: '2023-10-30T09:30:27.000Z', + name: 'Dunhui Xiao (肖敦慧)', + delegate_status: undefined, + wca_id: '2018XIAO03', + gender: 'm', + country_iso2: 'DE', + url: 'https://www.worldcubeassociation.org/persons/2018XIAO03', + country: { + id: 'Germany', + name: 'Germany', + continentId: '_Europe', + iso2: 'DE', + }, + class: 'user', + teams: [], + avatar: { + url: 'https://avatars.worldcubeassociation.org/uploads/user/avatar/2018XIAO03/1681755209.jpg', + pending_url: + 'https://www.worldcubeassociation.org/assets/missing_avatar_thumb-d77f478a307a91a9d4a083ad197012a391d5410f6dd26cb0b0e3118a5de71438.png', + thumb_url: + 'https://avatars.worldcubeassociation.org/uploads/user/avatar/2018XIAO03/1681755209_thumb.jpg', + is_default: false, + }, + }, + ], + 'tabs': [ + { + id: 31963, + competition_id: 'KoelnerKubing2023', + name: 'Neulinge \u0026 Ergebnisse / Newcomers \u0026 Results', + content: + "# Deutsch\r\n## Informationen für Neulinge\r\nFalls dies dein erstes WCA Turnier sein sollte, beachte bitte die folgenden Punkte:\r\n\r\n- Alle Neulinge sind dazu verpflichtet ein **Ausweisdokument** an der Anmeldung vor zu zeigen (Regulation [2e](https://www.worldcubeassociation.org/regulations/#2e))). Aus dem Dokument müssen Name, Nationalität und Geburtsdatum hervorgehen.\r\n- Alle Neulinge sind eindringlich gebeten, am **Tutorial** (siehe Zeitplan) teilzunehmen.\r\n- Jeder Teilnehmer sollte bereits vor der Meisterschaft mindestens einmal die **[offiziellen Regeln der WCA](https://www.worldcubeassociation.org/regulations/translations/german/)** gelesen haben. Zusätzlich empfehlen wir euch einen Blick in das [WCA Competition Tutorial](https://www.worldcubeassociation.org/files/WCA_Competition_Tutorial.pdf) bzw. unsere deutschsprachige [Teilnehmer-Anleitung](https://www.germancubeassociation.de/anleitungen/). Dort findet ihr wichtige Infos zum Ablaub und zur Teilnahme am Wettbewerb. Weiterhin bietet zum Beispiel auch [dieses Video-Tutorial](https://www.youtube.com/watch?v=dPL3eV-A0ww) einen guten unterstützenden Einblick.\r\n- Keine Angst: während des Turniers besteht die Möglichkeit, sich mit dem offiziellen Equipment (Stackmat-Timer) vertraut zu machen und Fragen zu stellen.\r\n\r\n\r\nBei Unklarheiten oder wichtigen Fragen vor dem Turnier kannst du dich gerne [an das Organisationsteam wenden](mailto:KoelnerKubing@googlegroups.com).\r\n\r\nLive Ergebnisse sind über [WCA Live](https://live.worldcubeassociation.org/) verfügbar. Nach dem Wettkampf werden alle Ergebnisse in die Datenbank der WCA hochgeladen, und auf dieser Seite einzusehen sein.\r\n\r\n___\r\n\r\n# English\r\n## Newcomer information\r\nIf this is your first WCA competition, please pay attention to the following:\r\n\r\n- According to Regulation [2e)](https://www.worldcubeassociation.org/regulations/#2e), all newcomers are required to bring some form of **identification document** that shows name, citizenship and date of birth.\r\n- All newcomers are urgently asked to attend the **Tutorial** (see schedule).\r\n- Every competitor should have read the **[official WCA regulations](https://www.worldcubeassociation.org/regulations)** at least once before attending the competition! A more condensed version of the important regulations can be found at the [WCA Competition Tutorial](https://www.worldcubeassociation.org/files/WCA_Competition_Tutorial.pdf). We also recommend [this video guide](https://www.youtube.com/watch?v=dPL3eV-A0ww) for a more visual impression.\r\n- Don't be afraid: there will also be time to test the equipment (for example the official timing device, the Stackmat timer) and discuss the rules if you have questions at the competition.\r\n\r\nFeel free to [contact the organizer](mailto:KoelnerKubing@googlegroups.com) if you have any uncertainties.\r\n\r\nLive results are available via [WCA Live](https://live.worldcubeassociation.org/). All results will be uploaded to the WCA database after the competition, and will be available right here.", + display_order: 1, + }, + ], + 'class': 'competition', +} export const COMMENT_REQUIRED: CompetitionInfo = { 'id': 'FMCFrance2023', 'name': 'FMC France 2023', diff --git a/Frontend/src/api/mocks/get_competition_info.ts b/Frontend/src/api/mocks/get_competition_info.ts index f0379316..88b8d0d5 100644 --- a/Frontend/src/api/mocks/get_competition_info.ts +++ b/Frontend/src/api/mocks/get_competition_info.ts @@ -4,6 +4,7 @@ import { CLOSED_COMPETITION, COMMENT_REQUIRED, FAVOURITES_COMPETITION, + LOW_COMPETITOR_LIMIT, NOT_YET_OPEN, OPEN_COMPETITION, OPEN_WITH_PAYMENTS, @@ -31,6 +32,9 @@ export default async function getCompetitionInfoMockWithRealFallback( case 'PickeringFavouritesAutumn2023': { return FAVOURITES_COMPETITION } + case 'LowLimit2023': { + return LOW_COMPETITOR_LIMIT + } default: { // This allows non mocked response when debugging a certain competition return getCompetitionInfo(competitionId) diff --git a/Frontend/src/api/mocks/get_permissions.ts b/Frontend/src/api/mocks/get_permissions.ts index 03ecea50..eca299ca 100644 --- a/Frontend/src/api/mocks/get_permissions.ts +++ b/Frontend/src/api/mocks/get_permissions.ts @@ -25,7 +25,7 @@ export default function getPermissionsMock(): Permissions { scope: [], }, can_administer_competitions: { - scope: ['KoelnerKubing2023'], + scope: ['KoelnerKubing2023', 'LowLimit2023'], }, } case '6427': diff --git a/Frontend/src/ui/Header.jsx b/Frontend/src/ui/Header.jsx index 6151de31..b8cc2776 100644 --- a/Frontend/src/ui/Header.jsx +++ b/Frontend/src/ui/Header.jsx @@ -27,6 +27,12 @@ const DROPDOWNS = [ title: 'Comments Enforced', reactRoute: true, }, + { + path: `${BASE_ROUTE}/LowLimit2023`, + icon: 'battery empty', + title: 'Low Competitor Limit', + reactRoute: true, + }, { path: `${BASE_ROUTE}/PickeringFavouritesAutumn2023`, icon: 'heart', diff --git a/app/helpers/mocks.rb b/app/helpers/mocks.rb index 8a905e3d..6b20c18e 100644 --- a/app/helpers/mocks.rb +++ b/app/helpers/mocks.rb @@ -16,10 +16,10 @@ def self.permissions_mock(user_id) 'scope' => '*', }, 'can_organize_competitions' => { - 'scope' => %w[CubingZANationalChampionship2023], + 'scope' => %w[CubingZANationalChampionship2023 LowLimit2023], }, 'can_administer_competitions' => { - 'scope' => %w[CubingZANationalChampionship2023], + 'scope' => %w[CubingZANationalChampionship2023 LowLimit2023], }, } when '2' # Test Multi-Comp Organizer @@ -28,10 +28,10 @@ def self.permissions_mock(user_id) 'scope' => '*', }, 'can_organize_competitions' => { - 'scope' => %w[LazarilloOpen2023 CubingZANationalChampionship2023 KoelnerKubing2023], + 'scope' => %w[LazarilloOpen2023 CubingZANationalChampionship2023 KoelnerKubing2023 LowLimit2023], }, 'can_administer_competitions' => { - 'scope' => %w[LazarilloOpen2023 CubingZANationalChampionship2023 KoelnerKubing2023], + 'scope' => %w[LazarilloOpen2023 CubingZANationalChampionship2023 KoelnerKubing2023 LowLimit2023], }, } when '15073', '15074' # Test Admin @@ -74,7 +74,6 @@ def self.permissions_mock(user_id) end def self.mock_competition(competition_id) - puts competition_id case competition_id when 'KoelnerKubing2023' { @@ -146,6 +145,76 @@ def self.mock_competition(competition_id) ], 'class' => 'competition', } + when 'LowLimit2023' + { + 'id' => 'LowLimit2023', + 'name' => 'Low Limit 2023', + 'information' => '', + 'venue' => 'Pfarrheim St. Engelbert', + 'contact' => + '[Kölner Kubing 2023 Orgateam](mailto:koelnerkubing@googlegroups.com)', + 'registration_open' => self.date_from_now(-1, 1), + 'registration_close' => self.date_from_now(2), + 'use_wca_registration' => true, + 'announced_at' => '2023-08-22T17:10:32.000Z', + 'base_entry_fee_lowest_denomination' => 1600, + 'currency_code' => 'EUR', + 'start_date' => self.date_from_now(2), + 'end_date' => self.date_from_now(2, 1), + 'enable_donations' => true, + 'competitor_limit' => 3, + 'extra_registration_requirements' => '', + 'on_the_spot_registration' => false, + 'on_the_spot_entry_fee_lowest_denomination' => nil, + 'refund_policy_percent' => 100, + 'refund_policy_limit_date' => self.date_from_now(2), + 'guests_entry_fee_lowest_denomination' => 300, + 'qualification_results' => false, + 'external_registration_page' => '', + 'event_restrictions' => false, + 'cancelled_at' => nil, + 'waiting_list_deadline_date' => self.date_from_now(2, 1), + 'event_change_deadline_date' => self.date_from_now(2, 1), + 'guest_entry_status' => 'free', + 'allow_registration_edits' => true, + 'allow_registration_self_delete_after_acceptance' => false, + 'allow_registration_without_qualification' => false, + 'guests_per_registration_limit' => nil, + 'force_comment_in_registration' => false, + 'url' => 'https://www.worldcubeassociation.org/competitions/KoelnerKubing2023', + 'website' => + 'https://www.worldcubeassociation.org/competitions/KoelnerKubing2023', + 'short_name' => 'Kölner Kubing 2023', + 'city' => 'Köln', + 'venue_address' => 'Pfarrer-Moll-Str. 54, 51105 Cologne, Germany', + 'venue_details' => 'Obergeschoss // upstairs', + 'latitude_degrees' => 50.929833, + 'longitude_degrees' => 6.995431, + 'country_iso2' => 'DE', + 'event_ids' => %w[333 222 444 555 666 777 333fm 333oh clock pyram skewb], + 'registration_opened?' => true, + 'main_event_id' => '333', + 'number_of_bookmarks' => 66, + 'using_stripe_payments?' => nil, + 'uses_qualification?' => false, + 'uses_cutoff?' => true, + 'delegates' => [ + { + 'id' => 2, + }, + ], + 'organizers' => [ + { + 'id' => 80_119, + }, + ], + 'tabs' => [ + { + 'id' => 31_963, + }, + ], + 'class' => 'competition', + } when 'FMCFrance2023' { 'id' => 'FMCFrance2023',