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 0000000..1cfaaa2
Binary files /dev/null and b/static/favicon.ico differ
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(),
+});