From 7cf0fce43090e7dd429b4e7441020c865602135d Mon Sep 17 00:00:00 2001 From: sripwoud Date: Wed, 30 Oct 2024 14:36:00 +0100 Subject: [PATCH 1/3] feat(db): update tables schema --- apps/server/src/supabase/supabase.types.ts | 33 +++++++++++------- .../20241023064101_create_questions_table.sql | 13 +++---- .../20241023071130_create_feedbacks_table.sql | 34 ++++++++----------- 3 files changed, 43 insertions(+), 37 deletions(-) diff --git a/apps/server/src/supabase/supabase.types.ts b/apps/server/src/supabase/supabase.types.ts index 8305b33..52b3e93 100644 --- a/apps/server/src/supabase/supabase.types.ts +++ b/apps/server/src/supabase/supabase.types.ts @@ -55,19 +55,19 @@ export type Database = { feedbacks: { Row: { created_at: string - feedback: boolean + feedback: string id: number question_id: number } Insert: { created_at?: string - feedback: boolean + feedback: string id?: never question_id: number } Update: { created_at?: string - feedback?: boolean + feedback?: string id?: never question_id?: number } @@ -106,9 +106,9 @@ export type Database = { created_at: string group_id: string id: number - no: number + options: string[] | null title: string - yes: number + type: Database["public"]["Enums"]["question_type"] } Insert: { active?: boolean @@ -116,9 +116,9 @@ export type Database = { created_at?: string group_id: string id?: never - no?: number + options?: string[] | null title: string - yes?: number + type: Database["public"]["Enums"]["question_type"] } Update: { active?: boolean @@ -126,9 +126,9 @@ export type Database = { created_at?: string group_id?: string id?: never - no?: number + options?: string[] | null title?: string - yes?: number + type?: Database["public"]["Enums"]["question_type"] } Relationships: [] } @@ -158,13 +158,22 @@ export type Database = { [_ in never]: never } Functions: { - [_ in never]: never + count_boolean_feedbacks: { + Args: { + question_id: number + } + Returns: Database["public"]["CompositeTypes"]["boolean_feedbacks_count"] + } } Enums: { - [_ in never]: never + boolean_feedback: "yes" | "no" + question_type: "boolean" | "text" | "number" | "option" } CompositeTypes: { - [_ in never]: never + boolean_feedbacks_count: { + yes: number | null + no: number | null + } } } } diff --git a/supabase/migrations/20241023064101_create_questions_table.sql b/supabase/migrations/20241023064101_create_questions_table.sql index 2336fa7..f01edaa 100644 --- a/supabase/migrations/20241023064101_create_questions_table.sql +++ b/supabase/migrations/20241023064101_create_questions_table.sql @@ -1,14 +1,15 @@ +create type question_type as enum ('boolean', 'text', 'number', 'option'); create table public.questions ( id bigint generated always as identity primary key, active boolean not null default true, author text not null, created_at timestamptz not null default now(), - title text not null, - no integer not null default 0, - yes integer not null default 0, - group_id text not null -- TODO allow list of groupIds? Build dynamically union of groups on the fly in bandada? + group_id text not null, + -- TODO allow list of groupIds? Build dynamically union of groups on the fly in bandada? + options text [] default null, + type question_type not null, + title text not null ); comment on table public.questions is 'Questions for users to give feedback on'; -comment on column public.questions.yes is 'Number of yes votes'; -comment on column public.questions.no is 'Number of no votes'; comment on column public.questions.group_id is 'Id of the Bandada Semaphore Group that restrict who can see and answer this question'; +comment on column public.questions.type is 'List of possible feedback values (for option question type only)'; diff --git a/supabase/migrations/20241023071130_create_feedbacks_table.sql b/supabase/migrations/20241023071130_create_feedbacks_table.sql index 11d5f1f..10b6cc2 100644 --- a/supabase/migrations/20241023071130_create_feedbacks_table.sql +++ b/supabase/migrations/20241023071130_create_feedbacks_table.sql @@ -1,32 +1,28 @@ create table public.feedbacks ( id bigint generated always as identity primary key, created_at timestamptz not null default now(), - feedback boolean not null, + feedback text not null, + -- future proof, store all feedbacks for any question type as text, do validation on the application layer question_id bigint not null references public.questions(id) ); comment on table public.feedbacks is 'Feedback semaphore signals from users'; comment on column public.feedbacks.feedback is 'Feedback semaphore signal (only boolean answer supported for now)'; +-- +create type boolean_feedback as enum ('yes', 'no'); +create type boolean_feedbacks_count as (yes integer, no integer); -create function tally() -returns trigger +create or replace function count_boolean_feedbacks(question_id bigint) +returns boolean_feedbacks_count language plpgsql as $$ +declare result boolean_feedbacks_count; begin - if new.feedback then - update public.questions - set yes = yes + 1 - where id = new.question_id; - else - update public.questions - set no = no + 1 - where id = new.question_id; - end if; - return new; + select + coalesce(sum(case when feedback::boolean_feedback = 'yes' then 1 else 0 end), 0) as yes, + coalesce(sum(case when feedback::boolean_feedback = 'no' then 1 else 0 end), 0) as no + into result + from public.feedbacks + where question_id = $1; + return result; end; $$; - -create trigger tally_trigger -after -insert on public.feedbacks -for each row -execute function tally(); From a70476ec59774e6dedf49a0c005666e2bfa56895 Mon Sep 17 00:00:00 2001 From: sripwoud Date: Wed, 30 Oct 2024 17:13:47 +0100 Subject: [PATCH 2/3] refactor(client): send boolean feedback while supporting text feedback schema --- .../src/app/[groupId]/[questionId]/page.tsx | 6 +++-- .../src/components/CreateQuestionForm.tsx | 2 +- .../src/components/QuestionCard/YN/index.tsx | 18 ++++----------- apps/client/src/hooks/useQuestionStats.ts | 9 ++++++++ apps/client/src/hooks/useSendFeedback.ts | 6 +++-- .../src/feedbacks/dto/create-feedback.dto.ts | 2 +- .../src/feedbacks/dto/send-feedback.dto.ts | 22 +++++++++++++++--- .../server/src/feedbacks/feedbacks.service.ts | 18 +++++++++++---- .../src/questions/dto/create-question.dto.ts | 10 ++++++++ apps/server/src/questions/dto/index.ts | 1 + .../src/questions/dto/question-stats.dto.ts | 5 ++++ apps/server/src/questions/questions.router.ts | 9 +++++++- .../server/src/questions/questions.service.ts | 23 +++++++++++++++++-- apps/server/src/supabase/supabase.service.ts | 1 + 14 files changed, 103 insertions(+), 29 deletions(-) create mode 100644 apps/client/src/hooks/useQuestionStats.ts create mode 100644 apps/server/src/questions/dto/question-stats.dto.ts diff --git a/apps/client/src/app/[groupId]/[questionId]/page.tsx b/apps/client/src/app/[groupId]/[questionId]/page.tsx index 1c91dc6..e6712c3 100644 --- a/apps/client/src/app/[groupId]/[questionId]/page.tsx +++ b/apps/client/src/app/[groupId]/[questionId]/page.tsx @@ -2,6 +2,7 @@ import { useUser } from '@account-kit/react' import { Loader } from 'client/c/Loader' import { withAuth } from 'client/components/withAuth' +import { useQuestionStats } from 'client/h/useQuestionStats' import { trpc } from 'client/l/trpc' import { useEffect } from 'react' @@ -12,6 +13,7 @@ const QuestionDetails = ({ params: { questionId: questionIdStr } }: { params: { const { data: question, isLoading, refetch } = trpc.questions.find.useQuery({ questionId }, { select: ({ data }) => data, }) + const { data: { no, yes } } = useQuestionStats({ questionId }) useEffect(() => { refetch() @@ -21,8 +23,8 @@ const QuestionDetails = ({ params: { questionId: questionIdStr } }: { params: { return (

{question.title}

-

yes: {question.yes}

-

no: {question.no}

+

yes: {yes}

+

no: {no}

{user?.email === question.author && (