Skip to content

Commit

Permalink
feat: add devcontainer configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
war1oc committed Jun 13, 2024
1 parent d8a6056 commit 4890ef5
Show file tree
Hide file tree
Showing 26 changed files with 264 additions and 80 deletions.
50 changes: 50 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/docker-existing-docker-compose
{
"name": "Existing Docker Compose (Extend)",
// Update the 'dockerComposeFile' list if you have more compose files or use different names.
// The .devcontainer/docker-compose.yml file contains any overrides you need/want to make.
"dockerComposeFile": [
"../docker-compose.yml",
"docker-compose.yml"
],
// The 'service' property is the name of the service for the container that VS Code should
// use. Update this value and .devcontainer/docker-compose.yml to the real service name.
"service": "app",
// The optional 'workspaceFolder' property is the path VS Code should open by default when
// connected. This is typically a file mount in .devcontainer/docker-compose.yml
"workspaceFolder": "/usr/src/app",
"postCreateCommand": "npm install",
"customizations": {
"vscode": {
"extensions": [
"esbenp.prettier-vscode",
"editorconfig.editorconfig",
"dbaeumer.vscode-eslint",
"wayou.vscode-todo-highlight",
"mike-co.import-sorter",
"waderyan.gitblame",
"ms-vscode.vscode-typescript-tslint-plugin",
"ms-azuretools.vscode-docker"
]
}
},
"mounts": [
"source=${localWorkspaceFolder},target=/usr/src/app,type=bind",
"source=node_modules,target=/usr/src/app/node_modules,type=volume"
],
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Uncomment the next line if you want start specific services in your Docker Compose config.
// "runServices": [],
// Uncomment the next line if you want to keep your containers running after VS Code shuts down.
// "shutdownAction": "none",
// Uncomment the next line to run commands after the container is created.
// "postCreateCommand": "cat /etc/os-release",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as an existing user other than the container default. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "vscode"
}
29 changes: 29 additions & 0 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
version: '3.8'
services:
# Update this to the name of the service you want to work with in your docker-compose.yml file
app:
# Uncomment if you want to override the service's Dockerfile to one in the .devcontainer
# folder. Note that the path of the Dockerfile and context is relative to the *primary*
# docker-compose.yml file (the first in the devcontainer.json "dockerComposeFile"
# array). The sample below assumes your primary file is in the root of your project.
#
# build:
# context: .
# dockerfile: .devcontainer/Dockerfile

volumes:
# Update this to wherever you want VS Code to mount the folder of your project
- ..:/usr/src/app:cached
- node_modules:/usr/src/app/node_modules

# Uncomment the next four lines if you will use a ptrace-based debugger like C++, Go, and Rust.
# cap_add:
# - SYS_PTRACE
# security_opt:
# - seccomp:unconfined

# Overrides default command so things don't shut down after the process ends.
command: /bin/sh -c "while sleep 1000; do :; done"

volumes:
node_modules:
15 changes: 12 additions & 3 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Basic dependabot.yml file with
# minimum configuration for two package managers
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for more information:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
# https://containers.dev/guide/dependabot

version: 2
updates:
Expand All @@ -9,7 +12,7 @@ updates:
directory: '/'
# Check the npm registry for updates every day (weekdays)
schedule:
interval: 'daily'
interval: 'weekly'

# Enable version updates for Docker
- package-ecosystem: 'docker'
Expand All @@ -18,3 +21,9 @@ updates:
# Check for updates once a week
schedule:
interval: 'weekly'

# Enable version updates for devcontainers
- package-ecosystem: 'devcontainers'
directory: '/'
schedule:
interval: 'weekly'
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,4 @@ dist
local/
documentation
test-report.xml
tsconfig.build.tsbuildinfo
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodejs 20.14.0
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"editor.codeActionsOnSave": {
"source.fixAll.tslint": true
"source.fixAll.tslint": "explicit"
},
"editor.insertSpaces": true,
"editor.tabSize": 2,
Expand Down
38 changes: 18 additions & 20 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
# lts-gallium refers to v16
# Using this instead of node:16 to avoid dependabot updates
FROM node:lts-gallium as builder
# Development Dockerfile
FROM node:20.14.0-bookworm-slim

# Install additional tools for development
RUN apt-get update && apt-get install -y \
vim \
curl \
wget \
git \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /usr/src/app

# Copy and install dependencies
COPY package.json package-lock.json ./
RUN npm ci
RUN npm install

