Skip to content

Commit

Permalink
Merge pull request MrBrax#143 from MrBrax/develop-ts
Browse files Browse the repository at this point in the history
Develop ts
  • Loading branch information
MrBrax authored Apr 28, 2022
2 parents e654f8b + 525188b commit 9e56bf9
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 57 deletions.
2 changes: 1 addition & 1 deletion client-vue/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "twitchautomator-client",
"version": "0.7.3",
"version": "0.7.4",
"private": true,
"homepage": "https://github.com/MrBrax/TwitchAutomator",
"scripts": {
Expand Down
3 changes: 3 additions & 0 deletions client-vue/src/assets/_layout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ hr {
&[data-collapsed="1"] .section-content {
display: none;
}
&.is-fullwidth {
flex: 1;
}
}

.section-title {
Expand Down
118 changes: 96 additions & 22 deletions client-vue/src/components/FileManager.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,49 @@
<template>
<table class="table file-manager is-fullwidth is-striped" v-if="!error">
<tr class="file-manager-item" v-for="(item, index) in files">
<td class="file-manager-item-name">{{ item.name }}</td>
<td class="file-manager-item-size">{{ formatBytes(item.size) }}</td>
<td class="file-manager-item-date">{{ item.date }}</td>
<td class="file-manager-item-actions">
<a class="button is-small is-confirm" :href="downloadLink(item)" target="_blank" download><fa icon="download"></fa></a>
<button class="button is-small is-danger" @click="deleteFile(item)"><fa icon="trash"></fa></button>
</td>
</tr>
</table>
<div class="notification is-danger error" v-if="error">
{{ error }}
<div class="file-manager">
<table class="table is-fullwidth is-striped" v-if="!error">
<thead>
<tr>
<th></th>
<th style="cursor: pointer;" @click="setSort('name')">
Name
<span v-if="sortBy == 'name'" class="icon is-small">
<fa icon="sort-down" v-if="sortOrder === 'asc'" />
<fa icon="sort-up" v-if="sortOrder === 'desc'" />
</span>
</th>
<th style="cursor: pointer;" @click="setSort('size')">
Size
<span v-if="sortBy == 'size'" class="icon is-small">
<fa icon="sort-down" v-if="sortOrder === 'asc'" />
<fa icon="sort-up" v-if="sortOrder === 'desc'" />
</span>
</th>
<th style="cursor: pointer;" @click="setSort('date')">
Last modified
<span v-if="sortBy == 'date'" class="icon is-small">
<fa icon="sort-down" v-if="sortOrder === 'asc'" />
<fa icon="sort-up" v-if="sortOrder === 'desc'" />
</span>
</th>
<th>Actions</th>
</tr>
</thead>
<tr class="file-manager-item" v-for="(item, index) in sortedFiles">
<td class="file-manager-item-icon">
<fa :icon="getIconName(item.extension)" />
</td>
<td class="file-manager-item-name">{{ item.name }}</td>
<td class="file-manager-item-size">{{ formatBytes(item.size) }}</td>
<td class="file-manager-item-date">{{ item.date }}</td>
<td class="file-manager-item-actions">
<a v-if="web" class="button is-small is-confirm" :href="downloadLink(item)" target="_blank" download><fa icon="download"></fa></a>
<button class="button is-small is-danger" @click="deleteFile(item)"><fa icon="trash"></fa></button>
</td>
</tr>
</table>
<div class="notification is-danger error" v-if="error">
{{ error }}
</div>
</div>
</template>

Expand All @@ -20,17 +52,16 @@ import { useStore } from "@/store";
import { AxiosError } from "axios";
import { defineComponent } from "vue";
// import { library } from "@fortawesome/fontawesome-svg-core";
// import { faSkull, faTrash } from "@fortawesome/free-solid-svg-icons";
// import { useStore } from "@/store";
// import { JobStatus } from "@common/Defs";
// library.add(faSkull, faTrash);
import { library } from "@fortawesome/fontawesome-svg-core";
import { faSortUp, faSortDown, faFileVideo, faFile, faFileCsv, faFileCode } from "@fortawesome/free-solid-svg-icons";
library.add(faSortUp, faSortDown, faFileVideo, faFile, faFileCsv, faFileCode);
interface ApiFile {
name: string;
size: number;
date: string;
is_dir: boolean;
extension: string;
}
export default defineComponent({
Expand All @@ -42,7 +73,6 @@ export default defineComponent({
},
web: {
type: String,
required: true,
},
},
setup() {
Expand All @@ -52,10 +82,14 @@ export default defineComponent({
data(): {
files: ApiFile[];
error: string;
sortBy: "name" | "size" | "date";
sortOrder: "asc" | "desc";
} {
return {
files: [],
error: "",
sortBy: "name",
sortOrder: "asc",
};
},
created() {
Expand Down Expand Up @@ -85,14 +119,54 @@ export default defineComponent({
const url = `${base}${this.web}/${file.name}`;
return url;
},
setSort(sortBy: "name" | "size" | "date") {
this.sortBy = sortBy;
this.sortOrder = this.sortOrder === "asc" ? "desc" : "asc";
},
getIconName(extension: string) {
switch (extension) {
case "mp4":
return "file-video";
case "mkv":
return "file-video";
case "ts":
return "file-video";
case "csv":
return "file-csv";
case "json":
return "file-code";
default:
return "file";
}
}
},
components: {
},
computed: {
sortedFiles() {
return this.files.filter(file => !file.is_dir).sort((a, b) => {
if (typeof a[this.sortBy] === "string") {
if (this.sortOrder === "asc") {
return (a[this.sortBy] as string).localeCompare(b[this.sortBy] as string);
} else {
return (b[this.sortBy] as string).localeCompare(a[this.sortBy] as string);
}
} else {
if (this.sortOrder === "asc") {
return (a[this.sortBy] as number) - (b[this.sortBy] as number);
} else {
return (b[this.sortBy] as number) - (a[this.sortBy] as number);
}
}
});
},
},
});
</script>

<style lang="scss">
.file-manager {
max-height: 30rem;
overflow-y: auto;
}
</style>
20 changes: 16 additions & 4 deletions client-vue/src/views/FilesView.vue
Original file line number Diff line number Diff line change
@@ -1,17 +1,29 @@
<template>
<div class="container">
<section class="section">
<div class="container vertical">
<section class="section is-fullwidth">
<div class="section-title"><h1>Clips</h1></div>
<div class="section-content">
<FileManager path="storage/saved_clips" web="saved_clips" />
</div>
</section>
<section class="section">
<div class="section-title"><h1>VODs</h1></div>
<section class="section is-fullwidth">
<div class="section-title"><h1>Archived VODs</h1></div>
<div class="section-content">
<FileManager path="storage/saved_vods" web="saved_vods" />
</div>
</section>
<section class="section is-fullwidth">
<div class="section-title"><h1>Application logs</h1></div>
<div class="section-content">
<FileManager path="logs" />
</div>
</section>
<section class="section is-fullwidth">
<div class="section-title"><h1>Software logs</h1></div>
<div class="section-content">
<FileManager path="logs/software" />
</div>
</section>
</div>
</template>

Expand Down
3 changes: 2 additions & 1 deletion server/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "twitchautomator-server",
"version": "0.0.8",
"version": "0.0.9",
"description": "",
"main": "index.ts",
"scripts": {
Expand Down Expand Up @@ -46,6 +46,7 @@
"chalk": "4",
"cron": "^1.8.2",
"date-fns": "^2.28.0",
"dotenv": "^16.0.0",
"express": "^4.17.3",
"minimist": "^1.2.6",
"morgan": "^1.10.0",
Expand Down
32 changes: 17 additions & 15 deletions server/src/Controllers/Files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const allowedDataPaths = [
"logs",
];

const validatePath = (nastyPath: string) => {
export function validatePath(nastyPath: string): string | boolean {

// sanitize user path
nastyPath = nastyPath.normalize();
Expand Down Expand Up @@ -44,27 +44,21 @@ const validatePath = (nastyPath: string) => {

return true;

};

// console.debug("C:\\", validatePath("C:\\"));
// console.debug("C:\\storage", validatePath("C:\\storage"));
// console.debug("/", validatePath("/"));
// console.debug("/storage", validatePath("/storage"));
// console.debug(path.join(DataRoot, ".."), validatePath(path.join(DataRoot, "..")));
// console.debug(path.join(DataRoot, "\u0000"), validatePath(path.join(DataRoot, "\u0000")));
// console.debug(path.join(DataRoot, "CON1"), validatePath(path.join(DataRoot, "CON1")));
// console.debug(path.join(DataRoot, "storage", "saved_vods"), validatePath(path.join(DataRoot, "storage", "saved_vods")));
// console.debug(path.join(DataRoot, "cache"), validatePath(path.join(DataRoot, "cache")));
// console.debug(path.join(DataRoot, "logs"), validatePath(path.join(DataRoot, "logs")));
}

export function ListFiles(req: express.Request, res: express.Response): void {

if (process.env.TCD_ENABLE_FILES_API !== "1") {
res.status(404).send({ status: "ERROR", message: "Files API is disabled on this server. Enable with the TCD_ENABLE_FILES_API environment variable." });
return;
}

const user_path = req.query.path as string;

if (user_path == undefined) {
res.status(400).send({
status: "ERROR",
message: "Path is not defined"
message: "Path is not defined",
});
return;
}
Expand All @@ -82,12 +76,15 @@ export function ListFiles(req: express.Request, res: express.Response): void {

const raw_files = fs.readdirSync(full_path);

const files = raw_files.map((file) => {
const files = raw_files.filter((file) => {
return !file.startsWith(".");
}).map((file) => {
return {
name: file,
size: fs.statSync(path.join(full_path, file)).size,
date: fs.statSync(path.join(full_path, file)).mtime,
is_dir: fs.lstatSync(path.join(full_path, file)).isDirectory(),
extension: path.extname(file).substring(1),
};
});

Expand All @@ -102,6 +99,11 @@ export function ListFiles(req: express.Request, res: express.Response): void {

export function DeleteFile(req: express.Request, res: express.Response): void {

if (process.env.TCD_ENABLE_FILES_API !== "1") {
res.status(404).send({ status: "ERROR", message: "Files API is disabled on this server. Enable with the TCD_ENABLE_FILES_API environment variable." });
return;
}

const user_path = req.query.path as string;
const file_name = req.query.name as string;

Expand Down
2 changes: 2 additions & 0 deletions server/src/Core/TwitchVOD.ts
Original file line number Diff line number Diff line change
Expand Up @@ -681,9 +681,11 @@ export class TwitchVOD {
const base = [
`${this.basename}.json`,
`${this.basename}.chat`,
`${this.basename}_chat.json`,
`${this.basename}_vod.mp4`,
`${this.basename}-llc-edl.csv`,
`${this.basename}_chat.mp4`,
`${this.basename}_chat_mask.mp4`,
`${this.basename}_burned.mp4`,
`${this.basename}.chatdump`,
`${this.basename}.chatdump.txt`,
Expand Down
17 changes: 3 additions & 14 deletions server/src/Routes/Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import * as Notifications from "../Controllers/Notifications";
import * as Tools from "../Controllers/Tools";
import * as Files from "../Controllers/Files";
import { TwitchVOD } from "../Core/TwitchVOD";
import chalk from "chalk";

const router = express.Router();

Expand Down Expand Up @@ -46,7 +45,7 @@ router.post("/vod/:basename/download", Vod.DownloadVod);
router.post("/vod/:basename/check_mute", Vod.CheckMute);
router.post("/vod/:basename/match", Vod.MatchVod);
router.post("/vod/:basename/cut", Vod.CutVod);
// router.post("/vod/:basename/delete", Vod.DeleteVod); // old endpoint
router.post("/vod/:basename/save", Vod.ArchiveVod);

router.get("/games", Games.ListGames);

Expand Down Expand Up @@ -91,18 +90,8 @@ router.post("/tools/reset_channels", Tools.ResetChannels);
router.post("/tools/vod_download", Tools.DownloadVod);
router.post("/tools/chat_download", Tools.DownloadChat);

if (process.env.TCD_ENABLE_FILES_API) {
router.get("/files", Files.ListFiles);
router.delete("/files", Files.DeleteFile);
console.log(chalk.bgRedBright.whiteBright("Files API enabled"));
} else {
router.get("/files", (req, res) => {
res.status(404).send({ status: "ERROR", message: "Files API is disabled on this server. Enable with the TCD_ENABLE_FILES_API environment variable." });
});
router.delete("/files", (req, res) => {
res.status(404).send({ status: "ERROR", message: "Files API is disabled on this server. Enable with the TCD_ENABLE_FILES_API environment variable." });
});
}
router.get("/files", Files.ListFiles);
router.delete("/files", Files.DeleteFile);

router.get("/test_video_download", (req, res) => {
if (!req.query.video_id) {
Expand Down
3 changes: 3 additions & 0 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { AppName, BaseConfigDataFolder, BaseConfigFolder } from "./Core/BaseConf
import { ClientBroker } from "./Core/ClientBroker";
import { Config } from "./Core/Config";
import ApiRouter from "./Routes/Api";
import dotenv from "dotenv";

dotenv.config();

const argv = minimist(process.argv.slice(2));

Expand Down
17 changes: 17 additions & 0 deletions server/tests/files.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { DataRoot } from "../src/Core/BaseConfig";
import path from "path";
import { validatePath } from "../src/Controllers/Files";
describe("files controller", () => {
it("should sanitize path", () => {
expect(validatePath("C:\\")).not.toBe(true);
expect(validatePath("C:\\storage")).not.toBe(true);
expect(validatePath("/")).not.toBe(true);
expect(validatePath("/storage")).not.toBe(true);
expect(validatePath(path.join(DataRoot, ".."))).not.toBe(true);
expect(validatePath(path.join(DataRoot, "\u0000"))).not.toBe(true);
expect(validatePath(path.join(DataRoot, "CON1"))).not.toBe(true);
expect(validatePath(path.join(DataRoot, "cache"))).not.toBe(true);
expect(validatePath(path.join(DataRoot, "logs"))).toBe(true);
expect(validatePath(path.join(DataRoot, "storage", "saved_vods"))).toBe(true);
});
});
5 changes: 5 additions & 0 deletions server/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1471,6 +1471,11 @@ dot-prop@^5.2.0:
dependencies:
is-obj "^2.0.0"

dotenv@^16.0.0:
version "16.0.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.0.tgz#c619001253be89ebb638d027b609c75c26e47411"
integrity sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==

duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
Expand Down

0 comments on commit 9e56bf9

Please sign in to comment.