diff --git a/packages/server/src/apps/joplin/Application.ts b/packages/server/src/apps/joplin/Application.ts index f9cdb5516..ebb660978 100644 --- a/packages/server/src/apps/joplin/Application.ts +++ b/packages/server/src/apps/joplin/Application.ts @@ -274,4 +274,8 @@ export default class Application extends BaseApplication { return true; } + public async processSharedContentForSave(file:File):Promise { + // console.info(' + } + } diff --git a/packages/server/src/apps/joplin/routes/notes.ts b/packages/server/src/apps/joplin/routes/notes.ts index 33e8e9d7f..1f75b336e 100644 --- a/packages/server/src/apps/joplin/routes/notes.ts +++ b/packages/server/src/apps/joplin/routes/notes.ts @@ -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; diff --git a/packages/server/src/middleware/routeHandler.ts b/packages/server/src/middleware/routeHandler.ts index 5238f4b3b..7d5d97f73 100644 --- a/packages/server/src/middleware/routeHandler.ts +++ b/packages/server/src/middleware/routeHandler.ts @@ -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'); diff --git a/packages/server/src/models/BaseModel.ts b/packages/server/src/models/BaseModel.ts index 9de19d01a..f75c6a51c 100644 --- a/packages/server/src/models/BaseModel.ts +++ b/packages/server/src/models/BaseModel.ts @@ -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 { 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_; diff --git a/packages/server/src/models/FileModel.ts b/packages/server/src/models/FileModel.ts index 82f900bf7..1bf0219d9 100644 --- a/packages/server/src/models/FileModel.ts +++ b/packages/server/src/models/FileModel.ts @@ -302,6 +302,12 @@ export default class FileModel extends BaseModel { } } + private async processSharedContentForSave(file:File):Promise { + 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 { 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 { 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; } } diff --git a/packages/server/src/models/factory.ts b/packages/server/src/models/factory.ts index b87f9d865..a3b44046a 100644 --- a/packages/server/src/models/factory.ts +++ b/packages/server/src/models/factory.ts @@ -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) { diff --git a/packages/server/src/services/Applications.ts b/packages/server/src/services/Applications.ts index d70aeda9f..ec025ca4e 100644 --- a/packages/server/src/services/Applications.ts +++ b/packages/server/src/services/Applications.ts @@ -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 { + const app = await this.joplin(); + return app.processSharedContentForSave(file); + } + public async localFileFromUrl(url: string): Promise { if (url.indexOf('apps/') !== 0) return null; diff --git a/packages/server/src/utils/routeUtils.ts b/packages/server/src/utils/routeUtils.ts index 7bc04c070..ffdff25a3 100644 --- a/packages/server/src/utils/routeUtils.ts +++ b/packages/server/src/utils/routeUtils.ts @@ -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" diff --git a/packages/server/src/utils/setupAppContext.ts b/packages/server/src/utils/setupAppContext.ts index 3da28b859..015b6356c 100644 --- a/packages/server/src/utils/setupAppContext.ts +++ b/packages/server/src/utils/setupAppContext.ts @@ -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; } diff --git a/packages/server/src/utils/types.ts b/packages/server/src/utils/types.ts index 0f922fa2e..e13da95db 100644 --- a/packages/server/src/utils/types.ts +++ b/packages/server/src/utils/types.ts @@ -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 {