Skip to content

Commit

Permalink
[PB-2005]:(feature) Allow to trash a file and folder from WebDav
Browse files Browse the repository at this point in the history
  • Loading branch information
PixoDev committed Apr 15, 2024
1 parent 9a106fc commit d3a2bfe
Show file tree
Hide file tree
Showing 14 changed files with 105 additions and 6 deletions.
13 changes: 13 additions & 0 deletions src/services/database/drive-database-manager.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { DriveFile } from './drive-file/drive-file.domain';
import { DriveFolder } from './drive-folder/drive-folder.domain';
import DriveFileModel from './drive-file/drive-file.model';
import DriveFolderModel from './drive-folder/drive-folder.model';
import { DriveFileAttributes } from './drive-file/drive-file.attributes';
import { DriveFolderAttributes } from './drive-folder/drive-folder.attributes';

export class DriveDatabaseManager {
private static readonly sequelize: Sequelize = new Sequelize({
Expand Down Expand Up @@ -89,4 +91,15 @@ export class DriveDatabaseManager {

return WebDavUtils.joinURL(parentPath, folderName, '/');
};

updateFile = async (id: number, payload: Partial<Pick<DriveFileAttributes, 'status' | 'relativePath' | 'name'>>) => {
return await this.driveFileRepository.updateFile(id, payload);
};

updateFolder = async (
id: number,
payload: Partial<Pick<DriveFolderAttributes, 'name' | 'relativePath' | 'status'>>,
) => {
return await this.driveFolderRepository.updateFolder(id, payload);
};
}
16 changes: 16 additions & 0 deletions src/services/database/drive-file/drive-file.repository.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { DriveFileItem } from '../../../types/drive.types';
import { DriveFileModel } from './drive-file.model';
import { DriveFile } from './drive-file.domain';
import { DriveFileAttributes } from './drive-file.attributes';

export class DriveFileRepository {
findByRelativePath = async (relativePath: DriveFile['relativePath']): Promise<DriveFile | null> => {
Expand Down Expand Up @@ -30,6 +31,21 @@ export class DriveFileRepository {
return this.toDomain(newFile);
};

updateFile = async (
id: number,
driveFileItemAttributes: Partial<Pick<DriveFileAttributes, 'status' | 'name' | 'relativePath'>>,
): Promise<DriveFile | null> => {
const existingFile = await DriveFileModel.findByPk(id);
if (!existingFile) return null;

existingFile.updatedAt = new Date();
existingFile.status = driveFileItemAttributes.status ?? existingFile.status;
existingFile.name = driveFileItemAttributes.name ?? existingFile.name;
existingFile.relativePath = driveFileItemAttributes.relativePath ?? existingFile.relativePath;

return existingFile.save();
};

toDomain = (model: DriveFileModel): DriveFile => {
const driveFile = DriveFile.build({
...model.toJSON(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export interface DriveFolderAttributes {
id: number;
name: string;
uuid: string;
status: 'EXISTS' | 'TRASHED';
relativePath: string;
parentId: number | null;
createdAt: Date;
Expand Down
5 changes: 4 additions & 1 deletion src/services/database/drive-folder/drive-folder.domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ export class DriveFolder implements DriveFolderAttributes {
parentId: number | null;
createdAt: Date;
updatedAt: Date;
status: DriveFolderAttributes['status'];

constructor({ id, name, uuid, relativePath, parentId, createdAt, updatedAt }: DriveFolderAttributes) {
constructor({ id, name, uuid, relativePath, parentId, createdAt, updatedAt, status }: DriveFolderAttributes) {
this.id = id;
this.name = name;
this.uuid = uuid;
this.relativePath = relativePath;
this.parentId = parentId;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
this.status = status;
}

static build(folder: DriveFolderAttributes): DriveFolder {
Expand All @@ -28,6 +30,7 @@ export class DriveFolder implements DriveFolderAttributes {
id: this.id,
name: this.name,
uuid: this.uuid,
status: this.status,
relativePath: this.relativePath,
parentId: this.parentId,
createdAt: this.createdAt,
Expand Down
3 changes: 3 additions & 0 deletions src/services/database/drive-folder/drive-folder.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export class DriveFolderModel extends Model implements DriveFolderAttributes {
@Column(DataType.STRING)
declare name: string;

@Column(DataType.STRING)
declare status: 'EXISTS' | 'TRASHED';

@Unique
@Column(DataType.UUIDV4)
declare uuid: string;
Expand Down
16 changes: 16 additions & 0 deletions src/services/database/drive-folder/drive-folder.repository.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DriveFolderItem } from '../../../types/drive.types';
import { DriveFolderAttributes } from './drive-folder.attributes';
import { DriveFolder } from './drive-folder.domain';
import { DriveFolderModel } from './drive-folder.model';

Expand Down Expand Up @@ -30,6 +31,21 @@ export class DriveFolderRepository {
return this.toDomain(newFolder);
};

updateFolder = async (
id: number,
driveFolderItemAttributes: Partial<Pick<DriveFolderAttributes, 'name' | 'relativePath' | 'status'>>,
): Promise<DriveFolder | null> => {
const existingFolder = await DriveFolderModel.findByPk(id);
if (!existingFolder) return null;

existingFolder.updatedAt = new Date();
existingFolder.status = driveFolderItemAttributes.status ?? existingFolder.status;
existingFolder.name = driveFolderItemAttributes.name ?? existingFolder.name;
existingFolder.relativePath = driveFolderItemAttributes.relativePath ?? existingFolder.relativePath;

return existingFolder.save();
};

toDomain = (model: DriveFolderModel): DriveFolder => {
const driveFolder = DriveFolder.build({
...model.toJSON(),
Expand Down
1 change: 1 addition & 0 deletions src/types/drive.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ export type DriveFolderItem = Pick<DriveFolderData, 'name' | 'bucket' | 'id' | '
uuid: string;
createdAt: Date;
updatedAt: Date;
status: 'EXISTS' | 'TRASHED';
};
1 change: 1 addition & 0 deletions src/utils/drive.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class DriveUtils {
uuid: folderMeta.uuid,
id: folderMeta.id,
bucket: folderMeta.bucket,
status: folderMeta.deleted || folderMeta.removed ? 'TRASHED' : 'EXISTS',
name: folderMeta.plainName ?? folderMeta.name,
encryptedName: folderMeta.name,
parentId: folderMeta.parentId,
Expand Down
34 changes: 30 additions & 4 deletions src/webdav/handlers/DELETE.handler.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
import { Request, Response } from 'express';
import { WebDavMethodHandler } from '../../types/webdav.types';
import { NotImplementedError } from '../../utils/errors.utils';
import { NotFoundError } from '../../utils/errors.utils';
import { WebDavUtils } from '../../utils/webdav.utils';
import { DriveDatabaseManager } from '../../services/database/drive-database-manager.service';
import { TrashService } from '../../services/drive/trash.service';

export class DELETERequestHandler implements WebDavMethodHandler {
async handle() {
throw new NotImplementedError('DELETE is not implemented yet.');
}
constructor(private dependencies: { driveDatabaseManager: DriveDatabaseManager; trashService: TrashService }) {}
handle = async (req: Request, res: Response) => {
const resource = await WebDavUtils.getRequestedResource(req, this.dependencies.driveDatabaseManager);
const databaseItem = await this.dependencies.driveDatabaseManager.findByRelativePath(resource.url);

if (!databaseItem) throw new NotFoundError('Resource not found');

await this.dependencies.trashService.trashItems({
items: [{ type: resource.type, uuid: databaseItem.uuid }],
});

if (resource.type === 'folder') {
await this.dependencies.driveDatabaseManager.updateFolder(databaseItem.id, {
status: 'TRASHED',
});
}

if (resource.type === 'file') {
await this.dependencies.driveDatabaseManager.updateFile(databaseItem.id, {
status: 'TRASHED',
});
}

res.status(204).send();
};
}
4 changes: 4 additions & 0 deletions src/webdav/handlers/PROPFIND.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class PROPFINDRequestHandler implements WebDavMethodHandler {
const rootFolder = await this.dependencies.driveFolderService.getFolderMetaById(req.user.rootFolderId);
await this.dependencies.driveDatabaseManager.createFolder({
name: '',
status: 'EXISTS',
encryptedName: rootFolder.name,
bucket: rootFolder.bucket,
id: rootFolder.id,
Expand Down Expand Up @@ -127,6 +128,7 @@ export class PROPFINDRequestHandler implements WebDavMethodHandler {
{
name: folder.plainName,
bucket: folder.bucket,
status: folder.deleted || folder.removed ? 'TRASHED' : 'EXISTS',
createdAt: new Date(folder.createdAt),
updatedAt: new Date(folder.updatedAt),
id: folder.id,
Expand All @@ -143,10 +145,12 @@ export class PROPFINDRequestHandler implements WebDavMethodHandler {
...folder,
name: folder.plainName,
encryptedName: folder.name,
status: folder.deleted || folder.removed ? 'TRASHED' : 'EXISTS',
});
});

const filesXML = folderContent.files.map((file) => {
console.log('FILE', file.status);
const fileRelativePath = WebDavUtils.joinURL(
relativePath,
file.type ? `${file.plainName}.${file.type}` : file.plainName,
Expand Down
2 changes: 2 additions & 0 deletions src/webdav/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { UploadService } from '../services/network/upload.service';
import { DownloadService } from '../services/network/download.service';
import { AuthService } from '../services/auth.service';
import { CryptoService } from '../services/crypto.service';
import { TrashService } from '../services/drive/trash.service';

dotenv.config();

Expand All @@ -31,6 +32,7 @@ const init = async () => {
DownloadService.instance,
AuthService.instance,
CryptoService.instance,
TrashService.instance,
)
.start()
.then()
Expand Down
12 changes: 11 additions & 1 deletion src/webdav/webdav-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { DELETERequestHandler } from './handlers/DELETE.handler';
import { PROPPATCHRequestHandler } from './handlers/PROPPATCH.handler';
import { MOVERequestHandler } from './handlers/MOVE.handler';
import { COPYRequestHandler } from './handlers/COPY.handler';
import { TrashService } from '../services/drive/trash.service';

export class WebDavServer {
constructor(
Expand All @@ -39,6 +40,7 @@ export class WebDavServer {
private downloadService: DownloadService,
private authService: AuthService,
private cryptoService: CryptoService,
private trashService: TrashService,
) {}

private async getNetwork() {
Expand Down Expand Up @@ -107,7 +109,15 @@ export class WebDavServer {
);

this.app.mkcol('*', asyncHandler(new MKCOLRequestHandler().handle));
this.app.delete('*', asyncHandler(new DELETERequestHandler().handle));
this.app.delete(
'*',
asyncHandler(
new DELETERequestHandler({
driveDatabaseManager: this.driveDatabaseManager,
trashService: this.trashService,
}).handle,
),
);
this.app.proppatch('*', asyncHandler(new PROPPATCHRequestHandler().handle));
this.app.move('*', asyncHandler(new MOVERequestHandler().handle));
this.app.copy('*', asyncHandler(new COPYRequestHandler().handle));
Expand Down
1 change: 1 addition & 0 deletions test/fixtures/drive.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const newFolderItem = (attributes?: Partial<DriveFolderItem>): DriveFolde
encryptedName: crypto.randomBytes(16).toString('hex'),
createdAt: new Date(),
updatedAt: new Date(),
status: 'EXISTS',
};
return { ...folder, ...attributes };
};
Expand Down
2 changes: 2 additions & 0 deletions test/webdav/webdav-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { AuthService } from '../../src/services/auth.service';
import { CryptoService } from '../../src/services/crypto.service';
import { ConfigKeys } from '../../src/types/config.types';
import { NetworkUtils } from '../../src/utils/network.utils';
import { TrashService } from '../../src/services/drive/trash.service';

describe('WebDav server', () => {
const sandbox = sinon.createSandbox();
Expand Down Expand Up @@ -52,6 +53,7 @@ describe('WebDav server', () => {
DownloadService.instance,
AuthService.instance,
CryptoService.instance,
TrashService.instance,
);
server.start();

Expand Down

0 comments on commit d3a2bfe

Please sign in to comment.