Software Engineer | Studio XID, Inc.
Microsoft MVP
Deno Korea User Group 4th member
TypeScript Korea User Group Organizer
Marktube (Youtube)
➜ docker run -d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=1234 --name books-api-mysql mysql:5.7.30
Unable to find image 'mysql:5.7.30' locally
5.7.30: Pulling from library/mysql
8559a31e96f4: Pull complete
d51ce1c2e575: Pull complete
c2344adc4858: Pull complete
fcf3ceff18fc: Pull complete
16da0c38dc5b: Pull complete
b905d1797e97: Pull complete
4b50d1c6b05c: Pull complete
d85174a87144: Pull complete
a4ad33703fa8: Pull complete
f7a5433ce20d: Pull complete
3dcd2a278b4a: Pull complete
Digest: sha256:32f9d9a069f7a735e28fd44ea944d53c61f990ba71460c5c183e610854ca4854
Status: Downloaded newer image for mysql:5.7.30
9808ce150fc5038122f1b6fd40aa33377cd9f0b3c80ab8852e4067a9e79ad7c1
➜ docker exec -i -t books-api-mysql bash
root@9808ce150fc5:/#
root@9808ce150fc5:/# mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.30 MySQL Community Server (GPL)
Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql>
mysql> CREATE DATABASE `books-api` CHARACTER SET utf8 COLLATE utf8_general_ci;
Query OK, 1 row affected (0.01 sec)
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)
➜ deno run --allow-net --allow-read --allow-write https://deno.land/x/nessie/cli.ts init
Download https://deno.land/x/nessie/cli.ts
Download https://deno.land/x/nessie/cli/state.ts
Download https://deno.land/x/nessie/deps.ts
Download https://deno.land/std@v0.55.0/path/mod.ts
Download https://deno.land/std@v0.55.0/fs/read_json.ts
Download https://deno.land/std@v0.55.0/testing/asserts.ts
Download https://deno.land/x/denomander@0.6.2/mod.ts
Download https://deno.land/x/nessie/clients/AbstractClient.ts
Download https://deno.land/x/nessie/clients/ClientPostgreSQL.ts
Download https://deno.land/x/nessie/cli/utils.ts
Download https://deno.land/x/nessie/types.ts
Download https://deno.land/std@v0.55.0/fmt/colors.ts
Download https://deno.land/std@v0.55.0/testing/diff.ts
Download https://deno.land/std@v0.55.0/path/_constants.ts
Download https://deno.land/std@v0.55.0/path/win32.ts
Download https://deno.land/std@v0.55.0/path/posix.ts
Download https://deno.land/std@v0.55.0/path/common.ts
Download https://deno.land/std@v0.55.0/path/separator.ts
Download https://deno.land/std@v0.55.0/path/_interface.ts
Download https://deno.land/std@v0.55.0/path/glob.ts
Download https://deno.land/x/postgres@v0.4.1/connection_params.ts
Download https://deno.land/x/postgres@v0.4.1/mod.ts
Download https://deno.land/x/postgres@v0.4.1/query.ts
Download https://deno.land/x/denomander@0.6.2/src/Denomander.ts
Download https://deno.land/x/denomander@0.6.2/bootstrap.ts
Download https://deno.land/x/postgres@v0.4.1/connection.ts
Download https://deno.land/x/postgres@v0.4.1/encode.ts
Download https://deno.land/x/postgres@v0.4.1/decode.ts
Download https://deno.land/std@v0.55.0/path/_globrex.ts
Download https://deno.land/x/postgres@v0.4.1/utils.ts
Download https://deno.land/std@v0.55.0/path/_util.ts
Download https://deno.land/x/postgres@v0.4.1/client.ts
Download https://deno.land/x/postgres@v0.4.1/error.ts
Download https://deno.land/x/postgres@v0.4.1/pool.ts
Download https://deno.land/x/denomander@0.6.2/src/Arguments.ts
Download https://deno.land/x/denomander@0.6.2/src/Command.ts
Download https://deno.land/x/denomander@0.6.2/src/Kernel.ts
Download https://deno.land/x/denomander@0.6.2/src/interfaces.ts
Download https://deno.land/x/denomander@0.6.2/src/types.ts
Download https://deno.land/x/denomander@0.6.2/src/Lizard.ts
Download https://deno.land/x/postgres@v0.4.1/oid.ts
Download https://deno.land/x/postgres@v0.4.1/deps.ts
Download https://deno.land/x/postgres@v0.4.1/packet_writer.ts
Download https://deno.land/x/postgres@v0.4.1/packet_reader.ts
Download https://deno.land/x/postgres@v0.4.1/deferred.ts
Download https://deno.land/x/denomander@0.6.2/deps.ts
Download https://deno.land/x/denomander@0.6.2/src/Helper.ts
Download https://deno.land/x/denomander@0.6.2/src/Option.ts
Download https://deno.land/x/denomander@0.6.2/src/Validator.ts
Download https://deno.land/x/denomander@0.6.2/src/Executor.ts
Download https://deno.land/x/denomander@0.6.2/src/Util.ts
Download https://deno.land/std@0.51.0/io/bufio.ts
Download https://deno.land/std@0.51.0/io/util.ts
Download https://deno.land/std@0.51.0/async/deferred.ts
Download https://deno.land/x/checksum@1.2.0/mod.ts
Download https://deno.land/std@v0.51.0/flags/mod.ts
Download https://deno.land/std@v0.51.0/fmt/colors.ts
Download https://deno.land/std@v0.51.0/testing/asserts.ts
Download https://deno.land/x/checksum@1.2.0/hash.ts
Download https://deno.land/std@0.51.0/testing/asserts.ts
Download https://deno.land/std@0.51.0/path/mod.ts
Download https://deno.land/std@0.51.0/encoding/utf8.ts
Download https://deno.land/std@v0.51.0/testing/diff.ts
Download https://deno.land/std@0.51.0/fmt/colors.ts
Download https://deno.land/std@0.51.0/testing/diff.ts
Download https://deno.land/x/checksum@1.2.0/sha1.ts
Download https://deno.land/x/checksum@1.2.0/md5.ts
Download https://deno.land/std@0.51.0/path/win32.ts
Download https://deno.land/std@0.51.0/path/posix.ts
Download https://deno.land/std@0.51.0/path/common.ts
Download https://deno.land/std@0.51.0/path/separator.ts
Download https://deno.land/std@0.51.0/path/interface.ts
Download https://deno.land/std@0.51.0/path/glob.ts
Download https://deno.land/std@0.51.0/path/_constants.ts
Download https://deno.land/std@0.51.0/path/_util.ts
Download https://deno.land/std@0.51.0/path/_globrex.ts
Check https://deno.land/x/nessie/cli.ts
import { ClientPostgreSQL } from "https://deno.land/x/nessie/mod.ts";
import { ClientMySQL } from "https://deno.land/x/nessie/mod.ts";
import { ClientSQLite } from "https://deno.land/x/nessie/mod.ts";
/** These are the default config options. */
const clientOptions = {
migrationFolder: "./db/migrations",
seedFolder: "./db/seeds",
};
/** Select one of the supported clients */
const clientMySql = new ClientMySQL(clientOptions, {
hostname: "localhost",
port: 3306,
username: "root",
password: "books-api",
db: "nessie",
});
/** This is the final config object */
const config = {
client: clientMySql,
// Defaults to false, if you want the query builder exposed in migration files, set this to true.
exposeQueryBuilder: true,
};
export default config;
➜ deno run --allow-net --allow-read --allow-write https://deno.land/x/nessie/cli.ts make create_users
Download https://deno.land/x/nessie/mod.ts
Download https://deno.land/x/nessie/clients/ClientMySQL.ts
Download https://deno.land/x/nessie/clients/ClientSQLite.ts
Download https://deno.land/x/sqlite@v2.0.0/mod.ts
Download https://deno.land/x/mysql@2.2.0/mod.ts
Download https://deno.land/x/sqlite@v2.0.0/src/db.ts
Download https://deno.land/x/sqlite@v2.0.0/src/rows.ts
Download https://deno.land/x/sqlite@v2.0.0/src/constants.ts
Download https://deno.land/x/mysql@2.2.0/src/client.ts
Download https://deno.land/x/mysql@2.2.0/src/connection.ts
Download https://deno.land/x/sqlite@v2.0.0/build/sqlite.js
Download https://deno.land/x/sqlite@v2.0.0/src/wasm.ts
Download https://deno.land/x/sqlite@v2.0.0/src/error.ts
Download https://deno.land/x/mysql@2.2.0/src/constant/errors.ts
Download https://deno.land/x/mysql@2.2.0/src/deferred.ts
Download https://deno.land/x/mysql@2.2.0/src/logger.ts
Download https://deno.land/x/mysql@2.2.0/deps.ts
Download https://deno.land/x/mysql@2.2.0/src/packets/builders/auth.ts
Download https://deno.land/x/mysql@2.2.0/src/packets/builders/query.ts
Download https://deno.land/x/mysql@2.2.0/src/packets/packet.ts
Download https://deno.land/x/mysql@2.2.0/src/packets/parsers/err.ts
Download https://deno.land/x/mysql@2.2.0/src/packets/parsers/handshake.ts
Download https://deno.land/x/mysql@2.2.0/src/packets/parsers/result.ts
Download https://deno.land/x/mysql@2.2.0/src/buffer.ts
Download https://deno.land/x/mysql@2.2.0/src/constant/capabilities.ts
Download https://deno.land/x/mysql@2.2.0/src/constant/mysql_types.ts
Download https://deno.land/x/mysql@2.2.0/src/auth.ts
Download https://deno.land/x/mysql@2.2.0/src/constant/charset.ts
Download https://deno.land/std@0.53.0/async/mod.ts
Download https://deno.land/std@0.53.0/encoding/utf8.ts
Download https://deno.land/x/bytes_formater@1.2.0/mod.ts
Download https://deno.land/x/checksum@1.4.0/mod.ts
Download https://deno.land/x/sha256@v1.0.2/mod.ts
Download https://deno.land/x/sql_builder@1.5.0/util.ts
Download https://deno.land/x/std@0.53.0/log/mod.ts
Download https://deno.land/x/sha256@v1.0.2/deps.ts
Download https://deno.land/std@0.53.0/async/deferred.ts
Download https://deno.land/std@0.53.0/async/delay.ts
Download https://deno.land/std@0.53.0/async/mux_async_iterator.ts
Download https://deno.land/x/checksum@1.4.0/hash.ts
Download https://deno.land/x/bytes_formater@1.2.0/format.ts
Download https://deno.land/x/bytes_formater@1.2.0/deps.ts
Download https://deno.land/x/std@0.53.0/log/logger.ts
Download https://deno.land/x/std@0.53.0/log/handlers.ts
Download https://deno.land/x/std@0.53.0/testing/asserts.ts
Download https://deno.land/x/std@0.53.0/log/levels.ts
Download https://deno.land/x/sqlite@v2.0.0/build/vfs.js
Download https://deno.land/x/std@0.53.0/fmt/colors.ts
Download https://deno.land/x/std@0.53.0/testing/diff.ts
Download https://deno.land/x/std@0.53.0/fs/exists.ts
Download https://deno.land/x/checksum@1.4.0/sha1.ts
Download https://deno.land/x/checksum@1.4.0/md5.ts
Download https://deno.land/std@v0.50.0/fmt/colors.ts
Download https://denopkg.com/chiefbiiko/std-encoding@v1.0.0/mod.ts
Download https://raw.githubusercontent.com/chiefbiiko/std-encoding/v1.0.0/mod.ts
Download https://deno.land/x/base64/base64url.ts
Download https://deno.land/x/base64/base.ts
Check file:///Users/mark/Deno/books-api-deno/nessie.config.ts
Created migration 1593275229293-create_users.ts at /Users/mark/Deno/books-api-deno/db/migrations
import { Migration } from "https://deno.land/x/nessie/mod.ts";
import { Schema } from "https://deno.land/x/nessie/qb.ts";
import { v4 } from "https://deno.land/std/uuid/mod.ts";
import * as bcrypt from "https://deno.land/x/bcrypt/mod.ts";
/** Runs on migrate */
export const up: Migration<Schema> = async ({ queryBuilder }) => {
queryBuilder.create("Users", (table) => {
table.string("userId", 36).primary().notNullable();
table.string("name", 255).notNullable();
table.string("email", 255).notNullable();
table.string("password", 255).notNullable();
table.timestamps();
});
const uuid = v4.generate();
const name = "Mark Lee";
const email = "2woongjae@gmail.com";
const hashed = await bcrypt.hash("deno1234");
queryBuilder.queryString(
`INSERT INTO Users VALUES ('${uuid}', '${name}', '${email}', '${hashed}', DEFAULT, DEFAULT);`
);
return queryBuilder.query;
};
/** Runs on rollback */
export const down: Migration<Schema> = ({ queryBuilder }) => {
return queryBuilder.drop("Users");
};
➜ deno run --allow-net --allow-read https://deno.land/x/nessie/cli.ts migrate
INFO connecting localhost:3306
INFO connected to localhost
Check file:///Users/mark/Deno/books-api-deno/db/migrations/1593275229293-create_users.ts
Migrated 1593275229293-create_users.ts
Migration complete
INFO close connection
➜ deno run --allow-net --allow-read https://deno.land/x/nessie/cli.ts rollback
INFO connecting localhost:3306
INFO connected to localhost
Nothing to rollback
INFO close connection
import { Application } from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
import router from "./routes.ts";
import notFound from "./not_found.ts";
const port = 4000;
const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());
app.use(notFound);
await app.listen({ port });
import { Router } from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
import UserController from "./controllers/UserController.ts";
import BookController from "./controllers/BookController.ts";
const router = new Router();
const userController = new UserController();
const bookController = new BookController();
router
.post("/login", userController.login)
.delete("/logout", userController.logout)
.post("/book", bookController.createBook)
.get("/book", bookController.getBooks)
.get("/book/:id", bookController.getBook)
.patch("/book/:id", bookController.updateBook)
.delete("/book/:id", bookController.deleteBook);
export default router;
import { Context } from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
export default function notFound(context: Context) {
context.response.status = 404;
context.response.body = { error: "Not Found" };
}
import { RouterContext } from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
export default class UserController {
public login(context: RouterContext) {
context.response.body = "login";
}
public logout(context: RouterContext) {
context.response.body = "logout";
}
}
import { RouterContext } from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
export default class BookController {
public createBook(context: RouterContext) {
context.response.body = "createBook";
}
public getBooks(context: RouterContext) {
context.response.body = "getBooks";
}
public getBook(context: RouterContext) {
const id = context.params?.id || null;
context.response.body = "getBook : " + id;
}
public updateBook(context: RouterContext) {
const id = context.params?.id || null;
context.response.body = "updateBook : " + id;
}
public deleteBook(context: RouterContext) {
const id = context.params?.id || null;
context.response.body = "deleteBook : " + id;
}
}
PORT=8080
import { config } from "https://raw.githubusercontent.com/pietvanzoen/deno-dotenv/master/mod.ts";
import { Application } from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
import router from "./routes.ts";
import notFound from "./not_found.ts";
const env = config();
const port = Number(env.PORT) || 4000;
const app = new Application();
app.use(router.routes());
app.use(router.allowedMethods());
app.use(notFound);
await app.listen({ port });
import { RouterContext } from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
export default class UserController {
public async login(context: RouterContext) {
const result = await context.request.body();
console.log(result.type, result.value); // form URLSearchParamsImpl {}
context.response.body = "login";
}
public logout(context: RouterContext) {...}
}
interface URLSearchParams {
append(name: string, value: string): void;
delete(name: string): void;
getAll(name: string): string[];
get(name: string): string | null;
has(name: string): boolean;
set(name: string, value: string): void;
sort(): void;
forEach(
callbackfn: (value: string, key: string, parent: this) => void,
thisArg?: any
): void;
keys(): IterableIterator<string>;
values(): IterableIterator<string>;
entries(): IterableIterator<[string, string]>;
[Symbol.iterator](): IterableIterator<[string, string]>;
toString(): string;
}
import { RouterContext } from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
export default class UserController {
public async login(context: RouterContext) {
const { type, value } = (await context.request.body()) as BodyForm;
console.log(type, value); // form URLSearchParamsImpl {}
const email = value.get("email");
const password = value.get("password");
console.log(email, password); // 2woongjae@gmail.com deno1234
context.response.body = "login";
}
public logout(context: RouterContext) {...}
}
import { RouterContext } from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
export default class UserController {
public async login(context: RouterContext) {
const { type, value } = (await context.request.body()) as BodyForm;
console.log(type, value); // form URLSearchParamsImpl {}
const email = value.get("email");
const password = value.get("password");
console.log(email, password); // 2woongjae@gmail.com deno1234
if (email === null || password === null) {
context.response.status = 422;
context.response.body = { error: "email or password required" };
return;
}
context.response.body = "login";
}
public logout(context: RouterContext) {...}
}
import { Client } from "https://deno.land/x/mysql/mod.ts";
import { connectionOptions } from "../nessie.config.ts";
const client = await new Client().connect(connectionOptions);
export default client;
import { ClientMySQL } from "https://deno.land/x/nessie/mod.ts";
/** These are the default config options. */
const clientOptions = {
migrationFolder: "./db/migrations",
seedFolder: "./db/seeds",
};
export const connectionOptions = {
hostname: "localhost",
port: 3306,
username: "root",
password: "1234",
db: "books-api",
};
/** Select one of the supported clients */
const clientMySql = new ClientMySQL(clientOptions, connectionOptions);
/** This is the final config object */
const config = {
client: clientMySql,
// Defaults to false, if you want the query builder exposed in migration files, set this to true.
exposeQueryBuilder: true,
};
export default config;
import Dex from "https://deno.land/x/dex/mod.ts";
import client from "./config.ts";
const dex = Dex({ client: "mysql" });
export interface User {
userId: string;
name: string;
email: string;
password: string;
created_at: Date;
updated_at: Date;
}
export async function getUserByEmail(email: string): Promise<User> {
const selectQuery = dex
.queryBuilder()
.select()
.from("Users")
.where({ email })
.toString();
const { rows } = await client.execute(selectQuery);
if (rows === undefined) throw new Error();
if (rows.length !== 1) throw new Error();
return rows[0];
}
import { RouterContext } from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
export default class UserController {
public async login(context: RouterContext) {
const { type, value } = (await context.request.body()) as BodyForm;
console.log(type, value);
const email = value.get("email");
const password = value.get("password");
console.log(email, password);
if (email === null || password === null) {
context.response.status = 422;
context.response.body = { error: "email or password required" };
return;
}
const user = await getUserByEmail(email);
console.log(user);
const match = await bcrypt.compare(password, user.password);
console.log(match);
context.response.body = "login";
}
public logout(context: RouterContext) {...}
}
import {
makeJwt,
setExpiration,
Jose,
} from "https://deno.land/x/djwt/create.ts";
import { User } from "../models/Users.ts";
const key = "markzzang";
const header: Jose = {
alg: "HS256",
typ: "JWT",
};
export function generate(user: User): string {
return makeJwt({
header,
payload: {
iss: "mark",
exp: setExpiration(new Date().getTime() + 60000),
userId: user.userId,
email: user.email,
},
key,
});
}
import {
RouterContext,
BodyForm,
} from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
import * as bcrypt from "https://deno.land/x/bcrypt/mod.ts";
import { getUserByEmail } from "../models/Users.ts";
import { generate } from "../utils/token.ts";
export default class UserController {
public async login(context: RouterContext) {
...
const user = await getUserByEmail(email);
console.log(user);
const match = await bcrypt.compare(password, user.password);
console.log(match);
if (match) {
context.response.body = { token: generate(user) };
} else {
context.response.status = 400;
context.response.body = { error: "wrong password" };
}
}
public logout(context: RouterContext) {...}
}
➜ deno run --allow-net --allow-read --allow-write https://deno.land/x/nessie/cli.ts make create_books
Check file:///Users/mark/Deno/books-api-deno/nessie.config.ts
Created migration 1593422520239-create_books.ts at /Users/mark/Deno/books-api-deno/db/migrations
import { Migration } from "https://deno.land/x/nessie/mod.ts";
import { Schema } from "https://deno.land/x/nessie/qb.ts";
/** Runs on migrate */
export const up: Migration<Schema> = async ({ queryBuilder }) => {
queryBuilder.create("Books", (table) => {
table.id();
table.string("ownerId", 36).notNullable();
table.string("title", 255).notNullable();
table.string("message", 255).notNullable();
table.string("author", 255).notNullable();
table.string("url", 255).notNullable();
table.timestamps();
});
return queryBuilder.query;
};
/** Runs on rollback */
export const down: Migration<Schema> = ({ queryBuilder }) => {
return queryBuilder.drop("Books");
};
➜ deno run --allow-net --allow-read --unstable https://deno.land/x/nessie/cli.ts migrate
INFO connecting localhost:3306
INFO connected to localhost
Check file:///Users/mark/Deno/books-api-deno/db/migrations/1593422520239-create_books.ts
Migrated 1593422520239-create_books.ts
Migration complete
INFO close connection
import { RouterContext } from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
import { validate } from "../utils/token.ts";
import { getBooksByOwnerId } from "../models/Books.ts";
export default class BookController {
public async getBooks(context: RouterContext) {
console.log(context.request.headers);
const authorization = context.request.headers.get("authorization");
console.log(authorization);
}
}
import { RouterContext } from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
import { validate } from "../utils/token.ts";
import { getBooksByOwnerId } from "../models/Books.ts";
export default class BookController {
public async getBooks(context: RouterContext) {
console.log(context.request.headers);
const authorization = context.request.headers.get("authorization");
console.log(authorization);
if (authorization === null) {
context.response.status = 401;
context.response.body = { error: "Unauthorized" };
return;
}
const token = authorization.replace("Bearer ", "");
console.log(token);
const validation = await validate(token);
console.log(validation);
}
}
import {
makeJwt,
setExpiration,
Jose,
} from "https://deno.land/x/djwt/create.ts";
import {
validateJwt,
JwtValidation,
} from "https://deno.land/x/djwt/validate.ts";
import { User } from "../models/Users.ts";
const key = "markzzang";
const header: Jose = {
alg: "HS256",
typ: "JWT",
};
export function generate(user: User): string {...}
export async function validate(token: string): Promise<JwtValidation> {
return await validateJwt(token, key);
}
import { RouterContext } from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
import { validate } from "../utils/token.ts";
import { getBooksByOwnerId } from "../models/Books.ts";
export default class BookController {
public async getBooks(context: RouterContext) {
...
const validation = await validate(token);
console.log(validation);
if (!validation.isValid) {
context.response.status = 401;
context.response.body = { error: "Unauthorized" };
return;
}
const userId = validation.payload?.userId as string;
if (userId === undefined) {
context.response.status = 401;
context.response.body = { error: "Unauthorized" };
return;
}
const books = await getBooksByOwnerId(userId);
context.response.body = books;
}
}
import { config } from "https://raw.githubusercontent.com/pietvanzoen/deno-dotenv/master/mod.ts";
import { Application } from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
import router from "./routes.ts";
import notFound from "./not_found.ts";
const env = config();
const port = Number(env.PORT) || 4000;
const app = new Application();
app.use(async (context, next) => {
try {
await next();
} catch (error) {
context.response.status = error.status || 400;
context.response.body = { error: error.message };
}
});
app.use(router.routes());
app.use(router.allowedMethods());
app.use(notFound);
await app.listen({ port });
export class UnauthorizedError extends Error {
status = 401;
message = "Unauthorized Error";
}
import { RouterContext } from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
import { validate } from "../utils/token.ts";
import { getBooksByOwnerId } from "../models/Books.ts";
import { UnauthorizedError } from "../utils/errors.ts";
export default class BookController {
public async getBooks(context: RouterContext) {
console.log(context.request.headers);
const authorization = context.request.headers.get("authorization");
console.log(authorization);
if (authorization === null) {
throw new UnauthorizedError();
}
const token = authorization.replace("Bearer ", "");
console.log(token);
const validation = await validate(token);
console.log(validation);
if (!validation.isValid) {
throw new UnauthorizedError();
}
const userId = validation.payload?.userId as string;
if (userId === undefined) {
throw new UnauthorizedError();
}
const books = await getBooksByOwnerId(userId);
context.response.body = books;
}
}
import {
RouterContext,
BodyForm,
} from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
import { validate } from "../utils/token.ts";
import { getBooksByOwnerId, createBook } from "../models/Books.ts";
import { UnauthorizedError, ParameterRequired } from "../utils/errors.ts";
export default class BookController {
public async createBook(context: RouterContext) {
const authorization = context.request.headers.get("authorization");
if (authorization === null) {
throw new UnauthorizedError();
}
const token = authorization.replace("Bearer ", "");
const validation = await validate(token);
if (!validation.isValid) {
throw new UnauthorizedError();
}
const userId = validation.payload?.userId as string;
if (userId === undefined) {
throw new UnauthorizedError();
}
const { value } = (await context.request.body()) as BodyForm;
const title = value.get("title");
const message = value.get("message");
const author = value.get("author");
const url = value.get("url");
if (title === null || message === null || author === null || url === null) {
throw new ParameterRequired();
}
await createBook({ title, message, author, url }, userId);
context.response.body = { success: true };
}
}
export interface BookRawData {
title: string;
message: string;
author: string;
url: string;
}
export async function createBook(
book: BookRawData,
ownerId: string
): Promise<void> {
const insertQuery = dex
.queryBuilder()
.insert([{ ...book, ownerId }])
.into("Books")
.toString();
console.log(insertQuery);
await client.execute(insertQuery);
}
import {
RouterContext,
BodyForm,
} from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
import { validate } from "../utils/token.ts";
import { getBooksByOwnerId, createBook, getBookById } from "../models/Books.ts";
import { UnauthorizedError, ParameterRequired } from "../utils/errors.ts";
export default class BookController {
public async getBook(context: RouterContext) {
const authorization = context.request.headers.get("authorization");
if (authorization === null) {
throw new UnauthorizedError();
}
const token = authorization.replace("Bearer ", "");
const validation = await validate(token);
if (!validation.isValid) {
throw new UnauthorizedError();
}
const userId = validation.payload?.userId as string;
if (userId === undefined) {
throw new UnauthorizedError();
}
const bookId = Number(context.params?.id) || null;
if (bookId === null) {
throw new ParameterRequired();
}
const book = await getBookById(bookId, userId);
context.response.body = book;
}
}
export async function getBookById(id: number, ownerId: string): Promise<Book> {
const selectQuery = dex
.queryBuilder()
.select()
.from("Books")
.where({ id, ownerId })
.toString();
const { rows } = await client.execute(selectQuery);
if (rows === undefined) throw new Error();
if (rows.length !== 1) throw new Error("result not exist");
return rows[0];
}
import { RouterContext } from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
import { UnauthorizedError } from "./errors.ts";
import { validate } from "./token.ts";
export async function getUserIdFromAuthorization(
context: RouterContext
): Promise<string> {
const authorization = context.request.headers.get("authorization");
if (authorization === null) {
throw new UnauthorizedError();
}
const token = authorization.replace("Bearer ", "");
const validation = await validate(token);
if (!validation.isValid) {
throw new UnauthorizedError();
}
const userId = validation.payload?.userId as string;
if (userId === undefined) {
throw new UnauthorizedError();
}
return userId;
}
import {
RouterContext,
BodyForm,
} from "https://raw.githubusercontent.com/oakserver/oak/main/mod.ts";
import { validate } from "../utils/token.ts";
import { getBooksByOwnerId, createBook, getBookById } from "../models/Books.ts";
import { UnauthorizedError, ParameterRequired } from "../utils/errors.ts";
import { getUserIdFromAuthorization } from "../utils/auth.ts";
export default class BookController {
public async getBook(context: RouterContext) {
const userId = await getUserIdFromAuthorization(context);
const bookId = Number(context.params?.id) || null;
if (bookId === null) {
throw new ParameterRequired();
}
const book = await getBookById(bookId, userId);
context.response.body = book;
}
}