Fixed Docker and tests. Replaced ioredis.

This commit is contained in:
Hackntosh 2023-08-08 19:52:28 -03:00
parent 14e3990d76
commit 4e96576f5d
33 changed files with 1183 additions and 616 deletions

View file

@ -25,7 +25,6 @@ SERVER_PORT=<placeholder>
JWT_ACCESS_SECRET=<placeholder> JWT_ACCESS_SECRET=<placeholder>
# Localstack - The data can be fake (Change this to real data on production) # Localstack - The data can be fake (Change this to real data on production)
DOCKER_HOST=unix:///var/run/docker.sock
SERVICES=s3 SERVICES=s3
AWS_ACCESS_KEY_ID=<placeholder> AWS_ACCESS_KEY_ID=<placeholder>
AWS_SECRET_ACCESS_KEY=<placeholder> AWS_SECRET_ACCESS_KEY=<placeholder>

3
.gitignore vendored
View file

@ -8,4 +8,5 @@ client
dist dist
pnpm-lock.yaml pnpm-lock.yaml
package_backup.json package_backup.json
logs/ logs/
docker.env

11
.swcrc
View file

@ -7,7 +7,16 @@
"dynamicImport": true "dynamicImport": true
}, },
"target": "es2020", "target": "es2020",
"baseUrl": "./" "baseUrl": "./src",
"paths": {
"clients/*": ["clients/*"],
"config/*": ["config/*"],
"controllers/*": ["controllers/*"],
"interfaces/*": ["interfaces/*"],
"helpers/*": ["helpers/*"],
"middlewares/*": ["middlewares/*"],
"services/*": ["services/*"]
}
}, },
"exclude": ["@types/", "interfaces/"], "exclude": ["@types/", "interfaces/"],
"module": { "module": {

View file

@ -23,11 +23,12 @@ RUN npm i pm2 -g
COPY --from=builder /app/package*.json ./ COPY --from=builder /app/package*.json ./
COPY --from=builder /app/prisma ./prisma/ COPY --from=builder /app/prisma ./prisma/
COPY --from=builder /app/dist ./dist/ COPY --from=builder /app/dist ./dist/
COPY --from=builder /app/docker.env ./
RUN mv docker.env .env
RUN npm ci RUN npm ci
RUN npm run prisma:deploy
EXPOSE 8080 EXPOSE 8080
CMD ["npm", "run", "prod:start"] CMD ["npm", "run", "prod:start"]

View file

@ -1,5 +1,10 @@
version: '3.8' version: '3.8'
networks:
localstack-net:
name: localstack-net
driver: bridge
services: services:
postgres: postgres:
image: postgres:alpine image: postgres:alpine
@ -8,7 +13,7 @@ services:
ports: ports:
- 5432:5432 - 5432:5432
env_file: env_file:
- .env - docker.env
volumes: volumes:
- postgres:/var/lib/postgresql/data - postgres:/var/lib/postgresql/data
@ -22,6 +27,24 @@ services:
volumes: volumes:
- redis:/data - redis:/data
localstack:
image: localstack/localstack
container_name: localstack_main
restart: unless-stopped
networks:
- localstack-net
ports:
- 4566:4566
- 4572:4572
env_file:
- docker.env
volumes:
- localstack:/data
- '/var/run/docker.sock:/var/run/docker.sock'
volumes: volumes:
postgres: postgres:
name: backend-db name: backend-db
redis:
driver: local
localstack:

View file

@ -19,7 +19,7 @@ services:
- redis - redis
- localstack - localstack
env_file: env_file:
- .env - docker.env
postgres: postgres:
image: postgres:alpine image: postgres:alpine
@ -28,7 +28,7 @@ services:
ports: ports:
- 5432:5432 - 5432:5432
env_file: env_file:
- .env - docker.env
volumes: volumes:
- postgres:/var/lib/postgresql/data - postgres:/var/lib/postgresql/data
@ -42,7 +42,7 @@ services:
- 4566:4566 - 4566:4566
- 4572:4572 - 4572:4572
env_file: env_file:
- .env - docker.env
volumes: volumes:
- localstack:/data - localstack:/data
- '/var/run/docker.sock:/var/run/docker.sock' - '/var/run/docker.sock:/var/run/docker.sock'

30
docker.env.example Normal file
View file

@ -0,0 +1,30 @@
# Environment
NODE_ENV=production
# Postgres config
POSTGRES_DB=<placeholder>
POSTGRES_USER=<placeholder>
POSTGRES_PASSWORD=<placeholder>
DB_HOST=postgres
DATABASE_URL=postgresql://<placeholder>:<placeholder>@${DB_HOST}:<placeholder>/${POSTGRES_DB}?schema=<placeholder>
# Redis
REDIS_HOST=redis
REDIS_PORT=<placeholder>
REDIS_PASSWORD=<placeholder>
# Express
SERVER_PORT=<placeholder>
# Security
JWT_ACCESS_SECRET=<placeholder>
# Localstack - The data can be fake
SERVICES=s3
AWS_ACCESS_KEY_ID=<placeholder>
AWS_SECRET_ACCESS_KEY=<placeholder>
AWS_DEFAULT_REGION=us-east-1
AWS_DEFAULT_OUTPUT=json
AWS_BUCKET=<placeholder>

View file

@ -1,9 +0,0 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
setupFilesAfterEnv: ['<rootDir>/setupTest.ts'],
transform: {
'^.+\\.(t|j)sx?$': '@swc/jest'
}
}

17
jest.config.ts Normal file
View file

@ -0,0 +1,17 @@
import { pathsToModuleNameMapper } from 'ts-jest'
import { compilerOptions } from './tsconfig.json'
import type { JestConfigWithTsJest } from 'ts-jest'
const jestConfig: JestConfigWithTsJest = {
preset: 'ts-jest',
testEnvironment: 'node',
setupFilesAfterEnv: ['<rootDir>/setupTest.ts'],
transform: {
'^.+\\.(t|j)sx?$': '@swc/jest'
},
roots: ['<rootDir>'],
modulePaths: [compilerOptions.baseUrl],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths)
}
export default jestConfig

