Skip to content
This repository has been archived by the owner on Mar 23, 2024. It is now read-only.

Commit

Permalink
feat: added websocket
Browse files Browse the repository at this point in the history
  • Loading branch information
nmashchenko committed Dec 20, 2023
1 parent 46bb3d3 commit 3cfff00
Show file tree
Hide file tree
Showing 13 changed files with 201 additions and 13 deletions.
3 changes: 3 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@uiball/loaders": "^1.3.0",
"add": "^2.0.6",
"axios": "^1.5.1",
"bufferutil": "^4.0.8",
"clsx": "^2.0.0",
"eslint": "8.46.0",
"eslint-config-next": "13.4.12",
Expand All @@ -52,9 +53,11 @@
"react-select": "^5.7.4",
"react-tooltip": "^5.21.3",
"sass": "^1.64.2",
"socket.io-client": "^4.7.2",
"sonner": "^1.0.3",
"tsparticles": "^2.12.0",
"typescript": "5.1.6",
"utf-8-validate": "^6.0.3",
"yarn": "^1.22.19"
},
"devDependencies": {
Expand Down
3 changes: 3 additions & 0 deletions client/src/shared/api/socket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { io } from 'socket.io-client';

export const socket = io('ws://localhost:3001');
44 changes: 44 additions & 0 deletions client/src/widgets/sidebar/lib/hooks/useListenToNotifications.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useEffect } from 'react';
import { useQueryClient } from '@tanstack/react-query';
import { socket } from '@/shared/api/socket';
import { IUserProtectedResponse } from '@teameights/types';

export const useSocketConnection = (user?: IUserProtectedResponse) => {
const queryClient = useQueryClient();

useEffect(() => {
if (!user) {
return;
}
const handleConnect = () => {
console.log('connected');
};

const handleDisconnect = () => {
console.log('disconnected');
};

const handleNotification = () => {
queryClient
.invalidateQueries({ queryKey: ['useGetNotifications'] })
.then(() => console.log('notification added'));
};

const setupSocketListeners = () => {
socket.on('connect', handleConnect);
socket.on('disconnect', handleDisconnect);
socket.on(`notification-${user.id}`, handleNotification);
};

const cleanupSocketListeners = () => {
socket.off('connect', handleConnect);
socket.off('disconnect', handleDisconnect);
socket.off(`notification-${user.id}`, handleNotification);
};

setupSocketListeners();

// Cleanup socket listeners when the component unmounts
return cleanupSocketListeners;
}, [user, queryClient]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export const SidebarSystemNotification: React.FC<SystemNotificationProps> = prop
</div>
<p className={styles.messageText}>{notification.data.system_message}</p>
</Flex>
{/* TODO: FIX time here, rn in UTC() but function doesn't work for it */}
<p className={styles.sendingTime}>{getElapsedTime(notification.createdAt)}</p>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export const SidebarNotificationsList: FC<NotificationsListProps> = props => {
return () => {
observer.disconnect();
};
}, [setUnreadIds]);
}, [setUnreadIds, userNotifications]);

