Skip to content

Commit 7808652

Browse files
Init commit
0 parents  commit 7808652

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1565
-0
lines changed

.docker/Dockerfile

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
FROM denoland/deno:1.15.3
2+
3+
# The port that your application listens to.
4+
EXPOSE 1337
5+
6+
WORKDIR /app
7+
8+
# Prefer not to run as root.
9+
USER deno
10+
11+
# Cache the dependencies as a layer (the following two steps are re-run only when deps.ts is modified).
12+
# Ideally cache deps.ts will download and compile _all_ external files used in main.ts.
13+
COPY import_map.json .
14+
COPY deps_lock.json .
15+
16+
# These steps will be re-run upon each file change in your working directory:
17+
COPY ./src ./src
18+
COPY ./migrations ./migrations
19+
20+
# Compile the main app so that it doesn't need to be compiled each startup/entry.
21+
RUN deno cache --lock=deps_lock.json --import-map=import_map.json ./src/index.ts
22+
23+
CMD ["run", "--allow-net", "--allow-env", "./src/index.ts"]

.env.example

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# -- Server --
2+
PORT=1337
3+
4+
# -- Database --
5+
POSTGRES_HOSTNAME=postgres
6+
POSTGRES_DB=postgres
7+
POSTGRES_PASSWORD=postgres
8+
POSTGRES_USER=postgres
9+
POSTGRES_POOL_SIZE=10
10+
POSTGRES_MIGRATIONS_TABLE_NAME=migrations
11+
12+
# -- LOG LEVEL --
13+
# DEBUG = 0
14+
# INFO = 1
15+
# WARNING = 2
16+
# ERROR = 3
17+
# CRITICAL = 4
18+
LOG_LEVEL=0

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.env

.vscode/settings.json

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"deno.enable": true,
3+
"deno.lint": true,
4+
"deno.unstable": true,
5+
"deno.importMap": "import_map.json",
6+
"deno.suggest.imports.hosts": {
7+
"https://deno.land": false
8+
}
9+
}

README.md

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Deno starter API
2+
3+
- Added PostgreSQL db driver with ORM => https://deno.land/x/[email protected]
4+
- validation to endpoints
5+
- import_maps
6+
- dockerized
7+
8+
9+
## Install
10+
11+
Copy .env.example as .env in root directory.
12+
13+
Run ``./scripts/build-docker-image-local.sh `` to build docker image.
14+
15+
16+
Run ``docker-compose up api`` to run Deno.
17+
18+
19+
Run ``docker-compose up watch`` to run Deno in watch mode.
20+
21+
22+
Tip: For Apple M1 users for Deno watch mode we need to change in Dockerfile:
23+
24+
``FROM denoland/deno:1.15.3``
25+
26+
to this:
27+
28+
``FROM lukechannings/deno:latest``
29+
30+
and to docker-compose ``platform: linux/amd64``:
31+
32+
``
33+
services:
34+
watch:
35+
image: api:local
36+
hostname: api
37+
platform: linux/amd64
38+
volumes:
39+
...
40+
``
41+
42+
43+
## Migrations
44+
45+
Migrations are in `` migrations `` folder.
46+
47+
Syntax for class names is like in TypeORM:
48+
49+
File `` 20220124110458-init.ts `` then class should be named `` Init20220124110458 `` :
50+
File `` 20220130024421-fixes_on_post_users.ts `` then class should be named `` Fixes_on_post_users20220130024421 `` :
51+
52+
Files to migration folder we add manually.
53+
54+
"down" method is not supported yet.
55+
56+
Example migration file:
57+
58+
``
59+
import { Model } from "denodb";
60+
61+
export class Init20220124110458 {
62+
63+
async up(client: any): Promise<void> {
64+
await client.queryObject(`SELECT * FROM "migrations"`);
65+
}
66+
67+
async down(client: any): Promise<void> {
68+
await client.queryObject(`SELECT * FROM "migrations"`);
69+
}
70+
}
71+
``

deps_lock.json

+550
Large diffs are not rendered by default.

