From 58c8eb49b62d82b956bf03f196f8a976bf38c870 Mon Sep 17 00:00:00 2001 From: absoluteSpacehead Date: Mon, 5 Feb 2024 20:52:39 +0000 Subject: [PATCH] Add previews (mostly) --- Server/Source/Handlers/Database.ts | 18 +++- Server/Source/Modules/FNUtil.ts | 2 +- Server/Source/Routes/Downloads.ts | 24 +++-- Server/Source/Routes/Drafting.ts | 88 ++++++++++++++--- Server/Source/Schemas/Song.ts | 9 +- Server/package-lock.json | 152 ++++++++++++++++++++++++++++- Server/package.json | 1 + 7 files changed, 270 insertions(+), 24 deletions(-) diff --git a/Server/Source/Handlers/Database.ts b/Server/Source/Handlers/Database.ts index 9e7e5de..beedbf3 100644 --- a/Server/Source/Handlers/Database.ts +++ b/Server/Source/Handlers/Database.ts @@ -1,10 +1,11 @@ -import { DataSource } from "typeorm"; +import { DataSource, IsNull } from "typeorm"; import { ENVIRONMENT, SAVED_DATA_PATH } from "../Modules/Constants"; import { Song } from "../Schemas/Song"; import { ForcedCategory } from "../Schemas/ForcedCategory"; import { User } from "../Schemas/User"; import { Rating } from "../Schemas/Rating"; import { DiscordRole } from "../Schemas/DiscordRole"; +import { Debug } from "../Modules/Logger"; export const DBSource = new DataSource({ type: "better-sqlite3", @@ -26,6 +27,21 @@ export const DBSource = new DataSource({ (async () => { await DBSource.initialize(); + + // Look for songs without a PID here so we can resolve problems before we do anything else + const SongsWithNoPID = await Song.find({ where: { PID: IsNull() } }); + Debug(`We have ${SongsWithNoPID.length} song${SongsWithNoPID.length != 1 ? "s" : ""} with no PID`); + + SongsWithNoPID.forEach(async (Song) => { + Debug(`Fixing up ${Song.Name} PID`); + + // Existing songs that actually need separate PIDs (> 2 channels) will need to have their audio reuploaded entirely + // This is faster than checking to see if they all actually need one though... + Song.PID = Song.ID; + + await Song.save(); + Debug(`${Song.Name} PID is now ${Song.PID} to match ${Song.ID}`); + }) })(); /* diff --git a/Server/Source/Modules/FNUtil.ts b/Server/Source/Modules/FNUtil.ts index a971fac..a34c58f 100644 --- a/Server/Source/Modules/FNUtil.ts +++ b/Server/Source/Modules/FNUtil.ts @@ -111,7 +111,7 @@ export async function GenerateFortnitePages(ForUser: User | null): Promise<{ Suc siv: "Vocals", // siv - Vocals ID to use (only Vocals possible) qi: JSON.stringify({ // qi - Query Information (frontend related display stuff and language vocals channel related stuff) sid: Song.ID, // sid - Song UUID - pid: Song.ID, // pid - Playlist Asset ID + pid: Song.PID, // pid - Playlist Asset ID title: OriginalTrack._title, // title - Song Name - same as _title tracks: [ { diff --git a/Server/Source/Routes/Downloads.ts b/Server/Source/Routes/Downloads.ts index edd8df3..f4770a2 100644 --- a/Server/Source/Routes/Downloads.ts +++ b/Server/Source/Routes/Downloads.ts @@ -14,9 +14,12 @@ App.get("/song/download/:InternalID/:File", RequireAuthentication(), async (req, res) => { //const Song = AvailableFestivalSongs.find(x => x.UUID === req.params.SongUUID); - const SongData = await Song.findOne({ where: { ID: req.params.InternalID }, relations: { Author: true } }); + const SongData = await Song.findOne({ where: [ { ID: req.params.InternalID}, { PID: req.params.InternalID } ], relations: { Author: true } }); if (!SongData) return res.status(404).send("Song not found."); + + const IsPreview = SongData.ID != SongData.PID && req.params.InternalID == SongData.PID; + const ManifestPath = `${SongData.Directory}/${IsPreview ? `PreviewManifest.mpd` : `Manifest.mpd`}`; if (SongData.IsDraft && (req.user!.PermissionLevel! < UserPermissions.VerifiedUser && SongData.Author.ID !== req.user!.ID)) return res.status(403).send("You cannot use this track, because it's a draft."); @@ -32,7 +35,7 @@ async (req, res) => { type: "main", language: "en", url: `${BaseURL}master.blurl`, - data: readFileSync(`${SongData.Directory}/Manifest.mpd`).toString().replaceAll("{BASEURL}", BaseURL) + data: readFileSync(ManifestPath).toString().replaceAll("{BASEURL}", BaseURL) } ], type: "vod", @@ -42,7 +45,7 @@ async (req, res) => { case "manifest": case "manifest.mpd": - return res.set("content-type", "application/dash+xml").send(Buffer.from(readFileSync(`${SongData.Directory}/Manifest.mpd`).toString().replaceAll("{BASEURL}", BaseURL))); + return res.set("content-type", "application/dash+xml").send(Buffer.from(readFileSync(ManifestPath).toString().replaceAll("{BASEURL}", BaseURL))); case "cover": case "cover.png": @@ -65,27 +68,32 @@ async (req, res) => { if (!req.params.File.endsWith(".m4s") && !req.params.File.endsWith(".webm")) return res.sendStatus(403); - if (!existsSync(`${SongData.Directory}/Chunks/${req.params.File}`)) + const ChunkPath = `${SongData.Directory}/${IsPreview ? `PreviewChunks` : `Chunks`}/${req.params.File}` + if (!existsSync(ChunkPath)) return res.sendStatus(404); res.set("content-type", "video/mp4") - res.send(readFileSync(`${SongData.Directory}/Chunks/${req.params.File}`)); + res.send(readFileSync(ChunkPath)); + console.log(`Sending back ${ChunkPath}`); }); App.get("/:InternalID", RequireAuthentication(), async (req, res, next) => { - const SongData = await Song.findOne({ where: { ID: req.params.InternalID }, relations: { Author: true } }); + const SongData = await Song.findOne({ where: [ { ID: req.params.InternalID }, { PID: req.params.InternalID } ], relations: { Author: true } }); if (!SongData) return next(); // trust me bro + const IsPreview = SongData.ID != SongData.PID && req.params.InternalID == SongData.PID; + console.log(`We have ${IsPreview ? `PREVIEW` : `MAIN`} request`); + if (SongData.IsDraft && ((req.user ? req.user.PermissionLevel < UserPermissions.VerifiedUser : true) && SongData.Author.ID !== req.user!.ID)) return res.status(403).send("You cannot use this track, because it's a draft."); - const BaseURL = `${FULL_SERVER_ROOT}/song/download/${SongData.ID}/`; + const BaseURL = `${FULL_SERVER_ROOT}/song/download/${IsPreview ? SongData.PID : SongData.ID}/`; res.set("content-type", "application/json"); res.json({ - playlist: Buffer.from(readFileSync(`${SongData.Directory}/Manifest.mpd`).toString().replaceAll("{BASEURL}", BaseURL)).toString("base64"), + playlist: Buffer.from(readFileSync(`${SongData.Directory}/${IsPreview ? `PreviewManifest.mpd` : `Manifest.mpd`}`).toString().replaceAll("{BASEURL}", BaseURL)).toString("base64"), playlistType: "application/dash+xml", metadata: { assetId: "", diff --git a/Server/Source/Routes/Drafting.ts b/Server/Source/Routes/Drafting.ts index fc97666..6f0e44c 100644 --- a/Server/Source/Routes/Drafting.ts +++ b/Server/Source/Routes/Drafting.ts @@ -8,7 +8,8 @@ import { Song, SongStatus } from "../Schemas/Song"; import { Debug } from "../Modules/Logger"; import { magenta } from "colorette"; import { fromBuffer } from "file-type"; -import { rmSync, writeFileSync, renameSync, readFileSync } from "fs"; +import { v4 } from "uuid"; +import { rmSync, writeFileSync, renameSync, readFileSync, existsSync, mkdirSync } from "fs"; import { FULL_SERVER_ROOT, MAX_AMOUNT_OF_DRAFTS_AT_ONCE, SAVED_DATA_PATH } from "../Modules/Constants"; import { UserPermissions } from "../Schemas/User"; @@ -193,34 +194,97 @@ App.post("/upload/audio", await SongData.save(); } - await writeFileSync(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Audio.${ext}`, Decoded); + const AudioPath = `${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}`; + + await writeFileSync(AudioPath + `/Audio.${ext}`, Decoded); ffmpeg() - .input(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Audio.${ext}`) + .input(AudioPath + `/Audio.${ext}`) .audioCodec("libopus") .outputOptions([ "-use_timeline 1", "-f dash", "-mapping_family 255" ]) - .output(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Chunks/Manifest.mpd`) + .output(AudioPath + `/Chunks/Manifest.mpd`) .on("start", cl => Debug(`ffmpeg running with ${magenta(cl)}`)) .on("end", async () => { Debug("Ffmpeg finished running"); - rmSync(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Audio.${ext}`); - renameSync(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Chunks/Manifest.mpd`, `${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Manifest.mpd`); - // i love creating thread-safe code that always works! (never gonna error trust me) - writeFileSync(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Manifest.mpd`, readFileSync(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Manifest.mpd`).toString().replace(/[\w\d\r\n\t]*<\/ProgramInformation>/i, "{BASEURL}")); + // Check channels + ffmpeg.ffprobe(AudioPath + `/Audio.${ext}`, async (err, metadata) => { + if (err) // FUCK + { + console.log(err); + } + else if (metadata && metadata.streams && metadata.streams[0].codec_type == 'audio' && metadata.streams[0].channels && metadata.streams[0].channels > 2) // jfc ts + { + Debug(`Creating preview stream as it's needed...`); + + // Oh shit!! we need a preview stream!! so let's make one. - await SongData.reload(); - SongData.HasAudio = true; - await SongData.save(); + // Make a dir for it first + if (!existsSync(AudioPath + "/PreviewChunks")) + mkdirSync(AudioPath + "/PreviewChunks", { recursive: true }); + + // Then, figure out which channels from the original file to put into each channel on the output file. + // We already ran ffprobe earlier so we can just reuse that lol + var FilterLeft = "pan=stereo|c0="; + var FilterRight = "|c1="; + for (var i = 0; i < metadata.streams[0].channels; i++) + { + if (i == 0 || i % 2 == 0) + FilterLeft += `${i == 0 ? "" : "+"}c${i}` // out for 0 = "c0", out for 2 = "+c2" + else + FilterRight += `${i == 1 ? "" : "+"}c${i}` // out for 1 = "c1", out for 3 = "+c3" + } + + // Need to wait for this before removing the source file, but since it won't ALWAYS get called we don't put the rmSync in here + await new Promise((resolve, reject) => { + ffmpeg() + .input(AudioPath + `/Audio.${ext}`) + .audioCodec("libopus") + .audioFilter(FilterLeft + FilterRight) + .outputOptions([ + "-use_timeline 1", + "-f dash", + "-ac 2", // downmix + ]) + .output(AudioPath + `/PreviewChunks/PreviewManifest.mpd`) + .on("start", cl => Debug(`Creating preview stream with ${magenta(cl)}`)) + .on("end", async () => { + Debug(`Preview stream created`); + // Move the mpd out + renameSync(AudioPath + `/PreviewChunks/PreviewManifest.mpd`, AudioPath + `/PreviewManifest.mpd`); + writeFileSync(AudioPath + `/PreviewManifest.mpd`, readFileSync(AudioPath + `/PreviewManifest.mpd`).toString().replace(/[\w\d\r\n\t]*<\/ProgramInformation>/i, "{BASEURL}")); + SongData.PID = v4(); + await SongData.save(); + resolve(); + }) + .on("error", async (e, stdout, stderr) => { + console.error(e); + console.log(stdout); + console.error(stderr); + reject(e); + }).run(); + }); + } + + rmSync(AudioPath + `/Audio.${ext}`); + + renameSync(AudioPath + `/Chunks/Manifest.mpd`, AudioPath + `/Manifest.mpd`); + // i love creating thread-safe code that always works! (never gonna error trust me) + writeFileSync(AudioPath + `/Manifest.mpd`, readFileSync(AudioPath + `/Manifest.mpd`).toString().replace(/[\w\d\r\n\t]*<\/ProgramInformation>/i, "{BASEURL}")); + + await SongData.reload(); + SongData.HasAudio = true; + await SongData.save(); + }); }) .on("error", async (e, stdout, stderr) => { console.error(e); console.log(stdout); console.error(stderr); - rmSync(`${SAVED_DATA_PATH}/Songs/${req.body.TargetSong}/Audio.${ext}`); + rmSync(AudioPath + `/Audio.${ext}`); await SongData.reload(); SongData.Status = SongStatus.BROKEN; diff --git a/Server/Source/Schemas/Song.ts b/Server/Source/Schemas/Song.ts index 8e0f34b..3416c47 100644 --- a/Server/Source/Schemas/Song.ts +++ b/Server/Source/Schemas/Song.ts @@ -1,4 +1,4 @@ -import { BaseEntity, BeforeInsert, BeforeRemove, Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; +import { BaseEntity, BeforeInsert, BeforeRemove, Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, IsNull } from "typeorm"; import { FULL_SERVER_ROOT, SAVED_DATA_PATH } from "../Modules/Constants"; import { Rating } from "./Rating"; import { existsSync, mkdirSync, rmSync } from "fs"; @@ -21,6 +21,9 @@ export class Song extends BaseEntity { @PrimaryGeneratedColumn("uuid") ID: string; + @Column("uuid", { nullable: true, default: null }) + PID: string; + @ManyToOne(() => User, U => U.CreatedTracks) Author: User; @@ -102,6 +105,10 @@ export class Song extends BaseEntity { @BeforeInsert() Setup() { this.ID = v4(); + + if (this.PID == undefined) // im lazy but this will work regardless + this.PID = this.ID; // By default they should be the same to save space, if we *need* a preview stream later we can change this when processing audio + this.Directory = `${SAVED_DATA_PATH}/Songs/${this.ID}`; if (!existsSync(join(this.Directory, "Chunks"))) mkdirSync(join(this.Directory, "Chunks"), { recursive: true }); diff --git a/Server/package-lock.json b/Server/package-lock.json index f063e0d..452203d 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -23,6 +23,7 @@ "joi": "^17.12.0", "jsonwebtoken": "^9.0.2", "node-cron": "^3.0.3", + "ts-node": "^10.9.2", "typeorm": "^0.3.19", "underscore": "^1.13.6", "uuid": "^9.0.1" @@ -40,6 +41,17 @@ "typescript": "^5.2.2" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@discordjs/builders": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.7.0.tgz", @@ -175,6 +187,28 @@ "node": ">=12" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -242,6 +276,26 @@ "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" + }, "node_modules/@types/body-parser": { "version": "1.19.3", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.3.tgz", @@ -420,6 +474,25 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", @@ -458,6 +531,11 @@ "node": ">= 6.0.0" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -901,6 +979,11 @@ "node": ">= 0.10" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -974,6 +1057,14 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/discord-api-types": { "version": "0.37.61", "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.61.tgz", @@ -1549,6 +1640,11 @@ "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.8.0.tgz", "integrity": "sha512-lyWpfvNGVb5lu8YUAbER0+UMBTdR63w2mcSUlhhBTyVbxJvjgqwyAf3AZD6MprgK0uHuBoWXSDAMWLupX83o3Q==" }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -2368,6 +2464,48 @@ "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz", "integrity": "sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==" }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -2526,7 +2664,6 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -2589,6 +2726,11 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -2791,6 +2933,14 @@ "engines": { "node": ">=8" } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "engines": { + "node": ">=6" + } } } } diff --git a/Server/package.json b/Server/package.json index d92f4bd..df325db 100644 --- a/Server/package.json +++ b/Server/package.json @@ -55,6 +55,7 @@ "joi": "^17.12.0", "jsonwebtoken": "^9.0.2", "node-cron": "^3.0.3", + "ts-node": "^10.9.2", "typeorm": "^0.3.19", "underscore": "^1.13.6", "uuid": "^9.0.1"