Skip to content

Commit aeaba86

Browse files
authored
feat: nested folders + each user see only his files (#10)
1 parent e69d621 commit aeaba86

9 files changed

Lines changed: 70 additions & 14 deletions

File tree

eslint.config.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import prettier from 'eslint-config-prettier';
2-
import { fileURLToPath } from 'node:url';
31
import { includeIgnoreFile } from '@eslint/compat';
42
import js from '@eslint/js';
3+
import prettier from 'eslint-config-prettier';
54
import svelte from 'eslint-plugin-svelte';
65
import { defineConfig } from 'eslint/config';
76
import globals from 'globals';
7+
import { fileURLToPath } from 'node:url';
88
import ts from 'typescript-eslint';
99
import svelteConfig from './svelte.config.js';
1010

@@ -31,7 +31,6 @@ export default defineConfig(
3131

3232
languageOptions: {
3333
parserOptions: {
34-
projectService: true,
3534
extraFileExtensions: ['.svelte'],
3635
parser: ts.parser,
3736
svelteConfig
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*eslint-disable @typescript-eslint/no-explicit-any*/
2+
3+
import { Kysely } from 'kysely';
4+
5+
export const up = async (conn: Kysely<any>) => {
6+
await conn.schema
7+
.alterTable('files')
8+
.addColumn('path', 'varchar(255)', (cb) => cb.defaultTo(''))
9+
.execute();
10+
};
11+
12+
export const down = async (conn: Kysely<any>) => {
13+
await conn.schema.alterTable('files').dropColumn('path').execute();
14+
};

src/lib/server/functions.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ export const uploadFile = async (fd: FormData, user_id: number) => {
6363
const buffer = Buffer.from(await file.arrayBuffer());
6464
const filePath = path.join(uploadDir, filename);
6565

66+
const relativePath = fd.get('path') as string | null;
67+
6668
await writeFile(filePath, buffer);
6769

6870
await conn
@@ -72,7 +74,8 @@ export const uploadFile = async (fd: FormData, user_id: number) => {
7274
original_name: originalName,
7375
mime_type: file.type,
7476
size: file.size,
75-
uploaded_by: user_id
77+
uploaded_by: user_id,
78+
path: relativePath || ''
7679
})
7780
.execute();
7881

src/lib/server/routes/albums.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import type { SuccessApiResponse } from '$/types/types';
12
import { type ErrorApiResponse } from '@patrick115/sveltekitapi';
23
import { v4 as uuid } from 'uuid';
34
import { z } from 'zod';
4-
import type { SuccessApiResponse } from '$/types/types';
55
import { authProcedure, procedure } from '../api';
66
import { conn } from '../variables';
77

@@ -17,6 +17,7 @@ export const albumsRouter = {
1717
.selectFrom('files')
1818
.select(['id', 'mime_type'])
1919
.where('id', 'in', input.fileIds)
20+
.where('uploaded_by', '=', ctx.id)
2021
.execute();
2122

2223
if (files.length !== input.fileIds.length) {

src/lib/server/routes/files.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ export const filesRouter = {
3030
orderDir: z.enum(['asc', 'desc']).default('desc'),
3131
type: z.string().optional()
3232
})
33-
).query(async ({ input }) => {
34-
let query = conn.selectFrom('files').selectAll();
33+
).query(async ({ input, ctx }) => {
34+
let query = conn.selectFrom('files').selectAll().where('uploaded_by', '=', ctx.id);
3535

3636
if (input.type) {
3737
query = query.where('mime_type', 'like', `${input.type}%`);
@@ -47,11 +47,12 @@ export const filesRouter = {
4747
data
4848
} satisfies SuccessApiResponse<typeof data>;
4949
}),
50-
get: authProcedure.POST.input(z.object({ id: z.string() })).query(async ({ input }) => {
50+
get: authProcedure.POST.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => {
5151
const file = await conn
5252
.selectFrom('files')
5353
.selectAll()
5454
.where('id', '=', input.id)
55+
.where('uploaded_by', '=', ctx.id)
5556
.executeTakeFirst();
5657
if (!file) {
5758
return {
@@ -65,11 +66,12 @@ export const filesRouter = {
6566
data: file
6667
} satisfies SuccessApiResponse<typeof file>;
6768
}),
68-
delete: authProcedure.POST.input(z.object({ id: z.string() })).query(async ({ input }) => {
69+
delete: authProcedure.POST.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => {
6970
const file = await conn
7071
.selectFrom('files')
7172
.selectAll()
7273
.where('id', '=', input.id)
74+
.where('uploaded_by', '=', ctx.id)
7375
.executeTakeFirst();
7476
if (!file) {
7577
return {

src/lib/server/routes/folders.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ export const foldersRouter = {
122122
.selectFrom('files')
123123
.select(['id'])
124124
.where('id', 'in', input.fileIds)
125+
.where('uploaded_by', '=', ctx.id)
125126
.execute();
126127

127128
if (files.length !== input.fileIds.length) {

src/routes/+page.svelte

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,13 @@
9595
9696
const formData = new FormData();
9797
formData.append('file', file);
98+
if (file.webkitRelativePath) {
99+
const pathParts = file.webkitRelativePath.split('/');
100+
pathParts.pop(); // Remove the filename
101+
formData.append('path', pathParts.join('/'));
102+
} else {
103+
formData.append('path', '');
104+
}
98105
99106
try {
100107
const res = await API.files.upload(formData);
@@ -148,11 +155,33 @@
148155
multiple
149156
onchange={handleFileSelect}
150157
/>
158+
<input
159+
type="file"
160+
id="folderInput"
161+
class="hidden"
162+
webkitdirectory
163+
multiple
164+
onchange={handleFileSelect}
165+
/>
151166
<div class="flex flex-col items-center space-y-2">
152167
<UploadCloud class="h-12 w-12 text-muted-foreground" />
153168
<div class="text-sm text-muted-foreground">
154-
<span class="font-medium text-primary hover:text-primary/80"
155-
>Upload a file</span
169+
<button
170+
type="button"
171+
class="font-medium text-primary hover:text-primary/80"
172+
onclick={(e) => {
173+
e.stopPropagation();
174+
document.getElementById('fileInput')?.click();
175+
}}>Upload files</button
176+
>
177+
or
178+
<button
179+
type="button"
180+
class="font-medium text-primary hover:text-primary/80"
181+
onclick={(e) => {
182+
e.stopPropagation();
183+
document.getElementById('folderInput')?.click();
184+
}}>Upload a folder</button
156185
>
157186
or drag and drop
158187
</div>

src/routes/api/folder/[id]/+server.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export const GET: RequestHandler = async ({ params, request }) => {
6161
const files = await conn
6262
.selectFrom('folder_files')
6363
.innerJoin('files', 'files.id', 'folder_files.file_id')
64-
.select(['files.id', 'files.original_name', 'files.size'])
64+
.select(['files.id', 'files.original_name', 'files.size', 'files.path'])
6565
.where('folder_files.folder_id', '=', id)
6666
.execute();
6767

@@ -78,7 +78,10 @@ export const GET: RequestHandler = async ({ params, request }) => {
7878
const fileStat = await stat(filePath).catch(() => null);
7979

8080
if (fileStat) {
81-
const entry = pack.entry({ name: file.original_name, size: fileStat.size });
81+
const tarName = file.path
82+
? path.posix.join(file.path, file.original_name)
83+
: file.original_name;
84+
const entry = pack.entry({ name: tarName, size: fileStat.size });
8285
const rs = createReadStream(filePath);
8386
rs.pipe(entry);
8487
await new Promise<void>((resolve, reject) => {
@@ -139,6 +142,8 @@ export const POST: RequestHandler = async ({ params, request }) => {
139142
}
140143

141144
const originalName = path.basename(header.name);
145+
const relativePath = path.dirname(header.name);
146+
const dbPath = relativePath === '.' ? '' : relativePath;
142147

143148
// Check if file with same name exists in folder
144149
const existingFile = await conn
@@ -221,7 +226,8 @@ export const POST: RequestHandler = async ({ params, request }) => {
221226
original_name: originalName,
222227
mime_type: mimeType,
223228
size: buffer.length,
224-
uploaded_by: user.id
229+
uploaded_by: user.id,
230+
path: dbPath
225231
})
226232
.execute();
227233

src/types/database.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export interface Files {
3434
id: string;
3535
mime_type: string;
3636
original_name: string;
37+
path: Generated<string | null>;
3738
size: number;
3839
upload_date: Generated<Date>;
3940
uploaded_by: Generated<number | null>;

0 commit comments

Comments
 (0)