This commit is contained in:
2020-09-16 17:49:19 +02:00
commit 63cd5f112f
26 changed files with 2616 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
.idea/
.vscode/
node_modules/
build/
tmp/
temp/
dist/
log/

7
README.md Normal file
View File

@@ -0,0 +1,7 @@
# Awesome Project Build with TypeORM
Steps to run this project:
1. Run `npm i` command
2. Setup database settings inside `ormconfig.json` file
3. Run `npm start` command

16
docker-compose.yml Normal file
View File

@@ -0,0 +1,16 @@
version: "3"
services:
jokedb:
image: registry.dennisgunia.de/jokedb
build:
context: ./
dockerfile: dockerfile
restart: always
hostname: backend
networks:
- backend
ports:
- "8080:3000"
networks:
backend:
driver: overlay

21
dockerfile Normal file
View File

@@ -0,0 +1,21 @@
FROM node:12-alpine
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
RUN mkdir -p /home/node/app/node_modules/log && chown -R node:node /home/node/app
RUN mkdir -p /home/node/app/node_modules/dist && chown -R node:node /home/node/app
RUN mkdir -p /home/node/app/node_modules/static && chown -R node:node /home/node/app
COPY ./dist /home/node/app/dist
COPY ./static /home/node/app/static
COPY ./*.json /home/node/app/
WORKDIR /home/node/app
RUN chmod -R 777 /home/node/app
USER node
RUN npm install
EXPOSE 3000
CMD [ "node", "dist/index.js"]

24
ormconfig.json Normal file
View File

@@ -0,0 +1,24 @@
{
"type": "mysql",
"host": "sql1.dennisgunia.de",
"port": 7574,
"username": "joke",
"password": "FivXcBxrUB6Fm5qN",
"database": "joke",
"synchronize": true,
"logging": false,
"entities": [
"dist/entity/**/*.js"
],
"migrations": [
"dist/migration/**/*.js"
],
"subscribers": [
"dist/subscriber/**/*.js"
],
"cli": {
"entitiesDir": "dist/entity",
"migrationsDir": "dist/migration",
"subscribersDir": "dist/subscriber"
}
}

1455
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

31
package.json Normal file
View File

@@ -0,0 +1,31 @@
{
"name": "flachwitze",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "ts-node src/index.ts"
},
"author": "Dennis Gunia",
"license": "ISC",
"devDependencies": {
"@types/express": "^4.17.8",
"@types/morgan": "^1.9.1",
"@types/node": "^8.10.63",
"ts-node": "3.3.0",
"typeorm": "^0.2.26",
"typescript": "3.3.3333"
},
"dependencies": {
"@overnightjs/core": "^1.7.5",
"@overnightjs/logger": "^1.2.0",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"morgan": "^1.10.0",
"mysql": "^2.18.1",
"reflect-metadata": "^0.1.10",
"rotating-file-stream": "^2.1.3",
"typeorm": "0.2.26"
}
}

51
src/api/express.ts Normal file
View File

@@ -0,0 +1,51 @@
import express from 'express';
import * as bodyParser from 'body-parser';
import { logInfo, logError } from "../util/logging";
import { ApiController } from '../types/ApiController';
import { responseJson } from '../util/response';
import * as rfs from 'rotating-file-stream'
import morgan from 'morgan'
export class WebAPI {
public app: express.Application;
public port: number;
public accessLogStream: rfs.RotatingFileStream;
constructor(port: number) {
this.app = express();
this.port = port;
this.accessLogStream = rfs.createStream('access.log', {
interval: '1d', // rotate daily
path: './log'
});
this.initializeMiddlewares();
}
private initializeMiddlewares() {
this.app.use(morgan('combined', {
stream: this.accessLogStream
}))
this.app.use(bodyParser.json());
}
public initializeControllers(controllers: ApiController[]) {
controllers.forEach(el => {
this.app.use(el.path, el.router);
})
// JSON parse error
this.app.use((err: Error, req: express.Request, res: express.Response, next: any) => {
if (err instanceof SyntaxError) {
responseJson(req,res,400,{},"JSON invalid");
} else {
logError(`Express: ${err.message}`);
responseJson(req,res,500,{},"Internal error");
}
})
}
public listen() {
this.app.listen(this.port, () => {
logInfo(`App listening on the port ${this.port}`);
});
}
}

