Update the application to be express with types, run with bun

This commit is contained in:
Dave Smith-Hayes 2024-04-01 20:48:57 -04:00
parent 2b19e3440d
commit 0d2cfd3002
28 changed files with 77 additions and 331 deletions

View File

@ -1,11 +1,15 @@
# slovocast-api
To install dependencies: To install dependencies:
```sh
```bash
bun install bun install
``` ```
To run: To run:
```sh
bun run dev ```bash
bun run server.ts
``` ```
open http://localhost:3000 This project was created using `bun init` in bun v1.0.35. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.

Binary file not shown.

View File

@ -1,11 +1,15 @@
{ {
"scripts": { "name": "slovocast-api",
"dev": "bun run --hot src/index.ts" "module": "server.ts",
}, "type": "module",
"dependencies": {
"hono": "^4.0.8"
},
"devDependencies": { "devDependencies": {
"@types/bun": "latest" "@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"@types/express": "^4.17.21",
"express": "^4.19.2"
} }
} }

View File

@ -1,7 +0,0 @@
const FlashMessage = (props: { message: string, severity?: string }) => {
return (
<div class="flash-message {props.severity ?? null}">
<span>{props.message}</span>
</div>
)
};

View File

@ -1,9 +0,0 @@
const ErrorMessage = (props: { message: string }) => {
return (
<div class="form-error">
{props.message}
</div>
);
};
export default ErrorMessage;

View File

@ -1,24 +0,0 @@
import { FC } from "hono/jsx"
import ErrorMessage from "@slovo/frontend/components/form/error-message";
export const LoginForm: FC = (props) => {
return (
<>
{props.error ? <ErrorMessage message={props.error.message} /> : null}
<form action="/login" method="POST">
<label for="email">
<div>Email</div>
<input type="text" name="email" required />
</label>
<label for="password">
<div>Password</div>
<input type="password" name="password" required />
</label>
<div>
<input type="submit" name="login" value="Login" />
</div>
</form>
</>
);
};

View File

@ -1,29 +0,0 @@
import { FC } from 'hono/jsx';
import ErrorMessage from '@slovo/frontend/components/form/error-message';
export const RegisterForm: FC = (props) => {
return (
<>
{props.error ? <ErrorMessage message={props.error.message} /> : null}
<form action="/register" method="POST">
<label for="email">
<div>Email</div>
<input type="text" name="email" required />
</label>
<label for="password">
<div>Password</div>
<input type="password" name="password" required />
</label>
<label for="password-confirm">
<div>Confirm Password</div>
<input type="password" name="password-confirm" require />
</label>
<div>
<input type="submit" name="login" value="Login" />
</div>
</form>
</>
);
};

View File

@ -1,23 +0,0 @@
import { html } from 'hono/html';
export const Layout = (props: { title: string, children?: any }) => {
return html`<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>${props.title}</title>
</head>
<body>
<header>
<h1>Slovocast</h1>
</header>
<main>${props.children}</main>
<footer>
<p>&copy; 2024, Slovocast</p>
</footer>
</body>
</html>`;
}

View File

@ -1,13 +0,0 @@
import { FC } from 'hono/jsx';
// const currentYear = new Date().getFullYear();
const Footer: FC = (props) => {
return (
<footer>
<div>{props.copyright}</div>
</footer>
);
};
export default Footer;

View File

@ -1,11 +0,0 @@
import { FC } from 'hono/jsx';
const Header: FC = (props) => {
return (
<header>
<div class="title">{props.title}</div>
</header>
);
};
export default Header;

View File

@ -1,9 +0,0 @@
import { Layout } from "@slovo/frontend/layout";
export const HomePage = () => {
return (
<Layout title="Home">
<div>Welcome to Slovocast</div>
</Layout>
);
};

View File

@ -1,11 +0,0 @@
import { Layout } from '@slovo/frontend/layout';
import { LoginForm } from '@slovo/frontend/components/form/login-form';
export const LoginPage = (props: { error?: any }) => {
return (
<Layout title="Login">
<LoginForm action="/login" error={props.error} />
</Layout>
);
};

View File

@ -1,10 +0,0 @@
import { RegisterForm } from '@slovo/frontend/components/form/register-form';
import { Layout } from '@slovo/frontend/layout';
export const RegisterPage = (props: { error?: any }) => {
return (
<Layout title="Register">
<RegisterForm action="/register" error={props.error}>
</Layout>
);
};

View File

@ -1,35 +0,0 @@
import { Hono } from 'hono';
import { Context } from 'hono';
import { LoginPage } from '@slovo/frontend/pages/login';
import { RegisterPage } from '@slovo/frontend/pages/register';
const UserHandler = new Hono();
UserHandler.get('/login', async (c: Context) => {
const errors = c.get('login-errors');
return c.html(<LoginPage error={errors} />);
});
UserHandler.post('/login', async (c: Context) => {
const form = await c.req.formData();
// validate form
if (form.has('login')) {
if (form.get('email') == 'me@davesmithhayes.com') {
if (form.get('password') == 'test') {
c.set('auth', true);
return c.redirect('/');
}
}
}
c.set('login-errors', { error: { message: "Bad credentials" }});
return c.redirect('/login');
});
UserHandler.get('/register', async (c: Context) => {
const errors = c.get('register-errors');
return c.html(<RegisterPage error={errors} />);
})
export default UserHandler;