# Copy the rest of the application files
COPY . .

# Set the environment variables
ARG APP_ENV=development
ENV NODE_ENV=${APP_ENV}

RUN npm run build

RUN npm prune

FROM node:lts-gallium

ARG APP_ENV=development
ENV NODE_ENV=${APP_ENV}

WORKDIR /usr/src/app
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY --from=builder /usr/src/app/package*.json ./
COPY --from=builder /usr/src/app/dist ./dist

# Expose the application port
EXPOSE 3000

USER node
CMD [ "npm", "run", "start:prod" ]
# Set the default command to run the application with nodemon
CMD ["npm", "start:dev"]
42 changes: 42 additions & 0 deletions Dockerfile.prod
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Stage 1: Build the application
FROM node:20.14.0-bookworm-slim as builder

WORKDIR /usr/src/app

# Install dependencies
COPY package.json package-lock.json ./
RUN npm ci

# Copy the rest of the application files
COPY . .

# Build the application
ARG APP_ENV=production
ENV NODE_ENV=${APP_ENV}
RUN npm run build

# Prune development dependencies
RUN npm prune --production

# Stage 2: Create the production image
FROM node:20.14.0-bookworm-slim

WORKDIR /usr/src/app

# Copy only the necessary files from the builder stage
COPY --from=builder /usr/src/app/node_modules ./node_modules
COPY --from=builder /usr/src/app/package*.json ./
COPY --from=builder /usr/src/app/dist ./dist

# Set the environment variables
ARG APP_ENV=production
ENV NODE_ENV=${APP_ENV}

# Expose the application port
EXPOSE 3000

# Use a non-root user for security
USER node

