1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00
This commit is contained in:
Laurent Cozic 2021-02-05 16:17:49 +00:00
parent 58a464d040
commit aeb3c4a98d
10 changed files with 81 additions and 47 deletions

View File

@ -274,4 +274,8 @@ export default class Application extends BaseApplication {
return true;
}
public async processSharedContentForSave(file:File):Promise<File> {
// console.info('
}
}

View File

@ -1,11 +1,14 @@
import routes from '../../../routes/routes';
import Router from '../../../utils/Router';
import { SubPath } from '../../../utils/routeUtils';
import { execRequest, SubPath } from '../../../utils/routeUtils';
import { AppContext } from '../../../utils/types';
const router = new Router();
router.get('notes/:id', async (_path: SubPath, _ctx: AppContext) => {
return 'testing';
router.get('notes/:id', async (path: SubPath, ctx: AppContext) => {
// return execRequest(routes, ctx, 'api/files/root:/' + path.id);
// return execRoute('GET', 'api/files/:id', path, ctx);
//return ctx.routes['api/files'](path, ctx);
});
export default router;

View File

@ -1,7 +1,6 @@
import routes from '../routes/routes';
import { ErrorForbidden, ErrorNotFound } from '../utils/errors';
import { routeResponseFormat, findMatchingRoute, Response, RouteResponseFormat, MatchedRoute } from '../utils/routeUtils';
import { AppContext, Env, HttpMethod } from '../utils/types';
import { routeResponseFormat, Response, RouteResponseFormat, execRequest } from '../utils/routeUtils';
import { AppContext, Env } from '../utils/types';
import MustacheService, { isView, View } from '../services/MustacheService';
import config from '../config';
@ -16,38 +15,21 @@ function mustache(): MustacheService {
export default async function(ctx: AppContext) {
ctx.appLogger().info(`${ctx.request.method} ${ctx.path}`);
const match: MatchedRoute = null;
try {
const match = findMatchingRoute(ctx.path, routes);
const responseObject = await execRequest(routes, ctx);
if (match) {
let responseObject = null;
const routeHandler = match.route.findEndPoint(ctx.request.method as HttpMethod, match.subPath.schema);
// This is a generic catch-all for all private end points - if we
// couldn't get a valid session, we exit now. Individual end points
// might have additional permission checks depending on the action.
if (!match.route.public && !ctx.owner) throw new ErrorForbidden();
responseObject = await routeHandler(match.subPath, ctx);
if (responseObject instanceof Response) {
ctx.response = responseObject.response;
} else if (isView(responseObject)) {
ctx.response.status = 200;
ctx.response.body = await mustache().renderView(responseObject, {
notifications: ctx.notifications || [],
hasNotifications: !!ctx.notifications && !!ctx.notifications.length,
owner: ctx.owner,
});
} else {
ctx.response.status = 200;
ctx.response.body = responseObject;
}
if (responseObject instanceof Response) {
ctx.response = responseObject.response;
} else if (isView(responseObject)) {
ctx.response.status = 200;
ctx.response.body = await mustache().renderView(responseObject, {
notifications: ctx.notifications || [],
hasNotifications: !!ctx.notifications && !!ctx.notifications.length,
owner: ctx.owner,
});
} else {
throw new ErrorNotFound();
ctx.response.status = 200;
ctx.response.body = responseObject;
}
} catch (error) {
if (error.httpCode >= 400 && error.httpCode < 500) {
@ -58,7 +40,7 @@ export default async function(ctx: AppContext) {
ctx.response.status = error.httpCode ? error.httpCode : 500;
const responseFormat = routeResponseFormat(match, ctx);
const responseFormat = routeResponseFormat(ctx);
if (responseFormat === RouteResponseFormat.Html) {
ctx.response.set('Content-Type', 'text/html');

View File

@ -3,9 +3,11 @@ import TransactionHandler from '../utils/TransactionHandler';
import uuidgen from '../utils/uuidgen';
import { ErrorUnprocessableEntity, ErrorBadRequest } from '../utils/errors';
import { Models } from './factory';
import Applications from '../services/Applications';
export interface ModelOptions {
userId?: string;
apps?: Applications;
}
export interface SaveOptions {
@ -67,6 +69,10 @@ export default abstract class BaseModel<T> {
return this.options.userId;
}
protected get apps():Applications {
return this.options.apps;
}
protected get db(): DbConnection {
if (this.transactionHandler_.activeTransaction) return this.transactionHandler_.activeTransaction;
return this.db_;

View File

@ -302,6 +302,12 @@ export default class FileModel extends BaseModel<File> {
}
}
private async processSharedContentForSave(file:File):Promise<File> {
if (!('source_file_id' in file)) throw new Error('source_file_id prop is required');
if (!file.source_file_id) return file;
return this.apps.processSharedContentForSave(file);
}
public async createRootFile(): Promise<File> {
const existingRootFile = await this.userRootFile();
if (existingRootFile) throw new Error(`User ${this.userId} has already a root file`);
@ -477,9 +483,12 @@ export default class FileModel extends BaseModel<File> {
size: fileSize,
source_file_id: '',
};
sourceFile = await this.processSharedContentForSave(file);
delete file.content;
} else {
file.size = fileSize;
file.size = file.content ? file.content.byteLength : 0;
}
}

View File

@ -65,19 +65,22 @@ import ChangeModel from './ChangeModel';
import NotificationModel from './NotificationModel';
import ShareModel from './ShareModel';
import ShareUserModel from './ShareUserModel';
import Applications from '../services/Applications';
export class Models {
private db_: DbConnection;
private baseUrl_: string;
private apps_:Applications;
public constructor(db: DbConnection, baseUrl: string) {
public constructor(db: DbConnection, baseUrl: string, apps:Applications) {
this.db_ = db;
this.baseUrl_ = baseUrl;
this.apps_ = apps;
}
public file(options: ModelOptions = null) {
return new FileModel(this.db_, newModelFactory, this.baseUrl_, options);
return new FileModel(this.db_, newModelFactory, this.baseUrl_, { ...options, apps: this.apps_ });
}
public user(options: ModelOptions = null) {

View File

@ -1,5 +1,6 @@
import ApplicationJoplin from '../apps/joplin/Application';
import config from '../config';
import { File } from '../db';
import { Models } from '../models/factory';
export default class Applications {
@ -21,6 +22,11 @@ export default class Applications {
return this.joplin_;
}
public async processSharedContentForSave(file:File):Promise<File> {
const app = await this.joplin();
return app.processSharedContentForSave(file);
}
public async localFileFromUrl(url: string): Promise<string> {
if (url.indexOf('apps/') !== 0) return null;

View File

@ -1,7 +1,7 @@
import { File, ItemAddressingType } from '../db';
import { ErrorBadRequest } from './errors';
import { ErrorBadRequest, ErrorForbidden, ErrorNotFound } from './errors';
import Router from './Router';
import { AppContext } from './types';
import { AppContext, HttpMethod } from './types';
const { ltrimSlashes, rtrimSlashes } = require('@joplin/lib/path-utils');
@ -151,16 +151,33 @@ export function parseSubPath(basePath: string, p: string): SubPath {
return output;
}
export function routeResponseFormat(match: MatchedRoute, context: AppContext): RouteResponseFormat {
const rawPath = context.path;
if (match && match.route.responseFormat) return match.route.responseFormat;
export function routeResponseFormat(context: AppContext): RouteResponseFormat {
// const rawPath = context.path;
// if (match && match.route.responseFormat) return match.route.responseFormat;
let path = rawPath;
if (match) path = match.basePath ? match.basePath : match.subPath.raw;
// let path = rawPath;
// if (match) path = match.basePath ? match.basePath : match.subPath.raw;
const path = context.path;
return path.indexOf('api') === 0 || path.indexOf('/api') === 0 ? RouteResponseFormat.Json : RouteResponseFormat.Html;
}
export async function execRequest(routes:Routers, ctx:AppContext, path:string = null) {
path = path || ctx.path;
const match = findMatchingRoute(ctx.path, routes);
if (!match) throw new ErrorNotFound();
const routeHandler = match.route.findEndPoint(ctx.request.method as HttpMethod, match.subPath.schema);
// This is a generic catch-all for all private end points - if we
// couldn't get a valid session, we exit now. Individual end points
// might have additional permission checks depending on the action.
if (!match.route.public && !ctx.owner) throw new ErrorForbidden();
return routeHandler(match.subPath, ctx);
}
// In a path such as "/api/files/SOME_ID/content" we want to find:
// - The base path: "api/files"
// - The ID: "SOME_ID"

View File

@ -4,11 +4,13 @@ import { DbConnection } from '../db';
import newModelFactory from '../models/factory';
import Applications from '../services/Applications';
import { AppContext, Env } from './types';
import routes from '../routes/routes';
export default async function(appContext: AppContext, env: Env, dbConnection: DbConnection, appLogger: ()=> LoggerWrapper) {
appContext.env = env;
appContext.db = dbConnection;
appContext.models = newModelFactory(appContext.db, config().baseUrl);
appContext.apps = new Applications(appContext.models);
appContext.models = newModelFactory(appContext.db, config().baseUrl, appContext.apps);
appContext.appLogger = appLogger;
appContext.routes = routes;
}

View File

@ -3,6 +3,7 @@ import * as Koa from 'koa';
import { DbConnection, User, Uuid } from '../db';
import { Models } from '../models/factory';
import Applications from '../services/Applications';
import { Routers } from './routeUtils';
export enum Env {
Dev = 'dev',
@ -25,6 +26,7 @@ export interface AppContext extends Koa.Context {
notifications: NotificationView[];
owner: User;
apps: Applications;
routes: Routers;
}
export enum DatabaseConfigClient {