myheats

Live heats, scoring and leaderboard for sport events
git clone https://git.in0rdr.ch/myheats.git
Log | Files | Refs | Pull requests | README | LICENSE

README.md (5545B)


      1 # My Heats
      2 Live heats, scoring and leaderboard for sport events.
      3 
      4 This React application was build using [Vite](https://vitejs.dev) (`npm create
      5 vite@latest . --template react`) and the following core componentes:
      6 * [`react-router-dom`](https://reactrouter.com) for routing between the React
      7   components
      8 * [`@supabase/supabase-js`](https://supabase.com/docs/guides/getting-started/quickstarts/reactjs)
      9   for connection to the Supabase database
     10 * [`react-select`](https://react-select.com) for the selectbox widgets
     11 
     12 ## Running the App
     13 ```bash
     14 # Install dependencies
     15 npm install
     16 
     17 # Export Supabase URI and database access key
     18 export VITE_APP_SUPABASE_URL=
     19 export VITE_APP_SUPABASE_KEY=
     20 
     21 # Start app for development
     22 npm run dev
     23 ```
     24 
     25 ## Scoring and ranking logic
     26 The scoring and ranking logic is defined in `utils.js` inside the `rankByHeat`
     27 function. Following ranking options can be selected. Rank by:
     28 * Start number: Rank by athlete start number
     29 * Best heat: Rank by best heat (and by least worst if "best" is equally good)
     30 * Worst heat: Ranks worst heats only (no further comparison by best if equally
     31   bad)
     32 * Total: Ranks by sum of all selected heats
     33 
     34 ## Database schema
     35 The Supabase schema is stored in the `schema` folder and can be created using plain psql.
     36 
     37 ![supabase-schema](assets/supabase-schema.png)
     38 
     39 To update the schema from the current database, use (example for table `startlist`):
     40 ```bash
     41 pg_dump -h db.youruniquedb.supabase.co -U postgres -t 'public.startlist' --schema-only > schema/startlist.sql
     42 ```
     43 
     44 Additionally, following views and functions are required:
     45 
     46 ```sql
     47 -- Summarize all scores of a heat
     48 create or replace view score_summary
     49   with (security_invoker=on)
     50   as
     51   select a.id as athlete_id, s.heat as heat_id, SUM(s.score) as score_summary
     52   from scores s
     53   join athletes as a on a.id = s.athlete
     54   group by a.id, s.heat
     55 
     56 -- Create distinct set of athletes (startlist) from a set of heats
     57 create or replace function distinct_startlist(heat_ids numeric[])
     58 returns table(id bigint, athlete bigint, nr bigint, firstname text, lastname text,
     59 birthday date, school text)
     60 language plpgsql
     61 set search_path = ''
     62 as $$
     63 begin
     64   return query
     65   select distinct on (a.id) s.id, a.id, a.nr, a.firstname, a.lastname, a.birthday, a.school
     66   from public.startlist as s
     67   join public.athletes as a on s.athlete = a.id
     68   where s.heat = any (heat_ids);
     69 end;
     70 $$;
     71 ```
     72 
     73 Add a trigger for connecting users with judges:
     74 ```sql
     75 -- https://supabase.com/docs/guides/auth/managing-user-data#using-triggers
     76 -- inserts a row into public.judges
     77 --drop trigger if exists on_auth_user_created on auth.users;
     78 create or replace function public.handle_new_judge()
     79 returns trigger
     80 language plpgsql
     81 security definer set search_path = ''
     82 as $$
     83 begin
     84   insert into public.judges (id)
     85   values (new.id);
     86   return new;
     87 end;
     88 $$;
     89 
     90 -- trigger the function every time a user is created
     91 create trigger on_auth_user_created
     92   after insert on auth.users
     93   for each row execute procedure public.handle_new_judge();
     94 ```
     95 
     96 Lastly, enable [PostgreSQL publication](https://www.postgresql.org/docs/current/logical-replication-publication.html) for the [Supabase realtime feature on Postgres changes](https://supabase.com/docs/guides/realtime/extensions/postgres-changes):
     97 ```sql
     98 begin;
     99 
    100 -- remove the supabase_realtime publication
    101 drop
    102   publication if exists supabase_realtime;
    103 
    104 -- re-create the supabase_realtime publication with no tables
    105 create publication supabase_realtime;
    106 
    107 commit;
    108 
    109 -- add a table to the publication
    110 alter
    111   publication supabase_realtime add table scores;
    112 ```
    113 
    114 This is required for the leaderboard to automatically update scores when they are created or changed by judges.
    115 
    116 ## Authentication with magic links
    117 Authentication of judges is performed using [Supabase Magic
    118 Links](https://supabase.com/docs/guides/auth/auth-magic-link).
    119 
    120 The basic code for the authentication flow was taken from the [official React
    121 Getting Started
    122 Tutorial](https://supabase.com/docs/guides/getting-started/tutorials/with-react).
    123 
    124 Sign up process for new judges:
    125 * The judge is required to have a valid email address
    126 * Judges can be invied to the scoring backend by invite only (Click
    127   ["Invite"](https://app.supabase.com/project/_/auth/users) in Supabase
    128 backend)
    129 * The UUID is referenced automatically in the `judges` table, row can be
    130   amended with name of the judge
    131 
    132 Sign in process:
    133 * For sign in, the judge enters her email adress and receives a login link
    134 * The login information is stored in the browser session until sign out
    135 
    136 ### Supabase rate limits
    137 * 4 emails per hour (e.g., sign-in magic links)
    138 * https://supabase.com/docs/guides/auth/rate-limits
    139 
    140 ## Bulk import/export
    141 Use direct postgres database access:
    142 ```bash
    143 psql -h db.youruniquedb.supabase.co -U postgres
    144 ```
    145 
    146 To import data from local csv:
    147 ```sql
    148 \copy public.scores from 'scores_rows.csv' DELIMITER ',' CSV HEADER;
    149 ```
    150 
    151 To export data to local csv:
    152 ```sql
    153 \copy public.scores to 'scores_rows.csv' DELIMITER ',' CSV HEADER;
    154 ```
    155 
    156 ## Development & contributions
    157 * All source code is available in this repository. Contributions are always
    158   welcome! Pull requests at https://pr.in0rdr.ch/repos/myheats
    159 * A [Kanban
    160   board](https://board.in0rdr.ch/public/board/c445667f1c999857f6149cad967fa44f196f2e9fef02069c5f5136e036dd)
    161 documents plans, todos and decisions for further development of the project.
    162 
    163 ## Community and support
    164 For question and support visit
    165 [#p0c/libera](https://web.libera.chat/gamja/#p0c) on IRC.
    166 
    167 ## License
    168 This work uses the MIT License. See [LICENSE](./LICENSE) for details.