Skip to content

Commit d3429f5

Browse files
authored
Merge pull request #48 from waldemarnt/add-respositories
Refatoração para adicionar camada de Repository
2 parents 1e11978 + e09ac08 commit d3429f5

22 files changed

+304
-138
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"test:functional": "dotenv -e .env -- jest --projects ./test --runInBand",
1414
"test:unit": "dotenv -e .env -- jest",
1515
"style:check": "prettier --check src/**/*.ts test/**/*.ts",
16-
"style:fix": "prettier --write src/**/*.ts' test/**/*.ts"
16+
"style:fix": "prettier --write src/**/*.ts test/**/*.ts"
1717
},
1818
"engines": {
1919
"node": "16"

src/api-schema.json

+22-10
Original file line numberDiff line numberDiff line change
@@ -212,15 +212,15 @@
212212
"name": "orderBy",
213213
"in": "query",
214214
"required": false,
215-
"schema":{
215+
"schema": {
216216
"$ref": "#/components/schemas/orderBy"
217217
}
218218
},
219219
{
220220
"name": "orderField",
221221
"in": "query",
222222
"required": false,
223-
"schema":{
223+
"schema": {
224224
"$ref": "#/components/schemas/orderField"
225225
}
226226
}
@@ -252,7 +252,10 @@
252252
"orderBy": {
253253
"type": "string",
254254
"example": "desc",
255-
"enum": ["asc", "desc"],
255+
"enum": [
256+
"asc",
257+
"desc"
258+
],
256259
"default": "desc"
257260
},
258261
"orderField": {
@@ -288,13 +291,18 @@
288291
"type": "number",
289292
"example": -10.55
290293
},
291-
"id":{
294+
"id": {
292295
"type": "string",
293296
"example": "5e8131eba7768d9e4f06c884"
294297
},
295298
"beachPosition": {
296299
"type": "string",
297-
"enum": ["N", "S", "W", "E"],
300+
"enum": [
301+
"N",
302+
"S",
303+
"W",
304+
"E"
305+
],
298306
"example": "S"
299307
},
300308
"forecastPropertyKey": {
@@ -340,12 +348,17 @@
340348
"$ref": "#/components/schemas/beachPosition"
341349
}
342350
},
343-
"required": ["name", "lat", "lng", "position"]
351+
"required": [
352+
"name",
353+
"lat",
354+
"lng",
355+
"position"
356+
]
344357
},
345358
"TimeForecast": {
346-
"type":"array",
359+
"type": "array",
347360
"items": {
348-
"type":"object",
361+
"type": "object",
349362
"properties": {
350363
"time": {
351364
"type": "string",
@@ -542,5 +555,4 @@
542555
}
543556
}
544557
}
545-
}
546-
558+
}

src/controllers/beaches.ts

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
import { Controller, Post, ClassMiddleware } from '@overnightjs/core';
22
import { Request, Response } from 'express';
3-
import { Beach } from '@src/models/beach';
43
import { authMiddleware } from '@src/middlewares/auth';
54
import { BaseController } from '.';
5+
import { BeachRepository } from '@src/repositories';
66

