-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: fetching customers of businesses via businesses API
- Loading branch information
1 parent
ed8b6b9
commit bb89303
Showing
19 changed files
with
306 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,28 @@ | ||
import { | ||
Column, | ||
Entity, | ||
JoinColumn, | ||
ManyToOne, | ||
PrimaryGeneratedColumn, | ||
RelationId, | ||
} from 'typeorm'; | ||
import { BusinessEntity } from '../business/business.entity'; | ||
|
||
@Entity() | ||
export const CUSTOMER_TABLE_NAME = 'customers'; | ||
|
||
@Entity(CUSTOMER_TABLE_NAME) | ||
export class CustomerEntity { | ||
@PrimaryGeneratedColumn('uuid') | ||
id: string; | ||
|
||
@Column('varchar', { length: 200 }) | ||
name: string; | ||
|
||
@RelationId((customer: CustomerEntity) => customer.shopAt) | ||
// https://stackoverflow.com/a/61433772/8784518 | ||
// @RelationId is buggy! | ||
@Column('uuid') | ||
shopAtId: string; | ||
|
||
@ManyToOne(() => BusinessEntity, (business) => business.customers) | ||
@JoinColumn({ name: 'shopAtId' }) | ||
shopAt: BusinessEntity; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import { Repository } from 'typeorm'; | ||
import { CustomerEntity } from './customer.entity'; | ||
import { | ||
RawFindAllByBusinessesIdsCustomer, | ||
SerializedFindAllByBusinessesIds, | ||
} from './customer.type'; | ||
|
||
export class CustomerRepository { | ||
constructor( | ||
public readonly repository: Repository<CustomerEntity>, | ||
) {} | ||
|
||
async findAllByBusinessesIds(businessesIds: readonly string[]) { | ||
/** | ||
* @description | ||
* - Result needs to be serialized. | ||
* - We have to use `this.repository.manager.connection.createQueryBuilder` | ||
* since if we use `this.repository.createQueryBuilder` it'll add extra | ||
* `FROM` clause, generating a different query! | ||
* | ||
* @link https://github.com/typeorm/typeorm/issues/4015 | ||
* @link https://stackoverflow.com/a/73916935/8784518 | ||
*/ | ||
const result = await this.repository.manager.connection | ||
.createQueryBuilder() | ||
.select('*') | ||
.from((queryBuilder) => { | ||
return queryBuilder | ||
.select('*') | ||
.addSelect( | ||
'ROW_NUMBER() OVER(PARTITION BY "shopAtId" ORDER BY id)', | ||
) | ||
.from(CustomerEntity, 'customers') | ||
.where('"shopAtId" IN (:...ids)', { ids: businessesIds }); | ||
}, 'customers_of_shops') | ||
.where('row_number <= :length', { | ||
length: businessesIds.length, | ||
}) | ||
.getRawMany(); | ||
|
||
return this.serializeFindAllByBusinessesIds(result); | ||
} | ||
|
||
private serializeFindAllByBusinessesIds( | ||
result: RawFindAllByBusinessesIdsCustomer[], | ||
): SerializedFindAllByBusinessesIds[] { | ||
return result.map((unsanitizedCustomer) => { | ||
const { row_number, ...rest } = unsanitizedCustomer; | ||
return { | ||
...rest, | ||
}; | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { | ||
CursorPager, | ||
FilterQueryBuilder, | ||
PagingDto, | ||
QueryService, | ||
} from '@shared'; | ||
import { Args, Query, Resolver } from 'type-graphql'; | ||
import { AppDataSource } from '../shared/data-source'; | ||
import { CustomerEntity } from './customer.entity'; | ||
import { CustomerRepository } from './customer.repository'; | ||
import { CustomerService } from './customer.service'; | ||
import { | ||
CustomerDto, | ||
CustomerDtoConnection, | ||
} from './dto/customer.dto'; | ||
|
||
@Resolver(() => CustomerDto) | ||
export class CustomerResolver { | ||
private static customerService: CustomerService; | ||
|
||
static init() { | ||
const cursorPager = new CursorPager(CustomerDto, ['id']); | ||
const customerRepository = | ||
AppDataSource.getRepository(CustomerEntity); | ||
const filterQueryBuilder = new FilterQueryBuilder( | ||
customerRepository, | ||
); | ||
const queryService = new QueryService(filterQueryBuilder); | ||
CustomerResolver.customerService = new CustomerService( | ||
cursorPager, | ||
queryService, | ||
new CustomerRepository(customerRepository), | ||
); | ||
} | ||
|
||
@Query(() => CustomerDtoConnection) | ||
customers(@Args(() => PagingDto) paging: PagingDto) { | ||
CustomerResolver.customerService.findAll(paging); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { CursorPager, PagingDto, QueryService } from '@shared'; | ||
import { CustomerEntity } from './customer.entity'; | ||
import { CustomerRepository } from './customer.repository'; | ||
import { SerializedFindAllByBusinessesIds } from './customer.type'; | ||
import { CustomerDto } from './dto/customer.dto'; | ||
|
||
export class CustomerService { | ||
constructor( | ||
private readonly cursorPager: CursorPager<CustomerDto>, | ||
private readonly queryService: QueryService<CustomerEntity>, | ||
private readonly customerRepository: CustomerRepository, | ||
) {} | ||
|
||
findAll(paging: PagingDto) { | ||
return this.cursorPager.page( | ||
(query) => this.queryService.query(query), | ||
{ paging }, | ||
); | ||
} | ||
|
||
async getCustomersByBatch( | ||
businessesIds: readonly string[], | ||
): Promise<Array<SerializedFindAllByBusinessesIds[] | Error>> { | ||
const customers = | ||
await this.customerRepository.findAllByBusinessesIds( | ||
businessesIds, | ||
); | ||
const mappedResults = this.mapCustomersToBusinessesIds( | ||
customers, | ||
businessesIds, | ||
); | ||
|
||
return mappedResults; | ||
} | ||
|
||
/** | ||
* @description | ||
* When using Dataloader we have to fulfill 2 requirements: | ||
* 1. `?? null` part: The length of the returned array must be the same with the length of the supplied keys. | ||
* We need to return `null` if a customer is not found for a given business ID. | ||
* 2. `customers.filter` part: The returned values must be ordered in the same order as the supplied keys. | ||
* E.g. if the keys are `[1, 3, 4]`, the value must be something like `[customerOfBusiness1, customerOfBusiness3, customerOfBusiness4]`. | ||
* The data source might not return them in the same order, so we have to reorder them. | ||
*/ | ||
private mapCustomersToBusinessesIds( | ||
customers: Readonly<SerializedFindAllByBusinessesIds[]>, | ||
businessesIds: Readonly<string[]>, | ||
): SerializedFindAllByBusinessesIds[][] { | ||
const mappedCustomers = businessesIds.map((businessId) => { | ||
const customer = customers.filter( | ||
(customer) => customer.shopAtId === businessId, | ||
); | ||
|
||
return customer ?? null; | ||
}); | ||
|
||
// console.log(mappedCustomers); | ||
|
||
return mappedCustomers; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { CustomerEntity } from './customer.entity'; | ||
|
||
export interface RawFindAllByBusinessesIdsCustomer { | ||
/**@example '000d2a08-21d4-4482-a4a6-f18f67c62787' */ | ||
id: string; | ||
/**@example 'customer01' */ | ||
name: string; | ||
/**@example '0088cbc0-c82a-473c-bcae-6954610ddd72' */ | ||
shopAtId: string; | ||
/** | ||
* @description This field in the DB is of type `bigint` but it seems that TypeORM is converting it to `string`. | ||
* @example '1' | ||
*/ | ||
row_number: string; | ||
} | ||
export type SerializedFindAllByBusinessesIds = Omit< | ||
CustomerEntity, | ||
'shopAt' | ||
>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.