View File

@ -1,16 +0,0 @@
import { Hono } from 'hono';
import { siteDataMiddleware } from './middleware/siteData';
import { HomePage } from './frontend/pages/home';
import UserHandler from './handlers/user';
const app = new Hono();
app.use(siteDataMiddleware);
app.get('/', async (c) => {
return c.html(<HomePage />);
});
// Handlers
app.route('', UserHandler);
export default app;

View File

@ -1,45 +0,0 @@
import { Context } from 'hono';
class Session {
private readonly id: string;
private data: Record<string, any>;
public constructor(id: string, data?: Record<string, any>) {
this.id = id;
this.data = data ?? {};
}
public getId(): string {
return this.id;
}
public getData(key: string): any {
return this.data[key];
}
public setData(key: string, data: any): void {
this.data[key] = data;
}
}
class SessionHandler {
private static instance: SessionHandler;
private constructor() { }
public static getInstance(): SessionHandler {
if (!SessionHandler.instance) {
SessionHandler.instance = new SessionHandler();
}
return SessionHandler.instance;
}
}
const sessionMiddleware = async (c: Context, next: CallableFunction) => {
c.set('session', SessionHandler.getInstance());
await next();
};
export { sessionMiddleware, SessionHandler, Session };

View File

@ -1,18 +0,0 @@
import { Context } from 'hono';
import SiteData from '@slovo/model/SiteData';
const currentYear = new Date().getFullYear();
const config: SiteData = {
name: "Slovocast",
description: "A no-nonesense Podcast hosting platform.",
baseUrl: "dev.slovocast.com",
copyright: `Copyright ${currentYear} Slovocast`,
};
const siteDataMiddleware = async function(c: Context, next: CallableFunction) {
c.set('config', config);
await next();
};
export { siteDataMiddleware };

View File

@ -1,6 +0,0 @@
type Category = {
name: string,
categories?: Category[]
};
export default Category;

View File

@ -1,19 +0,0 @@
import Category from '@slovo/model/Category';
import Image from '@slovo/model/Image';
import Episode from '@slovo/model/Episode';
type Channel = {
name: string,
description: string,
link: URL,
language: string,
copyright: string,
explicit: boolean,
category: Category,
image: Image,
episodes: Episode[],
};
export default Channel;

View File

@ -1,13 +0,0 @@
import Image from '@slovo/model/Image';
type Episode = {
title: string,
link: URL,
duration: string,
description: string,
explicit: boolean,
image: Image,
};
export default Episode;

View File

@ -1,8 +0,0 @@
type SiteData = {
name: string,
description: string,
baseUrl: string
copyright: string,
};
export default SiteData;

View File

@ -1,6 +0,0 @@
type User = {
name: string,
email: string,
};
export default User;

11
app/src/models/channel.ts Normal file
View File

@ -0,0 +1,11 @@
type Channel = {
name: string,
description: string,
link: URL,
language: string,
copyright: string,
explicit: boolean,
category: string,
};
export default Channel;

View File

@ -0,0 +1,9 @@
type Episode = {
title: string,
link: URL,
duration: string,
description: string,
explicit: boolean
};
export default Episode;

View File

@ -1,8 +1,8 @@
type Image = { type Image = {
title: string,
url: URL, url: URL,
width?: number, title: string,
height?: number width: number,
height: number,
}; };
export default Image; export default Image;

12
app/src/server.ts Normal file
View File

@ -0,0 +1,12 @@
import express, { Express, type Request, type Response } from "express";
const server: Express = express();
server.get("/", async (req: Request, res: Response) => {
console.log(req);
return res.json({ message: "Hello!" });
});
server.listen(3000, () => {
console.log("Running on 3000");
});

View File

@ -1,10 +1,27 @@
{ {
"compilerOptions": { "compilerOptions": {
"strict": true, // Enable latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "hono/jsx", "allowJs": true,
"paths": {
"@slovo/*": [ "src/*" ] // Bundler mode
} "moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
} }
} }

View File

@ -3,6 +3,7 @@ CREATE TABLE episodes (
title VARCHAR(255) NOT NULL, title VARCHAR(255) NOT NULL,
link VARCHAR(255) NOT NULL, link VARCHAR(255) NOT NULL,
duration VARCHAR(127) NOT NULL, duration VARCHAR(127) NOT NULL,
length INT(11) UNSIGNED NOT NULL,
description TEXT NOT NULL, description TEXT NOT NULL,
explicit BOOLEAN NOT NULL DEFAULT true, explicit BOOLEAN NOT NULL DEFAULT true,