1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-11-24 08:12:24 +02:00

Server: Adding basic file manager

This commit is contained in:
Laurent Cozic 2020-12-28 16:37:12 +00:00
parent 2fda067034
commit 0e0de1207f
12 changed files with 188 additions and 5 deletions

View File

@ -1445,6 +1445,9 @@ packages/server/src/controllers/api/UserController.test.js.map
packages/server/src/controllers/factory.d.ts
packages/server/src/controllers/factory.js
packages/server/src/controllers/factory.js.map
packages/server/src/controllers/index/FileController.d.ts
packages/server/src/controllers/index/FileController.js
packages/server/src/controllers/index/FileController.js.map
packages/server/src/controllers/index/HomeController.d.ts
packages/server/src/controllers/index/HomeController.js
packages/server/src/controllers/index/HomeController.js.map
@ -1511,6 +1514,9 @@ packages/server/src/routes/api/sessions.js.map
packages/server/src/routes/default.d.ts
packages/server/src/routes/default.js
packages/server/src/routes/default.js.map
packages/server/src/routes/index/files.d.ts
packages/server/src/routes/index/files.js
packages/server/src/routes/index/files.js.map
packages/server/src/routes/index/home.d.ts
packages/server/src/routes/index/home.js
packages/server/src/routes/index/home.js.map

6
.gitignore vendored
View File

@ -1434,6 +1434,9 @@ packages/server/src/controllers/api/UserController.test.js.map
packages/server/src/controllers/factory.d.ts
packages/server/src/controllers/factory.js
packages/server/src/controllers/factory.js.map
packages/server/src/controllers/index/FileController.d.ts
packages/server/src/controllers/index/FileController.js
packages/server/src/controllers/index/FileController.js.map
packages/server/src/controllers/index/HomeController.d.ts
packages/server/src/controllers/index/HomeController.js
packages/server/src/controllers/index/HomeController.js.map
@ -1500,6 +1503,9 @@ packages/server/src/routes/api/sessions.js.map
packages/server/src/routes/default.d.ts
packages/server/src/routes/default.js
packages/server/src/routes/default.js.map
packages/server/src/routes/index/files.d.ts
packages/server/src/routes/index/files.js
packages/server/src/routes/index/files.js.map
packages/server/src/routes/index/home.d.ts
packages/server/src/routes/index/home.js
packages/server/src/routes/index/home.js.map

View File

