Move to sqlite

This commit is contained in:
Nikola Petrov 2025-07-16 19:17:34 +02:00
parent 9ff76bd210
commit eef2646054
17 changed files with 182 additions and 217 deletions

5
.gitignore vendored
View File

@ -66,4 +66,7 @@ public/assets/build/
.vscode/ .vscode/
package-lock.json package-lock.json
bun.lockb bun.lockb
bundle.js bundle.js
mydb.sqlite
public/poster/
output/

6
app.ts
View File

@ -1,20 +1,18 @@
import express from "express"; import express from "express";
import path from 'path'
import 'dotenv/config'
const hostname = '127.0.0.1'; const hostname = '127.0.0.1';
const httpPort = 4080; const httpPort = 4080;
const app = express(); const app = express();
app.set('views', path.join(__dirname, 'views')); app.set('views', 'views');
app.set('view engine', 'hbs'); app.set('view engine', 'hbs');
// import morgan from 'morgan' // import morgan from 'morgan'
// app.use(morgan('dev')); // app.use(morgan('dev'));
app.use(express.json()); app.use(express.json());
app.use(express.urlencoded({ extended: false })); app.use(express.urlencoded({ extended: false }));
app.use(express.static(path.join(__dirname, 'public'))); app.use(express.static('public'));
import mainRouter from './routes/main'; import mainRouter from './routes/main';
import apiRouter from './routes/api/apiRouter'; import apiRouter from './routes/api/apiRouter';

6
build.sh Executable file
View File

@ -0,0 +1,6 @@
mkdir output
bun build ./app.ts --outfile=output/app.js --target=bun --minify
bun build ./frontend/list/list.tsx --outfile=public/assets/build/list.js --minify
cp -r views/ output/
cp -r public/ output/

View File

@ -1,20 +0,0 @@
async function build() {
const minify = {
whitespace: true,
syntax: true,
identifiers: true,
}
const sa = await Bun.build({
entrypoints: ["./frontend/list/list"],
outdir: "./public/assets/build/",
minify,
})
console.log(sa);
}
build();
export default build;

View File

