diff --git a/.env.example b/.env.example
index a4463c7..9b4b1ea 100644
--- a/.env.example
+++ b/.env.example
@@ -1,3 +1,6 @@
+# The base URL the site is hosted on
+BASE_URL = "https://autoplex.dlii.tech"
+
# Keys ---------------------------------------------------------------------------------------------
# Application key to sign stuff
@@ -25,6 +28,9 @@ DB_DATABASE = autoplex_request
# Interfaces ---------------------------------------------------------------------------------------
+# Seeker IPC socket path
+SEEKER_IPC_SOCKET = /tmp/seeker.sock
+
# Torrent client IPC socket path
TORRENT_CLIENT_IPC_SOCKET = /tmp/torrent_client.sock
diff --git a/index.html b/index.html
index 05a93c1..81c3e00 100644
--- a/index.html
+++ b/index.html
@@ -4,7 +4,7 @@
-
Autoplex Torrent
+ Autoplex
diff --git a/package.json b/package.json
index a81e2f4..bcee839 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"main": "./build/server/index.js",
"scripts": {
"clean": "rimraf ./build",
+ "cli:token": "node -r ts-node/register -r tsconfig-paths/register ./src/cli/create_token.ts",
"dev": "vite",
"build": "yarn run build:backend && yarn run build:frontend",
"build:backend": "ttsc -P ./tsconfig.server.json",
diff --git a/src/cli/create_token.ts b/src/cli/create_token.ts
new file mode 100644
index 0000000..41261a7
--- /dev/null
+++ b/src/cli/create_token.ts
@@ -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);
+})();
diff --git a/src/server/database/entities/MovieQuota.ts b/src/server/database/entities/MovieQuota.ts
new file mode 100644
index 0000000..edba67b
--- /dev/null
+++ b/src/server/database/entities/MovieQuota.ts
@@ -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;
+}
diff --git a/src/server/database/entities/MovieTicket.ts b/src/server/database/entities/MovieTicket.ts
index 24db1ec..cfb8aeb 100644
--- a/src/server/database/entities/MovieTicket.ts
+++ b/src/server/database/entities/MovieTicket.ts
@@ -1,5 +1,5 @@
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 { MovieTorrent } from "./MovieTorrent";
import { User } from "./User";
@@ -10,14 +10,26 @@ export class MovieTicket extends BaseEntity
@PrimaryGeneratedColumn()
id!: number;
- @Column({ type: "varchar", length: 27, unique: true, nullable: true })
+ @Column({ type: "varchar", length: 27, nullable: true })
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)
user!: User;
@@ -27,15 +39,16 @@ export class MovieTicket extends BaseEntity
@OneToOne(() => MovieInfo, { nullable: true })
@JoinColumn()
- info!: MovieInfo;
+ info!: MovieInfo | null;
/**
* 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();
ticket.imdbId = imdbId;
ticket.title = title;
+ ticket.year = year;
ticket.user = user;
return await ticket.save();
}
diff --git a/src/server/database/entities/User.ts b/src/server/database/entities/User.ts
index 6f68d74..7ee702c 100644
--- a/src/server/database/entities/User.ts
+++ b/src/server/database/entities/User.ts
@@ -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 jwt from "jsonwebtoken";
import { MovieTicket } from "./MovieTicket";
import Application from "@server/Application";
+import { MovieQuota } from "./MovieQuota";
@Entity()
export class User extends BaseEntity
@@ -22,6 +23,13 @@ export class User extends BaseEntity
@Column({ type: "char", length: 60 })
password!: string;
+ @CreateDateColumn()
+ createdAt!: Date;
+
+ @OneToOne(() => MovieQuota, { nullable: true })
+ @JoinColumn()
+ quota!: MovieQuota;
+
@OneToMany(() => User, user => user.movieTickets)
movieTickets!: MovieTicket[];
@@ -43,4 +51,22 @@ export class User extends BaseEntity
let body = { id: this.id, name: this.name, isAdmin: this.isAdmin };
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();
+ }
}
diff --git a/src/server/database/entities/index.ts b/src/server/database/entities/index.ts
index b85cb59..2b8c4c9 100644
--- a/src/server/database/entities/index.ts
+++ b/src/server/database/entities/index.ts
@@ -1,4 +1,5 @@
export * from "./MovieInfo";
+export * from "./MovieQuota";
export * from "./MovieTicket";
export * from "./MovieTorrent";
export * from "./RegisterToken";
diff --git a/src/server/database/index.ts b/src/server/database/index.ts
new file mode 100644
index 0000000..bfe0be5
--- /dev/null
+++ b/src/server/database/index.ts
@@ -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(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(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"]
+ });
+}
diff --git a/src/server/services/Database.ts b/src/server/services/Database.ts
index baf2d0d..bcdea60 100644
--- a/src/server/services/Database.ts
+++ b/src/server/services/Database.ts
@@ -1,8 +1,8 @@
import { Connection, createConnection } from "typeorm";
import * as entities from "../database/entities";
import Service from "./Service";
-import { readFile } from "fs/promises";
import Application from "../Application";
+import connectToDatabase from "@server/database";
export default class Database extends Service
{
@@ -22,22 +22,7 @@ export default class Database extends Service
* Boot the database service
*/
public async boot() {
- // Fetch the database password from the secret file
- let password = (await readFile(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(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();
}
/**
diff --git a/src/server/services/WebServer/routes/api.ts b/src/server/services/WebServer/routes/api.ts
index 8fa8f1f..2a4f254 100644
--- a/src/server/services/WebServer/routes/api.ts
+++ b/src/server/services/WebServer/routes/api.ts
@@ -73,26 +73,27 @@ export default function register(factory: RouteRegisterFactory, app: Application
/**
* 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 = (request.params)["imdb_id"];
- let title = (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("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 = (request.params)["imdb_id"];
+ // let title = (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("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
diff --git a/src/server/services/WebServer/routes/auth.ts b/src/server/services/WebServer/routes/auth.ts
index a272099..39fcc95 100644
--- a/src/server/services/WebServer/routes/auth.ts
+++ b/src/server/services/WebServer/routes/auth.ts
@@ -48,12 +48,11 @@ export default function register(factory: RouteRegisterFactory, app: Application
*/
factory.post("/auth/register", handle([RegisterRequest], async (request, reply) => {
let body = 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 });
reply.send({ status: "success" });
}));