Add more services; Version 0.3.3

This commit is contained in:
Aslan 2025-12-29 02:02:56 +01:00
parent 50348cc494
commit ddcc591d12
26 changed files with 887 additions and 38 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "tether", "name": "tether",
"version": "0.3.1", "version": "0.3.3",
"description": "Communication server using the Nexlink protocol", "description": "Communication server using the Nexlink protocol",
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -0,0 +1,11 @@
-- DropForeignKey
ALTER TABLE "Channel" DROP CONSTRAINT "Channel_communityId_fkey";
-- DropForeignKey
ALTER TABLE "Role" DROP CONSTRAINT "Role_communityId_fkey";
-- AddForeignKey
ALTER TABLE "Channel" ADD CONSTRAINT "Channel_communityId_fkey" FOREIGN KEY ("communityId") REFERENCES "Community"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Role" ADD CONSTRAINT "Role_communityId_fkey" FOREIGN KEY ("communityId") REFERENCES "Community"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View file

@ -0,0 +1,17 @@
-- DropForeignKey
ALTER TABLE "Invite" DROP CONSTRAINT "Invite_communityId_fkey";
-- DropForeignKey
ALTER TABLE "Invite" DROP CONSTRAINT "Invite_creatorId_fkey";
-- DropForeignKey
ALTER TABLE "Session" DROP CONSTRAINT "Session_userId_fkey";
-- AddForeignKey
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Invite" ADD CONSTRAINT "Invite_communityId_fkey" FOREIGN KEY ("communityId") REFERENCES "Community"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Invite" ADD CONSTRAINT "Invite_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View file

