Copy project from personal website
This commit is contained in:
2026-03-17 13:41:37 +01:00
commit 8e1df92813
29 changed files with 1989 additions and 0 deletions

29
backend/app.ts Normal file
View File

@@ -0,0 +1,29 @@
import express from "express";
const hostname = '127.0.0.1';
const httpPort = 4080;
const app = express();
// app.set('views', 'views');
// app.set('view engine', 'hbs');
// import morgan from 'morgan'
// app.use(morgan('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(express.static('public'));
import mainRouter from './routes/main';
import apiRouter from './routes/api/apiRouter';
app.use('/', mainRouter);
app.use('/api', apiRouter);
app.listen(httpPort, () => {
console.log(`Server running at http://${hostname}:${httpPort}/`);
});
import mediaController from "./controllers/mediaController";
await mediaController.checkImages();

View File

@@ -0,0 +1,246 @@
import { type Request, type Response } from "express";
import UserModel, { values } from '../models/userModel';
import MediaModel, { Table, Media } from '../models/mediaModel';
import mediaModel from "../models/mediaModel";
interface omdbRes {
Title: string,
Released: string,
Response: string,
Poster: string,
Type: string,
imdbID: string,
Year: string,
}
function fromStringToTable(value: string): (Table | undefined) {
if (value.localeCompare("games") == 0) return Table.games;
if (value.localeCompare("movies") == 0) return Table.movies;
if (value.localeCompare("series") == 0) return Table.series;
return;
}
async function downloadImage(mData: Media, type: Table) {
// Specify the path where you want to save the image
const outputPath = '/poster/' + type + '/' + mData.code + '.jpg';
// Use Bun's built-in fetch to download the image
const response = await fetch(mData.poster);
// Check if the request was successful
if (!response.ok) {
console.log("fetch image error");
console.log(mData.title);
return;
}
// Convert the response to a blob
const imageBlob = await response.blob();
// Use Bun's write to save the image to a file
await Bun.write('./public/' + outputPath, await imageBlob.arrayBuffer());
MediaModel.updateWebImg(type, mData.code, outputPath);
}
async function createMed(req: Request, res: Response) {
const mediaCode: string = req.body.code;
const omdb_key = UserModel.getValue(values.omdb_key);
if (!omdb_key) {
return res.status(500).json({ message: 'Error when creating media' });
}
try {
const uri = `http://www.omdbapi.com/?i=${mediaCode}&apikey=${omdb_key}`;
const mJson = await fetch(uri);
const mData: omdbRes = await mJson.json();
if (mData.Response == 'False') {
return res.status(404).json({ message: 'wrong code' });
}
const media: Media = {
id: 0,
code: mData.imdbID,
title: mData.Title,
released: mData.Released,
webImg: "",
poster: mData.Poster,
year: mData.Year
};
var tableType = Table.series;
if (mData.Type.localeCompare("movie") == 0) {
tableType = Table.movies;
}
const found = MediaModel.findOne(tableType, mediaCode);
if (found.length != 0) {
res.status(409).json({ message: 'Media already exists' });
await downloadImage(media, tableType);
return;
}
const savedMedia = MediaModel.save(tableType, mData.imdbID, mData.Title, mData.Released, "", mData.Poster, mData.Year);
await downloadImage(media, tableType);
res.status(201).json(media);
} catch (err) {
return res.status(500).json({ message: 'Error when creating media' });
}
}
async function createGame(req: Request, res: Response) {
var gameCode = req.body.code;
const twitch_client_id = UserModel.getValue(values.twitch_client_id);
const twitch_client_secret = UserModel.getValue(values.twitch_client_secret);
if (!twitch_client_id || !twitch_client_secret) {
return res.status(500).json({ message: 'Error when creating game' });
}
try {
const gameFound = MediaModel.findOne(Table.games, gameCode);
if (gameFound) {
return res.status(409).json({ message: 'Game already exists' });
}
const uri = "https://id.twitch.tv/oauth2/token?client_id=" + twitch_client_id + "&client_secret=" + twitch_client_secret + "&grant_type=client_credentials";
var response = await fetch(uri, { method: 'POST' });
const mData = await response.json();
const mheaders: HeadersInit = {
'Accept': 'application/json',
'Client-ID': twitch_client_id,
'Authorization': 'Bearer ' + mData.access_token
}
gameCode = parseInt(gameCode)
response = await fetch(
"https://api.igdb.com/v4/games",
{
method: 'POST',
headers: mheaders,
body: `fields name, first_release_date; where id = ${gameCode};`
}
)
const gameData = await response.json()
if (gameData.length == 0) {
return res.status(404).json({ message: 'wrong code' });
}
const date = new Date(gameData[0].first_release_date * 1000);
const options: Intl.DateTimeFormatOptions = { day: 'numeric', month: 'short', year: 'numeric' }
const dateStr = date.toLocaleDateString(undefined, options);
response = await fetch(
"https://api.igdb.com/v4/covers",
{
method: 'POST',
headers: mheaders,
body: `fields image_id; where game = ${gameCode};`
}
)
const coverData = await response.json()
const game: Media = {
id: 0,
code: gameCode,
title: gameData[0].name,
released: dateStr,
webImg: "",
poster: `https://images.igdb.com/igdb/image/upload/t_cover_big/${coverData[0].image_id}.jpg`,
year: date.getFullYear().toString(),
};
const savedGame = MediaModel.save(Table.games, game.code, game.title, game.released, game.webImg, game.poster, game.year);
await downloadImage(game, Table.games);
return res.status(201).json(game);
} catch (error) {
return res.status(500).json({ message: 'Error when creating game', error: error });
}
}
function list(req: Request, res: Response) {
const mediaTable = fromStringToTable(req.params.mediaType);
if (!mediaTable) {
return res.status(500).json({
message: 'Error when getting media.'
});
}
const media = MediaModel.find(mediaTable);
return res.json(media);
}
async function create(req: Request, res: Response) {
const mediaCode: string = req.body.code;
if (mediaCode.startsWith("t")) {
return await createMed(req, res);
} else {
return await createGame(req, res);
}
}
function remove(req: Request, res: Response) {
const mediaTable = fromStringToTable(req.params.mediaType);
if (!mediaTable) {
return res.status(500).json({
message: 'Error when deleting the media.'
});
}
const code = req.body.code;
try {
const mediaTable = req.baseUrl.includes('movies') ? Table.movies : Table.series;
const media = MediaModel.findOneAndDelete(mediaTable, code);
if (!media) {
return res.status(404).json({ message: 'No such media' });
}
return res.status(204).json();
}
catch (err) {
return res.status(500).json({ message: 'Error when deleting the media.' });
}
}
async function checkImages() {
await checkTableImages(Table.games);
await checkTableImages(Table.movies);
await checkTableImages(Table.series);
}
function delay(time:number) {
return new Promise(resolve => setTimeout(resolve, time));
}
async function checkTableImages(table: Table) {
const list = mediaModel.find(table);
for (const element of list) {
const path = "./public/" + element.webImg;
const f = Bun.file(path);
const exists = await f.exists();
if (!exists){
console.log(element.title);
await downloadImage(element, table);
await delay(1000);
}
}
}
export default {
list,
create,
remove,
checkImages
};