# Set the default command to run the application
CMD [ "npm", "run", "start:prod" ]
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
![Build Badge](https://github.com/monstar-lab-oss/nestjs-starter-rest-api/workflows/build/badge.svg)
![Tests Badge](https://github.com/monstar-lab-oss/nestjs-starter-rest-api/workflows/tests/badge.svg)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=monstar-lab-oss_nestjs-starter-rest-api&metric=alert_status)](https://sonarcloud.io/dashboard?id=monstar-lab-oss_nestjs-starter-rest-api)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=monstar-lab-oss_nestjs-starter-rest-api&metric=coverage)](https://sonarcloud.io/dashboard?id=monstar-lab-oss_nestjs-starter-rest-api)
[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=monstar-lab-oss_nestjs-starter-rest-api&metric=code_smells)](https://sonarcloud.io/dashboard?id=monstar-lab-oss_nestjs-starter-rest-api)

This starter kit has the following outline:

- Monolithic Project.
- Monolithic Project
- REST API

This is a Github Template Repository, so it can be easily [used as a starter template](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template) for other repositories.
Expand All @@ -32,6 +29,7 @@ One of our main principals has been to keep the starter kit as lightweight as po
| Request Validation | class-validator | Done |
| Pagination | SQL offset & limit | Done |
| Docker Ready | Dockerfile | Done |
| Devcontainer | - | Done |
| Auto-generated OpenAPI | - | Done |
| Auto-generated ChangeLog | - | WIP |

Expand Down
6 changes: 3 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ services:
ports:
- 3000:3000
volumes:
- ./:/usr/src/app
- node_modules:/usr/src/app/node_modules/
- ./:/usr/src/app:cached
- node_modules:/usr/src/app/node_modules
environment:
APP_ENV: ${APP_ENV}
APP_PORT: ${APP_PORT}
Expand All @@ -29,7 +29,7 @@ services:
- pgsqldb

pgsqldb:
image: postgres:14.3
image: postgres:16.3
environment:
POSTGRES_USER: "${DB_USER}"
POSTGRES_PASSWORD: "${DB_PASS}"
Expand Down
2 changes: 1 addition & 1 deletion ormconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ dotenv.config();
const typeOrmConfig = new DataSource({
type: 'postgres',
host: process.env.DB_HOST,
port: process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : null,
port: process.env.DB_PORT ? parseInt(process.env.DB_PORT, 10) : undefined,
database: process.env.DB_NAME,
username: process.env.DB_USER,
password: process.env.DB_PASS,
Expand Down
10 changes: 5 additions & 5 deletions src/article/services/article.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class ArticleService {

const article = plainToClass(Article, input);

const actor: Actor = ctx.user;
const actor: Actor = ctx.user!;

const user = await this.userService.getUserById(ctx, actor.id);

Expand Down Expand Up @@ -63,7 +63,7 @@ export class ArticleService {
): Promise<{ articles: ArticleOutput[]; count: number }> {
this.logger.log(ctx, `${this.getArticles.name} was called`);

const actor: Actor = ctx.user;
const actor: Actor = ctx.user!;

const isAllowed = this.aclService.forActor(actor).canDoAction(Action.List);
if (!isAllowed) {
Expand All @@ -90,7 +90,7 @@ export class ArticleService {
): Promise<ArticleOutput> {
this.logger.log(ctx, `${this.getArticleById.name} was called`);

const actor: Actor = ctx.user;
const actor: Actor = ctx.user!;

this.logger.log(ctx, `calling ${ArticleRepository.name}.getById`);
const article = await this.repository.getById(id);
Expand All @@ -117,7 +117,7 @@ export class ArticleService {
this.logger.log(ctx, `calling ${ArticleRepository.name}.getById`);
const article = await this.repository.getById(articleId);

const actor: Actor = ctx.user;
const actor: Actor = ctx.user!;

const isAllowed = this.aclService
.forActor(actor)
Expand Down Expand Up @@ -145,7 +145,7 @@ export class ArticleService {
this.logger.log(ctx, `calling ${ArticleRepository.name}.getById`);
const article = await this.repository.getById(id);

const actor: Actor = ctx.user;
const actor: Actor = ctx.user!;

const isAllowed = this.aclService
.forActor(actor)
Expand Down
2 changes: 1 addition & 1 deletion src/auth/guards/jwt-auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class JwtAuthGuard extends AuthGuard(STRATEGY_JWT_AUTH) {
}

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
handleRequest(err, user, info) {
handleRequest(err: any, user: any, info: any) {
// You can throw an exception based on either "info" or "err" arguments
if (err || !user) {
throw err || new UnauthorizedException(`${info}`);
Expand Down
2 changes: 1 addition & 1 deletion src/auth/guards/jwt-refresh.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class JwtRefreshGuard extends AuthGuard(STRATEGY_JWT_REFRESH) {
}

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
handleRequest(err, user, info) {
handleRequest(err: any, user: any, info: any) {
// You can throw an exception based on either "info" or "err" arguments
if (err || !user) {
throw err || new UnauthorizedException(`${info}`);
Expand Down
4 changes: 2 additions & 2 deletions src/auth/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export class AuthService {
login(ctx: RequestContext): AuthTokenOutput {
this.logger.log(ctx, `${this.login.name} was called`);

return this.getAuthToken(ctx, ctx.user);
return this.getAuthToken(ctx, ctx.user!);
}

async register(
Expand All @@ -73,7 +73,7 @@ export class AuthService {
async refreshToken(ctx: RequestContext): Promise<AuthTokenOutput> {
this.logger.log(ctx, `${this.refreshToken.name} was called`);

const user = await this.userService.findById(ctx, ctx.user.id);
const user = await this.userService.findById(ctx, ctx.user!.id);
if (!user) {
throw new UnauthorizedException('Invalid user id');
}
Expand Down
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ async function bootstrap() {
const configService = app.get(ConfigService);
const defaultAdminUserPassword = configService.get<string>(
'defaultAdminUserPassword',
);
)!;

const userService = app.get(UserService);

Expand Down
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ValidationPipe } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder,SwaggerModule } from '@nestjs/swagger';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

import { AppModule } from './app.module';
import { VALIDATION_PIPE_OPTIONS } from './shared/constants';
Expand All @@ -28,6 +28,6 @@ async function bootstrap() {

const configService = app.get(ConfigService);
const port = configService.get<number>('port');
await app.listen(port);
await app.listen(port || 3000);
}
bootstrap();
13 changes: 10 additions & 3 deletions src/shared/acl/acl.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,16 @@ export class BaseAclService<Resource> {
aclRule.actions.includes(Action.Manage);

//check for custom `ruleCallback` callback
canDoAction =
hasActionPermission &&
(!aclRule.ruleCallback || aclRule.ruleCallback(resource, actor));
if (!aclRule.ruleCallback) {
canDoAction = hasActionPermission;
} else {
if (!resource) {
throw new Error('Resource is required for ruleCallback');
}

canDoAction =
hasActionPermission && aclRule.ruleCallback(resource, actor);
}
});
});

Expand Down
Loading

0 comments on commit 4890ef5

Please sign in to comment.