1569
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@
"scripts": { "scripts": {
"build": "swc src -d dist", "build": "swc src -d dist",
"dev:start": "ts-node-dev -r tsconfig-paths/register --transpile-only --respawn src/server.ts", "dev:start": "ts-node-dev -r tsconfig-paths/register --transpile-only --respawn src/server.ts",
"docker": "docker compose up -d", "docker": "docker compose --env-file docker.env up -d",
"docker:build": "docker build -t api . && docker compose up -d", "docker:build": "docker build -t api . && docker compose up -d",
"docker:db": "docker compose -f docker-compose.db.yml up -d", "docker:db": "docker compose -f docker-compose.db.yml up -d",
"docker:seed": "docker exec -it api npm run prisma:seed", "docker:seed": "docker exec -it api npm run prisma:seed",
@ -16,8 +16,7 @@
"prisma:generate": "npx prisma generate", "prisma:generate": "npx prisma generate",
"prisma:seed": "prisma db seed", "prisma:seed": "prisma db seed",
"prisma:studio": "npx prisma studio", "prisma:studio": "npx prisma studio",
"prisma:deploy": "npx prisma migrate deploy", "prod:start": "npx prisma migrate deploy && pm2-runtime start dist/server.js",
"prod:start": "pm2-runtime start dist/server.js",
"test": "jest" "test": "jest"
}, },
"ts-standard": { "ts-standard": {
@ -54,7 +53,7 @@
"eslint-plugin-import": "^2.28.0", "eslint-plugin-import": "^2.28.0",
"eslint-plugin-n": "^16.0.1", "eslint-plugin-n": "^16.0.1",
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-promise": "^6.1.1",
"jest": "^29.5.0", "jest": "^29.6.2",
"nodemon": "^3.0.1", "nodemon": "^3.0.1",
"pm2": "^4.2.3", "pm2": "^4.2.3",
"prettier": "^3.0.0", "prettier": "^3.0.0",
@ -80,9 +79,10 @@
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"multer-s3": "^3.0.1", "multer-s3": "^3.0.1",
"rate-limit-redis": "^3.0.2", "rate-limit-redis": "^3.0.2",
"redis": "^4.6.7",
"sharp": "^0.32.3", "sharp": "^0.32.3",
"socket.io": "^4.7.2", "socket.io": "^4.7.2",
"validator": "^13.9.0", "validator": "^13.9.0",
"winston": "^3.10.0" "winston": "^3.10.0"
} }
} }

View file

@ -1,7 +1,9 @@
import prisma from './src/clients/prisma-client' import prisma from './src/clients/prisma-client'
import redis from './src/clients/redis-client' import redis from './src/clients/redis-client'
process.env.NODE_ENV = 'development'
afterAll(async () => { afterAll(async () => {
redis.disconnect() await redis.disconnect()
await prisma.$disconnect() await prisma.$disconnect()
}) })