77
@Controller('beaches')
88
@ClassMiddleware(authMiddleware)
99
export class BeachesController extends BaseController {
10+
constructor(private beachRepository: BeachRepository) {
11+
super();
12+
}
1013
@Post('')
1114
public async create(req: Request, res: Response): Promise<void> {
1215
try {
13-
const beach = new Beach({
16+
const result = await this.beachRepository.create({
1417
...req.body,
1518
...{ userId: req.context?.userId },
1619
});
17-
const result = await beach.save();
1820
res.status(201).send(result);
1921
} catch (error) {
2022
this.sendCreateUpdateErrorResponse(res, error);

src/controllers/forecast.ts

+18-2
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ import {
55
Middleware,
66
} from '@overnightjs/core';
77
import { Request, Response } from 'express';
8-
import { Beach } from '@src/models/beach';
98
import { BeachForecast, Forecast } from '@src/services/forecast';
109
import { authMiddleware } from '@src/middlewares/auth';
1110
import { BaseController } from '.';
1211
import logger from '@src/logger';
1312
import rateLimit from 'express-rate-limit';
1413
import ApiError from '@src/util/errors/api-error';
14+
import { BeachRepository } from '@src/repositories';
1515

1616
const forecast = new Forecast();
1717

@@ -34,6 +34,10 @@ const rateLimiter = rateLimit({
3434
@Controller('forecast')
3535
@ClassMiddleware(authMiddleware)
3636
export class ForecastController extends BaseController {
37+
constructor(private beachRepository: BeachRepository) {
38+
super();
39+
}
40+
3741
@Get('')
3842
@Middleware(rateLimiter)
3943
public async getForecastForgeLoggedUser(
@@ -48,7 +52,19 @@ export class ForecastController extends BaseController {
4852
orderBy?: 'asc' | 'desc';
4953
orderField?: keyof BeachForecast;
5054
} = req.query;
51-
const beaches = await Beach.find({ userId: req.context?.userId });
55+
56+
if (!req.context.userId) {
57+
this.sendErrorResponse(res, {
58+
code: 500,
59+
message: 'Something went wrong',
60+
});
61+
logger.error('Missing userId');
62+
return;
63+
}
64+
65+
const beaches = await this.beachRepository.findAllBeachesForUser(
66+
req.context.userId
67+
);
5268
const forecastData = await forecast.processForecastForBeaches(
5369
beaches,
5470
orderBy,

src/controllers/index.ts

+11-10
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
import { Response } from 'express';
2-
import mongoose from 'mongoose';
3-
import { CUSTOM_VALIDATION } from '@src/models/user';
42
import logger from '@src/logger';
53
import ApiError, { APIError } from '@src/util/errors/api-error';
4+
import {
5+
DatabaseError,
6+
DatabaseUnknownClientError,
7+
DatabaseValidationError,
8+
} from '@src/repositories/repository';
69

710
export abstract class BaseController {
811
protected sendCreateUpdateErrorResponse(res: Response, error: unknown): void {
9-
if (error instanceof mongoose.Error.ValidationError) {
12+
if (
13+
error instanceof DatabaseValidationError ||
14+
error instanceof DatabaseUnknownClientError
15+
) {
1016
const clientErrors = this.handleClientErrors(error);
1117
res.status(clientErrors.code).send(
1218
ApiError.format({
@@ -22,16 +28,11 @@ export abstract class BaseController {
2228
}
2329
}
2430

25-
private handleClientErrors(error: mongoose.Error.ValidationError): {
31+
private handleClientErrors(error: DatabaseError): {
2632
code: number;
2733
error: string;
2834
} {
29-
const duplicatedKindErrors = Object.values(error.errors).filter(
30-
(err) =>
31-
err.name === 'ValidatorError' &&
32-
err.kind === CUSTOM_VALIDATION.DUPLICATED
33-
);
34-
if (duplicatedKindErrors.length) {
35+
if (error instanceof DatabaseValidationError) {
3536
return { code: 409, error: error.message };
3637
}
3738
return { code: 400, error: error.message };

src/controllers/users.ts

+15-6
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import { Controller, Post, Get, Middleware } from '@overnightjs/core';
22
import { Response, Request } from 'express';
3-
import { User } from '@src/models/user';
43
import AuthService from '@src/services/auth';
54
import { BaseController } from './index';
65
import { authMiddleware } from '@src/middlewares/auth';
6+
import { UserRepository } from '@src/repositories';
77

88
@Controller('users')
99
export class UsersController extends BaseController {
10+
constructor(private userRepository: UserRepository) {
11+
super();
12+
}
13+
1014
@Post('')
1115
public async create(req: Request, res: Response): Promise<void> {
1216
try {
13-
const user = new User(req.body);
14-
const newUser = await user.save();
17+
const newUser = await this.userRepository.create(req.body);
1518
res.status(201).send(newUser);
1619
} catch (error) {
1720
this.sendCreateUpdateErrorResponse(res, error);
@@ -20,7 +23,7 @@ export class UsersController extends BaseController {
2023

2124
@Post('authenticate')
2225
public async authenticate(req: Request, res: Response): Promise<Response> {
23-
const user = await User.findOne({ email: req.body.email });
26+
const user = await this.userRepository.findOneByEmail(req.body.email);
2427
if (!user) {
2528
return this.sendErrorResponse(res, {
2629
code: 401,
@@ -38,14 +41,20 @@ export class UsersController extends BaseController {
3841
}
3942
const token = AuthService.generateToken(user.id);
4043

41-
return res.send({ ...user.toJSON(), ...{ token } });
44+
return res.send({ ...user, ...{ token } });
4245
}
4346

4447
@Get('me')
4548
@Middleware(authMiddleware)
4649
public async me(req: Request, res: Response): Promise<Response> {
4750
const userId = req.context?.userId;
48-
const user = await User.findOne({ _id: userId });
51+
if (!userId) {
52+
return this.sendErrorResponse(res, {
53+
code: 404,
54+
message: 'user id not provided',
55+
});
56+
}
57+
const user = await this.userRepository.findOneById(userId);
4958
if (!user) {
5059
return this.sendErrorResponse(res, {
5160
code: 404,

src/models/beach.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import mongoose, { Document, Model, Schema } from 'mongoose';
1+
import mongoose, { Schema } from 'mongoose';
2+
import { BaseModel } from '.';
23

34
export enum GeoPosition {
45
S = 'S',
@@ -7,15 +8,18 @@ export enum GeoPosition {
78
N = 'N',
89
}
910

10-
export interface Beach {
11-
_id?: string;
11+
export interface Beach extends BaseModel {
1212
name: string;
1313
position: GeoPosition;
1414
lat: number;
1515
lng: number;
1616
userId: string;
1717
}
1818

19+
export interface ExistingBeach extends Beach {
20+
id: string;
21+
}
22+
1923
const schema = new mongoose.Schema(
2024
{
2125
lat: { type: Number, required: true },
@@ -27,13 +31,12 @@ const schema = new mongoose.Schema(
2731
{
2832
toJSON: {
2933
transform: (_, ret): void => {
30-
ret.id = ret._id;
34+
ret.id = ret._id.toString();
3135
delete ret._id;
3236
delete ret.__v;
3337
},
3438
},
3539
}
3640
);
3741

38-
interface BeachModel extends Omit<Beach, '_id'>, Document {}
39-
export const Beach: Model<BeachModel> = mongoose.model('Beach', schema);
42+
export const Beach = mongoose.model<Beach>('Beach', schema);

src/models/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
//eslint-disable-next-line
2+
export interface BaseModel {}

src/models/user.ts

+10-9
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
1-
import mongoose, { Document, Model } from 'mongoose';
1+
import mongoose, { Document } from 'mongoose';
22
import AuthService from '@src/services/auth';
33
import logger from '@src/logger';
4+
import { BaseModel } from '.';
45

5-
export interface User {
6-
_id?: string;
6+
export interface User extends BaseModel {
77
name: string;
88
email: string;
99
password: string;
1010
}
1111

12+
export interface ExistingUser extends User {
13+
id: string;
14+
}
15+
1216
export enum CUSTOM_VALIDATION {
1317
DUPLICATED = 'DUPLICATED',
1418
}
1519

16-
interface UserModel extends Omit<User, '_id'>, Document {}
17-
1820
const schema = new mongoose.Schema(
1921
{
2022
name: { type: String, required: true },
@@ -27,7 +29,7 @@ const schema = new mongoose.Schema(
2729
{
2830
toJSON: {
2931
transform: (_, ret): void => {
30-
ret.id = ret._id;
32+
ret.id = ret._id.toString();
3133
delete ret._id;
3234
delete ret.__v;
3335
},
@@ -47,7 +49,7 @@ schema.path('email').validate(
4749
CUSTOM_VALIDATION.DUPLICATED
4850
);
4951

50-
schema.pre<UserModel>('save', async function (): Promise<void> {
52+
schema.pre<User & Document>('save', async function (): Promise<void> {
5153
if (!this.password || !this.isModified('password')) {
5254
return;
5355
}
@@ -58,5 +60,4 @@ schema.pre<UserModel>('save', async function (): Promise<void> {
5860
logger.error(`Error hashing the password for the user ${this.name}`, err);
5961
}
6062
});
61-
62-
export const User: Model<UserModel> = mongoose.model('User', schema);
63+
export const User = mongoose.model<User>('User', schema);
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { DefaultMongoDBRepository } from './defaultMongoDBRepository';
2+
import { Beach } from '@src/models/beach';
3+
import { BeachRepository } from '.';
4+
5+
export class BeachMongoDBRepository
6+
extends DefaultMongoDBRepository<Beach>
7+
implements BeachRepository
8+
{
9+
constructor(beachModel = Beach) {
10+
super(beachModel);
11+
}
12+
13+
async findAllBeachesForUser(userId: string) {
14+
return await this.find({ userId });
15+
}
16+
}

0 commit comments

Comments
 (0)