return (
<ul className={styles.notificationsList} ref={listRef}>
Expand Down
6 changes: 4 additions & 2 deletions client/src/widgets/sidebar/ui/sidebar/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import styles from './sidebar.module.scss';
import { useGetMe, useLogout } from '@/entities/session';
import { LOGIN } from '@/shared/constant';
import { useGetNotifications } from '@/entities/session/api/useGetNotifications';
import { useSocketConnection } from '@/widgets/sidebar/lib/hooks/useListenToNotifications';

export const Sidebar: React.FC = () => {
const router = useRouter();
Expand All @@ -24,13 +25,14 @@ export const Sidebar: React.FC = () => {
const { data: notifications } = useGetNotifications();
const { mutate: logoutUser } = useLogout();

console.log(notifications);

const [isSidebarExpanded, setIsSidebarExpanded] = useState(false);
const [notificationModal, setNotificationModal] = useState(false);

const isSignedUp = !!user?.username;

// TODO: FIX THAT so only one connection to websocket exists
useSocketConnection(user);

const sidebarItemsData = React.useMemo(() => {
return getSidebarItems(user);
}, [user]);
Expand Down
107 changes: 106 additions & 1 deletion client/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3427,6 +3427,13 @@ __metadata:
languageName: node
linkType: hard

"@socket.io/component-emitter@npm:~3.1.0":
version: 3.1.0
resolution: "@socket.io/component-emitter@npm:3.1.0"
checksum: db069d95425b419de1514dffe945cc439795f6a8ef5b9465715acf5b8b50798e2c91b8719cbf5434b3fe7de179d6cdcd503c277b7871cb3dd03febb69bdd50fa
languageName: node
linkType: hard

"@storybook/addon-a11y@npm:^7.4.5":
version: 7.4.5
resolution: "@storybook/addon-a11y@npm:7.4.5"
Expand Down Expand Up @@ -7393,6 +7400,16 @@ __metadata:
languageName: node
linkType: hard

"bufferutil@npm:^4.0.8":
version: 4.0.8
resolution: "bufferutil@npm:4.0.8"
dependencies:
node-gyp: latest
node-gyp-build: ^4.3.0
checksum: 7e9a46f1867dca72fda350966eb468eca77f4d623407b0650913fadf73d5750d883147d6e5e21c56f9d3b0bdc35d5474e80a600b9f31ec781315b4d2469ef087
languageName: node
linkType: hard

"builtin-status-codes@npm:^3.0.0":
version: 3.0.0
resolution: "builtin-status-codes@npm:3.0.0"
Expand Down Expand Up @@ -7743,6 +7760,7 @@ __metadata:
"@uiball/loaders": ^1.3.0
add: ^2.0.6
axios: ^1.5.1
bufferutil: ^4.0.8
clsx: ^2.0.0
eslint: 8.46.0
eslint-config-next: 13.4.12
Expand All @@ -7765,11 +7783,13 @@ __metadata:
react-select: ^5.7.4
react-tooltip: ^5.21.3
sass: ^1.64.2
socket.io-client: ^4.7.2
sonner: ^1.0.3
storybook: 7.2.1
storybook-addon-next-router: ^4.0.2
tsparticles: ^2.12.0
typescript: 5.1.6
utf-8-validate: ^6.0.3
yarn: ^1.22.19
languageName: unknown
linkType: soft
Expand Down Expand Up @@ -8358,7 +8378,7 @@ __metadata:
languageName: node
linkType: hard

"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4":
"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:~4.3.1, debug@npm:~4.3.2":
version: 4.3.4
resolution: "debug@npm:4.3.4"
dependencies:
Expand Down Expand Up @@ -8913,6 +8933,26 @@ __metadata:
languageName: node
linkType: hard

"engine.io-client@npm:~6.5.2":
version: 6.5.3
resolution: "engine.io-client@npm:6.5.3"
dependencies:
"@socket.io/component-emitter": ~3.1.0
debug: ~4.3.1
engine.io-parser: ~5.2.1
ws: ~8.11.0
xmlhttprequest-ssl: ~2.0.0
checksum: a72596fae99afbdb899926fccdb843f8fa790c69085b881dde121285a6935da2c2c665ebe88e0e6aa4285637782df84ac882084ff4892ad2430b059fc0045db0
languageName: node
linkType: hard

"engine.io-parser@npm:~5.2.1":
version: 5.2.1
resolution: "engine.io-parser@npm:5.2.1"
checksum: 55b0e8e18500f50c1573675c53597c5552554ead08d3f30ff19fde6409e48f882a8e01f84e9772cd155c18a1d653d06f6bf57b4e1f8b834c63c9eaf3b657b88e
languageName: node
linkType: hard

"enhanced-resolve@npm:^5.12.0, enhanced-resolve@npm:^5.15.0, enhanced-resolve@npm:^5.7.0":
version: 5.15.0
resolution: "enhanced-resolve@npm:5.15.0"
Expand Down Expand Up @@ -13615,6 +13655,17 @@ __metadata:
languageName: node
linkType: hard

"node-gyp-build@npm:^4.3.0":
version: 4.7.1
resolution: "node-gyp-build@npm:4.7.1"
bin:
node-gyp-build: bin.js
node-gyp-build-optional: optional.js
node-gyp-build-test: build-test.js
checksum: 2ef8248021489db03be3e8098977cdc797b80a9b12b77c6dcb89b0dc89b8c62e6a482672ee298f61021740ae7f080fb33154cfec8fb158cec620f57b0fae87c0
languageName: node
linkType: hard

"node-gyp@npm:latest":
version: 9.4.0
resolution: "node-gyp@npm:9.4.0"
Expand Down Expand Up @@ -15874,6 +15925,28 @@ __metadata:
languageName: node
linkType: hard

"socket.io-client@npm:^4.7.2":
version: 4.7.2
resolution: "socket.io-client@npm:4.7.2"
dependencies:
"@socket.io/component-emitter": ~3.1.0
debug: ~4.3.2
engine.io-client: ~6.5.2
socket.io-parser: ~4.2.4
checksum: 8f0ab6b623e014d889bae0cd847ef7826658e8f131bd9367ee5ae4404bb52a6d7b1755b8fbe8e68799b60e92149370a732b381f913b155e40094facb135cd088
languageName: node
linkType: hard

"socket.io-parser@npm:~4.2.4":
version: 4.2.4
resolution: "socket.io-parser@npm:4.2.4"
dependencies:
"@socket.io/component-emitter": ~3.1.0
debug: ~4.3.1
checksum: 61540ef99af33e6a562b9effe0fad769bcb7ec6a301aba5a64b3a8bccb611a0abdbe25f469933ab80072582006a78ca136bf0ad8adff9c77c9953581285e2263
languageName: node
linkType: hard

"socks-proxy-agent@npm:^7.0.0":
version: 7.0.0
resolution: "socks-proxy-agent@npm:7.0.0"
Expand Down Expand Up @@ -17693,6 +17766,16 @@ __metadata:
languageName: node
linkType: hard

"utf-8-validate@npm:^6.0.3":
version: 6.0.3
resolution: "utf-8-validate@npm:6.0.3"
dependencies:
node-gyp: latest
node-gyp-build: ^4.3.0
checksum: 5e21383c81ff7469c1912119ca69d07202d944c73ddd8a54b84dddcc546b939054e5101c78c294e494d206fe93bd43428adc635a0660816b3ec9c8ec89286ac4
languageName: node
linkType: hard

"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2, util-deprecate@npm:~1.0.1":
version: 1.0.2
resolution: "util-deprecate@npm:1.0.2"
Expand Down Expand Up @@ -18155,6 +18238,21 @@ __metadata:
languageName: node
linkType: hard

"ws@npm:~8.11.0":
version: 8.11.0
resolution: "ws@npm:8.11.0"
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ^5.0.2
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
checksum: 316b33aba32f317cd217df66dbfc5b281a2f09ff36815de222bc859e3424d83766d9eb2bd4d667de658b6ab7be151f258318fb1da812416b30be13103e5b5c67
languageName: node
linkType: hard

"xml-name-validator@npm:^4.0.0":
version: 4.0.0
resolution: "xml-name-validator@npm:4.0.0"
Expand All @@ -18169,6 +18267,13 @@ __metadata:
languageName: node
linkType: hard

"xmlhttprequest-ssl@npm:~2.0.0":
version: 2.0.0
resolution: "xmlhttprequest-ssl@npm:2.0.0"
checksum: 1e98df67f004fec15754392a131343ea92e6ab5ac4d77e842378c5c4e4fd5b6a9134b169d96842cc19422d77b1606b8df84a5685562b3b698cb68441636f827e
languageName: node
linkType: hard

"xtend@npm:^4.0.2, xtend@npm:~4.0.1":
version: 4.0.2
resolution: "xtend@npm:4.0.2"
Expand Down
11 changes: 10 additions & 1 deletion server/src/modules/auth/base/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,18 @@ import { IsExist } from 'src/utils/validators/is-exists.validator';
import { IsNotExist } from 'src/utils/validators/is-not-exists.validator';
import { SessionModule } from 'src/modules/session/session.module';
import { JwtRefreshStrategy } from './strategies/jwt-refresh.strategy';
import { NotificationsService } from '../../notifications/notifications.service';
import { NotificationsModule } from '../../notifications/notifications.module';

@Module({
imports: [UsersModule, SessionModule, PassportModule, MailModule, JwtModule.register({})],
imports: [
UsersModule,
SessionModule,
PassportModule,
MailModule,
JwtModule.register({}),
NotificationsModule,
],
controllers: [AuthController],
providers: [IsExist, IsNotExist, AuthService, JwtStrategy, JwtRefreshStrategy, AnonymousStrategy],
exports: [AuthService],
Expand Down
16 changes: 16 additions & 0 deletions server/src/modules/auth/base/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { SessionService } from 'src/modules/session/session.service';
import { JwtRefreshPayloadType } from './strategies/types/jwt-refresh-payload.type';
import { Session } from 'src/modules/session/entities/session.entity';
import { JwtPayloadType } from './strategies/types/jwt-payload.type';
import { NotificationsService } from '../../notifications/notifications.service';

@Injectable()
export class AuthService {
Expand All @@ -31,6 +32,7 @@ export class AuthService {
private usersService: UsersService,
private sessionService: SessionService,
private mailService: MailService,
private notificationsService: NotificationsService,
private configService: ConfigService<AllConfigType>
) {}

Expand Down Expand Up @@ -149,6 +151,8 @@ export class AuthService {
status,
});

await this.sendWelcomeNotification(user);

user = await this.usersService.findOne({
id: user.id,
});
Expand Down Expand Up @@ -214,6 +218,8 @@ export class AuthService {
}
);

await this.sendWelcomeNotification(user);

await this.mailService.userSignUp({
to: dto.email,
data: {
Expand Down Expand Up @@ -480,4 +486,14 @@ export class AuthService {
tokenExpires,
};
}

private async sendWelcomeNotification(user: User) {
await this.notificationsService.createNotification({
receiver: user.id,
type: 'system',
data: {
system_message: 'Welcome to Teameights!',
},
});
}
}
10 changes: 5 additions & 5 deletions server/src/modules/notifications/dto/create-notification.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApiExtraModels, ApiProperty } from '@nestjs/swagger';
import { IsIn, IsNotEmpty, IsObject, IsString, ValidateNested } from 'class-validator';
import { IsIn, IsNotEmpty, IsNumber, IsObject, IsString, ValidateNested } from 'class-validator';
import { Transform, Type } from 'class-transformer';
import { lowerCaseTransformer } from '../../../utils/transformers/lower-case.transformer';
export class SystemNotificationDataDto {
Expand Down Expand Up @@ -27,10 +27,10 @@ export class SystemNotificationDataDto {

@ApiExtraModels(SystemNotificationDataDto)
export class CreateNotificationDto {
@ApiProperty({ example: 'nmashchenko' })
@Transform(lowerCaseTransformer)
@IsNotEmpty()
receiver: string;
@ApiProperty({ example: '1' })
@Transform(({ value }) => (value ? Number(value) : undefined))
@IsNumber()
receiver: number;

@ApiProperty({ enum: ['system', 'team_invitation'] })
@IsNotEmpty({ message: 'mustBeNotEmpty' })
Expand Down
Loading

0 comments on commit 3cfff00

Please sign in to comment.