View file

@ -9,6 +9,8 @@ import router from './routes'
const app = express() const app = express()
// TODO: test socket io, emit notifications when create one.
app.use(express.json()) app.use(express.json())
app.use(express.urlencoded({ extended: true })) app.use(express.urlencoded({ extended: true }))
app.use(morganMiddleware) app.use(morganMiddleware)
@ -23,7 +25,7 @@ app.use(cors({
app.use((_req, res) => { app.use((_req, res) => {
res.status(404).json({ res.status(404).json({
error: 'Not found' error: 'Endpoint not found'
}) })
}) })

View file

@ -1,11 +1,24 @@
import RedisClient from 'ioredis' import logger from 'helpers/logger'
import { createClient, type RedisClientOptions } from 'redis'
const redisPort = parseInt(process.env.REDIS_PORT ?? '6379', 10)
const redisHost = process.env.REDIS_HOST ?? '127.0.0.1'
const redisPassword = process.env.REDIS_PASSWORD ?? '' const redisPassword = process.env.REDIS_PASSWORD ?? ''
const redisHost = process.env.REDIS_HOST ?? ''
const redisPort = process.env.REDIS_PORT ?? ''
const redis = new RedisClient( const redisConfig: RedisClientOptions = {
`redis://:${redisPassword}@${redisHost}:${redisPort}/0` url: `redis://:${redisPassword}@${redisHost}:${redisPort}/0`
) }
const redis = createClient(redisConfig)
redis.connect().then(() => {
logger.info('Successfully connected to Redis')
}).catch((e: Error) => {
logger.error(`Error while connecting to Redis: ${e.message}`)
})
redis.on('error', async (e: Error) => {
logger.error(`Error in Redis client: ${e.message}`)
})
export default redis export default redis

View file

@ -10,6 +10,6 @@ const comments = {
fetch: commentFetchController, fetch: commentFetchController,
fetchLikes: commentFetchLikesController, fetchLikes: commentFetchLikesController,
update: commentUpdateController update: commentUpdateController
} } as const
export default comments export default comments

View file

@ -10,6 +10,6 @@ const post = {
fetch: postFetchInfoController, fetch: postFetchInfoController,
fetchLikes: postFetchLikesController, fetchLikes: postFetchLikesController,
update: postUpdateController update: postUpdateController
} } as const
export default post export default post

View file

@ -22,6 +22,6 @@ const user = {
signup: userSignupController, signup: userSignupController,
update: userUpdateController, update: userUpdateController,
uploadPicture: userUploadPictureController uploadPicture: userUploadPictureController
} } as const
export default user export default user

View file

@ -20,8 +20,7 @@ const limiter = rateLimit({
// Store configuration // Store configuration
store: new RedisStore({ store: new RedisStore({
// @ts-expect-error - `call` function is not present in @types/ioredis sendCommand: async (...args: string[]) => await redis.sendCommand(args)
sendCommand: async (...args: string[]) => await redis.call(...args)
}) })
}) })

View file

@ -1,7 +1,7 @@
import app from './app' import app from './app'
import { createServer } from 'http' import { createServer } from 'http'
import logger from 'helpers/logger' import logger from 'helpers/logger'
import createSocketIOInstance from 'socket' import createSocketIOInstance from './socket'
const server = createServer(app) const server = createServer(app)
const io = createSocketIOInstance(server) const io = createSocketIOInstance(server)

View file

@ -10,6 +10,6 @@ const comment = {
fetch: commentFetchService, fetch: commentFetchService,
fetchLikes: commentFetchLikesService, fetchLikes: commentFetchLikesService,
update: commentUpdateService update: commentUpdateService
} } as const
export default comment export default comment

View file

@ -10,6 +10,6 @@ const post = {
fetch: postFetchInfoService, fetch: postFetchInfoService,
fetchLikes: postFetchLikesService, fetchLikes: postFetchLikesService,
update: postUpdateService update: postUpdateService
} } as const
export default post export default post

View file

@ -22,6 +22,6 @@ const user = {
signup: userSignupService, signup: userSignupService,
update: userUpdateService, update: userUpdateService,
uploadPicture: userUploadPictureService uploadPicture: userUploadPictureService
} } as const
export default user export default user

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

View file

