Version 0.7.0
This commit is contained in:
parent
603d969972
commit
8d3b0fa7d3
44 changed files with 611 additions and 41 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "tether",
|
"name": "tether",
|
||||||
"version": "0.6.0",
|
"version": "0.7.0",
|
||||||
"description": "Communication server using the Nexlink protocol",
|
"description": "Communication server using the Nexlink protocol",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Attachment" ADD COLUMN "iv" TEXT;
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Channel" ADD COLUMN "category" TEXT,
|
||||||
|
ADD COLUMN "order" INTEGER;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Role" ADD COLUMN "color" TEXT,
|
||||||
|
ADD COLUMN "order" INTEGER;
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Role" ADD COLUMN "showInMembers" BOOLEAN;
|
||||||
9
prisma/migrations/20260120183150_order/migration.sql
Normal file
9
prisma/migrations/20260120183150_order/migration.sql
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
-- AlterTable
|
||||||
|
CREATE SEQUENCE channel_order_seq;
|
||||||
|
ALTER TABLE "Channel" ALTER COLUMN "order" SET DEFAULT nextval('channel_order_seq');
|
||||||
|
ALTER SEQUENCE channel_order_seq OWNED BY "Channel"."order";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
CREATE SEQUENCE role_order_seq;
|
||||||
|
ALTER TABLE "Role" ALTER COLUMN "order" SET DEFAULT nextval('role_order_seq');
|
||||||
|
ALTER SEQUENCE role_order_seq OWNED BY "Role"."order";
|
||||||
|
|
@ -28,21 +28,26 @@ model Channel {
|
||||||
id String @id @unique @default(uuid())
|
id String @id @unique @default(uuid())
|
||||||
name String
|
name String
|
||||||
description String?
|
description String?
|
||||||
|
category String?
|
||||||
|
order Int? @default(autoincrement())
|
||||||
community Community @relation(fields: [communityId], references: [id], onDelete: Cascade)
|
community Community @relation(fields: [communityId], references: [id], onDelete: Cascade)
|
||||||
communityId String
|
communityId String
|
||||||
creationDate DateTime @default(now())
|
|
||||||
messages Message[]
|
messages Message[]
|
||||||
|
creationDate DateTime @default(now())
|
||||||
}
|
}
|
||||||
|
|
||||||
model Role {
|
model Role {
|
||||||
id String @id @unique @default(uuid())
|
id String @id @unique @default(uuid())
|
||||||
name String
|
name String
|
||||||
description String?
|
description String?
|
||||||
community Community @relation(fields: [communityId], references: [id], onDelete: Cascade)
|
color String?
|
||||||
communityId String
|
order Int? @default(autoincrement())
|
||||||
users User[] @relation(name: "UsersRolesToUsers")
|
showInMembers Boolean?
|
||||||
permissions String[]
|
community Community @relation(fields: [communityId], references: [id], onDelete: Cascade)
|
||||||
creationDate DateTime @default(now())
|
communityId String
|
||||||
|
users User[] @relation(name: "UsersRolesToUsers")
|
||||||
|
permissions String[]
|
||||||
|
creationDate DateTime @default(now())
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
|
|
@ -115,6 +120,7 @@ model Reaction {
|
||||||
|
|
||||||
model Attachment {
|
model Attachment {
|
||||||
id String @id @unique @default(uuid())
|
id String @id @unique @default(uuid())
|
||||||
|
iv String?
|
||||||
filename String
|
filename String
|
||||||
mimetype String
|
mimetype String
|
||||||
size BigInt
|
size BigInt
|
||||||
|
|
|
||||||
35
src/controllers/announcement/announcement.ts
Normal file
35
src/controllers/announcement/announcement.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { type FastifyReply, type FastifyRequest } from "fastify";
|
||||||
|
import type {
|
||||||
|
IPostAnnouncementRequest,
|
||||||
|
IPostAnnouncementError,
|
||||||
|
} from "./types.js";
|
||||||
|
import { API_ERROR } from "../errors.js";
|
||||||
|
import { createAnnouncementAuth } from "../../services/announcement/announcement.js";
|
||||||
|
|
||||||
|
const postAnnouncement = async (
|
||||||
|
request: FastifyRequest,
|
||||||
|
reply: FastifyReply,
|
||||||
|
) => {
|
||||||
|
const { title, text } = request.body as IPostAnnouncementRequest;
|
||||||
|
const authHeader = request.headers["authorization"];
|
||||||
|
|
||||||
|
const result = await createAnnouncementAuth(
|
||||||
|
{
|
||||||
|
title: title,
|
||||||
|
text: text,
|
||||||
|
},
|
||||||
|
authHeader,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result === API_ERROR.ACCESS_DENIED) {
|
||||||
|
reply.status(403);
|
||||||
|
return {
|
||||||
|
error: API_ERROR.ACCESS_DENIED,
|
||||||
|
} as IPostAnnouncementError;
|
||||||
|
}
|
||||||
|
|
||||||
|
reply.status(200);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { postAnnouncement };
|
||||||
3
src/controllers/announcement/index.ts
Normal file
3
src/controllers/announcement/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from "./announcement.js";
|
||||||
|
export * from "./routes.js";
|
||||||
|
export * from "./types.js";
|
||||||
8
src/controllers/announcement/routes.ts
Normal file
8
src/controllers/announcement/routes.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { type FastifyInstance } from "fastify";
|
||||||
|
import * as controller from "./announcement.js";
|
||||||
|
|
||||||
|
const announcementRoutes = async (fastify: FastifyInstance) => {
|
||||||
|
fastify.post(`/`, controller.postAnnouncement);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { announcementRoutes };
|
||||||
12
src/controllers/announcement/types.ts
Normal file
12
src/controllers/announcement/types.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import type { API_ERROR } from "../errors.js";
|
||||||
|
|
||||||
|
interface IPostAnnouncementRequest {
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPostAnnouncementError {
|
||||||
|
error: API_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { type IPostAnnouncementRequest, type IPostAnnouncementError };
|
||||||
|
|
@ -6,6 +6,8 @@ const responseFullChannel = (channel: Channel) =>
|
||||||
id: channel.id,
|
id: channel.id,
|
||||||
name: channel.name,
|
name: channel.name,
|
||||||
description: channel.description,
|
description: channel.description,
|
||||||
|
category: channel.category,
|
||||||
|
order: channel.order,
|
||||||
communityId: channel.communityId,
|
communityId: channel.communityId,
|
||||||
creationDate: channel.creationDate.getTime(),
|
creationDate: channel.creationDate.getTime(),
|
||||||
}) as IChannel;
|
}) as IChannel;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ interface IChannel {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
category: string;
|
||||||
|
order: number;
|
||||||
communityId: string;
|
communityId: string;
|
||||||
creationDate: number;
|
creationDate: number;
|
||||||
}
|
}
|
||||||
|
|
@ -39,6 +41,7 @@ interface IPatchChannelParams {
|
||||||
interface IPatchChannelRequest {
|
interface IPatchChannelRequest {
|
||||||
name?: string;
|
name?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
category?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IPatchChannelResponseError {
|
interface IPatchChannelResponseError {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,14 @@ import type {
|
||||||
IGetInvitesParams,
|
IGetInvitesParams,
|
||||||
IGetInvitesResponseError,
|
IGetInvitesResponseError,
|
||||||
IGetInvitesResponseSuccess,
|
IGetInvitesResponseSuccess,
|
||||||
|
IPatchCommunityChannelOrderParams,
|
||||||
|
IPatchCommunityChannelOrderRequest,
|
||||||
|
IPatchCommunityChannelOrderResponseError,
|
||||||
|
IPatchCommunityChannelOrderResponseSuccess,
|
||||||
|
IPatchCommunityRoleOrderParams,
|
||||||
|
IPatchCommunityRoleOrderRequest,
|
||||||
|
IPatchCommunityRoleOrderResponseError,
|
||||||
|
IPatchCommunityRoleOrderResponseSuccess,
|
||||||
IPostCreateInviteParams,
|
IPostCreateInviteParams,
|
||||||
IPostCreateInviteRequest,
|
IPostCreateInviteRequest,
|
||||||
IPostCreateInviteResponseError,
|
IPostCreateInviteResponseError,
|
||||||
|
|
@ -42,12 +50,18 @@ import {
|
||||||
getCommunityMembersByIdAuth,
|
getCommunityMembersByIdAuth,
|
||||||
getCommunityRolesByIdAuth,
|
getCommunityRolesByIdAuth,
|
||||||
getCommunityInvitesByIdAuth,
|
getCommunityInvitesByIdAuth,
|
||||||
|
updateCommunityChannelOrderByIdAuth,
|
||||||
|
updateCommunityRoleOrderByIdAuth,
|
||||||
createInviteAuth,
|
createInviteAuth,
|
||||||
deleteMemberByIdAuth,
|
deleteMemberByIdAuth,
|
||||||
} 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";
|
||||||
import { responseFullCommunity } from "./helpers.js";
|
import { responseFullCommunity } from "./helpers.js";
|
||||||
|
import {
|
||||||
|
hasUnlimitedInvites,
|
||||||
|
isInviteValid,
|
||||||
|
} from "../../services/invite/invite.js";
|
||||||
|
|
||||||
const getCommunity = async (request: FastifyRequest, reply: FastifyReply) => {
|
const getCommunity = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
const { id } = request.params as IGetCommunityParams;
|
const { id } = request.params as IGetCommunityParams;
|
||||||
|
|
@ -203,6 +217,9 @@ const getChannels = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
channels: channels.map((channel) => ({
|
channels: channels.map((channel) => ({
|
||||||
id: channel.id,
|
id: channel.id,
|
||||||
name: channel.name,
|
name: channel.name,
|
||||||
|
description: channel.description,
|
||||||
|
category: channel.category,
|
||||||
|
order: channel.order,
|
||||||
})),
|
})),
|
||||||
} as IGetChannelsResponseSuccess;
|
} as IGetChannelsResponseSuccess;
|
||||||
};
|
};
|
||||||
|
|
@ -234,6 +251,9 @@ const getRoles = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
roles: roles.map((role) => ({
|
roles: roles.map((role) => ({
|
||||||
id: role.id,
|
id: role.id,
|
||||||
name: role.name,
|
name: role.name,
|
||||||
|
color: role.color,
|
||||||
|
order: role.order,
|
||||||
|
showInMembers: role.showInMembers,
|
||||||
})),
|
})),
|
||||||
} as IGetRolesResponseSuccess;
|
} as IGetRolesResponseSuccess;
|
||||||
};
|
};
|
||||||
|
|
@ -264,10 +284,66 @@ const getInvites = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
name: community.name,
|
name: community.name,
|
||||||
invites: invites.map((invite) => ({
|
invites: invites.map((invite) => ({
|
||||||
id: invite.id,
|
id: invite.id,
|
||||||
|
communityId: invite.communityId,
|
||||||
|
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,
|
||||||
})),
|
})),
|
||||||
} as IGetInvitesResponseSuccess;
|
} as IGetInvitesResponseSuccess;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const patchCommunityChannelOrder = async (
|
||||||
|
request: FastifyRequest,
|
||||||
|
reply: FastifyReply,
|
||||||
|
) => {
|
||||||
|
const { id } = request.params as IPatchCommunityChannelOrderParams;
|
||||||
|
const { order } = request.body as IPatchCommunityChannelOrderRequest;
|
||||||
|
const authHeader = request.headers["authorization"];
|
||||||
|
|
||||||
|
const result = await updateCommunityChannelOrderByIdAuth(
|
||||||
|
id,
|
||||||
|
order,
|
||||||
|
authHeader,
|
||||||
|
);
|
||||||
|
if (result === API_ERROR.ACCESS_DENIED) {
|
||||||
|
reply.status(403);
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
error: API_ERROR.ACCESS_DENIED,
|
||||||
|
} as IPatchCommunityChannelOrderResponseError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { id } as IPatchCommunityChannelOrderResponseSuccess;
|
||||||
|
};
|
||||||
|
|
||||||
|
const patchCommunityRoleOrder = async (
|
||||||
|
request: FastifyRequest,
|
||||||
|
reply: FastifyReply,
|
||||||
|
) => {
|
||||||
|
const { id } = request.params as IPatchCommunityRoleOrderParams;
|
||||||
|
const { order } = request.body as IPatchCommunityRoleOrderRequest;
|
||||||
|
const authHeader = request.headers["authorization"];
|
||||||
|
|
||||||
|
const result = await updateCommunityRoleOrderByIdAuth(
|
||||||
|
id,
|
||||||
|
order,
|
||||||
|
authHeader,
|
||||||
|
);
|
||||||
|
if (result === API_ERROR.ACCESS_DENIED) {
|
||||||
|
reply.status(403);
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
error: API_ERROR.ACCESS_DENIED,
|
||||||
|
} as IPatchCommunityRoleOrderResponseError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { id } as IPatchCommunityRoleOrderResponseSuccess;
|
||||||
|
};
|
||||||
|
|
||||||
const postCreateInvite = async (
|
const postCreateInvite = async (
|
||||||
request: FastifyRequest,
|
request: FastifyRequest,
|
||||||
reply: FastifyReply,
|
reply: FastifyReply,
|
||||||
|
|
@ -347,6 +423,8 @@ export {
|
||||||
getChannels,
|
getChannels,
|
||||||
getRoles,
|
getRoles,
|
||||||
getInvites,
|
getInvites,
|
||||||
|
patchCommunityChannelOrder,
|
||||||
|
patchCommunityRoleOrder,
|
||||||
postCreateInvite,
|
postCreateInvite,
|
||||||
deleteCommunityMember,
|
deleteCommunityMember,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ const communityRoutes = async (fastify: FastifyInstance) => {
|
||||||
fastify.get(`/:id/channels`, controller.getChannels);
|
fastify.get(`/:id/channels`, controller.getChannels);
|
||||||
fastify.get(`/:id/roles`, controller.getRoles);
|
fastify.get(`/:id/roles`, controller.getRoles);
|
||||||
fastify.get(`/:id/invites`, controller.getInvites);
|
fastify.get(`/:id/invites`, controller.getInvites);
|
||||||
|
fastify.patch(`/:id/channels/order`, controller.patchCommunityChannelOrder);
|
||||||
|
fastify.patch(`/:id/roles/order`, controller.patchCommunityRoleOrder);
|
||||||
fastify.post(`/:id/invite`, controller.postCreateInvite);
|
fastify.post(`/:id/invite`, controller.postCreateInvite);
|
||||||
fastify.delete(`/:id/members/:memberId`, controller.deleteCommunityMember);
|
fastify.delete(`/:id/members/:memberId`, controller.deleteCommunityMember);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,9 @@ interface IGetChannelsResponseSuccess {
|
||||||
interface IGetChannelsResponseChannel {
|
interface IGetChannelsResponseChannel {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
description: string;
|
||||||
|
category: string;
|
||||||
|
order: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IGetRolesParams {
|
interface IGetRolesParams {
|
||||||
|
|
@ -121,6 +124,9 @@ interface IGetRolesResponseSuccess {
|
||||||
interface IGetRolesResponseRole {
|
interface IGetRolesResponseRole {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
color: string;
|
||||||
|
order: number;
|
||||||
|
showInMembers: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IGetInvitesParams {
|
interface IGetInvitesParams {
|
||||||
|
|
@ -140,6 +146,48 @@ interface IGetInvitesResponseSuccess {
|
||||||
|
|
||||||
interface IGetInvitesResponseInvite {
|
interface IGetInvitesResponseInvite {
|
||||||
id: string;
|
id: string;
|
||||||
|
communityId: string;
|
||||||
|
valid: boolean;
|
||||||
|
unlimitedInvites: boolean;
|
||||||
|
hasExpiration: boolean;
|
||||||
|
totalInvites: number;
|
||||||
|
remainingInvites: number;
|
||||||
|
creationDate: number;
|
||||||
|
expirationDate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPatchCommunityChannelOrderParams {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPatchCommunityChannelOrderRequest {
|
||||||
|
order: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPatchCommunityChannelOrderResponseError {
|
||||||
|
id: string;
|
||||||
|
error: API_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPatchCommunityChannelOrderResponseSuccess {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPatchCommunityRoleOrderParams {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPatchCommunityRoleOrderRequest {
|
||||||
|
order: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPatchCommunityRoleOrderResponseError {
|
||||||
|
id: string;
|
||||||
|
error: API_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPatchCommunityRoleOrderResponseSuccess {
|
||||||
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IPostCreateInviteParams {
|
interface IPostCreateInviteParams {
|
||||||
|
|
@ -207,6 +255,14 @@ export {
|
||||||
type IGetInvitesResponseError,
|
type IGetInvitesResponseError,
|
||||||
type IGetInvitesResponseSuccess,
|
type IGetInvitesResponseSuccess,
|
||||||
type IGetInvitesResponseInvite,
|
type IGetInvitesResponseInvite,
|
||||||
|
type IPatchCommunityChannelOrderParams,
|
||||||
|
type IPatchCommunityChannelOrderRequest,
|
||||||
|
type IPatchCommunityChannelOrderResponseError,
|
||||||
|
type IPatchCommunityChannelOrderResponseSuccess,
|
||||||
|
type IPatchCommunityRoleOrderParams,
|
||||||
|
type IPatchCommunityRoleOrderRequest,
|
||||||
|
type IPatchCommunityRoleOrderResponseError,
|
||||||
|
type IPatchCommunityRoleOrderResponseSuccess,
|
||||||
type IPostCreateInviteParams,
|
type IPostCreateInviteParams,
|
||||||
type IPostCreateInviteRequest,
|
type IPostCreateInviteRequest,
|
||||||
type IPostCreateInviteResponseError,
|
type IPostCreateInviteResponseError,
|
||||||
|
|
|
||||||
|
|
@ -216,12 +216,13 @@ const postCreateAttachment = async (
|
||||||
request: FastifyRequest,
|
request: FastifyRequest,
|
||||||
reply: FastifyReply,
|
reply: FastifyReply,
|
||||||
) => {
|
) => {
|
||||||
const { filename, mimetype, size, communityId } =
|
const { iv, filename, mimetype, size, communityId } =
|
||||||
request.body as IPostCreateAttachmentRequest;
|
request.body as IPostCreateAttachmentRequest;
|
||||||
const authHeader = request.headers["authorization"];
|
const authHeader = request.headers["authorization"];
|
||||||
|
|
||||||
const attachment = await createAttachmentAuth(
|
const attachment = await createAttachmentAuth(
|
||||||
{
|
{
|
||||||
|
iv: iv,
|
||||||
filename: filename,
|
filename: filename,
|
||||||
mimetype: mimetype,
|
mimetype: mimetype,
|
||||||
size: size,
|
size: size,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import type { IAttachment } from "./types.js";
|
||||||
const responseFullAttachment = (attachment: AttachmentWithChunks) =>
|
const responseFullAttachment = (attachment: AttachmentWithChunks) =>
|
||||||
({
|
({
|
||||||
id: attachment.id,
|
id: attachment.id,
|
||||||
|
iv: attachment.iv,
|
||||||
filename: attachment.filename,
|
filename: attachment.filename,
|
||||||
mimetype: attachment.mimetype,
|
mimetype: attachment.mimetype,
|
||||||
size: Number(attachment.size),
|
size: Number(attachment.size),
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ interface IPostUploadCommunityAvatarResponseSuccess {
|
||||||
|
|
||||||
interface IAttachment {
|
interface IAttachment {
|
||||||
id: string;
|
id: string;
|
||||||
|
iv: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
mimetype: string;
|
mimetype: string;
|
||||||
size: number;
|
size: number;
|
||||||
|
|
@ -69,6 +70,7 @@ interface IGetAttachmentResponseError {
|
||||||
interface IGetAttachmentResponseSuccess extends IAttachment {}
|
interface IGetAttachmentResponseSuccess extends IAttachment {}
|
||||||
|
|
||||||
interface IPostCreateAttachmentRequest {
|
interface IPostCreateAttachmentRequest {
|
||||||
|
iv: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
mimetype: string;
|
mimetype: string;
|
||||||
size: number;
|
size: number;
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ interface IPatchMessageParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IPatchMessageRequest {
|
interface IPatchMessageRequest {
|
||||||
|
iv: string;
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ const responseFullRole = (role: Role) =>
|
||||||
id: role.id,
|
id: role.id,
|
||||||
name: role.name,
|
name: role.name,
|
||||||
description: role.description,
|
description: role.description,
|
||||||
|
color: role.color,
|
||||||
|
order: role.order,
|
||||||
|
showInMembers: role.showInMembers,
|
||||||
communityId: role.communityId,
|
communityId: role.communityId,
|
||||||
permissions: role.permissions,
|
permissions: role.permissions,
|
||||||
creationDate: role.creationDate.getTime(),
|
creationDate: role.creationDate.getTime(),
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import type {
|
||||||
IPostUnassignRoleRequest,
|
IPostUnassignRoleRequest,
|
||||||
IPostUnassignRoleResponseError,
|
IPostUnassignRoleResponseError,
|
||||||
IPostUnassignRoleResponseSuccess,
|
IPostUnassignRoleResponseSuccess,
|
||||||
|
IGetPermissionsResponseSuccess,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
import {
|
import {
|
||||||
assignRoleByIdAuth,
|
assignRoleByIdAuth,
|
||||||
|
|
@ -32,6 +33,7 @@ import {
|
||||||
} from "../../services/role/role.js";
|
} from "../../services/role/role.js";
|
||||||
import { API_ERROR } from "../errors.js";
|
import { API_ERROR } from "../errors.js";
|
||||||
import { responseFullRole } from "./helpers.js";
|
import { responseFullRole } from "./helpers.js";
|
||||||
|
import { PERMISSION } from "../../services/auth/permission.js";
|
||||||
|
|
||||||
const getRole = async (request: FastifyRequest, reply: FastifyReply) => {
|
const getRole = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
const { id } = request.params as IGetRoleParams;
|
const { id } = request.params as IGetRoleParams;
|
||||||
|
|
@ -178,6 +180,15 @@ const postUnassignRole = async (
|
||||||
} as IPostUnassignRoleResponseSuccess;
|
} as IPostUnassignRoleResponseSuccess;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getPermissions = async (
|
||||||
|
_request: FastifyRequest,
|
||||||
|
_reply: FastifyReply,
|
||||||
|
) => {
|
||||||
|
return {
|
||||||
|
permissions: Object.values(PERMISSION),
|
||||||
|
} as IGetPermissionsResponseSuccess;
|
||||||
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getRole,
|
getRole,
|
||||||
patchRole,
|
patchRole,
|
||||||
|
|
@ -185,4 +196,5 @@ export {
|
||||||
postCreateRole,
|
postCreateRole,
|
||||||
postAssignRole,
|
postAssignRole,
|
||||||
postUnassignRole,
|
postUnassignRole,
|
||||||
|
getPermissions,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const roleRoutes = async (fastify: FastifyInstance) => {
|
||||||
fastify.delete(`/:id`, controller.deleteRole);
|
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);
|
||||||
|
fastify.get(`/permissions`, controller.getPermissions);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { roleRoutes };
|
export { roleRoutes };
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ interface IRole {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
color: string;
|
||||||
|
order: number;
|
||||||
|
showInMembers: boolean;
|
||||||
communityId: string;
|
communityId: string;
|
||||||
permissions: string[];
|
permissions: string[];
|
||||||
creationDate: number;
|
creationDate: number;
|
||||||
|
|
@ -42,6 +45,9 @@ interface IPatchRoleParams {
|
||||||
interface IPatchRoleRequest {
|
interface IPatchRoleRequest {
|
||||||
name?: string;
|
name?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
|
color?: string;
|
||||||
|
showInMembers?: boolean;
|
||||||
|
permissions?: PERMISSION[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IPatchRoleResponseError {
|
interface IPatchRoleResponseError {
|
||||||
|
|
@ -105,6 +111,10 @@ interface IPostUnassignRoleResponseSuccess {
|
||||||
userId: string;
|
userId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IGetPermissionsResponseSuccess {
|
||||||
|
permissions: PERMISSION[];
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
type IRole,
|
type IRole,
|
||||||
type IGetRoleParams,
|
type IGetRoleParams,
|
||||||
|
|
@ -128,4 +138,5 @@ export {
|
||||||
type IPostUnassignRoleRequest,
|
type IPostUnassignRoleRequest,
|
||||||
type IPostUnassignRoleResponseError,
|
type IPostUnassignRoleResponseError,
|
||||||
type IPostUnassignRoleResponseSuccess,
|
type IPostUnassignRoleResponseSuccess,
|
||||||
|
type IGetPermissionsResponseSuccess,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,10 @@ const userRoutes = async (fastify: FastifyInstance) => {
|
||||||
fastify.delete(`/:id`, controller.deleteUser);
|
fastify.delete(`/:id`, controller.deleteUser);
|
||||||
fastify.get(`/:id/sessions`, controller.getSessions);
|
fastify.get(`/:id/sessions`, controller.getSessions);
|
||||||
fastify.get(`/:id/communities`, controller.getCommunities);
|
fastify.get(`/:id/communities`, controller.getCommunities);
|
||||||
|
fastify.get(
|
||||||
|
`/:id/community/:communityId/roles`,
|
||||||
|
controller.getCommunityRoles,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { userRoutes };
|
export { userRoutes };
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,28 @@ interface IGetCommunitiesResponseCommunity {
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface IGetCommunityRolesParams {
|
||||||
|
id: string;
|
||||||
|
communityId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IGetCommunityRolesResponseError {
|
||||||
|
id: string;
|
||||||
|
error: API_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IGetCommunityRolesResponseSuccess {
|
||||||
|
id: string;
|
||||||
|
communityId: string;
|
||||||
|
roles: IGetCommunityRolesResponseCommunity[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IGetCommunityRolesResponseCommunity {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
type IUser,
|
type IUser,
|
||||||
type IGetLoggedUserResponseError,
|
type IGetLoggedUserResponseError,
|
||||||
|
|
@ -145,4 +167,8 @@ export {
|
||||||
type IGetCommunitiesResponseError,
|
type IGetCommunitiesResponseError,
|
||||||
type IGetCommunitiesResponseSuccess,
|
type IGetCommunitiesResponseSuccess,
|
||||||
type IGetCommunitiesResponseCommunity,
|
type IGetCommunitiesResponseCommunity,
|
||||||
|
type IGetCommunityRolesParams,
|
||||||
|
type IGetCommunityRolesResponseError,
|
||||||
|
type IGetCommunityRolesResponseSuccess,
|
||||||
|
type IGetCommunityRolesResponseCommunity,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,9 @@ import type {
|
||||||
IGetCommunitiesParams,
|
IGetCommunitiesParams,
|
||||||
IGetCommunitiesResponseError,
|
IGetCommunitiesResponseError,
|
||||||
IGetCommunitiesResponseSuccess,
|
IGetCommunitiesResponseSuccess,
|
||||||
|
IGetCommunityRolesParams,
|
||||||
|
IGetCommunityRolesResponseError,
|
||||||
|
IGetCommunityRolesResponseSuccess,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
import {
|
import {
|
||||||
getUserByIdAuth,
|
getUserByIdAuth,
|
||||||
|
|
@ -30,6 +33,7 @@ import {
|
||||||
createUserAuth,
|
createUserAuth,
|
||||||
getUserCommunitiesByIdAuth,
|
getUserCommunitiesByIdAuth,
|
||||||
getLoggedUserAuth,
|
getLoggedUserAuth,
|
||||||
|
getUserCommunityRolesByIdAuth,
|
||||||
} from "../../services/user/user.js";
|
} from "../../services/user/user.js";
|
||||||
import { API_ERROR } from "../errors.js";
|
import { API_ERROR } from "../errors.js";
|
||||||
import { responseFullUser } from "./helpers.js";
|
import { responseFullUser } from "./helpers.js";
|
||||||
|
|
@ -201,6 +205,44 @@ const getCommunities = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
} as IGetCommunitiesResponseSuccess;
|
} as IGetCommunitiesResponseSuccess;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getCommunityRoles = async (
|
||||||
|
request: FastifyRequest,
|
||||||
|
reply: FastifyReply,
|
||||||
|
) => {
|
||||||
|
const { id, communityId } = request.params as IGetCommunityRolesParams;
|
||||||
|
const authHeader = request.headers["authorization"];
|
||||||
|
|
||||||
|
const roles = await getUserCommunityRolesByIdAuth(
|
||||||
|
id,
|
||||||
|
communityId,
|
||||||
|
authHeader,
|
||||||
|
);
|
||||||
|
if (!roles) {
|
||||||
|
reply.status(404);
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
error: API_ERROR.NOT_FOUND,
|
||||||
|
} as IGetCommunityRolesResponseError;
|
||||||
|
}
|
||||||
|
if (roles === API_ERROR.ACCESS_DENIED) {
|
||||||
|
reply.status(403);
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
error: API_ERROR.ACCESS_DENIED,
|
||||||
|
} as IGetCommunityRolesResponseError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
communityId: communityId,
|
||||||
|
roles: roles.map((role) => ({
|
||||||
|
id: role.id,
|
||||||
|
name: role.name,
|
||||||
|
description: role.description,
|
||||||
|
})),
|
||||||
|
} as IGetCommunityRolesResponseSuccess;
|
||||||
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getUserLogged,
|
getUserLogged,
|
||||||
getUser,
|
getUser,
|
||||||
|
|
@ -209,4 +251,5 @@ export {
|
||||||
deleteUser,
|
deleteUser,
|
||||||
getSessions,
|
getSessions,
|
||||||
getCommunities,
|
getCommunities,
|
||||||
|
getCommunityRoles,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import { config } from "./config.js";
|
||||||
import { getCookieSecret } from "./services/auth/helpers.js";
|
import { getCookieSecret } from "./services/auth/helpers.js";
|
||||||
|
|
||||||
import { testRoutes } from "./controllers/test/routes.js";
|
import { testRoutes } from "./controllers/test/routes.js";
|
||||||
|
import { announcementRoutes } from "./controllers/announcement/routes.js";
|
||||||
import { authRoutes } from "./controllers/auth/routes.js";
|
import { authRoutes } from "./controllers/auth/routes.js";
|
||||||
import { userRoutes } from "./controllers/user/routes.js";
|
import { userRoutes } from "./controllers/user/routes.js";
|
||||||
import { sessionRoutes } from "./controllers/session/routes.js";
|
import { sessionRoutes } from "./controllers/session/routes.js";
|
||||||
|
|
@ -45,6 +46,7 @@ app.register(multipart, {
|
||||||
});
|
});
|
||||||
|
|
||||||
app.register(testRoutes);
|
app.register(testRoutes);
|
||||||
|
app.register(announcementRoutes, { prefix: "/api/v1/announcement" });
|
||||||
app.register(authRoutes, { prefix: "/api/v1/auth" });
|
app.register(authRoutes, { prefix: "/api/v1/auth" });
|
||||||
app.register(userRoutes, { prefix: "/api/v1/user" });
|
app.register(userRoutes, { prefix: "/api/v1/user" });
|
||||||
app.register(sessionRoutes, { prefix: "/api/v1/session" });
|
app.register(sessionRoutes, { prefix: "/api/v1/session" });
|
||||||
|
|
|
||||||
30
src/services/announcement/announcement.ts
Normal file
30
src/services/announcement/announcement.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import { API_ERROR } from "../../controllers/errors.js";
|
||||||
|
import { getUserFromAuth } from "../auth/helpers.js";
|
||||||
|
import { SocketMessageTypes } from "../websocket/types.js";
|
||||||
|
import { sendMessageToEveryone } from "../websocket/websocket.js";
|
||||||
|
import type { ICreateAnnouncement } from "./types.js";
|
||||||
|
|
||||||
|
const createAnnouncement = async (create: ICreateAnnouncement) => {
|
||||||
|
sendMessageToEveryone({
|
||||||
|
type: SocketMessageTypes.ANNOUNCEMENT,
|
||||||
|
payload: {
|
||||||
|
title: create.title,
|
||||||
|
text: create.text,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const createAnnouncementAuth = async (
|
||||||
|
create: ICreateAnnouncement,
|
||||||
|
authHeader: string | undefined,
|
||||||
|
): Promise<void | API_ERROR.ACCESS_DENIED> => {
|
||||||
|
const authUser = await getUserFromAuth(authHeader);
|
||||||
|
|
||||||
|
if (!authUser?.admin) {
|
||||||
|
//return API_ERROR.ACCESS_DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await createAnnouncement(create);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { createAnnouncement, createAnnouncementAuth };
|
||||||
2
src/services/announcement/index.ts
Normal file
2
src/services/announcement/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./announcement.js";
|
||||||
|
export * from "./types.js";
|
||||||
6
src/services/announcement/types.ts
Normal file
6
src/services/announcement/types.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
interface ICreateAnnouncement {
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { type ICreateAnnouncement };
|
||||||
|
|
@ -4,6 +4,7 @@ import { getDB } from "../../store/store.js";
|
||||||
import {
|
import {
|
||||||
createSessionCookie,
|
createSessionCookie,
|
||||||
createToken,
|
createToken,
|
||||||
|
getRandomBytesBase64,
|
||||||
getRandomBytesHex,
|
getRandomBytesHex,
|
||||||
hashPassword,
|
hashPassword,
|
||||||
verifyPassword,
|
verifyPassword,
|
||||||
|
|
@ -77,7 +78,7 @@ const loginUser = async (login: IUserLogin): Promise<Session | null> => {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
name: sessionName,
|
name: sessionName,
|
||||||
userAgent: login.userAgent,
|
userAgent: login.userAgent,
|
||||||
storageSecret: `${getRandomBytesHex(32)};${getRandomBytesHex(12)}`,
|
storageSecret: `${getRandomBytesBase64(32)};${getRandomBytesBase64(12)}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,10 @@ const getRandomBytesHex = (amount: number) => {
|
||||||
return crypto.randomBytes(amount).toString("hex");
|
return crypto.randomBytes(amount).toString("hex");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getRandomBytesBase64 = (amount: number) => {
|
||||||
|
return crypto.randomBytes(amount).toString("base64");
|
||||||
|
};
|
||||||
|
|
||||||
const createSessionCookie = () => {
|
const createSessionCookie = () => {
|
||||||
return getRandomBytesHex(32);
|
return getRandomBytesHex(32);
|
||||||
};
|
};
|
||||||
|
|
@ -243,6 +247,7 @@ export {
|
||||||
getJwtSecret,
|
getJwtSecret,
|
||||||
getCookieSecret,
|
getCookieSecret,
|
||||||
getRandomBytesHex,
|
getRandomBytesHex,
|
||||||
|
getRandomBytesBase64,
|
||||||
createSessionCookie,
|
createSessionCookie,
|
||||||
createToken,
|
createToken,
|
||||||
verifyToken,
|
verifyToken,
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,7 @@ const getChannelMessagesById = async (
|
||||||
id: string,
|
id: string,
|
||||||
): Promise<FullMessage[] | null> => {
|
): Promise<FullMessage[] | null> => {
|
||||||
return await getDB().message.findMany({
|
return await getDB().message.findMany({
|
||||||
|
take: 50,
|
||||||
include: {
|
include: {
|
||||||
reactions: {
|
reactions: {
|
||||||
select: {
|
select: {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,9 @@ interface ICreateChannel {
|
||||||
|
|
||||||
interface IUpdateChannel {
|
interface IUpdateChannel {
|
||||||
name?: string;
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
category?: string;
|
||||||
|
order?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { type ICreateChannel, type IUpdateChannel };
|
export { type ICreateChannel, type IUpdateChannel };
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import {
|
||||||
isUserOwnerOrAdmin,
|
isUserOwnerOrAdmin,
|
||||||
} from "../auth/helpers.js";
|
} from "../auth/helpers.js";
|
||||||
import { PERMISSION } from "../auth/permission.js";
|
import { PERMISSION } from "../auth/permission.js";
|
||||||
|
import { getChannelById } from "../channel/channel.js";
|
||||||
|
import { getRoleById } from "../role/role.js";
|
||||||
import { getUserIdsInCommunity } from "../user/user.js";
|
import { getUserIdsInCommunity } from "../user/user.js";
|
||||||
import { SocketMessageTypes } from "../websocket/types.js";
|
import { SocketMessageTypes } from "../websocket/types.js";
|
||||||
import { sendMessageToUsersWS } from "../websocket/websocket.js";
|
import { sendMessageToUsersWS } from "../websocket/websocket.js";
|
||||||
|
|
@ -16,7 +18,6 @@ import type {
|
||||||
ICommunityChannel,
|
ICommunityChannel,
|
||||||
ICommunityMember,
|
ICommunityMember,
|
||||||
ICommunityRole,
|
ICommunityRole,
|
||||||
ICommunityInvite,
|
|
||||||
ICreateInvite,
|
ICreateInvite,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
|
|
||||||
|
|
@ -60,7 +61,7 @@ const updateCommunityById = async (
|
||||||
id: string,
|
id: string,
|
||||||
update: IUpdateCommunity,
|
update: IUpdateCommunity,
|
||||||
): Promise<Community | null> => {
|
): Promise<Community | null> => {
|
||||||
return await getDB().community.update({
|
const updatedCommunity = await getDB().community.update({
|
||||||
where: {
|
where: {
|
||||||
id: id,
|
id: id,
|
||||||
},
|
},
|
||||||
|
|
@ -68,6 +69,17 @@ const updateCommunityById = async (
|
||||||
...update,
|
...update,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const userIds = await getUserIdsInCommunity(id);
|
||||||
|
|
||||||
|
sendMessageToUsersWS(userIds, {
|
||||||
|
type: SocketMessageTypes.UPDATE_COMMUNITY,
|
||||||
|
payload: {
|
||||||
|
communityId: id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return updatedCommunity;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateCommunityByIdAuth = async (
|
const updateCommunityByIdAuth = async (
|
||||||
|
|
@ -133,6 +145,7 @@ const getCommunityMembersById = async (
|
||||||
id: true,
|
id: true,
|
||||||
username: true,
|
username: true,
|
||||||
nickname: true,
|
nickname: true,
|
||||||
|
avatar: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -170,6 +183,9 @@ const getCommunityChannelsById = async (
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
name: true,
|
name: true,
|
||||||
|
description: true,
|
||||||
|
category: true,
|
||||||
|
order: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -205,6 +221,9 @@ const getCommunityRolesById = async (id: string): Promise<ICommunityRole[]> => {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
name: true,
|
name: true,
|
||||||
|
color: true,
|
||||||
|
order: true,
|
||||||
|
showInMembers: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
@ -232,23 +251,18 @@ const getCommunityRolesByIdAuth = async (
|
||||||
return await getCommunityRolesById(id);
|
return await getCommunityRolesById(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCommunityInvitesById = async (
|
const getCommunityInvitesById = async (id: string): Promise<Invite[]> => {
|
||||||
id: string,
|
|
||||||
): Promise<ICommunityInvite[]> => {
|
|
||||||
return await getDB().invite.findMany({
|
return await getDB().invite.findMany({
|
||||||
where: {
|
where: {
|
||||||
communityId: id,
|
communityId: id,
|
||||||
},
|
},
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCommunityInvitesByIdAuth = async (
|
const getCommunityInvitesByIdAuth = async (
|
||||||
id: string,
|
id: string,
|
||||||
authHeader: string | undefined,
|
authHeader: string | undefined,
|
||||||
): Promise<ICommunityInvite[] | API_ERROR.ACCESS_DENIED> => {
|
): Promise<Invite[] | API_ERROR.ACCESS_DENIED> => {
|
||||||
const authUser = await getUserFromAuth(authHeader);
|
const authUser = await getUserFromAuth(authHeader);
|
||||||
const community = await getCommunityById(id);
|
const community = await getCommunityById(id);
|
||||||
|
|
||||||
|
|
@ -268,6 +282,118 @@ const getCommunityInvitesByIdAuth = async (
|
||||||
return await getCommunityInvitesById(id);
|
return await getCommunityInvitesById(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateCommunityChannelOrderById = async (id: string, order: string[]) => {
|
||||||
|
for (let i = 0; i < order.length; i++) {
|
||||||
|
const channelId = order[i];
|
||||||
|
if (!channelId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const channel = await getChannelById(channelId);
|
||||||
|
if (channel?.communityId !== id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await getDB().channel.update({
|
||||||
|
where: {
|
||||||
|
id: channelId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
order: i,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const userIds = await getUserIdsInCommunity(id);
|
||||||
|
|
||||||
|
sendMessageToUsersWS(userIds, {
|
||||||
|
type: SocketMessageTypes.UPDATE_CHANNELS,
|
||||||
|
payload: {
|
||||||
|
communityId: id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateCommunityChannelOrderByIdAuth = async (
|
||||||
|
id: string,
|
||||||
|
order: string[],
|
||||||
|
authHeader: string | undefined,
|
||||||
|
): Promise<void | API_ERROR.ACCESS_DENIED> => {
|
||||||
|
const authUser = await getUserFromAuth(authHeader);
|
||||||
|
const community = await getCommunityById(id);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(await isUserAllowed(
|
||||||
|
authUser,
|
||||||
|
{
|
||||||
|
community: community,
|
||||||
|
},
|
||||||
|
community,
|
||||||
|
[PERMISSION.CHANNELS_MANAGE],
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
return API_ERROR.ACCESS_DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateCommunityChannelOrderById(id, order);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateCommunityRoleOrderById = async (id: string, order: string[]) => {
|
||||||
|
for (let i = 0; i < order.length; i++) {
|
||||||
|
const roleId = order[i];
|
||||||
|
if (!roleId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = await getRoleById(roleId);
|
||||||
|
if (role?.communityId !== id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await getDB().role.update({
|
||||||
|
where: {
|
||||||
|
id: roleId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
order: i,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const userIds = await getUserIdsInCommunity(id);
|
||||||
|
|
||||||
|
sendMessageToUsersWS(userIds, {
|
||||||
|
type: SocketMessageTypes.UPDATE_ROLES,
|
||||||
|
payload: {
|
||||||
|
communityId: id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateCommunityRoleOrderByIdAuth = async (
|
||||||
|
id: string,
|
||||||
|
order: string[],
|
||||||
|
authHeader: string | undefined,
|
||||||
|
): Promise<void | API_ERROR.ACCESS_DENIED> => {
|
||||||
|
const authUser = await getUserFromAuth(authHeader);
|
||||||
|
const community = await getCommunityById(id);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(await isUserAllowed(
|
||||||
|
authUser,
|
||||||
|
{
|
||||||
|
community: community,
|
||||||
|
},
|
||||||
|
community,
|
||||||
|
[PERMISSION.ROLES_MANAGE],
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
return API_ERROR.ACCESS_DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateCommunityRoleOrderById(id, order);
|
||||||
|
};
|
||||||
|
|
||||||
const createInvite = async (
|
const createInvite = async (
|
||||||
id: string,
|
id: string,
|
||||||
creatorId: string,
|
creatorId: string,
|
||||||
|
|
@ -377,6 +503,10 @@ export {
|
||||||
getCommunityRolesByIdAuth,
|
getCommunityRolesByIdAuth,
|
||||||
getCommunityInvitesById,
|
getCommunityInvitesById,
|
||||||
getCommunityInvitesByIdAuth,
|
getCommunityInvitesByIdAuth,
|
||||||
|
updateCommunityChannelOrderById,
|
||||||
|
updateCommunityChannelOrderByIdAuth,
|
||||||
|
updateCommunityRoleOrderById,
|
||||||
|
updateCommunityRoleOrderByIdAuth,
|
||||||
createInvite,
|
createInvite,
|
||||||
createInviteAuth,
|
createInviteAuth,
|
||||||
deleteMemberById,
|
deleteMemberById,
|
||||||
|
|
|
||||||
|
|
@ -12,21 +12,23 @@ interface ICommunityMember {
|
||||||
id: string;
|
id: string;
|
||||||
username: string;
|
username: string;
|
||||||
nickname?: string | null;
|
nickname?: string | null;
|
||||||
avatar?: string;
|
avatar?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ICommunityChannel {
|
interface ICommunityChannel {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
description: string | null;
|
||||||
|
category: string | null;
|
||||||
|
order: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ICommunityRole {
|
interface ICommunityRole {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
}
|
color: string | null;
|
||||||
|
order: number | null;
|
||||||
interface ICommunityInvite {
|
showInMembers?: boolean | null;
|
||||||
id: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ICreateInvite {
|
interface ICreateInvite {
|
||||||
|
|
@ -41,6 +43,5 @@ export {
|
||||||
type ICommunityMember,
|
type ICommunityMember,
|
||||||
type ICommunityChannel,
|
type ICommunityChannel,
|
||||||
type ICommunityRole,
|
type ICommunityRole,
|
||||||
type ICommunityInvite,
|
|
||||||
type ICreateInvite,
|
type ICreateInvite,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ interface AttachmentWithChunks extends Attachment {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ICreateAttachment {
|
interface ICreateAttachment {
|
||||||
|
iv: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
mimetype: string;
|
mimetype: string;
|
||||||
size: number;
|
size: number;
|
||||||
|
|
|
||||||
|
|
@ -102,6 +102,10 @@ const acceptInviteByIdAuth = async (
|
||||||
return API_ERROR.ACCESS_DENIED;
|
return API_ERROR.ACCESS_DENIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isInviteValid(invite)) {
|
||||||
|
return API_ERROR.ACCESS_DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
if (await isUserInCommunity(authUser, community)) {
|
if (await isUserInCommunity(authUser, community)) {
|
||||||
return API_ERROR.ACCESS_DENIED;
|
return API_ERROR.ACCESS_DENIED;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -250,14 +250,6 @@ const deleteMessageById = async (
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const attachmentId of deletedMessage.attachments) {
|
|
||||||
await getDB().attachment.delete({
|
|
||||||
where: {
|
|
||||||
id: attachmentId.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const userIds =
|
const userIds =
|
||||||
await getUserIdsInCommunityReadMessagesPermission(communityId);
|
await getUserIdsInCommunityReadMessagesPermission(communityId);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ interface ICreateMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IUpdateMessage {
|
interface IUpdateMessage {
|
||||||
|
iv: string;
|
||||||
text: string;
|
text: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,10 @@ interface ICreateRole {
|
||||||
|
|
||||||
interface IUpdateRole {
|
interface IUpdateRole {
|
||||||
name?: string;
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
color?: string;
|
||||||
|
order?: number;
|
||||||
|
showInMembers?: boolean;
|
||||||
permissions?: PERMISSION[];
|
permissions?: PERMISSION[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,19 @@ import type {
|
||||||
User,
|
User,
|
||||||
Session,
|
Session,
|
||||||
Community,
|
Community,
|
||||||
|
Role,
|
||||||
} from "../../generated/prisma/client.js";
|
} from "../../generated/prisma/client.js";
|
||||||
import {
|
import {
|
||||||
getUserFromAuth,
|
getUserFromAuth,
|
||||||
getUserPermissions,
|
getUserPermissions,
|
||||||
|
isUserAllowed,
|
||||||
isUserOwnerOrAdmin,
|
isUserOwnerOrAdmin,
|
||||||
} from "../auth/helpers.js";
|
} 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 { ICreateUser, IUpdateUser } from "./types.js";
|
import type { ICreateUser, IUpdateUser } from "./types.js";
|
||||||
import { PERMISSION } from "../auth/permission.js";
|
import { PERMISSION } from "../auth/permission.js";
|
||||||
|
import { getCommunityById } from "../community/community.js";
|
||||||
|
|
||||||
const communitiesWithReadableUsersCache = new Map<string, Set<string>>();
|
const communitiesWithReadableUsersCache = new Map<string, Set<string>>();
|
||||||
|
|
||||||
|
|
@ -105,11 +108,7 @@ const getUserByIdAuth = async (
|
||||||
const authUser = await getUserFromAuth(authHeader);
|
const authUser = await getUserFromAuth(authHeader);
|
||||||
const user = await getUserById(id);
|
const user = await getUserById(id);
|
||||||
|
|
||||||
if (
|
if (!authUser) {
|
||||||
!(await isUserOwnerOrAdmin(authUser, {
|
|
||||||
user: user,
|
|
||||||
}))
|
|
||||||
) {
|
|
||||||
return API_ERROR.ACCESS_DENIED;
|
return API_ERROR.ACCESS_DENIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,6 +253,46 @@ const getUserCommunitiesByIdAuth = async (
|
||||||
return communities;
|
return communities;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getUserCommunityRolesById = async (
|
||||||
|
id: string,
|
||||||
|
communityId: string,
|
||||||
|
): Promise<Role[] | null> => {
|
||||||
|
return await getDB().role.findMany({
|
||||||
|
where: {
|
||||||
|
communityId: communityId,
|
||||||
|
users: {
|
||||||
|
some: {
|
||||||
|
id: id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserCommunityRolesByIdAuth = async (
|
||||||
|
id: string,
|
||||||
|
communityId: string,
|
||||||
|
authHeader: string | undefined,
|
||||||
|
): Promise<Role[] | null | API_ERROR.ACCESS_DENIED> => {
|
||||||
|
const authUser = await getUserFromAuth(authHeader);
|
||||||
|
const community = await getCommunityById(communityId);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(await isUserAllowed(
|
||||||
|
authUser,
|
||||||
|
{
|
||||||
|
community: community,
|
||||||
|
},
|
||||||
|
community,
|
||||||
|
[PERMISSION.MEMBERS_READ, PERMISSION.ROLES_READ],
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
return API_ERROR.ACCESS_DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await getUserCommunityRolesById(id, communityId);
|
||||||
|
};
|
||||||
|
|
||||||
const getUserIdsInCommunity = async (
|
const getUserIdsInCommunity = async (
|
||||||
communityId: string,
|
communityId: string,
|
||||||
): Promise<string[]> => {
|
): Promise<string[]> => {
|
||||||
|
|
@ -318,6 +357,8 @@ export {
|
||||||
getUserSessionsByIdAuth,
|
getUserSessionsByIdAuth,
|
||||||
getUserCommunitiesById,
|
getUserCommunitiesById,
|
||||||
getUserCommunitiesByIdAuth,
|
getUserCommunitiesByIdAuth,
|
||||||
|
getUserCommunityRolesById,
|
||||||
|
getUserCommunityRolesByIdAuth,
|
||||||
getUserIdsInCommunity,
|
getUserIdsInCommunity,
|
||||||
getUserIdsInCommunityReadMessagesPermission,
|
getUserIdsInCommunityReadMessagesPermission,
|
||||||
getUserIdsWithRole,
|
getUserIdsWithRole,
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ enum SocketMessageTypes {
|
||||||
ANNOUNCEMENT = "ANNOUNCEMENT",
|
ANNOUNCEMENT = "ANNOUNCEMENT",
|
||||||
SET_MESSAGE = "SET_MESSAGE",
|
SET_MESSAGE = "SET_MESSAGE",
|
||||||
DELETE_MESSAGE = "DELETE_MESSAGE",
|
DELETE_MESSAGE = "DELETE_MESSAGE",
|
||||||
|
UPDATE_COMMUNITY = "UPDATE_COMMUNITY",
|
||||||
UPDATE_CHANNELS = "UPDATE_CHANNELS",
|
UPDATE_CHANNELS = "UPDATE_CHANNELS",
|
||||||
UPDATE_ROLES = "UPDATE_ROLES",
|
UPDATE_ROLES = "UPDATE_ROLES",
|
||||||
UPDATE_MEMBERS = "UPDATE_MEMBERS",
|
UPDATE_MEMBERS = "UPDATE_MEMBERS",
|
||||||
|
|
@ -28,7 +29,7 @@ type SocketMessage =
|
||||||
type: SocketMessageTypes.ANNOUNCEMENT;
|
type: SocketMessageTypes.ANNOUNCEMENT;
|
||||||
payload: {
|
payload: {
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
text: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
|
|
@ -51,6 +52,12 @@ type SocketMessage =
|
||||||
communityId: string;
|
communityId: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: SocketMessageTypes.UPDATE_COMMUNITY;
|
||||||
|
payload: {
|
||||||
|
communityId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: SocketMessageTypes.UPDATE_ROLES;
|
type: SocketMessageTypes.UPDATE_ROLES;
|
||||||
payload: {
|
payload: {
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,14 @@ const onMessageWsHandler = (connection: ISocketConnection) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendMessageToEveryone = (message: SocketMessage) => {
|
||||||
|
userConnections?.forEach((connections) => {
|
||||||
|
connections?.forEach((connection) => {
|
||||||
|
connection.socket.send(JSON.stringify(message));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const sendMessageToUserWS = (userId: string, message: SocketMessage) => {
|
const sendMessageToUserWS = (userId: string, message: SocketMessage) => {
|
||||||
const connections = userConnections.get(userId);
|
const connections = userConnections.get(userId);
|
||||||
|
|
||||||
|
|
@ -70,6 +78,7 @@ const sendMessageToUsersWS = (userIds: string[], message: SocketMessage) => {
|
||||||
export {
|
export {
|
||||||
userConnections,
|
userConnections,
|
||||||
handleNewWebSocket,
|
handleNewWebSocket,
|
||||||
|
sendMessageToEveryone,
|
||||||
sendMessageToUserWS,
|
sendMessageToUserWS,
|
||||||
sendMessageToUsersWS,
|
sendMessageToUsersWS,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue