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:
parent
2fda067034
commit
0e0de1207f
@ -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
6
.gitignore
vendored
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
44
packages/server/src/controllers/index/FileController.ts
Normal file
44
packages/server/src/controllers/index/FileController.ts
Normal 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;
|
||||
// }
|
||||
|
||||
}
|
56
packages/server/src/models/FileModel.test.ts
Normal file
56
packages/server/src/models/FileModel.test.ts
Normal 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);
|
||||
});
|
||||
|
||||
});
|
@ -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;
|
||||
|
21
packages/server/src/routes/index/files.ts
Normal file
21
packages/server/src/routes/index/files.ts
Normal 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;
|
@ -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,
|
||||
};
|
||||
|
@ -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();
|
||||
|
5
packages/server/src/views/index/files.mustache
Normal file
5
packages/server/src/views/index/files.mustache
Normal file
@ -0,0 +1,5 @@
|
||||
<div>
|
||||
{{#paginatedFiles.items}}
|
||||
<div>{{name}}</div>
|
||||
{{/paginatedFiles.items}}
|
||||
</div>
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user