View File

@@ -0,0 +1,88 @@
import * as express from 'express';
import { ApiController } from '../../../types/ApiController';
import { responseJson } from '../../../util/response';
import { JokeService } from '../../../services/JokeService';
import { logError } from '../../../util/logging';
export class JokeController implements ApiController{
public path: string= '/joke/';
public router: express.Router = express.Router();
public childComponents: ApiController[] = [];
constructor() {
this.intializeRoutes();
}
public intializeRoutes() {
this.router.get(`/:id`,this.getSingle)
this.router.get(`/:id/upvote`,this.upvote)
this.router.get(`/:id/downvote`,this.upvote)
this.childComponents.forEach( el => {
this.router.use(el.path, el.router)
})
// 404 Error Response
this.router.use((req, res, next) => {
responseJson(req,res,404,{},"api request not found");
});
}
public upvote(req: express.Request, res: express.Response){
const js = new JokeService();
const id = req.params.id as unknown as string;
if (!(id.match(/^[0-9]*$/))){
responseJson(req,res,400,{},"invalid id");
}else{
js.upvote(Number(id));
js.getRandomJoke().then( resp => {
responseJson(req,res,200, resp);
}).catch( err => {
if (err.message === "No Joke found"){
responseJson(req,res,404,{},"Not found");
return
}
logError(err)
responseJson(req,res,500,{},"internal error");
})
}
}
public downvote(req: express.Request, res: express.Response){
const js = new JokeService();
const id = req.params.id as unknown as string;
if (!(id.match(/^[0-9]*$/))){
responseJson(req,res,400,{},"invalid id");
}else{
js.downvote(Number(id));
js.getRandomJoke().then( resp => {
responseJson(req,res,200, resp);
}).catch( err => {
if (err.message === "No Joke found"){
responseJson(req,res,404,{},"Not found");
return
}
logError(err)
responseJson(req,res,500,{},"internal error");
})
}
}
public getSingle(req: express.Request, res: express.Response){
const js = new JokeService();
const id = req.params.id as unknown as string;
if (!(id.match(/^[0-9]*$/))){
responseJson(req,res,400,{},"invalid id");
}else{
js.getJoke(Number(id)).then( resp => {
responseJson(req,res,200, resp);
}).catch( err => {
if (err.message === "No Joke found"){
responseJson(req,res,404,{},"Not found");
return
}
logError(err)
responseJson(req,res,500,{},"internal error");
})
}
}
}

View File

@@ -0,0 +1,39 @@
import * as express from 'express';
import { ApiController } from '../../../types/ApiController';
import { responseJson } from '../../../util/response';
import { JokeService } from '../../../services/JokeService';
import { logError } from '../../../util/logging';
export class JokesController implements ApiController{
public path: string= '/jokes/';
public router: express.Router = express.Router();
public childComponents: ApiController[] = [];
public jokesrv: JokeService;
constructor() {
this.intializeRoutes();
this.jokesrv = new JokeService();
}
public intializeRoutes() {
this.router.get(`/getRand`,this.getRandJoke)
this.childComponents.forEach( el => {
this.router.use(el.path, el.router)
})
// 404 Error Response
this.router.use((req, res, next) => {
responseJson(req,res,404,{},"api request not found");
});
}
public getRandJoke(req: express.Request, res: express.Response){
const jokesrv = new JokeService();
jokesrv.getRandomJoke().then( resp => {
responseJson(req,res,200, resp);
}).catch( err => {
logError(err)
responseJson(req,res,500,{},"internal error");
})
}
}

View File

