Skip to content

Commit

Permalink
chore: docker compose and docker files for each app (xgeekshq#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
nunocaseiro committed Feb 11, 2022
1 parent 40c164f commit 7e79c9b
Show file tree
Hide file tree
Showing 21 changed files with 349 additions and 130 deletions.
2 changes: 1 addition & 1 deletion .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"contributors": [
],
"contributorsPerLine": 7,
"projectName": "oss-template",
"projectName": "divide-and-conquer",
"projectOwner": "xgeekshq",
"repoType": "github",
"repoHost": "https://github.com",
Expand Down
62 changes: 50 additions & 12 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,17 +1,55 @@
# database host value
DB_HOST=localhost

# admin username
#admin db user
DB_ROOT_USER=admin

# admin password
DB_ROOT_PASSWORD=password
#admin db password
DB_ROOT_PASSWORD=123456

#db user
DB_USER=backend

#db password
DB_PASSWORD=dc2021backend

#db name
DB_NAME=dc

#docker database container name
DB_HOST=mongo

#-----------

#db port
DB_PORT=27017

#backend port
BACKEND_PORT=3200

#secret to sign access token in backend
JWT_ACCESS_TOKEN_SECRET=backenddc2021

#access token expiration time in seconds
JWT_ACCESS_TOKEN_EXPIRATION_TIME=900

#secret to sign refresh token in backend
JWT_REFRESH_TOKEN_SECRET=backenddc2021refresh

#refresh token expiration time in days
JWT_REFRESH_TOKEN_EXPIRATION_TIME=1

#backend url exposed to the browser
NEXT_PUBLIC_BACKEND_URL=http://localhost:3200

#backend url in server side
BACKEND_URL=http://backend:3200

#auth url
NEXTAUTH_URL=http://localhost:3000

# user with lower privileges
DB_USER=dcuser
#auth url exposed to the browser
NEXT_PUBLIC_NEXTAUTH_URL=http://localhost:3000

# password for the user
DB_PASSWORD=password
#secret to encode jwt in frontend
SECRET=56e9169e3383d4a73fef9e0b4a3ff4e2

# database name
DB_NAME=dc
#jwt expiration time exposed to the browser
NEXT_PUBLIC_EXPIRATION_TIME=900
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
- [Database](#database)
- [Backend](#backend)
- [Frontend](#frontend)
- [Usage](#usage)
- [🏃 How to Run - with docker](#--how-to-run---with-docker)
- [Usage](#usage)
- [📝 License](#-license)
- [Contributors ✨](#contributors-)

Expand All @@ -37,7 +38,7 @@ Check out our [**Contributing Guide**](.github/CONTRIBUTING.md) for information
## 🏃 How to Run - Dev mode

To run the project you will need the requirements listed below and configure the env files as described in the example.
In the near future all applications will be dockerized.
In the next section [**How to run - with docker**](#--how-to-run---with-docker) you can find instructions on how to run the entire project with docker.

### Requirements

Expand All @@ -46,14 +47,15 @@ In the near future all applications will be dockerized.
3. Env files

### Env files
An .env file must be in the project root folder where the docker compose file is located and the others in each app folder (frontend and backend).
This files are already provisioned as an example (`.env.example`) in the respective folders and you can use and edit them.

The frontend .env file have the parameter named *SECRET* that is required by next-auth on the frontend and to generate it just run the following command `openssl rand -base64 32` in the shell then paste it in the .env file.
An `.env` file must be present in each app folder (frontend and backend) and it's also needed the `.env` in the root folder of the project in order to create the database.
This files are already provisioned as an example (`.env.example`) in the respective folders and you can use and edit them. In order to use the example, please remove the suffix `.example` from the file name.

The frontend `.env` file have the parameter named *SECRET* that is required by next-auth on the frontend and to generate it just run the following command `openssl rand -base64 32` in the shell then paste it in the `.env` file.

### Database

Since the database is the only app that is containerized, to run it step into the project root folder and run `docker-compose up -d`.
Since the database is the only app required to run in dev mode. You need to build it running the follow command `docker-compose up -d mongo` in the project's root folder.
The mongo image is downloaded, built and the database is created with the name that is passed as described in the env file parameter called *DB_NAME*. After the container is built, the init script that's inside the database folder runs in order to create a user to manage and connect to the database from the backend.

### Backend
Expand All @@ -64,7 +66,11 @@ To run this application for the first time run `npm i` inside the backend folder

To run this application for the first time run `npm i` inside the frontend folder. Once you have installed the dependencies, simply run: `npm run dev`

### Usage
## 🏃 How to Run - with docker

In order to run the whole project with docker you need to prepare the `.env.example` file that is present in the root folder of the project and from there run the following command: `docker-compose up -d`

## Usage

The backend will run on `http://localhost:BACKEND_PORT` and the frontend on `http://localhost:3000`. Be aware the frontend root page ("/") is the landing page and has not yet been built so you must manually enter one of the following routes:

Expand Down
2 changes: 2 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/*
dist/*
29 changes: 15 additions & 14 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
# database host value

#docker database container name
DB_HOST=localhost

# user with lower privileges
DB_USER=dcuser
#db user
DB_USER=backend

# password for the user
DB_PASSWORD=password
#db password
DB_PASSWORD=dc2021backend

# database name
#db name
DB_NAME=dc

# database port
#db port
DB_PORT=27017

# backend port
#backend poxrt
BACKEND_PORT=3200

# a strong secret to sign the access tokens
JWT_ACCESS_TOKEN_SECRET=a_strong_secret
#secret to sign access token in backend
JWT_ACCESS_TOKEN_SECRET=backenddc2021

# expiration time of refresh token in seconds
#access token expiration time in seconds
JWT_ACCESS_TOKEN_EXPIRATION_TIME=900

# a strong secret to sign the refresh tokens
JWT_REFRESH_TOKEN_SECRET=another_strong_secret
#secret to sign refresh token in backend
JWT_REFRESH_TOKEN_SECRET=backenddc2021refresh

# expiration time of refresh token in days
#refresh token expiration time in days
JWT_REFRESH_TOKEN_EXPIRATION_TIME=1
30 changes: 30 additions & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
FROM node:17-alpine as builder

ENV NODE_ENV build

WORKDIR /home/node

COPY . /home/node

RUN npm ci \
&& npm run build \
&& npm prune --production

# ---

FROM node:17-alpine

ENV NODE_ENV production

EXPOSE 3200

ENV PORT 3200

USER node
WORKDIR /home/node

COPY --from=builder /home/node/package*.json /home/node/
COPY --from=builder /home/node/node_modules/ /home/node/node_modules/
COPY --from=builder /home/node/dist/ /home/node/dist/

CMD ["node", "dist/src/main.js"]
7 changes: 2 additions & 5 deletions backend/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import LocalStrategy from './strategy/local.strategy';
import JwtStrategy from './strategy/jwt.strategy';
import JwtRefreshTokenStrategy from './strategy/refresh.strategy';
import {
describeJWT,
JWT_ACCESS_TOKEN_SECRET,
JWT_ACCESS_TOKEN_EXPIRATION_TIME,
} from '../constants/jwt';
Expand All @@ -23,11 +22,9 @@ import {
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => ({
secret: configService.get(describeJWT(JWT_ACCESS_TOKEN_SECRET)),
secret: configService.get(JWT_ACCESS_TOKEN_SECRET),
signOptions: {
expiresIn: `${configService.get(
describeJWT(JWT_ACCESS_TOKEN_EXPIRATION_TIME),
)}s`,
expiresIn: `${configService.get(JWT_ACCESS_TOKEN_EXPIRATION_TIME)}s`,
},
}),
}),
Expand Down
30 changes: 13 additions & 17 deletions backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ import {
JWT_ACCESS_TOKEN_SECRET,
JWT_REFRESH_TOKEN_EXPIRATION_TIME,
JWT_REFRESH_TOKEN_SECRET,
describeJWT,
} from '../constants/jwt';
import { INVALID_CREDENTIALS, EMAIL_EXISTS } from '../constants/httpExceptions';
import {
INVALID_CREDENTIALS,
EMAIL_EXISTS,
USER_NOT_FOUND,
} from '../constants/httpExceptions';

@Injectable()
export default class AuthService {
Expand All @@ -25,8 +28,7 @@ export default class AuthService {

public async getAuthenticatedUser(email: string, plainTextPassword: string) {
const user = await this.usersService.getByEmail(email);
if (!user.password)
throw new HttpException(INVALID_CREDENTIALS, HttpStatus.BAD_REQUEST);
if (!user) throw new HttpException(USER_NOT_FOUND, HttpStatus.NOT_FOUND);
await this.verifyPassword(plainTextPassword, user.password);
return user;
}
Expand All @@ -37,7 +39,7 @@ export default class AuthService {
) {
const isPasswordMatching = await compare(plainTextPassword, hashedPassword);
if (!isPasswordMatching) {
throw new HttpException(INVALID_CREDENTIALS, HttpStatus.BAD_REQUEST);
throw new HttpException(INVALID_CREDENTIALS, HttpStatus.UNAUTHORIZED);
}
}

Expand Down Expand Up @@ -71,15 +73,11 @@ export default class AuthService {
public getJwtAccessToken(userId: string) {
const payload: TokenPayload = { userId };
const token = this.jwtService.sign(payload, {
secret: this.configService.get(describeJWT(JWT_ACCESS_TOKEN_SECRET)),
expiresIn: `${this.configService.get(
describeJWT(JWT_ACCESS_TOKEN_EXPIRATION_TIME),
)}s`,
secret: this.configService.get(JWT_ACCESS_TOKEN_SECRET),
expiresIn: `${this.configService.get(JWT_ACCESS_TOKEN_EXPIRATION_TIME)}s`,
});
return {
expiresIn: this.configService.get(
describeJWT(JWT_ACCESS_TOKEN_EXPIRATION_TIME),
),
expiresIn: this.configService.get(JWT_ACCESS_TOKEN_EXPIRATION_TIME),
token,
};
}
Expand All @@ -88,15 +86,13 @@ export default class AuthService {
const payload: TokenPayload = { userId };

const token = this.jwtService.sign(payload, {
secret: this.configService.get(describeJWT(JWT_REFRESH_TOKEN_SECRET)),
secret: this.configService.get(JWT_REFRESH_TOKEN_SECRET),
expiresIn: `${this.configService.get(
describeJWT(JWT_REFRESH_TOKEN_EXPIRATION_TIME),
JWT_REFRESH_TOKEN_EXPIRATION_TIME,
)}d`,
});
return {
expiresIn: this.configService.get(
describeJWT(JWT_REFRESH_TOKEN_EXPIRATION_TIME),
),
expiresIn: this.configService.get(JWT_REFRESH_TOKEN_EXPIRATION_TIME),
token,
};
}
Expand Down
29 changes: 6 additions & 23 deletions backend/src/constants/jwt.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,7 @@
export const JWT_REFRESH_TOKEN_SECRET = Symbol('refresh token secret');
export const JWT_REFRESH_TOKEN_EXPIRATION_TIME = Symbol(
'refresh token expiration time',
);
export const JWT_REFRESH_TOKEN_SECRET = 'JWT_REFRESH_TOKEN_SECRET';
export const JWT_REFRESH_TOKEN_EXPIRATION_TIME =
'JWT_REFRESH_TOKEN_EXPIRATION_TIME';

export const JWT_ACCESS_TOKEN_SECRET = Symbol('access token secret');
export const JWT_ACCESS_TOKEN_EXPIRATION_TIME = Symbol(
'access token expiration time',
);

export function describeJWT(key: symbol): string {
switch (key) {
case JWT_REFRESH_TOKEN_SECRET:
return 'JWT_REFRESH_TOKEN_SECRET';
case JWT_REFRESH_TOKEN_EXPIRATION_TIME:
return 'JWT_REFRESH_TOKEN_EXPIRATION_TIME';
case JWT_ACCESS_TOKEN_SECRET:
return 'JWT_ACCESS_TOKEN_SECRET';
case JWT_ACCESS_TOKEN_EXPIRATION_TIME:
return 'JWT_ACCESS_TOKEN_EXPIRATION_TIME';
default:
return 'ERROR';
}
}
export const JWT_ACCESS_TOKEN_SECRET = 'JWT_ACCESS_TOKEN_SECRET';
export const JWT_ACCESS_TOKEN_EXPIRATION_TIME =
'JWT_ACCESS_TOKEN_EXPIRATION_TIME';
4 changes: 1 addition & 3 deletions backend/src/models/users/tests/users.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,7 @@ describe('UsersService', () => {
findOne.mockReturnValue(undefined);
});
it('should throw an error', async () => {
await expect(
usersService.getByEmail('test@test.com'),
).rejects.toThrow();
expect(await usersService.getByEmail('test@test.com')).toEqual(false);
});
});
});
Expand Down
3 changes: 1 addition & 2 deletions backend/src/models/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import User, { UserDocument } from './schemas/user.schema';
import CreateUserDto from './dto/createUser.dto';
import { compare, encrypt } from '../../utils/bcrypt';
import {
EMAIL_NOT_EXISTS,
USER_NOT_FOUND,
TOKENS_NOT_MATCHING,
DELETE_FAILED,
Expand All @@ -18,7 +17,7 @@ export default class UsersService {
async getByEmail(email: string) {
const user = await this.userModel.findOne({ email });
if (user) return user;
throw new HttpException(EMAIL_NOT_EXISTS, HttpStatus.NOT_FOUND);
return false;
}

async getById(_id: string) {
Expand Down
Loading

0 comments on commit 7e79c9b

Please sign in to comment.