Browse Source

Add CLI scripts. Move database connection code. Add movie quotas. Clean up models and routes

master
David Ludwig 4 years ago
parent
commit
464b24fa91
12 changed files with 128 additions and 53 deletions
  1. +6
    -0
      .env.example
  2. +1
    -1
      index.html
  3. +1
    -0
      package.json
  4. +10
    -0
      src/cli/create_token.ts
  5. +11
    -0
      src/server/database/entities/MovieQuota.ts
  6. +21
    -8
      src/server/database/entities/MovieTicket.ts
  7. +27
    -1
      src/server/database/entities/User.ts
  8. +1
    -0
      src/server/database/entities/index.ts
  9. +22
    -0
      src/server/database/index.ts
  10. +2
    -17
      src/server/services/Database.ts
  11. +21
    -20
      src/server/services/WebServer/routes/api.ts
  12. +5
    -6
      src/server/services/WebServer/routes/auth.ts

+ 6
- 0
.env.example View File

@ -1,3 +1,6 @@
# The base URL the site is hosted on
BASE_URL = "https://autoplex.dlii.tech"
# Keys --------------------------------------------------------------------------------------------- # Keys ---------------------------------------------------------------------------------------------
# Application key to sign stuff # Application key to sign stuff
@ -25,6 +28,9 @@ DB_DATABASE = autoplex_request
# Interfaces --------------------------------------------------------------------------------------- # Interfaces ---------------------------------------------------------------------------------------
# Seeker IPC socket path
SEEKER_IPC_SOCKET = /tmp/seeker.sock
# Torrent client IPC socket path # Torrent client IPC socket path
TORRENT_CLIENT_IPC_SOCKET = /tmp/torrent_client.sock TORRENT_CLIENT_IPC_SOCKET = /tmp/torrent_client.sock


+ 1
- 1
index.html View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Autoplex Torrent</title>
<title>Autoplex</title>
</head> </head>
<body class="bg-blue-50"> <body class="bg-blue-50">
<div id="app" class="contents"></div> <div id="app" class="contents"></div>


+ 1
- 0
package.json View File

@ -7,6 +7,7 @@
"main": "./build/server/index.js", "main": "./build/server/index.js",
"scripts": { "scripts": {
"clean": "rimraf ./build", "clean": "rimraf ./build",
"cli:token": "node -r ts-node/register -r tsconfig-paths/register ./src/cli/create_token.ts",
"dev": "vite", "dev": "vite",
"build": "yarn run build:backend && yarn run build:frontend", "build": "yarn run build:backend && yarn run build:frontend",
"build:backend": "ttsc -P ./tsconfig.server.json", "build:backend": "ttsc -P ./tsconfig.server.json",


+ 10
- 0
src/cli/create_token.ts View File

@ -0,0 +1,10 @@
import connectToDatabase from "@server/database";
import { RegisterToken } from "@server/database/entities";
import { env } from "@server/util";
(async () => {
await connectToDatabase();
let token = await RegisterToken.generate()
console.log(`${env("BASE_URL")}/register?token=${token.token}`);
process.exit(0);
})();

+ 11
- 0
src/server/database/entities/MovieQuota.ts View File

@ -0,0 +1,11 @@
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from "typeorm";
@Entity()
export class MovieQuota extends BaseEntity
{
@PrimaryGeneratedColumn()
id!: number;
@Column({ default: 5 })
moviesPerWeek!: number;
}

+ 21
- 8
src/server/database/entities/MovieTicket.ts View File

@ -1,5 +1,5 @@
import { IApiMovieDetails } from "@common/api_schema"; import { IApiMovieDetails } from "@common/api_schema";
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, ManyToOne, OneToMany, OneToOne, JoinColumn } from "typeorm";
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, ManyToOne, OneToMany, OneToOne, JoinColumn, CreateDateColumn } from "typeorm";
import { MovieInfo } from "./MovieInfo"; import { MovieInfo } from "./MovieInfo";
import { MovieTorrent } from "./MovieTorrent"; import { MovieTorrent } from "./MovieTorrent";
import { User } from "./User"; import { User } from "./User";
@ -10,14 +10,26 @@ export class MovieTicket extends BaseEntity
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id!: number; id!: number;
@Column({ type: "varchar", length: 27, unique: true, nullable: true })
@Column({ type: "varchar", length: 27, nullable: true })
imdbId!: string | null; imdbId!: string | null;
@Column({ type: "varchar", nullable: true })
title!: string | null;
@Column({ type: "varchar" })
title!: string;
@Column({ type: "year", nullable: true })
year!: number | null;
@Column({ type: "year" })
year!: number;
@CreateDateColumn()
createdAt!: Date;
@Column({ default: false })
isFulfilled!: boolean;
@Column({ default: false })
isCanceled!: boolean;
@Column({ default: false })
isStale!: boolean;
@ManyToOne(() => User, user => user.movieTickets) @ManyToOne(() => User, user => user.movieTickets)
user!: User; user!: User;
@ -27,15 +39,16 @@ export class MovieTicket extends BaseEntity
@OneToOne(() => MovieInfo, { nullable: true }) @OneToOne(() => MovieInfo, { nullable: true })
@JoinColumn() @JoinColumn()
info!: MovieInfo;
info!: MovieInfo | null;
/** /**
* Insert a request via IMDb movie details * Insert a request via IMDb movie details
*/ */
public static async requestImdb(user: User, imdbId: string, title: string | null = null) {
public static async requestImdb(user: User, imdbId: string, title: string, year: number) {
let ticket = new MovieTicket(); let ticket = new MovieTicket();
ticket.imdbId = imdbId; ticket.imdbId = imdbId;
ticket.title = title; ticket.title = title;
ticket.year = year;
ticket.user = user; ticket.user = user;
return await ticket.save(); return await ticket.save();
} }