@@ -0,0 +1,30 @@
import * as express from 'express';
import { ApiController } from '../../types/ApiController';
import { responseJson } from '../../util/response';
import { JokeController } from './joke/joke-controller';
import { JokesController } from './joke/jokes-controller';
export class RootController implements ApiController{
public path: string= '/';
public router: express.Router = express.Router();
public childComponents: ApiController[] = [
new JokeController(),
new JokesController(),
];
constructor() {
this.intializeRoutes();
}
public intializeRoutes() {
this.router.use(express.static('./static'));
this.childComponents.forEach( el => {
this.router.use(el.path, el.router)
})
// 404 Error Response
this.router.use((req, res, next) => {
responseJson(req,res,404,{},"api request not found");
});
}
}

16
src/entity/Category.ts Normal file
View File

@@ -0,0 +1,16 @@
import { userInfo } from "os";
import {Entity, PrimaryGeneratedColumn, Column, OneToMany} from "typeorm";
import { Joke } from "./Joke";
@Entity()
export class Category {
@PrimaryGeneratedColumn()
id!: number;
@Column()
category!: string;
@OneToMany(type => Joke, jokes => jokes.category)
jokes!: Joke;
}

36
src/entity/Joke.ts Normal file
View File

@@ -0,0 +1,36 @@
import {Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn} from "typeorm";
import { Category } from "./Category";
import { User } from "./User";
@Entity()
export class Joke {
@PrimaryGeneratedColumn()
id!: number;
@Column()
header!: string;
@Column({ type: "longtext"})
joke!: string;
@Column({nullable: true})
source!: string;
@Column({default: 0, type: "int"})
upvotes!: number;
@Column({default: 0, type: "int"})
downvotes!: number;
@Column({default: 0, type: "int"})
views!: number;
@ManyToOne(type => User, user => user.jokes)
@JoinColumn()
user!: User;
@ManyToOne(type => Category, user => user.jokes)
@JoinColumn()
category!: Category;
}

28
src/entity/User.ts Normal file
View File

@@ -0,0 +1,28 @@
import { userInfo } from "os";
import {Entity, PrimaryGeneratedColumn, Column, OneToMany} from "typeorm";
import { Joke } from "./Joke";
@Entity()
export class User {
@PrimaryGeneratedColumn()
id!: number;
@Column()
firstName!: string;
@Column()
lastName!: string;
@Column()
displayname!: string;
@Column()
age!: number;
@Column()
mail!: string;
@OneToMany(type => Joke, jokes => jokes.user)
jokes!: Joke;
}

22
src/index.ts Normal file
View File

@@ -0,0 +1,22 @@
import "reflect-metadata";
import {createConnection} from "typeorm";
import { WebAPI } from "./api/express";
import { RootController } from "./api/root/root-controller";
import { ApiController } from "./types/ApiController";
import { logError, logInfo } from "./util/logging";
// initialize Database Connection
createConnection().then(async connection => {
logInfo("Database Connected");
}).catch(error => {
logError(error);
process.exit(10);
});
// initialize Web API
const app: WebAPI = new WebAPI(3000);
const controllers: ApiController[] = [
new RootController()
];
app.initializeControllers(controllers);
app.listen();

View File

