Creator App Tests / PR Spam Gate (push) Skipped
Frontend Tests / PR Spam Gate (push) Skipped
NPM Format (website & console) / PR Spam Gate (push) Skipped
Pre-commit Checks / PR Spam Gate (push) Skipped
QwenPaw Creator Plugin Release / check-version (push) Canceled after 0s
QwenPaw Creator Plugin Release / upload-oss (push) Canceled after 0s
Tests / PR Spam Gate (push) Skipped
CodeQL / CodeQL (python) (push) Canceled after 0s
CodeQL / CodeQL (typescript) (push) Canceled after 0s
Creator App Tests / Creator Backend Tests (push) Canceled after 0s
Creator App Tests / Creator UI Build & Tests (push) Canceled after 0s
E2E Smoke Tests / Playwright UI Smoke (push) Canceled after 0s
Frontend Tests / Detect console changes (push) Canceled after 0s
NPM Format (website & console) / website format check (push) Canceled after 0s
NPM Format (website & console) / console format check (push) Canceled after 0s
Pre-commit Checks / Pre-commit (push) Canceled after 0s
QwenPaw Data Plugin Release / check-version (push) Canceled after 0s
Tests / Detect code changes (push) Canceled after 0s
Frontend Tests / Vitest Unit Tests (push) Canceled after 0s
Frontend Tests / Frontend Summary (push) Canceled after 0s
QwenPaw Data Plugin Release / upload-oss (push) Canceled after 0s
Tests / Maintainer Approval (push) Canceled after 0s
Tests / Unit Tests - py3.11 - macos-latest (push) Canceled after 0s
Tests / Unit Tests - py3.11 - ubuntu-latest (push) Canceled after 0s
Tests / Unit Tests - py3.13 - ubuntu-latest (push) Canceled after 0s
Tests / Unit Tests - py3.11 - windows-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - macos-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - ubuntu-latest (push) Canceled after 0s
Tests / Contract Tests - py3.13 - ubuntu-latest (push) Canceled after 0s
Tests / Contract Tests - py3.11 - windows-latest (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - macos-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - fallback (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - ubuntu-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.13 - ubuntu-latest - p2 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p0 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p1 (push) Canceled after 0s
Tests / Integrated Tests - py3.11 - windows-latest - p2 (push) Canceled after 0s
Tests / Coverage Report (push) Canceled after 0s
Tests / Test Summary (push) Canceled after 0s
106 lines
2.9 KiB
PL/PgSQL
106 lines
2.9 KiB
PL/PgSQL
-- QwenPaw blog views + likes
|
|
-- Run once in Supabase Dashboard → SQL Editor → New query → Run
|
|
|
|
create table if not exists public.blog_stats (
|
|
slug text primary key,
|
|
views bigint not null default 0 check (views >= 0),
|
|
likes bigint not null default 0 check (likes >= 0),
|
|
updated_at timestamptz not null default now()
|
|
);
|
|
|
|
create index if not exists blog_stats_views_idx on public.blog_stats (views desc);
|
|
|
|
alter table public.blog_stats enable row level security;
|
|
|
|
-- Public read for list + article pages
|
|
drop policy if exists "blog_stats_select_public" on public.blog_stats;
|
|
create policy "blog_stats_select_public"
|
|
on public.blog_stats
|
|
for select
|
|
to anon, authenticated
|
|
using (true);
|
|
|
|
-- No direct insert/update/delete for anon — writes go through RPCs below
|
|
|
|
create or replace function public.increment_blog_view(p_slug text)
|
|
returns public.blog_stats
|
|
language plpgsql
|
|
security definer
|
|
set search_path = public
|
|
as $$
|
|
declare
|
|
result public.blog_stats;
|
|
begin
|
|
if p_slug is null or length(trim(p_slug)) = 0 then
|
|
raise exception 'slug required';
|
|
end if;
|
|
|
|
insert into public.blog_stats (slug, views, likes)
|
|
values (p_slug, 1, 0)
|
|
on conflict (slug) do update
|
|
set views = public.blog_stats.views + 1,
|
|
updated_at = now()
|
|
returning * into result;
|
|
|
|
return result;
|
|
end;
|
|
$$;
|
|
|
|
create or replace function public.increment_blog_like(p_slug text)
|
|
returns public.blog_stats
|
|
language plpgsql
|
|
security definer
|
|
set search_path = public
|
|
as $$
|
|
declare
|
|
result public.blog_stats;
|
|
begin
|
|
if p_slug is null or length(trim(p_slug)) = 0 then
|
|
raise exception 'slug required';
|
|
end if;
|
|
|
|
insert into public.blog_stats (slug, views, likes)
|
|
values (p_slug, 0, 1)
|
|
on conflict (slug) do update
|
|
set likes = public.blog_stats.likes + 1,
|
|
updated_at = now()
|
|
returning * into result;
|
|
|
|
return result;
|
|
end;
|
|
$$;
|
|
|
|
create or replace function public.decrement_blog_like(p_slug text)
|
|
returns public.blog_stats
|
|
language plpgsql
|
|
security definer
|
|
set search_path = public
|
|
as $$
|
|
declare
|
|
result public.blog_stats;
|
|
begin
|
|
if p_slug is null or length(trim(p_slug)) = 0 then
|
|
raise exception 'slug required';
|
|
end if;
|
|
|
|
insert into public.blog_stats (slug, views, likes)
|
|
values (p_slug, 0, 0)
|
|
on conflict (slug) do update
|
|
set likes = greatest(public.blog_stats.likes - 1, 0),
|
|
updated_at = now()
|
|
returning * into result;
|
|
|
|
return result;
|
|
end;
|
|
$$;
|
|
|
|
revoke all on function public.increment_blog_view(text) from public;
|
|
revoke all on function public.increment_blog_like(text) from public;
|
|
revoke all on function public.decrement_blog_like(text) from public;
|
|
|
|
grant execute on function public.increment_blog_view(text) to anon, authenticated;
|
|
grant execute on function public.increment_blog_like(text) to anon, authenticated;
|
|
grant execute on function public.decrement_blog_like(text) to anon, authenticated;
|
|
|
|
grant select on table public.blog_stats to anon, authenticated;
|