View File

@@ -0,0 +1,50 @@
import { type Request, type Response } from "express";
import UserModel, { values } from '../models/userModel';
export default {
render: function (req: Request, res: Response) {
res.render('user', { keys: UserModel.namesOfValues });
},
create: function (req: Request, res: Response) {
const reqPassword: string = req.body.reqPassword;
if (!reqPassword) return res.render('user', { keys: UserModel.namesOfValues });
const password = UserModel.getValue(values.pass);
// if no password in db save reqPassword
if (!password) {
const affectedRows = UserModel.updateValue("pass", reqPassword);
if (affectedRows > 0) {
return res.redirect('/list');
}
return res.render('user', { keys: UserModel.namesOfValues });
}
// check if passwords equal
if (password != reqPassword) {
return res.render('user', { keys: UserModel.namesOfValues });
}
// update
const name: string = req.body.name;
const value: string = req.body.value;
if (!name || !value) {
return res.render('user', { keys: UserModel.namesOfValues });
}
const affectedRows = UserModel.updateValue(name, value);
if (affectedRows == 0) {
return res.render('user', { keys: UserModel.namesOfValues });
}
return res.redirect('/list');
},
get: function (req: Request, res: Response) {
const usersFound = UserModel.getAll();
return res.status(200).json(usersFound);
},
};

