diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 647b0b4..c8d2618 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -23,6 +23,16 @@ const main = async () => { "/issues/:projectBlob": withCors(withAuth(routes.issuesInProject)), "/issues/all": withCors(withAuth(routes.issues)), + "/organisation/create": withCors(withAuth(routes.organisationCreate)), + "/organisation/by-id": withCors(withAuth(routes.organisationById)), + "/organisation/by-user": withCors(withAuth(routes.organisationByUser)), + "/organisation/update": withCors(withAuth(routes.organisationUpdate)), + "/organisation/delete": withCors(withAuth(routes.organisationDelete)), + "/organisation/add-member": withCors(withAuth(routes.organisationAddMember)), + "/organisation/members": withCors(withAuth(routes.organisationMembers)), + "/organisation/remove-member": withCors(withAuth(routes.organisationRemoveMember)), + "/organisation/update-member-role": withCors(withAuth(routes.organisationUpdateMemberRole)), + "/project/create": withCors(withAuth(routes.projectCreate)), "/project/update": withCors(withAuth(routes.projectUpdate)), "/project/delete": withCors(withAuth(routes.projectDelete)), diff --git a/packages/backend/src/routes/index.ts b/packages/backend/src/routes/index.ts index 00a8535..ad4454f 100644 --- a/packages/backend/src/routes/index.ts +++ b/packages/backend/src/routes/index.ts @@ -6,6 +6,15 @@ import issueDelete from "./issue/delete"; import issueUpdate from "./issue/update"; import issuesInProject from "./issues/[projectBlob]"; import issues from "./issues/all"; +import organisationAddMember from "./organisation/add-member"; +import organisationById from "./organisation/by-id"; +import organisationByUser from "./organisation/by-user"; +import organisationCreate from "./organisation/create"; +import organisationDelete from "./organisation/delete"; +import organisationMembers from "./organisation/members"; +import organisationRemoveMember from "./organisation/remove-member"; +import organisationUpdate from "./organisation/update"; +import organisationUpdateMemberRole from "./organisation/update-member-role"; import projectsAll from "./project/all"; import projectsByCreator from "./project/by-creator"; import projectCreate from "./project/create"; @@ -22,6 +31,16 @@ export const routes = { issuesInProject, issues, + organisationCreate, + organisationById, + organisationByUser, + organisationUpdate, + organisationDelete, + organisationAddMember, + organisationMembers, + organisationRemoveMember, + organisationUpdateMemberRole, + projectCreate, projectUpdate, projectDelete, diff --git a/packages/backend/src/routes/organisation/add-member.ts b/packages/backend/src/routes/organisation/add-member.ts new file mode 100644 index 0000000..79c4ea5 --- /dev/null +++ b/packages/backend/src/routes/organisation/add-member.ts @@ -0,0 +1,38 @@ +import type { BunRequest } from "bun"; +import { createOrganisationMember, getOrganisationById, getUserById } from "../../db/queries"; + +// /organisation/add-member?organisationId=1&userId=2&role=member +export default async function organisationAddMember(req: BunRequest) { + const url = new URL(req.url); + const organisationId = url.searchParams.get("organisationId"); + const userId = url.searchParams.get("userId"); + const role = url.searchParams.get("role") || "member"; + + if (!organisationId || !userId) { + return new Response( + `missing parameters: ${!organisationId ? "organisationId " : ""}${!userId ? "userId" : ""}`, + { status: 400 }, + ); + } + + const orgIdNumber = Number(organisationId); + const userIdNumber = Number(userId); + + if (!Number.isInteger(orgIdNumber) || !Number.isInteger(userIdNumber)) { + return new Response("organisationId and userId must be integers", { status: 400 }); + } + + const organisation = await getOrganisationById(orgIdNumber); + if (!organisation) { + return new Response(`organisation with id ${organisationId} not found`, { status: 404 }); + } + + const user = await getUserById(userIdNumber); + if (!user) { + return new Response(`user with id ${userId} not found`, { status: 404 }); + } + + const member = await createOrganisationMember(orgIdNumber, userIdNumber, role); + + return Response.json(member); +} diff --git a/packages/backend/src/routes/organisation/by-id.ts b/packages/backend/src/routes/organisation/by-id.ts new file mode 100644 index 0000000..02756c7 --- /dev/null +++ b/packages/backend/src/routes/organisation/by-id.ts @@ -0,0 +1,24 @@ +import type { BunRequest } from "bun"; +import { getOrganisationById } from "../../db/queries"; + +// /organisation/by-id?id=1 +export default async function organisationById(req: BunRequest) { + const url = new URL(req.url); + const id = url.searchParams.get("id"); + + if (!id) { + return new Response("organisation id is required", { status: 400 }); + } + + const organisationId = Number(id); + if (!Number.isInteger(organisationId)) { + return new Response("organisation id must be an integer", { status: 400 }); + } + + const organisation = await getOrganisationById(organisationId); + if (!organisation) { + return new Response(`organisation with id ${id} not found`, { status: 404 }); + } + + return Response.json(organisation); +} diff --git a/packages/backend/src/routes/organisation/by-user.ts b/packages/backend/src/routes/organisation/by-user.ts new file mode 100644 index 0000000..b4cdaeb --- /dev/null +++ b/packages/backend/src/routes/organisation/by-user.ts @@ -0,0 +1,26 @@ +import type { BunRequest } from "bun"; +import { getOrganisationsByUserId, getUserById } from "../../db/queries"; + +// /organisation/by-user?userId=1 +export default async function organisationsByUser(req: BunRequest) { + const url = new URL(req.url); + const userId = url.searchParams.get("userId"); + + if (!userId) { + return new Response("userId is required", { status: 400 }); + } + + const userIdNumber = Number(userId); + if (!Number.isInteger(userIdNumber)) { + return new Response("userId must be an integer", { status: 400 }); + } + + const user = await getUserById(userIdNumber); + if (!user) { + return new Response(`user with id ${userId} not found`, { status: 404 }); + } + + const organisations = await getOrganisationsByUserId(user.id); + + return Response.json(organisations); +} diff --git a/packages/backend/src/routes/organisation/create.ts b/packages/backend/src/routes/organisation/create.ts new file mode 100644 index 0000000..4882f9f --- /dev/null +++ b/packages/backend/src/routes/organisation/create.ts @@ -0,0 +1,23 @@ +import type { BunRequest } from "bun"; +import { createOrganisation } from "../../db/queries"; + +// /organisation/create?name=Org%20Name&slug=org-name&description=Optional%20description +export default async function organisationCreate(req: BunRequest) { + const url = new URL(req.url); + const name = url.searchParams.get("name"); + const slug = url.searchParams.get("slug"); + const description = url.searchParams.get("description") || undefined; + + if (!name || !slug) { + return new Response(`missing parameters: ${!name ? "name " : ""}${!slug ? "slug" : ""}`, { + status: 400, + }); + } + + // Check if organisation with slug already exists + // TODO: Add this check when we have a getOrganisationBySlug function + + const organisation = await createOrganisation(name, slug, description); + + return Response.json(organisation); +} diff --git a/packages/backend/src/routes/organisation/delete.ts b/packages/backend/src/routes/organisation/delete.ts new file mode 100644 index 0000000..5737916 --- /dev/null +++ b/packages/backend/src/routes/organisation/delete.ts @@ -0,0 +1,26 @@ +import type { BunRequest } from "bun"; +import { deleteOrganisation, getOrganisationById } from "../../db/queries"; + +// /organisation/delete?id=1 +export default async function organisationDelete(req: BunRequest) { + const url = new URL(req.url); + const id = url.searchParams.get("id"); + + if (!id) { + return new Response("organisation id is required", { status: 400 }); + } + + const organisationId = Number(id); + if (!Number.isInteger(organisationId)) { + return new Response("organisation id must be an integer", { status: 400 }); + } + + const organisation = await getOrganisationById(organisationId); + if (!organisation) { + return new Response(`organisation with id ${id} not found`, { status: 404 }); + } + + await deleteOrganisation(organisationId); + + return Response.json({ success: true }); +} diff --git a/packages/backend/src/routes/organisation/members.ts b/packages/backend/src/routes/organisation/members.ts new file mode 100644 index 0000000..d13f7ba --- /dev/null +++ b/packages/backend/src/routes/organisation/members.ts @@ -0,0 +1,26 @@ +import type { BunRequest } from "bun"; +import { getOrganisationById, getOrganisationMembers } from "../../db/queries"; + +// /organisation/members?organisationId=1 +export default async function organisationMembers(req: BunRequest) { + const url = new URL(req.url); + const organisationId = url.searchParams.get("organisationId"); + + if (!organisationId) { + return new Response("organisationId is required", { status: 400 }); + } + + const orgIdNumber = Number(organisationId); + if (!Number.isInteger(orgIdNumber)) { + return new Response("organisationId must be an integer", { status: 400 }); + } + + const organisation = await getOrganisationById(orgIdNumber); + if (!organisation) { + return new Response(`organisation with id ${organisationId} not found`, { status: 404 }); + } + + const members = await getOrganisationMembers(orgIdNumber); + + return Response.json(members); +} diff --git a/packages/backend/src/routes/organisation/remove-member.ts b/packages/backend/src/routes/organisation/remove-member.ts new file mode 100644 index 0000000..dfde1a4 --- /dev/null +++ b/packages/backend/src/routes/organisation/remove-member.ts @@ -0,0 +1,37 @@ +import type { BunRequest } from "bun"; +import { getOrganisationById, getUserById, removeOrganisationMember } from "../../db/queries"; + +// /organisation/remove-member?organisationId=1&userId=2 +export default async function organisationRemoveMember(req: BunRequest) { + const url = new URL(req.url); + const organisationId = url.searchParams.get("organisationId"); + const userId = url.searchParams.get("userId"); + + if (!organisationId || !userId) { + return new Response( + `missing parameters: ${!organisationId ? "organisationId " : ""}${!userId ? "userId" : ""}`, + { status: 400 }, + ); + } + + const orgIdNumber = Number(organisationId); + const userIdNumber = Number(userId); + + if (!Number.isInteger(orgIdNumber) || !Number.isInteger(userIdNumber)) { + return new Response("organisationId and userId must be integers", { status: 400 }); + } + + const organisation = await getOrganisationById(orgIdNumber); + if (!organisation) { + return new Response(`organisation with id ${organisationId} not found`, { status: 404 }); + } + + const user = await getUserById(userIdNumber); + if (!user) { + return new Response(`user with id ${userId} not found`, { status: 404 }); + } + + await removeOrganisationMember(orgIdNumber, userIdNumber); + + return Response.json({ success: true }); +} diff --git a/packages/backend/src/routes/organisation/update-member-role.ts b/packages/backend/src/routes/organisation/update-member-role.ts new file mode 100644 index 0000000..3c7a4b9 --- /dev/null +++ b/packages/backend/src/routes/organisation/update-member-role.ts @@ -0,0 +1,38 @@ +import type { BunRequest } from "bun"; +import { getOrganisationById, getUserById, updateOrganisationMemberRole } from "../../db/queries"; + +// /organisation/update-member-role?organisationId=1&userId=2&role=admin +export default async function organisationUpdateMemberRole(req: BunRequest) { + const url = new URL(req.url); + const organisationId = url.searchParams.get("organisationId"); + const userId = url.searchParams.get("userId"); + const role = url.searchParams.get("role"); + + if (!organisationId || !userId || !role) { + return new Response( + `missing parameters: ${!organisationId ? "organisationId " : ""}${!userId ? "userId " : ""}${!role ? "role" : ""}`, + { status: 400 }, + ); + } + + const orgIdNumber = Number(organisationId); + const userIdNumber = Number(userId); + + if (!Number.isInteger(orgIdNumber) || !Number.isInteger(userIdNumber)) { + return new Response("organisationId and userId must be integers", { status: 400 }); + } + + const organisation = await getOrganisationById(orgIdNumber); + if (!organisation) { + return new Response(`organisation with id ${organisationId} not found`, { status: 404 }); + } + + const user = await getUserById(userIdNumber); + if (!user) { + return new Response(`user with id ${userId} not found`, { status: 404 }); + } + + const member = await updateOrganisationMemberRole(orgIdNumber, userIdNumber, role); + + return Response.json(member); +} diff --git a/packages/backend/src/routes/organisation/update.ts b/packages/backend/src/routes/organisation/update.ts new file mode 100644 index 0000000..e3365f4 --- /dev/null +++ b/packages/backend/src/routes/organisation/update.ts @@ -0,0 +1,39 @@ +import type { BunRequest } from "bun"; +import { getOrganisationById, updateOrganisation } from "../../db/queries"; + +// /organisation/update?id=1&name=New%20Name&description=New%20Description&slug=new-slug +export default async function organisationUpdate(req: BunRequest) { + const url = new URL(req.url); + const id = url.searchParams.get("id"); + const name = url.searchParams.get("name") || undefined; + const description = url.searchParams.get("description") || undefined; + const slug = url.searchParams.get("slug") || undefined; + + if (!id) { + return new Response("organisation id is required", { status: 400 }); + } + + const organisationId = Number(id); + if (!Number.isInteger(organisationId)) { + return new Response("organisation id must be an integer", { status: 400 }); + } + + const existingOrganisation = await getOrganisationById(organisationId); + if (!existingOrganisation) { + return new Response(`organisation with id ${id} does not exist`, { status: 404 }); + } + + if (!name && !description && !slug) { + return new Response("at least one of name, description, or slug must be provided", { + status: 400, + }); + } + + const organisation = await updateOrganisation(organisationId, { + name, + description, + slug, + }); + + return Response.json(organisation); +} diff --git a/todo.md b/todo.md index 190c05a..8dccf3f 100644 --- a/todo.md +++ b/todo.md @@ -1,7 +1,7 @@ - user settings/profile page -- organisations system - create organisation - create project - create issue - add user(s) to project - replace "no projects" text with create project button +- rename Project.blob to Project.key