288 lines
6.5 KiB
PL/PgSQL
288 lines
6.5 KiB
PL/PgSQL
create extension if not exists pgcrypto;
|
|
|
|
create table if not exists public.wordle_words (
|
|
position integer primary key,
|
|
word text not null unique check (word ~ '^[a-z]{5}$')
|
|
);
|
|
|
|
insert into public.wordle_words (position, word) values
|
|
(1, 'cigar'),
|
|
(2, 'rebut'),
|
|
(3, 'sissy'),
|
|
(4, 'humph'),
|
|
(5, 'awake'),
|
|
(6, 'blush'),
|
|
(7, 'focal'),
|
|
(8, 'evade'),
|
|
(9, 'naval'),
|
|
(10, 'serve'),
|
|
(11, 'heath'),
|
|
(12, 'dwarf'),
|
|
(13, 'model'),
|
|
(14, 'karma'),
|
|
(15, 'stink'),
|
|
(16, 'grade'),
|
|
(17, 'quiet'),
|
|
(18, 'bench'),
|
|
(19, 'abate'),
|
|
(20, 'feign'),
|
|
(21, 'major'),
|
|
(22, 'death'),
|
|
(23, 'fresh'),
|
|
(24, 'crust'),
|
|
(25, 'stool'),
|
|
(26, 'colon'),
|
|
(27, 'abase'),
|
|
(28, 'marry'),
|
|
(29, 'react'),
|
|
(30, 'batty'),
|
|
(31, 'pride'),
|
|
(32, 'floss'),
|
|
(33, 'helix'),
|
|
(34, 'croak'),
|
|
(35, 'staff'),
|
|
(36, 'paper'),
|
|
(37, 'unfed'),
|
|
(38, 'whelp'),
|
|
(39, 'trawl'),
|
|
(40, 'outdo'),
|
|
(41, 'adobe'),
|
|
(42, 'crazy'),
|
|
(43, 'sower'),
|
|
(44, 'repay'),
|
|
(45, 'digit'),
|
|
(46, 'crate'),
|
|
(47, 'cluck'),
|
|
(48, 'spike'),
|
|
(49, 'mimic'),
|
|
(50, 'pound'),
|
|
(51, 'maxim'),
|
|
(52, 'linen'),
|
|
(53, 'unmet'),
|
|
(54, 'flesh'),
|
|
(55, 'booby'),
|
|
(56, 'forth'),
|
|
(57, 'first'),
|
|
(58, 'stand'),
|
|
(59, 'belly'),
|
|
(60, 'ivory'),
|
|
(61, 'seedy'),
|
|
(62, 'print'),
|
|
(63, 'yearn'),
|
|
(64, 'drain'),
|
|
(65, 'bribe'),
|
|
(66, 'stout'),
|
|
(67, 'panel'),
|
|
(68, 'crass'),
|
|
(69, 'flume'),
|
|
(70, 'offal'),
|
|
(71, 'agree'),
|
|
(72, 'error'),
|
|
(73, 'swirl'),
|
|
(74, 'argue'),
|
|
(75, 'bleed'),
|
|
(76, 'delta'),
|
|
(77, 'flick'),
|
|
(78, 'totem'),
|
|
(79, 'wooer'),
|
|
(80, 'front'),
|
|
(81, 'shrub'),
|
|
(82, 'parry'),
|
|
(83, 'biome'),
|
|
(84, 'lapel'),
|
|
(85, 'start'),
|
|
(86, 'greet'),
|
|
(87, 'goner'),
|
|
(88, 'golem'),
|
|
(89, 'lusty'),
|
|
(90, 'loopy'),
|
|
(91, 'round'),
|
|
(92, 'audit'),
|
|
(93, 'lying'),
|
|
(94, 'gamma'),
|
|
(95, 'labor'),
|
|
(96, 'islet'),
|
|
(97, 'civic'),
|
|
(98, 'forge'),
|
|
(99, 'corny'),
|
|
(100, 'moult'),
|
|
(101, 'basic'),
|
|
(102, 'salad'),
|
|
(103, 'agate'),
|
|
(104, 'spicy'),
|
|
(105, 'spray'),
|
|
(106, 'essay'),
|
|
(107, 'fjord'),
|
|
(108, 'spend'),
|
|
(109, 'kebab'),
|
|
(110, 'guild'),
|
|
(111, 'aback'),
|
|
(112, 'motor'),
|
|
(113, 'alone'),
|
|
(114, 'hatch'),
|
|
(115, 'hyper'),
|
|
(116, 'thumb'),
|
|
(117, 'dowry'),
|
|
(118, 'ought'),
|
|
(119, 'belch'),
|
|
(120, 'dutch')
|
|
on conflict (position) do nothing;
|
|
|
|
alter table public.wordle_words enable row level security;
|
|
|
|
create table if not exists public.wordle_rounds (
|
|
id uuid primary key default gen_random_uuid(),
|
|
user_id uuid not null references auth.users(id) on delete cascade,
|
|
word text not null check (word ~ '^[a-z]{5}$'),
|
|
hour_start timestamptz,
|
|
started_at timestamptz not null default now(),
|
|
next_playable_at timestamptz not null default (now() + interval '1 hour'),
|
|
completed_at timestamptz,
|
|
won boolean,
|
|
guess_count integer check (guess_count between 1 and 6),
|
|
created_at timestamptz not null default now()
|
|
);
|
|
|
|
alter table public.wordle_rounds
|
|
add column if not exists hour_start timestamptz;
|
|
|
|
update public.wordle_rounds
|
|
set hour_start = date_trunc('hour', started_at)
|
|
where hour_start is null;
|
|
|
|
alter table public.wordle_rounds
|
|
alter column hour_start set not null;
|
|
|
|
create index if not exists wordle_rounds_user_started_idx
|
|
on public.wordle_rounds (user_id, started_at desc);
|
|
|
|
create unique index if not exists wordle_rounds_user_hour_idx
|
|
on public.wordle_rounds (user_id, hour_start);
|
|
|
|
alter table public.wordle_rounds enable row level security;
|
|
|
|
drop policy if exists "Users can read their own rounds" on public.wordle_rounds;
|
|
create policy "Users can read their own rounds"
|
|
on public.wordle_rounds
|
|
for select
|
|
using (auth.uid() = user_id);
|
|
|
|
drop function if exists public.start_hourly_round(text);
|
|
|
|
create or replace function public.start_hourly_round()
|
|
returns table (
|
|
round_id uuid,
|
|
word text,
|
|
hour_start timestamptz,
|
|
started_at timestamptz,
|
|
next_playable_at timestamptz,
|
|
completed_at timestamptz,
|
|
won boolean,
|
|
guess_count integer,
|
|
server_now timestamptz,
|
|
is_existing boolean
|
|
)
|
|
language plpgsql
|
|
security definer
|
|
set search_path = public
|
|
as $$
|
|
declare
|
|
existing_round public.wordle_rounds%rowtype;
|
|
new_round public.wordle_rounds%rowtype;
|
|
current_hour timestamptz := date_trunc('hour', now());
|
|
word_count integer;
|
|
word_offset integer;
|
|
hourly_word text;
|
|
begin
|
|
if auth.uid() is null then
|
|
raise exception 'Authentication required';
|
|
end if;
|
|
|
|
select count(*) into word_count from public.wordle_words;
|
|
if word_count = 0 then
|
|
raise exception 'No hourly words are configured';
|
|
end if;
|
|
|
|
word_offset := (floor(extract(epoch from current_hour) / 3600)::bigint % word_count)::integer;
|
|
|
|
select public.wordle_words.word
|
|
into hourly_word
|
|
from public.wordle_words
|
|
order by position
|
|
limit 1 offset word_offset;
|
|
|
|
select *
|
|
into existing_round
|
|
from public.wordle_rounds
|
|
where user_id = auth.uid()
|
|
and public.wordle_rounds.hour_start = current_hour
|
|
order by started_at desc
|
|
limit 1;
|
|
|
|
if found then
|
|
return query select
|
|
existing_round.id,
|
|
existing_round.word,
|
|
existing_round.hour_start,
|
|
existing_round.started_at,
|
|
existing_round.next_playable_at,
|
|
existing_round.completed_at,
|
|
existing_round.won,
|
|
existing_round.guess_count,
|
|
now(),
|
|
true;
|
|
return;
|
|
end if;
|
|
|
|
insert into public.wordle_rounds (user_id, word, hour_start, next_playable_at)
|
|
values (auth.uid(), hourly_word, current_hour, current_hour + interval '1 hour')
|
|
returning * into new_round;
|
|
|
|
return query select
|
|
new_round.id,
|
|
new_round.word,
|
|
new_round.hour_start,
|
|
new_round.started_at,
|
|
new_round.next_playable_at,
|
|
new_round.completed_at,
|
|
new_round.won,
|
|
new_round.guess_count,
|
|
now(),
|
|
false;
|
|
end;
|
|
$$;
|
|
|
|
create or replace function public.complete_hourly_round(
|
|
round_id uuid,
|
|
did_win boolean,
|
|
guess_total integer
|
|
)
|
|
returns void
|
|
language plpgsql
|
|
security definer
|
|
set search_path = public
|
|
as $$
|
|
begin
|
|
if auth.uid() is null then
|
|
raise exception 'Authentication required';
|
|
end if;
|
|
|
|
if guess_total < 1 or guess_total > 6 then
|
|
raise exception 'Guess total must be between 1 and 6';
|
|
end if;
|
|
|
|
update public.wordle_rounds
|
|
set completed_at = coalesce(completed_at, now()),
|
|
won = did_win,
|
|
guess_count = guess_total
|
|
where id = round_id
|
|
and user_id = auth.uid();
|
|
end;
|
|
$$;
|
|
|
|
revoke all on public.wordle_rounds from anon, authenticated;
|
|
revoke all on public.wordle_words from anon, authenticated;
|
|
grant select on public.wordle_rounds to authenticated;
|
|
grant execute on function public.start_hourly_round() to authenticated;
|
|
grant execute on function public.complete_hourly_round(uuid, boolean, integer) to authenticated;
|