diff --git a/.gitignore b/.gitignore index a19f004..a547bf3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,24 @@ -# ---> Vue -# gitignore template for Vue.js projects -# -# Recommended template: Node.gitignore +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* -# TODO: where does this rule come from? -docs/_book - -# TODO: where does this rule come from? -test/ +node_modules +dist +dist-ssr +*.local +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/README.md b/README.md index 74ab4f2..33895ab 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ -# unloze-wrapper +# Vue 3 + TypeScript + Vite -Unloze Fast Download Wrapper \ No newline at end of file +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..edf5ef7 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "unloze-fast-download-wrapper", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@aws-sdk/client-s3": "^3.812.0", + "@mdi/font": "^7.4.47", + "cors": "^2.8.5", + "dotenv": "^16.5.0", + "express": "^5.1.0", + "path": "^0.12.7", + "url": "^0.11.4", + "vue": "^3.5.13", + "vuetify": "^3.8.0-beta.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.3", + "@vue/tsconfig": "^0.7.0", + "sass": "^1.87.0", + "sass-loader": "^16.0.5", + "typescript": "~5.8.3", + "vite": "^6.3.5", + "vue-tsc": "^2.2.8" + } +} diff --git a/public/UNLOZE_logo_blue.png b/public/UNLOZE_logo_blue.png new file mode 100644 index 0000000..1ecabf6 Binary files /dev/null and b/public/UNLOZE_logo_blue.png differ diff --git a/public/carbon.jpg b/public/carbon.jpg new file mode 100644 index 0000000..7b9c4fd Binary files /dev/null and b/public/carbon.jpg differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..e7d0595 Binary files /dev/null and b/public/favicon.ico differ diff --git a/server.js b/server.js new file mode 100644 index 0000000..57357ee --- /dev/null +++ b/server.js @@ -0,0 +1,79 @@ +import express from 'express'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import dotenv from 'dotenv'; +import cors from "cors"; +import { S3Client, ListObjectsV2Command } from "@aws-sdk/client-s3"; + +// Load environment variables +dotenv.config(); + +// Set up __dirname in ESM +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const app = express(); +const port = process.env.PORT || 3000; + +app.use(cors()); + +const s3 = new S3Client({ + region: "auto", + endpoint: process.env.R2_ENDPOINT, + credentials: { + accessKeyId: process.env.R2_ACCESS_KEY, + secretAccessKey: process.env.R2_SECRET_KEY, + }, +}); + +app.get("/api/list", async (req, res) => { + const prefix = req.query.prefix || ""; + const token = req.query.token; + const search = req.query.search?.toLowerCase(); + + try { + const response = await s3.send( + new ListObjectsV2Command({ + Bucket: process.env.R2_BUCKET, + Prefix: prefix, + ContinuationToken: token, + Delimiter: "/", + }) + ); + + const filteredFolders = (response.CommonPrefixes || []).filter( + (p) => !search || p.Prefix.toLowerCase().includes(search) + ); + + const filteredFiles = (response.Contents || []) + .filter((obj) => obj.Size > 0) + .filter((obj) => !search || obj.Key.toLowerCase().includes(search)); + + res.json({ + folders: filteredFolders.map((p) => p.Prefix), + files: filteredFiles.map((obj) => ({ + key: obj.Key, + size: obj.Size, + lastModified: obj.LastModified, + })), + nextToken: response.NextContinuationToken || null, + isTruncated: response.IsTruncated || false, + }); + } catch (err) { + console.error(err); + res.status(500).json({ error: "Failed to list objects" }); + } +}); + +// === STATIC FRONTEND === +app.use(express.static(path.join(__dirname, 'dist'))); + +// === Fallback to index.html for client-side routing === +app.get('/{*splat}', (req, res) => { + res.sendFile(path.join(__dirname, 'dist', 'index.html')); +}); + +// === START SERVER === +app.listen(port, () => { + console.log(`Server running on http://localhost:${port}`); +}); \ No newline at end of file diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..7d92469 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,11 @@ + + + diff --git a/src/components/DownloadList.vue b/src/components/DownloadList.vue new file mode 100644 index 0000000..5c6200a --- /dev/null +++ b/src/components/DownloadList.vue @@ -0,0 +1,227 @@ + + + diff --git a/src/main.css b/src/main.css new file mode 100644 index 0000000..affe09c --- /dev/null +++ b/src/main.css @@ -0,0 +1,20 @@ +/* src/main.css */ +@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap'); + +body { + margin: 0; + padding: 0; + font-family: 'Roboto', sans-serif; + overflow: hidden; +} + +#app, +.v-application { + background-image: url('/carbon.jpg'); + background-repeat: no-repeat; + background-position: center; + position: relative; + z-index: 0; + height: 100vh; + overflow-y: auto; +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..12d4697 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,9 @@ +import { createApp } from 'vue' +import App from './App.vue' +import { vuetify } from './plugins/vuetify' +import '@mdi/font/css/materialdesignicons.css' +import './main.css'; + +const app = createApp(App) +app.use(vuetify) +app.mount('#app') \ No newline at end of file diff --git a/src/plugins/vuetify.ts b/src/plugins/vuetify.ts new file mode 100644 index 0000000..ae71f7b --- /dev/null +++ b/src/plugins/vuetify.ts @@ -0,0 +1,34 @@ +// src/plugins/vuetify.ts +import 'vuetify/styles' +import { createVuetify } from 'vuetify' +import { aliases, mdi } from 'vuetify/iconsets/mdi' +import * as components from 'vuetify/components' +import * as directives from 'vuetify/directives' + +export const vuetify = createVuetify({ + components, + directives, + icons: { + defaultSet: 'mdi', + aliases, + sets: { mdi }, + }, + theme: { + defaultTheme: 'dark', + themes: { + dark: { + dark: true, + colors: { + background: '#121212', + surface: '#1E1E1E', + primary: '#BB86FC', + secondary: '#03DAC6', + error: '#CF6679', + info: '#2196F3', + success: '#4CAF50', + warning: '#FB8C00', + }, + }, + }, + }, +}) diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..3dbbc45 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,15 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..7f5ec3a --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + }, + "types": ["vuetify"], + "module": "ESNext", + "moduleResolution": "Node", + "target": "ESNext", + "jsx": "preserve", + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true, + "strict": true, + "skipLibCheck": true, + "noEmit": true + }, + "include": ["src"] +} \ No newline at end of file diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..9728af2 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo new file mode 100644 index 0000000..d44e1c8 --- /dev/null +++ b/tsconfig.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/app.vue","./src/main.ts","./src/vite-env.d.ts","./src/components/downloadlist.vue","./src/plugins/vuetify.ts"],"version":"5.8.3"} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..44b0d26 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [vue()], + server: { + proxy: { + '/fastdl': { + target: 'https://uk-fastdl.unloze.com', + changeOrigin: true, + rewrite: path => path.replace(/^\/fastdl/, '') + } + } + } +})