@@ -0,0 +1,88 @@
import { getManager, Repository } from "typeorm";
import { Category } from "../entity/Category";
import { Joke } from "../entity/Joke";
import { User } from "../entity/User";
export interface JokeResponse {
id: number,
author: string,
text: string,
upvotes: number,
downvotes: number,
views: number,
category: string,
categoryId: number,
}
export class JokeService {
constructor(){
// Nothing to do here
}
public async getRandomJoke(): Promise<JokeResponse> {
const JokeRepo: Repository<Joke> = getManager().getRepository(Joke);
const joke = await (JokeRepo.createQueryBuilder('joke')
.leftJoinAndSelect('joke.user', 'user')
.leftJoinAndSelect('joke.category', 'category')
.orderBy("RAND()")
.limit(1)
.getOne()
);
if (joke === undefined){
throw new Error("No Joke found");
}
JokeRepo.increment({ id: joke.id }, "views", 1);
return {
id: joke.id,
author: joke.user.displayname,
text: joke.joke,
views: joke.views,
upvotes: joke.upvotes,
downvotes: joke.downvotes,
category: joke.category.category,
categoryId: joke.category.id
};
}
public async upvote(jid: number): Promise<boolean> {
const JokeRepo: Repository<Joke> = getManager().getRepository(Joke);
try {
await JokeRepo.increment({ id: jid }, "upvotes", 1);
return true;
} catch (error) {
return false;
}
}
public async downvote(jid: number): Promise<boolean> {
const JokeRepo: Repository<Joke> = getManager().getRepository(Joke);
try {
await JokeRepo.increment({ id: jid }, "downvotes", 1);
return true;
} catch (error) {
return false;
}
}
public async getJoke(jid: number): Promise<JokeResponse> {
const JokeRepo: Repository<Joke> = getManager().getRepository(Joke);
const UserRepo: Repository<User> = getManager().getRepository(User);
const CatRepo: Repository<Category> = getManager().getRepository(Category);
const joke = await (JokeRepo.findOne({id: jid},{relations: ['user','category']}));
if (joke === undefined){
throw new Error("No Joke found");
}
JokeRepo.increment({ id: joke.id }, "views", 1);
return {
id: joke.id,
author: joke.user.displayname,
text: joke.joke,
views: joke.views,
upvotes: joke.upvotes,
downvotes: joke.downvotes,
category: joke.category.category,
categoryId: joke.category.id
};
}
}

View File

@@ -0,0 +1,9 @@
import * as express from 'express';
export interface ApiController {
path: string;
router: express.Router
childComponents: ApiController[]
intializeRoutes():void
}

5
src/types/global.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
import * as rfs from 'rotating-file-stream'
export interface Global {
ls: rfs.RotatingFileStream;
}

35
src/util/logging.ts Normal file
View File

@@ -0,0 +1,35 @@
export function logInfo(text: string) {
// tslint:disable-next-line: no-console
console.log(`\x1b[32m[Info] \x1b[0m ${text}`)
}
export function logWarning(text: string) {
// tslint:disable-next-line: no-console
console.log(`\x1b[33m[Warn] \x1b[0m ${text}`)
}
export function logError(text: string) {
// tslint:disable-next-line: no-console
console.log(`\x1b[31m[Error] \x1b[0m ${text}`)
}
export function logCritical(text: string) {
// tslint:disable-next-line: no-console
console.log(`\x1b[4m\x1b[31m[Critical]\x1b[0m \x1b[4m ${text}\x1b[0m`)
}
export function logNotice (text: string) {
// tslint:disable-next-line: no-console
console.log(`\x1b[36m[Notice] \x1b[0m ${text}`)
}
export function logDebug(text: string) {
// tslint:disable-next-line: no-console
console.log(`\x1b[36m[DEBUG] \x1b[0m ${text}`)
}
export function logChat (text: string) {
// tslint:disable-next-line: no-console
console.log(`\x1b[35m[CHAT] \x1b[0m ${text}`)
}

16
src/util/response.ts Normal file
View File

@@ -0,0 +1,16 @@
import * as express from 'express';
export function responseJson(req: express.Request, res: express.Response,code: number, data: any, err?: string): void{
const respObj: any = {
rescource: req.originalUrl.toString(),
success: err ? false : true,
data,
debug: {
host: req.get('host'),
proto: req.protocol
},
error: err ? err : undefined
}
res.status(code);
res.json(respObj);
}

BIN
static/go-next-8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

59
static/index.html Normal file
View File