@ -24,7 +24,7 @@ model Community {
model Channel { model Channel {
id String @id @unique @default(uuid()) id String @id @unique @default(uuid())
name String name String
community Community @relation(fields: [communityId], references: [id]) community Community @relation(fields: [communityId], references: [id], onDelete: Cascade)
communityId String communityId String
creationDate DateTime @default(now()) creationDate DateTime @default(now())
} }
@ -32,7 +32,7 @@ model Channel {
model Role { model Role {
id String @id @unique @default(uuid()) id String @id @unique @default(uuid())
name String name String
community Community @relation(fields: [communityId], references: [id]) community Community @relation(fields: [communityId], references: [id], onDelete: Cascade)
communityId String communityId String
users User[] @relation(name: "UsersRolesToUsers") users User[] @relation(name: "UsersRolesToUsers")
permissions String[] permissions String[]
@ -57,7 +57,7 @@ model User {
model Session { model Session {
id String @id @unique @default(uuid()) id String @id @unique @default(uuid())
owner User @relation(fields: [userId], references: [id]) owner User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String userId String
token String token String
creationDate DateTime @default(now()) creationDate DateTime @default(now())
@ -65,9 +65,9 @@ model Session {
model Invite { model Invite {
id String @id @unique @default(uuid()) id String @id @unique @default(uuid())
Community Community @relation(fields: [communityId], references: [id]) Community Community @relation(fields: [communityId], references: [id], onDelete: Cascade)
communityId String communityId String
User User @relation(fields: [creatorId], references: [id]) User User @relation(fields: [creatorId], references: [id], onDelete: Cascade)
creatorId String creatorId String
totalInvites Int @default(0) totalInvites Int @default(0)
remainingInvites Int @default(0) remainingInvites Int @default(0)

View file

@ -6,10 +6,19 @@ import type {
IPostCreateChannelRequest, IPostCreateChannelRequest,
IPostCreateChannelResponseError, IPostCreateChannelResponseError,
IPostCreateChannelResponseSuccess, IPostCreateChannelResponseSuccess,
IPatchChannelParams,
IPatchChannelRequest,
IPatchChannelResponseError,
IPatchChannelResponseSuccess,
IDeleteChannelParams,
IDeleteChannelResponseError,
IDeleteChannelResponseSuccess,
} from "./types.js"; } from "./types.js";
import { import {
createChannelAuth, createChannelAuth,
deleteChannelByIdAuth,
getChannelByIdAuth, getChannelByIdAuth,
updateChannelByIdAuth,
} from "../../services/channel/channel.js"; } from "../../services/channel/channel.js";
import { API_ERROR } from "../errors.js"; import { API_ERROR } from "../errors.js";
@ -63,4 +72,62 @@ const postCreateChannel = async (
} as IPostCreateChannelResponseSuccess; } as IPostCreateChannelResponseSuccess;
}; };
export { getChannel, postCreateChannel }; const patchChannel = async (request: FastifyRequest, reply: FastifyReply) => {
const { id } = request.params as IPatchChannelParams;
const patchChannelRequest = request.body as IPatchChannelRequest;
const authHeader = request.headers["authorization"];
const channel = await updateChannelByIdAuth(
id,
patchChannelRequest,
authHeader,
);
if (!channel) {
reply.status(404);
return {
id: id,
error: API_ERROR.NOT_FOUND,
} as IPatchChannelResponseError;
}
if (channel === API_ERROR.ACCESS_DENIED) {
reply.status(403);
return {
id: id,
error: API_ERROR.ACCESS_DENIED,
} as IPatchChannelResponseError;
}
return {
id: channel.id,
name: channel.name,
communityId: channel.communityId,
} as IPatchChannelResponseSuccess;
};
const deleteChannel = async (request: FastifyRequest, reply: FastifyReply) => {
const { id } = request.params as IDeleteChannelParams;
const authHeader = request.headers["authorization"];
const channel = await deleteChannelByIdAuth(id, authHeader);
if (!channel) {
reply.status(404);
return {
id: id,
error: API_ERROR.NOT_FOUND,
} as IDeleteChannelResponseError;
}
if (channel === API_ERROR.ACCESS_DENIED) {
reply.status(403);
return {
id: id,
error: API_ERROR.ACCESS_DENIED,
} as IDeleteChannelResponseError;
}
return {
id: channel.id,
communityId: channel.communityId,
} as IDeleteChannelResponseSuccess;
};
export { getChannel, postCreateChannel, patchChannel, deleteChannel };

View file

@ -4,6 +4,8 @@ import * as controller from "./channel.js";
const channelRoutes = async (fastify: FastifyInstance) => { const channelRoutes = async (fastify: FastifyInstance) => {
fastify.get(`/:id`, controller.getChannel); fastify.get(`/:id`, controller.getChannel);
fastify.post(`/`, controller.postCreateChannel); fastify.post(`/`, controller.postCreateChannel);
fastify.patch(`/:id`, controller.patchChannel);
fastify.delete(`/:id`, controller.deleteChannel);
}; };
export { channelRoutes }; export { channelRoutes };

View file

@ -32,6 +32,39 @@ interface IPostCreateChannelResponseSuccess {
communityId: string; communityId: string;
} }
interface IPatchChannelParams {
id: string;
}
interface IPatchChannelRequest {
name?: string;
}
interface IPatchChannelResponseError {
id: string;
error: API_ERROR;
}
interface IPatchChannelResponseSuccess {
id: string;
name: string;
communityId: string;
}
interface IDeleteChannelParams {
id: string;
}
interface IDeleteChannelResponseError {
id: string;
error: API_ERROR;
}
interface IDeleteChannelResponseSuccess {
id: string;
communityId: string;
}
export { export {
type IGetChannelParams, type IGetChannelParams,
type IGetChannelResponseError, type IGetChannelResponseError,
@ -39,4 +72,11 @@ export {
type IPostCreateChannelRequest, type IPostCreateChannelRequest,
type IPostCreateChannelResponseError, type IPostCreateChannelResponseError,
type IPostCreateChannelResponseSuccess, type IPostCreateChannelResponseSuccess,
type IPatchChannelParams,
type IPatchChannelRequest,
type IPatchChannelResponseError,
type IPatchChannelResponseSuccess,
type IDeleteChannelParams,
type IDeleteChannelResponseError,
type IDeleteChannelResponseSuccess,
}; };

View file

@ -10,6 +10,9 @@ import type {
IPatchCommunityRequest, IPatchCommunityRequest,
IPatchCommunityResponseError, IPatchCommunityResponseError,
IPatchCommunityResponseSuccess, IPatchCommunityResponseSuccess,
IDeleteCommunityParams,
IDeleteCommunityResponseError,
IDeleteCommunityResponseSuccess,
IGetMembersParams, IGetMembersParams,
IGetMembersResponseError, IGetMembersResponseError,
IGetMembersResponseSuccess, IGetMembersResponseSuccess,
@ -32,6 +35,7 @@ import {
getCommunityMembersByIdAuth, getCommunityMembersByIdAuth,
getCommunityRolesByIdAuth, getCommunityRolesByIdAuth,
createInviteAuth, createInviteAuth,
deleteCommunityByIdAuth,
} from "../../services/community/community.js"; } from "../../services/community/community.js";
import { API_ERROR } from "../errors.js"; import { API_ERROR } from "../errors.js";
import type { ICreateInvite } from "../../services/community/types.js"; import type { ICreateInvite } from "../../services/community/types.js";
@ -115,6 +119,34 @@ const patchCommunity = async (request: FastifyRequest, reply: FastifyReply) => {
} as IPatchCommunityResponseSuccess; } as IPatchCommunityResponseSuccess;
}; };
const deleteCommunity = async (
request: FastifyRequest,
reply: FastifyReply,
) => {
const { id } = request.params as IDeleteCommunityParams;
const authHeader = request.headers["authorization"];
const community = await deleteCommunityByIdAuth(id, authHeader);
if (!community) {
reply.status(404);
return {
id: id,
error: API_ERROR.NOT_FOUND,
} as IDeleteCommunityResponseError;
}
if (community === API_ERROR.ACCESS_DENIED) {
reply.status(403);
return {
id: id,
error: API_ERROR.ACCESS_DENIED,
} as IDeleteCommunityResponseError;
}
return {
id: community.id,
} as IDeleteCommunityResponseSuccess;
};
const getMembers = async (request: FastifyRequest, reply: FastifyReply) => { const getMembers = async (request: FastifyRequest, reply: FastifyReply) => {
const { id } = request.params as IGetMembersParams; const { id } = request.params as IGetMembersParams;
const authHeader = request.headers["authorization"]; const authHeader = request.headers["authorization"];
@ -253,6 +285,7 @@ export {
getCommunity, getCommunity,
postCreateCommunity, postCreateCommunity,
patchCommunity, patchCommunity,
deleteCommunity,
getMembers, getMembers,
getChannels, getChannels,
getRoles, getRoles,

View file

@ -5,6 +5,7 @@ const communityRoutes = async (fastify: FastifyInstance) => {
fastify.get(`/:id`, controller.getCommunity); fastify.get(`/:id`, controller.getCommunity);
fastify.post(`/`, controller.postCreateCommunity); fastify.post(`/`, controller.postCreateCommunity);
fastify.patch(`/:id`, controller.patchCommunity); fastify.patch(`/:id`, controller.patchCommunity);
fastify.delete(`/:id`, controller.deleteCommunity);
fastify.get(`/:id/members`, controller.getMembers); fastify.get(`/:id/members`, controller.getMembers);
fastify.get(`/:id/channels`, controller.getChannels); fastify.get(`/:id/channels`, controller.getChannels);
fastify.get(`/:id/roles`, controller.getRoles); fastify.get(`/:id/roles`, controller.getRoles);

View file

@ -54,6 +54,19 @@ interface IPatchCommunityResponseSuccess {
description: string; description: string;
} }
interface IDeleteCommunityParams {
id: string;
}
interface IDeleteCommunityResponseError {
id: string;
error: API_ERROR;
}
interface IDeleteCommunityResponseSuccess {
id: string;
}
interface IGetMembersParams { interface IGetMembersParams {
id: string; id: string;
} }
@ -144,6 +157,9 @@ export {
type IPatchCommunityRequest, type IPatchCommunityRequest,
type IPatchCommunityResponseError, type IPatchCommunityResponseError,
type IPatchCommunityResponseSuccess, type IPatchCommunityResponseSuccess,
type IDeleteCommunityParams,
type IDeleteCommunityResponseError,
type IDeleteCommunityResponseSuccess,
type IGetMembersParams, type IGetMembersParams,
type IGetMembersResponseError, type IGetMembersResponseError,
type IGetMembersResponseSuccess, type IGetMembersResponseSuccess,

View file

@ -3,9 +3,19 @@ import type {
IGetRoleParams, IGetRoleParams,
IGetRoleResponseError, IGetRoleResponseError,
IGetRoleResponseSuccess, IGetRoleResponseSuccess,
IGetRolePermissionsParams,
IGetRolePermissionsResponseError,
IGetRolePermissionsResponseSuccess,
IPostCreateRoleRequest, IPostCreateRoleRequest,
IPostCreateRoleResponseError, IPostCreateRoleResponseError,
IPostCreateRoleResponseSuccess, IPostCreateRoleResponseSuccess,
IPatchRoleParams,
IPatchRoleRequest,
IPatchRoleResponseError,
IPatchRoleResponseSuccess,
IDeleteRoleParams,
IDeleteRoleResponseError,
IDeleteRoleResponseSuccess,
IPostAssignRoleParams, IPostAssignRoleParams,
IPostAssignRoleRequest, IPostAssignRoleRequest,
IPostAssignRoleResponseError, IPostAssignRoleResponseError,
@ -18,8 +28,10 @@ import type {
import { import {
assignRoleByIdAuth, assignRoleByIdAuth,
createRoleAuth, createRoleAuth,
deleteRoleByIdAuth,
getRoleByIdAuth, getRoleByIdAuth,
unassignRoleByIdAuth, unassignRoleByIdAuth,
updateRoleByIdAuth,
} from "../../services/role/role.js"; } from "../../services/role/role.js";
import { API_ERROR } from "../errors.js"; import { API_ERROR } from "../errors.js";
@ -51,6 +63,38 @@ const getRole = async (request: FastifyRequest, reply: FastifyReply) => {
} as IGetRoleResponseSuccess; } as IGetRoleResponseSuccess;
}; };
const getRolePermissions = async (
request: FastifyRequest,
reply: FastifyReply,
) => {
const { id } = request.params as IGetRolePermissionsParams;
const authHeader = request.headers["authorization"];
const role = await getRoleByIdAuth(id, authHeader);
if (!role) {
reply.status(404);
return {
id: id,
error: API_ERROR.NOT_FOUND,
} as IGetRolePermissionsResponseError;
}
if (role === API_ERROR.ACCESS_DENIED) {
reply.status(403);
return {
id: id,
error: API_ERROR.ACCESS_DENIED,
} as IGetRolePermissionsResponseError;
}
return {
id: role.id,
name: role.name,
communityId: role.communityId,
permissions: role.permissions,
creationDate: role.creationDate.getTime(),
} as IGetRolePermissionsResponseSuccess;
};
const postCreateRole = async (request: FastifyRequest, reply: FastifyReply) => { const postCreateRole = async (request: FastifyRequest, reply: FastifyReply) => {
const createRoleRequest = request.body as IPostCreateRoleRequest; const createRoleRequest = request.body as IPostCreateRoleRequest;
const authHeader = request.headers["authorization"]; const authHeader = request.headers["authorization"];
@ -70,12 +114,73 @@ const postCreateRole = async (request: FastifyRequest, reply: FastifyReply) => {
} as IPostCreateRoleResponseSuccess; } as IPostCreateRoleResponseSuccess;
}; };
const patchRole = async (request: FastifyRequest, reply: FastifyReply) => {
const { id } = request.params as IPatchRoleParams;
const patchRoleRequest = request.body as IPatchRoleRequest;
const authHeader = request.headers["authorization"];
const role = await updateRoleByIdAuth(id, patchRoleRequest, authHeader);
if (!role) {
reply.status(404);
return {
id: id,
error: API_ERROR.NOT_FOUND,
} as IPatchRoleResponseError;
}
if (role === API_ERROR.ACCESS_DENIED) {
reply.status(403);
return {
id: id,
error: API_ERROR.ACCESS_DENIED,
} as IPatchRoleResponseError;
}
return {
id: role.id,
name: role.name,
communityId: role.communityId,
permissions: role.permissions,
} as IPatchRoleResponseSuccess;
};
const deleteRole = async (request: FastifyRequest, reply: FastifyReply) => {
const { id } = request.params as IDeleteRoleParams;
const authHeader = request.headers["authorization"];
const role = await deleteRoleByIdAuth(id, authHeader);
if (!role) {
reply.status(404);
return {
id: id,
error: API_ERROR.NOT_FOUND,
} as IDeleteRoleResponseError;
}
if (role === API_ERROR.ACCESS_DENIED) {
reply.status(403);
return {
id: id,
error: API_ERROR.ACCESS_DENIED,
} as IDeleteRoleResponseError;
}
return {
id: role.id,
communityId: role.communityId,
} as IDeleteRoleResponseSuccess;
};
const postAssignRole = async (request: FastifyRequest, reply: FastifyReply) => { const postAssignRole = async (request: FastifyRequest, reply: FastifyReply) => {
const { id } = request.params as IPostAssignRoleParams; const { id } = request.params as IPostAssignRoleParams;
const { userId } = request.body as IPostAssignRoleRequest; const { userId } = request.body as IPostAssignRoleRequest;
const authHeader = request.headers["authorization"]; const authHeader = request.headers["authorization"];
const role = await assignRoleByIdAuth(id, userId, authHeader); const role = await assignRoleByIdAuth(id, userId, authHeader);
if (!role) {
reply.status(404);
return {
error: API_ERROR.NOT_FOUND,
} as IPostAssignRoleResponseError;
}
if (role === API_ERROR.ACCESS_DENIED) { if (role === API_ERROR.ACCESS_DENIED) {
reply.status(403); reply.status(403);
return { return {
@ -106,6 +211,12 @@ const postUnassignRole = async (
error: API_ERROR.ACCESS_DENIED, error: API_ERROR.ACCESS_DENIED,
} as IPostUnassignRoleResponseError; } as IPostUnassignRoleResponseError;
} }
if (!role) {
reply.status(404);
return {
error: API_ERROR.NOT_FOUND,
} as IPostUnassignRoleResponseError;
}
return { return {
id: role.id, id: role.id,
@ -115,4 +226,12 @@ const postUnassignRole = async (
} as IPostUnassignRoleResponseSuccess; } as IPostUnassignRoleResponseSuccess;
}; };
export { getRole, postCreateRole, postAssignRole, postUnassignRole }; export {
getRole,
getRolePermissions,
patchRole,
deleteRole,
postCreateRole,
postAssignRole,
postUnassignRole,
};

View file

@ -3,7 +3,10 @@ import * as controller from "./role.js";
const roleRoutes = async (fastify: FastifyInstance) => { const roleRoutes = async (fastify: FastifyInstance) => {
fastify.get(`/:id`, controller.getRole); fastify.get(`/:id`, controller.getRole);
fastify.get(`/:id/permissions`, controller.getRolePermissions);
fastify.post(`/`, controller.postCreateRole); fastify.post(`/`, controller.postCreateRole);
fastify.patch(`/:id`, controller.patchRole);
fastify.delete(`/:id`, controller.deleteRole);
fastify.post(`/:id/assign`, controller.postAssignRole); fastify.post(`/:id/assign`, controller.postAssignRole);
fastify.post(`/:id/unassign`, controller.postUnassignRole); fastify.post(`/:id/unassign`, controller.postUnassignRole);
}; };

View file

@ -17,6 +17,23 @@ interface IGetRoleResponseSuccess {
creationDate: number; creationDate: number;
} }
interface IGetRolePermissionsParams {
id: string;
}
interface IGetRolePermissionsResponseError {
id: string;
error: API_ERROR;
}
interface IGetRolePermissionsResponseSuccess {
id: string;
name: string;
communityId: string;
permissions: PERMISSION[];
creationDate: number;
}
interface IPostCreateRoleRequest { interface IPostCreateRoleRequest {
name: string; name: string;
communityId: string; communityId: string;
@ -34,6 +51,40 @@ interface IPostCreateRoleResponseSuccess {
communityId: string; communityId: string;
} }
interface IPatchRoleParams {
id: string;
}
interface IPatchRoleRequest {
name?: string;
}
interface IPatchRoleResponseError {
id: string;
error: API_ERROR;
}
interface IPatchRoleResponseSuccess {
id: string;
name: string;
communityId: string;
permissions: PERMISSION[];
}
interface IDeleteRoleParams {
id: string;
}
interface IDeleteRoleResponseError {
id: string;
error: API_ERROR;
}
interface IDeleteRoleResponseSuccess {
id: string;
communityId: string;
}
interface IPostAssignRoleParams { interface IPostAssignRoleParams {
id: string; id: string;
} }
@ -78,9 +129,19 @@ export {
type IGetRoleParams, type IGetRoleParams,
type IGetRoleResponseError, type IGetRoleResponseError,
type IGetRoleResponseSuccess, type IGetRoleResponseSuccess,
type IGetRolePermissionsParams,
type IGetRolePermissionsResponseError,
type IGetRolePermissionsResponseSuccess,
type IPostCreateRoleRequest, type IPostCreateRoleRequest,
type IPostCreateRoleResponseError, type IPostCreateRoleResponseError,
type IPostCreateRoleResponseSuccess, type IPostCreateRoleResponseSuccess,
type IPatchRoleParams,
type IPatchRoleRequest,
type IPatchRoleResponseError,
type IPatchRoleResponseSuccess,
type IDeleteRoleParams,
type IDeleteRoleResponseError,
type IDeleteRoleResponseSuccess,
type IPostAssignRoleParams, type IPostAssignRoleParams,
type IPostAssignRoleRequest, type IPostAssignRoleRequest,
type IPostAssignRoleResponseError, type IPostAssignRoleResponseError,

View file

@ -3,7 +3,9 @@ import * as controller from "./user.js";
const userRoutes = async (fastify: FastifyInstance) => { const userRoutes = async (fastify: FastifyInstance) => {
fastify.get(`/:id`, controller.getUser); fastify.get(`/:id`, controller.getUser);
fastify.post(`/`, controller.postCreateUser);
fastify.patch(`/:id`, controller.patchUser); fastify.patch(`/:id`, controller.patchUser);
fastify.delete(`/:id`, controller.deleteUser);
fastify.get(`/:id/sessions`, controller.getSessions); fastify.get(`/:id/sessions`, controller.getSessions);
}; };

View file

@ -19,6 +19,27 @@ interface IGetUserResponseSuccess {
lastLogin: number; lastLogin: number;
} }
interface IPostCreateUserRequest {
username: string;
password: string;
email?: string;
description?: string;
admin?: boolean;
}
interface IPostCreateUserResponseError {
id: string;
error: API_ERROR;
}
interface IPostCreateUserResponseSuccess {
id: string;
username: string;
email: string;
description: string;
admin: boolean;
}
interface IPatchUserParams { interface IPatchUserParams {
id: string; id: string;
} }
@ -39,6 +60,19 @@ interface IPatchUserResponseSuccess {
description: string; description: string;
} }
interface IDeleteUserParams {
id: string;
}
interface IDeleteUserResponseError {
id: string;
error: API_ERROR;
}
interface IDeleteUserResponseSuccess {
id: string;
}
interface IGetSessionsParams { interface IGetSessionsParams {
id: string; id: string;
} }
@ -62,10 +96,16 @@ export {
type IGetUserParams, type IGetUserParams,
type IGetUserResponseError, type IGetUserResponseError,
type IGetUserResponseSuccess, type IGetUserResponseSuccess,
type IPostCreateUserRequest,
type IPostCreateUserResponseError,
type IPostCreateUserResponseSuccess,
type IPatchUserParams, type IPatchUserParams,
type IPatchUserRequest, type IPatchUserRequest,
type IPatchUserResponseError, type IPatchUserResponseError,
type IPatchUserResponseSuccess, type IPatchUserResponseSuccess,
type IDeleteUserParams,
type IDeleteUserResponseError,
type IDeleteUserResponseSuccess,
type IGetSessionsParams, type IGetSessionsParams,
type IGetSessionsResponseError, type IGetSessionsResponseError,
type IGetSessionsResponseSuccess, type IGetSessionsResponseSuccess,

View file

@ -3,10 +3,16 @@ import type {
IGetUserParams, IGetUserParams,
IGetUserResponseError, IGetUserResponseError,
IGetUserResponseSuccess, IGetUserResponseSuccess,
IPostCreateUserRequest,
IPostCreateUserResponseError,
IPostCreateUserResponseSuccess,
IPatchUserParams, IPatchUserParams,
IPatchUserRequest, IPatchUserRequest,
IPatchUserResponseError, IPatchUserResponseError,
IPatchUserResponseSuccess, IPatchUserResponseSuccess,
IDeleteUserParams,
IDeleteUserResponseError,
IDeleteUserResponseSuccess,
IGetSessionsParams, IGetSessionsParams,
IGetSessionsResponseError, IGetSessionsResponseError,
IGetSessionsResponseSuccess, IGetSessionsResponseSuccess,
@ -15,6 +21,8 @@ import {
getUserByIdAuth, getUserByIdAuth,
updateUserByIdAuth, updateUserByIdAuth,
getUserSessionsByIdAuth, getUserSessionsByIdAuth,
deleteUserByIdAuth,
createUserAuth,
} from "../../services/user/user.js"; } from "../../services/user/user.js";
import { API_ERROR } from "../errors.js"; import { API_ERROR } from "../errors.js";
@ -49,6 +57,27 @@ const getUser = async (request: FastifyRequest, reply: FastifyReply) => {
} as IGetUserResponseSuccess; } as IGetUserResponseSuccess;
}; };
const postCreateUser = async (request: FastifyRequest, reply: FastifyReply) => {
const createUserRequest = request.body as IPostCreateUserRequest;
const authHeader = request.headers["authorization"];
const user = await createUserAuth(createUserRequest, authHeader);
if (user === API_ERROR.ACCESS_DENIED) {
reply.status(403);
return {
error: API_ERROR.ACCESS_DENIED,
} as IPostCreateUserResponseError;
}
return {
id: user.id,
username: user.username,
email: user.email,
description: user.description,
admin: user.admin,
} as IPostCreateUserResponseSuccess;
};
const patchUser = async (request: FastifyRequest, reply: FastifyReply) => { const patchUser = async (request: FastifyRequest, reply: FastifyReply) => {
const { id } = request.params as IPatchUserParams; const { id } = request.params as IPatchUserParams;
const patchUserRequest = request.body as IPatchUserRequest; const patchUserRequest = request.body as IPatchUserRequest;
@ -77,6 +106,31 @@ const patchUser = async (request: FastifyRequest, reply: FastifyReply) => {
} as IPatchUserResponseSuccess; } as IPatchUserResponseSuccess;
}; };
const deleteUser = async (request: FastifyRequest, reply: FastifyReply) => {
const { id } = request.params as IDeleteUserParams;
const authHeader = request.headers["authorization"];
const user = await deleteUserByIdAuth(id, authHeader);
if (!user) {
reply.status(404);
return {
id: id,
error: API_ERROR.NOT_FOUND,
} as IDeleteUserResponseError;
}
if (user === API_ERROR.ACCESS_DENIED) {
reply.status(403);
return {
id: id,
error: API_ERROR.ACCESS_DENIED,
} as IDeleteUserResponseError;
}
return {
id: user.id,
} as IDeleteUserResponseSuccess;
};
const getSessions = async (request: FastifyRequest, reply: FastifyReply) => { const getSessions = async (request: FastifyRequest, reply: FastifyReply) => {
const { id } = request.params as IGetSessionsParams; const { id } = request.params as IGetSessionsParams;
const authHeader = request.headers["authorization"]; const authHeader = request.headers["authorization"];
@ -106,4 +160,4 @@ const getSessions = async (request: FastifyRequest, reply: FastifyReply) => {
} as IGetSessionsResponseSuccess; } as IGetSessionsResponseSuccess;
}; };
export { getUser, patchUser, getSessions }; export { getUser, postCreateUser, patchUser, deleteUser, getSessions };

View file

@ -4,7 +4,7 @@ import { getDB } from "../../store/store.js";
import { getUserFromAuth, isUserAllowed } from "../auth/helpers.js"; import { getUserFromAuth, isUserAllowed } from "../auth/helpers.js";
import { PERMISSION } from "../auth/permission.js"; import { PERMISSION } from "../auth/permission.js";
import { getCommunityById } from "../community/community.js"; import { getCommunityById } from "../community/community.js";
import type { ICreateChannel } from "./types.js"; import type { ICreateChannel, IUpdateChannel } from "./types.js";
const getChannelById = async (id: string): Promise<Channel | null> => { const getChannelById = async (id: string): Promise<Channel | null> => {
return await getDB().channel.findUnique({ return await getDB().channel.findUnique({
@ -24,7 +24,7 @@ const getChannelByIdAuth = async (
!(await isUserAllowed( !(await isUserAllowed(
authUser, authUser,
{ {
channel: channel, community: community,
}, },
community, community,
[PERMISSION.CHANNELS_READ], [PERMISSION.CHANNELS_READ],
@ -67,4 +67,82 @@ const createChannelAuth = async (
return await createChannel(create); return await createChannel(create);
}; };
export { getChannelById, getChannelByIdAuth, createChannel, createChannelAuth }; const updateChannelById = async (
id: string,
update: IUpdateChannel,
): Promise<Channel | null> => {
return await getDB().channel.update({
where: {
id: id,
},
data: {
...update,
},
});
};
const updateChannelByIdAuth = async (
id: string,
update: IUpdateChannel,
authHeader: string | undefined,
): Promise<Channel | null | API_ERROR.ACCESS_DENIED> => {
const authUser = await getUserFromAuth(authHeader);
const channel = await getChannelById(id);
const community = await getCommunityById(channel?.communityId ?? "");
if (
!(await isUserAllowed(
authUser,
{
community: community,
},
community,
[PERMISSION.CHANNELS_MANAGE],
))
) {
return API_ERROR.ACCESS_DENIED;
}
return await updateChannelById(id, update);
};
const deleteChannelById = async (id: string): Promise<Channel | null> => {
return await getDB().channel.delete({
where: { id: id },
});
};
const deleteChannelByIdAuth = async (
id: string,
authHeader: string | undefined,
): Promise<Channel | null | API_ERROR.ACCESS_DENIED> => {
const authUser = await getUserFromAuth(authHeader);
const channel = await getChannelById(id);
const community = await getCommunityById(channel?.communityId ?? "");
if (
!(await isUserAllowed(
authUser,
{
community: community,
},
community,
[PERMISSION.CHANNELS_MANAGE],
))
) {
return API_ERROR.ACCESS_DENIED;
}
return await deleteChannelById(id);
};
export {
getChannelById,
getChannelByIdAuth,
createChannel,
createChannelAuth,
updateChannelById,
updateChannelByIdAuth,
deleteChannelById,
deleteChannelByIdAuth,
};

View file

@ -3,4 +3,8 @@ interface ICreateChannel {
communityId: string; communityId: string;
} }
export { type ICreateChannel }; interface IUpdateChannel {
name?: string;
}
export { type ICreateChannel, type IUpdateChannel };

View file

@ -1,7 +1,11 @@
import { API_ERROR } from "../../controllers/errors.js"; import { API_ERROR } from "../../controllers/errors.js";
import type { Community, Invite, User } from "../../generated/prisma/client.js"; import type { Community, Invite, User } from "../../generated/prisma/client.js";
import { getDB } from "../../store/store.js"; import { getDB } from "../../store/store.js";
import { getUserFromAuth, isUserAllowed } from "../auth/helpers.js"; import {
getUserFromAuth,
isUserAllowed,
isUserOwnerOrAdmin,
} from "../auth/helpers.js";
import { PERMISSION } from "../auth/permission.js"; import { PERMISSION } from "../auth/permission.js";
import type { import type {
ICreateCommunity, ICreateCommunity,
@ -86,6 +90,30 @@ const updateCommunityByIdAuth = async (
return await updateCommunityById(id, update); return await updateCommunityById(id, update);
}; };
const deleteCommunityById = async (id: string): Promise<Community | null> => {
return await getDB().community.delete({
where: { id: id },
});
};
const deleteCommunityByIdAuth = async (
id: string,
authHeader: string | undefined,
): Promise<Community | null | API_ERROR.ACCESS_DENIED> => {
const authUser = await getUserFromAuth(authHeader);
const community = await getCommunityById(id);
if (
!(await isUserOwnerOrAdmin(authUser, {
community: community,
}))
) {
return API_ERROR.ACCESS_DENIED;
}
return await deleteCommunityById(id);
};
const getCommunityMembersById = async ( const getCommunityMembersById = async (
id: string, id: string,
): Promise<ICommunityMember[]> => { ): Promise<ICommunityMember[]> => {
@ -244,6 +272,8 @@ export {
createCommunityAuth, createCommunityAuth,
updateCommunityById, updateCommunityById,
updateCommunityByIdAuth, updateCommunityByIdAuth,
deleteCommunityById,
deleteCommunityByIdAuth,
getCommunityMembersById, getCommunityMembersById,
getCommunityMembersByIdAuth, getCommunityMembersByIdAuth,
getCommunityChannelsById, getCommunityChannelsById,

View file

@ -26,7 +26,7 @@ const deleteInviteByIdAuth = async (
authHeader: string | undefined, authHeader: string | undefined,
): Promise<Invite | null | API_ERROR.ACCESS_DENIED> => { ): Promise<Invite | null | API_ERROR.ACCESS_DENIED> => {
const authUser = await getUserFromAuth(authHeader); const authUser = await getUserFromAuth(authHeader);
const invite = await deleteInviteById(id); const invite = await getInviteById(id);
const community = await getCommunityById(invite?.communityId ?? ""); const community = await getCommunityById(invite?.communityId ?? "");
if ( if (
@ -42,7 +42,7 @@ const deleteInviteByIdAuth = async (
return API_ERROR.ACCESS_DENIED; return API_ERROR.ACCESS_DENIED;
} }
return invite; return await deleteInviteById(id);
}; };
const acceptInviteById = async ( const acceptInviteById = async (

View file

@ -4,7 +4,7 @@ import { getDB } from "../../store/store.js";
import { getUserFromAuth, isUserAllowed } from "../auth/helpers.js"; import { getUserFromAuth, isUserAllowed } from "../auth/helpers.js";
import { PERMISSION } from "../auth/permission.js"; import { PERMISSION } from "../auth/permission.js";
import { getCommunityById } from "../community/community.js"; import { getCommunityById } from "../community/community.js";
import type { ICreateRole } from "./types.js"; import type { ICreateRole, IUpdateRole } from "./types.js";
const getRoleById = async (id: string): Promise<Role | null> => { const getRoleById = async (id: string): Promise<Role | null> => {
return await getDB().role.findUnique({ return await getDB().role.findUnique({
@ -24,7 +24,7 @@ const getRoleByIdAuth = async (
!(await isUserAllowed( !(await isUserAllowed(
authUser, authUser,
{ {
role: role, community: community,
}, },
community, community,
[PERMISSION.ROLES_READ], [PERMISSION.ROLES_READ],
@ -67,7 +67,79 @@ const createRoleAuth = async (
return await createRole(create); return await createRole(create);
}; };
const assignRoleById = async (id: string, userId: string): Promise<Role> => { const updateRoleById = async (
id: string,
update: IUpdateRole,
): Promise<Role | null> => {
return await getDB().role.update({
where: {
id: id,
},
data: {
...update,
},
});
};
const updateRoleByIdAuth = async (
id: string,
update: IUpdateRole,
authHeader: string | undefined,
): Promise<Role | null | API_ERROR.ACCESS_DENIED> => {
const authUser = await getUserFromAuth(authHeader);
const role = await getRoleById(id);
const community = await getCommunityById(role?.communityId ?? "");
if (
!(await isUserAllowed(
authUser,
{
community: community,
},
community,
[PERMISSION.ROLES_MANAGE],
))
) {
return API_ERROR.ACCESS_DENIED;
}
return await updateRoleById(id, update);
};
const deleteRoleById = async (id: string): Promise<Role | null> => {
return await getDB().role.delete({
where: { id: id },
});
};
const deleteRoleByIdAuth = async (
id: string,
authHeader: string | undefined,
): Promise<Role | null | API_ERROR.ACCESS_DENIED> => {
const authUser = await getUserFromAuth(authHeader);
const role = await getRoleById(id);
const community = await getCommunityById(role?.communityId ?? "");
if (
!(await isUserAllowed(
authUser,
{
community: community,
},
community,
[PERMISSION.ROLES_MANAGE],
))
) {
return API_ERROR.ACCESS_DENIED;
}
return await deleteRoleById(id);
};
const assignRoleById = async (
id: string,
userId: string,
): Promise<Role | null> => {
return await getDB().role.update({ return await getDB().role.update({
where: { where: {
id: id, id: id,
@ -86,7 +158,7 @@ const assignRoleByIdAuth = async (
id: string, id: string,
userId: string, userId: string,
authHeader: string | undefined, authHeader: string | undefined,
): Promise<Role | API_ERROR.ACCESS_DENIED> => { ): Promise<Role | null | API_ERROR.ACCESS_DENIED> => {
const authUser = await getUserFromAuth(authHeader); const authUser = await getUserFromAuth(authHeader);
const role = await getRoleById(id); const role = await getRoleById(id);
const community = await getCommunityById(role?.communityId ?? ""); const community = await getCommunityById(role?.communityId ?? "");
@ -107,7 +179,10 @@ const assignRoleByIdAuth = async (
return await assignRoleById(id, userId); return await assignRoleById(id, userId);
}; };
const unassignRoleById = async (id: string, userId: string): Promise<Role> => { const unassignRoleById = async (
id: string,
userId: string,
): Promise<Role | null> => {
return await getDB().role.update({ return await getDB().role.update({
where: { where: {
id: id, id: id,
@ -126,7 +201,7 @@ const unassignRoleByIdAuth = async (
id: string, id: string,
userId: string, userId: string,
authHeader: string | undefined, authHeader: string | undefined,
): Promise<Role | API_ERROR.ACCESS_DENIED> => { ): Promise<Role | null | API_ERROR.ACCESS_DENIED> => {
const authUser = await getUserFromAuth(authHeader); const authUser = await getUserFromAuth(authHeader);
const role = await getRoleById(id); const role = await getRoleById(id);
const community = await getCommunityById(role?.communityId ?? ""); const community = await getCommunityById(role?.communityId ?? "");
@ -152,6 +227,10 @@ export {
getRoleByIdAuth, getRoleByIdAuth,
createRole, createRole,
createRoleAuth, createRoleAuth,
updateRoleById,
updateRoleByIdAuth,
deleteRoleById,
deleteRoleByIdAuth,
assignRoleById, assignRoleById,
assignRoleByIdAuth, assignRoleByIdAuth,
unassignRoleById, unassignRoleById,

View file

@ -6,4 +6,9 @@ interface ICreateRole {
permissions: PERMISSION[]; permissions: PERMISSION[];
} }
export { type ICreateRole }; interface IUpdateRole {
name?: string;
permissions?: PERMISSION[];
}
export { type ICreateRole, type IUpdateRole };

View file

@ -1,6 +1,14 @@
interface ICreateUser {
username: string;
password: string;
email?: string;
description?: string;
admin?: boolean;
}
interface IUpdateUser { interface IUpdateUser {
email?: string; email?: string;
description?: string; description?: string;
} }
export { type IUpdateUser }; export { type ICreateUser, type IUpdateUser };

View file

@ -2,7 +2,7 @@ import type { User, Session } from "../../generated/prisma/client.js";
import { getUserFromAuth, isUserOwnerOrAdmin } from "../auth/helpers.js"; import { getUserFromAuth, isUserOwnerOrAdmin } from "../auth/helpers.js";
import { getDB } from "../../store/store.js"; import { getDB } from "../../store/store.js";
import { API_ERROR } from "../../controllers/errors.js"; import { API_ERROR } from "../../controllers/errors.js";
import type { IUpdateUser } from "./types.js"; import type { ICreateUser, IUpdateUser } from "./types.js";
const getUserById = async (id: string): Promise<User | null> => { const getUserById = async (id: string): Promise<User | null> => {
return await getDB().user.findUnique({ return await getDB().user.findUnique({
@ -28,6 +28,27 @@ const getUserByIdAuth = async (
return user; return user;
}; };
const createUser = async (create: ICreateUser): Promise<User> => {
return await getDB().user.create({
data: {
...create,
},
});
};
const createUserAuth = async (
create: ICreateUser,
authHeader: string | undefined,
): Promise<User | API_ERROR.ACCESS_DENIED> => {
const authUser = await getUserFromAuth(authHeader);
if (!authUser?.admin) {
return API_ERROR.ACCESS_DENIED;
}
return await createUser(create);
};
const updateUserById = async ( const updateUserById = async (
id: string, id: string,
update: IUpdateUser, update: IUpdateUser,
@ -61,6 +82,30 @@ const updateUserByIdAuth = async (
return await updateUserById(id, update); return await updateUserById(id, update);
}; };
const deleteUserById = async (id: string): Promise<User | null> => {
return await getDB().user.delete({
where: { id: id },
});
};
const deleteUserByIdAuth = async (
id: string,
authHeader: string | undefined,
): Promise<User | null | API_ERROR.ACCESS_DENIED> => {
const authUser = await getUserFromAuth(authHeader);
const user = await getUserById(id);
if (
!(await isUserOwnerOrAdmin(authUser, {
user: user,
}))
) {
return API_ERROR.ACCESS_DENIED;
}
return await deleteUserById(id);
};
const getUserSessionsById = async (id: string): Promise<Session[] | null> => { const getUserSessionsById = async (id: string): Promise<Session[] | null> => {
return await getDB().session.findMany({ return await getDB().session.findMany({
where: { where: {
@ -91,8 +136,12 @@ const getUserSessionsByIdAuth = async (
export { export {
getUserById, getUserById,
getUserByIdAuth, getUserByIdAuth,
createUser,
createUserAuth,
updateUserById, updateUserById,
updateUserByIdAuth, updateUserByIdAuth,
deleteUserById,
deleteUserByIdAuth,
getUserSessionsById, getUserSessionsById,
getUserSessionsByIdAuth, getUserSessionsByIdAuth,
}; };

View file

@ -134,3 +134,43 @@ test("can delete session", async () => {
assert.equal(responseSessions2.id, state.userId); assert.equal(responseSessions2.id, state.userId);
assert.equal(responseSessions2.sessions.length, 1); assert.equal(responseSessions2.sessions.length, 1);
}); });
/*
test("can create user", async () => {
state.newUserName = "New User";
state.newUserPassword = "2142";
state.newUserDescription = "This is a New User";
const response = await apiPost(
`user`,
{
username: state.newUserName,
password: state.newUserPassword,
description: state.newUserDescription,
},
state.token,
);
assert.equal(validate(response.id), true);
assert.equal(response.username, state.newUserName);
assert.equal(response.description, state.newUserDescription);
assert.equal(response.admin, false);
state.newUserId = response.id;
const responseGet = await apiGet(`user/${state.newUserId}`, state.token);
assert.equal(responseGet.username, state.newUserName);
});
test("can delete user", async () => {
const responseDelete = await apiDelete(
`user/${state.newUserId}`,
{},
state.token,
);
assert.equal(responseDelete.id, state.newUserId);
const responseGet = await apiGet(`user/${state.newUserId}`, state.token);
assert.equal(responseGet.error, "NOT_FOUND");
});
*/

View file

@ -44,7 +44,7 @@ test("can create community", async () => {
state.sessionId2 = responseLogin2.id; state.sessionId2 = responseLogin2.id;
state.token2 = responseLogin2.token; state.token2 = responseLogin2.token;
const createResponse = await apiPost( const responseCreate = await apiPost(
`community`, `community`,
{ {
name: state.communityName, name: state.communityName,
@ -52,15 +52,15 @@ test("can create community", async () => {
}, },
state.token1, state.token1,
); );
state.communityId = createResponse.id; state.communityId = responseCreate.id;
assert.equal(createResponse.name, state.communityName); assert.equal(responseCreate.name, state.communityName);
assert.equal(createResponse.description, state.communityDescription); assert.equal(responseCreate.description, state.communityDescription);
assert.equal(createResponse.ownerId, state.userId1); assert.equal(responseCreate.ownerId, state.userId1);
const getResponse = await apiGet(`community/${state.communityId}`); const responseGet = await apiGet(`community/${state.communityId}`);
assert.equal(getResponse.name, state.communityName); assert.equal(responseGet.name, state.communityName);
assert.equal(getResponse.description, state.communityDescription); assert.equal(responseGet.description, state.communityDescription);
assert.equal(getResponse.ownerId, state.userId1); assert.equal(responseGet.ownerId, state.userId1);
}); });
test("shouldn't be able to create invite", async () => { test("shouldn't be able to create invite", async () => {
@ -111,14 +111,14 @@ test("can accept invite", async () => {
assert.equal(response.communityId, state.communityId); assert.equal(response.communityId, state.communityId);
assert.equal(response.communityName, state.communityName); assert.equal(response.communityName, state.communityName);
const getResponse = await apiGet( const responseGet = await apiGet(
`community/${state.communityId}/members`, `community/${state.communityId}/members`,
state.token1, state.token1,
); );
assert.equal(getResponse.id, state.communityId); assert.equal(responseGet.id, state.communityId);
assert.equal(getResponse.name, state.communityName); assert.equal(responseGet.name, state.communityName);
assert.equal(getResponse.members.length === 2, true); assert.equal(responseGet.members.length === 2, true);
}); });
test("can get invite", async () => { test("can get invite", async () => {
@ -247,6 +247,18 @@ test("can get roles", async () => {
assert.equal(response.roles.length === 1, true); assert.equal(response.roles.length === 1, true);
}); });
test("can get permissions", async () => {
const response = await apiGet(
`role/${state.roleId}/permissions`,
state.token2,
);
assert.equal(response.id, state.roleId);
assert.equal(response.name, state.roleName);
assert.equal(response.communityId, state.communityId);
assert.equal(response.permissions.length === 3, true);
});
test("can unassign role from user", async () => { test("can unassign role from user", async () => {
const response = await apiPost( const response = await apiPost(
`role/${state.roleId}/unassign`, `role/${state.roleId}/unassign`,
@ -270,3 +282,81 @@ test("shouldn't be able to get channels 2", async () => {
assert.equal(response.error, "ACCESS_DENIED"); assert.equal(response.error, "ACCESS_DENIED");
}); });
test("can update channel", async () => {
state.channelName = "Test Channel Mod";
const responsePatch = await apiPatch(
`channel/${state.channelId}`,
{
name: state.channelName,
},
state.token1,
);
assert.equal(responsePatch.id, state.channelId);
assert.equal(responsePatch.name, state.channelName);
assert.equal(responsePatch.communityId, state.communityId);
const responseGet = await apiGet(
`channel/${state.channelId}`,
state.token1,
);
assert.equal(responseGet.name, state.channelName);
});
test("can delete channel", async () => {
const responseDelete = await apiDelete(
`channel/${state.channelId}`,
{},
state.token1,
);
assert.equal(responseDelete.id, state.channelId);
const responseGet = await apiGet(
`channel/${state.channelId}`,
state.token1,
);
assert.equal(responseGet.error, "ACCESS_DENIED");
});
test("can update role", async () => {
state.roleName = "Test Role Mod";
const responsePatch = await apiPatch(
`role/${state.roleId}`,
{
name: state.roleName,
},
state.token1,
);
assert.equal(responsePatch.id, state.roleId);
assert.equal(responsePatch.name, state.roleName);
assert.equal(responsePatch.communityId, state.communityId);
const responseGet = await apiGet(`role/${state.roleId}`, state.token1);
assert.equal(responseGet.name, state.roleName);
});
test("can delete role", async () => {
const responseDelete = await apiDelete(
`role/${state.roleId}`,
{},
state.token1,
);
assert.equal(responseDelete.id, state.roleId);
const responseGet = await apiGet(`role/${state.roleId}`, state.token1);
assert.equal(responseGet.error, "ACCESS_DENIED");
});
test("can delete community", async () => {
const responseDelete = await apiDelete(
`community/${state.communityId}`,
{},
state.token1,
);
assert.equal(responseDelete.id, state.communityId);
const responseGet = await apiGet(`community/${state.communityId}`);
assert.equal(responseGet.error, "NOT_FOUND");
});