From 8ed9bc76d3262f7458c21eb57f3c50b594de9af7 Mon Sep 17 00:00:00 2001 From: Mohammed Imran Date: Fri, 1 Jul 2022 15:28:51 +0530 Subject: [PATCH] initial commit --- .vscode/settings.json | 6 ++++ README.md | 11 +++++++ deno.json | 6 ++++ dev.ts | 5 +++ docker-compose.postgres.yml | 20 ++++++++++++ fresh.gen.ts | 22 +++++++++++++ import_map.json | 11 +++++++ islands/Counter.tsx | 23 ++++++++++++++ main.ts | 9 ++++++ models/Todo.ts | 16 ++++++++++ routes/api/todos/[id].ts | 60 ++++++++++++++++++++++++++++++++++++ routes/api/todos/index.ts | 40 ++++++++++++++++++++++++ routes/index.tsx | 26 ++++++++++++++++ static/favicon.ico | Bin 0 -> 22382 bytes static/logo.svg | 6 ++++ utils/db.ts | 15 +++++++++ utils/zodSchema.ts | 14 +++++++++ 17 files changed, 290 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 README.md create mode 100644 deno.json create mode 100755 dev.ts create mode 100644 docker-compose.postgres.yml create mode 100644 fresh.gen.ts create mode 100644 import_map.json create mode 100644 islands/Counter.tsx create mode 100644 main.ts create mode 100644 models/Todo.ts create mode 100644 routes/api/todos/[id].ts create mode 100644 routes/api/todos/index.ts create mode 100644 routes/index.tsx create mode 100644 static/favicon.ico create mode 100644 static/logo.svg create mode 100644 utils/db.ts create mode 100644 utils/zodSchema.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a36f4b8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "deno.enable": true, + "cSpell.words": [ + "preact" + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..f605bd9 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# fresh project + +### Usage + +Start the project: + +``` +deno task start +``` + +This will watch the project directory and restart as necessary. diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..673d9eb --- /dev/null +++ b/deno.json @@ -0,0 +1,6 @@ +{ + "tasks": { + "start": "deno run -A --watch=static/,routes/ dev.ts" + }, + "importMap": "./import_map.json" +} diff --git a/dev.ts b/dev.ts new file mode 100755 index 0000000..2d85d6c --- /dev/null +++ b/dev.ts @@ -0,0 +1,5 @@ +#!/usr/bin/env -S deno run -A --watch=static/,routes/ + +import dev from "$fresh/dev.ts"; + +await dev(import.meta.url, "./main.ts"); diff --git a/docker-compose.postgres.yml b/docker-compose.postgres.yml new file mode 100644 index 0000000..937897d --- /dev/null +++ b/docker-compose.postgres.yml @@ -0,0 +1,20 @@ +version: '3.8' + +services: + postgres: + container_name: postgres + image: postgres:14.3-alpine + restart: always + environment: + POSTGRES_USER: user + POSTGRES_PASSWORD: password + POSTGRES_DB: freshapp + ports: + - '5432:5432' + volumes: + - 'pg_data:/var/lib/postgresql/data/' + + +volumes: + pg_data: + driver: local diff --git a/fresh.gen.ts b/fresh.gen.ts new file mode 100644 index 0000000..a6215df --- /dev/null +++ b/fresh.gen.ts @@ -0,0 +1,22 @@ +// DO NOT EDIT. This file is generated by fresh. +// This file SHOULD be checked into source version control. +// This file is automatically updated during development when running `dev.ts`. + +import * as $0 from "./routes/api/todos/[id].ts"; +import * as $1 from "./routes/api/todos/index.ts"; +import * as $2 from "./routes/index.tsx"; +import * as $$0 from "./islands/Counter.tsx"; + +const manifest = { + routes: { + "./routes/api/todos/[id].ts": $0, + "./routes/api/todos/index.ts": $1, + "./routes/index.tsx": $2, + }, + islands: { + "./islands/Counter.tsx": $$0, + }, + baseUrl: import.meta.url, +}; + +export default manifest; diff --git a/import_map.json b/import_map.json new file mode 100644 index 0000000..4bbec93 --- /dev/null +++ b/import_map.json @@ -0,0 +1,11 @@ +{ + "imports": { + "$fresh/": "https://deno.land/x/fresh@1.0.0/", + "$zod/": "https://deno.land/x/zod@v3.17.3/", + "$denodb/": "https://deno.land/x/denodb@v1.0.40/", + "preact": "https://esm.sh/preact@10.8.1", + "preact/": "https://esm.sh/preact@10.8.1/", + "preact-render-to-string": "https://esm.sh/preact-render-to-string@5.2.0?deps=preact@10.8.1" + } +} + diff --git a/islands/Counter.tsx b/islands/Counter.tsx new file mode 100644 index 0000000..d087839 --- /dev/null +++ b/islands/Counter.tsx @@ -0,0 +1,23 @@ +/** @jsx h */ +import { h } from "preact"; +import { useState } from "preact/hooks"; +import { IS_BROWSER } from "$fresh/runtime.ts"; + +interface CounterProps { + start: number; +} + +export default function Counter(props: CounterProps) { + const [count, setCount] = useState(props.start); + return ( +
+

{count}

+ + +
+ ); +} diff --git a/main.ts b/main.ts new file mode 100644 index 0000000..bae9199 --- /dev/null +++ b/main.ts @@ -0,0 +1,9 @@ +/// +/// +/// +/// +/// + +import { start } from "$fresh/server.ts"; +import manifest from "./fresh.gen.ts"; +await start(manifest); diff --git a/models/Todo.ts b/models/Todo.ts new file mode 100644 index 0000000..669d436 --- /dev/null +++ b/models/Todo.ts @@ -0,0 +1,16 @@ +import { DataTypes, Model } from "$denodb/mod.ts"; + +export class Todo extends Model { + static table = 'todos'; + static timestamps = true; + + static fields = { + id: { primaryKey: true, autoIncrement: true }, + text: DataTypes.STRING, + completed: DataTypes.BOOLEAN, + }; + + static defaults = { + completed: false, + }; +} diff --git a/routes/api/todos/[id].ts b/routes/api/todos/[id].ts new file mode 100644 index 0000000..03f48cc --- /dev/null +++ b/routes/api/todos/[id].ts @@ -0,0 +1,60 @@ +import { HandlerContext } from '$fresh/server.ts'; +import { Todo } from '../../../Models/Todo.ts'; +import { db } from '../../../utils/db.ts'; +import * as zod from 'https://deno.land/x/zod@v3.17.3/mod.ts'; + +const idSchema = zod.number(); + +const todoSchema = zod.object({ + completed: zod.boolean(), +}); + +export async function handler(req: Request, ctx: HandlerContext) { + try { + if (req.method === 'GET') { + const id = idSchema.parse(Number(ctx.params.id)); + + const data = await Todo.find(id); + + return new Response(JSON.stringify({ data }), { + headers: { 'Content-Type': 'application/json' }, + }); + } + + if (req.method === 'PATCH') { + const id = idSchema.parse(Number(ctx.params.id)); + + const todo = todoSchema.parse(await req.json()); + + await Todo.where({ id }).update(todo); + const data = await Todo.find(id); + + return new Response(JSON.stringify({ data }), { + headers: { 'Content-Type': 'application/json' }, + }); + } + + return new Response(undefined, { + headers: { 'Content-Type': 'application/json' }, + status: 405, + statusText: 'Method Not Allowed', + }); + } catch (error) { + if (error instanceof zod.ZodError) { + return new Response(JSON.stringify({ error, message: 'Invalid Id' }), { + headers: { 'Content-Type': 'application/json' }, + status: 400, + statusText: 'Bad Request', + }); + } + + return new Response(JSON.stringify({ error }), { + headers: { 'Content-Type': 'application/json' }, + status: 500, + statusText: 'Internal Server Error', + }); + } finally { + await db.close(); + } +} + diff --git a/routes/api/todos/index.ts b/routes/api/todos/index.ts new file mode 100644 index 0000000..b257e2f --- /dev/null +++ b/routes/api/todos/index.ts @@ -0,0 +1,40 @@ +import { HandlerContext } from '$fresh/server.ts'; +import { Todo } from '../../../models/Todo.ts'; +import { db } from '../../../utils/db.ts'; +import { TodoInputSchema } from '../../../utils/zodSchema.ts'; + +export async function handler(req: Request, _ctx: HandlerContext) { + try { + if (req.method === 'GET') { + const data = await Todo.all(); + return new Response(JSON.stringify({ data }), { + headers: { 'Content-Type': 'application/json' }, + }); + } + + if (req.method === 'POST') { + const body = await req.json(); + const todo = TodoInputSchema.parse(body); + const data = await Todo.create({ ...todo }); + + return new Response(JSON.stringify({ data }), { + headers: { 'Content-Type': 'application/json' }, + }); + } + + return new Response(undefined, { + headers: { 'Content-Type': 'application/json' }, + status: 405, + statusText: 'Method Not Allowed', + }); + } catch (error) { + return new Response(JSON.stringify({ error }), { + headers: { 'Content-Type': 'application/json' }, + status: 400, + statusText: 'Bad Request', + }); + } finally { + await db.close(); + } +} + diff --git a/routes/index.tsx b/routes/index.tsx new file mode 100644 index 0000000..36ae691 --- /dev/null +++ b/routes/index.tsx @@ -0,0 +1,26 @@ +/** @jsx h */ +import { h } from 'preact'; + +import { Handlers } from '$fresh/server.ts'; +import { Todo } from '../models/Todo.ts'; + +export const handler: Handlers = { + async GET(_, ctx) { + const todos = await Todo.select('id', 'text', 'completed').orderBy('id').all(); + return ctx.render(todos); + }, +}; + +export default function Home({ data }: { data: Todo[] | null }) { + return ( +
+ {data?.map((todo) => ( +
+

Todo: {todo.text}

+

Completed: {String(todo.completed)}

+
+ ))} +
+ ); +} + diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1cfaaa2193b0f210107a559f7421569f57a25388 GIT binary patch literal 22382 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 bck@HGWY%2idMme??%p8ZW3z(%VE+9-Ofn0d literal 0 HcmV?d00001 diff --git a/static/logo.svg b/static/logo.svg new file mode 100644 index 0000000..ef2fbe4 --- /dev/null +++ b/static/logo.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/utils/db.ts b/utils/db.ts new file mode 100644 index 0000000..6e707ab --- /dev/null +++ b/utils/db.ts @@ -0,0 +1,15 @@ +import { Database, PostgresConnector } from 'https://deno.land/x/denodb@v1.0.40/mod.ts'; +import { Todo } from "../Models/Todo.ts"; + +const connection = new PostgresConnector({ + host: 'localhost', + port: 5432, + username: 'user', + password: 'password', + database: 'freshapp', +}); + +export const db = new Database(connection); + +db.link([Todo]) + diff --git a/utils/zodSchema.ts b/utils/zodSchema.ts new file mode 100644 index 0000000..e2ed90c --- /dev/null +++ b/utils/zodSchema.ts @@ -0,0 +1,14 @@ +import { z } from '$zod/mod.ts'; + + +export const TodoInputSchema = z.object({ + text: z.string(), + completed: z.boolean(), +}); + + +export const TodoSchema = TodoInputSchema.extend({ + id: z.number(), + createdAt: z.string(), + updatedAt: z.string(), +});