+ 27
- 1
src/server/database/entities/User.ts View File

@ -1,8 +1,9 @@
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, OneToMany } from "typeorm";
import { Entity, PrimaryGeneratedColumn, Column, BaseEntity, OneToMany, OneToOne, JoinColumn, CreateDateColumn } from "typeorm";
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { MovieTicket } from "./MovieTicket"; import { MovieTicket } from "./MovieTicket";
import Application from "@server/Application"; import Application from "@server/Application";
import { MovieQuota } from "./MovieQuota";
@Entity() @Entity()
export class User extends BaseEntity export class User extends BaseEntity
@ -22,6 +23,13 @@ export class User extends BaseEntity
@Column({ type: "char", length: 60 }) @Column({ type: "char", length: 60 })
password!: string; password!: string;
@CreateDateColumn()
createdAt!: Date;
@OneToOne(() => MovieQuota, { nullable: true })
@JoinColumn()
quota!: MovieQuota;
@OneToMany(() => User, user => user.movieTickets) @OneToMany(() => User, user => user.movieTickets)
movieTickets!: MovieTicket[]; movieTickets!: MovieTicket[];
@ -43,4 +51,22 @@ export class User extends BaseEntity
let body = { id: this.id, name: this.name, isAdmin: this.isAdmin }; let body = { id: this.id, name: this.name, isAdmin: this.isAdmin };
return jwt.sign(body, key, { expiresIn }); return jwt.sign(body, key, { expiresIn });
} }
/**
* Create a new user
*/
public static async createUser(name: string, email: string, password: string, quota: number|null = 5) {
let user = new User();
user.isAdmin = false;
user.name = name;
user.email = email;
user.password = await bcrypt.hash(password, 8);
// Create a quota if necessary
if (quota !== null) {
user.quota = new MovieQuota;
user.quota.moviesPerWeek = quota;
await user.quota.save();
}
return await user.save();
}
} }

+ 1
- 0
src/server/database/entities/index.ts View File

@ -1,4 +1,5 @@
export * from "./MovieInfo"; export * from "./MovieInfo";
export * from "./MovieQuota";
export * from "./MovieTicket"; export * from "./MovieTicket";
export * from "./MovieTorrent"; export * from "./MovieTorrent";
export * from "./RegisterToken"; export * from "./RegisterToken";


+ 22
- 0
src/server/database/index.ts View File

@ -0,0 +1,22 @@
import * as entities from "./entities";
import { readFile } from "fs/promises";
import { createConnection } from "typeorm";
export default async function connectToDatabase() {
// Fetch the database password from the secret file
let password = (await readFile(<string>process.env["DB_PASSWORD_FILE"])).toString().trim();
// Create the database connection
await createConnection({
type : <"mysql" | "mariadb">process.env["DB_TYPE"],
host : process.env["DB_HOST"],
port : parseInt(<string>process.env["DB_PORT"]),
username : process.env["DB_USER"],
password : password,
database : process.env["DB_DATABASE"],
// synchronize: process.env["NODE_ENV"] != "production",
synchronize: true, // Seems stable enough for my liking
entities : Object.values(entities),
migrations : ["src/migrations/*.ts"]
});
}