@@ -0,0 +1,59 @@
<!DOCTYPE html>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
<html>
<head>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="site.css" >
<link href="https://fonts.googleapis.com/css?family=Bowlby+One+SC&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Press+Start+2P&display=swap" rel="stylesheet">
</head>
<body>
<div class="background">
<div id="bg-content"></div>
</div>
<div class="header-bar">
<div class="row" style="width:100%;">
<div class="col">flachwitze.dennisgunia.de</div>
<div class="col text-right">Time Wasted <p id="wasted"></p></div>
</div>
</div>
<div class="score-bar">
<div id="score-content">
</div>
</div>
<div class="gradient-fill" id="gradient-fill"></div>
<div class="vertical-center">
<div class="container container-card">
<div class="card" style="width: 100%" id="joke-card">
<div class="card-body">
<div id=content>
<h5 class="card-title" id="joke-title">Card title</h5>
<h6 class="card-subtitle mb-2 text-muted" id="joke-source">Card subtitle</h6>
<p class="card-text" id="joke-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
</div>
<br />
<a href="#" class="card-link cmd-like">
<svg width="1em" height="1em" class="heart" viewBox="0 0 16 16" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M4 1c2.21 0 4 1.755 4 3.92C8 2.755 9.79 1 12 1s4 1.755 4 3.92c0 3.263-3.234 4.414-7.608 9.608a.513.513 0 0 1-.784 0C3.234 9.334 0 8.183 0 4.92 0 2.755 1.79 1 4 1z"/>
<path stroke-linejoin="round" stroke="currentColor" fill="none" stroke-width="2" d="M8 2.748l-.717-.737C5.6.281 2.514.878 1.4 3.053c-.523 1.023-.641 2.5.314 4.385.92 1.815 2.834 3.989 6.286 6.357 3.452-2.368 5.365-4.542 6.286-6.357.955-1.886.838-3.362.314-4.385C13.486.878 10.4.28 8.717 2.01L8 2.748z"/>
</svg>
</a>
<a href="#" class="card-link cmd-more">Mehr Davon!</a>
<a href="#" class="card-link cmd-next">Einen anderen Witz bitte ...</a>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.js" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.js" crossorigin="anonymous"></script>
<script src="site.js"></script>
</body>
</html>

232
static/site.css Normal file
View File

@@ -0,0 +1,232 @@
body {
background-color: #b0b0b0;
}
.vertical-center {
z-index:-1;
min-height: 100%; /* Fallback for browsers do NOT support vh unit */
min-height: 100vh; /* These two lines are counted as one :-) */
display: flex;
align-items: center;
/*background-color: rgba(0, 0, 0,0.5);*/
}
.gradient-fill {
opacity: 50%;
top: 0;
bottom: 0;
left: 0;
right: 0;
position: absolute;
}
.bg-fill-s1 {
background: rgb(2,0,36);
background: linear-gradient(30deg, rgba(2,0,36,1) 0%, rgba(9,9,121,1) 33%, rgba(0,212,255,1) 100%);;
opacity: 50%;
}
.bg-fill-s2 {
background: rgb(131,58,180);
background: linear-gradient(127deg, rgba(131,58,180,1) 0%, rgba(253,29,29,1) 50%, rgba(252,176,69,1) 100%);
opacity: 50%;
}
.bg-fill-s3 {
background: rgb(34,193,195);
background: linear-gradient(320deg, rgba(34,193,195,1) 0%, rgba(253,187,45,1) 100%);
opacity: 50%;
}
.background {
z-index:0;
font-family: 'Bowlby One SC', cursive;
display: flex;
position: absolute;
overflow: hidden;
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
}
.header-bar {
display: flex;
position : absolute;
z-index:20;
font-family: 'Bowlby One SC', cursive;
top: 0px;
width:100%;
/*background-color: rgba(0, 0, 0,0.5);*/
color: rgb(255,255,255);
padding:1rem;
background-image: linear-gradient(rgba(0,0,0,0.5), rgba(0, 0, 0,0));
}
.score-bar {
display: flex;
position : absolute;
z-index:19;
font-family: 'Bowlby One SC', cursive;
bottom: 0px;
width:100%;
/*background-color: rgba(0, 0, 0,0.5);*/
color: rgb(255,255,255);
padding:1rem;
background-image: linear-gradient(rgba(0,0,0,0), rgba(0, 0, 0,0.5));
}
#bg-content {
position : absolute;
left: 50%;
top: 50%;
-webkit-transform: translate(-50%, -50%) rotate(-45deg);
-moz-transform: translate(-50%, -50%) rotate(-45deg);
-ms-transform: translate(-50%, -50%) rotate(-45deg);
transform: translate(-50%, -50%) rotate(-45deg);
position: absolute;
transform-origin: 50% 50%;
}
.scr {
margin-right: 1rem;
}
.single-line {
background: url("go-next-8.png") repeat-x;
white-space: nowrap;
overflow:hidden;
color:rgb(153, 136, 136);
padding-bottom:48px;
width: 5000px;
height: 32px;
background-size: auto;
}
@keyframes slide {
from {
background-position-x: 0;
}
to {
background-position-x: -653px;
}
}
@keyframes slide_out {
from {
position-x: 0;
}
to {
position-x: -100%;
}
}
@keyframes slide_in {
from {
position-x: 100%;
}
to {
position-x: 0;
}
}
.heart > path{
fill: transparent;
}
.heart-en > svg > path {
animation: heartClickEn 0.3s linear;
color: red;
fill: lightcoral;
}
.heart-en > svg {
animation: heartPopIn 0.3s linear;
color: red;
}
@keyframes dash {
to {
stroke-dashoffset: 200;
}
}
@keyframes heartClickEn {
0% {
color: #007bff ;
}
50% {
fill: transparent;
}
100% {
color: red ;
fill: lightcoral;
}
}
@keyframes heartPopIn {
0% {
transform: scale(1.0);
}
50% {
transform: scale(2);
}
100% {
transform: scale(1.0);
}
}
@keyframes heartPopOut {
0% {
transform: scale(1.0);
}
50% {
transform: scale(2);
}
100% {
transform: scale(1.0);
}
}
.heart-da > svg > path {
animation: heartClickDa 0.3s linear;
fill: transparent;
}
.heart-da > svg {
animation: heartPopOut 0.3s linear;
color: 007bff;
}
@keyframes heartClickDa {
0% {
color: red ;
fill: lightcoral;
}
50% {
fill: transparent;
}
100% {
color: #007bff ;
}
}

