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

3
.gitignore vendored
View File

@ -67,3 +67,6 @@ public/assets/build/
package-lock.json
bun.lockb
bundle.js
mydb.sqlite
public/poster/
output/

6
app.ts
View File

@ -1,20 +1,18 @@
import express from "express";
import path from 'path'
import 'dotenv/config'
const hostname = '127.0.0.1';
const httpPort = 4080;
const app = express();
app.set('views', path.join(__dirname, 'views'));
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(path.join(__dirname, 'public')));
app.use(express.static('public'));
import mainRouter from './routes/main';
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 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) {
if (value.localeCompare("games") == 0) return Table.games;
@ -10,10 +17,31 @@ function fromStringToTable(value: string): (Table | undefined) {
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) {
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) {
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 mJson = await fetch(uri);
const mData = await mJson.json();
const mData: omdbRes = await mJson.json();
if (mData.Response == 'False') {
return res.status(404).json({ message: 'wrong code' });
}
const seriesFound = await MediaModel.findOne(Table.series, cleanCode);
if (seriesFound.length != 0) {
await MediaModel.updateWebImg(Table.series, cleanCode, mData.Poster);
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,
const media: Media = {
id:0,
code: cleanCode,
title: mData.Title,
released: mData.Released,
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) {
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) {
var gameCode = req.body.code;
const twitch_client_id = await UserModel.getValue(values.twitch_client_id);
const twitch_client_secret = await UserModel.getValue(values.twitch_client_secret);
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 = await MediaModel.findOne(Table.games, gameCode);
const gameFound = MediaModel.findOne(Table.games, gameCode);
if (gameFound) {
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 game = {
const game: Media = {
id: 0,
code: gameCode,
title: gameData[0].name,
released: dateStr,
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);
} catch (error) {
@ -150,16 +184,8 @@ export default {
});
}
MediaModel.find(mediaTable)
.then(media => {
const media = MediaModel.find(mediaTable);
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) {
@ -174,7 +200,7 @@ export default {
/**
* mediaController.delete()
*/
remove: async function (req: Request, res: Response) {
remove: function (req: Request, res: Response) {
const mediaTable = fromStringToTable(req.params.mediaType);
if (!mediaTable) {
return res.status(500).json({
@ -186,7 +212,7 @@ export default {
try {
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) {
return res.status(404).json({ message: 'No such media' });
}

View File

@ -3,20 +3,20 @@ import UserModel, { values } from '../models/userModel';
export default {
render: async function (req: Request, res: Response) {
render: function (req: Request, res: Response) {
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;
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 (!password) {
const affectedRows = await UserModel.updateValue("pass", reqPassword);
const affectedRows = UserModel.updateValue("pass", reqPassword);
if (affectedRows > 0) {
return res.redirect('/list');
}
@ -35,7 +35,7 @@ export default {
return res.render('user', { keys: UserModel.namesOfValues });
}
const affectedRows = await UserModel.updateValue(name, value);
const affectedRows = UserModel.updateValue(name, value);
if (affectedRows == 0) {
return res.render('user', { keys: UserModel.namesOfValues });
}
@ -43,8 +43,8 @@ export default {
return res.redirect('/list');
},
get: async function (req: Request, res: Response) {
const usersFound = await UserModel.getAll();
get: function (req: Request, res: Response) {
const usersFound = UserModel.getAll();
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_app = Bun.spawn(["bun", "--watch", "./app.ts"]);

View File

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

View File

@ -1,9 +1,9 @@
import { type NextFunction, type Request, type Response } from "express";
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 password = await userModel.getValue(values.pass);
const password = userModel.getValue(values.pass);
if (pass && password) {
if (pass == password) {
return next();

View File

@ -1,34 +1,65 @@
import mysql, { type PoolOptions } from 'mysql2/promise'
import { Database } from "bun:sqlite";
const poolOptions: PoolOptions = {
host: "",
port: 0,
user: "",
password: "",
database: ""
const pool = new Database("mydb.sqlite", { strict: true });
pool.exec(`
CREATE TABLE IF NOT EXISTS series (
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 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;
}
if (process.env.DBIP) {
poolOptions.host = process.env.DBIP;
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.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);
inset_keys();
export default pool;

View File

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

View File

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

View File

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

View File

@ -1,5 +1,4 @@
import express, { type Request, type Response } from "express";
import checkAuthenticated from 'miscellaneous/checkAuthenticated';
import mediaRouter from 'routes/api/mediaRouter';
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 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=''