mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Server: Removed all controller
These controllers were mostly here to allow testing the business logic. However now that the routes are tested directly they are no longer necessary. And testing the routes significantly increase the test coverage.
This commit is contained in:
parent
9b2e5e2959
commit
4a230d7cd5
@ -1481,15 +1481,9 @@ packages/server/src/config.js.map
|
||||
packages/server/src/controllers/BaseController.d.ts
|
||||
packages/server/src/controllers/BaseController.js
|
||||
packages/server/src/controllers/BaseController.js.map
|
||||
packages/server/src/controllers/api/FileController.d.ts
|
||||
packages/server/src/controllers/api/FileController.js
|
||||
packages/server/src/controllers/api/FileController.js.map
|
||||
packages/server/src/controllers/api/OAuthController.d.ts
|
||||
packages/server/src/controllers/api/OAuthController.js
|
||||
packages/server/src/controllers/api/OAuthController.js.map
|
||||
packages/server/src/controllers/api/SessionController.d.ts
|
||||
packages/server/src/controllers/api/SessionController.js
|
||||
packages/server/src/controllers/api/SessionController.js.map
|
||||
packages/server/src/controllers/factory.d.ts
|
||||
packages/server/src/controllers/factory.js
|
||||
packages/server/src/controllers/factory.js.map
|
||||
@ -1610,6 +1604,9 @@ packages/server/src/routes/index/home.js.map
|
||||
packages/server/src/routes/index/login.d.ts
|
||||
packages/server/src/routes/index/login.js
|
||||
packages/server/src/routes/index/login.js.map
|
||||
packages/server/src/routes/index/login.test.d.ts
|
||||
packages/server/src/routes/index/login.test.js
|
||||
packages/server/src/routes/index/login.test.js.map
|
||||
packages/server/src/routes/index/logout.d.ts
|
||||
packages/server/src/routes/index/logout.js
|
||||
packages/server/src/routes/index/logout.js.map
|
||||
|
9
.gitignore
vendored
9
.gitignore
vendored
@ -1470,15 +1470,9 @@ packages/server/src/config.js.map
|
||||
packages/server/src/controllers/BaseController.d.ts
|
||||
packages/server/src/controllers/BaseController.js
|
||||
packages/server/src/controllers/BaseController.js.map
|
||||
packages/server/src/controllers/api/FileController.d.ts
|
||||
packages/server/src/controllers/api/FileController.js
|
||||
packages/server/src/controllers/api/FileController.js.map
|
||||
packages/server/src/controllers/api/OAuthController.d.ts
|
||||
packages/server/src/controllers/api/OAuthController.js
|
||||
packages/server/src/controllers/api/OAuthController.js.map
|
||||
packages/server/src/controllers/api/SessionController.d.ts
|
||||
packages/server/src/controllers/api/SessionController.js
|
||||
packages/server/src/controllers/api/SessionController.js.map
|
||||
packages/server/src/controllers/factory.d.ts
|
||||
packages/server/src/controllers/factory.js
|
||||
packages/server/src/controllers/factory.js.map
|
||||
@ -1599,6 +1593,9 @@ packages/server/src/routes/index/home.js.map
|
||||
packages/server/src/routes/index/login.d.ts
|
||||
packages/server/src/routes/index/login.js
|
||||
packages/server/src/routes/index/login.js.map
|
||||
packages/server/src/routes/index/login.test.d.ts
|
||||
packages/server/src/routes/index/login.test.js
|
||||
packages/server/src/routes/index/login.test.js.map
|
||||
packages/server/src/routes/index/logout.d.ts
|
||||
packages/server/src/routes/index/logout.js
|
||||
packages/server/src/routes/index/logout.js.map
|
||||
|
@ -12,7 +12,6 @@ import configBuildTypes from './config-buildTypes';
|
||||
import { createDb, dropDb } from './tools/dbTools';
|
||||
import { dropTables, connectDb, disconnectDb, migrateDb, waitForConnection } from './db';
|
||||
import modelFactory from './models/factory';
|
||||
import controllerFactory from './controllers/factory';
|
||||
import { AppContext, Config, Env } from './utils/types';
|
||||
import FsDriverNode from '@joplin/lib/fs-driver-node';
|
||||
import routeHandler from './middleware/routeHandler';
|
||||
@ -106,7 +105,6 @@ async function main() {
|
||||
appContext.env = env;
|
||||
appContext.db = connectionCheck.connection;
|
||||
appContext.models = modelFactory(appContext.db, baseUrl());
|
||||
appContext.controllers = controllerFactory(appContext.models);
|
||||
appContext.appLogger = appLogger;
|
||||
|
||||
appLogger().info('Migrating database...');
|
||||
|
@ -1,25 +0,0 @@
|
||||
import { User } from '../db';
|
||||
import { Models } from '../models/factory';
|
||||
import { ErrorForbidden } from '../utils/errors';
|
||||
|
||||
export default abstract class BaseController {
|
||||
|
||||
private models_: Models;
|
||||
|
||||
public constructor(models: Models) {
|
||||
this.models_ = models;
|
||||
}
|
||||
|
||||
protected get models(): Models {
|
||||
return this.models_;
|
||||
}
|
||||
|
||||
protected async initSession(sessionId: string, mustBeAdmin: boolean = false): Promise<User> {
|
||||
if (!sessionId) throw new ErrorForbidden('Session is required');
|
||||
const user: User = await this.models.session().sessionUser(sessionId);
|
||||
if (!user) throw new ErrorForbidden(`Invalid session ID: ${sessionId}`);
|
||||
if (!user.is_admin && mustBeAdmin) throw new ErrorForbidden('Non-admin user is not allowed');
|
||||
return user;
|
||||
}
|
||||
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
// import BaseController from '../BaseController';
|
||||
// import mustacheService from '../../services/MustacheService';
|
||||
// import { ErrorNotFound } from '../../utils/errors';
|
||||
// import uuidgen from '../../utils/uuidgen';
|
||||
// import controllers from '../factory';
|
||||
|
||||
// export default class OAuthController extends BaseController {
|
||||
|
||||
// async getAuthorize(query: any): Promise<string> {
|
||||
// const clientModel = this.models.apiClient();
|
||||
// const client = await clientModel.load(query.client_id);
|
||||
// if (!client) throw new ErrorNotFound(`client_id missing or invalid client ID: ${query.client_id}`);
|
||||
|
||||
// return mustacheService.render('oauth2/authorize', {
|
||||
// response_type: query.response_type,
|
||||
// client: client,
|
||||
// }, {
|
||||
// cssFiles: ['oauth2/authorize'],
|
||||
// });
|
||||
// }
|
||||
|
||||
// async postAuthorize(query: any): Promise<string> {
|
||||
// const clientModel = this.models.apiClient();
|
||||
// const sessionModel = this.models.session();
|
||||
// const sessionController = controllers(this.models).session();
|
||||
|
||||
// let client = null;
|
||||
|
||||
// try {
|
||||
// client = await clientModel.load(query.client_id);
|
||||
// if (!client) throw new ErrorNotFound(`client_id missing or invalid client ID: ${query.client_id}`);
|
||||
|
||||
// const session = await sessionController.authenticate(query.email, query.password);
|
||||
// const authCode = uuidgen(32);
|
||||
// await sessionModel.save({ id: session.id, auth_code: authCode });
|
||||
|
||||
// return mustacheService.render('oauth2/authcode', {
|
||||
// client: client,
|
||||
// authCode: authCode,
|
||||
// }, {
|
||||
// cssFiles: ['oauth2/authorize'],
|
||||
// });
|
||||
// } catch (error) {
|
||||
// return mustacheService.render('oauth2/authorize', {
|
||||
// response_type: query.response_type,
|
||||
// client: client,
|
||||
// error: error,
|
||||
// }, {
|
||||
// cssFiles: ['oauth2/authorize'],
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// }
|
@ -1,45 +0,0 @@
|
||||
import { Models } from '../models/factory';
|
||||
// import OAuthController from './api/OAuthController';
|
||||
import IndexLoginController from './index/LoginController';
|
||||
import IndexHomeController from './index/HomeController';
|
||||
import IndexUserController from './index/UserController';
|
||||
import IndexFileController from './index/FileController';
|
||||
import IndexNotificationController from './index/NotificationController';
|
||||
|
||||
export class Controllers {
|
||||
|
||||
private models_: Models;
|
||||
|
||||
public constructor(models: Models) {
|
||||
this.models_ = models;
|
||||
}
|
||||
|
||||
// public oauth() {
|
||||
// return new OAuthController(this.models_);
|
||||
// }
|
||||
|
||||
public indexLogin() {
|
||||
return new IndexLoginController(this.models_);
|
||||
}
|
||||
|
||||
public indexHome() {
|
||||
return new IndexHomeController(this.models_);
|
||||
}
|
||||
|
||||
public indexUser() {
|
||||
return new IndexUserController(this.models_);
|
||||
}
|
||||
|
||||
public indexFiles() {
|
||||
return new IndexFileController(this.models_);
|
||||
}
|
||||
|
||||
public indexNotifications() {
|
||||
return new IndexNotificationController(this.models_);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default function(models: Models) {
|
||||
return new Controllers(models);
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
import BaseController from '../BaseController';
|
||||
import { View } from '../../services/MustacheService';
|
||||
import defaultView from '../../utils/defaultView';
|
||||
import { Pagination, pageMaxSize, PaginationOrder, requestPaginationOrder, PaginationOrderDir, validatePagination, createPaginationLinks } from '../../models/utils/pagination';
|
||||
import { File } from '../../db';
|
||||
import { baseUrl } from '../../config';
|
||||
import { formatDateTime } from '../../utils/time';
|
||||
import { setQueryParameters } from '../../utils/urlUtils';
|
||||
|
||||
export function makeFilePagination(query: any): Pagination {
|
||||
const limit = Number(query.limit) || pageMaxSize;
|
||||
const order: PaginationOrder[] = requestPaginationOrder(query, 'name', PaginationOrderDir.ASC);
|
||||
order.splice(0, 0, { by: 'is_directory', dir: PaginationOrderDir.DESC });
|
||||
const page: number = 'page' in query ? Number(query.page) : 1;
|
||||
|
||||
const output: Pagination = { limit, order, page };
|
||||
validatePagination(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
export default class FileController extends BaseController {
|
||||
|
||||
public async getIndex(sessionId: string, dirId: string, query: any): Promise<View> {
|
||||
// Query parameters that should be appended to pagination-related URLs
|
||||
const baseUrlQuery: any = {};
|
||||
if (query.limit) baseUrlQuery.limit = query.limit;
|
||||
if (query.order_by) baseUrlQuery.order_by = query.order_by;
|
||||
if (query.order_dir) baseUrlQuery.order_dir = query.order_dir;
|
||||
|
||||
const pagination = makeFilePagination(query);
|
||||
const owner = await this.initSession(sessionId);
|
||||
const fileModel = this.models.file({ userId: owner.id });
|
||||
const root = await fileModel.userRootFile();
|
||||
const parentTemp: File = dirId ? await fileModel.entityFromItemId(dirId) : root;
|
||||
const parent: File = await fileModel.load(parentTemp.id);
|
||||
const paginatedFiles = await fileModel.childrens(parent.id, pagination);
|
||||
const pageCount = Math.ceil((await fileModel.childrenCount(parent.id)) / pagination.limit);
|
||||
|
||||
const parentBaseUrl = await fileModel.fileUrl(parent.id);
|
||||
const paginationLinks = createPaginationLinks(pagination.page, pageCount, setQueryParameters(parentBaseUrl, { ...baseUrlQuery, 'page': 'PAGE_NUMBER' }));
|
||||
|
||||
async function fileToViewItem(file: File, fileFullPaths: Record<string, string>): Promise<any> {
|
||||
const filePath = fileFullPaths[file.id];
|
||||
|
||||
let url = `${baseUrl()}/files/${filePath}`;
|
||||
if (!file.is_directory) {
|
||||
url += '/content';
|
||||
} else {
|
||||
url = setQueryParameters(url, baseUrlQuery);
|
||||
}
|
||||
|
||||
return {
|
||||
name: file.name,
|
||||
url,
|
||||
type: file.is_directory ? 'directory' : 'file',
|
||||
icon: file.is_directory ? 'far fa-folder' : 'far fa-file',
|
||||
timestamp: formatDateTime(file.updated_time),
|
||||
mime: !file.is_directory ? (file.mime_type || 'binary') : '',
|
||||
};
|
||||
}
|
||||
|
||||
const files: any[] = [];
|
||||
|
||||
const fileFullPaths = await fileModel.itemFullPaths(paginatedFiles.items);
|
||||
|
||||
if (parent.id !== root.id) {
|
||||
const p = await fileModel.load(parent.parent_id);
|
||||
files.push({
|
||||
...await fileToViewItem(p, await fileModel.itemFullPaths([p])),
|
||||
icon: 'fas fa-arrow-left',
|
||||
name: '..',
|
||||
});
|
||||
}
|
||||
|
||||
for (const file of paginatedFiles.items) {
|
||||
files.push(await fileToViewItem(file, fileFullPaths));
|
||||
}
|
||||
|
||||
const view: View = defaultView('files');
|
||||
view.content.paginatedFiles = { ...paginatedFiles, items: files };
|
||||
view.content.paginationLinks = paginationLinks;
|
||||
view.content.postUrl = `${baseUrl()}/files`;
|
||||
view.content.parentId = parent.id;
|
||||
view.cssFiles = ['index/files'];
|
||||
view.partials.push('pagination');
|
||||
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
public async deleteAll(sessionId: string, dirId: string): Promise<void> {
|
||||
const owner = await this.initSession(sessionId);
|
||||
const fileModel = this.models.file({ userId: owner.id });
|
||||
const parent: File = await fileModel.entityFromItemId(dirId, { returnFullEntity: true });
|
||||
await fileModel.deleteChildren(parent.id);
|
||||
}
|
||||
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import BaseController from '../BaseController';
|
||||
import { View } from '../../services/MustacheService';
|
||||
import defaultView from '../../utils/defaultView';
|
||||
|
||||
export default class HomeController extends BaseController {
|
||||
|
||||
public async getIndex(sessionId: string): Promise<View> {
|
||||
await this.initSession(sessionId);
|
||||
return defaultView('home');
|
||||
}
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
import BaseController from '../BaseController';
|
||||
import { View } from '../../services/MustacheService';
|
||||
import defaultView from '../../utils/defaultView';
|
||||
|
||||
export default class LoginController extends BaseController {
|
||||
|
||||
public async getIndex(error: any = null): Promise<View> {
|
||||
const view = defaultView('login');
|
||||
view.content.error = error;
|
||||
view.partials = ['errorBanner'];
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import BaseController from '../BaseController';
|
||||
import { Notification } from '../../db';
|
||||
import { ErrorNotFound } from '../../utils/errors';
|
||||
|
||||
export default class NotificationController extends BaseController {
|
||||
|
||||
public async patchOne(sessionId: string, notificationId: string, notification: Notification): Promise<void> {
|
||||
const owner = await this.initSession(sessionId);
|
||||
const model = this.models.notification({ userId: owner.id });
|
||||
const existingNotification = await model.load(notificationId);
|
||||
if (!existingNotification) throw new ErrorNotFound();
|
||||
|
||||
|
||||
console.info('aaaaaaa', notification);
|
||||
const toSave: Notification = {};
|
||||
if ('read' in notification) toSave.read = notification.read;
|
||||
if (!Object.keys(toSave).length) return;
|
||||
|
||||
toSave.id = notificationId;
|
||||
await model.save(toSave);
|
||||
}
|
||||
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
import BaseController from '../BaseController';
|
||||
import { View } from '../../services/MustacheService';
|
||||
import defaultView from '../../utils/defaultView';
|
||||
import { User } from '../../db';
|
||||
import { baseUrl } from '../../config';
|
||||
|
||||
export default class UserController extends BaseController {
|
||||
|
||||
public async getIndex(sessionId: string): Promise<View> {
|
||||
const owner = await this.initSession(sessionId, true);
|
||||
const userModel = this.models.user({ userId: owner.id });
|
||||
const users = await userModel.all();
|
||||
|
||||
const view: View = defaultView('users');
|
||||
view.content.users = users;
|
||||
return view;
|
||||
}
|
||||
|
||||
public async getOne(sessionId: string, isNew: boolean, isMe: 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;
|
||||
}
|
||||
|
||||
let postUrl = `${baseUrl()}/users/${user.id}`;
|
||||
if (isNew) postUrl = `${baseUrl()}/users/new`;
|
||||
if (isMe) postUrl = `${baseUrl()}/users/me`;
|
||||
|
||||
const view: View = defaultView('user');
|
||||
view.content.user = user;
|
||||
view.content.isNew = isNew;
|
||||
view.content.buttonTitle = isNew ? 'Create user' : 'Update profile';
|
||||
view.content.error = error;
|
||||
view.content.postUrl = postUrl;
|
||||
view.content.showDeleteButton = !!owner.is_admin && owner.id !== user.id;
|
||||
view.partials.push('errorBanner');
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
}
|
@ -3,39 +3,142 @@ import { AppContext } from '../../utils/types';
|
||||
import { contextSessionId, formParse } from '../../utils/requestUtils';
|
||||
import { ErrorMethodNotAllowed, ErrorNotFound } from '../../utils/errors';
|
||||
import { File } from '../../db';
|
||||
import { createPaginationLinks, pageMaxSize, Pagination, PaginationOrder, PaginationOrderDir, requestPaginationOrder, validatePagination } from '../../models/utils/pagination';
|
||||
import { setQueryParameters } from '../../utils/urlUtils';
|
||||
import { baseUrl } from '../../config';
|
||||
import { formatDateTime } from '../../utils/time';
|
||||
import defaultView from '../../utils/defaultView';
|
||||
import { View } from '../../services/MustacheService';
|
||||
|
||||
const route: Route = {
|
||||
function makeFilePagination(query: any): Pagination {
|
||||
const limit = Number(query.limit) || pageMaxSize;
|
||||
const order: PaginationOrder[] = requestPaginationOrder(query, 'name', PaginationOrderDir.ASC);
|
||||
order.splice(0, 0, { by: 'is_directory', dir: PaginationOrderDir.DESC });
|
||||
const page: number = 'page' in query ? Number(query.page) : 1;
|
||||
|
||||
exec: async function(path: SubPath, ctx: AppContext) {
|
||||
const sessionId = contextSessionId(ctx);
|
||||
const output: Pagination = { limit, order, page };
|
||||
validatePagination(output);
|
||||
return output;
|
||||
}
|
||||
|
||||
if (ctx.method === 'GET') {
|
||||
if (!path.link) {
|
||||
return ctx.controllers.indexFiles().getIndex(sessionId, path.id, ctx.query);
|
||||
} else if (path.link === 'content') {
|
||||
const fileModel = ctx.models.file({ userId: ctx.owner.id });
|
||||
let file: File = await fileModel.entityFromItemId(path.id);
|
||||
file = await fileModel.loadWithContent(file.id);
|
||||
if (!file) throw new ErrorNotFound();
|
||||
return respondWithFileContent(ctx.response, file);
|
||||
const endPoints = {
|
||||
|
||||
'GET': {
|
||||
'files/:id': async function(path: SubPath, ctx: AppContext) {
|
||||
const dirId = path.id;
|
||||
const query = ctx.query;
|
||||
|
||||
// Query parameters that should be appended to pagination-related URLs
|
||||
const baseUrlQuery: any = {};
|
||||
if (query.limit) baseUrlQuery.limit = query.limit;
|
||||
if (query.order_by) baseUrlQuery.order_by = query.order_by;
|
||||
if (query.order_dir) baseUrlQuery.order_dir = query.order_dir;
|
||||
|
||||
const pagination = makeFilePagination(query);
|
||||
const owner = ctx.owner;
|
||||
const fileModel = ctx.models.file({ userId: owner.id });
|
||||
const root = await fileModel.userRootFile();
|
||||
const parentTemp: File = dirId ? await fileModel.entityFromItemId(dirId) : root;
|
||||
const parent: File = await fileModel.load(parentTemp.id);
|
||||
const paginatedFiles = await fileModel.childrens(parent.id, pagination);
|
||||
const pageCount = Math.ceil((await fileModel.childrenCount(parent.id)) / pagination.limit);
|
||||
|
||||
const parentBaseUrl = await fileModel.fileUrl(parent.id);
|
||||
const paginationLinks = createPaginationLinks(pagination.page, pageCount, setQueryParameters(parentBaseUrl, { ...baseUrlQuery, 'page': 'PAGE_NUMBER' }));
|
||||
|
||||
async function fileToViewItem(file: File, fileFullPaths: Record<string, string>): Promise<any> {
|
||||
const filePath = fileFullPaths[file.id];
|
||||
|
||||
let url = `${baseUrl()}/files/${filePath}`;
|
||||
if (!file.is_directory) {
|
||||
url += '/content';
|
||||
} else {
|
||||
url = setQueryParameters(url, baseUrlQuery);
|
||||
}
|
||||
|
||||
return {
|
||||
name: file.name,
|
||||
url,
|
||||
type: file.is_directory ? 'directory' : 'file',
|
||||
icon: file.is_directory ? 'far fa-folder' : 'far fa-file',
|
||||
timestamp: formatDateTime(file.updated_time),
|
||||
mime: !file.is_directory ? (file.mime_type || 'binary') : '',
|
||||
};
|
||||
}
|
||||
|
||||
throw new ErrorNotFound();
|
||||
}
|
||||
const files: any[] = [];
|
||||
|
||||
const fileFullPaths = await fileModel.itemFullPaths(paginatedFiles.items);
|
||||
|
||||
if (parent.id !== root.id) {
|
||||
const p = await fileModel.load(parent.parent_id);
|
||||
files.push({
|
||||
...await fileToViewItem(p, await fileModel.itemFullPaths([p])),
|
||||
icon: 'fas fa-arrow-left',
|
||||
name: '..',
|
||||
});
|
||||
}
|
||||
|
||||
for (const file of paginatedFiles.items) {
|
||||
files.push(await fileToViewItem(file, fileFullPaths));
|
||||
}
|
||||
|
||||
const view: View = defaultView('files');
|
||||
view.content.paginatedFiles = { ...paginatedFiles, items: files };
|
||||
view.content.paginationLinks = paginationLinks;
|
||||
view.content.postUrl = `${baseUrl()}/files`;
|
||||
view.content.parentId = parent.id;
|
||||
view.cssFiles = ['index/files'];
|
||||
view.partials.push('pagination');
|
||||
return view;
|
||||
},
|
||||
|
||||
'files/:id/content': async function(path: SubPath, ctx: AppContext) {
|
||||
const fileModel = ctx.models.file({ userId: ctx.owner.id });
|
||||
let file: File = await fileModel.entityFromItemId(path.id);
|
||||
file = await fileModel.loadWithContent(file.id);
|
||||
if (!file) throw new ErrorNotFound();
|
||||
return respondWithFileContent(ctx.response, file);
|
||||
},
|
||||
},
|
||||
|
||||
'POST': {
|
||||
'files': async function(_path: SubPath, ctx: AppContext) {
|
||||
const sessionId = contextSessionId(ctx);
|
||||
|
||||
if (ctx.method === 'POST') {
|
||||
const body = await formParse(ctx.req);
|
||||
const fields = body.fields;
|
||||
const parentId = fields.parent_id;
|
||||
const user = await ctx.models.session().sessionUser(sessionId);
|
||||
|
||||
if (fields.delete_all_button) {
|
||||
await ctx.controllers.indexFiles().deleteAll(sessionId, parentId);
|
||||
const fileModel = ctx.models.file({ userId: ctx.owner.id });
|
||||
const parent: File = await fileModel.entityFromItemId(parentId, { returnFullEntity: true });
|
||||
await fileModel.deleteChildren(parent.id);
|
||||
} else {
|
||||
throw new Error('Invalid form button');
|
||||
}
|
||||
|
||||
return redirect(ctx, await ctx.models.file({ userId: user.id }).fileUrl(parentId, ctx.query));
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const route: Route = {
|
||||
|
||||
exec: async function(path: SubPath, ctx: AppContext) {
|
||||
if (ctx.method === 'GET') {
|
||||
if (!path.link) {
|
||||
return endPoints.GET['files/:id'](path, ctx);
|
||||
} else if (path.link === 'content') {
|
||||
return endPoints.GET['files/:id/content'](path, ctx);
|
||||
}
|
||||
|
||||
throw new ErrorNotFound();
|
||||
}
|
||||
|
||||
if (ctx.method === 'POST') {
|
||||
return endPoints.POST['files'](path, ctx);
|
||||
}
|
||||
|
||||
throw new ErrorMethodNotAllowed();
|
||||
|
@ -2,15 +2,15 @@ import { SubPath, Route } from '../../utils/routeUtils';
|
||||
import { AppContext } from '../../utils/types';
|
||||
import { contextSessionId } from '../../utils/requestUtils';
|
||||
import { ErrorMethodNotAllowed } from '../../utils/errors';
|
||||
import defaultView from '../../utils/defaultView';
|
||||
|
||||
const route: Route = {
|
||||
|
||||
exec: async function(_path: SubPath, ctx: AppContext) {
|
||||
const sessionId = contextSessionId(ctx);
|
||||
const homeController = ctx.controllers.indexHome();
|
||||
contextSessionId(ctx);
|
||||
|
||||
if (ctx.method === 'GET') {
|
||||
return homeController.getIndex(sessionId);
|
||||
return defaultView('home');
|
||||
}
|
||||
|
||||
throw new ErrorMethodNotAllowed();
|
||||
|
@ -3,14 +3,21 @@ import { ErrorMethodNotAllowed } from '../../utils/errors';
|
||||
import { AppContext } from '../../utils/types';
|
||||
import { formParse } from '../../utils/requestUtils';
|
||||
import { baseUrl } from '../../config';
|
||||
import defaultView from '../../utils/defaultView';
|
||||
import { View } from '../../services/MustacheService';
|
||||
|
||||
function makeView(error: any = null): View {
|
||||
const view = defaultView('login');
|
||||
view.content.error = error;
|
||||
view.partials = ['errorBanner'];
|
||||
return view;
|
||||
}
|
||||
|
||||
const route: Route = {
|
||||
|
||||
exec: async function(_path: SubPath, ctx: AppContext) {
|
||||
const loginController = ctx.controllers.indexLogin();
|
||||
|
||||
if (ctx.method === 'GET') {
|
||||
return loginController.getIndex();
|
||||
return makeView();
|
||||
}
|
||||
|
||||
if (ctx.method === 'POST') {
|
||||
@ -21,7 +28,7 @@ const route: Route = {
|
||||
ctx.cookies.set('sessionId', session.id);
|
||||
return redirect(ctx, `${baseUrl()}/home`);
|
||||
} catch (error) {
|
||||
return loginController.getIndex(error);
|
||||
return makeView(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,28 @@
|
||||
import { SubPath, Route } from '../../utils/routeUtils';
|
||||
import { AppContext } from '../../utils/types';
|
||||
import { bodyFields, contextSessionId } from '../../utils/requestUtils';
|
||||
import { ErrorMethodNotAllowed } from '../../utils/errors';
|
||||
import { ErrorMethodNotAllowed, ErrorNotFound } from '../../utils/errors';
|
||||
import { Notification } from '../../db';
|
||||
|
||||
const route: Route = {
|
||||
|
||||
exec: async function(path: SubPath, ctx: AppContext) {
|
||||
const sessionId = contextSessionId(ctx);
|
||||
contextSessionId(ctx);
|
||||
|
||||
if (path.id && ctx.method === 'PATCH') {
|
||||
return ctx.controllers.indexNotifications().patchOne(sessionId, path.id, await bodyFields(ctx.req));
|
||||
const fields: Notification = await bodyFields(ctx.req);
|
||||
const notificationId = path.id;
|
||||
const model = ctx.models.notification({ userId: ctx.owner.id });
|
||||
const existingNotification = await model.load(notificationId);
|
||||
if (!existingNotification) throw new ErrorNotFound();
|
||||
|
||||
const toSave: Notification = {};
|
||||
if ('read' in fields) toSave.read = fields.read;
|
||||
if (!Object.keys(toSave).length) return;
|
||||
|
||||
toSave.id = notificationId;
|
||||
await model.save(toSave);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ErrorMethodNotAllowed();
|
||||
|
@ -4,6 +4,8 @@ import { contextSessionId, formParse } from '../../utils/requestUtils';
|
||||
import { ErrorMethodNotAllowed, ErrorUnprocessableEntity } from '../../utils/errors';
|
||||
import { User } from '../../db';
|
||||
import { baseUrl } from '../../config';
|
||||
import { View } from '../../services/MustacheService';
|
||||
import defaultView from '../../utils/defaultView';
|
||||
|
||||
function makeUser(isNew: boolean, fields: any): User {
|
||||
const user: User = {};
|
||||
@ -21,35 +23,73 @@ function makeUser(isNew: boolean, fields: any): User {
|
||||
return user;
|
||||
}
|
||||
|
||||
const route: Route = {
|
||||
function userIsNew(path: SubPath): boolean {
|
||||
return path.id === 'new';
|
||||
}
|
||||
|
||||
exec: async function(path: SubPath, ctx: AppContext) {
|
||||
const sessionId = contextSessionId(ctx);
|
||||
const isNew = path.id === 'new';
|
||||
const isMe = path.id === 'me';
|
||||
const userId = isMe ? ctx.owner.id : path.id;
|
||||
function userIsMe(path: SubPath): boolean {
|
||||
return path.id === 'me';
|
||||
}
|
||||
|
||||
if (ctx.method === 'GET') {
|
||||
if (path.id) {
|
||||
return ctx.controllers.indexUser().getOne(sessionId, isNew, isMe, !isNew ? userId : null);
|
||||
const endPoints = {
|
||||
|
||||
'GET': {
|
||||
'users': async function(_path: SubPath, ctx: AppContext) {
|
||||
const userModel = ctx.models.user({ userId: ctx.owner.id });
|
||||
const users = await userModel.all();
|
||||
|
||||
const view: View = defaultView('users');
|
||||
view.content.users = users;
|
||||
return view;
|
||||
},
|
||||
|
||||
'users/:id': async function(path: SubPath, ctx: AppContext, user: User = null, error: any = null) {
|
||||
const owner = ctx.owner;
|
||||
const isMe = userIsMe(path);
|
||||
const isNew = userIsNew(path);
|
||||
const userModel = ctx.models.user({ userId: owner.id });
|
||||
const userId = userIsMe(path) ? owner.id : path.id;
|
||||
|
||||
user = !isNew ? user || await userModel.load(userId) : null;
|
||||
|
||||
let postUrl = '';
|
||||
|
||||
if (isNew) {
|
||||
postUrl = `${baseUrl()}/users/new`;
|
||||
} else if (isMe) {
|
||||
postUrl = `${baseUrl()}/users/me`;
|
||||
} else {
|
||||
return ctx.controllers.indexUser().getIndex(sessionId);
|
||||
postUrl = `${baseUrl()}/users/${user.id}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.method === 'POST') {
|
||||
const view: View = defaultView('user');
|
||||
view.content.user = user;
|
||||
view.content.isNew = isNew;
|
||||
view.content.buttonTitle = isNew ? 'Create user' : 'Update profile';
|
||||
view.content.error = error;
|
||||
view.content.postUrl = postUrl;
|
||||
view.content.showDeleteButton = !isNew && !!owner.is_admin && owner.id !== user.id;
|
||||
view.partials.push('errorBanner');
|
||||
|
||||
return view;
|
||||
},
|
||||
},
|
||||
|
||||
'POST': {
|
||||
'users': async function(path: SubPath, ctx: AppContext) {
|
||||
let user: User = {};
|
||||
const userId = userIsMe(path) ? ctx.owner.id : path.id;
|
||||
|
||||
try {
|
||||
const body = await formParse(ctx.req);
|
||||
const fields = body.fields;
|
||||
if (isMe) fields.id = userId;
|
||||
user = makeUser(isNew, fields);
|
||||
if (userIsMe(path)) fields.id = userId;
|
||||
user = makeUser(userIsNew(path), fields);
|
||||
|
||||
const userModel = ctx.models.user({ userId: ctx.owner.id });
|
||||
|
||||
if (fields.post_button) {
|
||||
if (isNew) {
|
||||
if (userIsNew(path)) {
|
||||
await userModel.save(userModel.fromApiInput(user));
|
||||
} else {
|
||||
await userModel.save(userModel.fromApiInput(user), { isNew: false });
|
||||
@ -60,10 +100,29 @@ const route: Route = {
|
||||
throw new Error('Invalid form button');
|
||||
}
|
||||
|
||||
return redirect(ctx, `${baseUrl()}/users${isMe ? '/me' : ''}`);
|
||||
return redirect(ctx, `${baseUrl()}/users${userIsMe(path) ? '/me' : ''}`);
|
||||
} catch (error) {
|
||||
return ctx.controllers.indexUser().getOne(sessionId, isNew, isMe, user, error);
|
||||
return endPoints.GET['users/:id'](path, ctx, user, error);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const route: Route = {
|
||||
|
||||
exec: async function(path: SubPath, ctx: AppContext) {
|
||||
contextSessionId(ctx);
|
||||
|
||||
if (ctx.method === 'GET') {
|
||||
if (path.id) {
|
||||
return endPoints.GET['users/:id'](path, ctx);
|
||||
} else {
|
||||
return endPoints.GET['users'](path, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
if (ctx.method === 'POST') {
|
||||
return endPoints.POST['users'](path, ctx);
|
||||
}
|
||||
|
||||
throw new ErrorMethodNotAllowed();
|
||||
|
@ -25,3 +25,65 @@
|
||||
// };
|
||||
|
||||
// export default route;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// ORIGINAL CONTROLLER:
|
||||
|
||||
|
||||
// import BaseController from '../BaseController';
|
||||
// import mustacheService from '../../services/MustacheService';
|
||||
// import { ErrorNotFound } from '../../utils/errors';
|
||||
// import uuidgen from '../../utils/uuidgen';
|
||||
// import controllers from '../factory';
|
||||
|
||||
// export default class OAuthController extends BaseController {
|
||||
|
||||
// async getAuthorize(query: any): Promise<string> {
|
||||
// const clientModel = this.models.apiClient();
|
||||
// const client = await clientModel.load(query.client_id);
|
||||
// if (!client) throw new ErrorNotFound(`client_id missing or invalid client ID: ${query.client_id}`);
|
||||
|
||||
// return mustacheService.render('oauth2/authorize', {
|
||||
// response_type: query.response_type,
|
||||
// client: client,
|
||||
// }, {
|
||||
// cssFiles: ['oauth2/authorize'],
|
||||
// });
|
||||
// }
|
||||
|
||||
// async postAuthorize(query: any): Promise<string> {
|
||||
// const clientModel = this.models.apiClient();
|
||||
// const sessionModel = this.models.session();
|
||||
// const sessionController = controllers(this.models).session();
|
||||
|
||||
// let client = null;
|
||||
|
||||
// try {
|
||||
// client = await clientModel.load(query.client_id);
|
||||
// if (!client) throw new ErrorNotFound(`client_id missing or invalid client ID: ${query.client_id}`);
|
||||
|
||||
// const session = await sessionController.authenticate(query.email, query.password);
|
||||
// const authCode = uuidgen(32);
|
||||
// await sessionModel.save({ id: session.id, auth_code: authCode });
|
||||
|
||||
// return mustacheService.render('oauth2/authcode', {
|
||||
// client: client,
|
||||
// authCode: authCode,
|
||||
// }, {
|
||||
// cssFiles: ['oauth2/authorize'],
|
||||
// });
|
||||
// } catch (error) {
|
||||
// return mustacheService.render('oauth2/authorize', {
|
||||
// response_type: query.response_type,
|
||||
// client: client,
|
||||
// error: error,
|
||||
// }, {
|
||||
// cssFiles: ['oauth2/authorize'],
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// }
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { User, Session, DbConnection, connectDb, disconnectDb, File, truncateTables } from '../../db';
|
||||
import { createDb } from '../../tools/dbTools';
|
||||
import modelFactory from '../../models/factory';
|
||||
import controllerFactory from '../../controllers/factory';
|
||||
import baseConfig from '../../config-tests';
|
||||
import { AppContext, Config, Env } from '../types';
|
||||
import { initConfig } from '../../config';
|
||||
@ -112,7 +111,6 @@ export async function koaAppContext(options: AppContextTestOptions = null): Prom
|
||||
appContext.env = Env.Dev;
|
||||
appContext.db = db_;
|
||||
appContext.models = models();
|
||||
appContext.controllers = controllers();
|
||||
appContext.appLogger = () => appLogger;
|
||||
appContext.path = req.url;
|
||||
appContext.owner = owner;
|
||||
@ -154,10 +152,6 @@ export function models() {
|
||||
return modelFactory(db(), baseUrl());
|
||||
}
|
||||
|
||||
export function controllers() {
|
||||
return controllerFactory(models());
|
||||
}
|
||||
|
||||
export function parseHtml(html: string): Document {
|
||||
const dom = new jsdom.JSDOM(html);
|
||||
return dom.window.document;
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { LoggerWrapper } from '@joplin/lib/Logger';
|
||||
import * as Koa from 'koa';
|
||||
import { Controllers } from '../controllers/factory';
|
||||
import { DbConnection, User, Uuid } from '../db';
|
||||
import { Models } from '../models/factory';
|
||||
|
||||
@ -21,7 +20,6 @@ export interface AppContext extends Koa.Context {
|
||||
env: Env;
|
||||
db: DbConnection;
|
||||
models: Models;
|
||||
controllers: Controllers;
|
||||
appLogger(): LoggerWrapper;
|
||||
notifications: NotificationView[];
|
||||
owner: User;
|
||||
|
Loading…
Reference in New Issue
Block a user