@ -2,7 +2,7 @@ import app from '../../app'
import request from 'supertest' import request from 'supertest'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import deleteUser from '../utils/delete-user' import deleteUser from '../utils/delete-user'
import type User from '../../interfaces/user' import type User from 'interfaces/user'
let user: User let user: User

View file

@ -2,7 +2,7 @@ import app from '../../app'
import request from 'supertest' import request from 'supertest'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import deleteUser from '../utils/delete-user' import deleteUser from '../utils/delete-user'
import type User from '../../interfaces/user' import type User from 'interfaces/user'
let user: User let user: User

View file

@ -2,7 +2,7 @@ import app from '../../app'
import request from 'supertest' import request from 'supertest'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import deleteUser from '../utils/delete-user' import deleteUser from '../utils/delete-user'
import type User from '../../interfaces/user' import type User from 'interfaces/user'
let postId: string let postId: string

View file

@ -2,7 +2,7 @@ import app from '../../app'
import request from 'supertest' import request from 'supertest'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import deleteUser from '../utils/delete-user' import deleteUser from '../utils/delete-user'
import type User from '../../interfaces/user' import type User from 'interfaces/user'
let user: User let user: User

View file

@ -2,7 +2,7 @@ import request from 'supertest'
import app from '../../app' import app from '../../app'
import deleteUser from '../utils/delete-user' import deleteUser from '../utils/delete-user'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import type User from '../../interfaces/user' import type User from 'interfaces/user'
let user: User let user: User
@ -22,7 +22,7 @@ describe('POST /user/auth', () => {
.expect(400) .expect(400)
expect(response.body).toHaveProperty('error') expect(response.body).toHaveProperty('error')
expect(response.body.error).toBe('User does not exists') expect(response.body.error).toBe('Invalid email or password')
}) })
it('should respond with a error if receive an invalid email or password', async () => { it('should respond with a error if receive an invalid email or password', async () => {

View file

@ -1,7 +1,7 @@
import app from '../../app' import app from '../../app'
import request from 'supertest' import request from 'supertest'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import type User from '../../interfaces/user' import type User from 'interfaces/user'
let user: User let user: User

View file

@ -2,7 +2,7 @@ import app from '../../app'
import request from 'supertest' import request from 'supertest'
import deleteUser from '../utils/delete-user' import deleteUser from '../utils/delete-user'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import type User from '../../interfaces/user' import type User from 'interfaces/user'
let user: User let user: User

View file

@ -2,7 +2,7 @@ import request from 'supertest'
import app from '../../app' import app from '../../app'
import deleteUser from '../utils/delete-user' import deleteUser from '../utils/delete-user'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import type User from '../../interfaces/user' import type User from 'interfaces/user'
let user: User let user: User

View file

@ -2,7 +2,7 @@ import request from 'supertest'
import app from '../../app' import app from '../../app'
import signUpNewUser from '../utils/create-user' import signUpNewUser from '../utils/create-user'
import deleteUser from '../utils/delete-user' import deleteUser from '../utils/delete-user'
import type User from '../../interfaces/user' import type User from 'interfaces/user'
let user: User let user: User

View file

@ -1,46 +1,25 @@
{ {
"compilerOptions": { "compilerOptions": {
/* Language and Environment */ "target": "ESNext",
"target": "es2016",
/* Modules */
"module": "commonjs", "module": "commonjs",
"rootDir": "./", "rootDir": "./",
"baseUrl": "./src", "baseUrl": "./src",
"paths": { "paths": {
"clients/*": [ "clients/*": ["clients/*"],
"clients/*" "config/*": ["config/*"],
], "controllers/*": ["controllers/*"],
"config/*": [ "interfaces/*": ["interfaces/*"],
"config/*" "helpers/*": ["helpers/*"],
], "middlewares/*": ["middlewares/*"],
"controllers/*": [ "services/*": ["services/*"]
"controllers/*"
],
"interfaces/*": [
"interfaces/*"
],
"lib/*": [
"lib/*"
],
"middlewares/*": [
"middlewares/*"
],
"services/*": [
"services/*"
],
}, },
"typeRoots": [ "typeRoots": ["./src/@types", "./node_modules/@types"],
"./src/@types",
"./node_modules/@types"
],
/* Emit */
"outDir": "./dist", "outDir": "./dist",
"removeComments": true, "removeComments": true,
/* Interop Constraints */
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
/* Type Checking */
"strict": true, "strict": true,
"skipLibCheck": true "skipLibCheck": true,
"resolveJsonModule": true
} }
} }