View File

@@ -0,0 +1,15 @@
import { type NextFunction, type Request, type Response } from "express";
import userModel, { values } from 'backend/models/userModel';
function checkAuthenticated(req: Request, res: Response, next: NextFunction) {
const pass = req.body.pass;
const password = userModel.getValue(values.pass);
if (pass && password) {
if (pass == password) {
return next();
}
}
return res.status(500).json({ message: 'Error when getting transactions.' });
}
export default checkAuthenticated;

View File

@@ -0,0 +1,68 @@
import { Database } from "bun:sqlite";
const pool = new Database("mydb.sqlite", { strict: true });
pool.run(`
CREATE TABLE IF NOT EXISTS series (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT NOT NULL,
title TEXT NOT NULL,
released TEXT NOT NULL,
webImg TEXT NOT NULL,
poster TEXT NOT NULL,
year TEXT NOT NULL
);
`);
pool.run(`
CREATE TABLE IF NOT EXISTS movies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT NOT NULL,
title TEXT NOT NULL,
released TEXT NOT NULL,
webImg TEXT NOT NULL,
poster TEXT NOT NULL,
year TEXT NOT NULL
);
`);
pool.run(`
CREATE TABLE IF NOT EXISTS games (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT NOT NULL,
title TEXT NOT NULL,
released TEXT NOT NULL,
webImg TEXT NOT NULL,
poster TEXT NOT NULL,
year TEXT NOT NULL
);
`);
pool.run(`
CREATE TABLE IF NOT EXISTS userData (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
value TEXT NOT NULL
);
`);
function inset_keys() {
class co {
count!: number;
}
const result = pool.query("SELECT count(*) as count FROM userData;").as(co).get();
if(result && result.count >= 4){
return;
}
pool.run(`
INSERT INTO userData (name, value) VALUES ("pass", "");
INSERT INTO userData (name, value) VALUES ("omdb_key", "");
INSERT INTO userData (name, value) VALUES ("twitch_client_id", "");
INSERT INTO userData (name, value) VALUES ("twitch_client_secret", "");
`);
}
inset_keys();
export default pool;

View File

@@ -0,0 +1,84 @@
import pool from 'backend/miscellaneous/db'
export class Media {
id!: number;
code!: string
title!: string;
released!: string;
webImg!: string;
poster!: string;
year!: string;
}
export enum Table {
movies = "movies",
series = "series",
games = "games",
}
function save(table: Table, code: string, title: string, released:string, webImg:string, poster: string, year: string): number {
try {
const sql = "INSERT INTO " + table + " (code, title, released, webImg, poster, year) VALUES (?,?,?,?,?,?)";
const result = pool.query(sql).run(code, title, released, webImg, poster, year);
return result.changes;
}
catch (err) {
console.log(err);
}
return 0;
}
function updateWebImg(table: Table, code: string, webImg: string): number {
try {
const sql = "UPDATE " + table + " SET webImg = ? WHERE code = ?;";
const result = pool.query(sql).run(webImg, code);
return result.changes;
}
catch (err) {
console.log(err);
}
return 0;
}
function findOneAndDelete(table: Table, code: string): number {
try {
const result = pool.query("DELETE FROM " + table + " WHERE code = ?;").run(code);
return result.changes;
}
catch (err) {
console.log(err);
}
return 0;
}
function findOne(table: Table, code: string): Media[] {
try {
const rows = pool.query("SELECT * FROM " + table + " WHERE code = ?;").as(Media).all(code);
return rows;
}
catch (err) {
console.log(err);
}
return [];
}
function find(table: Table): Media[] {
try {
const rows = pool.query("SELECT * FROM " + table + ";").as(Media).all();
return rows;
}
catch (err) {
console.log(err);
}
return [];
}
export default {
save,
updateWebImg,
findOneAndDelete,
findOne,
find
};

