Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Manifold TODO #3211

Merged
merged 16 commits into from
Dec 12, 2024
18 changes: 18 additions & 0 deletions backend/api/src/create-category.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { APIHandler } from './helpers/endpoint'
import { createSupabaseDirectClient } from 'shared/supabase/init'

export const createCategory: APIHandler<'create-category'> = async (props, auth) => {
const { name, color } = props
const pg = createSupabaseDirectClient()

console.log('Creating category', { userId: auth.uid, name, color })

const result = await pg.one(
`insert into categories (user_id, name, color)
values ($1, $2, $3)
returning id`,
[auth.uid, name, color]
)

return { id: result.id }
}
29 changes: 29 additions & 0 deletions backend/api/src/create-task.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { APIHandler } from './helpers/endpoint'
import { createSupabaseDirectClient } from 'shared/supabase/init'

export const createTask: APIHandler<'create-task'> = async (props, auth) => {
const {
text,
category_id: categoryId,
priority,
assignee_id: assigneeId,
} = props
const pg = createSupabaseDirectClient()

console.log('Creating task', {
userId: auth.uid,
text,
categoryId,
priority,
assigneeId,
})

const result = await pg.one(
`insert into tasks (creator_id, assignee_id, text, category_id, priority)
values ($1, $2, $3, $4, $5)
returning *`,
[auth.uid, assigneeId || auth.uid, text, categoryId, priority]
)

return result
}
20 changes: 20 additions & 0 deletions backend/api/src/get-categories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { APIHandler } from './helpers/endpoint'
import { createSupabaseDirectClient } from 'shared/supabase/init'

export const getCategories: APIHandler<'get-categories'> = async (_, auth) => {
const pg = createSupabaseDirectClient()

console.log('Getting categories for user', auth.uid)

const categories = await pg.manyOrNone(
`SELECT DISTINCT ON (c.id) c.*
FROM categories c
LEFT JOIN tasks t ON c.id = t.category_id
WHERE c.user_id = $1
OR t.assignee_id = $1
ORDER BY c.id, c.display_order, c.created_time`,
[auth.uid]
)

return { categories }
}
18 changes: 18 additions & 0 deletions backend/api/src/get-tasks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { APIHandler } from './helpers/endpoint'
import { createSupabaseDirectClient } from 'shared/supabase/init'

export const getTasks: APIHandler<'get-tasks'> = async (_, auth) => {
const pg = createSupabaseDirectClient()

console.log('Getting tasks for user', auth.uid)

const tasks = await pg.manyOrNone(
`select *
from tasks
where creator_id = $1 or assignee_id = $1
order by priority, created_time desc`,
[auth.uid]
)

return { tasks }
}
15 changes: 15 additions & 0 deletions backend/api/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,17 @@ import { generateAIAnswers } from './generate-ai-answers'
import { getmonthlybets2024 } from './get-monthly-bets-2024'
import { getmaxminprofit2024 } from './get-max-min-profit-2024'
import { getNextLoanAmount } from './get-next-loan-amount'

import { createTask } from './create-task'
import { updateTask } from './update-task'
import { createCategory } from './create-category'
import { getCategories } from './get-categories'
import { updateCategory } from './update-category'
import { getTasks } from './get-tasks'

import { getSiteActivity } from './get-site-activity'


