diff --git a/__tests__/e2e/countries.spec.ts b/__tests__/e2e/countries.spec.ts index e757540b..c052c492 100644 --- a/__tests__/e2e/countries.spec.ts +++ b/__tests__/e2e/countries.spec.ts @@ -23,6 +23,27 @@ describe('', () => { }); }); + it('countries/v2/6632544', async () => { + const response = await supertest(app).get('/countries/v2/6632544'); + expect(response.status).toBe(200); + expect(response.body).toMatchObject({ + id: 6632544, + name: 'China', + }); + }); + + // 103.819073145824,36.5617653792527 + it('countries/v2/?lat=36.5617653792527&lon=103.819073145824', async () => { + const response = await supertest(app).get( + '/countries/v2/?lat=36.5617653792527&lon=103.819073145824', + ); + expect(response.status).toBe(200); + expect(response.body.countries[0]).toMatchObject({ + id: 6632544, + name: 'China', + }); + }); + it( 'countries/leaderboard', async () => { diff --git a/server/infra/database/CountryRepositoryV2.ts b/server/infra/database/CountryRepositoryV2.ts new file mode 100644 index 00000000..9e67d746 --- /dev/null +++ b/server/infra/database/CountryRepositoryV2.ts @@ -0,0 +1,61 @@ +import Country from 'interfaces/Country'; +import HttpError from 'utils/HttpError'; +import BaseRepository from './BaseRepository'; +import Session from './Session'; + +type Filter = Partial<{ lat: number; lon: number }>; + +export default class CountryRepositoryV2 extends BaseRepository { + constructor(session: Session) { + super('region', session); + } + + async getById(id: string | number) { + const object = await this.session + .getDB() + .select( + this.session.getDB().raw(` + id, + name, + St_asgeojson(centroid) as centroid + `), + ) + .table(this.tableName) + .where('id', id) + .first(); + if (!object) { + throw new HttpError(404, `Can not found ${this.tableName} by id:${id}`); + } + return object; + } + + async getByFilter( + filter: Filter, + // options?: { limit?: number | undefined } | undefined, + ): Promise { + const { lat, lon } = filter; + const sql = ` + WITH country_id AS ( + select id from region_type where type = 'country' + ) + SELECT + id, + name, + St_asgeojson(centroid) as centroid + FROM + region + WHERE + ST_Contains(geom, ST_GeomFromText('POINT(${lon} ${lat})', 4326)) = true + AND + type_id in (select id from country_id); + `; + const object = await this.session.getDB().raw(sql); + if (!object || object.rows.length <= 0) { + throw new HttpError( + 404, + `Can not found ${this.tableName} by lat:${lat} lon:${lon}`, + ); + } + return object.rows; + } +} diff --git a/server/models/CountryV2.ts b/server/models/CountryV2.ts new file mode 100644 index 00000000..1cf03570 --- /dev/null +++ b/server/models/CountryV2.ts @@ -0,0 +1,20 @@ +import CountryRepositoryV2 from 'infra/database/CountryRepositoryV2'; +import Country from 'interfaces/Country'; +import { delegateRepository } from '../infra/database/delegateRepository'; + +type Filter = Partial<{ lat: number; lon: number }>; + +function getCountries( + countryRepository: CountryRepositoryV2, +): (filter: Filter) => Promise { + return async function (filter: Filter) { + const countries = await countryRepository.getByFilter(filter); + return countries; + }; +} + +export default { + getCountries, + getById: delegateRepository('getById'), + getByFilter: delegateRepository('getByFilter'), +}; diff --git a/server/routers/countriesRouter.ts b/server/routers/countriesRouter.ts index 4cd63613..5c64b2b8 100644 --- a/server/routers/countriesRouter.ts +++ b/server/routers/countriesRouter.ts @@ -1,12 +1,47 @@ import express from 'express'; import Joi from 'joi'; +import CountryRepositoryV2 from 'infra/database/CountryRepositoryV2'; import { handlerWrapper } from './utils'; import CountryRepository from '../infra/database/CountryRepository'; import Session from '../infra/database/Session'; import CountryModel from '../models/Country'; +import CountryModelV2 from '../models/CountryV2'; const router = express.Router(); +router.get( + '/v2/:id', + handlerWrapper(async (req, res) => { + Joi.assert(req.params.id, Joi.number().required()); + const repo = new CountryRepositoryV2(new Session()); + const exe = CountryModelV2.getById(repo); + const result = await exe(req.params.id); + res.send(result); + res.end(); + }), +); + +router.get( + '/v2/', + handlerWrapper(async (req, res) => { + Joi.assert( + req.query, + Joi.object().keys({ + limit: Joi.number().integer().min(1).max(1000), + offset: Joi.number().integer().min(0), + lat: Joi.number(), + lon: Joi.number(), + }), + ); + const repo = new CountryRepositoryV2(new Session()); + const result = await CountryModelV2.getByFilter(repo)(req.query); + res.send({ + countries: result, + }); + res.end(); + }), +); + router.get( '/leaderboard', handlerWrapper(async (req, res) => {