Skip to content

Commit

Permalink
feat: leave and kick group members
Browse files Browse the repository at this point in the history
  • Loading branch information
aseerkt committed Jul 12, 2024
1 parent 67c6bb1 commit 1724b45
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 24 deletions.
63 changes: 63 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
"dotenv": "^16.4.5",
"drizzle-orm": "^0.31.2",
"express": "^4.19.2",
"helmet": "^7.1.0",
"ioredis": "^5.4.1",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21",
"morgan": "^1.10.0",
"pg": "^8.12.0",
"socket.io": "^4.7.5"
},
Expand All @@ -41,6 +43,7 @@
"@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.6",
"@types/lodash": "^4.17.6",
"@types/morgan": "^1.9.9",
"@types/node": "^20.14.5",
"@types/pg": "^8.11.6",
"drizzle-kit": "^0.22.8",
Expand Down
9 changes: 8 additions & 1 deletion server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { setupMaster, setupWorker } from '@socket.io/sticky'
import 'colors'
import cors from 'cors'
import express from 'express'
import helmet from 'helmet'
import morgan from 'morgan'
import cluster from 'node:cluster'
import { createServer } from 'node:http'
import { availableParallelism } from 'node:os'
Expand Down Expand Up @@ -61,7 +63,12 @@ const createApp = async () => {

const app = express()

app.use(cors({ origin: config.corsOrigin }), express.json())
app.use(
cors({ origin: config.corsOrigin }),
helmet(),
express.json(),
morgan(config.isProd ? 'combined' : 'dev'),
)

const server = createServer(app)

Expand Down
99 changes: 77 additions & 22 deletions server/src/modules/groups/groups.controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { db } from '@/database'
import { getPaginationParams, withPagination } from '@/database/helpers'
import { deleteGroupMembersRoles } from '@/redis/handlers'
import { deleteGroupRoles, deleteMemberRole } from '@/redis/handlers'
import { getGroupRoomId } from '@/socket/helpers'
import { TypedIOServer } from '@/socket/socket.interface'
import { notFound } from '@/utils/api'
import { badRequest, notFound } from '@/utils/api'
import {
and,
count,
Expand Down Expand Up @@ -68,6 +68,35 @@ export const getGroup: RequestHandler = async (req, res, next) => {
}
}

export const getNonGroupMembers: RequestHandler = async (req, res, next) => {
try {
const groupMembers = await db
.select({ userId: members.userId })
.from(members)
.where(eq(members.groupId, Number(req.params.groupId)))
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { password, ...columns } = getTableColumns(users)
const rows = await db
.select(columns)
.from(users)
.where(
and(
like(users.username, `%${req.query.query}%`),
notInArray(
users.id,
groupMembers.map(m => m.userId),
),
),
)
.limit(Number(req.query.limit) || 5)
.orderBy(users.username)

res.json(rows)
} catch (error) {
next(error)
}
}

export const listGroups: RequestHandler = async (req, res, next) => {
try {
const userGroupIds = await db
Expand Down Expand Up @@ -241,7 +270,7 @@ export const deleteGroup: RequestHandler = async (req, res, next) => {
}

// TODO: move these db operations to queue
await deleteGroupMembersRoles(groupId)
await deleteGroupRoles(groupId)
await db.delete(messages).where(eq(messages.groupId, groupId))
await db.delete(members).where(eq(members.groupId, groupId))

Expand All @@ -251,30 +280,56 @@ export const deleteGroup: RequestHandler = async (req, res, next) => {
}
}

export const getNonGroupMembers: RequestHandler = async (req, res, next) => {
export const leaveGroup: RequestHandler = async (req, res, next) => {
try {
const groupMembers = await db
.select({ userId: members.userId })
.from(members)
.where(eq(members.groupId, Number(req.params.groupId)))
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { password, ...columns } = getTableColumns(users)
const rows = await db
.select(columns)
.from(users)
const groupId = Number(req.params.groupId)
if (req.group?.role === 'owner' && req.user!.id === req.body.ownerId) {
return badRequest(res, 'New owner id should be of different user')
}
await db.transaction(async tx => {
if (req.group?.role === 'owner') {
await tx
.update(members)
.set({ role: 'owner' })
.where(
and(
eq(members.groupId, groupId),
eq(members.userId, req.body.newOwnerId),
),
)
}

await tx
.delete(members)
.where(
and(eq(members.groupId, groupId), eq(members.userId, req.user!.id)),
)
await deleteMemberRole(groupId, req.user!.id)
})
res.json({ message: 'Left the room successfully' })
} catch (error) {
next(error)
}
}

export const kickMember: RequestHandler = async (req, res, next) => {
try {
if (req.user!.id === Number(req.params.memberId)) {
return badRequest(res, 'Cannot kick yourself')
}
await db
.delete(members)
.where(
and(
like(users.username, `%${req.query.query}%`),
notInArray(
users.id,
groupMembers.map(m => m.userId),
),
eq(members.groupId, Number(req.params.groupId)),
eq(members.userId, Number(req.params.memberId)),
),
)
.limit(Number(req.query.limit) || 5)
.orderBy(users.username)

res.json(rows)
await deleteMemberRole(
Number(req.params.groupId),
Number(req.params.memberId),
)
res.json({ message: 'Kicked member successfully' })
} catch (error) {
next(error)
}
Expand Down
9 changes: 9 additions & 0 deletions server/src/modules/groups/groups.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
deleteGroup,
getGroup,
getNonGroupMembers,
kickMember,
leaveGroup,
listGroups,
} from './groups.controller'

Expand All @@ -22,6 +24,13 @@ router.get('/', listGroups)
router.get('/:groupId', hasGroupPermission('member'), getGroup)
router.delete('/:groupId', hasGroupPermission('owner'), deleteGroup)

router.delete('/:groupId/leave', hasGroupPermission('member'), leaveGroup)
router.delete(
'/:groupId/members/:memberId',
hasGroupPermission('admin'),
kickMember,
)

router.post('/:groupId/members', hasGroupPermission('admin'), addGroupMembers)
router.get('/:groupId/members', hasGroupPermission('member'), getGroupMembers)
router.get(
Expand Down
7 changes: 6 additions & 1 deletion server/src/redis/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,16 @@ export const getMemberRole = (groupId: number, userId: number) => {
return redisClient.hget(cacheKey, userId.toString())
}

export const deleteGroupMembersRoles = (groupId: number) => {
export const deleteGroupRoles = (groupId: number) => {
const cacheKey = redisKeys.MEMBER_ROLES(groupId)
return redisClient.hdel(cacheKey)
}

export const deleteMemberRole = (groupId: number, memberId: number) => {
const cacheKey = redisKeys.MEMBER_ROLES(groupId)
return redisClient.hdel(cacheKey, memberId.toString())
}

// ONLINE USER

export const getOnlineUsers = async () => {
Expand Down

0 comments on commit 1724b45

Please sign in to comment.