From 94f63a33f887d15c5ad1ed171069f67e62e72411 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Fri, 31 Mar 2023 13:30:48 -0400 Subject: [PATCH 01/36] update entities for messaging and update gateway --- client/src/views/TempChat.tsx | 1 + server/package-lock.json | 6 +- .../account-manager.controller.ts | 2 +- .../acccount-manager/entities/user.entity.ts | 2 +- .../guards/ws-cookie-auth.guard.ts | 1 - server/src/database-connection.service.ts | 2 +- server/src/main.ts | 1 - .../src/messages/entities/message.entity.ts | 10 ++- server/src/messages/messages.service.ts | 21 +++--- .../entities/organization.entity.ts | 4 + server/src/poc-chat/poc-chat.gateway.ts | 73 ++++++++++++++++--- server/src/poc-chat/poc-chat.module.ts | 19 ++++- .../entities/transaction.entity.ts | 13 +++- .../src/transactions/transactions.service.ts | 34 +++++++++ .../src/user-org/entities/user-org.entity.ts | 9 +-- 15 files changed, 155 insertions(+), 43 deletions(-) diff --git a/client/src/views/TempChat.tsx b/client/src/views/TempChat.tsx index 2e89e16e..ec1580e4 100644 --- a/client/src/views/TempChat.tsx +++ b/client/src/views/TempChat.tsx @@ -8,6 +8,7 @@ const TempChat = () => { useEffect(() => { const opts: Partial = { withCredentials: true, + transports: ['websocket'], }; const newSocket = io(`http://${window.location.hostname}:3002`, opts); setSocket(newSocket); diff --git a/server/package-lock.json b/server/package-lock.json index 849eb780..f98f8f38 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -60,8 +60,8 @@ "@types/passport-jwt": "^3.0.5", "@types/passport-local": "^1.0.33", "@types/supertest": "^2.0.10", - "@typescript-eslint/eslint-plugin": "^4.5.0", - "@typescript-eslint/parser": "^4.5.0", + "@typescript-eslint/eslint-plugin": "^4.15.2", + "@typescript-eslint/parser": "^4.15.2", "eslint": "^7.20.0", "eslint-config-prettier": "^8.1.0", "eslint-plugin-prettier": "^3.3.1", @@ -74,7 +74,7 @@ "ts-loader": "^8.0.17", "ts-node": "^10.7.0", "tsconfig-paths": "^3.9.0", - "typescript": "^4.5.0", + "typescript": "^4.1.5", "webpack": "^5.51.2" } }, diff --git a/server/src/acccount-manager/account-manager.controller.ts b/server/src/acccount-manager/account-manager.controller.ts index bda16198..c9e63d86 100644 --- a/server/src/acccount-manager/account-manager.controller.ts +++ b/server/src/acccount-manager/account-manager.controller.ts @@ -100,7 +100,7 @@ export class AccountManagerController { `, mailSettings: { sandboxMode: { enable: process.env.MODE !== 'production' } }, }; - await this.sendgridService.send(mail); + //await this.sendgridService.send(mail); return user; } diff --git a/server/src/acccount-manager/entities/user.entity.ts b/server/src/acccount-manager/entities/user.entity.ts index 97be97b7..3bfde443 100644 --- a/server/src/acccount-manager/entities/user.entity.ts +++ b/server/src/acccount-manager/entities/user.entity.ts @@ -46,7 +46,7 @@ export class User { @OneToMany(() => Transaction, (transaction) => transaction.donater_user) transactions: Transaction[]; - @OneToMany(() => Message, (message) => message.user) + @OneToMany(() => Message, (message) => message.sending_user) messages: Message[]; @OneToMany(() => UserOrganization, (user_org) => user_org.user) diff --git a/server/src/acccount-manager/guards/ws-cookie-auth.guard.ts b/server/src/acccount-manager/guards/ws-cookie-auth.guard.ts index 9b3bce6e..66c378a7 100644 --- a/server/src/acccount-manager/guards/ws-cookie-auth.guard.ts +++ b/server/src/acccount-manager/guards/ws-cookie-auth.guard.ts @@ -38,7 +38,6 @@ export class WsCookieGuard extends AuthGuard() { } catch (error) { return false; } - context.switchToHttp().getRequest().user = user; return Boolean(user); } diff --git a/server/src/database-connection.service.ts b/server/src/database-connection.service.ts index 8506a201..3087d43b 100644 --- a/server/src/database-connection.service.ts +++ b/server/src/database-connection.service.ts @@ -14,7 +14,7 @@ export class DatabaseConnectionService implements TypeOrmOptionsFactory { database: process.env.DATABASE_DB, synchronize: true, //shouldn't be used in production dropSchema: false, //toggle to true to clear database schema - logging: true, + logging: 'all', autoLoadEntities: true, ssl: process.env.MODE === 'production' // only require ssl when in production/ diff --git a/server/src/main.ts b/server/src/main.ts index 743f7f3e..22f0c0b0 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -29,7 +29,6 @@ async function bootstrap() { .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('docs', app, document); - await app.listen(process.env.PORT || 3001); } bootstrap(); diff --git a/server/src/messages/entities/message.entity.ts b/server/src/messages/entities/message.entity.ts index 5b8a224c..95385756 100644 --- a/server/src/messages/entities/message.entity.ts +++ b/server/src/messages/entities/message.entity.ts @@ -1,3 +1,4 @@ +import { Organization } from 'src/organizations/entities/organization.entity'; import { Entity, Column, @@ -5,6 +6,7 @@ import { ManyToOne, CreateDateColumn, JoinColumn, + OneToOne, } from 'typeorm'; import { User } from '../../acccount-manager/entities/user.entity'; @@ -24,9 +26,13 @@ export class Message { }) created_date: Date; - @ManyToOne(() => User, (user) => user.messages, { eager: true }) + @ManyToOne(() => User, (user) => user.messages) @JoinColumn() - user: User; + sending_user: User; + + @ManyToOne(() => Organization, (org) => org.messages) + @JoinColumn() + sending_org: Organization; @ManyToOne(() => Transaction, (transaction) => transaction.messages) @JoinColumn() diff --git a/server/src/messages/messages.service.ts b/server/src/messages/messages.service.ts index 4747ff91..73adc5ae 100644 --- a/server/src/messages/messages.service.ts +++ b/server/src/messages/messages.service.ts @@ -23,18 +23,21 @@ export class MessagesService { return this.messagesRepository.findOneBy({ id }); } - async findByUser(user: User): Promise { - const messages = this.messagesRepository.find({ where: { user: { id: user.id } } }); + async findByUser(user_id: number): Promise { + const messages = this.messagesRepository.find({ + where: { + sending_user: { id: user_id }, + }, + }); return messages; } - // when transactions are set up - // async findByTransaction(transaction: Transaction): Promise { - // const messages = this.messagesRepository.find({ - // where: { transaction: transaction }, - // }); - // return messages; - // } + async findByTransaction(transaction_id: number): Promise { + const messages = this.messagesRepository.find({ + where: { transaction: { id: transaction_id } }, + }); + return messages; + } async update(id: number, updateMessageDto: UpdateMessageDto): Promise { await this.messagesRepository.update(id, updateMessageDto); diff --git a/server/src/organizations/entities/organization.entity.ts b/server/src/organizations/entities/organization.entity.ts index 94c287df..6048022f 100644 --- a/server/src/organizations/entities/organization.entity.ts +++ b/server/src/organizations/entities/organization.entity.ts @@ -2,6 +2,7 @@ import { Entity, Column, PrimaryGeneratedColumn, OneToMany, JoinColumn } from 't import { UserOrganization } from '../../user-org/entities/user-org.entity'; import { Transaction } from '../../transactions/entities/transaction.entity'; +import { Message } from 'src/messages/entities/message.entity'; @Entity('organizations') export class Organization { @@ -50,6 +51,9 @@ export class Organization { @Column({ type: 'text' }) nonprofit_classification: string; + @OneToMany(() => Message, (message) => message.sending_org) + messages: Message[]; + @OneToMany(() => UserOrganization, (user_org) => user_org.organization) users: UserOrganization[]; diff --git a/server/src/poc-chat/poc-chat.gateway.ts b/server/src/poc-chat/poc-chat.gateway.ts index 7f8232c8..253d2885 100644 --- a/server/src/poc-chat/poc-chat.gateway.ts +++ b/server/src/poc-chat/poc-chat.gateway.ts @@ -8,10 +8,12 @@ import { import { PocChatService } from './poc-chat.service'; import { CreatePocChatDto } from './dto/create-poc-chat.dto'; import { Server, Socket } from 'socket.io'; -import { Request, UseGuards } from '@nestjs/common'; +import { Logger, Request, UseGuards } from '@nestjs/common'; import * as dotenv from 'dotenv'; import { WsCookieGuard } from '../acccount-manager/guards/ws-cookie-auth.guard'; - +import { TransactionsService } from 'src/transactions/transactions.service'; +import { MessagesService } from 'src/messages/messages.service'; +import { UsersService } from 'src/acccount-manager/user.service'; dotenv.config({ path: __dirname + '/../../.env' }); @WebSocketGateway(3002, { @@ -20,10 +22,14 @@ dotenv.config({ path: __dirname + '/../../.env' }); export class PocChatGateway { @WebSocketServer() server: Server; + constructor( + private readonly pocChatService: PocChatService, + private transactionsService: TransactionsService, + private messagesService: MessagesService, + private usersService: UsersService, + ) {} - constructor(private readonly pocChatService: PocChatService) {} - - @UseGuards(WsCookieGuard) + // @UseGuards(WsCookieGuard) @SubscribeMessage('createPocChat') async create( @MessageBody() createPocChatDto: CreatePocChatDto, @@ -37,7 +43,7 @@ export class PocChatGateway { this.server.emit('message', messages); } - @UseGuards(WsCookieGuard) + // @UseGuards(WsCookieGuard) @SubscribeMessage('findAllPocChat') findAll() { return this.pocChatService.findAll(); @@ -45,17 +51,64 @@ export class PocChatGateway { @SubscribeMessage('join') @UseGuards(WsCookieGuard) - joinRoom(@MessageBody('name') name: string, @ConnectedSocket() client: Socket) { - return this.pocChatService.identify(name, client.id); + joinRoom( + @MessageBody('transactionId') transactionId: number, + @MessageBody('orgId') org_id: number, + @ConnectedSocket() client: Socket, + @Request() request: Request, + ) { + const user = request['user']; + const isValid = this._can_user_join(user, transactionId, org_id); + if (isValid) { + client.join(`${transactionId}`); + Logger.log('joining room'); + client.emit('join', { success: true }); + } else { + client.emit('join', { success: false }); + } } @UseGuards(WsCookieGuard) + @SubscribeMessage('message') + async sendMessage( + @MessageBody('text') text: string, + @MessageBody('transactionId') transactionId: number, + @MessageBody('orgId') org_id: number, + @ConnectedSocket() client: Socket, + @Request() request: Request, + ) { + client + .to(`${transactionId}`) + .emit('sendMessage', { text: text, sendingUserId: request['user'].id, sendingOrgId: org_id }); + } + @SubscribeMessage('typing') - async typing( + @UseGuards(WsCookieGuard) + typing( @MessageBody('isTyping') isTyping: boolean, @ConnectedSocket() client: Socket, @Request() req: Request, ) { - client.broadcast.emit('typing', { name: req['user'].firstName, isTyping }); + Logger.log('recieved typing'); + client.emit('typing', { name: req['user'].firstName, isTyping }); + } + + async _can_user_join(user, transaction_id, org_id = null) { + const transaction = await this.transactionsService.getTransactionById(transaction_id); + if (!transaction) { + return false; + } + if (org_id) { + if (!user.organizations.reduce((acc, org) => org.id === org_id && acc, true)) { + // user id does not match org_id so return false + return false; + } + if (org_id !== transaction.donater_organizationId && org_id !== transaction.claimerId) { + return false; + } + } else if (user.id !== transaction_id.donater_userId) { + return false; + } + return true; } } diff --git a/server/src/poc-chat/poc-chat.module.ts b/server/src/poc-chat/poc-chat.module.ts index 429bed7a..99abfef1 100644 --- a/server/src/poc-chat/poc-chat.module.ts +++ b/server/src/poc-chat/poc-chat.module.ts @@ -4,9 +4,24 @@ import { PocChatGateway } from './poc-chat.gateway'; import { TypeOrmModule } from '@nestjs/typeorm'; import { PocChat } from './entities/poc-chat.entity'; import { AcccountManagerModule } from '../acccount-manager/acccount-manager.module'; +import { TransactionsService } from 'src/transactions/transactions.service'; +import { MessagesService } from 'src/messages/messages.service'; +import { Transaction } from 'src/transactions/entities/transaction.entity'; +import { Message } from 'src/messages/entities/message.entity'; @Module({ - imports: [TypeOrmModule.forFeature([PocChat]), AcccountManagerModule], - providers: [PocChatGateway, PocChatService, AcccountManagerModule], + imports: [ + TypeOrmModule.forFeature([PocChat]), + AcccountManagerModule, + TypeOrmModule.forFeature([Transaction]), + TypeOrmModule.forFeature([Message]), + ], + providers: [ + PocChatGateway, + PocChatService, + AcccountManagerModule, + TransactionsService, + MessagesService, + ], }) export class PocChatModule {} diff --git a/server/src/transactions/entities/transaction.entity.ts b/server/src/transactions/entities/transaction.entity.ts index c06c3cef..5774e260 100644 --- a/server/src/transactions/entities/transaction.entity.ts +++ b/server/src/transactions/entities/transaction.entity.ts @@ -3,11 +3,8 @@ import { CreateDateColumn, Entity, JoinColumn, - JoinTable, - ManyToMany, ManyToOne, OneToMany, - OneToOne, PrimaryGeneratedColumn, } from 'typeorm'; import { TransactionStatus } from '../transaction-status.enum'; @@ -35,10 +32,16 @@ export class Transaction { @JoinColumn() donater_user: User; + @Column({ nullable: true }) + donater_userId: number; + @ManyToOne(() => Organization, (organization) => organization.donated_transactions) @JoinColumn() donater_organization?: Organization; + @Column({ nullable: true }) + donater_organizationId: number; + @ManyToOne(() => Asset, (asset) => asset.transactions, { eager: true }) @JoinColumn() asset: Asset; @@ -47,7 +50,9 @@ export class Transaction { @JoinColumn() claimer: Organization; + @Column({ nullable: true }) // TODO: update seeder to always have claimerID + claimerId: number; + @OneToMany(() => Message, (message) => message.transaction) - @JoinColumn() messages: Message[]; } diff --git a/server/src/transactions/transactions.service.ts b/server/src/transactions/transactions.service.ts index cdcc74a0..6ec7edd3 100644 --- a/server/src/transactions/transactions.service.ts +++ b/server/src/transactions/transactions.service.ts @@ -22,6 +22,40 @@ export class TransactionsService { return this.transactionsRepository.find({ where: { ...getTransactionsDto } }); } + async find_by_user_with_messages(user_id: number): Promise { + return this.transactionsRepository.find({ + relations: { + donater_user: true, + donater_organization: true, + claimer: true, + messages: true, + }, + where: { + donater_user: { + id: user_id, + }, + }, + }); + } + + async find_by_org_with_messages(org_id: number): Promise { + return this.transactionsRepository.find({ + relations: { donater_user: true, donater_organization: true, claimer: true, messages: true }, + where: [ + { + donater_organization: { + id: org_id, + }, + }, + { + claimer: { + id: org_id, + }, + }, + ], + }); + } + async getTransactionById(id: number): Promise { const found = await this.transactionsRepository.findOneBy({ id }); if (!found) { diff --git a/server/src/user-org/entities/user-org.entity.ts b/server/src/user-org/entities/user-org.entity.ts index 51251843..aaa37fe7 100644 --- a/server/src/user-org/entities/user-org.entity.ts +++ b/server/src/user-org/entities/user-org.entity.ts @@ -1,11 +1,4 @@ -import { - Entity, - Column, - PrimaryGeneratedColumn, - ManyToOne, - JoinColumn, - CreateDateColumn, -} from 'typeorm'; +import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, CreateDateColumn } from 'typeorm'; import { Organization } from '../../organizations/entities/organization.entity'; import { User } from '../../acccount-manager/entities/user.entity'; From 525c8505d53ddd74a8080b976c9ecacca466d0dc Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Wed, 5 Apr 2023 18:00:19 -0400 Subject: [PATCH 02/36] update with new "inbox" methods --- server/src/poc-chat/poc-chat.gateway.ts | 13 ++-- .../entities/transaction.entity.ts | 6 +- .../src/transactions/transactions.service.ts | 66 ++++++++++--------- 3 files changed, 47 insertions(+), 38 deletions(-) diff --git a/server/src/poc-chat/poc-chat.gateway.ts b/server/src/poc-chat/poc-chat.gateway.ts index 253d2885..5ecab658 100644 --- a/server/src/poc-chat/poc-chat.gateway.ts +++ b/server/src/poc-chat/poc-chat.gateway.ts @@ -61,7 +61,6 @@ export class PocChatGateway { const isValid = this._can_user_join(user, transactionId, org_id); if (isValid) { client.join(`${transactionId}`); - Logger.log('joining room'); client.emit('join', { success: true }); } else { client.emit('join', { success: false }); @@ -77,9 +76,13 @@ export class PocChatGateway { @ConnectedSocket() client: Socket, @Request() request: Request, ) { - client - .to(`${transactionId}`) - .emit('sendMessage', { text: text, sendingUserId: request['user'].id, sendingOrgId: org_id }); + if (client.rooms.has(`${transactionId}`)) { + client.to(`${transactionId}`).emit('sendMessage', { + text: text, + sendingUserId: request['user'].id, + sendingOrgId: org_id, + }); + } } @SubscribeMessage('typing') @@ -103,7 +106,7 @@ export class PocChatGateway { // user id does not match org_id so return false return false; } - if (org_id !== transaction.donater_organizationId && org_id !== transaction.claimerId) { + if (org_id !== transaction.donaterOrganizationId && org_id !== transaction.claimerId) { return false; } } else if (user.id !== transaction_id.donater_userId) { diff --git a/server/src/transactions/entities/transaction.entity.ts b/server/src/transactions/entities/transaction.entity.ts index 5774e260..d078dc5d 100644 --- a/server/src/transactions/entities/transaction.entity.ts +++ b/server/src/transactions/entities/transaction.entity.ts @@ -33,14 +33,14 @@ export class Transaction { donater_user: User; @Column({ nullable: true }) - donater_userId: number; + donaterUserId: number; @ManyToOne(() => Organization, (organization) => organization.donated_transactions) @JoinColumn() donater_organization?: Organization; @Column({ nullable: true }) - donater_organizationId: number; + donaterOrganizationId: number; @ManyToOne(() => Asset, (asset) => asset.transactions, { eager: true }) @JoinColumn() @@ -53,6 +53,6 @@ export class Transaction { @Column({ nullable: true }) // TODO: update seeder to always have claimerID claimerId: number; - @OneToMany(() => Message, (message) => message.transaction) + @OneToMany(() => Message, (message) => message.transaction, { eager: true }) messages: Message[]; } diff --git a/server/src/transactions/transactions.service.ts b/server/src/transactions/transactions.service.ts index 6ec7edd3..a0b9e5ed 100644 --- a/server/src/transactions/transactions.service.ts +++ b/server/src/transactions/transactions.service.ts @@ -22,38 +22,44 @@ export class TransactionsService { return this.transactionsRepository.find({ where: { ...getTransactionsDto } }); } - async find_by_user_with_messages(user_id: number): Promise { - return this.transactionsRepository.find({ - relations: { - donater_user: true, - donater_organization: true, - claimer: true, - messages: true, - }, - where: { - donater_user: { - id: user_id, - }, - }, - }); + async find_by_user_with_latest_message(user_id: number): Promise { + // get an "inbox" of latest messages, one per transaction (for users without an organization)) + + return this.transactionsRepository + .createQueryBuilder('transaction') + .distinctOn(['transaction.id']) + .leftJoinAndSelect('transaction.messages', 'message') + .leftJoinAndSelect('transaction.donaterUser', 'donaterUser', 'user.id = :user_id', { + user_id: user_id, + }) + .orderBy('transaction.id', 'DESC') + .addOrderBy('message.id', 'DESC') + .getMany(); } - async find_by_org_with_messages(org_id: number): Promise { - return this.transactionsRepository.find({ - relations: { donater_user: true, donater_organization: true, claimer: true, messages: true }, - where: [ - { - donater_organization: { - id: org_id, - }, - }, - { - claimer: { - id: org_id, - }, - }, - ], - }); + async find_by_org_with_latest_message(org_id: number): Promise { + // get an "inbox" of latest messages, one per transaction (for an org) + + return this.transactionsRepository + .createQueryBuilder('transaction') + .distinctOn(['transaction.id']) + .leftJoinAndSelect('transaction.messages', 'message') + .leftJoinAndSelect( + 'transaction.donaterOrganizationId', + 'donaterOrganization', + 'donaterOrganization.id = :org_id', + { org_id: org_id }, + ) + .leftJoinAndSelect('transaction.claimerId', 'claimer', 'claimer.id = :claimer_id', { + claimer_id: org_id, + }) + .where('transaction.claimerId =:claimer_id', { claimer_id: org_id }) + .orWhere('transaction.donaterOrganizationId =:donaterOrganization', { + donaterOrganization: org_id, + }) + .orderBy('transaction.id', 'DESC') + .addOrderBy('message.id', 'DESC') + .getMany(); } async getTransactionById(id: number): Promise { From 5ee7b6f1f01880e64efc6a076109e10a18dfb29e Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Wed, 5 Apr 2023 18:05:06 -0400 Subject: [PATCH 03/36] bugfix imports message entity --- server/src/organizations/entities/organization.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/organizations/entities/organization.entity.ts b/server/src/organizations/entities/organization.entity.ts index 6048022f..d93319a3 100644 --- a/server/src/organizations/entities/organization.entity.ts +++ b/server/src/organizations/entities/organization.entity.ts @@ -2,7 +2,7 @@ import { Entity, Column, PrimaryGeneratedColumn, OneToMany, JoinColumn } from 't import { UserOrganization } from '../../user-org/entities/user-org.entity'; import { Transaction } from '../../transactions/entities/transaction.entity'; -import { Message } from 'src/messages/entities/message.entity'; +import { Message } from '../../messages/entities/message.entity'; @Entity('organizations') export class Organization { From be363446135d21a252eca98ff750bb51da124064 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Wed, 5 Apr 2023 18:06:41 -0400 Subject: [PATCH 04/36] bugfix imports --- server/src/messages/entities/message.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/messages/entities/message.entity.ts b/server/src/messages/entities/message.entity.ts index 95385756..e33464b8 100644 --- a/server/src/messages/entities/message.entity.ts +++ b/server/src/messages/entities/message.entity.ts @@ -1,4 +1,4 @@ -import { Organization } from 'src/organizations/entities/organization.entity'; +import { Organization } from '../../organizations/entities/organization.entity'; import { Entity, Column, From f875787ebbd60176fb6e0ba737e138cb61a10eb2 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Wed, 5 Apr 2023 18:17:28 -0400 Subject: [PATCH 05/36] undo transports --- client/src/views/TempChat.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/src/views/TempChat.tsx b/client/src/views/TempChat.tsx index ec1580e4..2e89e16e 100644 --- a/client/src/views/TempChat.tsx +++ b/client/src/views/TempChat.tsx @@ -8,7 +8,6 @@ const TempChat = () => { useEffect(() => { const opts: Partial = { withCredentials: true, - transports: ['websocket'], }; const newSocket = io(`http://${window.location.hostname}:3002`, opts); setSocket(newSocket); From 37d5e2f7645889cc810b6dfe2f6dd6e9889c85e6 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Wed, 5 Apr 2023 18:19:11 -0400 Subject: [PATCH 06/36] uncomment send mail --- server/src/acccount-manager/account-manager.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/acccount-manager/account-manager.controller.ts b/server/src/acccount-manager/account-manager.controller.ts index c9e63d86..bda16198 100644 --- a/server/src/acccount-manager/account-manager.controller.ts +++ b/server/src/acccount-manager/account-manager.controller.ts @@ -100,7 +100,7 @@ export class AccountManagerController { `, mailSettings: { sandboxMode: { enable: process.env.MODE !== 'production' } }, }; - //await this.sendgridService.send(mail); + await this.sendgridService.send(mail); return user; } From c2d75c44c107a0313b381857c54f6dee39172732 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Wed, 5 Apr 2023 18:19:49 -0400 Subject: [PATCH 07/36] revert change to db connection service --- server/src/database-connection.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/database-connection.service.ts b/server/src/database-connection.service.ts index 3087d43b..8506a201 100644 --- a/server/src/database-connection.service.ts +++ b/server/src/database-connection.service.ts @@ -14,7 +14,7 @@ export class DatabaseConnectionService implements TypeOrmOptionsFactory { database: process.env.DATABASE_DB, synchronize: true, //shouldn't be used in production dropSchema: false, //toggle to true to clear database schema - logging: 'all', + logging: true, autoLoadEntities: true, ssl: process.env.MODE === 'production' // only require ssl when in production/ From eea8cbe01d617096d3c857e710b525e5d70e9832 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Wed, 5 Apr 2023 22:53:46 -0400 Subject: [PATCH 08/36] add controller methods --- .../src/acccount-manager/acccount-manager.module.ts | 3 ++- .../acccount-manager/account-manager.controller.ts | 9 +++++++++ server/src/organizations/organizations.controller.ts | 12 +++++++++++- server/src/organizations/organizations.module.ts | 3 ++- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/server/src/acccount-manager/acccount-manager.module.ts b/server/src/acccount-manager/acccount-manager.module.ts index a3180f71..00bf26ec 100644 --- a/server/src/acccount-manager/acccount-manager.module.ts +++ b/server/src/acccount-manager/acccount-manager.module.ts @@ -13,6 +13,7 @@ import { UsersService } from './user.service'; import { FileStorageModule } from '../file-storage/file-storage.module'; import { FilesStorageService } from '../file-storage/file-storage.service'; import { CategoriesModule } from '../categories/categories.module'; +import { TransactionsService } from '../transactions/transactions.service'; const providers = [ AccountManagerService, @@ -35,7 +36,7 @@ const providers = [ CategoriesModule, ], controllers: [AccountManagerController], - providers: [...providers, SendgridService, FilesStorageService], + providers: [...providers, SendgridService, FilesStorageService, TransactionsService], exports: providers, }) export class AcccountManagerModule {} diff --git a/server/src/acccount-manager/account-manager.controller.ts b/server/src/acccount-manager/account-manager.controller.ts index c9e63d86..6e9e0cfc 100644 --- a/server/src/acccount-manager/account-manager.controller.ts +++ b/server/src/acccount-manager/account-manager.controller.ts @@ -41,6 +41,8 @@ import { ReturnUserDto, } from './dto/auth.dto'; import { ApiBody, ApiConsumes, ApiExcludeEndpoint, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { TransactionsService } from '../transactions/transactions.service'; +import { Transaction } from '../transactions/entities/transaction.entity'; type AuthedRequest = RequestT & { user: User }; @@ -57,6 +59,7 @@ export class AccountManagerController { private usersService: UsersService, private jwtService: JwtService, private fileStorageService: FilesStorageService, + private transactionService: TransactionsService, ) {} @Patch('verify-email') @@ -262,4 +265,10 @@ export class AccountManagerController { remove(@Param('id', ParseIntPipe) id: number) { return this.usersService.remove(id); } + + @Get('users/:id/transactions') + // TODO move to users/:id/transactions + userInbox(@Param('id') user_id: number): Promise { + return this.transactionService.find_by_org_with_latest_message(user_id); + } } diff --git a/server/src/organizations/organizations.controller.ts b/server/src/organizations/organizations.controller.ts index 16b0d736..061f518b 100644 --- a/server/src/organizations/organizations.controller.ts +++ b/server/src/organizations/organizations.controller.ts @@ -18,11 +18,16 @@ import { Organization } from './entities/organization.entity'; import { DeleteResult } from 'typeorm'; import { PropublicaOrg } from './organizations.service'; import { ApiTags } from '@nestjs/swagger'; +import { TransactionsService } from '../transactions/transactions.service'; +import { Transaction } from '../transactions/entities/transaction.entity'; @ApiTags('organizations') @Controller('organizations') export class OrganizationsController { - constructor(private readonly organizationsService: OrganizationsService) {} + constructor( + private readonly organizationsService: OrganizationsService, + private readonly transactionsService: TransactionsService, + ) {} @Post() async create(@Body() createOrganizationDto: CreateOrganizationDto): Promise { @@ -60,6 +65,11 @@ export class OrganizationsController { return this.organizationsService.update(+id, updateOrganizationDto); } + @Get('/:id/transactions') + orgInbox(@Param('id') org_id: number): Promise { + return this.transactionsService.find_by_org_with_latest_message(org_id); + } + @Delete(':id') remove(@Param('id') id: string): Promise { return this.organizationsService.remove(+id); diff --git a/server/src/organizations/organizations.module.ts b/server/src/organizations/organizations.module.ts index 30291477..83e1aa3f 100644 --- a/server/src/organizations/organizations.module.ts +++ b/server/src/organizations/organizations.module.ts @@ -3,11 +3,12 @@ import { OrganizationsService } from './organizations.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { OrganizationsController } from './organizations.controller'; import { Organization } from './entities/organization.entity'; +import { TransactionsService } from '../transactions/transactions.service'; @Module({ imports: [TypeOrmModule.forFeature([Organization])], controllers: [OrganizationsController], - providers: [OrganizationsService], + providers: [OrganizationsService, TransactionsService], exports: [OrganizationsService], }) export class OrganizationsModule {} From de6aa6699bcb57f74bbe3e9012c8ccf6f8d20d28 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Thu, 6 Apr 2023 20:07:13 -0400 Subject: [PATCH 09/36] refactor user inbox --- .../acccount-manager.module.ts | 3 +-- .../account-manager.controller.ts | 9 ------- .../organizations/organizations.controller.ts | 12 +-------- .../src/organizations/organizations.module.ts | 3 +-- .../entities/transaction.entity.ts | 2 +- .../transactions/transactions.controller.ts | 27 ++++++++++++++++++- .../src/transactions/transactions.service.ts | 10 +++++++ .../src/user-org/entities/user-org.entity.ts | 3 +++ 8 files changed, 43 insertions(+), 26 deletions(-) diff --git a/server/src/acccount-manager/acccount-manager.module.ts b/server/src/acccount-manager/acccount-manager.module.ts index 00bf26ec..a3180f71 100644 --- a/server/src/acccount-manager/acccount-manager.module.ts +++ b/server/src/acccount-manager/acccount-manager.module.ts @@ -13,7 +13,6 @@ import { UsersService } from './user.service'; import { FileStorageModule } from '../file-storage/file-storage.module'; import { FilesStorageService } from '../file-storage/file-storage.service'; import { CategoriesModule } from '../categories/categories.module'; -import { TransactionsService } from '../transactions/transactions.service'; const providers = [ AccountManagerService, @@ -36,7 +35,7 @@ const providers = [ CategoriesModule, ], controllers: [AccountManagerController], - providers: [...providers, SendgridService, FilesStorageService, TransactionsService], + providers: [...providers, SendgridService, FilesStorageService], exports: providers, }) export class AcccountManagerModule {} diff --git a/server/src/acccount-manager/account-manager.controller.ts b/server/src/acccount-manager/account-manager.controller.ts index 6e9e0cfc..c9e63d86 100644 --- a/server/src/acccount-manager/account-manager.controller.ts +++ b/server/src/acccount-manager/account-manager.controller.ts @@ -41,8 +41,6 @@ import { ReturnUserDto, } from './dto/auth.dto'; import { ApiBody, ApiConsumes, ApiExcludeEndpoint, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { TransactionsService } from '../transactions/transactions.service'; -import { Transaction } from '../transactions/entities/transaction.entity'; type AuthedRequest = RequestT & { user: User }; @@ -59,7 +57,6 @@ export class AccountManagerController { private usersService: UsersService, private jwtService: JwtService, private fileStorageService: FilesStorageService, - private transactionService: TransactionsService, ) {} @Patch('verify-email') @@ -265,10 +262,4 @@ export class AccountManagerController { remove(@Param('id', ParseIntPipe) id: number) { return this.usersService.remove(id); } - - @Get('users/:id/transactions') - // TODO move to users/:id/transactions - userInbox(@Param('id') user_id: number): Promise { - return this.transactionService.find_by_org_with_latest_message(user_id); - } } diff --git a/server/src/organizations/organizations.controller.ts b/server/src/organizations/organizations.controller.ts index 061f518b..16b0d736 100644 --- a/server/src/organizations/organizations.controller.ts +++ b/server/src/organizations/organizations.controller.ts @@ -18,16 +18,11 @@ import { Organization } from './entities/organization.entity'; import { DeleteResult } from 'typeorm'; import { PropublicaOrg } from './organizations.service'; import { ApiTags } from '@nestjs/swagger'; -import { TransactionsService } from '../transactions/transactions.service'; -import { Transaction } from '../transactions/entities/transaction.entity'; @ApiTags('organizations') @Controller('organizations') export class OrganizationsController { - constructor( - private readonly organizationsService: OrganizationsService, - private readonly transactionsService: TransactionsService, - ) {} + constructor(private readonly organizationsService: OrganizationsService) {} @Post() async create(@Body() createOrganizationDto: CreateOrganizationDto): Promise { @@ -65,11 +60,6 @@ export class OrganizationsController { return this.organizationsService.update(+id, updateOrganizationDto); } - @Get('/:id/transactions') - orgInbox(@Param('id') org_id: number): Promise { - return this.transactionsService.find_by_org_with_latest_message(org_id); - } - @Delete(':id') remove(@Param('id') id: string): Promise { return this.organizationsService.remove(+id); diff --git a/server/src/organizations/organizations.module.ts b/server/src/organizations/organizations.module.ts index 83e1aa3f..30291477 100644 --- a/server/src/organizations/organizations.module.ts +++ b/server/src/organizations/organizations.module.ts @@ -3,12 +3,11 @@ import { OrganizationsService } from './organizations.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { OrganizationsController } from './organizations.controller'; import { Organization } from './entities/organization.entity'; -import { TransactionsService } from '../transactions/transactions.service'; @Module({ imports: [TypeOrmModule.forFeature([Organization])], controllers: [OrganizationsController], - providers: [OrganizationsService, TransactionsService], + providers: [OrganizationsService], exports: [OrganizationsService], }) export class OrganizationsModule {} diff --git a/server/src/transactions/entities/transaction.entity.ts b/server/src/transactions/entities/transaction.entity.ts index d078dc5d..f9bc3faf 100644 --- a/server/src/transactions/entities/transaction.entity.ts +++ b/server/src/transactions/entities/transaction.entity.ts @@ -53,6 +53,6 @@ export class Transaction { @Column({ nullable: true }) // TODO: update seeder to always have claimerID claimerId: number; - @OneToMany(() => Message, (message) => message.transaction, { eager: true }) + @OneToMany(() => Message, (message) => message.transaction) messages: Message[]; } diff --git a/server/src/transactions/transactions.controller.ts b/server/src/transactions/transactions.controller.ts index 1a187c17..59a3db1d 100644 --- a/server/src/transactions/transactions.controller.ts +++ b/server/src/transactions/transactions.controller.ts @@ -1,4 +1,15 @@ -import { Controller, Post, Body, Get, Query, Param, Delete, Patch } from '@nestjs/common'; +import { + Controller, + Post, + Body, + Get, + Query, + Param, + Delete, + Patch, + Logger, + Request, +} from '@nestjs/common'; import { TransactionsService } from './transactions.service'; import { CreateTransactionDto } from './dto/create-transaction.dto'; @@ -6,6 +17,7 @@ import { Transaction } from './entities/transaction.entity'; import { GetTransactionsDto } from './dto/get-transactions-filter.dto'; import { UpdateTransactionDto } from './dto/update-transaction.dto'; import { ApiTags } from '@nestjs/swagger'; +import { Request } from 'express'; @ApiTags('transactions') @Controller('transactions') @@ -35,6 +47,19 @@ export class TransactionsController { return this.transactionsService.updateTransaction(id, updateTransactionStatusDto); } + @Get('/inbox') + // returns trasnactions with latest messages + userInbox(@Request() req: Request): Promise { + const user = req['user']; + const user_orgs = user.organizations; + const org_id = user_orgs.length > 0 ? user_orgs[0].organizationId : false; + if (org_id) { + return this.transactionsService.find_by_org_with_latest_message(org_id); + } else { + return this.transactionsService.find_by_user_with_latest_message(user.id); + } + } + @Delete('/:id') delete(@Param('id') id: number): Promise { return this.transactionsService.deleteTransaction(id); diff --git a/server/src/transactions/transactions.service.ts b/server/src/transactions/transactions.service.ts index a0b9e5ed..cc5974a8 100644 --- a/server/src/transactions/transactions.service.ts +++ b/server/src/transactions/transactions.service.ts @@ -61,6 +61,16 @@ export class TransactionsService { .addOrderBy('message.id', 'DESC') .getMany(); } + async getTransactionWithRelations(id: number): Promise { + const found = await this.transactionsRepository.findOne({ + relations: { messages: true, donater_user: true, donater_organization: true, claimer: true }, + where: { id: id }, + }); + if (!found) { + throw new NotFoundException(); + } + return found; + } async getTransactionById(id: number): Promise { const found = await this.transactionsRepository.findOneBy({ id }); diff --git a/server/src/user-org/entities/user-org.entity.ts b/server/src/user-org/entities/user-org.entity.ts index aaa37fe7..3df83d50 100644 --- a/server/src/user-org/entities/user-org.entity.ts +++ b/server/src/user-org/entities/user-org.entity.ts @@ -19,6 +19,9 @@ export class UserOrganization { @ManyToOne(() => Organization, (org) => org.users, { eager: true }) organization!: Organization; + @Column() + organizationId: number; + @Column({ type: 'enum', enum: Role, From ed28d63b306d7fa3265007d78f61b1bc6bbe3f00 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Thu, 6 Apr 2023 23:40:34 -0400 Subject: [PATCH 10/36] update inbox methods --- .../transactions/transactions.controller.ts | 31 ++++++++++--------- .../src/transactions/transactions.module.ts | 11 ++++++- .../src/transactions/transactions.service.ts | 20 +++++++----- 3 files changed, 39 insertions(+), 23 deletions(-) diff --git a/server/src/transactions/transactions.controller.ts b/server/src/transactions/transactions.controller.ts index 59a3db1d..1ab530ab 100644 --- a/server/src/transactions/transactions.controller.ts +++ b/server/src/transactions/transactions.controller.ts @@ -7,8 +7,8 @@ import { Param, Delete, Patch, - Logger, Request, + UseGuards, } from '@nestjs/common'; import { TransactionsService } from './transactions.service'; @@ -17,7 +17,7 @@ import { Transaction } from './entities/transaction.entity'; import { GetTransactionsDto } from './dto/get-transactions-filter.dto'; import { UpdateTransactionDto } from './dto/update-transaction.dto'; import { ApiTags } from '@nestjs/swagger'; -import { Request } from 'express'; +import { CookieAuthGuard } from 'src/acccount-manager/guards/cookie-auth.guard'; @ApiTags('transactions') @Controller('transactions') @@ -34,6 +34,20 @@ export class TransactionsController { return this.transactionsService.getTransactions(getTransactionsDto); } + @UseGuards(CookieAuthGuard) + @Get('/inbox') + // returns trasnactions with latest messages + async userInbox(@Request() req: Request): Promise { + const user = req['user']; + const userOrgs = user.organizations; // TODO: load a users organizations + const org_id = userOrgs && userOrgs.length > 0 ? userOrgs[0].organizationId : false; + if (org_id) { + return this.transactionsService.find_by_org_with_latest_message(org_id); + } else { + return this.transactionsService.find_by_user_with_latest_message(user.id); + } + } + @Get('/:id') getTransactionById(@Param('id') id: number): Promise { return this.transactionsService.getTransactionById(id); @@ -47,19 +61,6 @@ export class TransactionsController { return this.transactionsService.updateTransaction(id, updateTransactionStatusDto); } - @Get('/inbox') - // returns trasnactions with latest messages - userInbox(@Request() req: Request): Promise { - const user = req['user']; - const user_orgs = user.organizations; - const org_id = user_orgs.length > 0 ? user_orgs[0].organizationId : false; - if (org_id) { - return this.transactionsService.find_by_org_with_latest_message(org_id); - } else { - return this.transactionsService.find_by_user_with_latest_message(user.id); - } - } - @Delete('/:id') delete(@Param('id') id: number): Promise { return this.transactionsService.deleteTransaction(id); diff --git a/server/src/transactions/transactions.module.ts b/server/src/transactions/transactions.module.ts index e95e9bf9..b376eba0 100644 --- a/server/src/transactions/transactions.module.ts +++ b/server/src/transactions/transactions.module.ts @@ -3,9 +3,18 @@ import { TransactionsController } from './transactions.controller'; import { TransactionsService } from './transactions.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Transaction } from './entities/transaction.entity'; +import { PassportModule } from '@nestjs/passport'; +import { JwtModule } from '@nestjs/jwt'; @Module({ - imports: [TypeOrmModule.forFeature([Transaction])], + imports: [ + TypeOrmModule.forFeature([Transaction]), + PassportModule, + JwtModule.register({ + secret: process.env.JWT_SECRET, + signOptions: { expiresIn: '60s' }, + }), + ], controllers: [TransactionsController], providers: [TransactionsService], exports: [TransactionsService], diff --git a/server/src/transactions/transactions.service.ts b/server/src/transactions/transactions.service.ts index cc5974a8..1972ac54 100644 --- a/server/src/transactions/transactions.service.ts +++ b/server/src/transactions/transactions.service.ts @@ -29,9 +29,14 @@ export class TransactionsService { .createQueryBuilder('transaction') .distinctOn(['transaction.id']) .leftJoinAndSelect('transaction.messages', 'message') - .leftJoinAndSelect('transaction.donaterUser', 'donaterUser', 'user.id = :user_id', { - user_id: user_id, - }) + .innerJoinAndSelect( + 'transaction.donater_user', + 'donater_user', + 'donater_user.id = :user_id', + { + user_id: user_id, + }, + ) .orderBy('transaction.id', 'DESC') .addOrderBy('message.id', 'DESC') .getMany(); @@ -45,22 +50,23 @@ export class TransactionsService { .distinctOn(['transaction.id']) .leftJoinAndSelect('transaction.messages', 'message') .leftJoinAndSelect( - 'transaction.donaterOrganizationId', + 'transaction.donater_organizations', 'donaterOrganization', 'donaterOrganization.id = :org_id', { org_id: org_id }, ) - .leftJoinAndSelect('transaction.claimerId', 'claimer', 'claimer.id = :claimer_id', { + .leftJoinAndSelect('transaction.claimer', 'claimer', 'claimer.id = :claimer_id', { claimer_id: org_id, }) .where('transaction.claimerId =:claimer_id', { claimer_id: org_id }) - .orWhere('transaction.donaterOrganizationId =:donaterOrganization', { - donaterOrganization: org_id, + .orWhere('transaction.donaterOrganizationId =:org_id', { + org_id: org_id, }) .orderBy('transaction.id', 'DESC') .addOrderBy('message.id', 'DESC') .getMany(); } + async getTransactionWithRelations(id: number): Promise { const found = await this.transactionsRepository.findOne({ relations: { messages: true, donater_user: true, donater_organization: true, claimer: true }, From b170905eaf5c9837fe93f66bed2904bde7bf08c4 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Fri, 7 Apr 2023 09:47:55 -0400 Subject: [PATCH 11/36] fix org inbox --- server/src/acccount-manager/guards/cookie-auth.guard.ts | 2 +- server/src/transactions/transactions.service.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/src/acccount-manager/guards/cookie-auth.guard.ts b/server/src/acccount-manager/guards/cookie-auth.guard.ts index 986b4f3a..526978b2 100644 --- a/server/src/acccount-manager/guards/cookie-auth.guard.ts +++ b/server/src/acccount-manager/guards/cookie-auth.guard.ts @@ -20,7 +20,7 @@ export class CookieAuthGuard extends AuthGuard() { return false; } - let user: User = {} as User; + let user: User; // instantiate as user class instead of object so that relations can be loaded try { user = await this.jwtService.verify(jwt, { secret: process.env.JWT_SECRET }); } catch (_e) { diff --git a/server/src/transactions/transactions.service.ts b/server/src/transactions/transactions.service.ts index 1972ac54..a6aaefb0 100644 --- a/server/src/transactions/transactions.service.ts +++ b/server/src/transactions/transactions.service.ts @@ -38,7 +38,7 @@ export class TransactionsService { }, ) .orderBy('transaction.id', 'DESC') - .addOrderBy('message.id', 'DESC') + .addOrderBy('message.created_date', 'DESC') .getMany(); } @@ -50,7 +50,7 @@ export class TransactionsService { .distinctOn(['transaction.id']) .leftJoinAndSelect('transaction.messages', 'message') .leftJoinAndSelect( - 'transaction.donater_organizations', + 'transaction.donater_organization', 'donaterOrganization', 'donaterOrganization.id = :org_id', { org_id: org_id }, @@ -63,7 +63,7 @@ export class TransactionsService { org_id: org_id, }) .orderBy('transaction.id', 'DESC') - .addOrderBy('message.id', 'DESC') + .addOrderBy('message.created_date', 'DESC') .getMany(); } From c4e6764bd534a88439f81e3225a95c6a6f3e904e Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Fri, 7 Apr 2023 10:25:20 -0400 Subject: [PATCH 12/36] fix messages stub --- server/test/stubs/messages.stub.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/test/stubs/messages.stub.ts b/server/test/stubs/messages.stub.ts index 325783ce..b6c350b2 100644 --- a/server/test/stubs/messages.stub.ts +++ b/server/test/stubs/messages.stub.ts @@ -7,7 +7,8 @@ export const messageStub = (user?: User, transaction?: Transaction): Message => id: 1, text: 'fakeMessage', created_date: new Date(2021, 11, 6), - user: user || new User(), + sending_user: user || new User(), + sending_org: null, transaction: transaction || new Transaction(), }; }; From e2d69dfcc4c488a029d61b97bfc96bd908ce2ed6 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Sat, 8 Apr 2023 10:45:56 -0400 Subject: [PATCH 13/36] bugfix nullable column organizationId --- server/src/user-org/entities/user-org.entity.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/user-org/entities/user-org.entity.ts b/server/src/user-org/entities/user-org.entity.ts index 3df83d50..a5381dc3 100644 --- a/server/src/user-org/entities/user-org.entity.ts +++ b/server/src/user-org/entities/user-org.entity.ts @@ -19,7 +19,7 @@ export class UserOrganization { @ManyToOne(() => Organization, (org) => org.users, { eager: true }) organization!: Organization; - @Column() + @Column({ nullable: true }) organizationId: number; @Column({ @@ -29,7 +29,7 @@ export class UserOrganization { }) role: Role; - @ManyToOne(() => User, (user) => user.organizations, { eager: true }) + @ManyToOne(() => User, (user) => user.organizations) user!: User; @CreateDateColumn() From c6ca7b68d751523007a47004fdb88e71d5c91f19 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Tue, 11 Apr 2023 22:17:35 -0400 Subject: [PATCH 14/36] build proof of concept --- .../acccount-manager/entities/user.entity.ts | 6 ++- server/src/database/seeding/seed-data.ts | 4 ++ server/src/messages/dto/create-message.dto.ts | 10 ++++- .../src/messages/entities/message.entity.ts | 8 +++- server/src/messages/messages.controller.ts | 2 +- server/src/messages/messages.module.ts | 3 +- server/src/messages/messages.service.ts | 4 +- server/src/poc-chat/poc-chat.gateway.ts | 35 +++++++++++---- .../entities/received-messages.entity.ts | 45 +++++++++++++++++++ server/src/seeder/seeder.service.ts | 5 +-- .../transactions/transactions.controller.ts | 2 +- .../src/transactions/transactions.service.ts | 3 +- 12 files changed, 106 insertions(+), 21 deletions(-) create mode 100644 server/src/received-messages/entities/received-messages.entity.ts diff --git a/server/src/acccount-manager/entities/user.entity.ts b/server/src/acccount-manager/entities/user.entity.ts index 3bfde443..e58e29ad 100644 --- a/server/src/acccount-manager/entities/user.entity.ts +++ b/server/src/acccount-manager/entities/user.entity.ts @@ -4,6 +4,7 @@ import { UserOrganization } from '../../user-org/entities/user-org.entity'; import { Asset } from '../../assets/entities/asset.entity'; import { Message } from '../../messages/entities/message.entity'; import { Transaction } from '../../transactions/entities/transaction.entity'; +import { Receivedmessage } from 'src/received-messages/entities/received-messages.entity'; @Entity('users') export class User { @@ -49,7 +50,10 @@ export class User { @OneToMany(() => Message, (message) => message.sending_user) messages: Message[]; - @OneToMany(() => UserOrganization, (user_org) => user_org.user) + @OneToMany(() => Receivedmessage, (received) => received.message) + receivedMessages: Message[]; + + @OneToMany(() => UserOrganization, (user_org) => user_org.user, { eager: true }) organizations: UserOrganization[]; @Column({ type: 'text', nullable: true }) diff --git a/server/src/database/seeding/seed-data.ts b/server/src/database/seeding/seed-data.ts index 770de0d5..8ed03b60 100644 --- a/server/src/database/seeding/seed-data.ts +++ b/server/src/database/seeding/seed-data.ts @@ -215,10 +215,14 @@ export const seedMessages = (): CreateMessageDto[] => { { text: 'I would like to accept the paper products.', transaction: null, + sending_user: null, + sending_org: null, }, { text: 'I would like to accept the furniture.', transaction: null, + sending_user: null, + sending_org: null, }, ]; return messages; diff --git a/server/src/messages/dto/create-message.dto.ts b/server/src/messages/dto/create-message.dto.ts index 6b2a417e..6168a2db 100644 --- a/server/src/messages/dto/create-message.dto.ts +++ b/server/src/messages/dto/create-message.dto.ts @@ -1,6 +1,8 @@ -import { IsNotEmpty } from 'class-validator'; +import { IsNotEmpty, IsOptional } from 'class-validator'; import { Transaction } from '../../transactions/entities/transaction.entity'; +import { User } from 'src/acccount-manager/entities/user.entity'; +import { Organization } from 'src/organizations/entities/organization.entity'; export class CreateMessageDto { @IsNotEmpty() @@ -8,4 +10,10 @@ export class CreateMessageDto { @IsNotEmpty() transaction: Transaction; + + @IsNotEmpty() + sending_user: User; + + @IsOptional() + sending_org: Organization; } diff --git a/server/src/messages/entities/message.entity.ts b/server/src/messages/entities/message.entity.ts index e33464b8..ac53d851 100644 --- a/server/src/messages/entities/message.entity.ts +++ b/server/src/messages/entities/message.entity.ts @@ -7,10 +7,13 @@ import { CreateDateColumn, JoinColumn, OneToOne, + OneToMany, } from 'typeorm'; import { User } from '../../acccount-manager/entities/user.entity'; import { Transaction } from '../../transactions/entities/transaction.entity'; +import { IsOptional } from 'class-validator'; +import { Receivedmessage } from 'src/received-messages/entities/received-messages.entity'; @Entity('messages') export class Message { @@ -32,9 +35,12 @@ export class Message { @ManyToOne(() => Organization, (org) => org.messages) @JoinColumn() - sending_org: Organization; + sending_org?: Organization; @ManyToOne(() => Transaction, (transaction) => transaction.messages) @JoinColumn() transaction: Transaction; + + @OneToMany(() => Receivedmessage, (userMessage) => userMessage.message) + readReceipts?: Receivedmessage[]; } diff --git a/server/src/messages/messages.controller.ts b/server/src/messages/messages.controller.ts index bb697fa9..87f66e28 100644 --- a/server/src/messages/messages.controller.ts +++ b/server/src/messages/messages.controller.ts @@ -33,7 +33,7 @@ export class MessagesController { @Body() createMessageDto: CreateMessageDto, ): Promise { const { user } = request; - const newMessage = await this.messagesService.create(createMessageDto, user as User); + const newMessage = await this.messagesService.create(createMessageDto); return newMessage; } diff --git a/server/src/messages/messages.module.ts b/server/src/messages/messages.module.ts index 979d1abc..1c94f594 100644 --- a/server/src/messages/messages.module.ts +++ b/server/src/messages/messages.module.ts @@ -4,9 +4,10 @@ import { MessagesService } from './messages.service'; import { MessagesController } from './messages.controller'; import { Message } from './entities/message.entity'; import { AcccountManagerModule } from '../acccount-manager/acccount-manager.module'; +import { Receivedmessage } from 'src/received-messages/entities/received-messages.entity'; @Module({ - imports: [TypeOrmModule.forFeature([Message]), AcccountManagerModule], + imports: [TypeOrmModule.forFeature([Message, Receivedmessage]), AcccountManagerModule], controllers: [MessagesController], providers: [MessagesService], exports: [MessagesService], diff --git a/server/src/messages/messages.service.ts b/server/src/messages/messages.service.ts index 73adc5ae..2415e0eb 100644 --- a/server/src/messages/messages.service.ts +++ b/server/src/messages/messages.service.ts @@ -11,8 +11,8 @@ import { UpdateMessageDto } from './dto/update-message.dto'; export class MessagesService { constructor(@InjectRepository(Message) private messagesRepository: Repository) {} - async create(createMessageDto: CreateMessageDto, user: User): Promise { - return this.messagesRepository.save({ ...createMessageDto, user }); + async create(createMessageDto: CreateMessageDto): Promise { + return this.messagesRepository.save({ ...createMessageDto }); } async findAll(): Promise { diff --git a/server/src/poc-chat/poc-chat.gateway.ts b/server/src/poc-chat/poc-chat.gateway.ts index 5ecab658..39003a09 100644 --- a/server/src/poc-chat/poc-chat.gateway.ts +++ b/server/src/poc-chat/poc-chat.gateway.ts @@ -72,16 +72,12 @@ export class PocChatGateway { async sendMessage( @MessageBody('text') text: string, @MessageBody('transactionId') transactionId: number, - @MessageBody('orgId') org_id: number, @ConnectedSocket() client: Socket, @Request() request: Request, ) { if (client.rooms.has(`${transactionId}`)) { - client.to(`${transactionId}`).emit('sendMessage', { - text: text, - sendingUserId: request['user'].id, - sendingOrgId: org_id, - }); + const message = this._createMessage(request['user'], transactionId, text); + client.broadcast.to(`${transactionId}`).emit('sendMessage', message); } } @@ -89,11 +85,32 @@ export class PocChatGateway { @UseGuards(WsCookieGuard) typing( @MessageBody('isTyping') isTyping: boolean, + @MessageBody('transactionId') transactionId: number, @ConnectedSocket() client: Socket, @Request() req: Request, ) { - Logger.log('recieved typing'); - client.emit('typing', { name: req['user'].firstName, isTyping }); + if (client.rooms.has(`${transactionId}`)) { + client.broadcast + .to(`${transactionId}`) + .emit('typing', { name: req['user'].firstName, isTyping }); + } + } + + async _createMessage(user, transaction_id, text) { + const transaction = await this.transactionsService.getTransactionById(transaction_id); + const sending_user = user; + const from_claimer = + user.organizations && + user.organizations.find((org) => org.organizationId === transaction.claimer.id); + const sending_org = + (from_claimer ? transaction.claimer : transaction.donater_organization) || null; + const receiving_org = !from_claimer && transaction.donater_organization; + const message = this.messagesService.create({ text, transaction, sending_org, sending_user }); + Logger.log(message); + if (receiving_org) { + // TODO: create seen message record + } + return message; } async _can_user_join(user, transaction_id, org_id = null) { @@ -102,7 +119,7 @@ export class PocChatGateway { return false; } if (org_id) { - if (!user.organizations.reduce((acc, org) => org.id === org_id && acc, true)) { + if (!user.organizations.find((org) => org.organizationId === org_id)) { // user id does not match org_id so return false return false; } diff --git a/server/src/received-messages/entities/received-messages.entity.ts b/server/src/received-messages/entities/received-messages.entity.ts new file mode 100644 index 00000000..a5de3305 --- /dev/null +++ b/server/src/received-messages/entities/received-messages.entity.ts @@ -0,0 +1,45 @@ +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToMany, + ManyToOne, + PrimaryGeneratedColumn, +} from 'typeorm'; +import { User } from '../../acccount-manager/entities/user.entity'; +import { Transaction } from '../../transactions/entities/transaction.entity'; +import { IsOptional } from 'class-validator'; +import { Message } from 'src/messages/entities/message.entity'; + +@Entity('receivedmessages') +export class Receivedmessage { + @PrimaryGeneratedColumn() + id: number; + + @Column('text') + text: string; + + @CreateDateColumn({ + type: 'timestamp', + default: () => 'CURRENT_TIMESTAMP(6)', + }) + created_date: Date; + + @ManyToMany(() => User, (user) => user.receivedMessages) + @JoinColumn() + sending_user: User; + + @ManyToOne(() => Message, (message) => message.readReceipts, { eager: true }) + @JoinColumn() + message: Message; + + @Column({ nullable: true }) + messageId: number; + + @Column({ nullable: true }) + sendingUserId: number; + + @Column('boolean') + seen: boolean; +} diff --git a/server/src/seeder/seeder.service.ts b/server/src/seeder/seeder.service.ts index 22fc5721..84d15f2e 100644 --- a/server/src/seeder/seeder.service.ts +++ b/server/src/seeder/seeder.service.ts @@ -167,9 +167,8 @@ export class SeederService { Logger.log('Seeding a message', SeederService.name); for (const message of messages) { message.transaction = insertedTransaction; - await this.messageService - .create(message, transaction.donater_user) - .catch((err) => Logger.log(err)); + message.sending_user = transaction.donater_user; + await this.messageService.create(message).catch((err) => Logger.log(err)); newMessages.push(message); } Logger.log('at end of seeding the messages', SeederService.name); diff --git a/server/src/transactions/transactions.controller.ts b/server/src/transactions/transactions.controller.ts index 1ab530ab..27b4827d 100644 --- a/server/src/transactions/transactions.controller.ts +++ b/server/src/transactions/transactions.controller.ts @@ -39,7 +39,7 @@ export class TransactionsController { // returns trasnactions with latest messages async userInbox(@Request() req: Request): Promise { const user = req['user']; - const userOrgs = user.organizations; // TODO: load a users organizations + const userOrgs = await user.organizations; const org_id = userOrgs && userOrgs.length > 0 ? userOrgs[0].organizationId : false; if (org_id) { return this.transactionsService.find_by_org_with_latest_message(org_id); diff --git a/server/src/transactions/transactions.service.ts b/server/src/transactions/transactions.service.ts index a6aaefb0..556c320d 100644 --- a/server/src/transactions/transactions.service.ts +++ b/server/src/transactions/transactions.service.ts @@ -28,6 +28,7 @@ export class TransactionsService { return this.transactionsRepository .createQueryBuilder('transaction') .distinctOn(['transaction.id']) + .leftJoinAndSelect('transaction.asset', 'asset') .leftJoinAndSelect('transaction.messages', 'message') .innerJoinAndSelect( 'transaction.donater_user', @@ -44,10 +45,10 @@ export class TransactionsService { async find_by_org_with_latest_message(org_id: number): Promise { // get an "inbox" of latest messages, one per transaction (for an org) - return this.transactionsRepository .createQueryBuilder('transaction') .distinctOn(['transaction.id']) + .leftJoinAndSelect('transaction.asset', 'asset') .leftJoinAndSelect('transaction.messages', 'message') .leftJoinAndSelect( 'transaction.donater_organization', From beb49a4aacb273284b2238cd3ce765236ca70a7e Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Wed, 12 Apr 2023 11:57:57 -0400 Subject: [PATCH 15/36] updates to backend for messaging --- server/src/poc-chat/poc-chat.gateway.ts | 37 ++++++++++++------- .../transactions/transactions.controller.ts | 2 +- .../src/transactions/transactions.service.ts | 6 ++- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/server/src/poc-chat/poc-chat.gateway.ts b/server/src/poc-chat/poc-chat.gateway.ts index 39003a09..2c2d591d 100644 --- a/server/src/poc-chat/poc-chat.gateway.ts +++ b/server/src/poc-chat/poc-chat.gateway.ts @@ -2,8 +2,9 @@ import { WebSocketGateway, SubscribeMessage, MessageBody, - WebSocketServer, ConnectedSocket, + WsResponse, + WebSocketServer, } from '@nestjs/websockets'; import { PocChatService } from './poc-chat.service'; import { CreatePocChatDto } from './dto/create-poc-chat.dto'; @@ -44,10 +45,10 @@ export class PocChatGateway { } // @UseGuards(WsCookieGuard) - @SubscribeMessage('findAllPocChat') - findAll() { - return this.pocChatService.findAll(); - } + // @SubscribeMessage('findAllPocChat') + // findAll() { + // return this.pocChatService.findAll(); + // } @SubscribeMessage('join') @UseGuards(WsCookieGuard) @@ -61,9 +62,9 @@ export class PocChatGateway { const isValid = this._can_user_join(user, transactionId, org_id); if (isValid) { client.join(`${transactionId}`); - client.emit('join', { success: true }); + return { event: 'join', data: { success: true } } as WsResponse; } else { - client.emit('join', { success: false }); + return { event: 'join', data: { success: false } } as WsResponse; } } @@ -76,8 +77,9 @@ export class PocChatGateway { @Request() request: Request, ) { if (client.rooms.has(`${transactionId}`)) { - const message = this._createMessage(request['user'], transactionId, text); - client.broadcast.to(`${transactionId}`).emit('sendMessage', message); + const message = await this._createMessage(request['user'], transactionId, text); + Logger.log(message); + this.server.to(`${transactionId}`).emit('message', message); } } @@ -89,10 +91,13 @@ export class PocChatGateway { @ConnectedSocket() client: Socket, @Request() req: Request, ) { + const name = + req['user'].organizations.length > 0 + ? req['user'].organizations[0].organization.name + : req['user'].firstName; + if (client.rooms.has(`${transactionId}`)) { - client.broadcast - .to(`${transactionId}`) - .emit('typing', { name: req['user'].firstName, isTyping }); + client.to(`${transactionId}`).emit('typing', { name: name, isTyping }); } } @@ -105,8 +110,12 @@ export class PocChatGateway { const sending_org = (from_claimer ? transaction.claimer : transaction.donater_organization) || null; const receiving_org = !from_claimer && transaction.donater_organization; - const message = this.messagesService.create({ text, transaction, sending_org, sending_user }); - Logger.log(message); + const message = await this.messagesService.create({ + text, + transaction, + sending_org, + sending_user, + }); if (receiving_org) { // TODO: create seen message record } diff --git a/server/src/transactions/transactions.controller.ts b/server/src/transactions/transactions.controller.ts index 27b4827d..dfad6596 100644 --- a/server/src/transactions/transactions.controller.ts +++ b/server/src/transactions/transactions.controller.ts @@ -50,7 +50,7 @@ export class TransactionsController { @Get('/:id') getTransactionById(@Param('id') id: number): Promise { - return this.transactionsService.getTransactionById(id); + return this.transactionsService.getTransactionWithRelations(id); } @Patch('/:id') diff --git a/server/src/transactions/transactions.service.ts b/server/src/transactions/transactions.service.ts index 556c320d..5ea09140 100644 --- a/server/src/transactions/transactions.service.ts +++ b/server/src/transactions/transactions.service.ts @@ -23,13 +23,14 @@ export class TransactionsService { } async find_by_user_with_latest_message(user_id: number): Promise { - // get an "inbox" of latest messages, one per transaction (for users without an organization)) + // get an "inbox" of latest messages, one per transaction ( use this for citizen accounts) return this.transactionsRepository .createQueryBuilder('transaction') .distinctOn(['transaction.id']) .leftJoinAndSelect('transaction.asset', 'asset') .leftJoinAndSelect('transaction.messages', 'message') + .leftJoinAndSelect('transaction.donater_organization', 'donater_organization') .innerJoinAndSelect( 'transaction.donater_user', 'donater_user', @@ -44,12 +45,13 @@ export class TransactionsService { } async find_by_org_with_latest_message(org_id: number): Promise { - // get an "inbox" of latest messages, one per transaction (for an org) + // get an "inbox" of latest messages, one per transaction (use this for organization accounts) return this.transactionsRepository .createQueryBuilder('transaction') .distinctOn(['transaction.id']) .leftJoinAndSelect('transaction.asset', 'asset') .leftJoinAndSelect('transaction.messages', 'message') + .leftJoinAndSelect('transaction.donater_user', 'donater_user') .leftJoinAndSelect( 'transaction.donater_organization', 'donaterOrganization', From 5501cd7e9a336dbe8af919352a9ef36b226e6047 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Wed, 12 Apr 2023 12:05:07 -0400 Subject: [PATCH 16/36] bugfix imports for user entity --- server/src/acccount-manager/entities/user.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/acccount-manager/entities/user.entity.ts b/server/src/acccount-manager/entities/user.entity.ts index e58e29ad..368155c7 100644 --- a/server/src/acccount-manager/entities/user.entity.ts +++ b/server/src/acccount-manager/entities/user.entity.ts @@ -4,7 +4,7 @@ import { UserOrganization } from '../../user-org/entities/user-org.entity'; import { Asset } from '../../assets/entities/asset.entity'; import { Message } from '../../messages/entities/message.entity'; import { Transaction } from '../../transactions/entities/transaction.entity'; -import { Receivedmessage } from 'src/received-messages/entities/received-messages.entity'; +import { Receivedmessage } from '../../received-messages/entities/received-messages.entity'; @Entity('users') export class User { From c40c7a97bb18cf088d87d66fd94d11cae41160ba Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Wed, 12 Apr 2023 12:06:15 -0400 Subject: [PATCH 17/36] bugfix imports --- server/src/messages/entities/message.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/messages/entities/message.entity.ts b/server/src/messages/entities/message.entity.ts index ac53d851..a64c8f16 100644 --- a/server/src/messages/entities/message.entity.ts +++ b/server/src/messages/entities/message.entity.ts @@ -13,7 +13,7 @@ import { import { User } from '../../acccount-manager/entities/user.entity'; import { Transaction } from '../../transactions/entities/transaction.entity'; import { IsOptional } from 'class-validator'; -import { Receivedmessage } from 'src/received-messages/entities/received-messages.entity'; +import { Receivedmessage } from '../../received-messages/entities/received-messages.entity'; @Entity('messages') export class Message { From 87c3fe1c9904a16d013a27ac9c99d9b61926cb66 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Wed, 12 Apr 2023 12:07:59 -0400 Subject: [PATCH 18/36] bugfix import for message --- .../src/received-messages/entities/received-messages.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/received-messages/entities/received-messages.entity.ts b/server/src/received-messages/entities/received-messages.entity.ts index a5de3305..9605d5d2 100644 --- a/server/src/received-messages/entities/received-messages.entity.ts +++ b/server/src/received-messages/entities/received-messages.entity.ts @@ -10,7 +10,7 @@ import { import { User } from '../../acccount-manager/entities/user.entity'; import { Transaction } from '../../transactions/entities/transaction.entity'; import { IsOptional } from 'class-validator'; -import { Message } from 'src/messages/entities/message.entity'; +import { Message } from '../../messages/entities/message.entity'; @Entity('receivedmessages') export class Receivedmessage { From d498dda44fce42767e2b756339ffe933abd8d820 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Wed, 12 Apr 2023 12:11:54 -0400 Subject: [PATCH 19/36] bugfix imports final --- server/src/messages/dto/create-message.dto.ts | 4 ++-- server/src/poc-chat/poc-chat.gateway.ts | 6 +++--- server/src/poc-chat/poc-chat.module.ts | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/server/src/messages/dto/create-message.dto.ts b/server/src/messages/dto/create-message.dto.ts index 6168a2db..463f2f6a 100644 --- a/server/src/messages/dto/create-message.dto.ts +++ b/server/src/messages/dto/create-message.dto.ts @@ -1,8 +1,8 @@ import { IsNotEmpty, IsOptional } from 'class-validator'; import { Transaction } from '../../transactions/entities/transaction.entity'; -import { User } from 'src/acccount-manager/entities/user.entity'; -import { Organization } from 'src/organizations/entities/organization.entity'; +import { User } from '../../acccount-manager/entities/user.entity'; +import { Organization } from '../../organizations/entities/organization.entity'; export class CreateMessageDto { @IsNotEmpty() diff --git a/server/src/poc-chat/poc-chat.gateway.ts b/server/src/poc-chat/poc-chat.gateway.ts index 2c2d591d..d37c28a1 100644 --- a/server/src/poc-chat/poc-chat.gateway.ts +++ b/server/src/poc-chat/poc-chat.gateway.ts @@ -12,9 +12,9 @@ import { Server, Socket } from 'socket.io'; import { Logger, Request, UseGuards } from '@nestjs/common'; import * as dotenv from 'dotenv'; import { WsCookieGuard } from '../acccount-manager/guards/ws-cookie-auth.guard'; -import { TransactionsService } from 'src/transactions/transactions.service'; -import { MessagesService } from 'src/messages/messages.service'; -import { UsersService } from 'src/acccount-manager/user.service'; +import { TransactionsService } from '../transactions/transactions.service'; +import { MessagesService } from '../messages/messages.service'; +import { UsersService } from '../acccount-manager/user.service'; dotenv.config({ path: __dirname + '/../../.env' }); @WebSocketGateway(3002, { diff --git a/server/src/poc-chat/poc-chat.module.ts b/server/src/poc-chat/poc-chat.module.ts index 99abfef1..caec9ffb 100644 --- a/server/src/poc-chat/poc-chat.module.ts +++ b/server/src/poc-chat/poc-chat.module.ts @@ -5,9 +5,9 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { PocChat } from './entities/poc-chat.entity'; import { AcccountManagerModule } from '../acccount-manager/acccount-manager.module'; import { TransactionsService } from 'src/transactions/transactions.service'; -import { MessagesService } from 'src/messages/messages.service'; -import { Transaction } from 'src/transactions/entities/transaction.entity'; -import { Message } from 'src/messages/entities/message.entity'; +import { MessagesService } from '../messages/messages.service'; +import { Transaction } from '../transactions/entities/transaction.entity'; +import { Message } from '../messages/entities/message.entity'; @Module({ imports: [ From 5a835f5a3a53f0a2a98646caf4728d8f8c888d82 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Wed, 12 Apr 2023 12:22:06 -0400 Subject: [PATCH 20/36] remove logger --- server/src/poc-chat/poc-chat.gateway.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/poc-chat/poc-chat.gateway.ts b/server/src/poc-chat/poc-chat.gateway.ts index d37c28a1..880d6aa1 100644 --- a/server/src/poc-chat/poc-chat.gateway.ts +++ b/server/src/poc-chat/poc-chat.gateway.ts @@ -9,7 +9,7 @@ import { import { PocChatService } from './poc-chat.service'; import { CreatePocChatDto } from './dto/create-poc-chat.dto'; import { Server, Socket } from 'socket.io'; -import { Logger, Request, UseGuards } from '@nestjs/common'; +import { Request, UseGuards } from '@nestjs/common'; import * as dotenv from 'dotenv'; import { WsCookieGuard } from '../acccount-manager/guards/ws-cookie-auth.guard'; import { TransactionsService } from '../transactions/transactions.service'; @@ -78,7 +78,6 @@ export class PocChatGateway { ) { if (client.rooms.has(`${transactionId}`)) { const message = await this._createMessage(request['user'], transactionId, text); - Logger.log(message); this.server.to(`${transactionId}`).emit('message', message); } } From 68bbd692c5aa3429fb3ca6b2c199f93feaca5559 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Wed, 12 Apr 2023 12:51:41 -0400 Subject: [PATCH 21/36] update transactions with relations --- server/src/poc-chat/poc-chat.gateway.ts | 7 +++++-- server/src/transactions/transactions.service.ts | 12 ++++++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/server/src/poc-chat/poc-chat.gateway.ts b/server/src/poc-chat/poc-chat.gateway.ts index 880d6aa1..14f0d27e 100644 --- a/server/src/poc-chat/poc-chat.gateway.ts +++ b/server/src/poc-chat/poc-chat.gateway.ts @@ -101,11 +101,14 @@ export class PocChatGateway { } async _createMessage(user, transaction_id, text) { - const transaction = await this.transactionsService.getTransactionById(transaction_id); + const transaction = await this.transactionsService.getTransactionWithRelations(transaction_id, { + claimer: true, + donater_organization: true, + }); const sending_user = user; const from_claimer = user.organizations && - user.organizations.find((org) => org.organizationId === transaction.claimer.id); + user.organizations.find((org) => org.organizationId === transaction.claimerId); const sending_org = (from_claimer ? transaction.claimer : transaction.donater_organization) || null; const receiving_org = !from_claimer && transaction.donater_organization; diff --git a/server/src/transactions/transactions.service.ts b/server/src/transactions/transactions.service.ts index 5ea09140..2cfd20db 100644 --- a/server/src/transactions/transactions.service.ts +++ b/server/src/transactions/transactions.service.ts @@ -70,9 +70,17 @@ export class TransactionsService { .getMany(); } - async getTransactionWithRelations(id: number): Promise { + async getTransactionWithRelations( + id: number, + relations: any = { + messages: true, + donater_user: true, + donater_organization: true, + claimer: true, + }, + ): Promise { const found = await this.transactionsRepository.findOne({ - relations: { messages: true, donater_user: true, donater_organization: true, claimer: true }, + relations: relations, where: { id: id }, }); if (!found) { From 6f352e9f8c0539fcc93a2beb3b23bc3a92729e04 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Wed, 12 Apr 2023 13:13:50 -0400 Subject: [PATCH 22/36] update user stub relations --- server/src/acccount-manager/entities/user.entity.ts | 2 +- server/src/messages/entities/message.entity.ts | 2 +- server/test/stubs/users.stub.ts | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/server/src/acccount-manager/entities/user.entity.ts b/server/src/acccount-manager/entities/user.entity.ts index 368155c7..8e6e097e 100644 --- a/server/src/acccount-manager/entities/user.entity.ts +++ b/server/src/acccount-manager/entities/user.entity.ts @@ -48,7 +48,7 @@ export class User { transactions: Transaction[]; @OneToMany(() => Message, (message) => message.sending_user) - messages: Message[]; + sentMessages: Message[]; @OneToMany(() => Receivedmessage, (received) => received.message) receivedMessages: Message[]; diff --git a/server/src/messages/entities/message.entity.ts b/server/src/messages/entities/message.entity.ts index a64c8f16..7d89809a 100644 --- a/server/src/messages/entities/message.entity.ts +++ b/server/src/messages/entities/message.entity.ts @@ -29,7 +29,7 @@ export class Message { }) created_date: Date; - @ManyToOne(() => User, (user) => user.messages) + @ManyToOne(() => User, (user) => user.sentMessages) @JoinColumn() sending_user: User; diff --git a/server/test/stubs/users.stub.ts b/server/test/stubs/users.stub.ts index 50145078..5c5fb4fa 100644 --- a/server/test/stubs/users.stub.ts +++ b/server/test/stubs/users.stub.ts @@ -7,9 +7,11 @@ import { UserOrganization } from '../../src/user-org/entities/user-org.entity'; export const userEntityStub = ( assets?: Asset[], - messages?: Message[], + sentMessages?: Message[], transactions?: Transaction[], organizations?: UserOrganization[], + receivedMessages?: Message[], + ): User => { return { id: 234545, @@ -21,8 +23,8 @@ export const userEntityStub = ( state: 'WA', zip_code: '98101', assets: assets, - messages, transactions, + receivedMessages, organizations, email_verified: true, email_notification_opt_out: false, From d067c33ea26e3173bff3f8dd4ee9a9d6c11da535 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Thu, 13 Apr 2023 15:20:17 -0400 Subject: [PATCH 23/36] update message entity --- server/src/messages/entities/message.entity.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/src/messages/entities/message.entity.ts b/server/src/messages/entities/message.entity.ts index 7d89809a..0d480619 100644 --- a/server/src/messages/entities/message.entity.ts +++ b/server/src/messages/entities/message.entity.ts @@ -33,10 +33,16 @@ export class Message { @JoinColumn() sending_user: User; + @Column({ nullable: true }) + sendingUserId: number; + @ManyToOne(() => Organization, (org) => org.messages) @JoinColumn() sending_org?: Organization; + @Column({ nullable: true }) + sendingOrgId: number; + @ManyToOne(() => Transaction, (transaction) => transaction.messages) @JoinColumn() transaction: Transaction; From f35c96bc6f813219691c2bd333562c09ff045f6f Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Fri, 14 Apr 2023 14:17:13 -0400 Subject: [PATCH 24/36] front end messaging --- .husky/post-checkout | 10 +- client/src/FetchActions.ts | 9 + .../components/Users/Inbox/MessageCard.tsx | 18 +- .../Users/Inbox/TransactionThreadCard.tsx | 130 ++++++++------ client/src/types/index.ts | 19 +- client/src/views/Inbox.tsx | 166 +++++++----------- client/src/views/Messages.tsx | 69 ++++---- client/src/views/NewMessage.tsx | 14 +- client/src/views/SearchResults.tsx | 6 +- client/src/views/TempChat.tsx | 55 +++++- .../src/transactions/transactions.service.ts | 1 + 11 files changed, 276 insertions(+), 221 deletions(-) diff --git a/.husky/post-checkout b/.husky/post-checkout index c53fd937..c825b313 100755 --- a/.husky/post-checkout +++ b/.husky/post-checkout @@ -1,8 +1,8 @@ #!/bin/sh FILE="$(dirname "$0")/_/husky.sh" -if [ ! -f "$FILE" ]; then - cd server && npm ci --legacy-peer-deps && npm run prepare && cd ../client && npm ci --legacy-peer-deps && npm run prepare && cd .. -fi +# if [ ! -f "$FILE" ]; then +# cd server && npm ci --legacy-peer-deps && npm run prepare && cd ../client && npm ci --legacy-peer-deps && npm run prepare && cd .. +# fi -. "$FILE" -cd server && npm ci --legacy-peer-deps && cd ../client && npm ci --legacy-peer-deps && cd .. +# . "$FILE" +# cd server && npm ci --legacy-peer-deps && cd ../client && npm ci --legacy-peer-deps && cd .. diff --git a/client/src/FetchActions.ts b/client/src/FetchActions.ts index c952c980..31a03123 100644 --- a/client/src/FetchActions.ts +++ b/client/src/FetchActions.ts @@ -54,3 +54,12 @@ export const fetchDonations = ( getRequest(assetsApiRequest.href + assetsApiRequest.hash, onSuccess, abortController, onError); }; + +export const fetchInbox = ( + onSuccess: Function, + abortController?: AbortController, + onError = (statusCode?: Number, statusText?: string) => {}, +) => { + const assetsApiRequest = new URL(`${APP_API_BASE_URL}/transactions/inbox`); + getRequest(assetsApiRequest.href + assetsApiRequest.hash, onSuccess, abortController, onError); +}; diff --git a/client/src/components/Users/Inbox/MessageCard.tsx b/client/src/components/Users/Inbox/MessageCard.tsx index d65374dd..f29c2972 100644 --- a/client/src/components/Users/Inbox/MessageCard.tsx +++ b/client/src/components/Users/Inbox/MessageCard.tsx @@ -3,7 +3,7 @@ import makeStyles from '@mui/styles/makeStyles'; import type { Theme } from '@mui/material/styles'; -import type { Message } from '../../../types'; +import { Typography } from '@mui/material'; const useStyles = makeStyles((theme: Theme) => ({ currentUserMessage: { @@ -25,17 +25,25 @@ const useStyles = makeStyles((theme: Theme) => ({ })); function MessageCard({ + text, + senderName, isCurrentUser, - message, + dateString, }: { + senderName: string; + text: string; isCurrentUser: boolean; - message: Message; + dateString: string; }): JSX.Element { const classes = useStyles(); - return (
- {message.user.firstName}: {message.text} + + {text} + + + {dateString} +
); } diff --git a/client/src/components/Users/Inbox/TransactionThreadCard.tsx b/client/src/components/Users/Inbox/TransactionThreadCard.tsx index 319b1ebf..5ae26a98 100644 --- a/client/src/components/Users/Inbox/TransactionThreadCard.tsx +++ b/client/src/components/Users/Inbox/TransactionThreadCard.tsx @@ -1,39 +1,12 @@ import * as React from 'react'; -import Box from '@mui/material/Box'; -import makeStyles from '@mui/styles/makeStyles'; import Typography from '@mui/material/Typography'; -import Card from '@mui/material/Card'; -import CardContent from '@mui/material/CardContent'; -import PermIdentityRoundedIcon from '@mui/icons-material/PermIdentityRounded'; - -import type { Theme } from '@mui/material/styles'; +import Divider from '@mui/material/Divider'; +import ListItemText from '@mui/material/ListItemText'; +import ListItemAvatar from '@mui/material/ListItemAvatar'; +import Avatar from '@mui/material/Avatar'; import type { Transaction, User } from '../../../types'; - -const useStyles = makeStyles((theme: Theme) => ({ - threadCardSelected: { - background: 'rgba(196, 196, 196, 0.3)', - width: '95%', - margin: '0 auto', - }, - threadCard: { - background: 'white', - width: '95%', - margin: '0 auto', - boxShadow: 'none', - }, - threadCardContent: { - padding: '10px 4px 10px 40px', - }, - threadsSection: { - marginRight: '20px', - }, - threadCardTitle: { - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-between', - }, -})); +import { ListItemButton } from '@mui/material'; function TransactionThreadCard({ isSelected, @@ -44,30 +17,81 @@ function TransactionThreadCard({ isSelected: boolean; onClick: (transaction: Transaction) => void; transaction: Transaction; - user?: User; + user?: User | null; }): JSX.Element { - const classes = useStyles(); - const otherUser = - user?.id === transaction.requester.id - ? transaction.requester.firstName - : transaction.donater.firstName; + const message = transaction.messages[0]; + const sendingOrg = [transaction.donater_organization, transaction.claimer].find( + (org) => org && org.id === message.sendingOrgId, + ); + const renderMessage = () => { + if (message) { + return ( + + + {(sendingOrg && sendingOrg.name) || + (message.sendingUserId === (user && user.id) + ? ' Me: ' + : `${transaction.donater_user.firstName} ${transaction.donater_user.last_name}: `)} + + {message.text} + + ); + } + }; + const userOrg = + user && user.organizations && user.organizations[0] && user.organizations[0].organization.id; + const userIsClaimer = userOrg === transaction.claimer.id; + const donaterIsOrg = !!transaction.donater_organization; + + let otherUser = ''; + if (userIsClaimer) { + // other user is donater + if (donaterIsOrg) { + otherUser = transaction.donater_organization && transaction.donater_organization.name; + } else { + otherUser = transaction.donater_user && transaction.donater_user.firstName; + } + } else { + // other user is claimer + otherUser = transaction.claimer && transaction.claimer.name; + } return ( - onClick(transaction)} - variant={isSelected ? 'outlined' : undefined} - > - - - - {otherUser} - - - - Re: {transaction.asset.title} - - + <> + onClick(transaction)} + > + + {!sendingOrg && ( + + )} + + + + {otherUser} + + + `Re: ${transaction.asset.title}` + + + } + secondary={renderMessage()} + /> + + + ); } diff --git a/client/src/types/index.ts b/client/src/types/index.ts index eb7bdec5..82c26081 100644 --- a/client/src/types/index.ts +++ b/client/src/types/index.ts @@ -39,26 +39,39 @@ export enum ApprovalStatus { denied = 'DENIED', } +export type UserOrg = { + organization: Organization; + organizationId: number; + user: User; + role: string; + approvalStatus: string; +}; + export type User = { id?: number; firstName: string; last_name?: string; email?: string; profile_image_url?: string; + organizations: UserOrg[]; }; export type Transaction = { id: number; - donater: User; - requester: User; + donater_user: User; + donater_organization: Organization; asset: Pick; + claimer: Organization; + messages: Message[]; }; export type Message = { id: number; text: string; transactionId: number; - user: User; + sendingUserId: number | null; + sendingOrgId: number | null; + created_date: string; }; export type Option = { diff --git a/client/src/views/Inbox.tsx b/client/src/views/Inbox.tsx index 9f840971..c666f320 100644 --- a/client/src/views/Inbox.tsx +++ b/client/src/views/Inbox.tsx @@ -4,20 +4,19 @@ import Box from '@mui/material/Box'; import Grid from '@mui/material/Grid'; import makeStyles from '@mui/styles/makeStyles'; import Typography from '@mui/material/Typography'; -import TextField from '@mui/material/TextField'; -import SendOutlinedIcon from '@mui/icons-material/SendOutlined'; -import IconButton from '@mui/material/IconButton'; +import List from '@mui/material/List'; import type { Theme } from '@mui/material/styles'; import SubHeader from '../components/Users/Inbox/SubHeader'; import TransactionThreadCard from '../components/Users/Inbox/TransactionThreadCard'; -import MessageCard from '../components/Users/Inbox/MessageCard'; import { UserContext } from '../providers'; import routes from '../routes/routes'; import type { Message, Transaction } from '../types'; import { APP_API_BASE_URL } from '../configs'; +import { fetchInbox } from '../FetchActions'; +import TempChat from './TempChat'; const useStyles = makeStyles((theme: Theme) => ({ inboxWrapper: { @@ -48,6 +47,7 @@ const useStyles = makeStyles((theme: Theme) => ({ }, threadsSection: { marginRight: '20px', + maxWidth: '30%', }, messageInputWrapper: { width: '100%', @@ -76,72 +76,36 @@ const useStyles = makeStyles((theme: Theme) => ({ }, })); -const fetchTransactions = (): Promise => { - return Promise.resolve([ - { - id: 1, - donater: { id: 1, firstName: 'firstName1' }, - requester: { id: 2, firstName: 'firstName2' }, - asset: { - id: 1, - title: 'title 1', - }, - }, - { - id: 2, - donater: { id: 1, firstName: 'firstName1' }, - requester: { id: 3, firstName: 'firstName3' }, - asset: { - id: 2, - title: 'title 2', - }, - }, - ]); -}; - // TODO: make the fetch find messages by transaction // TODO: seed data so that messages appear without manually creating them -const fetchMessages = async (): Promise => { - const MESSAGES_API_URL = `${APP_API_BASE_URL}/messages`; +const fetchMessages = async (id: number): Promise => { + const MESSAGES_API_URL = `${APP_API_BASE_URL}/transactions/${id}`; const res = await fetch(MESSAGES_API_URL); const data = await res.json(); - - const messages = await data.map((message: any) => { - return { - id: message.id, - text: message.text, - transactionId: message.transaction_id, - user: { - id: message.user.id, - firstName: message.user.firstName, - }, - }; - }); - - return messages; + return data.messages.sort( + (message1: Message, message2: Message) => + (new Date(message1.created_date) as any) - (new Date(message2.created_date) as any), + ); }; // TODO use SubHeader component in Offer and Assets pages // maybe call it SearchBar and have an optional leftContent prop? function MessageInboxView(): JSX.Element { - const classes = useStyles(); + const classes: any = useStyles(); const { user } = React.useContext(UserContext); const [transactions, setTransactions] = React.useState([]); const [selectedTransaction, setSelectedTransaction] = React.useState(null); const [messages, setMessages] = React.useState([]); - const handleSendMessage = () => {}; - // todo switch to custom hook React.useEffect(() => { if (user) { - (async function () { - const transactions = await fetchTransactions(); // user.id - setTransactions(transactions); + fetchInbox((transactions: Transaction[]) => { setSelectedTransaction(transactions[0]); - })(); + setTransactions(transactions); + }); // user.id } }, [user]); @@ -149,12 +113,51 @@ function MessageInboxView(): JSX.Element { React.useEffect(() => { if (selectedTransaction) { (async function () { - const messages = await fetchMessages(); + const messages = await fetchMessages(selectedTransaction.id); setMessages(messages); })(); } }, [selectedTransaction]); + const transactionCards = ( + + {transactions && + transactions.length && + transactions.map((t) => ( + setSelectedTransaction(transaction)} + transaction={t} + user={user} + /> + ))} + + ); + + const noTransactionsMessage = ( + + Inbox empty + + Support an organization by contributingsomething they + need + + or + + {/* update to prop to use routes once set up */} + Post a need + + + ); + + const transactionList = ( +
+ + Inbox + + {transactions.length && transactions.length > 0 ? transactionCards : noTransactionsMessage} +
+ ); + if (!user) { return ; } else { @@ -162,62 +165,17 @@ function MessageInboxView(): JSX.Element { <> - - - Inbox - - {transactions.length ? ( - transactions.map((t) => ( - setSelectedTransaction(transaction)} - transaction={t} - user={user} - /> - )) - ) : ( - - Inbox empty - - Support an organization by contributing{' '} - something they need - - or - - {/* update to prop to use routes once set up */} - Post a need - - - )} - + {transactionList} {selectedTransaction && ( - - - Re: {selectedTransaction.asset.title} - - {messages?.map((m) => ( - - ))} - + )} - -
- - - - - -
diff --git a/client/src/views/Messages.tsx b/client/src/views/Messages.tsx index 3460d466..194ab7b7 100644 --- a/client/src/views/Messages.tsx +++ b/client/src/views/Messages.tsx @@ -1,46 +1,41 @@ -import React, { useEffect, useState } from 'react'; +import React from 'react'; +import { Message } from '../types'; +import MessageCard from '../components/Users/Inbox/MessageCard'; -interface IMessage { - text: string; - name: string; - id: string; - created_date: Date; -} - -function Messages({ socket }: any) { - const [messages, setMessages] = useState([]); - const [showTypingGesture, setShowTypingGesture] = useState(false); - const [userTyping, setUserTyping] = useState(''); - - useEffect(() => { - socket.on('message', (res: IMessage[]) => { - setMessages(res); - }); - - socket.on('typing', (res: { name: string; isTyping: boolean }) => { - setShowTypingGesture(res.isTyping); - setUserTyping(res.name); - }); - - socket.emit('findAllPocChat', (res: IMessage[]) => { - setMessages(res); - }); - }, [socket]); - - const formatDate = (date: Date): string => { +function Messages({ socket, messages, transaction, user }: any) { + const formatDate = (date: string): string => { return new Date(date).toLocaleString('en-US'); }; return (
- {messages.map((message: IMessage) => ( -
- {formatDate(message.created_date)}: - {message.name}: - {message.text} -
- ))} - {showTypingGesture &&

... {userTyping} is typing

} + {messages.map((message: Message) => { + const sendingOrg = [transaction.donater_organization, transaction.claimer].find( + (org) => org && org.id === message.sendingOrgId, + ); + const userOrg = + user && + user.organizations[0] && + user.organizations[0].organization && + user.organizations[0].organization.id; + + let sendingUser = null; + if (transaction.donater_user && transaction.donater_user.id === message.sendingUserId) { + sendingUser = transaction.donater_user; + } + const senderName = sendingOrg ? sendingOrg.name : sendingUser && sendingUser.firstName; + const isCurrentUser = + message.sendingUserId === user.id || (userOrg && userOrg === message.sendingOrgId); + + return ( + + ); + })}
); } diff --git a/client/src/views/NewMessage.tsx b/client/src/views/NewMessage.tsx index 608d44ed..7c362fef 100644 --- a/client/src/views/NewMessage.tsx +++ b/client/src/views/NewMessage.tsx @@ -1,10 +1,12 @@ +import { SendOutlined } from '@mui/icons-material'; +import { IconButton, TextField } from '@mui/material'; import React, { useState } from 'react'; -const NewMessage = ({ socket }: any) => { +const NewMessage = ({ socket, transactionId, classes }: any) => { const [value, setValue] = useState(''); const submitForm = (e: any) => { e.preventDefault(); - socket.emit('createPocChat', { text: value }); + socket.emit('message', { text: value, transactionId: transactionId, fromClaimer: false }); setValue(''); }; @@ -23,8 +25,9 @@ const NewMessage = ({ socket }: any) => { }; return ( -
- + { onBlur={handleBlur} onFocus={handleOnFocus} /> + + + ); }; diff --git a/client/src/views/SearchResults.tsx b/client/src/views/SearchResults.tsx index 2f6625b0..3c5a2a54 100644 --- a/client/src/views/SearchResults.tsx +++ b/client/src/views/SearchResults.tsx @@ -85,7 +85,7 @@ function SearchResults(): JSX.Element { // TODO: create Volunteer Type in types/index.ts and change Object[] to Volunteer[] below const [volunteer, setVolunteer] = React.useState([]); - function fetchSearchData() { + const fetchSearchData = React.useCallback(() => { if (querySearchCategory === 'Volunteer') { // TODO: Change API to /volunteer fetch(`${APP_API_BASE_URL}/organizations`) @@ -117,7 +117,7 @@ function SearchResults(): JSX.Element { } else { // TODO: Change API to fetch ALL combined data } - } + }, [querySearchCategory, querySearchText]); const handleCheck = (event: React.ChangeEvent) => { setSelectedFilters({ @@ -128,7 +128,7 @@ function SearchResults(): JSX.Element { React.useEffect(() => { fetchSearchData(); - }, [querySearchText, querySearchCategory]); + }, [fetchSearchData]); return (
diff --git a/client/src/views/TempChat.tsx b/client/src/views/TempChat.tsx index 2e89e16e..c867ef8d 100644 --- a/client/src/views/TempChat.tsx +++ b/client/src/views/TempChat.tsx @@ -2,30 +2,71 @@ import { useEffect, useState } from 'react'; import { io, ManagerOptions, SocketOptions } from 'socket.io-client'; import Messages from './Messages'; import NewMessage from './NewMessage'; +import { Box, Typography } from '@mui/material'; +import { Message, Transaction, User } from '../types'; -const TempChat = () => { +type TempChatProps = { + classes: any; + transaction: Transaction; + messages: Message[] | []; + user: User | null; +}; +const TempChat = (props: TempChatProps) => { + const { classes, transaction, messages, user } = props; const [socket, setSocket] = useState(null); + const [newMessages, setNewMessages] = useState([]); + const [showTypingGesture, setShowTypingGesture] = useState(false); + const [userTyping, setUserTyping] = useState(''); + useEffect(() => { const opts: Partial = { withCredentials: true, }; const newSocket = io(`http://${window.location.hostname}:3002`, opts); + newSocket.emit('join', { transactionId: transaction.id }); + // to do - send org id if user is logged in as an organization + setSocket(newSocket); + newSocket.on('message', (res: any) => { + console.log(res); + setNewMessages((prev) => [...prev, res]); + }); + + newSocket.on('join', (res: any) => { + console.log(res); + }); + + newSocket.on('typing', (res: { name: string; isTyping: boolean }) => { + console.log(res); + setShowTypingGesture(res.isTyping); + setUserTyping(res.name); + }); + return () => newSocket.close() as any; - }, [setSocket]); + }, [transaction]); return ( -
-
React Chat
+ + + Re: {transaction.asset.title} + {socket ? (
- +
) : (
Not Connected
)} - -
+ {showTypingGesture &&

... {userTyping} is typing

} + + + + ); }; diff --git a/server/src/transactions/transactions.service.ts b/server/src/transactions/transactions.service.ts index 2cfd20db..d4535465 100644 --- a/server/src/transactions/transactions.service.ts +++ b/server/src/transactions/transactions.service.ts @@ -31,6 +31,7 @@ export class TransactionsService { .leftJoinAndSelect('transaction.asset', 'asset') .leftJoinAndSelect('transaction.messages', 'message') .leftJoinAndSelect('transaction.donater_organization', 'donater_organization') + .leftJoinAndSelect('transaction.claimer', 'claimer') .innerJoinAndSelect( 'transaction.donater_user', 'donater_user', From 2535683f57ccdae9e149d9e82268892a4e2dfbef Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Fri, 14 Apr 2023 14:18:54 -0400 Subject: [PATCH 25/36] remove tempchat from routes --- client/src/routes/routes.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/client/src/routes/routes.ts b/client/src/routes/routes.ts index 08f5e660..d7af0ade 100644 --- a/client/src/routes/routes.ts +++ b/client/src/routes/routes.ts @@ -25,7 +25,6 @@ import Help from '../views/Help'; import ForgotPassword from '../components/Users/Auth/ForgotPassword'; import SetNewPassword from '../components/Users/Auth/SetNewPassword'; import CookiePolicy from '../views/CookiePolicy'; -import TempChat from '../views/TempChat'; import EmailVerification from '../views/EmailVerification'; type RouteMap = { @@ -172,11 +171,6 @@ const routes: RouteMap = { roles: [], path: '/help', }, - TempChat: { - component: TempChat, - roles: [], - path: '/chat', - }, EmailVerification: { component: EmailVerification, roles: [], From facd5f3f91a8c0999619908f1d31bb8739285668 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Fri, 14 Apr 2023 14:21:09 -0400 Subject: [PATCH 26/36] finish removing tempchat from routes --- client/src/views/Main.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/src/views/Main.tsx b/client/src/views/Main.tsx index b88bd527..1f5098c6 100644 --- a/client/src/views/Main.tsx +++ b/client/src/views/Main.tsx @@ -31,7 +31,6 @@ const { OfferFormSkills, ContactUs, Help, - TempChat, EmailVerification, } = routes; @@ -115,7 +114,6 @@ function Main() { {/* support */} - From 9d9cc71a720cbbd64a4cab4c01882babbdab1284 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Fri, 14 Apr 2023 14:22:38 -0400 Subject: [PATCH 27/36] undo change post-checkout --- .husky/post-checkout | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.husky/post-checkout b/.husky/post-checkout index c825b313..c53fd937 100755 --- a/.husky/post-checkout +++ b/.husky/post-checkout @@ -1,8 +1,8 @@ #!/bin/sh FILE="$(dirname "$0")/_/husky.sh" -# if [ ! -f "$FILE" ]; then -# cd server && npm ci --legacy-peer-deps && npm run prepare && cd ../client && npm ci --legacy-peer-deps && npm run prepare && cd .. -# fi +if [ ! -f "$FILE" ]; then + cd server && npm ci --legacy-peer-deps && npm run prepare && cd ../client && npm ci --legacy-peer-deps && npm run prepare && cd .. +fi -# . "$FILE" -# cd server && npm ci --legacy-peer-deps && cd ../client && npm ci --legacy-peer-deps && cd .. +. "$FILE" +cd server && npm ci --legacy-peer-deps && cd ../client && npm ci --legacy-peer-deps && cd .. From 991b0ca0d5210e8d98489e337e7130d1e160e398 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Fri, 14 Apr 2023 16:27:17 -0400 Subject: [PATCH 28/36] update transaction message card --- client/src/components/Users/Inbox/TransactionThreadCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Users/Inbox/TransactionThreadCard.tsx b/client/src/components/Users/Inbox/TransactionThreadCard.tsx index 5ae26a98..0fe94ca5 100644 --- a/client/src/components/Users/Inbox/TransactionThreadCard.tsx +++ b/client/src/components/Users/Inbox/TransactionThreadCard.tsx @@ -83,7 +83,7 @@ function TransactionThreadCard({ {otherUser} - `Re: ${transaction.asset.title}` + {`Re: ${transaction.asset.title}`} } From f06ddb33c8773773f0f9aa052767d7cb61b8f60d Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Fri, 14 Apr 2023 16:28:36 -0400 Subject: [PATCH 29/36] update messages stub --- server/test/stubs/messages.stub.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/test/stubs/messages.stub.ts b/server/test/stubs/messages.stub.ts index b6c350b2..c92bbb7e 100644 --- a/server/test/stubs/messages.stub.ts +++ b/server/test/stubs/messages.stub.ts @@ -10,5 +10,7 @@ export const messageStub = (user?: User, transaction?: Transaction): Message => sending_user: user || new User(), sending_org: null, transaction: transaction || new Transaction(), + sendingOrgId: null, + sendingUserId: null, }; }; From 48c572c3e74ba8632669c778017b068d1e2b8f40 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Fri, 14 Apr 2023 17:33:49 -0400 Subject: [PATCH 30/36] updates to trasnsaction thread card --- .../Users/Inbox/TransactionThreadCard.tsx | 37 +++++++++---------- server/test/stubs/users.stub.ts | 1 + 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/client/src/components/Users/Inbox/TransactionThreadCard.tsx b/client/src/components/Users/Inbox/TransactionThreadCard.tsx index 0fe94ca5..eb415850 100644 --- a/client/src/components/Users/Inbox/TransactionThreadCard.tsx +++ b/client/src/components/Users/Inbox/TransactionThreadCard.tsx @@ -20,9 +20,6 @@ function TransactionThreadCard({ user?: User | null; }): JSX.Element { const message = transaction.messages[0]; - const sendingOrg = [transaction.donater_organization, transaction.claimer].find( - (org) => org && org.id === message.sendingOrgId, - ); const renderMessage = () => { if (message) { return ( @@ -33,10 +30,7 @@ function TransactionThreadCard({ variant="body2" color="text.primary" > - {(sendingOrg && sendingOrg.name) || - (message.sendingUserId === (user && user.id) - ? ' Me: ' - : `${transaction.donater_user.firstName} ${transaction.donater_user.last_name}: `)} + {message.sendingUserId === (user && user.id) ? ' Me: ' : ''} {message.text} @@ -46,19 +40,26 @@ function TransactionThreadCard({ const userOrg = user && user.organizations && user.organizations[0] && user.organizations[0].organization.id; const userIsClaimer = userOrg === transaction.claimer.id; + const isCurrentUser = + (userOrg && message && message.sendingOrgId === userOrg) || + (user && message && message.sendingUserId === user.id); const donaterIsOrg = !!transaction.donater_organization; - let otherUser = ''; + let otherUserName = ''; + let otherUserImage = '' as string | undefined; if (userIsClaimer) { - // other user is donater + // other user is claimer if (donaterIsOrg) { - otherUser = transaction.donater_organization && transaction.donater_organization.name; + const otherUser = transaction.donater_organization && transaction.donater_organization; + otherUserName = otherUser.name; } else { - otherUser = transaction.donater_user && transaction.donater_user.firstName; + const otherUser = transaction.donater_user && transaction.donater_user; + otherUserName = otherUser.firstName; + otherUserImage = otherUser.profile_image_url; } } else { // other user is claimer - otherUser = transaction.claimer && transaction.claimer.name; + otherUserName = transaction.claimer && transaction.claimer.name; } return ( @@ -69,18 +70,14 @@ function TransactionThreadCard({ onClick={() => onClick(transaction)} > - {!sendingOrg && ( - - )} + {otherUserImage && } - - {otherUser} + + {message ? (isCurrentUser ? 'To : ' : 'From: ') : ''} + {otherUserName} {`Re: ${transaction.asset.title}`} diff --git a/server/test/stubs/users.stub.ts b/server/test/stubs/users.stub.ts index 5c5fb4fa..a99b8131 100644 --- a/server/test/stubs/users.stub.ts +++ b/server/test/stubs/users.stub.ts @@ -25,6 +25,7 @@ export const userEntityStub = ( assets: assets, transactions, receivedMessages, + sentMessages, organizations, email_verified: true, email_notification_opt_out: false, From 449a05cb07511af445a746181dfd328461fb77ef Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Sat, 15 Apr 2023 08:30:01 -0400 Subject: [PATCH 31/36] make message targeted to transaction --- client/src/views/TempChat.tsx | 2 +- server/src/poc-chat/poc-chat.gateway.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/views/TempChat.tsx b/client/src/views/TempChat.tsx index c867ef8d..8aba5193 100644 --- a/client/src/views/TempChat.tsx +++ b/client/src/views/TempChat.tsx @@ -27,7 +27,7 @@ const TempChat = (props: TempChatProps) => { // to do - send org id if user is logged in as an organization setSocket(newSocket); - newSocket.on('message', (res: any) => { + newSocket.on(`message_${transaction.id}`, (res: any) => { console.log(res); setNewMessages((prev) => [...prev, res]); }); diff --git a/server/src/poc-chat/poc-chat.gateway.ts b/server/src/poc-chat/poc-chat.gateway.ts index 14f0d27e..22250a1b 100644 --- a/server/src/poc-chat/poc-chat.gateway.ts +++ b/server/src/poc-chat/poc-chat.gateway.ts @@ -78,7 +78,7 @@ export class PocChatGateway { ) { if (client.rooms.has(`${transactionId}`)) { const message = await this._createMessage(request['user'], transactionId, text); - this.server.to(`${transactionId}`).emit('message', message); + this.server.to(`${transactionId}`).emit(`message_${transactionId}`, message); } } From 40a71319f2f4077b847293e54902f7b4b88c0622 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Mon, 17 Apr 2023 14:03:26 -0400 Subject: [PATCH 32/36] minor updates to styling chat --- .../components/Users/Inbox/MessageCard.tsx | 7 +++--- .../Users/Inbox/TransactionThreadCard.tsx | 6 ++--- client/src/views/Inbox.tsx | 5 +--- client/src/views/TempChat.tsx | 24 +++++++++++++++++-- .../acccount-manager/entities/user.entity.ts | 2 +- 5 files changed, 30 insertions(+), 14 deletions(-) diff --git a/client/src/components/Users/Inbox/MessageCard.tsx b/client/src/components/Users/Inbox/MessageCard.tsx index f29c2972..e954f3c0 100644 --- a/client/src/components/Users/Inbox/MessageCard.tsx +++ b/client/src/components/Users/Inbox/MessageCard.tsx @@ -8,16 +8,17 @@ import { Typography } from '@mui/material'; const useStyles = makeStyles((theme: Theme) => ({ currentUserMessage: { alignSelf: 'flex-end', - border: '1px solid black', + border: '1px solid lightgrey', borderRadius: '10px', padding: '5px', maxWidth: '70%', marginLeft: '30%', + background: 'rgba(196, 196, 196, 0.3)', }, otherUserMessage: { alignSelf: 'flex-start', - background: 'rgba(196, 196, 196, 0.3)', - border: '1px solid black', + background: 'lightblue', + border: '1px solid lightgrey', borderRadius: '10px', padding: '5px', maxWidth: '70%', diff --git a/client/src/components/Users/Inbox/TransactionThreadCard.tsx b/client/src/components/Users/Inbox/TransactionThreadCard.tsx index eb415850..36f5e616 100644 --- a/client/src/components/Users/Inbox/TransactionThreadCard.tsx +++ b/client/src/components/Users/Inbox/TransactionThreadCard.tsx @@ -75,13 +75,11 @@ function TransactionThreadCard({ - + {message ? (isCurrentUser ? 'To : ' : 'From: ') : ''} {otherUserName} - - {`Re: ${transaction.asset.title}`} - + {`Topic: ${transaction.asset.title}`} } secondary={renderMessage()} diff --git a/client/src/views/Inbox.tsx b/client/src/views/Inbox.tsx index c666f320..8c58cb27 100644 --- a/client/src/views/Inbox.tsx +++ b/client/src/views/Inbox.tsx @@ -26,7 +26,7 @@ const useStyles = makeStyles((theme: Theme) => ({ textAlign: 'left', }, sectionWrapper: { - border: '1px solid black', + border: '1px solid lightgrey', borderRadius: '10px', display: 'flex', flexDirection: 'column', @@ -151,9 +151,6 @@ function MessageInboxView(): JSX.Element { const transactionList = (
- - Inbox - {transactions.length && transactions.length > 0 ? transactionCards : noTransactionsMessage}
); diff --git a/client/src/views/TempChat.tsx b/client/src/views/TempChat.tsx index 8aba5193..2e961215 100644 --- a/client/src/views/TempChat.tsx +++ b/client/src/views/TempChat.tsx @@ -45,10 +45,30 @@ const TempChat = (props: TempChatProps) => { return () => newSocket.close() as any; }, [transaction]); + const userOrg = + user && user.organizations && user.organizations[0] && user.organizations[0].organization.id; + const userIsClaimer = userOrg === transaction.claimer.id; + const donaterIsOrg = !!transaction.donater_organization; + + let otherUserName = ''; + if (userIsClaimer) { + // other user is claimer + if (donaterIsOrg) { + const otherUser = transaction.donater_organization && transaction.donater_organization; + otherUserName = otherUser.name; + } else { + const otherUser = transaction.donater_user && transaction.donater_user; + otherUserName = otherUser.firstName; + } + } else { + // other user is claimer + otherUserName = transaction.claimer && transaction.claimer.name; + } + return ( - - Re: {transaction.asset.title} + + Message with {otherUserName} {socket ? (
diff --git a/server/src/acccount-manager/entities/user.entity.ts b/server/src/acccount-manager/entities/user.entity.ts index 8e6e097e..ce021f03 100644 --- a/server/src/acccount-manager/entities/user.entity.ts +++ b/server/src/acccount-manager/entities/user.entity.ts @@ -1,4 +1,4 @@ -import { Entity, Column, PrimaryGeneratedColumn, OneToMany, JoinColumn } from 'typeorm'; +import { Entity, Column, PrimaryGeneratedColumn, OneToMany } from 'typeorm'; import { UserOrganization } from '../../user-org/entities/user-org.entity'; import { Asset } from '../../assets/entities/asset.entity'; From 07dec53e0206f19ea5930f22d88c02f413cf7dc4 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Tue, 18 Apr 2023 12:11:27 -0400 Subject: [PATCH 33/36] show company image in chat --- client/src/components/Users/Inbox/TransactionThreadCard.tsx | 1 + client/src/types/index.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/client/src/components/Users/Inbox/TransactionThreadCard.tsx b/client/src/components/Users/Inbox/TransactionThreadCard.tsx index 36f5e616..54039d4e 100644 --- a/client/src/components/Users/Inbox/TransactionThreadCard.tsx +++ b/client/src/components/Users/Inbox/TransactionThreadCard.tsx @@ -60,6 +60,7 @@ function TransactionThreadCard({ } else { // other user is claimer otherUserName = transaction.claimer && transaction.claimer.name; + otherUserImage = transaction.claimer && transaction.claimer.image_url; } return ( diff --git a/client/src/types/index.ts b/client/src/types/index.ts index 82c26081..1605be03 100644 --- a/client/src/types/index.ts +++ b/client/src/types/index.ts @@ -25,6 +25,7 @@ export type Organization = { state: string; ein: string; nonprofit_classification: string; + image_url: string; }; export enum Role { From e2788797b19122fd1b1ac76852711279acb1a8d7 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Wed, 19 Apr 2023 12:25:56 -0400 Subject: [PATCH 34/36] add handling for disconnected websocket --- client/src/views/Messages.tsx | 2 +- client/src/views/NewMessage.tsx | 6 ++++-- client/src/views/TempChat.tsx | 14 +++++++++++--- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/client/src/views/Messages.tsx b/client/src/views/Messages.tsx index 194ab7b7..280dbed3 100644 --- a/client/src/views/Messages.tsx +++ b/client/src/views/Messages.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Message } from '../types'; import MessageCard from '../components/Users/Inbox/MessageCard'; -function Messages({ socket, messages, transaction, user }: any) { +function Messages({ messages, transaction, user }: any) { const formatDate = (date: string): string => { return new Date(date).toLocaleString('en-US'); }; diff --git a/client/src/views/NewMessage.tsx b/client/src/views/NewMessage.tsx index 7c362fef..359aaf7e 100644 --- a/client/src/views/NewMessage.tsx +++ b/client/src/views/NewMessage.tsx @@ -6,8 +6,10 @@ const NewMessage = ({ socket, transactionId, classes }: any) => { const [value, setValue] = useState(''); const submitForm = (e: any) => { e.preventDefault(); - socket.emit('message', { text: value, transactionId: transactionId, fromClaimer: false }); - setValue(''); + if (socket.connected) { + socket.emit('message', { text: value, transactionId: transactionId, fromClaimer: false }); + setValue(''); + } }; const handleOnChange = (e: any): void => { diff --git a/client/src/views/TempChat.tsx b/client/src/views/TempChat.tsx index 2e961215..9c1a521b 100644 --- a/client/src/views/TempChat.tsx +++ b/client/src/views/TempChat.tsx @@ -14,6 +14,7 @@ type TempChatProps = { const TempChat = (props: TempChatProps) => { const { classes, transaction, messages, user } = props; const [socket, setSocket] = useState(null); + const [connected, setConnected] = useState(false); const [newMessages, setNewMessages] = useState([]); const [showTypingGesture, setShowTypingGesture] = useState(false); const [userTyping, setUserTyping] = useState(''); @@ -23,7 +24,11 @@ const TempChat = (props: TempChatProps) => { withCredentials: true, }; const newSocket = io(`http://${window.location.hostname}:3002`, opts); - newSocket.emit('join', { transactionId: transaction.id }); + + newSocket.on('connect', () => { + newSocket.emit('join', { transactionId: transaction.id }); + setConnected(true); + }); // to do - send org id if user is logged in as an organization setSocket(newSocket); @@ -35,9 +40,11 @@ const TempChat = (props: TempChatProps) => { newSocket.on('join', (res: any) => { console.log(res); }); + newSocket.on('disconnect', (res: any) => { + setConnected(false); + }); newSocket.on('typing', (res: { name: string; isTyping: boolean }) => { - console.log(res); setShowTypingGesture(res.isTyping); setUserTyping(res.name); }); @@ -82,8 +89,9 @@ const TempChat = (props: TempChatProps) => { ) : (
Not Connected
)} - {showTypingGesture &&

... {userTyping} is typing

} + {showTypingGesture &&

... {userTyping} is typing

} + {!connected ? 'Connecting to server...' : null}
From 88324b9dba7f8078ed4d0aac2002e5a9805cd819 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Wed, 17 Apr 2024 23:06:56 -0400 Subject: [PATCH 35/36] update message seen functionality --- client/src/FetchActions.ts | 46 +++++++++++++++++ .../components/Users/Inbox/MessageCard.tsx | 5 +- client/src/types/index.ts | 1 + client/src/views/Messages.tsx | 49 +++++++++++-------- server/src/messages/dto/create-message.dto.ts | 5 +- server/src/messages/dto/return-message.dto.ts | 20 +++++++- server/src/messages/dto/update-message.dto.ts | 2 +- .../src/messages/entities/message.entity.ts | 5 +- server/src/messages/messages.controller.ts | 6 +-- .../transactions/transactions.controller.ts | 2 +- 10 files changed, 110 insertions(+), 31 deletions(-) diff --git a/client/src/FetchActions.ts b/client/src/FetchActions.ts index 31a03123..d6b118a9 100644 --- a/client/src/FetchActions.ts +++ b/client/src/FetchActions.ts @@ -1,4 +1,5 @@ import { APP_API_BASE_URL } from './configs'; +import { Message } from './types'; export const getRequest = ( url: string, @@ -26,6 +27,42 @@ export const getRequest = ( } }); }; + +export const patchRequest = ( + url: string, + body: any, + onSuccess: Function, + abortController?: AbortController, + onError = (statusCode?: Number, statusText?: string) => {}, +) => { + let options = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + signal: abortController?.signal, + body: JSON.stringify(body), + }; + fetch(url, options) + .then((resp) => { + if (!resp.ok) { + onError(resp.status, resp.statusText); + } else { + return resp.json(); + } + }) + .then((data) => onSuccess(data)) + .catch((e) => { + if (e.name === 'AbortError') { + // usually, this abort is intentional due to a change in props + console.log('fetch aborted'); + } else { + onError(); + } + }); +}; +// best practice for fetching is to allow fetches to be aborted, in case props change while a fetch is in progress + export const fetchNeeds = ( limit: string, offset: string, @@ -63,3 +100,12 @@ export const fetchInbox = ( const assetsApiRequest = new URL(`${APP_API_BASE_URL}/transactions/inbox`); getRequest(assetsApiRequest.href + assetsApiRequest.hash, onSuccess, abortController, onError); }; + +export const updateMessage = ( + messageBody: Message, + onSuccess: Function, + onError = (statusCode?: Number, statusText?: string) => {}, +) => { + const messageApiRequest = new URL(`${APP_API_BASE_URL}/messages/${messageBody.id}`); + patchRequest(messageApiRequest.href, messageBody, onSuccess, new AbortController(), () => {}); +}; diff --git a/client/src/components/Users/Inbox/MessageCard.tsx b/client/src/components/Users/Inbox/MessageCard.tsx index db5e33ef..0afa4122 100644 --- a/client/src/components/Users/Inbox/MessageCard.tsx +++ b/client/src/components/Users/Inbox/MessageCard.tsx @@ -4,6 +4,7 @@ import { makeStyles } from 'tss-react/mui'; import type { Theme } from '@mui/material/styles'; import { Typography } from '@mui/material'; +import { Button } from '@mui/base'; const useStyles = makeStyles()((theme: Theme) => ({ currentUserMessage: { @@ -30,14 +31,15 @@ function MessageCard({ senderName, isCurrentUser, dateString, + messageReadCallback, }: { senderName: string; text: string; isCurrentUser: boolean; dateString: string; + messageReadCallback: React.MouseEventHandler; }): JSX.Element { const { classes } = useStyles(); - return (
@@ -46,6 +48,7 @@ function MessageCard({ {dateString} +
); } diff --git a/client/src/types/index.ts b/client/src/types/index.ts index 79ca6960..08a6a0df 100644 --- a/client/src/types/index.ts +++ b/client/src/types/index.ts @@ -78,6 +78,7 @@ export type Message = { sendingUserId: number | null; sendingOrgId: number | null; created_date: string; + read: boolean; }; export type Option = { diff --git a/client/src/views/Messages.tsx b/client/src/views/Messages.tsx index 280dbed3..0e8a6bb4 100644 --- a/client/src/views/Messages.tsx +++ b/client/src/views/Messages.tsx @@ -1,38 +1,47 @@ import React from 'react'; -import { Message } from '../types'; +import { Message, Transaction } from '../types'; import MessageCard from '../components/Users/Inbox/MessageCard'; +import { patchRequest, updateMessage } from '../FetchActions'; function Messages({ messages, transaction, user }: any) { const formatDate = (date: string): string => { return new Date(date).toLocaleString('en-US'); }; + const getSenderName = (transaction: Transaction, message: Message) => { + if (message.sendingOrgId) { + if (message.sendingOrgId === transaction.claimer?.id) { + return transaction.claimer.name; + } else if (message.sendingOrgId === transaction.donater_organization?.id) { + return transaction.donater_organization.name; + } else { + return ''; + } + } else if (message.sendingUserId === (user && user.id)) { + return user.firstName; + } else { + return transaction.donater_user?.firstName; + } + }; + return (
{messages.map((message: Message) => { - const sendingOrg = [transaction.donater_organization, transaction.claimer].find( - (org) => org && org.id === message.sendingOrgId, - ); - const userOrg = - user && - user.organizations[0] && - user.organizations[0].organization && - user.organizations[0].organization.id; - - let sendingUser = null; - if (transaction.donater_user && transaction.donater_user.id === message.sendingUserId) { - sendingUser = transaction.donater_user; - } - const senderName = sendingOrg ? sendingOrg.name : sendingUser && sendingUser.firstName; - const isCurrentUser = - message.sendingUserId === user.id || (userOrg && userOrg === message.sendingOrgId); - + const markMessageRead: React.MouseEventHandler = (event) => + updateMessage( + message, + () => {}, + () => { + console.log('error'); + }, + ); return ( ); })} diff --git a/server/src/messages/dto/create-message.dto.ts b/server/src/messages/dto/create-message.dto.ts index 463f2f6a..6918d3ad 100644 --- a/server/src/messages/dto/create-message.dto.ts +++ b/server/src/messages/dto/create-message.dto.ts @@ -8,6 +8,9 @@ export class CreateMessageDto { @IsNotEmpty() text: string; + @IsOptional() + read: boolean + @IsNotEmpty() transaction: Transaction; @@ -15,5 +18,5 @@ export class CreateMessageDto { sending_user: User; @IsOptional() - sending_org: Organization; + sending_org?: Organization; } diff --git a/server/src/messages/dto/return-message.dto.ts b/server/src/messages/dto/return-message.dto.ts index f9ad59cb..4a9490b8 100644 --- a/server/src/messages/dto/return-message.dto.ts +++ b/server/src/messages/dto/return-message.dto.ts @@ -1,8 +1,24 @@ -import { IsNotEmpty } from 'class-validator'; +import { IsNotEmpty, IsOptional } from 'class-validator'; import { CreateMessageDto } from './create-message.dto'; +import { Transaction } from 'src/transactions/entities/transaction.entity'; +import { User } from 'src/acccount-manager/entities/user.entity'; +import { Organization } from 'src/organizations/entities/organization.entity'; export class ReturnMessageDto extends CreateMessageDto { + id: number + + text: string + + read: boolean + @IsNotEmpty() - id: number; + transaction: Transaction; + + @IsOptional() + sending_user: User; + + @IsOptional() + sending_org?: Organization; + } diff --git a/server/src/messages/dto/update-message.dto.ts b/server/src/messages/dto/update-message.dto.ts index 2e88d19c..2972523e 100644 --- a/server/src/messages/dto/update-message.dto.ts +++ b/server/src/messages/dto/update-message.dto.ts @@ -2,4 +2,4 @@ import { PickType } from '@nestjs/swagger'; import { CreateMessageDto } from './create-message.dto'; -export class UpdateMessageDto extends PickType(CreateMessageDto, ['text'] as const) {} +export class UpdateMessageDto extends PickType(CreateMessageDto, ['text', 'read' ] as const) {} diff --git a/server/src/messages/entities/message.entity.ts b/server/src/messages/entities/message.entity.ts index 0d480619..4c08b135 100644 --- a/server/src/messages/entities/message.entity.ts +++ b/server/src/messages/entities/message.entity.ts @@ -43,10 +43,11 @@ export class Message { @Column({ nullable: true }) sendingOrgId: number; + @Column() + read: boolean + @ManyToOne(() => Transaction, (transaction) => transaction.messages) @JoinColumn() transaction: Transaction; - @OneToMany(() => Receivedmessage, (userMessage) => userMessage.message) - readReceipts?: Receivedmessage[]; } diff --git a/server/src/messages/messages.controller.ts b/server/src/messages/messages.controller.ts index 35dc09ce..3345c752 100644 --- a/server/src/messages/messages.controller.ts +++ b/server/src/messages/messages.controller.ts @@ -30,7 +30,7 @@ export class MessagesController { async create( @Request() request: ExpressRequest, @Body() createMessageDto: CreateMessageDto, - ): Promise { + ): Promise { const newMessage = await this.messagesService.create(createMessageDto); return newMessage; } @@ -52,10 +52,10 @@ export class MessagesController { @Patch(':id') @ApiOperation({ summary: 'Update a message.' }) async update( - @Param('id') id: string, + @Param('id') id: number, @Body() updateMessageDto: UpdateMessageDto, ): Promise { - return this.messagesService.update(+id, updateMessageDto); + return this.messagesService.update(id, updateMessageDto); } @Delete(':id') diff --git a/server/src/transactions/transactions.controller.ts b/server/src/transactions/transactions.controller.ts index d9ab88c3..5c630654 100644 --- a/server/src/transactions/transactions.controller.ts +++ b/server/src/transactions/transactions.controller.ts @@ -1,4 +1,4 @@ -import { Get, Post, Body, Query, Param, Patch, Delete, Controller } from '@nestjs/common'; +import { Get, Post, Body, Query, Param, Patch, Delete, Controller, UseGuards } from '@nestjs/common'; import { ApiTags, ApiOperation } from '@nestjs/swagger'; import { TransactionsService } from './transactions.service'; From 315ffa45eb278e828453d21e1739a347f8a14c38 Mon Sep 17 00:00:00 2001 From: Skylar Salerno Date: Mon, 17 Jun 2024 14:13:22 -0400 Subject: [PATCH 36/36] bugfix-messages --- .../Auth/SignUpCitizen/SignUpCitizen.tsx | 6 +-- .../account-manager.controller.ts | 14 +++--- .../acccount-manager/entities/user.entity.ts | 4 -- server/src/database/seeding/seed-data.ts | 3 ++ server/src/messages/dto/create-message.dto.ts | 2 +- .../src/messages/entities/message.entity.ts | 1 - server/src/messages/messages.module.ts | 3 +- server/src/migrations/.keep | 0 server/src/poc-chat/poc-chat.gateway.ts | 3 +- .../entities/received-messages.entity.ts | 45 ------------------- .../transactions/transactions.controller.ts | 5 ++- test | 0 12 files changed, 20 insertions(+), 66 deletions(-) create mode 100644 server/src/migrations/.keep delete mode 100644 server/src/received-messages/entities/received-messages.entity.ts create mode 100644 test diff --git a/client/src/components/Users/Auth/SignUpCitizen/SignUpCitizen.tsx b/client/src/components/Users/Auth/SignUpCitizen/SignUpCitizen.tsx index 68bd45a4..8d477357 100644 --- a/client/src/components/Users/Auth/SignUpCitizen/SignUpCitizen.tsx +++ b/client/src/components/Users/Auth/SignUpCitizen/SignUpCitizen.tsx @@ -70,17 +70,17 @@ function SignupCitizen() { useEffect(() => { if (submitForm) { - registerUserMutation.mutate(formData); setSubmitForm(false); + registerUserMutation.mutate(formData); } - }, [submitForm, setSubmitForm, registerUserMutation, formData]); + }, [submitForm, setSubmitForm, formData]); useEffect(() => { if (submitProfile) { updateProfileMutation.mutate({ file: image!, userId: user!['id'] }); setSubmitProfile(false); } - }, [submitProfile, updateProfileMutation, formData, image, user]); + }, [submitProfile, formData, image, user]); const handleNext = (newFormData: {}, doSubmit = false) => { setFormData((currFormData) => ({ diff --git a/server/src/acccount-manager/account-manager.controller.ts b/server/src/acccount-manager/account-manager.controller.ts index bff5bcc6..1b3b8974 100644 --- a/server/src/acccount-manager/account-manager.controller.ts +++ b/server/src/acccount-manager/account-manager.controller.ts @@ -78,7 +78,7 @@ export class AccountManagerController { } @MapTo(ReturnUserDto) - @Post('signup') + @Post('register') async signup(@Body() signupDto: CreateUserDto): Promise { const exists = await this.usersService.userEmailExists(signupDto.email); @@ -146,12 +146,12 @@ export class AccountManagerController { } // TODO: we probably need a better solution for this - if (!user.email_verified && process.env.NODE_ENV === 'staging') { - throw new HttpException( - { status: HttpStatus.UNAUTHORIZED, message: 'Unauthorized' }, - HttpStatus.UNAUTHORIZED, - ); - } + // if (!user.email_verified && process.env.NODE_ENV === 'staging') { + // throw new HttpException( + // { status: HttpStatus.UNAUTHORIZED, message: 'Unauthorized' }, + // HttpStatus.UNAUTHORIZED, + // ); + // } const jwt = await this.accountManagerService.createJwt(user); response diff --git a/server/src/acccount-manager/entities/user.entity.ts b/server/src/acccount-manager/entities/user.entity.ts index ce021f03..692a13cb 100644 --- a/server/src/acccount-manager/entities/user.entity.ts +++ b/server/src/acccount-manager/entities/user.entity.ts @@ -4,7 +4,6 @@ import { UserOrganization } from '../../user-org/entities/user-org.entity'; import { Asset } from '../../assets/entities/asset.entity'; import { Message } from '../../messages/entities/message.entity'; import { Transaction } from '../../transactions/entities/transaction.entity'; -import { Receivedmessage } from '../../received-messages/entities/received-messages.entity'; @Entity('users') export class User { @@ -50,9 +49,6 @@ export class User { @OneToMany(() => Message, (message) => message.sending_user) sentMessages: Message[]; - @OneToMany(() => Receivedmessage, (received) => received.message) - receivedMessages: Message[]; - @OneToMany(() => UserOrganization, (user_org) => user_org.user, { eager: true }) organizations: UserOrganization[]; diff --git a/server/src/database/seeding/seed-data.ts b/server/src/database/seeding/seed-data.ts index 25dec86c..1ba8074f 100644 --- a/server/src/database/seeding/seed-data.ts +++ b/server/src/database/seeding/seed-data.ts @@ -294,12 +294,15 @@ export const seedMessages = (): CreateMessageDto[] => { transaction: null, sending_user: null, sending_org: null, + read: false, }, { text: 'I would like to accept the furniture.', transaction: null, sending_user: null, sending_org: null, + read: false, + }, ]; return messages; diff --git a/server/src/messages/dto/create-message.dto.ts b/server/src/messages/dto/create-message.dto.ts index 6918d3ad..3b625aa2 100644 --- a/server/src/messages/dto/create-message.dto.ts +++ b/server/src/messages/dto/create-message.dto.ts @@ -9,7 +9,7 @@ export class CreateMessageDto { text: string; @IsOptional() - read: boolean + read: boolean; @IsNotEmpty() transaction: Transaction; diff --git a/server/src/messages/entities/message.entity.ts b/server/src/messages/entities/message.entity.ts index 4c08b135..a0ac993c 100644 --- a/server/src/messages/entities/message.entity.ts +++ b/server/src/messages/entities/message.entity.ts @@ -13,7 +13,6 @@ import { import { User } from '../../acccount-manager/entities/user.entity'; import { Transaction } from '../../transactions/entities/transaction.entity'; import { IsOptional } from 'class-validator'; -import { Receivedmessage } from '../../received-messages/entities/received-messages.entity'; @Entity('messages') export class Message { diff --git a/server/src/messages/messages.module.ts b/server/src/messages/messages.module.ts index 1c94f594..979d1abc 100644 --- a/server/src/messages/messages.module.ts +++ b/server/src/messages/messages.module.ts @@ -4,10 +4,9 @@ import { MessagesService } from './messages.service'; import { MessagesController } from './messages.controller'; import { Message } from './entities/message.entity'; import { AcccountManagerModule } from '../acccount-manager/acccount-manager.module'; -import { Receivedmessage } from 'src/received-messages/entities/received-messages.entity'; @Module({ - imports: [TypeOrmModule.forFeature([Message, Receivedmessage]), AcccountManagerModule], + imports: [TypeOrmModule.forFeature([Message]), AcccountManagerModule], controllers: [MessagesController], providers: [MessagesService], exports: [MessagesService], diff --git a/server/src/migrations/.keep b/server/src/migrations/.keep new file mode 100644 index 00000000..e69de29b diff --git a/server/src/poc-chat/poc-chat.gateway.ts b/server/src/poc-chat/poc-chat.gateway.ts index 22250a1b..2f001bfb 100644 --- a/server/src/poc-chat/poc-chat.gateway.ts +++ b/server/src/poc-chat/poc-chat.gateway.ts @@ -117,8 +117,9 @@ export class PocChatGateway { transaction, sending_org, sending_user, + read: false, }); - if (receiving_org) { + if (receiving_org) { // TODO: create seen message record } return message; diff --git a/server/src/received-messages/entities/received-messages.entity.ts b/server/src/received-messages/entities/received-messages.entity.ts deleted file mode 100644 index 9605d5d2..00000000 --- a/server/src/received-messages/entities/received-messages.entity.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { - Column, - CreateDateColumn, - Entity, - JoinColumn, - ManyToMany, - ManyToOne, - PrimaryGeneratedColumn, -} from 'typeorm'; -import { User } from '../../acccount-manager/entities/user.entity'; -import { Transaction } from '../../transactions/entities/transaction.entity'; -import { IsOptional } from 'class-validator'; -import { Message } from '../../messages/entities/message.entity'; - -@Entity('receivedmessages') -export class Receivedmessage { - @PrimaryGeneratedColumn() - id: number; - - @Column('text') - text: string; - - @CreateDateColumn({ - type: 'timestamp', - default: () => 'CURRENT_TIMESTAMP(6)', - }) - created_date: Date; - - @ManyToMany(() => User, (user) => user.receivedMessages) - @JoinColumn() - sending_user: User; - - @ManyToOne(() => Message, (message) => message.readReceipts, { eager: true }) - @JoinColumn() - message: Message; - - @Column({ nullable: true }) - messageId: number; - - @Column({ nullable: true }) - sendingUserId: number; - - @Column('boolean') - seen: boolean; -} diff --git a/server/src/transactions/transactions.controller.ts b/server/src/transactions/transactions.controller.ts index 5c630654..46320711 100644 --- a/server/src/transactions/transactions.controller.ts +++ b/server/src/transactions/transactions.controller.ts @@ -1,10 +1,11 @@ import { Get, Post, Body, Query, Param, Patch, Delete, Controller, UseGuards } from '@nestjs/common'; import { ApiTags, ApiOperation } from '@nestjs/swagger'; - +import { Request } from '@nestjs/common'; import { TransactionsService } from './transactions.service'; import { CreateTransactionDto } from './dto/create-transaction.dto'; import { GetTransactionsDto } from './dto/get-transactions-filter.dto'; import { UpdateTransactionDto } from './dto/update-transaction.dto'; +import { Transaction } from './entities/transaction.entity'; import { CookieAuthGuard } from 'src/acccount-manager/guards/cookie-auth.guard'; import { ReturnTransactionDto } from './dto/return-transaction.dto'; @@ -28,7 +29,7 @@ export class TransactionsController { @UseGuards(CookieAuthGuard) @Get('/inbox') // returns trasnactions with latest messages - async userInbox(@Request() req: Request): Promise { + async userInbox( @Request() req: Request): Promise { const user = req['user']; const userOrgs = await user.organizations; const org_id = userOrgs && userOrgs.length > 0 ? userOrgs[0].organizationId : false; diff --git a/test b/test new file mode 100644 index 00000000..e69de29b