New endpoints; New tests; Version 0.3.0

This commit is contained in:
Aslan 2025-12-28 04:29:42 +01:00
parent 4bc3be87b4
commit 1f3a94cf38
27 changed files with 551 additions and 72 deletions

View file

@ -3,8 +3,14 @@ import type {
IGetChannelParams,
IGetChannelResponseError,
IGetChannelResponseSuccess,
IPostCreateChannelRequest,
IPostCreateChannelResponseError,
IPostCreateChannelResponseSuccess,
} from "./types.js";
import { getChannelByIdAuth } from "../../services/channel/channel.js";
import {
createChannelAuth,
getChannelByIdAuth,
} from "../../services/channel/channel.js";
import { API_ERROR } from "../errors.js";
const getChannel = async (request: FastifyRequest, reply: FastifyReply) => {
@ -35,4 +41,26 @@ const getChannel = async (request: FastifyRequest, reply: FastifyReply) => {
} as IGetChannelResponseSuccess;
};
export { getChannel };
const postCreateChannel = async (
request: FastifyRequest,
reply: FastifyReply,
) => {
const createChannelRequest = request.body as IPostCreateChannelRequest;
const authHeader = request.headers["authorization"];
const channel = await createChannelAuth(createChannelRequest, authHeader);
if (channel === API_ERROR.ACCESS_DENIED) {
reply.status(403);
return {
error: API_ERROR.ACCESS_DENIED,
} as IPostCreateChannelResponseError;
}
return {
id: channel.id,
name: channel.name,
communityId: channel.communityId,
} as IPostCreateChannelResponseSuccess;
};
export { getChannel, postCreateChannel };

View file

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

View file

@ -16,8 +16,27 @@ interface IGetChannelResponseSuccess {
creationDate: number;
}
interface IPostCreateChannelRequest {
name: string;
communityId: string;
}
interface IPostCreateChannelResponseError {
id: string;
error: API_ERROR;
}
interface IPostCreateChannelResponseSuccess {
id: string;
name: string;
communityId: string;
}
export {
type IGetChannelParams,
type IGetChannelResponseError,
type IGetChannelResponseSuccess,
type IPostCreateChannelRequest,
type IPostCreateChannelResponseError,
type IPostCreateChannelResponseSuccess,
};

View file

@ -3,6 +3,9 @@ import type {
IGetCommunityParams,
IGetCommunityResponseError,
IGetCommunityResponseSuccess,
IPostCreateCommunityRequest,
IPostCreateCommunityResponseError,
IPostCreateCommunityResponseSuccess,
IPatchCommunityParams,
IPatchCommunityRequest,
IPatchCommunityResponseError,
@ -22,12 +25,13 @@ import type {
IPostCreateInviteResponseSuccess,
} from "./types.js";
import {
createInviteAuth,
getCommunityById,
createCommunityAuth,
updateCommunityByIdAuth,
getCommunityChannelsByIdAuth,
getCommunityMembersByIdAuth,
getCommunityRolesByIdAuth,
updateCommunityByIdAuth,
createInviteAuth,
} from "../../services/community/community.js";
import { API_ERROR } from "../errors.js";
import type { ICreateInvite } from "../../services/community/types.js";
@ -48,10 +52,37 @@ const getCommunity = async (request: FastifyRequest, reply: FastifyReply) => {
id: community.id,
name: community.name,
description: community.description,
ownerId: community.ownerId,
creationDate: community.creationDate.getTime(),
} as IGetCommunityResponseSuccess;
};
const postCreateCommunity = async (
request: FastifyRequest,
reply: FastifyReply,
) => {
const createCommunityRequest = request.body as IPostCreateCommunityRequest;
const authHeader = request.headers["authorization"];
const community = await createCommunityAuth(
createCommunityRequest,
authHeader,
);
if (community === API_ERROR.ACCESS_DENIED) {
reply.status(403);
return {
error: API_ERROR.ACCESS_DENIED,
} as IPostCreateCommunityResponseError;
}
return {
id: community.id,
name: community.name,
description: community.description,
ownerId: community.ownerId,
} as IPostCreateCommunityResponseSuccess;
};
const patchCommunity = async (request: FastifyRequest, reply: FastifyReply) => {
const { id } = request.params as IPatchCommunityParams;
const patchCommunityRequest = request.body as IPatchCommunityRequest;
@ -182,13 +213,11 @@ const postCreateInvite = async (
reply: FastifyReply,
) => {
const { id } = request.params as IPostCreateInviteParams;
const { creatorId, totalInvites, expirationDate } =
const { totalInvites, expirationDate } =
request.body as IPostCreateInviteRequest;
const authHeader = request.headers["authorization"];
const createInviteData = {
creatorId: creatorId,
} as ICreateInvite;
const createInviteData = {} as ICreateInvite;
if (totalInvites) {
createInviteData.totalInvites = totalInvites;
createInviteData.remainingInvites = totalInvites;
@ -222,6 +251,7 @@ const postCreateInvite = async (
export {
getCommunity,
postCreateCommunity,
patchCommunity,
getMembers,
getChannels,

View file

@ -3,6 +3,7 @@ import * as controller from "./community.js";
const communityRoutes = async (fastify: FastifyInstance) => {
fastify.get(`/:id`, controller.getCommunity);
fastify.post(`/`, controller.postCreateCommunity);
fastify.patch(`/:id`, controller.patchCommunity);
fastify.get(`/:id/members`, controller.getMembers);
fastify.get(`/:id/channels`, controller.getChannels);

View file

@ -13,9 +13,27 @@ interface IGetCommunityResponseSuccess {
id: string;
name: string;
description: string;
ownerId: string;
creationDate: number;
}
interface IPostCreateCommunityRequest {
name: string;
description?: string;
}
interface IPostCreateCommunityResponseError {
id: string;
error: API_ERROR;
}
interface IPostCreateCommunityResponseSuccess {
id: string;
name: string;
description: string;
ownerId: string;
}
interface IPatchCommunityParams {
id: string;
}
@ -101,7 +119,6 @@ interface IPostCreateInviteParams {
}
interface IPostCreateInviteRequest {
creatorId: string;
totalInvites?: number;
expirationDate?: number;
}
@ -120,6 +137,9 @@ export {
type IGetCommunityParams,
type IGetCommunityResponseError,
type IGetCommunityResponseSuccess,
type IPostCreateCommunityRequest,
type IPostCreateCommunityResponseError,
type IPostCreateCommunityResponseSuccess,
type IPatchCommunityParams,
type IPatchCommunityRequest,
type IPatchCommunityResponseError,

View file

@ -7,7 +7,6 @@ import type {
IGetInviteResponseError,
IGetInviteResponseSuccess,
IPostAcceptInviteParams,
IPostAcceptInviteRequest,
IPostAcceptDeleteInviteResponseError,
IPostAcceptDeleteInviteResponseSuccess,
} from "./types.js";
@ -19,7 +18,7 @@ import {
isInviteValid,
} from "../../services/invite/invite.js";
import { API_ERROR } from "../errors.js";
import { getUserById } from "../../services/user/user.js";
import { getUserFromAuth } from "../../services/auth/helpers.js";
const getInvite = async (request: FastifyRequest, reply: FastifyReply) => {
const { id } = request.params as IGetInviteParams;
@ -39,6 +38,7 @@ const getInvite = async (request: FastifyRequest, reply: FastifyReply) => {
valid: isInviteValid(invite),
unlimitedInvites: hasUnlimitedInvites(invite),
hasExpiration: invite.expirationDate != null,
totalInvites: invite.totalInvites,
remainingInvites: invite.remainingInvites,
creationDate: invite.creationDate.getTime(),
expirationDate: invite.expirationDate?.getTime() ?? 0,
@ -71,21 +71,19 @@ const deleteInvite = async (request: FastifyRequest, reply: FastifyReply) => {
} as IDeleteInviteResponseSuccess;
};
const postAcceptInvite = async (
const getAcceptInvite = async (
request: FastifyRequest,
reply: FastifyReply,
) => {
const { id } = request.params as IPostAcceptInviteParams;
const { userId } = request.body as IPostAcceptInviteRequest;
const authHeader = request.headers["authorization"];
const community = await acceptInviteByIdAuth(id, authHeader);
const user = await getUserById(id);
const user = await getUserFromAuth(authHeader);
if (!community || !user) {
reply.status(404);
return {
id: id,
userId: userId,
error: API_ERROR.NOT_FOUND,
} as IPostAcceptDeleteInviteResponseError;
}
@ -93,7 +91,6 @@ const postAcceptInvite = async (
reply.status(403);
return {
id: id,
userId: userId,
error: API_ERROR.ACCESS_DENIED,
} as IPostAcceptDeleteInviteResponseError;
}
@ -107,4 +104,4 @@ const postAcceptInvite = async (
} as IPostAcceptDeleteInviteResponseSuccess;
};
export { getInvite, deleteInvite, postAcceptInvite };
export { getInvite, deleteInvite, getAcceptInvite };

View file

@ -4,7 +4,7 @@ import * as controller from "./index.js";
const inviteRoutes = async (fastify: FastifyInstance) => {
fastify.get(`/:id`, controller.getInvite);
fastify.delete(`/:id`, controller.deleteInvite);
fastify.post(`/:id/accept`, controller.postAcceptInvite);
fastify.get(`/:id/accept`, controller.getAcceptInvite);
};
export { inviteRoutes };

View file

@ -15,6 +15,7 @@ interface IGetInviteResponseSuccess {
valid: boolean;
unlimitedInvites: boolean;
hasExpiration: boolean;
totalInvites: number;
remainingInvites: number;
creationDate: number;
expirationDate: number;
@ -38,13 +39,8 @@ interface IPostAcceptInviteParams {
id: string;
}
interface IPostAcceptInviteRequest {
userId: string;
}
interface IPostAcceptDeleteInviteResponseError {
id: string;
userId: string;
error: API_ERROR;
}
@ -64,7 +60,6 @@ export {
type IDeleteInviteResponseError,
type IDeleteInviteResponseSuccess,
type IPostAcceptInviteParams,
type IPostAcceptInviteRequest,
type IPostAcceptDeleteInviteResponseError,
type IPostAcceptDeleteInviteResponseSuccess,
};

View file

@ -3,8 +3,11 @@ import type {
IGetRoleParams,
IGetRoleResponseError,
IGetRoleResponseSuccess,
IPostCreateRoleRequest,
IPostCreateRoleResponseError,
IPostCreateRoleResponseSuccess,
} from "./types.js";
import { getRoleByIdAuth } from "../../services/role/role.js";
import { createRoleAuth, getRoleByIdAuth } from "../../services/role/role.js";
import { API_ERROR } from "../errors.js";
const getRole = async (request: FastifyRequest, reply: FastifyReply) => {
@ -35,4 +38,23 @@ const getRole = async (request: FastifyRequest, reply: FastifyReply) => {
} as IGetRoleResponseSuccess;
};
export { getRole };
const postCreateRole = async (request: FastifyRequest, reply: FastifyReply) => {
const createRoleRequest = request.body as IPostCreateRoleRequest;
const authHeader = request.headers["authorization"];
const role = await createRoleAuth(createRoleRequest, authHeader);
if (role === API_ERROR.ACCESS_DENIED) {
reply.status(403);
return {
error: API_ERROR.ACCESS_DENIED,
} as IPostCreateRoleResponseError;
}
return {
id: role.id,
name: role.name,
communityId: role.communityId,
} as IPostCreateRoleResponseSuccess;
};
export { getRole, postCreateRole };

View file

@ -3,6 +3,7 @@ import * as controller from "./role.js";
const roleRoutes = async (fastify: FastifyInstance) => {
fastify.get(`/:id`, controller.getRole);
fastify.post(`/`, controller.postCreateRole);
};
export { roleRoutes };

View file

@ -1,3 +1,4 @@
import type { PERMISSION } from "../../services/auth/permission.js";
import type { API_ERROR } from "../errors.js";
interface IGetRoleParams {
@ -16,8 +17,28 @@ interface IGetRoleResponseSuccess {
creationDate: number;
}
interface IPostCreateRoleRequest {
name: string;
communityId: string;
permissions: PERMISSION[];
}
interface IPostCreateRoleResponseError {
id: string;
error: API_ERROR;
}
interface IPostCreateRoleResponseSuccess {
id: string;
name: string;
communityId: string;
}
export {
type IGetRoleParams,
type IGetRoleResponseError,
type IGetRoleResponseSuccess,
type IPostCreateRoleRequest,
type IPostCreateRoleResponseError,
type IPostCreateRoleResponseSuccess,
};

View file

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

View file

@ -1 +1,2 @@
export * from "./channel.js";
export * from "./types.js";

View file

@ -0,0 +1,6 @@
interface ICreateChannel {
name: string;
communityId: string;
}
export { type ICreateChannel };

View file

@ -3,8 +3,8 @@ import type { Community, Invite, User } from "../../generated/prisma/client.js";
import { getDB } from "../../store/store.js";
import { getUserFromAuth, isUserAllowed } from "../auth/helpers.js";
import { PERMISSION } from "../auth/permission.js";
import { getInviteById } from "../invite/invite.js";
import type {
ICreateCommunity,
ICommunityChannel,
ICommunityMember,
ICommunityRole,
@ -18,6 +18,36 @@ const getCommunityById = async (id: string): Promise<Community | null> => {
});
};
const createCommunity = async (
ownerId: string,
create: ICreateCommunity,
): Promise<Community> => {
return await getDB().community.create({
data: {
ownerId: ownerId,
...create,
members: {
connect: {
id: ownerId,
},
},
},
});
};
const createCommunityAuth = async (
create: ICreateCommunity,
authHeader: string | undefined,
): Promise<Community | API_ERROR.ACCESS_DENIED> => {
const authUser = await getUserFromAuth(authHeader);
if (!authUser) {
return API_ERROR.ACCESS_DENIED;
}
return await createCommunity(authUser.id, create);
};
const updateCommunityById = async (
id: string,
update: IUpdateCommunity,
@ -171,11 +201,13 @@ const getCommunityRolesByIdAuth = async (
const createInvite = async (
id: string,
creatorId: string,
createInviteData: ICreateInvite,
): Promise<Invite> => {
return await getDB().invite.create({
data: {
...createInviteData,
creatorId: creatorId,
communityId: id,
},
});
@ -197,16 +229,19 @@ const createInviteAuth = async (
},
community,
[PERMISSION.INVITES_CREATE],
))
)) ||
!authUser
) {
return API_ERROR.ACCESS_DENIED;
}
return await createInvite(id, createInviteData);
return await createInvite(id, authUser.id, createInviteData);
};
export {
getCommunityById,
createCommunity,
createCommunityAuth,
updateCommunityById,
updateCommunityByIdAuth,
getCommunityMembersById,

View file

@ -1,3 +1,8 @@
interface ICreateCommunity {
name: string;
description?: string;
}
interface IUpdateCommunity {
name?: string;
description?: string;
@ -19,13 +24,13 @@ interface ICommunityRole {
}
interface ICreateInvite {
creatorId: string;
totalInvites?: number;
remainingInvites?: number;
expirationDate?: Date;
}
export {
type ICreateCommunity,
type IUpdateCommunity,
type ICommunityMember,
type ICommunityChannel,

View file

@ -3,8 +3,6 @@ import {
getUserFromAuth,
isUserAllowed,
isUserInCommunity,
isUserOwnerOrAdmin,
userHasPermissions,
} from "../auth/helpers.js";
import { getDB } from "../../store/store.js";
import { getCommunityById } from "../community/community.js";

View file

@ -1 +1,2 @@
export * from "./role.js";
export * from "./types.js";

View file

@ -4,6 +4,7 @@ import { getDB } from "../../store/store.js";
import { getUserFromAuth, isUserAllowed } from "../auth/helpers.js";
import { PERMISSION } from "../auth/permission.js";
import { getCommunityById } from "../community/community.js";
import type { ICreateRole } from "./types.js";
const getRoleById = async (id: string): Promise<Role | null> => {
return await getDB().role.findUnique({
@ -35,4 +36,35 @@ const getRoleByIdAuth = async (
return role;
};
export { getRoleById, getRoleByIdAuth };
const createRole = async (create: ICreateRole): Promise<Role> => {
return await getDB().role.create({
data: {
...create,
},
});
};
const createRoleAuth = async (
create: ICreateRole,
authHeader: string | undefined,
): Promise<Role | API_ERROR.ACCESS_DENIED> => {
const authUser = await getUserFromAuth(authHeader);
const community = await getCommunityById(create.communityId);
if (
!(await isUserAllowed(
authUser,
{
community: community,
},
community,
[PERMISSION.ROLES_MANAGE],
))
) {
return API_ERROR.ACCESS_DENIED;
}
return await createRole(create);
};
export { getRoleById, getRoleByIdAuth, createRole, createRoleAuth };

View file

@ -0,0 +1,9 @@
import type { PERMISSION } from "../auth/permission.js";
interface ICreateRole {
name: string;
communityId: string;
permissions: PERMISSION[];
}
export { type ICreateRole };