View File

@@ -0,0 +1,56 @@
import pool from 'backend/miscellaneous/db'
class UserD {
name?: string;
value?: string;
}
export enum values {
pass = 1,
omdb_key,
twitch_client_id,
twitch_client_secret,
}
const namesOfValues: string[] = ["", "pass", "omdb_key", "twitch_client_id", "twitch_client_secret"];
function getValue(name: values): string | undefined {
try {
const rows = pool.query("SELECT name, value FROM userData where id = ?;").as(UserD).all(name);
if (rows.length > 0)
return rows[0].value;
}
catch (err) {
console.log(err);
}
return;
}
function updateValue(name: string, value: string): number {
try {
const result = pool.query("UPDATE userData SET value = ? WHERE name = ?").run(value, name);
return result.changes;
}
catch (err) {
console.log(err);
}
return 0;
}
function getAll(): UserD[] {
try {
const rows = pool.query("SELECT name, value FROM userData;").as(UserD).all();
return rows;
}
catch (err) {
console.log(err);
}
return [];
}
export default {
getValue,
updateValue,
getAll,
namesOfValues
};

View File

@@ -0,0 +1,12 @@
import express, { type Request, type Response } from "express";
import mediaRouter from './mediaRouter';
const router = express.Router();
router.use('/media', mediaRouter);
router.get('/', function (req: Request, res: Response) {
res.status(200).json({ message: 'API is working' });
});
export default router;

View File

@@ -0,0 +1,13 @@
import express from "express";
import mediaController from '../../controllers/mediaController.js';
import checkAuthenticated from '../../miscellaneous/checkAuthenticated.js';
const router = express.Router();
router.get('/:mediaType', mediaController.list);
router.post('/:mediaType', checkAuthenticated, mediaController.create);
router.delete('/:mediaType', checkAuthenticated, mediaController.remove);
export default router;

10
backend/routes/main.ts Normal file
View File

@@ -0,0 +1,10 @@
import express, { type Request, type Response } from "express";
const router = express.Router();
//import userRouter from './user';
//router.use('/user', userRouter);
export default router;

14
backend/routes/user.ts Normal file
View File

@@ -0,0 +1,14 @@
import express from "express";
import userController from 'backend/controllers/userController';
import checkAuthenticated from 'backend/miscellaneous/checkAuthenticated';
const router = express.Router();
/* GET home page. */
router.get('/', userController.render);
router.post('/', userController.create);
router.put('/', checkAuthenticated, userController.get);
export default router;

69
backend/views/user.hbs Normal file
View File

@@ -0,0 +1,69 @@
<!doctype html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="/images/logo.ico" type="image/x-icon">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
crossorigin="anonymous"></script>
<title>User</title>
</head>
<body>
<div class="container">
<form action="/user" method="post">
<div class="mb-3">
<label class="form-label">Name</label>
<select name="name" class="form-select">
{{#each keys}}
<option value="{{this}}">{{this}}</option>
{{/each}}
</select>
</div>
<div class="mb-3">
<label class="form-label">Value</label>
<input type="text" class="form-control" name="value">
</div>
<div class="mb-3">
<label class="form-label">Password</label>
<input type="password" class="form-control" name="reqPassword" id="password">
</div>
<button type="submit" class="btn btn-primary">Add</button>
</form>
<br>
<div class="btn btn-primary" id="get" onclick="getUser()"> Get</div>
<br>
<div id="text"></div>
<script>
async function getUser() {
const password = document.getElementById('password').value;
const response = await fetch('/user', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ "pass": password }),
})
const data = await response.json();
document.getElementById('text').textContent = JSON.stringify(data, null, 2);
}
</script>
</div>
</body>
</html>