diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c5a96b80..2cfcbd94 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -50,7 +50,8 @@ "typescript": "^5.2.2", "vite": "^4.4.9", "vite-plugin-compression2": "^0.10.4", - "vite-plugin-rewrite-all": "^1.0.1" + "vite-plugin-rewrite-all": "^1.0.1", + "vue-tsc": "^1.8.10" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2541,6 +2542,33 @@ "vue": "^3.2.25" } }, + "node_modules/@volar/language-core": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.10.1.tgz", + "integrity": "sha512-JnsM1mIPdfGPxmoOcK1c7HYAsL6YOv0TCJ4aW3AXPZN/Jb4R77epDyMZIVudSGjWMbvv/JfUa+rQ+dGKTmgwBA==", + "dev": true, + "dependencies": { + "@volar/source-map": "1.10.1" + } + }, + "node_modules/@volar/source-map": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.10.1.tgz", + "integrity": "sha512-3/S6KQbqa7pGC8CxPrg69qHLpOvkiPHGJtWPkI/1AXCsktkJ6gIk/5z4hyuMp8Anvs6eS/Kvp/GZa3ut3votKA==", + "dev": true, + "dependencies": { + "muggle-string": "^0.3.1" + } + }, + "node_modules/@volar/typescript": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.10.1.tgz", + "integrity": "sha512-+iiO9yUSRHIYjlteT+QcdRq8b44qH19/eiUZtjNtuh6D9ailYM7DVR0zO2sEgJlvCaunw/CF9Ov2KooQBpR4VQ==", + "dev": true, + "dependencies": { + "@volar/language-core": "1.10.1" + } + }, "node_modules/@vue/compat": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/@vue/compat/-/compat-3.3.4.tgz", @@ -2619,6 +2647,54 @@ "prettier": ">= 3.0.0" } }, + "node_modules/@vue/language-core": { + "version": "1.8.10", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.10.tgz", + "integrity": "sha512-db8PtM4ZZr7SYNH30XpKxUYnUBYaTvcuJ4c2whKK04fuAjbtjAIZ2al5GzGEfUlesmvkpgdbiSviRXUxgD9Omw==", + "dev": true, + "dependencies": { + "@volar/language-core": "~1.10.0", + "@volar/source-map": "~1.10.0", + "@vue/compiler-dom": "^3.3.0", + "@vue/reactivity": "^3.3.0", + "@vue/shared": "^3.3.0", + "minimatch": "^9.0.0", + "muggle-string": "^0.3.1", + "vue-template-compiler": "^2.7.14" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@vue/language-core/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@vue/reactivity": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz", @@ -2675,6 +2751,16 @@ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==" }, + "node_modules/@vue/typescript": { + "version": "1.8.10", + "resolved": "https://registry.npmjs.org/@vue/typescript/-/typescript-1.8.10.tgz", + "integrity": "sha512-vPSpTXMk4chYwvyTGjM891cKgnx2r6vtbdANOp2mRU31f4HYGyLrZBlGgiua7SaO2cLjUg8y91OipJe0t8OFhA==", + "dev": true, + "dependencies": { + "@volar/typescript": "~1.10.0", + "@vue/language-core": "1.8.10" + } + }, "node_modules/@vueuse/core": { "version": "10.4.1", "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.4.1.tgz", @@ -3301,6 +3387,12 @@ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.9.tgz", "integrity": "sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==" }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -4194,6 +4286,15 @@ "node": ">=4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, "node_modules/html-encoding-sniffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", @@ -4858,6 +4959,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/muggle-string": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.3.1.tgz", + "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", + "dev": true + }, "node_modules/nanoid": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", @@ -6321,6 +6428,66 @@ "resolved": "https://registry.npmjs.org/vue-simple-progress/-/vue-simple-progress-1.1.1.tgz", "integrity": "sha512-ltLWYBA5eVQHWyt1NwZeGeK0VQC69JVh1oqUdro0po7r8Hc8SEMEyEfuwyCO4s27h5I3jbD99BKKkyPSQZgoZA==" }, + "node_modules/vue-template-compiler": { + "version": "2.7.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz", + "integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==", + "dev": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "1.8.10", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.10.tgz", + "integrity": "sha512-ptpTFFDoHQgkWJF7i5iERxooiQzOGtG1uKTfmAUuS3qPuSQGq+Ky/S8BFHhnFGwoOxq/PjmGN2QSZEfg1rtzQA==", + "dev": true, + "dependencies": { + "@vue/language-core": "1.8.10", + "@vue/typescript": "1.8.10", + "semver": "^7.3.8" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/vue-tsc/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vue-tsc/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vue-tsc/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 2ff04e29..262e3550 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,7 +6,7 @@ "scripts": { "dev": "vite dev", "serve": "vite serve", - "build": "vite build", + "build": "vue-tsc -p ./tsconfig.json --noEmit && vite build --emptyOutDir", "watch": "vite build --watch", "clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +", "lint": "eslint --ext .vue,.js src/", @@ -56,7 +56,8 @@ "typescript": "^5.2.2", "vite": "^4.4.9", "vite-plugin-compression2": "^0.10.4", - "vite-plugin-rewrite-all": "^1.0.1" + "vite-plugin-rewrite-all": "^1.0.1", + "vue-tsc": "^1.8.10" }, "browserslist": [ "> 1%", diff --git a/frontend/public/index.html b/frontend/public/index.html index 39d926d8..04135401 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -179,7 +179,7 @@ - + [{[ if .Theme -]}] conn.send(command); - conn.onmessage = onmessage; - conn.onclose = onclose; -} diff --git a/frontend/src/api/files.js b/frontend/src/api/files.js deleted file mode 100644 index 65a61e6a..00000000 --- a/frontend/src/api/files.js +++ /dev/null @@ -1,204 +0,0 @@ -import { createURL, fetchURL, removePrefix } from "./utils"; -import { baseURL } from "@/utils/constants"; -import { useAuthStore } from "@/stores/auth"; -import { upload as postTus, useTus } from "./tus"; - -export async function fetch(url) { - url = removePrefix(url); - - const res = await fetchURL(`/api/resources${url}`, {}); - - let data = await res.json(); - data.url = `/files${url}`; - - if (data.isDir) { - if (!data.url.endsWith("/")) data.url += "/"; - data.items = data.items.map((item, index) => { - item.index = index; - item.url = `${data.url}${encodeURIComponent(item.name)}`; - - if (item.isDir) { - item.url += "/"; - } - - return item; - }); - } - - return data; -} - -async function resourceAction(url, method, content) { - url = removePrefix(url); - - let opts = { method }; - - if (content) { - opts.body = content; - } - - const res = await fetchURL(`/api/resources${url}`, opts); - - return res; -} - -export async function remove(url) { - return resourceAction(url, "DELETE"); -} - -export async function put(url, content = "") { - return resourceAction(url, "PUT", content); -} - -export function download(format, ...files) { - let url = `${baseURL}/api/raw`; - - if (files.length === 1) { - url += removePrefix(files[0]) + "?"; - } else { - let arg = ""; - - for (let file of files) { - arg += removePrefix(file) + ","; - } - - arg = arg.substring(0, arg.length - 1); - arg = encodeURIComponent(arg); - url += `/?files=${arg}&`; - } - - if (format) { - url += `algo=${format}&`; - } - - const authStore = useAuthStore(); - if (authStore.jwt) { - url += `auth=${authStore.jwt}&`; - } - - window.open(url); -} - -export async function post(url, content = "", overwrite = false, onupload) { - // Use the pre-existing API if: - const useResourcesApi = - // a folder is being created - url.endsWith("/") || - // We're not using http(s) - (content instanceof Blob && - !["http:", "https:"].includes(window.location.protocol)) || - // Tus is disabled / not applicable - !(await useTus(content)); - return useResourcesApi - ? postResources(url, content, overwrite, onupload) - : postTus(url, content, overwrite, onupload); -} - -async function postResources(url, content = "", overwrite = false, onupload) { - url = removePrefix(url); - - let bufferContent; - if ( - content instanceof Blob && - !["http:", "https:"].includes(window.location.protocol) - ) { - bufferContent = await new Response(content).arrayBuffer(); - } - - const authStore = useAuthStore(); - return new Promise((resolve, reject) => { - let request = new XMLHttpRequest(); - request.open( - "POST", - `${baseURL}/api/resources${url}?override=${overwrite}`, - true - ); - request.setRequestHeader("X-Auth", authStore.jwt); - - if (typeof onupload === "function") { - request.upload.onprogress = onupload; - } - - request.onload = () => { - if (request.status === 200) { - resolve(request.responseText); - } else if (request.status === 409) { - reject(request.status); - } else { - reject(request.responseText); - } - }; - - request.onerror = () => { - reject(new Error("001 Connection aborted")); - }; - - request.send(bufferContent || content); - }); -} - -function moveCopy(items, copy = false, overwrite = false, rename = false) { - let promises = []; - - for (let item of items) { - const from = item.from; - const to = encodeURIComponent(removePrefix(item.to)); - const url = `${from}?action=${ - copy ? "copy" : "rename" - }&destination=${to}&override=${overwrite}&rename=${rename}`; - promises.push(resourceAction(url, "PATCH")); - } - - return Promise.all(promises); -} - -export function move(items, overwrite = false, rename = false) { - return moveCopy(items, false, overwrite, rename); -} - -export function copy(items, overwrite = false, rename = false) { - return moveCopy(items, true, overwrite, rename); -} - -export async function checksum(url, algo) { - const data = await resourceAction(`${url}?checksum=${algo}`, "GET"); - return (await data.json()).checksums[algo]; -} - -export function getDownloadURL(file, inline) { - const params = { - ...(inline && { inline: "true" }), - }; - - return createURL("api/raw" + file.path, params); -} - -export function getPreviewURL(file, size) { - const params = { - inline: "true", - key: Date.parse(file.modified), - }; - - return createURL("api/preview/" + size + file.path, params); -} - -export function getSubtitlesURL(file) { - const params = { - inline: "true", - }; - - const subtitles = []; - for (const sub of file.subtitles) { - subtitles.push(createURL("api/raw" + sub, params)); - } - - return subtitles; -} - -export async function usage(url) { - url = removePrefix(url); - - const res = await fetchURL(`/api/usage${url}`, {}); - - return await res.json(); -} diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js deleted file mode 100644 index abc189dc..00000000 --- a/frontend/src/api/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as files from "./files"; -import * as share from "./share"; -import * as users from "./users"; -import * as settings from "./settings"; -import * as pub from "./pub"; -import search from "./search"; -import commands from "./commands"; - -export { files, share, users, settings, pub, commands, search }; diff --git a/frontend/src/api/pub.js b/frontend/src/api/pub.js deleted file mode 100644 index 1511143d..00000000 --- a/frontend/src/api/pub.js +++ /dev/null @@ -1,70 +0,0 @@ -import { fetchURL, removePrefix, createURL } from "./utils"; -import { baseURL } from "@/utils/constants"; - -export async function fetch(url, password = "") { - url = removePrefix(url); - - const res = await fetchURL( - `/api/public/share${url}`, - { - headers: { "X-SHARE-PASSWORD": encodeURIComponent(password) }, - }, - false - ); - - let data = await res.json(); - data.url = `/share${url}`; - - if (data.isDir) { - if (!data.url.endsWith("/")) data.url += "/"; - data.items = data.items.map((item, index) => { - item.index = index; - item.url = `${data.url}${encodeURIComponent(item.name)}`; - - if (item.isDir) { - item.url += "/"; - } - - return item; - }); - } - - return data; -} - -export function download(format, hash, token, ...files) { - let url = `${baseURL}/api/public/dl/${hash}`; - - if (files.length === 1) { - url += encodeURIComponent(files[0]) + "?"; - } else { - let arg = ""; - - for (let file of files) { - arg += encodeURIComponent(file) + ","; - } - - arg = arg.substring(0, arg.length - 1); - arg = encodeURIComponent(arg); - url += `/?files=${arg}&`; - } - - if (format) { - url += `algo=${format}&`; - } - - if (token) { - url += `token=${token}&`; - } - - window.open(url); -} - -export function getDownloadURL(share, inline = false) { - const params = { - ...(inline && { inline: "true" }), - ...(share.token && { token: share.token }), - }; - - return createURL("api/public/dl/" + share.hash + share.path, params, false); -} diff --git a/frontend/src/api/search.js b/frontend/src/api/search.js deleted file mode 100644 index 42846880..00000000 --- a/frontend/src/api/search.js +++ /dev/null @@ -1,27 +0,0 @@ -import { fetchURL, removePrefix } from "./utils"; -import url from "../utils/url"; - -export default async function search(base, query) { - base = removePrefix(base); - query = encodeURIComponent(query); - - if (!base.endsWith("/")) { - base += "/"; - } - - let res = await fetchURL(`/api/search${base}?query=${query}`, {}); - - let data = await res.json(); - - data = data.map((item) => { - item.url = `/files${base}` + url.encodePath(item.path); - - if (item.dir) { - item.url += "/"; - } - - return item; - }); - - return data; -} diff --git a/frontend/src/api/settings.js b/frontend/src/api/settings.js deleted file mode 100644 index e03b0db1..00000000 --- a/frontend/src/api/settings.js +++ /dev/null @@ -1,12 +0,0 @@ -import { fetchURL, fetchJSON } from "./utils"; - -export function get() { - return fetchJSON(`/api/settings`, {}); -} - -export async function update(settings) { - await fetchURL(`/api/settings`, { - method: "PUT", - body: JSON.stringify(settings), - }); -} diff --git a/frontend/src/api/share.js b/frontend/src/api/share.js deleted file mode 100644 index 28d550ea..00000000 --- a/frontend/src/api/share.js +++ /dev/null @@ -1,40 +0,0 @@ -import { fetchURL, fetchJSON, removePrefix, createURL } from "./utils"; - -export async function list() { - return fetchJSON("/api/shares"); -} - -export async function get(url) { - url = removePrefix(url); - return fetchJSON(`/api/share${url}`); -} - -export async function remove(hash) { - await fetchURL(`/api/share/${hash}`, { - method: "DELETE", - }); -} - -export async function create(url, password = "", expires = "", unit = "hours") { - url = removePrefix(url); - url = `/api/share${url}`; - if (expires !== "") { - url += `?expires=${expires}&unit=${unit}`; - } - let body = "{}"; - if (password != "" || expires !== "" || unit !== "hours") { - body = JSON.stringify({ - password: password, - expires: expires.toString(), // backend expects string not number - unit: unit, - }); - } - return fetchJSON(url, { - method: "POST", - body: body, - }); -} - -export function getShareURL(share) { - return createURL("share/" + share.hash, {}, false); -} diff --git a/frontend/src/api/tus.js b/frontend/src/api/tus.js deleted file mode 100644 index 83cb607a..00000000 --- a/frontend/src/api/tus.js +++ /dev/null @@ -1,91 +0,0 @@ -import * as tus from "tus-js-client"; -import { baseURL, tusEndpoint, tusSettings } from "@/utils/constants"; -import { useAuthStore } from "@/stores/auth"; -import { removePrefix } from "@/api/utils"; -import { fetchURL } from "./utils"; - -const RETRY_BASE_DELAY = 1000; -const RETRY_MAX_DELAY = 20000; - -export async function upload( - filePath, - content = "", - overwrite = false, - onupload -) { - if (!tusSettings) { - // Shouldn't happen as we check for tus support before calling this function - throw new Error("Tus.io settings are not defined"); - } - - filePath = removePrefix(filePath); - let resourcePath = `${tusEndpoint}${filePath}?override=${overwrite}`; - - await createUpload(resourcePath); - - const authStore = useAuthStore(); - return new Promise((resolve, reject) => { - let upload = new tus.Upload(content, { - uploadUrl: `${baseURL}${resourcePath}`, - chunkSize: tusSettings.chunkSize, - retryDelays: computeRetryDelays(tusSettings), - parallelUploads: 1, - storeFingerprintForResuming: false, - headers: { - "X-Auth": authStore.jwt, - }, - onError: function (error) { - reject("Upload failed: " + error); - }, - onProgress: function (bytesUploaded) { - // Emulate ProgressEvent.loaded which is used by calling functions - // loaded is specified in bytes (https://developer.mozilla.org/en-US/docs/Web/API/ProgressEvent/loaded) - if (typeof onupload === "function") { - onupload({ loaded: bytesUploaded }); - } - }, - onSuccess: function () { - resolve(); - }, - }); - upload.start(); - }); -} - -async function createUpload(resourcePath) { - let headResp = await fetchURL(resourcePath, { - method: "POST", - }); - if (headResp.status !== 201) { - throw new Error( - `Failed to create an upload: ${headResp.status} ${headResp.statusText}` - ); - } -} - -function computeRetryDelays(tusSettings) { - if (!tusSettings.retryCount || tusSettings.retryCount < 1) { - // Disable retries altogether - return null; - } - // The tus client expects our retries as an array with computed backoffs - // E.g.: [0, 3000, 5000, 10000, 20000] - const retryDelays = []; - let delay = 0; - - for (let i = 0; i < tusSettings.retryCount; i++) { - retryDelays.push(Math.min(delay, RETRY_MAX_DELAY)); - delay = - delay === 0 ? RETRY_BASE_DELAY : Math.min(delay * 2, RETRY_MAX_DELAY); - } - - return retryDelays; -} - -export async function useTus(content) { - return isTusSupported() && content instanceof Blob; -} - -function isTusSupported() { - return tus.isSupported === true; -} diff --git a/frontend/src/api/users.js b/frontend/src/api/users.js deleted file mode 100644 index 105d6cc0..00000000 --- a/frontend/src/api/users.js +++ /dev/null @@ -1,41 +0,0 @@ -import { fetchURL, fetchJSON } from "./utils"; - -export async function getAll() { - return fetchJSON(`/api/users`, {}); -} - -export async function get(id) { - return fetchJSON(`/api/users/${id}`, {}); -} - -export async function create(user) { - const res = await fetchURL(`/api/users`, { - method: "POST", - body: JSON.stringify({ - what: "user", - which: [], - data: user, - }), - }); - - if (res.status === 201) { - return res.headers.get("Location"); - } -} - -export async function update(user, which = ["all"]) { - await fetchURL(`/api/users/${user.id}`, { - method: "PUT", - body: JSON.stringify({ - what: "user", - which: which, - data: user, - }), - }); -} - -export async function remove(id) { - await fetchURL(`/api/users/${id}`, { - method: "DELETE", - }); -} diff --git a/frontend/src/api/utils.js b/frontend/src/api/utils.js deleted file mode 100644 index 26b62d4f..00000000 --- a/frontend/src/api/utils.js +++ /dev/null @@ -1,84 +0,0 @@ -import { useAuthStore } from "@/stores/auth"; -import { renew, logout } from "@/utils/auth"; -import { baseURL } from "@/utils/constants"; -import { encodePath } from "@/utils/url"; - -export async function fetchURL(url, opts, auth = true) { - const authStore = useAuthStore(); - - opts = opts || {}; - opts.headers = opts.headers || {}; - - let { headers, ...rest } = opts; - let res; - try { - res = await fetch(`${baseURL}${url}`, { - headers: { - "X-Auth": authStore.jwt, - ...headers, - }, - ...rest, - }); - } catch { - const error = new Error("000 No connection"); - error.status = 0; - - throw error; - } - - if (auth && res.headers.get("X-Renew-Token") === "true") { - await renew(authStore.jwt); - } - - if (res.status < 200 || res.status > 299) { - const error = new Error(await res.text()); - error.status = res.status; - - if (auth && res.status == 401) { - logout(); - } - - throw error; - } - - return res; -} - -export async function fetchJSON(url, opts) { - const res = await fetchURL(url, opts); - - if (res.status === 200) { - return res.json(); - } else { - throw new Error(res.status); - } -} - -export function removePrefix(url) { - url = url.split("/").splice(2).join("/"); - - if (url === "") url = "/"; - if (url[0] !== "/") url = "/" + url; - return url; -} - -export function createURL(endpoint, params = {}, auth = true) { - const authStore = useAuthStore(); - - let prefix = baseURL; - if (!prefix.endsWith("/")) { - prefix = prefix + "/"; - } - const url = new URL(prefix + encodePath(endpoint), origin); - - const searchParams = { - ...(auth && { auth: authStore.jwt }), - ...params, - }; - - for (const key in searchParams) { - url.searchParams.set(key, searchParams[key]); - } - - return url.toString(); -} diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 31035b49..098b1be6 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -1,4 +1,4 @@ -import { createRouter, createWebHistory } from "vue-router"; +import { RouteLocation, createRouter, createWebHistory } from "vue-router"; import Login from "@/views/Login.vue"; import Layout from "@/views/Layout.vue"; import Files from "@/views/Files.vue"; @@ -144,7 +144,7 @@ const routes = [ }, { path: "/:catchAll(.*)*", - redirect: (to) => `/files/${[...to.params.catchAll].join("/")}`, + redirect: (to: RouteLocation) => `/files/${[...to.params.catchAll].join("/")}`, }, ]; @@ -156,7 +156,7 @@ async function initAuth() { } if (recaptcha) { - await new Promise((resolve) => { + await new Promise((resolve) => { const check = () => { if (typeof window.grecaptcha === "undefined") { setTimeout(check, 100); @@ -175,10 +175,11 @@ const router = createRouter({ routes, }); -router.beforeResolve(async (to, from, next) => { +router.beforeResolve(async (to: RouteLocation, from, next) => { let title; try { // this should not fail after we finished the migration + // @ts-ignore title = i18n.global.t(titles[to.name]); } catch (error) { console.error(error); @@ -187,14 +188,14 @@ router.beforeResolve(async (to, from, next) => { document.title = title + " - " + name; /*** RTL related settings per route ****/ - const rtlSet = document.querySelector("body").classList.contains("rtl"); + const rtlSet = document.querySelector("body")?.classList.contains("rtl"); const shouldSetRtl = rtlLanguages.includes(i18n.global.locale); switch (true) { case shouldSetRtl && !rtlSet: - document.querySelector("body").classList.add("rtl"); + document.querySelector("body")?.classList.add("rtl"); break; case !shouldSetRtl && rtlSet: - document.querySelector("body").classList.remove("rtl"); + document.querySelector("body")?.classList.remove("rtl"); break; } @@ -225,7 +226,7 @@ router.beforeResolve(async (to, from, next) => { } if (to.matched.some((record) => record.meta.requiresAdmin)) { - if (!authStore.user.perm.admin) { + if (authStore.user === null || !authStore.user.perm.admin) { next({ path: "/403" }); return; } diff --git a/frontend/src/stores/auth.ts b/frontend/src/stores/auth.ts index 7b57ef27..985010b3 100644 --- a/frontend/src/stores/auth.ts +++ b/frontend/src/stores/auth.ts @@ -5,7 +5,10 @@ import { cloneDeep } from "lodash-es"; export const useAuthStore = defineStore("auth", { // convert to a function - state: () => ({ + state: (): { + user: user | null, + jwt: string + } => ({ user: null, jwt: "", }), @@ -15,7 +18,7 @@ export const useAuthStore = defineStore("auth", { }, actions: { // no context as first argument, use `this` instead - setUser(value) { + setUser(value: user) { if (value === null) { this.user = null; return; @@ -23,19 +26,23 @@ export const useAuthStore = defineStore("auth", { const locale = value.locale || detectLocale(); dayjs.locale(locale); + // @ts-ignore Don't know how to fix this yet i18n.global.locale.value = locale; this.user = value; }, - updateUser(value) { + updateUser(value: user) { if (typeof value !== "object") return; - for (let field in value) { + let field: userKey + for (field in value) { if (field === "locale") { const locale = value[field]; dayjs.locale(locale); + // @ts-ignore Don't know how to fix this yet i18n.global.locale.value = locale; } + // @ts-ignore to fix this.user[field] = cloneDeep(value[field]); } }, diff --git a/frontend/src/stores/file.ts b/frontend/src/stores/file.ts index 893f6948..045f6e2c 100644 --- a/frontend/src/stores/file.ts +++ b/frontend/src/stores/file.ts @@ -2,7 +2,14 @@ import { defineStore } from "pinia"; export const useFileStore = defineStore("file", { // convert to a function - state: () => ({ + state: (): { + req: req, + oldReq: req, + reload: boolean, + selected: any[], + multiple: boolean, + isFiles: boolean + } => ({ req: {}, oldReq: {}, reload: false, @@ -29,11 +36,11 @@ export const useFileStore = defineStore("file", { toggleMultiple() { this.multiple = !this.multiple; }, - updateRequest(value) { + updateRequest(value: req) { this.oldReq = this.req; this.req = value; }, - removeSelected(value) { + removeSelected(value: any) { let i = this.selected.indexOf(value); if (i === -1) return; this.selected.splice(i, 1); diff --git a/frontend/src/stores/index.ts b/frontend/src/stores/index.ts index 3ab005bc..7f52aa28 100644 --- a/frontend/src/stores/index.ts +++ b/frontend/src/stores/index.ts @@ -1,7 +1,8 @@ import { createPinia as _createPinia } from "pinia"; import { markRaw } from "vue"; +import { Router } from "vue-router"; -export default function createPinia(router) { +export default function createPinia(router: Router) { const pinia = _createPinia(); pinia.use(({ store }) => { store.router = markRaw(router); diff --git a/frontend/src/stores/layout.ts b/frontend/src/stores/layout.ts index 267a27e9..1ca27243 100644 --- a/frontend/src/stores/layout.ts +++ b/frontend/src/stores/layout.ts @@ -4,7 +4,13 @@ import { defineStore } from "pinia"; export const useLayoutStore = defineStore("layout", { // convert to a function - state: () => ({ + state: (): { + loading: boolean, + show: string | null | boolean, + showConfirm: boolean | null, + showAction: boolean | null, + showShell: boolean | null + } => ({ loading: false, show: null, showConfirm: null, @@ -19,7 +25,7 @@ export const useLayoutStore = defineStore("layout", { toggleShell() { this.showShell = !this.showShell; }, - showHover(value) { + showHover(value: LayoutValue) { if (typeof value !== "object") { this.show = value; return; diff --git a/frontend/src/stores/upload.ts b/frontend/src/stores/upload.ts index 95eb4dbc..fe3d8d7c 100644 --- a/frontend/src/stores/upload.ts +++ b/frontend/src/stores/upload.ts @@ -6,14 +6,21 @@ import buttons from "@/utils/buttons"; const UPLOADS_LIMIT = 5; -const beforeUnload = (event) => { +const beforeUnload = (event: Event) => { event.preventDefault(); - event.returnValue = ""; + // To remove >> is deprecated + // event.returnValue = ""; }; export const useUploadStore = defineStore("upload", { // convert to a function - state: () => ({ + state: (): { + id: number, + sizes: any[], + progress: any[], + queue: any[], + uploads: uploads + } => ({ id: 0, sizes: [], progress: [], @@ -29,7 +36,8 @@ export const useUploadStore = defineStore("upload", { const totalSize = state.sizes.reduce((a, b) => a + b, 0); - const sum = state.progress.reduce((acc, val) => acc + val); + // @ts-ignore + const sum: number = state.progress.reduce((acc, val) => acc + val); return Math.ceil((sum / totalSize) * 100); }, filesInUploadCount: (state) => { @@ -64,8 +72,9 @@ export const useUploadStore = defineStore("upload", { }, actions: { // no context as first argument, use `this` instead - setProgress({ id, loaded }) { + setProgress(obj: { id: number, loaded: boolean }) { // Vue.set(this.progress, id, loaded); + const { id, loaded } = obj this.progress[id] = loaded; }, reset() { @@ -73,7 +82,7 @@ export const useUploadStore = defineStore("upload", { this.sizes = []; this.progress = []; }, - addJob(item) { + addJob(item: item) { this.queue.push(item); this.sizes[this.id] = item.file.size; this.id++; @@ -84,11 +93,11 @@ export const useUploadStore = defineStore("upload", { // Vue.set(this.uploads, item.id, item); this.uploads[item.id] = item; }, - removeJob(id) { + removeJob(id: number) { // Vue.delete(this.uploads, id); delete this.uploads[id]; }, - upload(item) { + upload(item: item) { let uploadsCount = Object.keys(this.uploads).length; let isQueueEmpty = this.queue.length == 0; @@ -102,8 +111,8 @@ export const useUploadStore = defineStore("upload", { this.addJob(item); this.processUploads(); }, - finishUpload(item) { - this.setProgress({ id: item.id, loaded: item.file.size }); + finishUpload(item: item) { + this.setProgress({ id: item.id, loaded: (item.file.size > 0) }); this.removeJob(item.id); this.processUploads(); }, diff --git a/frontend/src/types/global.d.ts b/frontend/src/types/global.d.ts index 8766bd75..8f8d3240 100644 --- a/frontend/src/types/global.d.ts +++ b/frontend/src/types/global.d.ts @@ -3,5 +3,6 @@ export {}; declare global { interface Window { FileBrowser: any; + grecaptcha: any } } \ No newline at end of file diff --git a/frontend/src/utils/auth.ts b/frontend/src/utils/auth.ts index 8e5fbcba..3715bfd1 100644 --- a/frontend/src/utils/auth.ts +++ b/frontend/src/utils/auth.ts @@ -3,9 +3,9 @@ import router from "@/router"; import jwt_decode from "jwt-decode"; import { baseURL } from "./constants"; -export function parseToken(token) { +export function parseToken(token: string) { // falsy or malformed jwt will throw InvalidTokenError - const data = jwt_decode(token); + const data = jwt_decode<{[key:string]: any, user: user}>(token); document.cookie = `auth=${token}; Path=/; SameSite=Strict;`; @@ -19,7 +19,7 @@ export function parseToken(token) { export async function validateLogin() { try { if (localStorage.getItem("jwt")) { - await renew(localStorage.getItem("jwt")); + await renew(localStorage.getItem("jwt")); } } catch (error) { console.warn("Invalid JWT token in storage"); // eslint-disable-line @@ -27,7 +27,7 @@ export async function validateLogin() { } } -export async function login(username, password, recaptcha) { +export async function login(username: string, password: string, recaptcha: string) { const data = { username, password, recaptcha }; const res = await fetch(`${baseURL}/api/login`, { @@ -47,7 +47,7 @@ export async function login(username, password, recaptcha) { } } -export async function renew(jwt) { +export async function renew(jwt: string) { const res = await fetch(`${baseURL}/api/renew`, { method: "POST", headers: { @@ -64,7 +64,7 @@ export async function renew(jwt) { } } -export async function signup(username, password) { +export async function signup(username: string, password: string) { const data = { username, password }; const res = await fetch(`${baseURL}/api/signup`, { @@ -76,6 +76,7 @@ export async function signup(username, password) { }); if (res.status !== 200) { + // @ts-ignore still need to fix these errors throw new Error(res.status); } } @@ -86,6 +87,6 @@ export function logout() { const authStore = useAuthStore(); authStore.clearUser(); - localStorage.setItem("jwt", null); + localStorage.setItem("jwt", ''); router.push({ path: "/login" }); } diff --git a/frontend/src/utils/buttons.ts b/frontend/src/utils/buttons.ts index 1c6bdeee..e115c8c1 100644 --- a/frontend/src/utils/buttons.ts +++ b/frontend/src/utils/buttons.ts @@ -1,5 +1,5 @@ -function loading(button) { - let el = document.querySelector(`#${button}-button > i`); +function loading(button: string) { + let el: HTMLButtonElement | null = document.querySelector(`#${button}-button > i`); if (el === undefined || el === null) { console.log("Error getting button " + button); // eslint-disable-line @@ -11,53 +11,62 @@ function loading(button) { } el.dataset.icon = el.innerHTML; - el.style.opacity = 0; + el.style.opacity = "0"; setTimeout(() => { - el.classList.add("spin"); - el.innerHTML = "autorenew"; - el.style.opacity = 1; + if(el) { + el.classList.add("spin"); + el.innerHTML = "autorenew"; + el.style.opacity = "1"; + } }, 100); } -function done(button) { - let el = document.querySelector(`#${button}-button > i`); +function done(button: string) { + let el: HTMLButtonElement | null = document.querySelector(`#${button}-button > i`); if (el === undefined || el === null) { console.log("Error getting button " + button); // eslint-disable-line return; } - el.style.opacity = 0; + el.style.opacity = "0"; setTimeout(() => { - el.classList.remove("spin"); - el.innerHTML = el.dataset.icon; - el.style.opacity = 1; + if(el !== null) { + el.classList.remove("spin"); + el.innerHTML = el?.dataset?.icon || ""; + el.style.opacity = "1"; + } + + }, 100); } -function success(button) { - let el = document.querySelector(`#${button}-button > i`); +function success(button: string) { + let el: HTMLButtonElement | null = document.querySelector(`#${button}-button > i`); if (el === undefined || el === null) { console.log("Error getting button " + button); // eslint-disable-line return; } - el.style.opacity = 0; + el.style.opacity = "0"; setTimeout(() => { - el.classList.remove("spin"); - el.innerHTML = "done"; - el.style.opacity = 1; - + if(el !== null) { + el.classList.remove("spin"); + el.innerHTML = "done"; + el.style.opacity = "1"; + } setTimeout(() => { - el.style.opacity = 0; + if(el) el.style.opacity = "0"; setTimeout(() => { - el.innerHTML = el.dataset.icon; - el.style.opacity = 1; + if(el !== null) { + el.innerHTML = el?.dataset?.icon || ""; + el.style.opacity = "1"; + } }, 100); }, 500); }, 100); diff --git a/frontend/src/utils/cookie.ts b/frontend/src/utils/cookie.ts index 72d59be4..7feb4265 100644 --- a/frontend/src/utils/cookie.ts +++ b/frontend/src/utils/cookie.ts @@ -1,4 +1,4 @@ -export default function (name) { +export default function (name: string) { let re = new RegExp( "(?:(?:^|.*;\\s*)" + name + "\\s*\\=\\s*([^;]*).*$)|^.*$" ); diff --git a/frontend/src/utils/css.ts b/frontend/src/utils/css.ts index 405c0dd2..7877f57d 100644 --- a/frontend/src/utils/css.ts +++ b/frontend/src/utils/css.ts @@ -1,4 +1,4 @@ -export default function getRule(rules) { +export default function getRule(rules: any) { for (let i = 0; i < rules.length; i++) { rules[i] = rules[i].toLowerCase(); } diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts index 0b6cd571..5646c03d 100644 --- a/frontend/src/utils/index.ts +++ b/frontend/src/utils/index.ts @@ -1 +1 @@ -export * from "./funcs"; +// export * from "./funcs"; diff --git a/frontend/src/utils/upload.ts b/frontend/src/utils/upload.ts index 88a08b7b..a4d216db 100644 --- a/frontend/src/utils/upload.ts +++ b/frontend/src/utils/upload.ts @@ -1,7 +1,7 @@ import { useUploadStore } from "@/stores/upload"; import url from "@/utils/url"; -export function checkConflict(files, items) { +export function checkConflict(files: file[], items: item[]) { if (typeof items === "undefined" || items === null) { items = []; } @@ -21,6 +21,7 @@ export function checkConflict(files, items) { } let res = items.findIndex(function hasConflict(element) { + // @ts-ignore Don't know what this does return element.name === this; }, name); @@ -33,10 +34,10 @@ export function checkConflict(files, items) { return conflict; } -export function scanFiles(dt) { +export function scanFiles(dt: {[key: string]: any, item: item}) { return new Promise((resolve) => { let reading = 0; - const contents = []; + const contents: any[] = []; if (dt.items !== undefined) { for (let item of dt.items) { @@ -52,10 +53,10 @@ export function scanFiles(dt) { resolve(dt.files); } - function readEntry(entry, directory = "") { + function readEntry(entry: any, directory = "") { if (entry.isFile) { reading++; - entry.file((file) => { + entry.file((file: file) => { reading--; file.fullPath = `${directory}${file.name}`; @@ -79,10 +80,10 @@ export function scanFiles(dt) { } } - function readReaderContent(reader, directory) { + function readReaderContent(reader: any, directory: string) { reading++; - reader.readEntries(function (entries) { + reader.readEntries(function (entries: any[]) { reading--; if (entries.length > 0) { for (const entry of entries) { @@ -100,7 +101,7 @@ export function scanFiles(dt) { }); } -function detectType(mimetype) { +function detectType(mimetype: string): uploadType { if (mimetype.startsWith("video")) return "video"; if (mimetype.startsWith("audio")) return "audio"; if (mimetype.startsWith("image")) return "image"; @@ -109,7 +110,7 @@ function detectType(mimetype) { return "blob"; } -export function handleFiles(files, base, overwrite = false) { +export function handleFiles(files: file[], base: string, overwrite = false) { const uploadStore = useUploadStore(); for (let i = 0; i < files.length; i++) { diff --git a/frontend/src/utils/url.ts b/frontend/src/utils/url.ts index bf30a17c..bcd8609f 100644 --- a/frontend/src/utils/url.ts +++ b/frontend/src/utils/url.ts @@ -1,4 +1,4 @@ -export function removeLastDir(url) { +export function removeLastDir(url: string) { var arr = url.split("/"); if (arr.pop() === "") { arr.pop(); @@ -9,7 +9,7 @@ export function removeLastDir(url) { // this function is taken from mozilla // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Examples -export function encodeRFC5987ValueChars(str) { +export function encodeRFC5987ValueChars(str: string) { return ( encodeURIComponent(str) // The following creates the sequences %27 %28 %29 %2A (Note that @@ -28,7 +28,7 @@ export function encodeRFC5987ValueChars(str) { ); } -export function encodePath(str) { +export function encodePath(str: string) { return str .split("/") .map((v) => encodeURIComponent(v))