+ 2
- 17
src/server/services/Database.ts View File

@ -1,8 +1,8 @@
import { Connection, createConnection } from "typeorm"; import { Connection, createConnection } from "typeorm";
import * as entities from "../database/entities"; import * as entities from "../database/entities";
import Service from "./Service"; import Service from "./Service";
import { readFile } from "fs/promises";
import Application from "../Application"; import Application from "../Application";
import connectToDatabase from "@server/database";
export default class Database extends Service export default class Database extends Service
{ {
@ -22,22 +22,7 @@ export default class Database extends Service
* Boot the database service * Boot the database service
*/ */
public async boot() { public async boot() {
// Fetch the database password from the secret file
let password = (await readFile(<string>process.env["DB_PASSWORD_FILE"])).toString().trim();
// Create the database connection
await createConnection({
type : <"mysql" | "mariadb">process.env["DB_TYPE"],
host : process.env["DB_HOST"],
port : parseInt(<string>process.env["DB_PORT"]),
username : process.env["DB_USER"],
password : password,
database : process.env["DB_DATABASE"],
// synchronize: process.env["NODE_ENV"] != "production",
synchronize: true, // Seems stable enough for my liking
entities : Object.values(entities),
migrations : ["src/migrations/*.ts"]
});
await connectToDatabase();
} }
/** /**


+ 21
- 20
src/server/services/WebServer/routes/api.ts View File

@ -73,26 +73,27 @@ export default function register(factory: RouteRegisterFactory, app: Application
/** /**
* Request a movie to download * Request a movie to download
*/ */
factory.get("/create/imdb/:imdb_id", handle([RequestImdbMovieRequest], async (request, reply) => {
// Verify that the ID has not yet been requested
let imdbId = (<any>request.params)["imdb_id"];
let title = (<any>request.query)["title"] || null;
if (0 != await MovieTicket.count({imdbId})) {
reply.status(409);
reply.send({ status: "Conflict" });
return;
}
// Verify that the IMDb ID exists
if (!await app.service<MovieSearch>("Movie Search").verifyImdbId(imdbId)) {
reply.status(404);
reply.send({ satus: "Not found" });
return;
}
// Create the movie request ticket
let user = request.middlewareParams.auth.user;
let ticket = await MovieTicket.requestImdb(user, imdbId, title);
return reply.send({ status: "Success", data: { ticket_id: ticket.id }});
}));
// factory.get("/create/imdb/:imdb_id", handle([RequestImdbMovieRequest], async (request, reply) => {
// // Verify that the ID has not yet been requested
// let imdbId = (<any>request.params)["imdb_id"];
// let title = (<any>request.query)["title"] || null;
// // let year = // Scrape IMDb to find out...
// if (0 != await MovieTicket.count({imdbId})) {
// reply.status(409);
// reply.send({ status: "Conflict" });
// return;
// }
// // Verify that the IMDb ID exists
// if (!await app.service<MovieSearch>("Movie Search").verifyImdbId(imdbId)) {
// reply.status(404);
// reply.send({ satus: "Not found" });
// return;
// }
// // Create the movie request ticket
// let user = request.middlewareParams.auth.user;
// // let ticket = await MovieTicket.requestImdb(user, imdbId, title, year);
// return reply.send({ status: "Success", data: { ticket_id: ticket.id }});
// }));
/** /**
* Remove/cancel a request * Remove/cancel a request


+ 5
- 6
src/server/services/WebServer/routes/auth.ts View File

@ -48,12 +48,11 @@ export default function register(factory: RouteRegisterFactory, app: Application
*/ */
factory.post("/auth/register", handle([RegisterRequest], async (request, reply) => { factory.post("/auth/register", handle([RegisterRequest], async (request, reply) => {
let body = <IRegisterFormBody>request.body; let body = <IRegisterFormBody>request.body;
let user = new User();
user.isAdmin = false;
user.name = body.name.trim();
user.email = body.email.trim();
user.password = await bcrypt.hash(body.password, 8);
await user.save();
await User.createUser(
body.name.trim(),
body.email.trim(),
body.password.trim()
);
await RegisterToken.delete({token: body.token }); await RegisterToken.delete({token: body.token });
reply.send({ status: "success" }); reply.send({ status: "success" });
})); }));


Loading…
Cancel
Save