adapté à la CLEAN
This commit is contained in:
parent
f540d03c1d
commit
31671d4ec0
4
api.http
4
api.http
@ -12,5 +12,7 @@ POST {{url}}/locations
|
|||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Bourg-Achard"
|
"name": "Bourg-Achard 2",
|
||||||
|
"lat": 49.443232,
|
||||||
|
"lon": 1.099971
|
||||||
}
|
}
|
||||||
6
jest.config.js
Normal file
6
jest.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
testEnvironment: "node",
|
||||||
|
transform: {
|
||||||
|
"^.+\\.tsx?$": "ts-jest",
|
||||||
|
},
|
||||||
|
};
|
||||||
7181
package-lock.json
generated
7181
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "ts-node src/index.ts",
|
"start": "ts-node src/index.ts",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
@ -13,7 +13,10 @@
|
|||||||
"@types/node-fetch": "^2.6.1",
|
"@types/node-fetch": "^2.6.1",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"ts-node": "^10.5.0",
|
"ts-node": "^10.5.0",
|
||||||
"typescript": "^4.5.5"
|
"typescript": "^4.5.5",
|
||||||
|
"@types/jest": "27.4.1",
|
||||||
|
"jest": "27.5.1",
|
||||||
|
"ts-jest": "27.1.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"better-sqlite3": "^7.5.0",
|
"better-sqlite3": "^7.5.0",
|
||||||
|
|||||||
67
solid/employee.ts
Normal file
67
solid/employee.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
class Employee_ {
|
||||||
|
// Nom, prénom, etc...
|
||||||
|
|
||||||
|
public reportHours() {}
|
||||||
|
public calculatePay() {}
|
||||||
|
public save() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Employee {
|
||||||
|
public constructor(private email: string) {}
|
||||||
|
// Nom, prénom, etc...
|
||||||
|
}
|
||||||
|
|
||||||
|
class PayCalculator {
|
||||||
|
calculatePay(employee: Employee) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HoursReporter {
|
||||||
|
reportHours(employee: Employee) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EmployeeSaver {
|
||||||
|
save(employee: Employee) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EmployeeRepository {
|
||||||
|
save(employee: Employee);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostgresRepository implements EmployeeRepository {
|
||||||
|
save(employee: Employee) {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerEmployee(repository: EmployeeRepository, email: string) {
|
||||||
|
// Validation mot de passe, email, unicité
|
||||||
|
const empl = new Employee(email);
|
||||||
|
|
||||||
|
repository.save(empl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dans mes tests unitaires
|
||||||
|
|
||||||
|
class InMemoryRepository implements EmployeeRepository {
|
||||||
|
constructor(public employees: Employee[] = []) {}
|
||||||
|
|
||||||
|
save(employee: Employee) {
|
||||||
|
this.employees.push(employee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function TestRegisterUser() {
|
||||||
|
const repository = new InMemoryRepository();
|
||||||
|
|
||||||
|
registerEmployee(repository, "un@email.com");
|
||||||
|
|
||||||
|
// assert(repository.employees.length) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function TestRegisterUserDuplicateEmail() {
|
||||||
|
const repository = new InMemoryRepository([new Employee("un@email.com")]);
|
||||||
|
|
||||||
|
registerEmployee(repository, "un@email.com");
|
||||||
|
|
||||||
|
// assert erreur levée car email dupliqué
|
||||||
|
}
|
||||||
@ -28,7 +28,7 @@ class Square implements Shape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AreaCalculator {
|
class AreaCalculator {
|
||||||
constructor(private readonly shapes: Shape[]) {}
|
constructor(protected readonly shapes: Shape[]) {}
|
||||||
|
|
||||||
public sum(): number {
|
public sum(): number {
|
||||||
return this.shapes.reduce((total, shape) => total + shape.area(), 0);
|
return this.shapes.reduce((total, shape) => total + shape.area(), 0);
|
||||||
@ -38,7 +38,7 @@ class AreaCalculator {
|
|||||||
class VolumeCalculator extends AreaCalculator {
|
class VolumeCalculator extends AreaCalculator {
|
||||||
public sum(): number {
|
public sum(): number {
|
||||||
// logique de calcul, on retournerait la somme des volumes
|
// logique de calcul, on retournerait la somme des volumes
|
||||||
return 0;
|
return this.shapes.reduce((total, shape) => total + shape.volume(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
interface ReaderAndWriter extends Writer, Reader {}
|
interface ReaderAndWriter extends Writer, Reader {}
|
||||||
|
|
||||||
|
interface File {
|
||||||
|
write(data: any): void;
|
||||||
|
read(): any;
|
||||||
|
}
|
||||||
|
|
||||||
interface Writer {
|
interface Writer {
|
||||||
write(data: any): void;
|
write(data: any): void;
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/clean/adapters/fastifyPresenter.ts
Normal file
10
src/clean/adapters/fastifyPresenter.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { FastifyReply } from "fastify";
|
||||||
|
import Presenter from "../usecases/presenter";
|
||||||
|
|
||||||
|
export default class FastifyPresenter<T> implements Presenter<T> {
|
||||||
|
constructor(private readonly res: FastifyReply) {}
|
||||||
|
|
||||||
|
show(result: T): void {
|
||||||
|
this.res.status(200).send(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/clean/adapters/inMemoryLocationsRepository.ts
Normal file
14
src/clean/adapters/inMemoryLocationsRepository.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import LocationsRepository from "../entities/locationsRepository";
|
||||||
|
import Location from "../entities/location";
|
||||||
|
|
||||||
|
export class InMemoryLocationRepository implements LocationsRepository {
|
||||||
|
constructor(public locations: Location[] = []) {}
|
||||||
|
|
||||||
|
getAll(): Location[] {
|
||||||
|
return this.locations;
|
||||||
|
}
|
||||||
|
|
||||||
|
save(location: Location): void {
|
||||||
|
this.locations.push(location);
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/clean/adapters/openWeatherService.ts
Normal file
22
src/clean/adapters/openWeatherService.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import fetch from "node-fetch";
|
||||||
|
import WeatherService, { Forecast } from "../entities/weatherService";
|
||||||
|
|
||||||
|
export default class OpenWeatherService implements WeatherService {
|
||||||
|
constructor(
|
||||||
|
private readonly appid: string,
|
||||||
|
private readonly units: string = "metric"
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async getForecastFor(lat: number, lon: number): Promise<Forecast> {
|
||||||
|
const { weather, main } = await fetch(
|
||||||
|
`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${this.appid}&units=${this.units}`
|
||||||
|
).then((r) => r.json());
|
||||||
|
|
||||||
|
return {
|
||||||
|
temperature: main.temp,
|
||||||
|
min: main.temp_min,
|
||||||
|
max: main.temp_max,
|
||||||
|
weather: weather[0].main,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/clean/adapters/sqliteLocationsRepository.ts
Normal file
29
src/clean/adapters/sqliteLocationsRepository.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Database } from "better-sqlite3";
|
||||||
|
import Location from "../entities/location";
|
||||||
|
import LocationsRepository from "../entities/locationsRepository";
|
||||||
|
|
||||||
|
export default class SqliteLocationsRepository implements LocationsRepository {
|
||||||
|
constructor(private readonly db: Database) {}
|
||||||
|
|
||||||
|
getAll(): Location[] {
|
||||||
|
return this.db
|
||||||
|
.prepare("SELECT id, name, lat, lon FROM locations")
|
||||||
|
.all()
|
||||||
|
.map((locationData) => {
|
||||||
|
return Object.setPrototypeOf(locationData, Location);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
save(location: Location): void {
|
||||||
|
this.db
|
||||||
|
.prepare(
|
||||||
|
"INSERT INTO locations (id, name, lat, lon) VALUES (@id, @name, @lat, @lon)"
|
||||||
|
)
|
||||||
|
.run({
|
||||||
|
id: location.id,
|
||||||
|
name: location.name,
|
||||||
|
lat: location.lat,
|
||||||
|
lon: location.lon,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/clean/entities/location.test.ts
Normal file
16
src/clean/entities/location.test.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import Location from "./location";
|
||||||
|
|
||||||
|
describe("entity location", () => {
|
||||||
|
it("can be created", () => {
|
||||||
|
const name = "Bourg-Achard";
|
||||||
|
const lat = 49.443232;
|
||||||
|
const lon = 1.099971;
|
||||||
|
|
||||||
|
const location = new Location(name, lat, lon);
|
||||||
|
|
||||||
|
expect(location.id).not.toBeUndefined();
|
||||||
|
expect(location.name).toEqual(name);
|
||||||
|
expect(location.lat).toEqual(lat);
|
||||||
|
expect(location.lon).toEqual(lon);
|
||||||
|
});
|
||||||
|
});
|
||||||
13
src/clean/entities/location.ts
Normal file
13
src/clean/entities/location.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { v4 as uuid } from "uuid";
|
||||||
|
|
||||||
|
export default class Location {
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
public name: string,
|
||||||
|
public lat: number,
|
||||||
|
public lon: number
|
||||||
|
) {
|
||||||
|
this.id = uuid();
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/clean/entities/locationsRepository.ts
Normal file
6
src/clean/entities/locationsRepository.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import Location from "./location";
|
||||||
|
|
||||||
|
export default interface LocationsRepository {
|
||||||
|
getAll(): Location[];
|
||||||
|
save(location: Location): void; // On pourrait séparer lecture / écriture
|
||||||
|
}
|
||||||
10
src/clean/entities/weatherService.ts
Normal file
10
src/clean/entities/weatherService.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export type Forecast = {
|
||||||
|
weather: string;
|
||||||
|
temperature: number;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default interface WeatherService {
|
||||||
|
getForecastFor(lat: number, lon: number): Promise<Forecast>;
|
||||||
|
}
|
||||||
33
src/clean/usecases/createLocation.test.ts
Normal file
33
src/clean/usecases/createLocation.test.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { InMemoryLocationRepository } from "../adapters/inMemoryLocationsRepository";
|
||||||
|
import Location from "../entities/location";
|
||||||
|
import createLocation, { CreateLocationData } from "./createLocation";
|
||||||
|
import Presenter from "./presenter";
|
||||||
|
|
||||||
|
export class DummyPresenter<T> implements Presenter<T> {
|
||||||
|
constructor(public result?: T) {}
|
||||||
|
|
||||||
|
show(result: T): void {
|
||||||
|
this.result = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("use case createLocation", () => {
|
||||||
|
it("persist a location", () => {
|
||||||
|
const cmd: CreateLocationData = {
|
||||||
|
name: "Bourg-Achard",
|
||||||
|
lat: 49.443232,
|
||||||
|
lon: 1.099971,
|
||||||
|
};
|
||||||
|
const presenter = new DummyPresenter<Location>();
|
||||||
|
const repository = new InMemoryLocationRepository();
|
||||||
|
|
||||||
|
createLocation(repository, presenter, cmd);
|
||||||
|
|
||||||
|
expect(repository.locations).toHaveLength(1);
|
||||||
|
const newLocation = repository.locations[0];
|
||||||
|
|
||||||
|
expect(newLocation.name).toEqual(cmd.name);
|
||||||
|
expect(newLocation.lat).toEqual(cmd.lat);
|
||||||
|
expect(newLocation.lon).toEqual(cmd.lon);
|
||||||
|
});
|
||||||
|
});
|
||||||
72
src/clean/usecases/createLocation.ts
Normal file
72
src/clean/usecases/createLocation.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import Location from "../entities/location";
|
||||||
|
import LocationsRepository from "../entities/locationsRepository";
|
||||||
|
import Presenter from "./presenter";
|
||||||
|
|
||||||
|
// On pourrait l'injecter dans le use case
|
||||||
|
// export interface CreateLocationDataValidator {
|
||||||
|
// validate(cmd: CreateLocationData): void;
|
||||||
|
// }
|
||||||
|
|
||||||
|
export type CreateLocationData = {
|
||||||
|
name: string;
|
||||||
|
lat: number;
|
||||||
|
lon: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// interface UseCase<TCommand> {
|
||||||
|
// execute(cmd: TCommand): void;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// class CreateLocationUseCase implements UseCase<CreateLocationData> {
|
||||||
|
// public constructor(private readonly locationRepository: LocationRepository) {}
|
||||||
|
|
||||||
|
// execute(cmd: CreateLocationData): void {
|
||||||
|
// // FIXME: Valider les entrées utilisateurs
|
||||||
|
// if (typeof cmd !== "object") {
|
||||||
|
// throw new Error("should be an object");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const location = new Location(cmd.name, cmd.lat, cmd.lon);
|
||||||
|
|
||||||
|
// this.locationRepository.save(location);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const db = new Database("monfichier.db")
|
||||||
|
// const sqliteRepository = new SqliteLocationRepository(db);
|
||||||
|
// const uc = new CreateLocationUseCase(sqliteRepository)
|
||||||
|
|
||||||
|
// uc.execute({
|
||||||
|
|
||||||
|
// })
|
||||||
|
|
||||||
|
// createLocation(sqliteRepository, {
|
||||||
|
|
||||||
|
// });
|
||||||
|
|
||||||
|
export default function createLocation(
|
||||||
|
locationRepository: LocationsRepository,
|
||||||
|
presenter: Presenter<Location>,
|
||||||
|
cmd: CreateLocationData
|
||||||
|
) {
|
||||||
|
// FIXME: Valider les entrées utilisateurs
|
||||||
|
if (typeof cmd !== "object") {
|
||||||
|
throw new Error("should be an object");
|
||||||
|
}
|
||||||
|
|
||||||
|
const location = new Location(cmd.name, cmd.lat, cmd.lon);
|
||||||
|
|
||||||
|
locationRepository.save(location);
|
||||||
|
presenter.show(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
// export function createLocation_(
|
||||||
|
// locationRepository: LocationsRepository,
|
||||||
|
// cmd: CreateLocationData
|
||||||
|
// ): Location {
|
||||||
|
// const location = new Location(cmd.name, cmd.lat, cmd.lon);
|
||||||
|
|
||||||
|
// locationRepository.save(location);
|
||||||
|
|
||||||
|
// return location;
|
||||||
|
// }
|
||||||
48
src/clean/usecases/getForecasts.test.ts
Normal file
48
src/clean/usecases/getForecasts.test.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { InMemoryLocationRepository } from "../adapters/inMemoryLocationsRepository";
|
||||||
|
import Location from "../entities/location";
|
||||||
|
import WeatherService, { Forecast } from "../entities/weatherService";
|
||||||
|
import { DummyPresenter } from "./createLocation.test";
|
||||||
|
import getForecasts, { GetForecastsResult } from "./getForecasts";
|
||||||
|
|
||||||
|
class DummyWeatherService implements WeatherService {
|
||||||
|
getForecastFor(lat: number, lon: number): Promise<Forecast> {
|
||||||
|
return Promise.resolve({
|
||||||
|
weather: "Sunny",
|
||||||
|
temperature: 12,
|
||||||
|
min: 10,
|
||||||
|
max: 16,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("use case getForecasts", () => {
|
||||||
|
it("retrieve all locations forecasts", async () => {
|
||||||
|
const repository = new InMemoryLocationRepository([
|
||||||
|
new Location("Rouen", 49.443232, 1.099971),
|
||||||
|
new Location("Bourg-Achard", 49.35322, 0.81623),
|
||||||
|
]);
|
||||||
|
const presenter = new DummyPresenter<GetForecastsResult[]>();
|
||||||
|
const weatherService = new DummyWeatherService();
|
||||||
|
|
||||||
|
await getForecasts(presenter, repository, weatherService);
|
||||||
|
|
||||||
|
expect(presenter.result).not.toBeUndefined();
|
||||||
|
expect(presenter.result).toHaveLength(2);
|
||||||
|
|
||||||
|
let forecast = presenter.result![0];
|
||||||
|
|
||||||
|
expect(forecast.location.name).toEqual("Rouen");
|
||||||
|
expect(forecast.forecast.max).toEqual(16);
|
||||||
|
expect(forecast.forecast.min).toEqual(10);
|
||||||
|
expect(forecast.forecast.temperature).toEqual(12);
|
||||||
|
expect(forecast.forecast.weather).toEqual("Sunny");
|
||||||
|
|
||||||
|
forecast = presenter.result![1];
|
||||||
|
|
||||||
|
expect(forecast.location.name).toEqual("Bourg-Achard");
|
||||||
|
expect(forecast.forecast.max).toEqual(16);
|
||||||
|
expect(forecast.forecast.min).toEqual(10);
|
||||||
|
expect(forecast.forecast.temperature).toEqual(12);
|
||||||
|
expect(forecast.forecast.weather).toEqual("Sunny");
|
||||||
|
});
|
||||||
|
});
|
||||||
32
src/clean/usecases/getForecasts.ts
Normal file
32
src/clean/usecases/getForecasts.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import Location from "../entities/location";
|
||||||
|
import LocationsRepository from "../entities/locationsRepository";
|
||||||
|
import WeatherService, { Forecast } from "../entities/weatherService";
|
||||||
|
import Presenter from "./presenter";
|
||||||
|
|
||||||
|
export type GetForecastsResult = {
|
||||||
|
location: Location;
|
||||||
|
forecast: Forecast;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function getForecasts(
|
||||||
|
presenter: Presenter<GetForecastsResult[]>,
|
||||||
|
repository: LocationsRepository,
|
||||||
|
weatherService: WeatherService
|
||||||
|
) {
|
||||||
|
const locations = repository.getAll();
|
||||||
|
const forecasts = await Promise.all(
|
||||||
|
locations.map(async (location) => {
|
||||||
|
const forecast = await weatherService.getForecastFor(
|
||||||
|
location.lat,
|
||||||
|
location.lon
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
location,
|
||||||
|
forecast,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
presenter.show(forecasts);
|
||||||
|
}
|
||||||
24
src/clean/usecases/locationService.ts
Normal file
24
src/clean/usecases/locationService.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import LocationsRepository from "../entities/locationsRepository";
|
||||||
|
import WeatherService from "../entities/weatherService";
|
||||||
|
import { CreateLocationData } from "./createLocation";
|
||||||
|
|
||||||
|
export default class LocationService {
|
||||||
|
constructor(
|
||||||
|
private readonly repository: LocationsRepository,
|
||||||
|
private readonly weatherService: WeatherService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
createLocation(data: CreateLocationData) {
|
||||||
|
// TODO
|
||||||
|
// const location = new Location()
|
||||||
|
// this.repository.save(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLocations() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
getForecasts() {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/clean/usecases/presenter.ts
Normal file
3
src/clean/usecases/presenter.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default interface Presenter<T> {
|
||||||
|
show(result: T): void;
|
||||||
|
}
|
||||||
19
src/clean/usecases/showLocations.test.ts
Normal file
19
src/clean/usecases/showLocations.test.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { InMemoryLocationRepository } from "../adapters/inMemoryLocationsRepository";
|
||||||
|
import Location from "../entities/location";
|
||||||
|
import { DummyPresenter } from "./createLocation.test";
|
||||||
|
import showLocations from "./showLocations";
|
||||||
|
|
||||||
|
describe("use case showsLocations", () => {
|
||||||
|
it("show every locations", () => {
|
||||||
|
const repository = new InMemoryLocationRepository([
|
||||||
|
new Location("Rouen", 49.443232, 1.099971),
|
||||||
|
new Location("Bourg-Achard", 49.35322, 0.81623),
|
||||||
|
]);
|
||||||
|
const presenter = new DummyPresenter<Location[]>();
|
||||||
|
|
||||||
|
showLocations(repository, presenter);
|
||||||
|
|
||||||
|
expect(presenter.result).not.toBeUndefined();
|
||||||
|
expect(presenter.result).toHaveLength(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
11
src/clean/usecases/showLocations.ts
Normal file
11
src/clean/usecases/showLocations.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import Location from "../entities/location";
|
||||||
|
import LocationsRepository from "../entities/locationsRepository";
|
||||||
|
import Presenter from "./presenter";
|
||||||
|
|
||||||
|
export default function showLocations(
|
||||||
|
repository: LocationsRepository,
|
||||||
|
presenter: Presenter<Location[]>
|
||||||
|
) {
|
||||||
|
const locations = repository.getAll();
|
||||||
|
presenter.show(locations);
|
||||||
|
}
|
||||||
54
src/index.old.ts
Normal file
54
src/index.old.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import fastify from "fastify";
|
||||||
|
import Database from "better-sqlite3";
|
||||||
|
import fetch from "node-fetch";
|
||||||
|
import { v4 as uuid } from "uuid";
|
||||||
|
|
||||||
|
const db = new Database("weathery.db");
|
||||||
|
const APPID = "d6f0f985f37372e9824c68a52662cc23";
|
||||||
|
|
||||||
|
const app = fastify({
|
||||||
|
logger: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/locations", async (req) => {
|
||||||
|
const payload: any = req.body;
|
||||||
|
const location = {
|
||||||
|
id: uuid(),
|
||||||
|
name: payload.name,
|
||||||
|
};
|
||||||
|
|
||||||
|
db.prepare("INSERT INTO locations (id, name) VALUES (@id, @name)").run(
|
||||||
|
location
|
||||||
|
);
|
||||||
|
|
||||||
|
return location;
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/locations", async () => {
|
||||||
|
const locations = db.prepare("SELECT * FROM locations").all();
|
||||||
|
|
||||||
|
return { locations };
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get("/", async () => {
|
||||||
|
const locations = db.prepare("SELECT * FROM locations").all();
|
||||||
|
|
||||||
|
const weathers = await Promise.all(
|
||||||
|
locations.map(async (location) => {
|
||||||
|
const { lat, lon } = location;
|
||||||
|
const { weather, main: properties } = await fetch(
|
||||||
|
`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${APPID}&units=metric`
|
||||||
|
).then((r) => r.json());
|
||||||
|
|
||||||
|
return {
|
||||||
|
...location,
|
||||||
|
weather,
|
||||||
|
properties,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return weathers;
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(8080);
|
||||||
64
src/index.ts
64
src/index.ts
@ -1,7 +1,13 @@
|
|||||||
import fastify from "fastify";
|
import fastify from "fastify";
|
||||||
|
import showLocations from "./clean/usecases/showLocations";
|
||||||
|
import LocationsRepository from "./clean/entities/locationsRepository";
|
||||||
|
import FastifyPresenter from "./clean/adapters/fastifyPresenter";
|
||||||
|
import SqliteLocationsRepository from "./clean/adapters/sqliteLocationsRepository";
|
||||||
import Database from "better-sqlite3";
|
import Database from "better-sqlite3";
|
||||||
import fetch from "node-fetch";
|
import createLocation from "./clean/usecases/createLocation";
|
||||||
import { v4 as uuid } from "uuid";
|
import getForecasts from "./clean/usecases/getForecasts";
|
||||||
|
import OpenWeatherService from "./clean/adapters/openWeatherService";
|
||||||
|
import WeatherService from "./clean/entities/weatherService";
|
||||||
|
|
||||||
const db = new Database("weathery.db");
|
const db = new Database("weathery.db");
|
||||||
const APPID = "d6f0f985f37372e9824c68a52662cc23";
|
const APPID = "d6f0f985f37372e9824c68a52662cc23";
|
||||||
@ -10,45 +16,33 @@ const app = fastify({
|
|||||||
logger: true,
|
logger: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post("/locations", async (req) => {
|
// const locationsRepository = new InMemoryLocationRepository([
|
||||||
const payload: any = req.body;
|
// new Location("Rouen", 49.443232, 1.099971),
|
||||||
const location = {
|
// new Location("Bourg-Achard", 49.35322, 0.81623),
|
||||||
id: uuid(),
|
// ]);
|
||||||
name: payload.name,
|
const locationsRepository: LocationsRepository = new SqliteLocationsRepository(
|
||||||
};
|
db
|
||||||
|
);
|
||||||
|
const weatherService: WeatherService = new OpenWeatherService(APPID);
|
||||||
|
|
||||||
db.prepare("INSERT INTO locations (id, name) VALUES (@id, @name)").run(
|
app.get("/locations", async (_, res) => {
|
||||||
location
|
showLocations(locationsRepository, new FastifyPresenter(res));
|
||||||
);
|
|
||||||
|
|
||||||
return location;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get("/locations", async () => {
|
app.get("/", async (_, res) => {
|
||||||
const locations = db.prepare("SELECT * FROM locations").all();
|
await getForecasts(
|
||||||
|
new FastifyPresenter(res),
|
||||||
return { locations };
|
locationsRepository,
|
||||||
|
weatherService
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get("/", async () => {
|
app.post("/locations", async (req, res) => {
|
||||||
const locations = db.prepare("SELECT * FROM locations").all();
|
createLocation(
|
||||||
|
locationsRepository,
|
||||||
const weathers = await Promise.all(
|
new FastifyPresenter(res),
|
||||||
locations.map(async (location) => {
|
req.body as any
|
||||||
const { lat, lon } = location;
|
|
||||||
const { weather, main: properties } = await fetch(
|
|
||||||
`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${APPID}&units=metric`
|
|
||||||
).then((r) => r.json());
|
|
||||||
|
|
||||||
return {
|
|
||||||
...location,
|
|
||||||
weather,
|
|
||||||
properties,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return weathers;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
app.listen(8080);
|
app.listen(8080);
|
||||||
|
|||||||
BIN
weathery.db
BIN
weathery.db
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user