Initial commit
This commit is contained in:
commit
3dc1cf3f02
|
@ -0,0 +1,2 @@
|
||||||
|
out/
|
||||||
|
db/
|
|
@ -0,0 +1,3 @@
|
||||||
|
db/
|
||||||
|
.env
|
||||||
|
node_modules/
|
|
@ -0,0 +1,20 @@
|
||||||
|
FROM node:20-alpine3.19
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN npm install -g typescript
|
||||||
|
|
||||||
|
RUN tsc
|
||||||
|
|
||||||
|
# WORKDIR /usr/src/app/out
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# CMD [ "node", "--env-file=../.env", "." ]
|
||||||
|
CMD [ "sh", "./run.sh" ]
|
|
@ -0,0 +1,31 @@
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
kawaiilingo-api:
|
||||||
|
hostname: kawaiilingo-api
|
||||||
|
image: aelita4/kawaiilingo-api
|
||||||
|
ports:
|
||||||
|
- "3002:3000"
|
||||||
|
depends_on:
|
||||||
|
- kawaiilingo-db
|
||||||
|
networks:
|
||||||
|
- kawaiilingo-network
|
||||||
|
- proxy
|
||||||
|
restart: always
|
||||||
|
kawaiilingo-db:
|
||||||
|
image: mysql
|
||||||
|
environment:
|
||||||
|
MYSQL_ROOT_PASSWORD: root
|
||||||
|
MYSQL_DATABASE: kawaiilingo
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
hostname: kawaiilingo-db
|
||||||
|
networks:
|
||||||
|
- kawaiilingo-network
|
||||||
|
volumes:
|
||||||
|
- ./db:/var/lib/mysql
|
||||||
|
networks:
|
||||||
|
kawaiilingo-network:
|
||||||
|
external: false
|
||||||
|
proxy:
|
||||||
|
external:
|
||||||
|
name: proxy-network
|
|
@ -0,0 +1,294 @@
|
||||||
|
-- phpMyAdmin SQL Dump
|
||||||
|
-- version 5.2.1
|
||||||
|
-- https://www.phpmyadmin.net/
|
||||||
|
--
|
||||||
|
-- Host: 127.0.0.1
|
||||||
|
-- Generation Time: Jul 02, 2024 at 04:46 PM
|
||||||
|
-- Server version: 10.4.32-MariaDB
|
||||||
|
-- PHP Version: 8.2.12
|
||||||
|
|
||||||
|
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
|
||||||
|
START TRANSACTION;
|
||||||
|
SET time_zone = "+00:00";
|
||||||
|
|
||||||
|
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||||
|
/*!40101 SET NAMES utf8mb4 */;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Database: `kawaiilingo`
|
||||||
|
--
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `categories`
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE `categories` (
|
||||||
|
`categoryId` int(11) NOT NULL,
|
||||||
|
`englishName` varchar(255) DEFAULT NULL,
|
||||||
|
`polishName` varchar(255) DEFAULT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `categories`
|
||||||
|
--
|
||||||
|
|
||||||
|
INSERT INTO `categories` (`categoryId`, `englishName`, `polishName`) VALUES
|
||||||
|
(1, 'numbers', 'Liczby'),
|
||||||
|
(2, 'weekDays', 'Dni tygodnia'),
|
||||||
|
(3, 'colors', 'Kolory'),
|
||||||
|
(4, 'weather', 'Pogoda'),
|
||||||
|
(5, 'food', 'Jedzenie'),
|
||||||
|
(6, 'family', 'Rodzina'),
|
||||||
|
(7, 'months', 'Miesiące'),
|
||||||
|
(8, 'animals', 'Zwierzęta');
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `learnedwords`
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE `learnedwords` (
|
||||||
|
`learnedID` int(11) NOT NULL,
|
||||||
|
`userID` int(11) NOT NULL,
|
||||||
|
`wordID` int(11) NOT NULL,
|
||||||
|
`percentage` int(11) NOT NULL DEFAULT 0
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `scores`
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE `scores` (
|
||||||
|
`scoreId` int(11) NOT NULL,
|
||||||
|
`userId` int(11) NOT NULL,
|
||||||
|
`time` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`xp` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`mistakes` int(11) NOT NULL DEFAULT 0
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `users`
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE `users` (
|
||||||
|
`userId` int(10) NOT NULL,
|
||||||
|
`username` varchar(255) NOT NULL,
|
||||||
|
`password` varchar(255) NOT NULL,
|
||||||
|
`email` varchar(255) NOT NULL,
|
||||||
|
`xp` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`level` int(11) NOT NULL DEFAULT 0,
|
||||||
|
`dateJoined` datetime NOT NULL DEFAULT current_timestamp()
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
-- --------------------------------------------------------
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Table structure for table `words`
|
||||||
|
--
|
||||||
|
|
||||||
|
CREATE TABLE `words` (
|
||||||
|
`wordID` int(11) NOT NULL,
|
||||||
|
`polishWord` varchar(255) NOT NULL,
|
||||||
|
`japaneseWord` varchar(255) NOT NULL,
|
||||||
|
`romaji` varchar(255) NOT NULL,
|
||||||
|
`categoryId` int(11) NOT NULL
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Dumping data for table `words`
|
||||||
|
--
|
||||||
|
|
||||||
|
INSERT INTO `words` (`wordID`, `polishWord`, `japaneseWord`, `romaji`, `categoryId`) VALUES
|
||||||
|
(6, 'zero', 'ぜろ', 'zero', 1),
|
||||||
|
(7, 'jeden', 'いち', 'ichi', 1),
|
||||||
|
(8, 'dwa', 'に', 'ni', 1),
|
||||||
|
(9, 'trzy', 'さん', 'san', 1),
|
||||||
|
(10, 'cztery', 'よん|し', 'yon|shi', 1),
|
||||||
|
(12, 'pięć', 'ご', 'go', 1),
|
||||||
|
(13, 'sześć', 'ろく', 'roku', 1),
|
||||||
|
(14, 'siedem', 'なな|しち', 'nana|shichi', 1),
|
||||||
|
(16, 'osiem', 'はち', 'hachi', 1),
|
||||||
|
(17, 'dziewięć', 'きゅう', 'kyuu', 1),
|
||||||
|
(18, 'dziesięć', 'じゅう', 'juu', 1),
|
||||||
|
(19, 'sto', 'ひゃく', 'hyaku', 1),
|
||||||
|
(20, 'tysiąc', 'せん', 'sen', 1),
|
||||||
|
(21, 'poniedziałek', 'げつようび', 'getsuyoubi', 2),
|
||||||
|
(22, 'wtorek', 'かようび', 'kayoubi', 2),
|
||||||
|
(23, 'środa', 'すいようび', 'suiyoubi', 2),
|
||||||
|
(24, 'czwartek', 'もくようび', 'mokuyoubi', 2),
|
||||||
|
(25, 'piątek', 'きんようび', 'kinyoubi', 2),
|
||||||
|
(26, 'sobota', 'どようび\r\n', 'doyoubi', 2),
|
||||||
|
(27, 'niedziela', 'にちようび', 'nichiyoubi', 2),
|
||||||
|
(28, 'kolor', 'いろ', 'iro', 3),
|
||||||
|
(29, 'czarny', 'くろ', 'kuro', 3),
|
||||||
|
(30, 'czerwony', 'あか', 'aka', 3),
|
||||||
|
(31, 'niebieski', 'あお', 'ao', 3),
|
||||||
|
(32, 'fieletowy', 'むらさき', 'murasaki', 3),
|
||||||
|
(33, 'biały', 'しろ', 'shiro', 3),
|
||||||
|
(34, 'zielony', 'みどり', 'midori', 3),
|
||||||
|
(35, 'pomarańczowy', 'おれんじ', 'orenji', 3),
|
||||||
|
(36, 'żółty', 'きいろ', 'kiiro', 3),
|
||||||
|
(37, 'brązowy', 'ちゃいろ', 'chairo', 3),
|
||||||
|
(38, 'pogoda', 'てんき', 'Tenki', 4),
|
||||||
|
(39, 'słonecznie', 'はれ\r\n\r\n', 'hare', 4),
|
||||||
|
(40, 'zachmurzenie', 'くもり', 'kumori', 4),
|
||||||
|
(41, 'deszcz', 'あめ', 'ame', 4),
|
||||||
|
(42, 'śnieg', 'ゆき', 'yuki', 4),
|
||||||
|
(43, 'gorąco', 'あつい', 'atsui', 4),
|
||||||
|
(44, 'zimno', 'さむい', 'samui', 4),
|
||||||
|
(45, 'ciepło', 'あたたかい', 'atatakai', 4),
|
||||||
|
(46, 'chłodno', 'すずしい', 'suzushii', 4),
|
||||||
|
(47, 'burza', 'らあいう', 'raiu', 4),
|
||||||
|
(48, 'ryż', 'ごはん', 'gohan', 5),
|
||||||
|
(49, 'zielona herbata', 'おちゃ', 'ocha', 5),
|
||||||
|
(50, 'czarna herbata', 'こうちゃ', 'koucha', 5),
|
||||||
|
(51, 'chleb', 'ぱん', 'pan', 5),
|
||||||
|
(52, 'mięso', 'にく', 'niku', 5),
|
||||||
|
(53, 'warzywa', 'やさい', 'yasai', 5),
|
||||||
|
(54, 'jajko', 'たまご', 'tamago', 5),
|
||||||
|
(55, 'ryba', 'さかな', 'sakana', 5),
|
||||||
|
(56, 'mleko', 'ぎゅにゅ', 'gyunyu', 5),
|
||||||
|
(57, 'woda', 'みず', 'mizu', 5),
|
||||||
|
(58, 'pizza', 'ぴざ', 'piza', 5),
|
||||||
|
(59, 'ciasto', 'けーき', 'keeki', 5),
|
||||||
|
(60, 'mama', 'はは', 'haha', 6),
|
||||||
|
(61, 'tata', 'ちち', 'chichi', 6),
|
||||||
|
(62, 'starszy brat', 'あに', 'ani', 6),
|
||||||
|
(63, 'młodszy brat', 'おとうと', 'otouto', 6),
|
||||||
|
(64, 'starsza siostra', 'あね', 'ane', 6),
|
||||||
|
(65, 'młodsza siostra', 'いもうと', 'imouto', 6),
|
||||||
|
(66, 'córka', 'むすめ', 'musume', 6),
|
||||||
|
(67, 'syn', 'むすこ', 'musuko', 6),
|
||||||
|
(68, 'mąż', 'おっと', 'otto', 6),
|
||||||
|
(69, 'żona', 'つま', 'tsuma', 6),
|
||||||
|
(70, 'babcia', 'そぼ', 'sobo', 6),
|
||||||
|
(71, 'dziadek', 'そふ', 'sofu', 6),
|
||||||
|
(72, 'styczeń', 'いちがつ', 'ichigatsu', 7),
|
||||||
|
(73, 'luty', 'にがつ', 'nigatsu', 7),
|
||||||
|
(74, 'marzed', 'さんがつ', 'sangatsu', 7),
|
||||||
|
(75, 'kwiecień', 'しがつ', 'shigatsu', 7),
|
||||||
|
(76, 'maj', 'ごがつ', 'gogatsu', 7),
|
||||||
|
(77, 'czerwiec', 'ろくがつ', 'rokugatsu', 7),
|
||||||
|
(78, 'lipiec', 'しちがつ', 'shichigatsu', 7),
|
||||||
|
(79, 'sierpień', 'はちがつ', 'hachigatsu', 7),
|
||||||
|
(80, 'wrzesień', 'くがつ', 'kugatsu', 7),
|
||||||
|
(81, 'październik', 'じゅうがつ', 'juugatsu', 7),
|
||||||
|
(82, 'listopad', 'じゅういちがつ', 'juuichigatsu', 7),
|
||||||
|
(83, 'grudzień', 'じゅうにがつ', 'juunigatsu', 7),
|
||||||
|
(84, 'zwierzęta', 'どうぶつ', 'doubutsu', 8),
|
||||||
|
(85, 'świnia', 'ぶた', 'buta', 8),
|
||||||
|
(86, 'pies', 'いぬ', 'inu', 8),
|
||||||
|
(87, 'kot', 'ねこ', 'neko', 8),
|
||||||
|
(88, 'lis', 'きつね', 'kitsune', 8),
|
||||||
|
(89, 'niedźwieź', 'くま', 'kuma', 8),
|
||||||
|
(90, 'żyrafa', 'きりん', 'kirin', 8),
|
||||||
|
(91, 'małpa', 'さる', 'saru', 8),
|
||||||
|
(92, 'mysz', 'ねずみ', 'nezumi', 8),
|
||||||
|
(93, 'ptak', 'とり', 'tori', 8),
|
||||||
|
(94, 'królik', 'うさぎ', 'usagi', 8),
|
||||||
|
(95, 'krowa', 'うし', 'ushi', 8),
|
||||||
|
(96, 'koza', 'やぎ', 'yagi', 8);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for dumped tables
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for table `categories`
|
||||||
|
--
|
||||||
|
ALTER TABLE `categories`
|
||||||
|
ADD PRIMARY KEY (`categoryId`);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for table `learnedwords`
|
||||||
|
--
|
||||||
|
ALTER TABLE `learnedwords`
|
||||||
|
ADD PRIMARY KEY (`learnedID`),
|
||||||
|
ADD KEY `FK_LearnedUser` (`userID`),
|
||||||
|
ADD KEY `FK_LearnedWord` (`wordID`);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for table `scores`
|
||||||
|
--
|
||||||
|
ALTER TABLE `scores`
|
||||||
|
ADD PRIMARY KEY (`scoreId`),
|
||||||
|
ADD KEY `FK_UserScore` (`userId`);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for table `users`
|
||||||
|
--
|
||||||
|
ALTER TABLE `users`
|
||||||
|
ADD PRIMARY KEY (`userId`);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Indexes for table `words`
|
||||||
|
--
|
||||||
|
ALTER TABLE `words`
|
||||||
|
ADD PRIMARY KEY (`wordID`),
|
||||||
|
ADD KEY `FK_WordCategory` (`categoryId`);
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT for dumped tables
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT for table `learnedwords`
|
||||||
|
--
|
||||||
|
ALTER TABLE `learnedwords`
|
||||||
|
MODIFY `learnedID` int(11) NOT NULL AUTO_INCREMENT;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT for table `scores`
|
||||||
|
--
|
||||||
|
ALTER TABLE `scores`
|
||||||
|
MODIFY `scoreId` int(11) NOT NULL AUTO_INCREMENT;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT for table `users`
|
||||||
|
--
|
||||||
|
ALTER TABLE `users`
|
||||||
|
MODIFY `userId` int(10) NOT NULL AUTO_INCREMENT;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- AUTO_INCREMENT for table `words`
|
||||||
|
--
|
||||||
|
ALTER TABLE `words`
|
||||||
|
MODIFY `wordID` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=97;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Constraints for dumped tables
|
||||||
|
--
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Constraints for table `learnedwords`
|
||||||
|
--
|
||||||
|
ALTER TABLE `learnedwords`
|
||||||
|
ADD CONSTRAINT `FK_LearnedUser` FOREIGN KEY (`userID`) REFERENCES `users` (`userId`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
ADD CONSTRAINT `FK_LearnedWord` FOREIGN KEY (`wordID`) REFERENCES `words` (`wordID`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Constraints for table `scores`
|
||||||
|
--
|
||||||
|
ALTER TABLE `scores`
|
||||||
|
ADD CONSTRAINT `FK_UserScore` FOREIGN KEY (`userId`) REFERENCES `users` (`userId`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Constraints for table `words`
|
||||||
|
--
|
||||||
|
ALTER TABLE `words`
|
||||||
|
ADD CONSTRAINT `FK_WordCategory` FOREIGN KEY (`categoryId`) REFERENCES `categories` (`categoryId`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||||
|
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||||
|
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "kawaiilingo",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"body-parser": "^1.20.2",
|
||||||
|
"express": "^4.19.2",
|
||||||
|
"jsonwebtoken": "^9.0.2",
|
||||||
|
"mysql2": "^3.10.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/jsonwebtoken": "^9.0.6"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
import express from 'express';
|
||||||
|
import bodyParser from 'body-parser';
|
||||||
|
import Database from './db/database';
|
||||||
|
import fs from 'fs';
|
||||||
|
import authenticateJWT from './lib/authenticateJWT';
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
app.use(bodyParser.urlencoded({extended : true}));
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
|
// const db = new Database();
|
||||||
|
|
||||||
|
// type User = {
|
||||||
|
// username: string,
|
||||||
|
// password: string,
|
||||||
|
// role: string,
|
||||||
|
// xp: number
|
||||||
|
// };
|
||||||
|
|
||||||
|
const routes = 'routes';
|
||||||
|
|
||||||
|
fs.readdirSync(routes).forEach((file: string) => {
|
||||||
|
const route = require(`./${routes}/${file}`);
|
||||||
|
app.use(`/${file.split('.')[0]}`, route.default);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.json({ message: 'Hello World!' });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
app.get('/test', authenticateJWT, (req, res) => {
|
||||||
|
//@ts-ignore
|
||||||
|
res.json({ code: 200, message: `O chuj, zalogowałeś się ${req.user.username}!` });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/db', (req, res) => {
|
||||||
|
const db = new Database();
|
||||||
|
db.query('INSERT INTO words VALUES (NULL, "kot", "猫", "animals", "neko")', []);
|
||||||
|
|
||||||
|
res.sendStatus(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(3000, () => {
|
||||||
|
console.log('Running on http://localhost:3000');
|
||||||
|
});
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
|
||||||
|
const authenticateJWT = (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
const authHeader = req.headers.authorization;
|
||||||
|
|
||||||
|
if (authHeader) {
|
||||||
|
const token = authHeader.split(' ')[1];
|
||||||
|
|
||||||
|
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET ?? "", (err: any, user: any) => {
|
||||||
|
if (err) {
|
||||||
|
return res.status(403).json({ code: 403, error: 'Forbidden' })
|
||||||
|
}
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
req.user = user;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(401).json({ code: 401, error: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default authenticateJWT;
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { Router } from "express";
|
||||||
|
import jwt from 'jsonwebtoken';
|
||||||
|
import Database from "../db/database";
|
||||||
|
import User from "../types/User";
|
||||||
|
|
||||||
|
const db = new Database();
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.post('/login', async (req, res) => {
|
||||||
|
const { username, password } = req.body;
|
||||||
|
|
||||||
|
const user = (await db.query('SELECT * FROM users WHERE username = ? AND password = ?', [username, password]))[0] as User | undefined;
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
const accessToken = jwt.sign({ userId: user.userId }, process.env.ACCESS_TOKEN_SECRET ?? "", { expiresIn: '7d' });
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
code: 200,
|
||||||
|
accessToken
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.status(401).json({ code: 401, error: 'Username or password incorrect' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/register', async (req, res) => {
|
||||||
|
const { username, email, password } = req.body;
|
||||||
|
|
||||||
|
const user = (await db.query('SELECT * FROM users WHERE username = ? OR email = ?', [username, email]))[0] as User | undefined;
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
return res.status(409).json({ code: 409, error: 'Username already exists' });
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.query('INSERT INTO users VALUES (NULL, ?, ?, ?, ?, ?, ?)', [username, password, email, 0, 0, new Date()]);
|
||||||
|
|
||||||
|
res.status(201).json({ code: 201, message: 'User created' });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/users', async (req, res) => {
|
||||||
|
const users = await db.query('SELECT * FROM users', []);
|
||||||
|
|
||||||
|
res.json({ code: 200, users });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
|
@ -0,0 +1,152 @@
|
||||||
|
import { Router } from "express";
|
||||||
|
import Database from "../db/database";
|
||||||
|
import authenticateJWT from "../lib/authenticateJWT";
|
||||||
|
import Word from "../types/Word";
|
||||||
|
|
||||||
|
const db = new Database();
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
const times: { start: number, userId: number }[] = [];
|
||||||
|
|
||||||
|
const mapMultipleWords = async (words: Word[]) => {
|
||||||
|
const categories = await db.query('SELECT * FROM categories', []);
|
||||||
|
// console.log(categories)
|
||||||
|
|
||||||
|
return words.map((word) => {
|
||||||
|
if(word.japaneseWord.includes("|")) {
|
||||||
|
const japaneseWords = word.japaneseWord.split("|");
|
||||||
|
const romajiWords = word.romaji.split("|");
|
||||||
|
return {
|
||||||
|
wordID: word.wordID,
|
||||||
|
polishWord: word.polishWord,
|
||||||
|
japaneseWord: japaneseWords,
|
||||||
|
romaji: romajiWords,
|
||||||
|
category: {
|
||||||
|
categoryId: word.categoryId,
|
||||||
|
categoryEnglishName: categories.find((category: any) => category.categoryId === word.categoryId).englishName,
|
||||||
|
categoryPolishName: categories.find((category: any) => category.categoryId === word.categoryId).polishName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else return {
|
||||||
|
wordID: word.wordID,
|
||||||
|
polishWord: word.polishWord,
|
||||||
|
japaneseWord: word.japaneseWord,
|
||||||
|
romaji: word.romaji,
|
||||||
|
category: {
|
||||||
|
categoryId: word.categoryId,
|
||||||
|
categoryEnglishName: categories.find((category: any) => category.categoryId === word.categoryId).englishName,
|
||||||
|
categoryPolishName: categories.find((category: any) => category.categoryId === word.categoryId).polishName
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
router.get('/all', (req, res) => {
|
||||||
|
db.query('SELECT * FROM words', [])
|
||||||
|
.then(async (result: Word[]) => {
|
||||||
|
res.json({ code: 200, words: await mapMultipleWords(result) });
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
console.log(err);
|
||||||
|
res.sendStatus(500);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/id/:id', (req, res) => {
|
||||||
|
db.query('SELECT * FROM words WHERE wordId = ?', [req.params.id])
|
||||||
|
.then(async (result: Word[]) => {
|
||||||
|
if(result.length === 0) return res.status(404).json({ code: 404, error: 'Word not found' });
|
||||||
|
res.json({ code: 200, word: await mapMultipleWords(result) });
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
console.log(err);
|
||||||
|
res.status(500).json({ code: 500, error: 'Internal server error' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/category/:categoryId', (req, res) => {
|
||||||
|
db.query('SELECT * FROM words WHERE categoryId = ?', [req.params.categoryId])
|
||||||
|
.then(async (result: Word[]) => {
|
||||||
|
if(result.length === 0) return res.status(404).json({ code: 404, error: 'Category not found' });
|
||||||
|
res.json({ code: 200, words: await mapMultipleWords(result) });
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
console.log(err);
|
||||||
|
res.status(500).json({ code: 500, error: 'Internal server error' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/pickRandom/', (req, res) => {
|
||||||
|
let { categoryId, amount } = req.query;
|
||||||
|
|
||||||
|
if(!amount) amount = "1";
|
||||||
|
|
||||||
|
const sql = `SELECT * FROM words ${categoryId ? `WHERE categoryId = ? ` : ""}ORDER BY RAND() LIMIT ${amount}`;
|
||||||
|
|
||||||
|
db.query(sql, [categoryId])
|
||||||
|
.then(async (result: any) => {
|
||||||
|
if(result.length === 0) return res.status(404).json({ code: 404, error: 'Category not found' });
|
||||||
|
res.json({ code: 200, words: await mapMultipleWords(result) });
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
console.log(err);
|
||||||
|
res.status(500).json({ code: 500, error: 'Internal server error' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/startTest', authenticateJWT, async (req, res) => {
|
||||||
|
times.push({
|
||||||
|
start: Date.now(),
|
||||||
|
// @ts-ignore
|
||||||
|
userId: req.user.userId
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.json({ code: 200, message: 'Test started' });
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/submitAnswer', authenticateJWT, (req, res) => {
|
||||||
|
const answers = req.body as { id: number, answer: string }[];
|
||||||
|
const promises = answers.map(answer => db.query('SELECT * FROM words WHERE wordId = ?', [answer.id]));
|
||||||
|
|
||||||
|
Promise.all(promises)
|
||||||
|
.then(async (results: Word[][]) => {
|
||||||
|
const verified = results.map((result, index) => {
|
||||||
|
if(result[0].romaji.includes("|")) {
|
||||||
|
const romajiWords = result[0].romaji.split("|");
|
||||||
|
return {id: result[0].wordID, correct: romajiWords.includes(answers[index].answer) }
|
||||||
|
}
|
||||||
|
return {id: result[0].wordID, correct: result[0].romaji === answers[index].answer }
|
||||||
|
});
|
||||||
|
|
||||||
|
const accuracy = verified.filter((v) => v.correct).length / verified.length;
|
||||||
|
const xp = accuracy * 10 + (accuracy === 1 ? 10 : 0);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const time = times.find((time) => time.userId === req.user.userId);
|
||||||
|
if(!time) return res.status(404).json({ code: 404, error: 'Test not started' });
|
||||||
|
|
||||||
|
times.splice(times.indexOf(time), 1);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
await db.updateUserXP(req.user.userId, xp);
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
await db.addScore(req.user.userId, Math.floor((Date.now() - time.start) / 1000), xp, verified.filter((v) => !v.correct).length);
|
||||||
|
|
||||||
|
res.json({ code: 200, results: {
|
||||||
|
questions: verified,
|
||||||
|
timeElapsed: Math.floor((Date.now() - time.start) / 1000),
|
||||||
|
accuracy,
|
||||||
|
experienceEarned: xp
|
||||||
|
}});
|
||||||
|
})
|
||||||
|
.catch((err: any) => {
|
||||||
|
console.log(err);
|
||||||
|
res.status(500).json({ code: 500, error: 'Internal server error' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Router } from "express";
|
||||||
|
import Database from "../db/database";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
const db = new Database();
|
||||||
|
|
||||||
|
router.get('/', async (req, res) => {
|
||||||
|
const scores = await db.query('SELECT userId, sum(time) AS totalTime, sum(xp) AS totalXP, sum(mistakes) AS totalMistakes FROM scores GROUP BY userId ORDER BY totalXP DESC', []);
|
||||||
|
res.json({ code: 200, scores });
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { Router } from "express";
|
||||||
|
import authenticateJWT from "../lib/authenticateJWT";
|
||||||
|
import Database from "../db/database";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
const db = new Database();
|
||||||
|
|
||||||
|
router.get('/', authenticateJWT, async (req, res) => {
|
||||||
|
// @ts-ignore
|
||||||
|
const userDB = (await db.query('SELECT * FROM users WHERE userId = ?', [req.user.userId]))[0];
|
||||||
|
|
||||||
|
return res.json({
|
||||||
|
code: 200,
|
||||||
|
user: {
|
||||||
|
username: userDB.username,
|
||||||
|
xp: userDB.xp,
|
||||||
|
level: userDB.level,
|
||||||
|
dateJoined: userDB.dateJoined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default interface User {
|
||||||
|
userId: number,
|
||||||
|
username: string,
|
||||||
|
password: string,
|
||||||
|
role: string,
|
||||||
|
xp: number
|
||||||
|
};
|
|
@ -0,0 +1,7 @@
|
||||||
|
export default interface Word {
|
||||||
|
wordID: number,
|
||||||
|
polishWord: string,
|
||||||
|
japaneseWord: string,
|
||||||
|
romaji: string,
|
||||||
|
categoryId: string
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
|
/* Projects */
|
||||||
|
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||||
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
|
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||||
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||||
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
|
/* Language and Environment */
|
||||||
|
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||||
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
|
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||||
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||||
|
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||||
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
"module": "commonjs", /* Specify what module code is generated. */
|
||||||
|
"rootDir": "./src/", /* Specify the root folder within your source files. */
|
||||||
|
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
|
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||||
|
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||||
|
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||||
|
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||||
|
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
|
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||||
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
|
/* JavaScript Support */
|
||||||
|
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||||
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||||
|
|
||||||
|
/* Emit */
|
||||||
|
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||||
|
"outDir": "./out/", /* Specify an output folder for all emitted files. */
|
||||||
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||||
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
|
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||||
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||||
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
|
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||||
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
|
||||||
|
/* Interop Constraints */
|
||||||
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
|
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
|
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||||
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
|
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||||
|
|
||||||
|
/* Type Checking */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
|
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||||
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||||
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
|
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||||
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||||
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||||
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|
||||||
|
/* Completeness */
|
||||||
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue