feat(server): search unknown place (#10866)

* Allow submission of null country

* Update searchAssetBuilder to handle nulls

andWhere({country:null}) produces `"exifInfo"."country" = NULL`. We want
`"exifInfo"."country" IS NULL`, so we have to treat NULL as a special
case

* Allow null country in frontend

* Make the query code a bit more straightforward

* Remove unused brackets import

* Remove log message

* Don't change whitespace for no reason

* Fix prettier style issue

* Update search.dto.ts validators per @jrasm91's recommendation

* Update api types

* Combine null country and state into one guard clause

* chore: clean up

* chore: add e2e for null/empty city, state, country search

* refactor: server returns suggestion for null values

* chore: clean up

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
Co-authored-by: Jason Rasmussen <jason@rasm.me>
This commit is contained in:
Justin Forseth
2024-08-01 21:27:40 -06:00
committed by GitHub
parent 3afb5b497f
commit d3a5490e71
21 changed files with 378 additions and 217 deletions
@@ -1,4 +1,5 @@
import { mapAsset } from 'src/dtos/asset-response.dto';
import { SearchSuggestionType } from 'src/dtos/search.dto';
import { IAssetRepository } from 'src/interfaces/asset.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { IMachineLearningRepository } from 'src/interfaces/machine-learning.interface';
@@ -95,4 +96,22 @@ describe(SearchService.name, () => {
expect(result).toEqual(expectedResponse);
});
});
describe('getSearchSuggestions', () => {
it('should return search suggestions (including null)', async () => {
metadataMock.getCountries.mockResolvedValue(['USA', null]);
await expect(
sut.getSearchSuggestions(authStub.user1, { includeNull: true, type: SearchSuggestionType.COUNTRY }),
).resolves.toEqual(['USA', null]);
expect(metadataMock.getCountries).toHaveBeenCalledWith(authStub.user1.user.id);
});
it('should return search suggestions (without null)', async () => {
metadataMock.getCountries.mockResolvedValue(['USA', null]);
await expect(
sut.getSearchSuggestions(authStub.user1, { includeNull: false, type: SearchSuggestionType.COUNTRY }),
).resolves.toEqual(['USA']);
expect(metadataMock.getCountries).toHaveBeenCalledWith(authStub.user1.user.id);
});
});
});
+14 -6
View File
@@ -120,22 +120,30 @@ export class SearchService {
return assets.map((asset) => mapAsset(asset));
}
getSearchSuggestions(auth: AuthDto, dto: SearchSuggestionRequestDto): Promise<string[]> {
async getSearchSuggestions(auth: AuthDto, dto: SearchSuggestionRequestDto) {
const results = await this.getSuggestions(auth.user.id, dto);
return results.filter((result) => (dto.includeNull ? true : result !== null));
}
private getSuggestions(userId: string, dto: SearchSuggestionRequestDto) {
switch (dto.type) {
case SearchSuggestionType.COUNTRY: {
return this.metadataRepository.getCountries(auth.user.id);
return this.metadataRepository.getCountries(userId);
}
case SearchSuggestionType.STATE: {
return this.metadataRepository.getStates(auth.user.id, dto.country);
return this.metadataRepository.getStates(userId, dto.country);
}
case SearchSuggestionType.CITY: {
return this.metadataRepository.getCities(auth.user.id, dto.country, dto.state);
return this.metadataRepository.getCities(userId, dto.country, dto.state);
}
case SearchSuggestionType.CAMERA_MAKE: {
return this.metadataRepository.getCameraMakes(auth.user.id, dto.model);
return this.metadataRepository.getCameraMakes(userId, dto.model);
}
case SearchSuggestionType.CAMERA_MODEL: {
return this.metadataRepository.getCameraModels(auth.user.id, dto.make);
return this.metadataRepository.getCameraModels(userId, dto.make);
}
default: {
return [];
}
}
}