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:
```sh
```bash
bun install
```
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": {
"dev": "bun run --hot src/index.ts"
},
"dependencies": {
"hono": "^4.0.8"
},
"name": "slovocast-api",
"module": "server.ts",
"type": "module",
"devDependencies": {
"@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 = {
title: string,
url: URL,
width?: number,
height?: number
title: string,
width: number,
height: number,
};
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": {
"strict": true,
// Enable latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx",
"paths": {
"@slovo/*": [ "src/*" ]
}
"allowJs": true,
// 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,
link VARCHAR(255) NOT NULL,
duration VARCHAR(127) NOT NULL,
length INT(11) UNSIGNED NOT NULL,
description TEXT NOT NULL,
explicit BOOLEAN NOT NULL DEFAULT true,