Skip to content

Commit d475991

Browse files
authored
Merge pull request #176 from cuappdev/fix/review-seller-filter
Add server-side seller filtering for review list endpoints.
2 parents d19e813 + 85021e3 commit d475991

10 files changed

Lines changed: 182 additions & 7 deletions

src/api/controllers/TransactionReviewController.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,16 @@ export class TransactionReviewController {
2121
}
2222

2323
@Get()
24-
async getTransactionReviews(): Promise<{ reviews: TransactionReviewModel[] }> {
25-
return { reviews: await this.transactionReviewService.getAllTransactionReviews() };
24+
async getTransactionReviews(
25+
@QueryParam("sellerId") sellerId?: string,
26+
): Promise<{ reviews: TransactionReviewModel[] }> {
27+
return {
28+
reviews: sellerId
29+
? await this.transactionReviewService.getTransactionReviewsBySellerId(
30+
sellerId,
31+
)
32+
: await this.transactionReviewService.getAllTransactionReviews(),
33+
};
2634
}
2735

2836
@Get('id/:id/')

src/api/controllers/UserReviewController.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
import { Body, CurrentUser, Delete, Get, JsonController, Params, Post } from 'routing-controllers';
1+
import {
2+
Body,
3+
CurrentUser,
4+
Delete,
5+
Get,
6+
JsonController,
7+
Params,
8+
Post,
9+
QueryParam,
10+
} from 'routing-controllers';
211
import {
312
CreateUserReviewRequest
413
} from '../../types';
@@ -16,8 +25,14 @@ export class UserReviewController {
1625
}
1726

1827
@Get()
19-
async getUserReviews(): Promise<{ reviews: UserReviewModel[] }> {
20-
return { reviews: await this.userReviewService.getAllUserReviews() };
28+
async getUserReviews(
29+
@QueryParam("sellerId") sellerId?: string,
30+
): Promise<{ reviews: UserReviewModel[] }> {
31+
return {
32+
reviews: sellerId
33+
? await this.userReviewService.getUserReviewsBySellerId(sellerId)
34+
: await this.userReviewService.getAllUserReviews(),
35+
};
2136
}
2237

2338
@Get('id/:id/')
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { MigrationInterface, QueryRunner } from "typeorm";
2+
3+
export class UserReviewBuyerSellerNotNull1770000000000
4+
implements MigrationInterface
5+
{
6+
name = "UserReviewBuyerSellerNotNull1770000000000";
7+
8+
public async up(queryRunner: QueryRunner): Promise<void> {
9+
const orphans: Array<{ id: string }> = await queryRunner.query(
10+
`SELECT "id" FROM "UserReview" WHERE "buyerId" IS NULL OR "sellerId" IS NULL`,
11+
);
12+
if (orphans.length > 0) {
13+
console.log(
14+
`UserReviewBuyerSellerNotNull: removing ${orphans.length} UserReview rows missing buyer or seller before tightening constraints.`,
15+
);
16+
await queryRunner.query(
17+
`DELETE FROM "UserReview" WHERE "buyerId" IS NULL OR "sellerId" IS NULL`,
18+
);
19+
}
20+
21+
await queryRunner.query(
22+
`ALTER TABLE "UserReview" ALTER COLUMN "buyerId" SET NOT NULL`,
23+
);
24+
await queryRunner.query(
25+
`ALTER TABLE "UserReview" ALTER COLUMN "sellerId" SET NOT NULL`,
26+
);
27+
}
28+
29+
public async down(queryRunner: QueryRunner): Promise<void> {
30+
await queryRunner.query(
31+
`ALTER TABLE "UserReview" ALTER COLUMN "sellerId" DROP NOT NULL`,
32+
);
33+
await queryRunner.query(
34+
`ALTER TABLE "UserReview" ALTER COLUMN "buyerId" DROP NOT NULL`,
35+
);
36+
}
37+
}

src/models/UserReviewModel.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,15 @@ export class UserReviewModel {
2828
@CreateDateColumn({ type: "timestamptz" })
2929
date: Date;
3030

31-
@ManyToOne(() => UserModel, (buyer) => buyer.reviewsWritten)
31+
@ManyToOne(() => UserModel, (buyer) => buyer.reviewsWritten, {
32+
nullable: false,
33+
})
3234
@JoinColumn({ name: "buyerId" })
3335
buyer: UserModel;
3436

35-
@ManyToOne(() => UserModel, (seller) => seller.reviewsReceived)
37+
@ManyToOne(() => UserModel, (seller) => seller.reviewsReceived, {
38+
nullable: false,
39+
})
3640
@JoinColumn({ name: "sellerId" })
3741
seller: UserModel;
3842

src/repositories/TransactionReviewRepository.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,20 @@ export class TransactionReviewRepository extends AbstractRepository<TransactionR
1111
return await this.repository
1212
.createQueryBuilder("review")
1313
.leftJoinAndSelect("review.transaction", "transaction")
14+
.leftJoinAndSelect("transaction.seller", "seller")
15+
.leftJoinAndSelect("transaction.buyer", "buyer")
16+
.getMany();
17+
}
18+
19+
public async getTransactionReviewsBySellerId(
20+
sellerId: Uuid,
21+
): Promise<TransactionReviewModel[]> {
22+
return await this.repository
23+
.createQueryBuilder("review")
24+
.leftJoinAndSelect("review.transaction", "transaction")
25+
.leftJoinAndSelect("transaction.seller", "seller")
26+
.leftJoinAndSelect("transaction.buyer", "buyer")
27+
.where("seller.firebaseUid = :sellerId", { sellerId })
1428
.getMany();
1529
}
1630

src/repositories/UserReviewRepository.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@ export class UserReviewRepository extends AbstractRepository<UserReviewModel> {
1010
return await this.repository
1111
.createQueryBuilder("review")
1212
.leftJoinAndSelect("review.buyer", "user")
13+
.leftJoinAndSelect("review.seller", "seller")
14+
.getMany();
15+
}
16+
17+
public async getUserReviewsBySellerId(
18+
sellerId: Uuid,
19+
): Promise<UserReviewModel[]> {
20+
return await this.repository
21+
.createQueryBuilder("review")
22+
.leftJoinAndSelect("review.buyer", "user")
23+
.leftJoinAndSelect("review.seller", "seller")
24+
.where("seller.firebaseUid = :sellerId", { sellerId })
1325
.getMany();
1426
}
1527

src/services/TransactionReviewService.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ export class TransactionReviewService {
2727
});
2828
}
2929