docker-compose.yml

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
version: "3.1"
2+
3+
services:
4+
watch:
5+
image: api:local
6+
hostname: api
7+
volumes:
8+
- ./src:/app/src:ro
9+
- ./migrations:/app/migrations:ro
10+
- ./import_map.json:/app/import_map.json:ro
11+
- ./deps_lock.json:/app/deps_lock.json:ro
12+
ports:
13+
- 1337:1337
14+
command: "run --watch -A --importmap import_map.json ./src/index.ts"
15+
env_file:
16+
- .env
17+
depends_on:
18+
- postgres
19+
- adminer # its optional, but good for development
20+
21+
api:
22+
image: api:local
23+
hostname: api
24+
volumes:
25+
- ./src:/app/src:ro
26+
- ./migrations:/app/migrations:ro
27+
- ./import_map.json:/app/import_map.json:ro
28+
- ./deps_lock.json:/app/deps_lock.json:ro
29+
ports:
30+
- 1337:1337
31+
command: "run -A --importmap import_map.json ./src/index.ts"
32+
env_file:
33+
- .env
34+
depends_on:
35+
- postgres
36+
37+
postgres:
38+
image: postgres:14-alpine
39+
hostname: postgres
40+
environment:
41+
- POSTGRES_DB=postgres
42+
- POSTGRES_PASSWORD=postgres
43+
- POSTGRES_USER=postgres
44+
volumes:
45+
- db:/var/lib/postgresql/data
46+
47+
adminer:
48+
hostname: adminer
49+
image: adminer
50+
ports:
51+
- 8080:8080
52+
53+
volumes:
54+
db:

import_map.json

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"imports": {
3+
"oak": "https://deno.land/x/[email protected]/mod.ts",
4+
"ansi_styles": "https://deno.land/x/[email protected]/mod.ts",
5+
"http_status": "https://deno.land/x/[email protected]/mod.ts",
6+
"dex": "https://deno.land/x/[email protected]/mod.ts",
7+
"denodb": "https://deno.land/x/[email protected]/mod.ts"
8+
}
9+
}

migrations/20220124110458-init.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Model } from "denodb";
2+
3+
export class Init20220124110458 {
4+
5+
async up(client: any): Promise<void> {
6+
await client.queryObject(`DROP TABLE IF EXISTS "users";`);
7+
8+
await client.queryObject(`DROP SEQUENCE IF EXISTS users_id_seq;`);
9+
10+
await client.queryObject(`CREATE SEQUENCE users_id_seq INCREMENT 1 MINVALUE 1 MAXVALUE 2147483647 CACHE 1;`);
11+
await client.queryObject(`CREATE TABLE "public"."users" (
12+
"id" integer DEFAULT nextval('users_id_seq') NOT NULL,
13+
"username" character varying(128) NOT NULL,
14+
"email" character varying(128) NOT NULL,
15+
CONSTRAINT "users_email" UNIQUE ("email"),
16+
CONSTRAINT "users_pkey" PRIMARY KEY ("id"),
17+
CONSTRAINT "users_username" UNIQUE ("username")
18+
) WITH (oids = false);`);
19+
}
20+
21+
async down(client: any): Promise<void> {
22+
}
23+
}

scripts.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"$schema": "https://deno.land/x/[email protected]/schema.json",
3+
"importmap": "import_map.json",
4+
"watch": false,
5+
"scripts": {
6+
"docker:build-image": {
7+
"cmd": "bash ./scripts/build-docker-image-local.sh",
8+
"desc": "Build docker images"
9+
},
10+
"docker:watch": {
11+
"cmd": "docker-compose up watch",
12+
"desc": "Start api in watch mode"
13+
},
14+
"docker:start": {
15+
"cmd": "docker-compose up api",
16+
"desc": "Start api"
17+
}
18+
}
19+
}

scripts/build-docker-image-local.sh

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env bash
2+
3+
IMAGE="api"
4+
TAG="local"
5+
6+
# Remove image to reduce dangling images
7+
docker image rm $IMAGE:$TAG
8+
9+
docker build -f $PWD/.docker/Dockerfile -t $IMAGE:$TAG --force-rm .