// we define the handlers in this object in order to typecheck that every API has a handler
export const handlers: { [k in APIPath]: APIHandler<k> } = {
'refresh-all-clients': refreshAllClients,
Expand Down Expand Up @@ -298,5 +307,11 @@ export const handlers: { [k in APIPath]: APIHandler<k> } = {
'get-monthly-bets-2024': getmonthlybets2024,
'get-max-min-profit-2024': getmaxminprofit2024,
'get-next-loan-amount': getNextLoanAmount,
'create-task': createTask,
'update-task': updateTask,
'create-category': createCategory,
'get-categories': getCategories,
'update-category': updateCategory,
'get-tasks': getTasks,
'get-site-activity': getSiteActivity,
}
38 changes: 38 additions & 0 deletions backend/api/src/update-category.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { APIHandler } from './helpers/endpoint'
import { createSupabaseDirectClient } from 'shared/supabase/init'

export const updateCategory: APIHandler<'update-category'> = async (
props,
auth
) => {
const { categoryId, name, color, displayOrder, archived } = props
const pg = createSupabaseDirectClient()

console.log('Updating category', {
categoryId,
name,
color,
displayOrder,
archived,
userId: auth.uid,
})

const updates: { [key: string]: any } = {}
if (name !== undefined) updates.name = name
if (color !== undefined) updates.color = color
if (displayOrder !== undefined) updates.display_order = displayOrder
if (archived !== undefined) updates.archived = archived

const setClauses = Object.entries(updates)
.map(([key], i) => `${key} = $${i + 3}`)
.join(', ')

await pg.none(
`update categories
set ${setClauses}
where id = $1 and user_id = $2`,
[categoryId, auth.uid, ...Object.values(updates)]
)

return { success: true }
}
37 changes: 37 additions & 0 deletions backend/api/src/update-task.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { log } from 'shared/utils'
import { APIError, APIHandler } from './helpers/endpoint'
import { createSupabaseDirectClient } from 'shared/supabase/init'

export const updateTask: APIHandler<'update-task'> = async (props, auth) => {
const { id, text, completed, priority, category_id, archived, assignee_id } =
props
const pg = createSupabaseDirectClient()
log('Updating task', props)

// Build update fields dynamically
const updates: { [key: string]: any } = {}
if (completed !== undefined) updates.completed = completed
if (priority !== undefined) updates.priority = priority
if (category_id !== undefined) updates.category_id = category_id
if (text !== undefined) updates.text = text
if (archived !== undefined) updates.archived = archived
if (assignee_id !== undefined) updates.assignee_id = assignee_id
const setClauses = Object.entries(updates)
.map(([key], i) => `${key} = $${i + 3}`)
.join(', ')

const result = await pg.oneOrNone(
`update tasks
set ${setClauses}
where id = $1 and (creator_id = $2 or assignee_id = $2)
returning id, priority, category_id, text, completed, assignee_id
`,
[id, auth.uid, ...Object.values(updates)]
)

if (!result) {
throw new APIError(404, 'Task not found or unauthorized')
}

return { success: true }
}
16 changes: 16 additions & 0 deletions backend/supabase/categories.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
create table if not exists
categories (
id bigint primary key generated always as identity not null,
user_id text not null,
name text not null,
color text,
display_order integer default 0 not null,
archived boolean default false not null,
created_time timestamp with time zone default now() not null
);

-- Row Level Security
alter table categories enable row level security;

-- Indexes
create index categories_user_id_idx on categories (user_id);
22 changes: 22 additions & 0 deletions backend/supabase/tasks.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
create table if not exists
tasks (
id bigint primary key generated always as identity not null,
creator_id text not null references users (id),
assignee_id text not null references users (id),
category_id bigint not null,
text text not null,
completed boolean default false not null,
priority integer default 0 not null,
archived boolean default false not null,
created_time timestamp with time zone default now() not null
);

-- Row Level Security
alter table tasks enable row level security;

-- Indexes
create index tasks_creator_id_idx on tasks (creator_id);

create index tasks_assignee_id_idx on tasks (assignee_id);

create index tasks_category_id_idx on tasks (category_id);
76 changes: 76 additions & 0 deletions common/src/api/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import { NON_POINTS_BETS_LIMIT } from 'common/supabase/bets'
import { ContractMetric } from 'common/contract-metric'

import { JSONContent } from '@tiptap/core'
import { Task, TaskCategory } from 'common/todo'
// mqp: very unscientific, just balancing our willingness to accept load
// with user willingness to put up with stale data
export const DEFAULT_CACHE_STRATEGY =
Expand Down Expand Up @@ -1913,6 +1914,80 @@ export const API = (_apiTypeCheck = {
userId: z.string(),
}),
},

'create-task': {
method: 'POST',
visibility: 'public',
authed: true,
returns: {} as Task,
props: z
.object({
text: z.string(),
category_id: z.number().optional(),
priority: z.number().default(0),
assignee_id: z.string().optional(),
})
.strict(),
},
'update-task': {
method: 'POST',
visibility: 'public',
authed: true,
returns: {} as { success: boolean },
props: z
.object({
id: z.number(),
text: z.string().optional(),
completed: z.boolean().optional(),
priority: z.number().optional(),
category_id: z.number().optional(),
archived: z.boolean().optional(),
assignee_id: z.string().optional(),
})
.strict(),
},
'create-category': {
method: 'POST',
visibility: 'public',
authed: true,
returns: {} as { id: number },
props: z
.object({
name: z.string(),
color: z.string().optional(),
displayOrder: z.number().optional(),
})
.strict(),
},
'get-categories': {
method: 'GET',
visibility: 'public',
authed: true,
returns: {} as { categories: TaskCategory[] },
props: z.object({}).strict(),
},
'update-category': {
method: 'POST',
visibility: 'public',
authed: true,
returns: {} as { success: boolean },
props: z
.object({
categoryId: z.number(),
name: z.string().optional(),
color: z.string().optional(),
displayOrder: z.number().optional(),
archived: z.boolean().optional(),
})
.strict(),
},
'get-tasks': {
method: 'GET',
visibility: 'public',
authed: true,
returns: {} as { tasks: Task[] },
props: z.object({}).strict(),

'get-site-activity': {
method: 'GET',
visibility: 'public',
Expand All @@ -1929,6 +2004,7 @@ export const API = (_apiTypeCheck = {
blockedGroupSlugs: z.array(z.string()).optional(),
blockedContractIds: z.array(z.string()).optional(),
}).strict(),

},
} as const)

Expand Down
19 changes: 19 additions & 0 deletions common/src/todo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export type TaskCategory = {
id: number
name: string
color?: string
displayOrder: number
archived?: boolean
}

export type Task = {
id: number
creator_id: string
assignee_id: string
text: string
completed: boolean
category_id: number // -1 for inbox
created_time: number
priority: number
archived: boolean
}
Loading
Loading