30+
public async getTransactionReviewsBySellerId(
31+
sellerId: string,
32+
): Promise<TransactionReviewModel[]> {
33+
return this.transactions.readOnly(async (transactionalEntityManager) => {
34+
const transactionReviewRepository = Repositories.transactionReview(
35+
transactionalEntityManager,
36+
);
37+
return await transactionReviewRepository.getTransactionReviewsBySellerId(
38+
sellerId,
39+
);
40+
});
41+
}
42+
3043
// Get a transaction review by its ID
3144
public async getTransactionReviewById(
3245
params: UuidParam,

src/services/UserReviewService.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ export class UserReviewService {
2626
});
2727
}
2828

29+
public async getUserReviewsBySellerId(sellerId: string): Promise<UserReviewModel[]> {
30+
return this.transactions.readOnly(async (transactionalEntityManager) => {
31+
const userReviewRepository = Repositories.userReview(
32+
transactionalEntityManager,
33+
);
34+
return await userReviewRepository.getUserReviewsBySellerId(sellerId);
35+
});
36+
}
37+
2938
public async getUserReviewById(params: UuidParam): Promise<UserReviewModel> {
3039
return this.transactions.readOnly(async (transactionalEntityManager) => {
3140
const userReviewRepository = Repositories.userReview(

src/tests/TransactionReviewTest.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
DataFactory,
99
TransactionFactory,
1010
TransactionReviewFactory,
11+
UserFactory,
1112
} from "./data";
1213
import { CreateTransactionReviewRequest } from "src/types";
1314

@@ -111,4 +112,40 @@ describe('transaction review tests', () => {
111112
const response = await transactionReviewController.getTransactionReviews();
112113
expect(response.reviews).toHaveLength(2);
113114
});
115+
116+
test("get transaction reviews filtered by sellerId", async () => {
117+
const seller = UserFactory.fakeTemplate2();
118+
const buyer = UserFactory.fakeTemplate();
119+
const otherSeller = UserFactory.fake();
120+
const otherBuyer = UserFactory.fake();
121+
122+
const matchingTransaction = TransactionFactory.fake();
123+
matchingTransaction.seller = seller;
124+
matchingTransaction.buyer = buyer;
125+
126+
const otherTransaction = TransactionFactory.fake();
127+
otherTransaction.seller = otherSeller;
128+
otherTransaction.buyer = otherBuyer;
129+
130+
const matchingReview = TransactionReviewFactory.fake();
131+
matchingReview.transaction = matchingTransaction;
132+
133+
const otherReview = TransactionReviewFactory.fake();
134+
otherReview.transaction = otherTransaction;
135+
136+
await new DataFactory()
137+
.createUsers(seller, buyer, otherSeller, otherBuyer)
138+
.createTransactions(matchingTransaction, otherTransaction)
139+
.createTransactionReviews(matchingReview, otherReview)
140+
.write();
141+
142+
const response = await transactionReviewController.getTransactionReviews(
143+
seller.firebaseUid,
144+
);
145+
146+
expect(response.reviews).toHaveLength(1);
147+
expect(response.reviews[0].transaction.seller.firebaseUid).toEqual(
148+
seller.firebaseUid,
149+
);
150+
});
114151
});

src/tests/UserReviewTest.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,32 @@ describe('user review tests', () => {
5555
const response = await userReviewController.getUserReviews();
5656

5757
expect(response.reviews).toHaveLength(1);
58+
expect(response.reviews[0].seller).toBeDefined();
59+
});
60+
61+
test("get user reviews filtered by sellerId", async () => {
62+
const seller = UserFactory.fakeTemplate2();
63+
const otherSeller = UserFactory.fake();
64+
const buyer = UserFactory.fakeTemplate();
65+
const otherBuyer = UserFactory.fake();
66+
67+
const matchingReview = UserReviewFactory.fake();
68+
matchingReview.buyer = buyer;
69+
matchingReview.seller = seller;
70+
71+
const nonMatchingReview = UserReviewFactory.fake();
72+
nonMatchingReview.buyer = otherBuyer;
73+
nonMatchingReview.seller = otherSeller;
74+
75+
await new DataFactory()
76+
.createUsers(buyer, otherBuyer, seller, otherSeller)
77+
.createUserReviews(matchingReview, nonMatchingReview)
78+
.write();
79+
80+
const response = await userReviewController.getUserReviews(seller.firebaseUid);
81+
82+
expect(response.reviews).toHaveLength(1);
83+
expect(response.reviews[0].seller.firebaseUid).toEqual(seller.firebaseUid);
5884
});
5985

6086
test("get user review by id", async () => {

0 commit comments

Comments
 (0)