scripts/generate-adr.sh

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env bash
2+
3+
: '
4+
Copyright © 2021 Kamide.re Laboratory
5+
6+
License: MIT https://mit-license.org
7+
8+
Permission is hereby granted, free of charge, to any
9+
person obtaining a copy of this software and associated
10+
documentation files (the “Software”), to deal in
11+
the Software without restriction, including without limitation
12+
the rights to use, copy, modify, merge, publish, distribute,
13+
sublicense, and/or sell copies of the Software,
14+
and to permit persons to whom the Software is furnished to do so,
15+
subject to the following conditions:
16+
17+
The above copyright notice and this permission notice
18+
shall be included in all copies or substantial portions of the Software.
19+
20+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
21+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
23+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
24+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
25+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
26+
OTHER DEALINGS IN THE SOFTWARE.
27+
'
28+
29+
TZ="Europe/Warsaw"
30+
31+
timestamp=`date +"%s"`
32+
fileName=$@
33+
34+
date=`date +"%F"`
35+
36+
echo "## Date: ${date}
37+
***
38+
## Context:
39+
example
40+
***
41+
## Options:
42+
-
43+
***
44+
## Decision:" >> "${PWD}/adr/${timestamp}-${fileName// /-}.md"
45+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Context } from "oak";
2+
import { AppState } from "../../../types/state.ts";
3+
import { CreateToDoRequest } from "../types/create-todo.request.ts";
4+
import { CreateToDoCase } from "../use-cases/create-todo.case.ts";
5+
import { ToDoService } from "../services/todo.service.ts";
6+
7+
export class CreateToDoController {
8+
async handle(context: Context<AppState>) {
9+
context.state.logger.debug("CreateUserController");
10+
const body = (await context.request.body({ type: "json" })
11+
.value) as CreateToDoRequest;
12+
const user = await new CreateToDoCase(
13+
context,
14+
new ToDoService(context.state.databaseClient)
15+
).execute(body);
16+
context.response.status = 201;
17+
context.response.body = JSON.stringify(user);
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Context } from "oak";
2+
import { AppState } from "../../../types/state.ts";
3+
import { DeleteToDoCase } from "../use-cases/delete-todo.case.ts";
4+
import { ToDoService } from "../services/todo.service.ts";
5+
import { DeleteToDoRequest } from "../types/delete-todo.request.ts";
6+
7+
export class DeleteToDoController {
8+
async handle(context: Context<AppState>) {
9+
context.state.logger.debug("DeleteToDoController");
10+
const body = (await context.request.body({ type: "json" })
11+
.value) as DeleteToDoRequest;
12+
const response = await new DeleteToDoCase(
13+
context,
14+
new ToDoService(context.state.databaseClient)
15+
).execute(body);
16+
context.response.status = 201;
17+
context.response.body = JSON.stringify(response);
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Context } from "oak";
2+
import { AppState } from "../../../types/state.ts";
3+
import { GetToDoCase } from "../use-cases/get-todos.case.ts";
4+
import { ToDoService } from "../services/todo.service.ts";
5+
6+
export class GetToDosController {
7+
async handle(context: Context<AppState>) {
8+
context.state.logger.debug("GetToDoController");
9+
const users = await new GetToDoCase(
10+
context,
11+
new ToDoService(context.state.databaseClient)
12+
).execute();
13+
context.response.body = JSON.stringify(users);
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { RouterContext, RouteParams } from "oak";
2+
import { AppState } from "../../../types/state.ts";
3+
import { UpdateToDoStatusCase } from "../use-cases/update-todo-status.case.ts";
4+
import { ToDoService } from "../services/todo.service.ts";
5+
import { UpdateToDoStatusRequest } from "../types/update-todo-status.request.ts";
6+
7+
export class UpdateToDoStatusController {
8+
async handle(context: RouterContext<RouteParams, AppState>) {
9+
context.state.logger.debug("UpdateToDoStatusController");
10+
11+
const id = {
12+
id: context?.params?.id,
13+
} as UpdateToDoStatusRequest;
14+
const todo = await new UpdateToDoStatusCase(
15+
context,
16+
new ToDoService(context.state.databaseClient)
17+
).execute(id);
18+
context.response.status = 201;
19+
context.response.body = JSON.stringify(todo);
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { HttpError, Status } from "oak";
2+
3+
export class ToDoNotFoundError extends HttpError {
4+
public status: Status = Status.NotFound;
5+
6+
constructor() {
7+
super("Todo not found");
8+
}
9+
}

src/domain/todo/models/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { ToDoModel } from "./todo.model.ts";
2+
3+
export default [ToDoModel];

0 commit comments

Comments
 (0)