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