diff --git a/package.json b/package.json index 968c641..5802d67 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tether", - "version": "0.2.0", + "version": "0.3.0", "description": "Communication server using the Nexlink protocol", "repository": { "type": "git", diff --git a/prisma/migrations/20251228031944_channel_community_mandatory/migration.sql b/prisma/migrations/20251228031944_channel_community_mandatory/migration.sql new file mode 100644 index 0000000..4a39a0b --- /dev/null +++ b/prisma/migrations/20251228031944_channel_community_mandatory/migration.sql @@ -0,0 +1,14 @@ +/* + Warnings: + + - Made the column `communityId` on table `Channel` required. This step will fail if there are existing NULL values in that column. + +*/ +-- DropForeignKey +ALTER TABLE "Channel" DROP CONSTRAINT "Channel_communityId_fkey"; + +-- AlterTable +ALTER TABLE "Channel" ALTER COLUMN "communityId" SET NOT NULL; + +-- AddForeignKey +ALTER TABLE "Channel" ADD CONSTRAINT "Channel_communityId_fkey" FOREIGN KEY ("communityId") REFERENCES "Community"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fb5efa6..401d22e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -22,11 +22,11 @@ model Community { } model Channel { - id String @id @unique @default(uuid()) + id String @id @unique @default(uuid()) name String - community Community? @relation(fields: [communityId], references: [id]) - communityId String? - creationDate DateTime @default(now()) + community Community @relation(fields: [communityId], references: [id]) + communityId String + creationDate DateTime @default(now()) } model Role { diff --git a/src/controllers/channel/channel.ts b/src/controllers/channel/channel.ts index 97bbe94..b4a1acc 100644 --- a/src/controllers/channel/channel.ts +++ b/src/controllers/channel/channel.ts @@ -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 }; diff --git a/src/controllers/channel/routes.ts b/src/controllers/channel/routes.ts index f9ec2c8..d643289 100644 --- a/src/controllers/channel/routes.ts +++ b/src/controllers/channel/routes.ts @@ -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 }; diff --git a/src/controllers/channel/types.ts b/src/controllers/channel/types.ts index ff8c166..8f38c49 100644 --- a/src/controllers/channel/types.ts +++ b/src/controllers/channel/types.ts @@ -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, }; diff --git a/src/controllers/community/community.ts b/src/controllers/community/community.ts index cf9768f..21cc5f8 100644 --- a/src/controllers/community/community.ts +++ b/src/controllers/community/community.ts @@ -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, diff --git a/src/controllers/community/routes.ts b/src/controllers/community/routes.ts index 975e8a8..e95a4d2 100644 --- a/src/controllers/community/routes.ts +++ b/src/controllers/community/routes.ts @@ -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); diff --git a/src/controllers/community/types.ts b/src/controllers/community/types.ts index c9c8a84..9243ce6 100644 --- a/src/controllers/community/types.ts +++ b/src/controllers/community/types.ts @@ -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, diff --git a/src/controllers/invite/invite.ts b/src/controllers/invite/invite.ts index f6c6f12..6c58234 100644 --- a/src/controllers/invite/invite.ts +++ b/src/controllers/invite/invite.ts @@ -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 }; diff --git a/src/controllers/invite/routes.ts b/src/controllers/invite/routes.ts index 712a105..7b51360 100644 --- a/src/controllers/invite/routes.ts +++ b/src/controllers/invite/routes.ts @@ -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 }; diff --git a/src/controllers/invite/types.ts b/src/controllers/invite/types.ts index e05e56f..8a49b26 100644 --- a/src/controllers/invite/types.ts +++ b/src/controllers/invite/types.ts @@ -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, }; diff --git a/src/controllers/role/role.ts b/src/controllers/role/role.ts index 4f93412..ccab7f6 100644 --- a/src/controllers/role/role.ts +++ b/src/controllers/role/role.ts @@ -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 }; diff --git a/src/controllers/role/routes.ts b/src/controllers/role/routes.ts index 0a63a03..8cbafad 100644 --- a/src/controllers/role/routes.ts +++ b/src/controllers/role/routes.ts @@ -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 }; diff --git a/src/controllers/role/types.ts b/src/controllers/role/types.ts index 8d8ffed..4c352f5 100644 --- a/src/controllers/role/types.ts +++ b/src/controllers/role/types.ts @@ -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, }; diff --git a/src/services/channel/channel.ts b/src/services/channel/channel.ts index b924a5e..f270e28 100644 --- a/src/services/channel/channel.ts +++ b/src/services/channel/channel.ts @@ -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 => { return await getDB().channel.findUnique({ @@ -35,4 +36,35 @@ const getChannelByIdAuth = async ( return channel; }; -export { getChannelById, getChannelByIdAuth }; +const createChannel = async (create: ICreateChannel): Promise => { + return await getDB().channel.create({ + data: { + ...create, + }, + }); +}; + +const createChannelAuth = async ( + create: ICreateChannel, + authHeader: string | undefined, +): Promise => { + 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 }; diff --git a/src/services/channel/index.ts b/src/services/channel/index.ts index d2f4394..10a2f26 100644 --- a/src/services/channel/index.ts +++ b/src/services/channel/index.ts @@ -1 +1,2 @@ export * from "./channel.js"; +export * from "./types.js"; diff --git a/src/services/channel/types.ts b/src/services/channel/types.ts new file mode 100644 index 0000000..97fa273 --- /dev/null +++ b/src/services/channel/types.ts @@ -0,0 +1,6 @@ +interface ICreateChannel { + name: string; + communityId: string; +} + +export { type ICreateChannel }; diff --git a/src/services/community/community.ts b/src/services/community/community.ts index dadc94a..642561d 100644 --- a/src/services/community/community.ts +++ b/src/services/community/community.ts @@ -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 => { }); }; +const createCommunity = async ( + ownerId: string, + create: ICreateCommunity, +): Promise => { + return await getDB().community.create({ + data: { + ownerId: ownerId, + ...create, + members: { + connect: { + id: ownerId, + }, + }, + }, + }); +}; + +const createCommunityAuth = async ( + create: ICreateCommunity, + authHeader: string | undefined, +): Promise => { + 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 => { 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, diff --git a/src/services/community/types.ts b/src/services/community/types.ts index fa5c501..9096244 100644 --- a/src/services/community/types.ts +++ b/src/services/community/types.ts @@ -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, diff --git a/src/services/invite/invite.ts b/src/services/invite/invite.ts index f20b8e8..23dd2f9 100644 --- a/src/services/invite/invite.ts +++ b/src/services/invite/invite.ts @@ -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"; diff --git a/src/services/role/index.ts b/src/services/role/index.ts index 5c495fc..0a75dc1 100644 --- a/src/services/role/index.ts +++ b/src/services/role/index.ts @@ -1 +1,2 @@ export * from "./role.js"; +export * from "./types.js"; diff --git a/src/services/role/role.ts b/src/services/role/role.ts index a91adb8..67d6553 100644 --- a/src/services/role/role.ts +++ b/src/services/role/role.ts @@ -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 => { return await getDB().role.findUnique({ @@ -35,4 +36,35 @@ const getRoleByIdAuth = async ( return role; }; -export { getRoleById, getRoleByIdAuth }; +const createRole = async (create: ICreateRole): Promise => { + return await getDB().role.create({ + data: { + ...create, + }, + }); +}; + +const createRoleAuth = async ( + create: ICreateRole, + authHeader: string | undefined, +): Promise => { + 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 }; diff --git a/src/services/role/types.ts b/src/services/role/types.ts new file mode 100644 index 0000000..86ae5d2 --- /dev/null +++ b/src/services/role/types.ts @@ -0,0 +1,9 @@ +import type { PERMISSION } from "../auth/permission.js"; + +interface ICreateRole { + name: string; + communityId: string; + permissions: PERMISSION[]; +} + +export { type ICreateRole }; diff --git a/tests/user.test.js b/tests/1.user.test.js similarity index 100% rename from tests/user.test.js rename to tests/1.user.test.js diff --git a/tests/2.community.test.js b/tests/2.community.test.js new file mode 100644 index 0000000..1044063 --- /dev/null +++ b/tests/2.community.test.js @@ -0,0 +1,244 @@ +import assert from "node:assert"; +import { test } from "node:test"; +import { validate } from "uuid"; +import { apiGet, apiPost, apiPatch, apiDelete } from "./api.js"; + +const state = {}; + +test("can create community", async () => { + state.communityName = "testCommunity"; + state.communityDescription = "this is a test community"; + + state.username1 = "testuser1"; + state.password1 = "8556"; + state.email1 = "testuser1@test.test"; + state.username2 = "testuser2"; + state.password2 = "8556"; + state.email2 = "testuser2@test.test"; + + const responseRegister1 = await apiPost(`auth/register`, { + username: state.username1, + password: state.password1, + email: state.email1, + }); + state.userId1 = responseRegister1.id; + + const responseRegister2 = await apiPost(`auth/register`, { + username: state.username2, + password: state.password2, + email: state.email2, + }); + state.userId2 = responseRegister2.id; + + const responseLogin1 = await apiPost(`auth/login`, { + username: state.username1, + password: state.password1, + }); + state.sessionId1 = responseLogin1.id; + state.token1 = responseLogin1.token; + + const responseLogin2 = await apiPost(`auth/login`, { + username: state.username2, + password: state.password2, + }); + state.sessionId2 = responseLogin2.id; + state.token2 = responseLogin2.token; + + const createResponse = await apiPost( + `community`, + { + name: state.communityName, + description: state.communityDescription, + }, + state.token1, + ); + state.communityId = createResponse.id; + assert.equal(createResponse.name, state.communityName); + assert.equal(createResponse.description, state.communityDescription); + assert.equal(createResponse.ownerId, state.userId1); + + const getResponse = await apiGet(`community/${state.communityId}`); + assert.equal(getResponse.name, state.communityName); + assert.equal(getResponse.description, state.communityDescription); + assert.equal(getResponse.ownerId, state.userId1); +}); + +test("shouldn't be able to create invite", async () => { + const response = await apiPost( + `community/${state.communityId}/invite`, + { + totalInvites: 1, + }, + state.token2, + ); + + assert.equal(response.error, "ACCESS_DENIED"); +}); + +test("can create invite", async () => { + const response = await apiPost( + `community/${state.communityId}/invite`, + { + totalInvites: 1, + }, + state.token1, + ); + + assert.equal(response.id, state.communityId); + assert.equal(validate(response.inviteId), true); + + state.inviteId = response.inviteId; +}); + +test("shouldn't be able to accept invite", async () => { + const response = await apiGet( + `invite/${state.inviteId}/accept`, + state.token1, + ); + + assert.equal(response.error, "ACCESS_DENIED"); +}); + +test("can accept invite", async () => { + const response = await apiGet( + `invite/${state.inviteId}/accept`, + state.token2, + ); + + assert.equal(response.id, state.inviteId); + assert.equal(response.userId, state.userId2); + assert.equal(response.userName, state.username2); + assert.equal(response.communityId, state.communityId); + assert.equal(response.communityName, state.communityName); + + const getResponse = await apiGet( + `community/${state.communityId}/members`, + state.token1, + ); + + assert.equal(getResponse.id, state.communityId); + assert.equal(getResponse.name, state.communityName); + assert.equal(getResponse.members.length === 2, true); +}); + +test("can get invite", async () => { + const response = await apiGet(`invite/${state.inviteId}`); + + assert.equal(response.id, state.inviteId); + assert.equal(response.communityId, state.communityId); + assert.equal(response.valid, false); + assert.equal(response.unlimitedInvites, false); + assert.equal(response.hasExpiration, false); + assert.equal(response.totalInvites, 1); + assert.equal(response.remainingInvites, 0); +}); + +test("shouldn't be able to get members", async () => { + const response = await apiGet( + `community/${state.communityId}/members`, + state.token2, + ); + + assert.equal(response.error, "ACCESS_DENIED"); +}); + +test("shouldn't be able to get channels", async () => { + const response = await apiGet( + `community/${state.communityId}/channels`, + state.token2, + ); + + assert.equal(response.error, "ACCESS_DENIED"); +}); + +test("shouldn't be able to get roles", async () => { + const response = await apiGet( + `community/${state.communityId}/roles`, + state.token2, + ); + + assert.equal(response.error, "ACCESS_DENIED"); +}); + +test("can create channel", async () => { + state.channelName = "Test Channel"; + + const response = await apiPost( + `channel`, + { + name: state.channelName, + communityId: state.communityId, + }, + state.token1, + ); + + assert.equal(validate(response.id), true); + assert.equal(response.name, state.channelName); + assert.equal(response.communityId, state.communityId); + + state.channelId = response.id; +}); + +test("can create role", async () => { + state.roleName = "Test Role"; + + const response = await apiPost( + `role`, + { + name: state.roleName, + communityId: state.communityId, + permissions: ["MEMBERS_READ", "CHANNELS_READ", "ROLES_READ"], + }, + state.token1, + ); + + assert.equal(validate(response.id), true); + assert.equal(response.name, state.roleName); + assert.equal(response.communityId, state.communityId); + + state.roleId = response.id; +}); + +test("can assign role to user", async () => { + const response = await apiGet( + `community/${state.communityId}/members`, + state.token2, + ); + + assert.equal(response.id, state.communityId); + assert.equal(response.name, state.communityName); + assert.equal(response.members.length === 2, true); +}); + +test("can get members", async () => { + const response = await apiGet( + `community/${state.communityId}/members`, + state.token2, + ); + + assert.equal(response.id, state.communityId); + assert.equal(response.name, state.communityName); + assert.equal(response.members.length === 2, true); +}); + +test("can get channels", async () => { + const response = await apiGet( + `community/${state.communityId}/channels`, + state.token2, + ); + + assert.equal(response.id, state.communityId); + assert.equal(response.name, state.communityName); + assert.equal(response.channels.length === 1, true); +}); + +test("can get roles", async () => { + const response = await apiGet( + `community/${state.communityId}/roles`, + state.token2, + ); + + assert.equal(response.id, state.communityId); + assert.equal(response.name, state.communityName); + assert.equal(response.roles.length === 1, true); +}); diff --git a/tests/community.test.js b/tests/community.test.js deleted file mode 100644 index 306b1d0..0000000 --- a/tests/community.test.js +++ /dev/null @@ -1,33 +0,0 @@ -import assert from "node:assert"; -import { test } from "node:test"; -import { validate } from "uuid"; -import { apiGet, apiPost, apiPatch, apiDelete } from "./api.js"; - -const state = {}; - -test("can create community", async () => { - state.communityName = "testCommunity"; - - state.username1 = "testuser1"; - state.password1 = "8556"; - state.email1 = "testuser1@test.test"; - state.username2 = "testuser2"; - state.password2 = "8556"; - state.email2 = "testuser2@test.test"; - - const response1 = await apiPost(`auth/login`, { - username: state.username1, - password: state.password1, - }); - state.sessionId1 = response1.id; - state.token1 = response1.token; - - const response2 = await apiPost(`auth/login`, { - username: state.username2, - password: state.password2, - }); - state.sessionId2 = response2.id; - state.token2 = response2.token; -}); - -// TO-DO: Create community test and code