214
static/site.js Normal file
View File

@@ -0,0 +1,214 @@
$.urlParam = function(name){
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
if (results==null) {
return null;
}
return decodeURI(results[1]) || 0;
}
theme = 0;
humor = [];
liked = false;
likelist = [];
function modHumor (catId, catDef, amount = 0) {
console.log(catId, catDef, amount)
let cf = humor.filter( el => el.catId === catId); // category found
if (cf.length == 1) {
cf[0].amount += amount;
}else{
humor.push({
catId: catId,
catDef: catDef,
amount: amount
})
}
}
function printHumor(){
$("#score-content").hide("fast",function(){
let htmlContent = "";
for (let i = 0 ; i < humor.length; i++){
htmlContent += "<button type=\"button\" class=\"btn scr btn-primary\"> "+humor[i].catDef+" <span class=\"badge badge-light\">"+humor[i].amount+"</span></button>";
}
$("#score-content").html(htmlContent);
$("#score-content").show("fast");
});
}
let jokeClass = {
cur_joke: {},
get: (id) => {
return new Promise((resolve,reject) => {
$.getJSON(`/joke/${id}`).done(function ( data ) {
resolve(data.data)
}).fail(function ( err ) {
reject(err);
})
})
},
getRnd: () => {
return new Promise((resolve,reject) => {
$.getJSON(`/jokes/getRand/`).done(function ( data ) {
resolve(data.data)
}).fail(function ( err ) {
reject(err);
})
})
},
up: (id) => {
return new Promise((resolve,reject) => {
$.getJSON(`/joke/${id}/upvote`).done(function ( data ) {
resolve(data.data)
}).fail(function ( err ) {
reject(err);
})
})
},
down: (id) => {
return new Promise((resolve,reject) => {
$.getJSON(`/joke/${id}/downvote`).done(function ( data ) {
resolve(data.data)
}).fail(function ( err ) {
reject(err);
})
})
},
}
function setLikeButton(state){
console.log(state)
if (state){
$('.cmd-like').removeClass('heart-da').addClass('heart-en');
}else{
$('.cmd-like').removeClass('heart-en').addClass('heart-da');
}
}
async function getWitz(action = 0,id = -1){
switch(action){
case 0:
jokeClass.cur_joke = await jokeClass.getRnd(); break;
case 1:
jokeClass.cur_joke = await jokeClass.up(id); break;
case 2:
jokeClass.cur_joke = await jokeClass.down(id); break;
case 3:
jokeClass.cur_joke = await jokeClass.get(id); break;
}
liked = (likelist.includes(jokeClass.cur_joke.id))
setLikeButton(liked);
$("#content").hide("fast", function () {
$("#joke-title").text(jokeClass.cur_joke.category);
$("#joke-source").text(jokeClass.cur_joke.author);
$("#joke-text").html(jokeClass.cur_joke.text);
$("#content").show("fast");
});
}
$('.cmd-next').click(function() {
modHumor(jokeClass.cur_joke.categoryId,jokeClass.cur_joke.category,-50);
getWitz(2,jokeClass.cur_joke.id);
printHumor();
});
$('.cmd-more').click(function() {
modHumor(jokeClass.cur_joke.categoryId,jokeClass.cur_joke.category,50);
getWitz(1,jokeClass.cur_joke.id);
printHumor();
});
$('.cmd-like').click(function() {
liked = !liked;
setLikeButton(liked);
if (liked && !likelist.includes(jokeClass.cur_joke.id)){
likelist.push(jokeClass.cur_joke.id);
}else{
likelist = likelist.filter(el => el !== jokeClass.cur_joke.id);
}
console.log(likelist)
});
// ANiMATION
let anim_rows = 50;
$( document ).ready(function() {
// randomize theme
theme = Math.round(Math.random()*2);
$("#gradient-fill").addClass(`bg-fill-s${theme + 1}`);
anim_rows= (window.innerWidth / 68 ) + (window.innerHeight / 68);
generateBackground(anim_rows);
try {
const tmp = JSON.parse($.cookie("score"));
humor = tmp.humor;
likelist = tmp.likes;
}catch{ }
printHumor();
const reqJoke = $.urlParam('wid');
console.log(reqJoke)
if (reqJoke){
getWitz(3,reqJoke);
}else{
getWitz();
}
$('#wasted').text(Math.floor((new Date - start) / 1000) + " Sekunden");
});
$(window).on('resize', function(){
var win = $(this); //this = window
anim_rows= (win.width() / 68) + (win.height() / 68);
generateBackground(anim_rows);
});
$(window).on("unload", function(e) {
$.cookie("score", JSON.stringify({
humor: humor,
likes: likelist
}));
});
const start = new Date;
setInterval(function() {
$('#wasted').text(Math.floor((new Date - start) / 1000) + " Sekunden");
}, 1000);
function generateBackground(rows){
let fullhmtl = ""
for (let i = 0; i < rows;i++){
const speed = Math.floor(Math.random() * 20 ) +20
const css = "animation: " + speed + "s linear infinite slide;"
fullhmtl += "<div class='single-line' style='" + css + "'></div>"
}
$("#bg-content").html(fullhmtl);
}
function calculateMapping(){
let min = 1000000000;
let max = -1000000000;
for (let i = 0 ; i < humor.length; i++){
if(humor[i].scr < min){
min = humor[i].scr
}
}
const offset = (min * -1);
let sum = 0;
for (let i = 0 ; i < humor.length; i++){
sum += (humor[i].scr + offset);
}
for (let i = 0 ; i < humor.length; i++){
const percentage = ((humor[i].scr + offset) / sum);
console.log(percentage);
}
}

73
tsconfig.json Normal file
View File

@@ -0,0 +1,73 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"lib": [
"es5",
"es6",
"DOM"
], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist/", /* Redirect output structure to the directory. */
"rootDir": "./src/", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
"typeRoots": [ "./src/types/" ], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

13
tslint.json Normal file
View File

@@ -0,0 +1,13 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {},
"rules": {
"no-console": true
},
"rulesDirectory": [
]
}