Update the application to be express with types, run with bun
This commit is contained in:
parent
2b19e3440d
commit
0d2cfd3002
@ -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.
|
||||||
|
BIN
app/bun.lockb
BIN
app/bun.lockb
Binary file not shown.
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +0,0 @@
|
|||||||
const FlashMessage = (props: { message: string, severity?: string }) => {
|
|
||||||
return (
|
|
||||||
<div class="flash-message {props.severity ?? null}">
|
|
||||||
<span>{props.message}</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
};
|
|
@ -1,9 +0,0 @@
|
|||||||
const ErrorMessage = (props: { message: string }) => {
|
|
||||||
return (
|
|
||||||
<div class="form-error">
|
|
||||||
{props.message}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ErrorMessage;
|
|
@ -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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -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>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -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>© 2024, Slovocast</p>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>`;
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
@ -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;
|
|
@ -1,9 +0,0 @@
|
|||||||
import { Layout } from "@slovo/frontend/layout";
|
|
||||||
|
|
||||||
export const HomePage = () => {
|
|
||||||
return (
|
|
||||||
<Layout title="Home">
|
|
||||||
<div>Welcome to Slovocast</div>
|
|
||||||
</Layout>
|
|
||||||
);
|
|
||||||
};
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
@ -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;
|
|
@ -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;
|
|
@ -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 };
|
|
||||||
|
|
@ -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 };
|
|
@ -1,6 +0,0 @@
|
|||||||
type Category = {
|
|
||||||
name: string,
|
|
||||||
categories?: Category[]
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Category;
|
|
@ -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;
|
|
@ -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;
|
|
@ -1,8 +0,0 @@
|
|||||||
type SiteData = {
|
|
||||||
name: string,
|
|
||||||
description: string,
|
|
||||||
baseUrl: string
|
|
||||||
copyright: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SiteData;
|
|
@ -1,6 +0,0 @@
|
|||||||
type User = {
|
|
||||||
name: string,
|
|
||||||
email: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default User;
|
|
11
app/src/models/channel.ts
Normal file
11
app/src/models/channel.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
type Channel = {
|
||||||
|
name: string,
|
||||||
|
description: string,
|
||||||
|
link: URL,
|
||||||
|
language: string,
|
||||||
|
copyright: string,
|
||||||
|
explicit: boolean,
|
||||||
|
category: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Channel;
|
9
app/src/models/episode.ts
Normal file
9
app/src/models/episode.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
type Episode = {
|
||||||
|
title: string,
|
||||||
|
link: URL,
|
||||||
|
duration: string,
|
||||||
|
description: string,
|
||||||
|
explicit: boolean
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Episode;
|
@ -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
12
app/src/server.ts
Normal 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");
|
||||||
|
});
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user