@ -58,10 +58,10 @@ export default class FileController extends BaseController {
await this.putFileContent(sessionId, fileId, null);
}
public async getChildren(sessionId: string, fileId: string, pagination: Pagination): Promise<PaginatedFiles> {
public async getChildren(sessionId: string, dirId: string, pagination: Pagination): Promise<PaginatedFiles> {
const user = await this.initSession(sessionId);
const fileModel = this.models.file({ userId: user.id });
const parent: File = await fileModel.entityFromItemId(fileId);
const parent: File = await fileModel.entityFromItemId(dirId);
return fileModel.toApiOutput(await fileModel.childrens(parent.id, pagination));
}

View File

@ -7,6 +7,7 @@ import IndexLoginController from './index/LoginController';
import IndexHomeController from './index/HomeController';
import IndexProfileController from './index/ProfileController';
import IndexUserController from './index/UserController';
import IndexFileController from './index/FileController';
export class Controllers {
@ -48,6 +49,10 @@ export class Controllers {
return new IndexUserController(this.models_);
}
public indexFiles() {
return new IndexFileController(this.models_);
}
}
export default function(models: Models) {

View File

@ -0,0 +1,44 @@
import BaseController from '../BaseController';
import { View } from '../../services/MustacheService';
import defaultView from '../../utils/defaultView';
import { Pagination } from '../../models/utils/pagination';
import { File } from '../../db';
export default class FileController extends BaseController {
public async getIndex(sessionId: string, dirId: string, pagination: Pagination): Promise<View> {
const owner = await this.initSession(sessionId);
const user = await this.initSession(sessionId);
const fileModel = this.models.file({ userId: user.id });
const parent: File = dirId ? await fileModel.entityFromItemId(dirId) : await fileModel.userRootFile();
const paginatedFiles = await fileModel.childrens(parent.id, pagination);
const view: View = defaultView('files', owner);
view.content.paginatedFiles = paginatedFiles;
return view;
}
// public async getOne(sessionId: string, isNew: boolean, userIdOrString: string | User = null, error: any = null): Promise<View> {
// const owner = await this.initSession(sessionId);
// const userModel = this.models.user({ userId: owner.id });
// let user: User = {};
// if (typeof userIdOrString === 'string') {
// user = await userModel.load(userIdOrString as string);
// } else {
// user = userIdOrString as User;
// }
// const view: View = defaultView('user', owner);
// view.content.user = user;
// view.content.isNew = isNew;
// view.content.buttonTitle = isNew ? 'Create user' : 'Update profile';
// view.content.error = error;
// view.content.postUrl = `${baseUrl()}/users${isNew ? '/new' : `/${user.id}`}`;
// view.partials.push('errorBanner');
// return view;
// }
}

View File

@ -0,0 +1,56 @@
import { createUserAndSession, beforeAllDb, afterAllDb, beforeEachDb, models, createFileTree } from '../utils/testUtils';
import { File } from '../db';
describe('FileModel', function() {
beforeAll(async () => {
await beforeAllDb('FileModel');
});
afterAll(async () => {
await afterAllDb();
});
beforeEach(async () => {
await beforeEachDb();
});
test('should compute item full path', async function() {
const { user } = await createUserAndSession(1, true);
const fileModel = models().file({ userId: user.id });
const rootId = await fileModel.userRootFileId();
const tree: any = {
folder1: {},
folder2: {
file2_1: null,
file2_2: null,
},
folder3: {
file3_1: null,
},
file1: null,
file2: null,
file3: null,
};
await createFileTree(fileModel, rootId, tree);
const testCases = Object.keys(tree)
.concat(Object.keys(tree.folder2))
.concat(Object.keys(tree.folder3));
for (const t of testCases) {
const file: File = await fileModel.loadByName(t);
const path = await fileModel.itemFullPath(file);
const fileBack: File = await fileModel.entityFromItemId(path);
expect(file.id).toBe(fileBack.id);
}
const rootPath = await fileModel.itemFullPath(await fileModel.userRootFile());
expect(rootPath).toBe('root');
const fileBack: File = await fileModel.entityFromItemId(rootPath);
expect(fileBack.id).toBe(rootId);
});
});

View File

@ -56,6 +56,17 @@ export default class FileModel extends BaseModel {
return null; // Not a special dir
}
public async itemFullPath(item: File): Promise<string> {
const segments: string[] = [];
while (item) {
if (item.is_root) break;
segments.splice(0, 0, item.name);
item = item.parent_id ? await this.load(item.parent_id) : null;
}
return segments.length ? (`root:/${segments.join('/')}:`) : 'root';
}
public async entityFromItemId(idOrPath: string, options: EntityFromItemIdOptions = {}): Promise<File> {
options = { mustExist: true, ...options };
@ -265,6 +276,19 @@ export default class FileModel extends BaseModel {
return output;
}
// Mostly makes sense for testing/debugging because the filename would
// have to globally unique, which is not a requirement.
public async loadByName(name: string): Promise<File> {
const file: File = await this.db(this.tableName)
.select(this.defaultFields)
.where({ name: name })
.andWhere({ owner_id: this.userId })
.first();
if (!file) throw new ErrorNotFound(`No such file: ${name}`);
await this.checkCanReadPermissions(file);
return file;
}
public async loadWithContent(id: string): Promise<any> {
const file: File = await this.db<File>(this.tableName).select('*').where({ id: id }).first();
if (!file) return null;

View File

@ -0,0 +1,21 @@
import { SubPath, Route } from '../../utils/routeUtils';
import { AppContext } from '../../utils/types';
import { contextSessionId } from '../../utils/requestUtils';
import { ErrorMethodNotAllowed } from '../../utils/errors';
import { requestPagination } from '../../models/utils/pagination';
const route: Route = {
exec: async function(path: SubPath, ctx: AppContext) {
const sessionId = contextSessionId(ctx);
if (ctx.method === 'GET') {
return ctx.controllers.indexFiles().getIndex(sessionId, path.id, requestPagination(ctx.query));
}
throw new ErrorMethodNotAllowed();
},
};
export default route;

View File

@ -3,20 +3,19 @@ import { Routes } from '../utils/routeUtils';
import apiSessions from './api/sessions';
import apiPing from './api/ping';
import apiFiles from './api/files';
// import oauth2Authorize from './oauth2/authorize';
import indexLoginRoute from './index/login';
import indexLogoutRoute from './index/logout';
import indexHomeRoute from './index/home';
import indexProfileRoute from './index/profile';
import indexUsersRoute from './index/users';
import indexUserRoute from './index/user';
import indexFilesRoute from './index/files';
import defaultRoute from './default';
const routes: Routes = {
'api/ping': apiPing,
'api/sessions': apiSessions,
'api/files': apiFiles,
// 'oauth2/authorize': oauth2Authorize,
'login': indexLoginRoute,
'logout': indexLogoutRoute,
@ -24,6 +23,7 @@ const routes: Routes = {
'profile': indexProfileRoute,
'users': indexUsersRoute,
'user': indexUserRoute,
'files': indexFilesRoute,
'': defaultRoute,
};

View File

@ -1,10 +1,11 @@
import { User, Session, DbConnection, connectDb, disconnectDb } from '../db';
import { User, Session, DbConnection, connectDb, disconnectDb, File } from '../db';
import { createDb } from '../tools/dbTools';
import modelFactory from '../models/factory';
import controllerFactory from '../controllers/factory';
import baseConfig from '../config-tests';
import { Config } from './types';
import { initConfig } from '../config';
import FileModel from '../models/FileModel';
// Takes into account the fact that this file will be inside the /dist directory
// when it runs.
@ -81,6 +82,20 @@ export const createUser = async function(index: number = 1, isAdmin: boolean = f
return models().user().save({ email: `user${index}@localhost`, password: '123456', is_admin: isAdmin ? 1 : 0 }, { skipValidation: true });
};
export async function createFileTree(fileModel: FileModel, parentId: string, tree: any): Promise<void> {
for (const name in tree) {
const children: any = tree[name];
const isDir = children !== null;
const newFile: File = await fileModel.save({
parent_id: parentId,
name: name,
is_directory: isDir ? 1 : 0,
});
if (isDir && Object.keys(children).length) await createFileTree(fileModel, newFile.id, children);
}
}
export async function checkThrowAsync(asyncFn: Function): Promise<any> {
try {
await asyncFn();

View File

@ -0,0 +1,5 @@
<div>
{{#paginatedFiles.items}}
<div>{{name}}</div>
{{/paginatedFiles.items}}
</div>

View File

@ -8,6 +8,7 @@
<div class="navbar-start">
<a class="navbar-item" href="{{{global.baseUrl}}}/home">Home</a>
<a class="navbar-item" href="{{{global.baseUrl}}}/users">Users</a>
<a class="navbar-item" href="{{{global.baseUrl}}}/files">Files</a>
</div>
<div class="navbar-end">
<div class="navbar-item">{{owner.email}}</div>