@ -1,7 +1,14 @@
import { type Request, type Response } from "express"; import { type Request, type Response } from "express";
import UserModel, { values } from '../models/userModel'; import UserModel, { values } from '../models/userModel';
import MediaModel, { Table } from '../models/mediaModel'; import MediaModel, { Table, Media } from '../models/mediaModel';
interface omdbRes{
Title: string,
Released: string,
Response: string,
Poster: string,
Type: string
}
function fromStringToTable(value: string): (Table | undefined) { function fromStringToTable(value: string): (Table | undefined) {
if (value.localeCompare("games") == 0) return Table.games; if (value.localeCompare("games") == 0) return Table.games;
@ -10,10 +17,31 @@ function fromStringToTable(value: string): (Table | undefined) {
return; 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.webImg);
// Check if the request was successful
if (!response.ok) {
console.log("fetch image error");
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) { async function createMed(req: Request, res: Response) {
const mediaCode: string = req.body.code; const mediaCode: string = req.body.code;
const omdb_key = await UserModel.getValue(values.omdb_key); const omdb_key = UserModel.getValue(values.omdb_key);
if (!omdb_key) { if (!omdb_key) {
return res.status(500).json({ message: 'Error when creating media' }); return res.status(500).json({ message: 'Error when creating media' });
@ -27,39 +55,44 @@ async function createMed(req: Request, res: Response) {
const uri = `http://www.omdbapi.com/?i=${mediaCode}&apikey=${omdb_key}`; const uri = `http://www.omdbapi.com/?i=${mediaCode}&apikey=${omdb_key}`;
const mJson = await fetch(uri); const mJson = await fetch(uri);
const mData = await mJson.json(); const mData: omdbRes = await mJson.json();
if (mData.Response == 'False') { if (mData.Response == 'False') {
return res.status(404).json({ message: 'wrong code' }); return res.status(404).json({ message: 'wrong code' });
} }
const seriesFound = await MediaModel.findOne(Table.series, cleanCode); const media: Media = {
if (seriesFound.length != 0) { id:0,
await MediaModel.updateWebImg(Table.series, cleanCode, mData.Poster); code: cleanCode,
return res.status(409).json({ message: 'Media already exists' });
}
const moviesFound = await MediaModel.findOne(Table.movies, cleanCode);
if (moviesFound.length != 0) {
await MediaModel.updateWebImg(Table.movies, cleanCode, mData.Poster);
return res.status(409).json({ message: 'Media already exists' });
}
if (mData.Type.localeCompare("movie") == 0) {
const savedMedia = await MediaModel.save(Table.movies, cleanCode, mData.Title, mData.Released, mData.Poster);
}
else if (mData.Type.localeCompare("series") == 0) {
const savedMedia = await MediaModel.save(Table.series, cleanCode, mData.Title, mData.Released, mData.Poster);
}
const media = {
code: mediaCode,
title: mData.Title, title: mData.Title,
released: mData.Released, released: mData.Released,
webImg: mData.Poster, webImg: mData.Poster,
}; };
return res.status(201).json(media); const seriesFound = MediaModel.findOne(Table.series, cleanCode);
if (seriesFound.length != 0) {
res.status(409).json({ message: 'Media already exists' });
await downloadImage(media, Table.series);
return;
}
const moviesFound = MediaModel.findOne(Table.movies, cleanCode);
if (moviesFound.length != 0) {
res.status(409).json({ message: 'Media already exists' });
await downloadImage(media, Table.movies);
return;
}
if (mData.Type.localeCompare("movie") == 0) {
const savedMedia = MediaModel.save(Table.movies, cleanCode, mData.Title, mData.Released, mData.Poster);
await downloadImage(media, Table.movies);
}
else if (mData.Type.localeCompare("series") == 0) {
const savedMedia = MediaModel.save(Table.series, cleanCode, mData.Title, mData.Released, mData.Poster);
await downloadImage(media, Table.series);
}
res.status(201).json(media);
} catch (err) { } catch (err) {
return res.status(500).json({ message: 'Error when creating media' }); return res.status(500).json({ message: 'Error when creating media' });
} }
@ -68,15 +101,15 @@ async function createMed(req: Request, res: Response) {
async function createGame(req: Request, res: Response) { async function createGame(req: Request, res: Response) {
var gameCode = req.body.code; var gameCode = req.body.code;
const twitch_client_id = await UserModel.getValue(values.twitch_client_id); const twitch_client_id = UserModel.getValue(values.twitch_client_id);
const twitch_client_secret = await UserModel.getValue(values.twitch_client_secret); const twitch_client_secret = UserModel.getValue(values.twitch_client_secret);
if (!twitch_client_id || !twitch_client_secret) { if (!twitch_client_id || !twitch_client_secret) {
return res.status(500).json({ message: 'Error when creating game' }); return res.status(500).json({ message: 'Error when creating game' });
} }
try { try {
const gameFound = await MediaModel.findOne(Table.games, gameCode); const gameFound = MediaModel.findOne(Table.games, gameCode);
if (gameFound) { if (gameFound) {
return res.status(409).json({ message: 'Game already exists' }); return res.status(409).json({ message: 'Game already exists' });
} }
@ -121,14 +154,15 @@ async function createGame(req: Request, res: Response) {
) )
const coverData = await response.json() const coverData = await response.json()
const game = { const game: Media = {
id: 0,
code: gameCode, code: gameCode,
title: gameData[0].name, title: gameData[0].name,
released: dateStr, released: dateStr,
webImg: `https://images.igdb.com/igdb/image/upload/t_cover_big/${coverData[0].image_id}.jpg`, webImg: `https://images.igdb.com/igdb/image/upload/t_cover_big/${coverData[0].image_id}.jpg`,
}; };
const savedGame = await MediaModel.save(Table.games, game.code, game.title, game.released, game.webImg); const savedGame = MediaModel.save(Table.games, game.code, game.title, game.released, game.webImg);
return res.status(201).json(game); return res.status(201).json(game);
} catch (error) { } catch (error) {
@ -150,16 +184,8 @@ export default {
}); });
} }
MediaModel.find(mediaTable) const media = MediaModel.find(mediaTable);
.then(media => { return res.json(media);
return res.json(media);
})
.catch(err => {
return res.status(500).json({
message: 'Error when getting media.',
error: err
});
});
}, },
create: async function (req: Request, res: Response) { create: async function (req: Request, res: Response) {
@ -174,7 +200,7 @@ export default {
/** /**
* mediaController.delete() * mediaController.delete()
*/ */
remove: async function (req: Request, res: Response) { remove: function (req: Request, res: Response) {
const mediaTable = fromStringToTable(req.params.mediaType); const mediaTable = fromStringToTable(req.params.mediaType);
if (!mediaTable) { if (!mediaTable) {
return res.status(500).json({ return res.status(500).json({
@ -186,7 +212,7 @@ export default {
try { try {
const mediaTable = req.baseUrl.includes('movies') ? Table.movies : Table.series; const mediaTable = req.baseUrl.includes('movies') ? Table.movies : Table.series;
const media = await MediaModel.findOneAndDelete(mediaTable, code); const media = MediaModel.findOneAndDelete(mediaTable, code);
if (!media) { if (!media) {
return res.status(404).json({ message: 'No such media' }); return res.status(404).json({ message: 'No such media' });
} }

View File

@ -3,20 +3,20 @@ import UserModel, { values } from '../models/userModel';
export default { export default {
render: async function (req: Request, res: Response) { render: function (req: Request, res: Response) {
res.render('user', { keys: UserModel.namesOfValues }); res.render('user', { keys: UserModel.namesOfValues });
}, },
create: async function (req: Request, res: Response) { create: function (req: Request, res: Response) {
const reqPassword: string = req.body.reqPassword; const reqPassword: string = req.body.reqPassword;
if (!reqPassword) return res.render('user', { keys: UserModel.namesOfValues }); if (!reqPassword) return res.render('user', { keys: UserModel.namesOfValues });
const password = await UserModel.getValue(values.pass); const password = UserModel.getValue(values.pass);
// if no password in db save reqPassword // if no password in db save reqPassword
if (!password) { if (!password) {
const affectedRows = await UserModel.updateValue("pass", reqPassword); const affectedRows = UserModel.updateValue("pass", reqPassword);
if (affectedRows > 0) { if (affectedRows > 0) {
return res.redirect('/list'); return res.redirect('/list');
} }
@ -35,7 +35,7 @@ export default {
return res.render('user', { keys: UserModel.namesOfValues }); return res.render('user', { keys: UserModel.namesOfValues });
} }
const affectedRows = await UserModel.updateValue(name, value); const affectedRows = UserModel.updateValue(name, value);
if (affectedRows == 0) { if (affectedRows == 0) {
return res.render('user', { keys: UserModel.namesOfValues }); return res.render('user', { keys: UserModel.namesOfValues });
} }
@ -43,8 +43,8 @@ export default {
return res.redirect('/list'); return res.redirect('/list');
}, },
get: async function (req: Request, res: Response) { get: function (req: Request, res: Response) {
const usersFound = await UserModel.getAll(); const usersFound = UserModel.getAll();
return res.status(200).json(usersFound); return res.status(200).json(usersFound);
}, },
}; };

1
dev.ts
View File

@ -1,5 +1,4 @@
const build_cash = Bun.spawn(["bun", "build", "./frontend/cash/cash.tsx", "--outfile=public/assets/build/cash/cash.js", "--watch"]);
const build_list = Bun.spawn(["bun", "build", "./frontend/list/list.tsx", "--outfile=public/assets/build/list/list.js", "--watch"]); const build_list = Bun.spawn(["bun", "build", "./frontend/list/list.tsx", "--outfile=public/assets/build/list/list.js", "--watch"]);
const build_app = Bun.spawn(["bun", "--watch", "./app.ts"]); const build_app = Bun.spawn(["bun", "--watch", "./app.ts"]);

View File

@ -244,6 +244,7 @@ function removeMedia(evt: Event) {
function onImgError(evt: Event) { function onImgError(evt: Event) {
const imgT = evt.target as HTMLImageElement; const imgT = evt.target as HTMLImageElement;
imgT.src = "/images/no_poster.jpg"; imgT.src = "/images/no_poster.jpg";
console.log(imgT.parentElement?.parentElement?.id);
} }
function renderMedias(unsorted_movies: Array<Movie>) { function renderMedias(unsorted_movies: Array<Movie>) {

View File

@ -1,9 +1,9 @@
import { type NextFunction, type Request, type Response } from "express"; import { type NextFunction, type Request, type Response } from "express";
import userModel, { values } from 'models/userModel'; import userModel, { values } from 'models/userModel';
async function checkAuthenticated(req: Request, res: Response, next: NextFunction) { function checkAuthenticated(req: Request, res: Response, next: NextFunction) {
const pass = req.body.pass; const pass = req.body.pass;
const password = await userModel.getValue(values.pass); const password = userModel.getValue(values.pass);
if (pass && password) { if (pass && password) {
if (pass == password) { if (pass == password) {
return next(); return next();

View File

@ -1,34 +1,65 @@
import mysql, { type PoolOptions } from 'mysql2/promise' import { Database } from "bun:sqlite";
const poolOptions: PoolOptions = { const pool = new Database("mydb.sqlite", { strict: true });
host: "",
port: 0, pool.exec(`
user: "", CREATE TABLE IF NOT EXISTS series (
password: "", id INTEGER PRIMARY KEY AUTOINCREMENT,
database: "" code INT NOT NULL,
title TEXT NOT NULL,
released TEXT NOT NULL,
webImg TEXT NOT NULL,
UNIQUE (code)
);
`);
pool.exec(`
CREATE TABLE IF NOT EXISTS movies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code INT NOT NULL,
title TEXT NOT NULL,
released TEXT NOT NULL,
webImg TEXT NOT NULL,
UNIQUE (code)
);
`);
pool.exec(`
CREATE TABLE IF NOT EXISTS games (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code INT NOT NULL,
title TEXT NOT NULL,
released TEXT NOT NULL,
webImg TEXT NOT NULL,
UNIQUE (code)
);
`);
pool.exec(`
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.exec(`
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", "");
`);
} }
if (process.env.DBIP) { inset_keys();
poolOptions.host = process.env.DBIP;
}
if (process.env.DBPort) {
poolOptions.port = parseInt(process.env.DBPort);
}
if (process.env.DBUser) {
poolOptions.user = process.env.DBUser;
}
if (process.env.DBPassword) {
poolOptions.password = process.env.DBPassword;
}
if (process.env.DBDatabase) {
poolOptions.database = process.env.DBDatabase;
}
const pool = mysql.createPool(poolOptions);
export default pool; export default pool;

View File

@ -1,13 +1,12 @@
import { type ResultSetHeader, type RowDataPacket, type QueryOptions } from "mysql2"
import pool from 'miscellaneous/db' import pool from 'miscellaneous/db'
interface Media extends RowDataPacket { export class Media {
id?: number; id!: number;
code?: number; code!: number;
title?: string; title!: string;
released?: string; released!: string;
webImg?: string; webImg!: string;
} }
export enum Table { export enum Table {
@ -16,14 +15,12 @@ export enum Table {
games = "games", games = "games",
} }
async function save(table: Table, code: number, title: string, released: string, webImg: string): Promise<number> { function save(table: Table, code: number, title: string, released: string, webImg: string): number {
try { try {
const options: QueryOptions = { const sql = "INSERT INTO " + table + " (code, title, released, webImg) VALUES (?,?,?,?)";
sql: "INSERT INTO " + table + " (code, title, released, webImg) VALUES (?,?,?,?)",
values: [code, title, released, webImg] const result = pool.query(sql).run(code, title, released, webImg);
}; return result.changes;
const [result, fields] = await pool.query<ResultSetHeader>(options);
return result.affectedRows;
} }
catch (err) { catch (err) {
console.log(err); console.log(err);
@ -31,14 +28,11 @@ async function save(table: Table, code: number, title: string, released: string,
return 0; return 0;
} }
async function updateWebImg(table: Table, code: number, webImg: string): Promise<number> { function updateWebImg(table: Table, code: number, webImg: string): number {
try { try {
const options: QueryOptions = { const sql = "UPDATE " + table + " SET webImg = ? WHERE code = ?;";
sql: "UPDATE " + table + "SET webImg = ? WHERE code = ?;", const result = pool.query(sql).run(webImg, code);
values: [webImg, code] return result.changes;
};
const [result, fields] = await pool.query<ResultSetHeader>(options);
return result.affectedRows;
} }
catch (err) { catch (err) {
console.log(err); console.log(err);
@ -46,10 +40,10 @@ async function updateWebImg(table: Table, code: number, webImg: string): Promise
return 0; return 0;
} }
async function findOneAndDelete(table: Table, code: number): Promise<number> { function findOneAndDelete(table: Table, code: number): number {
try { try {
const [result, fields] = await pool.query<ResultSetHeader>("DELETE FROM " + table + " WHERE code = ?;", [code]); const result = pool.query("DELETE FROM " + table + " WHERE code = ?;").run(code);
return result.affectedRows; return result.changes;
} }
catch (err) { catch (err) {
console.log(err); console.log(err);
@ -57,9 +51,9 @@ async function findOneAndDelete(table: Table, code: number): Promise<number> {
return 0; return 0;
} }
async function findOne(table: Table, code: number): Promise<Media[]> { function findOne(table: Table, code: number): Media[] {
try { try {
const [rows, fields] = await pool.query<Media[]>("SELECT * FROM " + table + " WHERE code = ?;", [code]); const rows = pool.query("SELECT * FROM " + table + " WHERE code = ?;").as(Media).all(code);
return rows; return rows;
} }
catch (err) { catch (err) {
@ -68,9 +62,9 @@ async function findOne(table: Table, code: number): Promise<Media[]> {
return []; return [];
} }
async function find(table: Table): Promise<Media[]> { function find(table: Table): Media[] {
try { try {
const [rows, fields] = await pool.query<Media[]>("SELECT * FROM " + table + ";"); const rows = pool.query("SELECT * FROM " + table + ";").as(Media).all();
return rows; return rows;
} }
catch (err) { catch (err) {

View File

@ -1,7 +1,6 @@
import { type ResultSetHeader, type RowDataPacket } from "mysql2"
import pool from 'miscellaneous/db' import pool from 'miscellaneous/db'
interface UserD extends RowDataPacket { class UserD {
name?: string; name?: string;
value?: string; value?: string;
} }
@ -15,9 +14,9 @@ export enum values {
const namesOfValues: string[] = ["", "pass", "omdb_key", "twitch_client_id", "twitch_client_secret"]; const namesOfValues: string[] = ["", "pass", "omdb_key", "twitch_client_id", "twitch_client_secret"];
async function getValue(name: values): Promise<string | undefined> { function getValue(name: values): string | undefined {
try { try {
const [rows, fields] = await pool.query<UserD[]>("SELECT name, value FROM userData where id = ?;", [name]); const rows = pool.query("SELECT name, value FROM userData where id = ?;").as(UserD).all(name);
if (rows.length > 0) if (rows.length > 0)
return rows[0].value; return rows[0].value;
} }
@ -27,10 +26,10 @@ async function getValue(name: values): Promise<string | undefined> {
return; return;
} }
async function updateValue(name: string, value: string): Promise<number> { function updateValue(name: string, value: string): number {
try { try {
const [result, fields] = await pool.query<ResultSetHeader>("UPDATE userData SET value = ? WHERE name = ?", [value, name]); const result = pool.query("UPDATE userData SET value = ? WHERE name = ?").run(value, name);
return result.affectedRows; return result.changes;
} }
catch (err) { catch (err) {
console.log(err); console.log(err);
@ -38,9 +37,9 @@ async function updateValue(name: string, value: string): Promise<number> {
return 0; return 0;
} }
async function getAll(): Promise<UserD[]> { function getAll(): UserD[] {
try { try {
const [rows, fields] = await pool.query<UserD[]>("SELECT name, value FROM userData;"); const rows = pool.query("SELECT name, value FROM userData;").as(UserD).all();
return rows; return rows;
} }
catch (err) { catch (err) {

View File

@ -2,19 +2,12 @@
"name": "web", "name": "web",
"version": "0.0.0", "version": "0.0.0",
"private": true, "private": true,
"scripts": {
"start": "bun ./app.ts",
"build_app": "bun build ./app.ts --outfile=bundle.js --target=bun"
},
"dependencies": { "dependencies": {
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/morgan": "^1.9.9", "@types/morgan": "^1.9.9",
"dotenv": "^16.4.5",
"express": "^4.18.2", "express": "^4.18.2",
"hbs": "^4.2.0", "hbs": "^4.2.0",
"morgan": "~1.9.1", "morgan": "~1.9.1",
"mysql2": "^3.10.3",
"chart.js": "^4.4.1",
"bun-types": "^1.0.23", "bun-types": "^1.0.23",
"typescript": "^5.3.3" "typescript": "^5.3.3"
} }

View File

@ -1,5 +1,4 @@
import express, { type Request, type Response } from "express"; import express, { type Request, type Response } from "express";
import checkAuthenticated from 'miscellaneous/checkAuthenticated';
import mediaRouter from 'routes/api/mediaRouter'; import mediaRouter from 'routes/api/mediaRouter';
const router = express.Router(); const router = express.Router();

View File

@ -1,4 +1,4 @@
import express, { type Request, type Response } from "express"; import express from "express";
import userController from 'controllers/userController'; import userController from 'controllers/userController';
import checkAuthenticated from 'miscellaneous/checkAuthenticated'; import checkAuthenticated from 'miscellaneous/checkAuthenticated';

View File

@ -1,59 +0,0 @@
CREATE TABLE series (
id INT NOT NULL AUTO_INCREMENT,
code INT NOT NULL,
title TEXT NOT NULL,
released TEXT NOT NULL,
webImg TEXT NOT NULL,
PRIMARY KEY (id),
UNIQUE code (code)
) ENGINE = InnoDB;
CREATE TABLE movies (
id INT NOT NULL AUTO_INCREMENT,
code INT NOT NULL,
title TEXT NOT NULL,
released TEXT NOT NULL,
webImg TEXT NOT NULL,
PRIMARY KEY (id),
UNIQUE code (code)
) ENGINE = InnoDB;
CREATE TABLE games (
id INT NOT NULL AUTO_INCREMENT,
code INT NOT NULL,
title TEXT NOT NULL,
released TEXT NOT NULL,
webImg TEXT NOT NULL,
PRIMARY KEY (id),
UNIQUE code (code)
) ENGINE = InnoDB;
CREATE TABLE userData (
id INT NOT NULL AUTO_INCREMENT,
name TEXT NOT NULL,
value TEXT NOT NULL,
PRIMARY KEY (id),
) ENGINE = InnoDB;
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", "");
CREATE TABLE bankCardTransaction (
id INT NOT NULL AUTO_INCREMENT,
day INT NOT NULL,
month INT NOT NULL,
year INT NOT NULL,
amount INT NOT NULL,
type INT NOT NULL,
raw TEXT NOT NULL,
company TEXT NOT NULL,
PRIMARY KEY (id),
INDEX date(day, month, year),
INDEX type (type)
) ENGINE = InnoDB;

View File

@ -1,5 +0,0 @@
DBIP=''
DBPort=
DBUser=''
DBPassword=''
DBDatabase=''