mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
Server: Moved Joplin-specific context properties under its own namespace
This commit is contained in:
parent
8e789ee2ee
commit
bfa7ea7871
@ -202,16 +202,16 @@ async function main() {
|
||||
delete connectionCheckLogInfo.connection;
|
||||
|
||||
appLogger().info('Connection check:', connectionCheckLogInfo);
|
||||
const appContext = app.context as AppContext;
|
||||
const ctx = app.context as AppContext;
|
||||
|
||||
await setupAppContext(appContext, env, connectionCheck.connection, appLogger);
|
||||
await initializeJoplinUtils(config(), appContext.models, appContext.services.mustache);
|
||||
await setupAppContext(ctx, env, connectionCheck.connection, appLogger);
|
||||
await initializeJoplinUtils(config(), ctx.models, ctx.joplin.services.mustache);
|
||||
|
||||
appLogger().info('Migrating database...');
|
||||
await migrateDb(appContext.db);
|
||||
await migrateDb(ctx.db);
|
||||
|
||||
appLogger().info('Starting services...');
|
||||
await startServices(appContext);
|
||||
await startServices(ctx);
|
||||
|
||||
appLogger().info(`Call this for testing: \`curl ${config().apiBaseUrl}/api/ping\``);
|
||||
|
||||
|
@ -26,15 +26,15 @@ describe('notificationHandler', function() {
|
||||
});
|
||||
|
||||
{
|
||||
const context = await koaAppContext({ sessionId: session.id });
|
||||
await notificationHandler(context, koaNext);
|
||||
const ctx = await koaAppContext({ sessionId: session.id });
|
||||
await notificationHandler(ctx, koaNext);
|
||||
|
||||
const notifications: Notification[] = await models().notification().all();
|
||||
expect(notifications.length).toBe(1);
|
||||
expect(notifications[0].key).toBe('change_admin_password');
|
||||
expect(notifications[0].read).toBe(0);
|
||||
|
||||
expect(context.notifications.length).toBe(1);
|
||||
expect(ctx.joplin.notifications.length).toBe(1);
|
||||
}
|
||||
|
||||
{
|
||||
@ -43,15 +43,15 @@ describe('notificationHandler', function() {
|
||||
password: 'changed!',
|
||||
});
|
||||
|
||||
const context = await koaAppContext({ sessionId: session.id });
|
||||
await notificationHandler(context, koaNext);
|
||||
const ctx = await koaAppContext({ sessionId: session.id });
|
||||
await notificationHandler(ctx, koaNext);
|
||||
|
||||
const notifications: Notification[] = await models().notification().all();
|
||||
expect(notifications.length).toBe(1);
|
||||
expect(notifications[0].key).toBe('change_admin_password');
|
||||
expect(notifications[0].read).toBe(1);
|
||||
|
||||
expect(context.notifications.length).toBe(0);
|
||||
expect(ctx.joplin.notifications.length).toBe(0);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -10,31 +10,31 @@ import { NotificationKey } from '../models/NotificationModel';
|
||||
const logger = Logger.create('notificationHandler');
|
||||
|
||||
async function handleChangeAdminPasswordNotification(ctx: AppContext) {
|
||||
if (!ctx.owner.is_admin) return;
|
||||
if (!ctx.joplin.owner.is_admin) return;
|
||||
|
||||
const defaultAdmin = await ctx.models.user().login(defaultAdminEmail, defaultAdminPassword);
|
||||
const notificationModel = ctx.models.notification();
|
||||
const defaultAdmin = await ctx.joplin.models.user().login(defaultAdminEmail, defaultAdminPassword);
|
||||
const notificationModel = ctx.joplin.models.notification();
|
||||
|
||||
if (defaultAdmin) {
|
||||
await notificationModel.add(
|
||||
ctx.owner.id,
|
||||
ctx.joplin.owner.id,
|
||||
NotificationKey.ChangeAdminPassword,
|
||||
NotificationLevel.Important,
|
||||
_('The default admin password is insecure and has not been changed! [Change it now](%s)', ctx.models.user().profileUrl())
|
||||
_('The default admin password is insecure and has not been changed! [Change it now](%s)', ctx.joplin.models.user().profileUrl())
|
||||
);
|
||||
} else {
|
||||
await notificationModel.markAsRead(ctx.owner.id, NotificationKey.ChangeAdminPassword);
|
||||
await notificationModel.markAsRead(ctx.joplin.owner.id, NotificationKey.ChangeAdminPassword);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSqliteInProdNotification(ctx: AppContext) {
|
||||
if (!ctx.owner.is_admin) return;
|
||||
if (!ctx.joplin.owner.is_admin) return;
|
||||
|
||||
const notificationModel = ctx.models.notification();
|
||||
const notificationModel = ctx.joplin.models.notification();
|
||||
|
||||
if (config().database.client === 'sqlite3' && ctx.env === 'prod') {
|
||||
if (config().database.client === 'sqlite3' && ctx.joplin.env === 'prod') {
|
||||
await notificationModel.add(
|
||||
ctx.owner.id,
|
||||
ctx.joplin.owner.id,
|
||||
NotificationKey.UsingSqliteInProd
|
||||
);
|
||||
}
|
||||
@ -43,8 +43,8 @@ async function handleSqliteInProdNotification(ctx: AppContext) {
|
||||
async function makeNotificationViews(ctx: AppContext): Promise<NotificationView[]> {
|
||||
const markdownIt = new MarkdownIt();
|
||||
|
||||
const notificationModel = ctx.models.notification();
|
||||
const notifications = await notificationModel.allUnreadByUserId(ctx.owner.id);
|
||||
const notificationModel = ctx.joplin.models.notification();
|
||||
const notifications = await notificationModel.allUnreadByUserId(ctx.joplin.owner.id);
|
||||
const views: NotificationView[] = [];
|
||||
for (const n of notifications) {
|
||||
views.push({
|
||||
@ -62,15 +62,15 @@ async function makeNotificationViews(ctx: AppContext): Promise<NotificationView[
|
||||
// notifications for any issue it finds. It is only active for logged in users
|
||||
// on the website. It is inactive for API calls.
|
||||
export default async function(ctx: AppContext, next: KoaNext): Promise<void> {
|
||||
ctx.notifications = [];
|
||||
ctx.joplin.notifications = [];
|
||||
|
||||
try {
|
||||
if (isApiRequest(ctx)) return next();
|
||||
if (!ctx.owner) return next();
|
||||
if (!ctx.joplin.owner) return next();
|
||||
|
||||
await handleChangeAdminPasswordNotification(ctx);
|
||||
await handleSqliteInProdNotification(ctx);
|
||||
ctx.notifications = await makeNotificationViews(ctx);
|
||||
ctx.joplin.notifications = await makeNotificationViews(ctx);
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
}
|
||||
|
@ -22,12 +22,12 @@ describe('ownerHandler', function() {
|
||||
sessionId: session.id,
|
||||
});
|
||||
|
||||
context.owner = null;
|
||||
context.joplin.owner = null;
|
||||
|
||||
await ownerHandler(context, koaNext);
|
||||
|
||||
expect(!!context.owner).toBe(true);
|
||||
expect(context.owner.id).toBe(user.id);
|
||||
expect(!!context.joplin.owner).toBe(true);
|
||||
expect(context.joplin.owner.id).toBe(user.id);
|
||||
});
|
||||
|
||||
test('should not login user with invalid session ID', async function() {
|
||||
@ -37,11 +37,11 @@ describe('ownerHandler', function() {
|
||||
sessionId: 'ihack',
|
||||
});
|
||||
|
||||
context.owner = null;
|
||||
context.joplin.owner = null;
|
||||
|
||||
await ownerHandler(context, koaNext);
|
||||
|
||||
expect(!!context.owner).toBe(false);
|
||||
expect(!!context.joplin.owner).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -3,6 +3,6 @@ import { contextSessionId } from '../utils/requestUtils';
|
||||
|
||||
export default async function(ctx: AppContext, next: KoaNext): Promise<void> {
|
||||
const sessionId = contextSessionId(ctx, false);
|
||||
if (sessionId) ctx.owner = await ctx.models.session().sessionUser(sessionId);
|
||||
if (sessionId) ctx.joplin.owner = await ctx.joplin.models.session().sessionUser(sessionId);
|
||||
return next();
|
||||
}
|
||||
|
@ -7,17 +7,17 @@ export default async function(ctx: AppContext) {
|
||||
const requestStartTime = Date.now();
|
||||
|
||||
try {
|
||||
const responseObject = await execRequest(ctx.routes, ctx);
|
||||
const responseObject = await execRequest(ctx.joplin.routes, ctx);
|
||||
|
||||
if (responseObject instanceof Response) {
|
||||
ctx.response = responseObject.response;
|
||||
} else if (isView(responseObject)) {
|
||||
const view = responseObject as View;
|
||||
ctx.response.status = view?.content?.error ? view?.content?.error?.httpCode || 500 : 200;
|
||||
ctx.response.body = await ctx.services.mustache.renderView(view, {
|
||||
notifications: ctx.notifications || [],
|
||||
hasNotifications: !!ctx.notifications && !!ctx.notifications.length,
|
||||
owner: ctx.owner,
|
||||
ctx.response.body = await ctx.joplin.services.mustache.renderView(view, {
|
||||
notifications: ctx.joplin.notifications || [],
|
||||
hasNotifications: !!ctx.joplin.notifications && !!ctx.joplin.notifications.length,
|
||||
owner: ctx.joplin.owner,
|
||||
});
|
||||
} else {
|
||||
ctx.response.status = 200;
|
||||
@ -25,9 +25,9 @@ export default async function(ctx: AppContext) {
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.httpCode >= 400 && error.httpCode < 500) {
|
||||
ctx.appLogger().error(`${error.httpCode}: ` + `${ctx.request.method} ${ctx.path}` + ` : ${error.message}`);
|
||||
ctx.joplin.appLogger().error(`${error.httpCode}: ` + `${ctx.request.method} ${ctx.path}` + ` : ${error.message}`);
|
||||
} else {
|
||||
ctx.appLogger().error(error);
|
||||
ctx.joplin.appLogger().error(error);
|
||||
}
|
||||
|
||||
// Uncomment this when getting HTML blobs as errors while running tests.
|
||||
@ -47,15 +47,15 @@ export default async function(ctx: AppContext) {
|
||||
content: {
|
||||
error,
|
||||
stack: config().showErrorStackTraces ? error.stack : '',
|
||||
owner: ctx.owner,
|
||||
owner: ctx.joplin.owner,
|
||||
},
|
||||
title: 'Error',
|
||||
};
|
||||
ctx.response.body = await ctx.services.mustache.renderView(view);
|
||||
ctx.response.body = await ctx.joplin.services.mustache.renderView(view);
|
||||
} else { // JSON
|
||||
ctx.response.set('Content-Type', 'application/json');
|
||||
const r: any = { error: error.message };
|
||||
if (ctx.env === Env.Dev && error.stack) r.stack = error.stack;
|
||||
if (ctx.joplin.env === Env.Dev && error.stack) r.stack = error.stack;
|
||||
if (error.code) r.code = error.code;
|
||||
ctx.response.body = r;
|
||||
}
|
||||
@ -63,6 +63,6 @@ export default async function(ctx: AppContext) {
|
||||
// Technically this is not the total request duration because there are
|
||||
// other middlewares but that should give a good approximation
|
||||
const requestDuration = Date.now() - requestStartTime;
|
||||
ctx.appLogger().info(`${ctx.request.method} ${ctx.path} (${requestDuration}ms)`);
|
||||
ctx.joplin.appLogger().info(`${ctx.request.method} ${ctx.path} (${requestDuration}ms)`);
|
||||
}
|
||||
}
|
||||
|
@ -44,13 +44,16 @@ function createSubRequestContext(ctx: AppContext, subRequest: SubRequest): AppCo
|
||||
...subRequest.headers,
|
||||
},
|
||||
body: subRequest.body,
|
||||
appLogger: ctx.appLogger,
|
||||
joplin: {
|
||||
...ctx.joplin,
|
||||
appLogger: ctx.joplin.appLogger,
|
||||
services: ctx.joplin.services,
|
||||
db: ctx.joplin.db,
|
||||
models: ctx.joplin.models,
|
||||
routes: ctx.joplin.routes,
|
||||
},
|
||||
path: `/${subRequest.url}`,
|
||||
url: fullUrl,
|
||||
services: ctx.services,
|
||||
db: ctx.db,
|
||||
models: ctx.models,
|
||||
routes: ctx.routes,
|
||||
};
|
||||
|
||||
return newContext;
|
||||
|
@ -20,7 +20,7 @@ router.post('api/debug', async (_path: SubPath, ctx: AppContext) => {
|
||||
console.info(`Action: ${query.action}`);
|
||||
|
||||
if (query.action === 'createTestUsers') {
|
||||
await createTestUsers(ctx.db, config());
|
||||
await createTestUsers(ctx.joplin.db, config());
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -11,7 +11,7 @@ interface Event {
|
||||
|
||||
const supportedEvents: Record<string, Function> = {
|
||||
syncStart: async (_ctx: AppContext) => {
|
||||
// await ctx.models.share().updateSharedItems2(ctx.owner.id);
|
||||
// await ctx.joplin.models.share().updateSharedItems2(ctx.joplin.owner.id);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -17,7 +17,7 @@ const router = new Router(RouteType.Api);
|
||||
const batchMaxSize = 1 * MB;
|
||||
|
||||
export async function putItemContents(path: SubPath, ctx: AppContext, isBatch: boolean) {
|
||||
if (!ctx.owner.can_upload) throw new ErrorForbidden('Uploading content is disabled');
|
||||
if (!ctx.joplin.owner.can_upload) throw new ErrorForbidden('Uploading content is disabled');
|
||||
|
||||
const parsedBody = await formParse(ctx.req);
|
||||
const bodyFields = parsedBody.fields;
|
||||
@ -49,12 +49,12 @@ export async function putItemContents(path: SubPath, ctx: AppContext, isBatch: b
|
||||
// query parameter.
|
||||
if (ctx.query['share_id']) {
|
||||
saveOptions.shareId = ctx.query['share_id'];
|
||||
await ctx.models.item().checkIfAllowed(ctx.owner, AclAction.Create, { jop_share_id: saveOptions.shareId });
|
||||
await ctx.joplin.models.item().checkIfAllowed(ctx.joplin.owner, AclAction.Create, { jop_share_id: saveOptions.shareId });
|
||||
}
|
||||
|
||||
items = [
|
||||
{
|
||||
name: ctx.models.item().pathToName(path.id),
|
||||
name: ctx.joplin.models.item().pathToName(path.id),
|
||||
body: buffer,
|
||||
},
|
||||
];
|
||||
@ -63,9 +63,9 @@ export async function putItemContents(path: SubPath, ctx: AppContext, isBatch: b
|
||||
}
|
||||
}
|
||||
|
||||
const output = await ctx.models.item().saveFromRawContent(ctx.owner, items, saveOptions);
|
||||
const output = await ctx.joplin.models.item().saveFromRawContent(ctx.joplin.owner, items, saveOptions);
|
||||
for (const [name] of Object.entries(output)) {
|
||||
if (output[name].item) output[name].item = ctx.models.item().toApiOutput(output[name].item) as Item;
|
||||
if (output[name].item) output[name].item = ctx.joplin.models.item().toApiOutput(output[name].item) as Item;
|
||||
}
|
||||
return output;
|
||||
}
|
||||
@ -89,8 +89,8 @@ async function itemFromPath(userId: Uuid, itemModel: ItemModel, path: SubPath, m
|
||||
}
|
||||
|
||||
router.get('api/items/:id', async (path: SubPath, ctx: AppContext) => {
|
||||
const itemModel = ctx.models.item();
|
||||
const item = await itemFromPath(ctx.owner.id, itemModel, path);
|
||||
const itemModel = ctx.joplin.models.item();
|
||||
const item = await itemFromPath(ctx.joplin.owner.id, itemModel, path);
|
||||
return itemModel.toApiOutput(item);
|
||||
});
|
||||
|
||||
@ -99,12 +99,12 @@ router.del('api/items/:id', async (path: SubPath, ctx: AppContext) => {
|
||||
if (path.id === 'root' || path.id === 'root:/:') {
|
||||
// We use this for testing only and for safety reasons it's probably
|
||||
// best to disable it on production.
|
||||
if (ctx.env !== 'dev') throw new ErrorMethodNotAllowed('Deleting the root is not allowed');
|
||||
await ctx.models.item().deleteAll(ctx.owner.id);
|
||||
if (ctx.joplin.env !== 'dev') throw new ErrorMethodNotAllowed('Deleting the root is not allowed');
|
||||
await ctx.joplin.models.item().deleteAll(ctx.joplin.owner.id);
|
||||
} else {
|
||||
const item = await itemFromPath(ctx.owner.id, ctx.models.item(), path);
|
||||
await ctx.models.item().checkIfAllowed(ctx.owner, AclAction.Delete, item);
|
||||
await ctx.models.item().deleteForUser(ctx.owner.id, item);
|
||||
const item = await itemFromPath(ctx.joplin.owner.id, ctx.joplin.models.item(), path);
|
||||
await ctx.joplin.models.item().checkIfAllowed(ctx.joplin.owner, AclAction.Delete, item);
|
||||
await ctx.joplin.models.item().deleteForUser(ctx.joplin.owner.id, item);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof ErrorNotFound) {
|
||||
@ -116,8 +116,8 @@ router.del('api/items/:id', async (path: SubPath, ctx: AppContext) => {
|
||||
});
|
||||
|
||||
router.get('api/items/:id/content', async (path: SubPath, ctx: AppContext) => {
|
||||
const itemModel = ctx.models.item();
|
||||
const item = await itemFromPath(ctx.owner.id, itemModel, path);
|
||||
const itemModel = ctx.joplin.models.item();
|
||||
const item = await itemFromPath(ctx.joplin.owner.id, itemModel, path);
|
||||
const serializedContent = await itemModel.serializedContent(item.id);
|
||||
return respondWithItemContent(ctx.response, item, serializedContent);
|
||||
});
|
||||
@ -130,14 +130,14 @@ router.put('api/items/:id/content', async (path: SubPath, ctx: AppContext) => {
|
||||
});
|
||||
|
||||
router.get('api/items/:id/delta', async (_path: SubPath, ctx: AppContext) => {
|
||||
const changeModel = ctx.models.change();
|
||||
return changeModel.delta(ctx.owner.id, requestDeltaPagination(ctx.query));
|
||||
const changeModel = ctx.joplin.models.change();
|
||||
return changeModel.delta(ctx.joplin.owner.id, requestDeltaPagination(ctx.query));
|
||||
});
|
||||
|
||||
router.get('api/items/:id/children', async (path: SubPath, ctx: AppContext) => {
|
||||
const itemModel = ctx.models.item();
|
||||
const itemModel = ctx.joplin.models.item();
|
||||
const parentName = itemModel.pathToName(path.id);
|
||||
const result = await itemModel.children(ctx.owner.id, parentName, requestPagination(ctx.query));
|
||||
const result = await itemModel.children(ctx.joplin.owner.id, parentName, requestPagination(ctx.query));
|
||||
return result;
|
||||
});
|
||||
|
||||
|
@ -12,10 +12,10 @@ router.public = true;
|
||||
|
||||
router.post('api/sessions', async (_path: SubPath, ctx: AppContext) => {
|
||||
const fields: User = await bodyFields(ctx.req);
|
||||
const user = await ctx.models.user().login(fields.email, fields.password);
|
||||
const user = await ctx.joplin.models.user().login(fields.email, fields.password);
|
||||
if (!user) throw new ErrorForbidden('Invalid username or password');
|
||||
|
||||
const session = await ctx.models.session().createUserSession(user.id);
|
||||
const session = await ctx.joplin.models.session().createUserSession(user.id);
|
||||
return { id: session.id, user_id: session.user_id };
|
||||
});
|
||||
|
||||
|
@ -9,11 +9,11 @@ import { AclAction } from '../../models/BaseModel';
|
||||
const router = new Router(RouteType.Api);
|
||||
|
||||
router.patch('api/share_users/:id', async (path: SubPath, ctx: AppContext) => {
|
||||
const shareUserModel = ctx.models.shareUser();
|
||||
const shareUserModel = ctx.joplin.models.shareUser();
|
||||
const shareUser = await shareUserModel.load(path.id);
|
||||
if (!shareUser) throw new ErrorNotFound();
|
||||
|
||||
await shareUserModel.checkIfAllowed(ctx.owner, AclAction.Update, shareUser);
|
||||
await shareUserModel.checkIfAllowed(ctx.joplin.owner, AclAction.Update, shareUser);
|
||||
|
||||
const body = await bodyFields<any>(ctx.req);
|
||||
|
||||
@ -25,20 +25,20 @@ router.patch('api/share_users/:id', async (path: SubPath, ctx: AppContext) => {
|
||||
});
|
||||
|
||||
router.del('api/share_users/:id', async (path: SubPath, ctx: AppContext) => {
|
||||
const shareUser = await ctx.models.shareUser().load(path.id);
|
||||
const shareUser = await ctx.joplin.models.shareUser().load(path.id);
|
||||
if (!shareUser) throw new ErrorNotFound();
|
||||
|
||||
await ctx.models.shareUser().checkIfAllowed(ctx.owner, AclAction.Delete, shareUser);
|
||||
await ctx.models.shareUser().delete(shareUser.id);
|
||||
await ctx.joplin.models.shareUser().checkIfAllowed(ctx.joplin.owner, AclAction.Delete, shareUser);
|
||||
await ctx.joplin.models.shareUser().delete(shareUser.id);
|
||||
});
|
||||
|
||||
router.get('api/share_users', async (_path: SubPath, ctx: AppContext) => {
|
||||
const shareUsers = await ctx.models.shareUser().byUserId(ctx.owner.id);
|
||||
const shareUsers = await ctx.joplin.models.shareUser().byUserId(ctx.joplin.owner.id);
|
||||
|
||||
const items: any[] = [];
|
||||
for (const su of shareUsers) {
|
||||
const share = await ctx.models.share().load(su.share_id);
|
||||
const sharer = await ctx.models.user().load(share.owner_id);
|
||||
const share = await ctx.joplin.models.share().load(su.share_id);
|
||||
const sharer = await ctx.joplin.models.user().load(share.owner_id);
|
||||
|
||||
items.push({
|
||||
id: su.id,
|
||||
|
@ -19,7 +19,7 @@ router.public = true;
|
||||
router.post('api/shares', async (_path: SubPath, ctx: AppContext) => {
|
||||
ownerRequired(ctx);
|
||||
|
||||
const shareModel = ctx.models.share();
|
||||
const shareModel = ctx.joplin.models.share();
|
||||
const fields = await bodyFields<any>(ctx.req);
|
||||
const shareInput: ShareApiInput = shareModel.fromApiInput(fields) as ShareApiInput;
|
||||
if (fields.folder_id) shareInput.folder_id = fields.folder_id;
|
||||
@ -31,9 +31,9 @@ router.post('api/shares', async (_path: SubPath, ctx: AppContext) => {
|
||||
// - Additionally, the App method is available, but not exposed via the API.
|
||||
|
||||
if (shareInput.folder_id) {
|
||||
return ctx.models.share().shareFolder(ctx.owner, shareInput.folder_id);
|
||||
return ctx.joplin.models.share().shareFolder(ctx.joplin.owner, shareInput.folder_id);
|
||||
} else if (shareInput.note_id) {
|
||||
return ctx.models.share().shareNote(ctx.owner, shareInput.note_id);
|
||||
return ctx.joplin.models.share().shareNote(ctx.joplin.owner, shareInput.note_id);
|
||||
} else {
|
||||
throw new ErrorBadRequest('Either folder_id or note_id must be provided');
|
||||
}
|
||||
@ -47,28 +47,28 @@ router.post('api/shares/:id/users', async (path: SubPath, ctx: AppContext) => {
|
||||
}
|
||||
|
||||
const fields = await bodyFields(ctx.req) as UserInput;
|
||||
const user = await ctx.models.user().loadByEmail(fields.email);
|
||||
const user = await ctx.joplin.models.user().loadByEmail(fields.email);
|
||||
if (!user) throw new ErrorNotFound('User not found');
|
||||
|
||||
const shareId = path.id;
|
||||
|
||||
await ctx.models.shareUser().checkIfAllowed(ctx.owner, AclAction.Create, {
|
||||
await ctx.joplin.models.shareUser().checkIfAllowed(ctx.joplin.owner, AclAction.Create, {
|
||||
share_id: shareId,
|
||||
user_id: user.id,
|
||||
});
|
||||
|
||||
return ctx.models.shareUser().addByEmail(shareId, user.email);
|
||||
return ctx.joplin.models.shareUser().addByEmail(shareId, user.email);
|
||||
});
|
||||
|
||||
router.get('api/shares/:id/users', async (path: SubPath, ctx: AppContext) => {
|
||||
ownerRequired(ctx);
|
||||
|
||||
const shareId = path.id;
|
||||
const share = await ctx.models.share().load(shareId);
|
||||
await ctx.models.share().checkIfAllowed(ctx.owner, AclAction.Read, share);
|
||||
const share = await ctx.joplin.models.share().load(shareId);
|
||||
await ctx.joplin.models.share().checkIfAllowed(ctx.joplin.owner, AclAction.Read, share);
|
||||
|
||||
const shareUsers = await ctx.models.shareUser().byShareId(shareId, null);
|
||||
const users = await ctx.models.user().loadByIds(shareUsers.map(su => su.user_id));
|
||||
const shareUsers = await ctx.joplin.models.shareUser().byShareId(shareId, null);
|
||||
const users = await ctx.joplin.models.user().loadByIds(shareUsers.map(su => su.user_id));
|
||||
|
||||
const items = shareUsers.map(su => {
|
||||
const user = users.find(u => u.id === su.user_id);
|
||||
@ -90,7 +90,7 @@ router.get('api/shares/:id/users', async (path: SubPath, ctx: AppContext) => {
|
||||
});
|
||||
|
||||
router.get('api/shares/:id', async (path: SubPath, ctx: AppContext) => {
|
||||
const shareModel = ctx.models.share();
|
||||
const shareModel = ctx.joplin.models.share();
|
||||
const share = await shareModel.load(path.id);
|
||||
|
||||
if (share && share.type === ShareType.Note) {
|
||||
@ -105,7 +105,7 @@ router.get('api/shares/:id', async (path: SubPath, ctx: AppContext) => {
|
||||
router.get('api/shares', async (_path: SubPath, ctx: AppContext) => {
|
||||
ownerRequired(ctx);
|
||||
|
||||
const shares = ctx.models.share().toApiOutput(await ctx.models.share().sharesByUser(ctx.owner.id)) as Share[];
|
||||
const shares = ctx.joplin.models.share().toApiOutput(await ctx.joplin.models.share().sharesByUser(ctx.joplin.owner.id)) as Share[];
|
||||
// Fake paginated results so that it can be added later on, if needed.
|
||||
return {
|
||||
items: shares.map(share => {
|
||||
@ -123,9 +123,9 @@ router.get('api/shares', async (_path: SubPath, ctx: AppContext) => {
|
||||
router.del('api/shares/:id', async (path: SubPath, ctx: AppContext) => {
|
||||
ownerRequired(ctx);
|
||||
|
||||
const share = await ctx.models.share().load(path.id);
|
||||
await ctx.models.share().checkIfAllowed(ctx.owner, AclAction.Delete, share);
|
||||
await ctx.models.share().delete(share.id);
|
||||
const share = await ctx.joplin.models.share().load(path.id);
|
||||
await ctx.joplin.models.share().checkIfAllowed(ctx.joplin.owner, AclAction.Delete, share);
|
||||
await ctx.joplin.models.share().delete(share.id);
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
@ -11,23 +11,23 @@ import uuidgen from '../../utils/uuidgen';
|
||||
const router = new Router(RouteType.Api);
|
||||
|
||||
async function fetchUser(path: SubPath, ctx: AppContext): Promise<User> {
|
||||
const user = await ctx.models.user().load(path.id);
|
||||
const user = await ctx.joplin.models.user().load(path.id);
|
||||
if (!user) throw new ErrorNotFound(`No user with ID ${path.id}`);
|
||||
return user;
|
||||
}
|
||||
|
||||
async function postedUserFromContext(ctx: AppContext): Promise<User> {
|
||||
return ctx.models.user().fromApiInput(await bodyFields<any>(ctx.req));
|
||||
return ctx.joplin.models.user().fromApiInput(await bodyFields<any>(ctx.req));
|
||||
}
|
||||
|
||||
router.get('api/users/:id', async (path: SubPath, ctx: AppContext) => {
|
||||
const user = await fetchUser(path, ctx);
|
||||
await ctx.models.user().checkIfAllowed(ctx.owner, AclAction.Read, user);
|
||||
await ctx.joplin.models.user().checkIfAllowed(ctx.joplin.owner, AclAction.Read, user);
|
||||
return user;
|
||||
});
|
||||
|
||||
router.post('api/users', async (_path: SubPath, ctx: AppContext) => {
|
||||
await ctx.models.user().checkIfAllowed(ctx.owner, AclAction.Create);
|
||||
await ctx.joplin.models.user().checkIfAllowed(ctx.joplin.owner, AclAction.Create);
|
||||
const user = await postedUserFromContext(ctx);
|
||||
|
||||
// We set a random password because it's required, but user will have to
|
||||
@ -35,30 +35,30 @@ router.post('api/users', async (_path: SubPath, ctx: AppContext) => {
|
||||
user.password = uuidgen();
|
||||
user.must_set_password = 1;
|
||||
user.email_confirmed = 0;
|
||||
const output = await ctx.models.user().save(user);
|
||||
return ctx.models.user().toApiOutput(output);
|
||||
const output = await ctx.joplin.models.user().save(user);
|
||||
return ctx.joplin.models.user().toApiOutput(output);
|
||||
});
|
||||
|
||||
router.get('api/users', async (_path: SubPath, ctx: AppContext) => {
|
||||
await ctx.models.user().checkIfAllowed(ctx.owner, AclAction.List);
|
||||
await ctx.joplin.models.user().checkIfAllowed(ctx.joplin.owner, AclAction.List);
|
||||
|
||||
return {
|
||||
items: await ctx.models.user().all(),
|
||||
items: await ctx.joplin.models.user().all(),
|
||||
has_more: false,
|
||||
};
|
||||
});
|
||||
|
||||
router.del('api/users/:id', async (path: SubPath, ctx: AppContext) => {
|
||||
const user = await fetchUser(path, ctx);
|
||||
await ctx.models.user().checkIfAllowed(ctx.owner, AclAction.Delete, user);
|
||||
await ctx.models.user().delete(user.id);
|
||||
await ctx.joplin.models.user().checkIfAllowed(ctx.joplin.owner, AclAction.Delete, user);
|
||||
await ctx.joplin.models.user().delete(user.id);
|
||||
});
|
||||
|
||||
router.patch('api/users/:id', async (path: SubPath, ctx: AppContext) => {
|
||||
const user = await fetchUser(path, ctx);
|
||||
await ctx.models.user().checkIfAllowed(ctx.owner, AclAction.Update, user);
|
||||
await ctx.joplin.models.user().checkIfAllowed(ctx.joplin.owner, AclAction.Update, user);
|
||||
const postedUser = await postedUserFromContext(ctx);
|
||||
await ctx.models.user().save({ id: user.id, ...postedUser });
|
||||
await ctx.joplin.models.user().save({ id: user.id, ...postedUser });
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
@ -53,7 +53,7 @@ router.public = true;
|
||||
router.get('', async (path: SubPath, ctx: AppContext) => {
|
||||
// Redirect to either /login or /home when trying to access the root
|
||||
if (!path.id && !path.link) {
|
||||
if (ctx.owner) {
|
||||
if (ctx.joplin.owner) {
|
||||
return redirect(ctx, 'home');
|
||||
} else {
|
||||
return redirect(ctx, 'login');
|
||||
|
@ -14,11 +14,11 @@ const router = new Router(RouteType.Web);
|
||||
|
||||
router.get('changes', async (_path: SubPath, ctx: AppContext) => {
|
||||
const pagination = makeTablePagination(ctx.query, 'updated_time', PaginationOrderDir.DESC);
|
||||
const paginatedChanges = await ctx.models.change().allByUser(ctx.owner.id, pagination);
|
||||
const items = await ctx.models.item().loadByIds(paginatedChanges.items.map(i => i.item_id), { fields: ['id'] });
|
||||
const paginatedChanges = await ctx.joplin.models.change().allByUser(ctx.joplin.owner.id, pagination);
|
||||
const items = await ctx.joplin.models.item().loadByIds(paginatedChanges.items.map(i => i.item_id), { fields: ['id'] });
|
||||
|
||||
const table: Table = {
|
||||
baseUrl: ctx.models.change().changeUrl(),
|
||||
baseUrl: ctx.joplin.models.change().changeUrl(),
|
||||
requestQuery: ctx.query,
|
||||
pageCount: paginatedChanges.page_count,
|
||||
pagination,
|
||||
@ -42,7 +42,7 @@ router.get('changes', async (_path: SubPath, ctx: AppContext) => {
|
||||
{
|
||||
value: change.item_name,
|
||||
stretch: true,
|
||||
url: showItemUrls(config()) ? (items.find(i => i.id === change.item_id) ? ctx.models.item().itemContentUrl(change.item_id) : '') : null,
|
||||
url: showItemUrls(config()) ? (items.find(i => i.id === change.item_id) ? ctx.joplin.models.item().itemContentUrl(change.item_id) : '') : null,
|
||||
},
|
||||
{
|
||||
value: changeTypeToString(change.type),
|
||||
|
@ -15,7 +15,7 @@ router.get('home', async (_path: SubPath, ctx: AppContext) => {
|
||||
contextSessionId(ctx);
|
||||
|
||||
if (ctx.method === 'GET') {
|
||||
const accountProps = accountTypeProperties(ctx.owner.account_type);
|
||||
const accountProps = accountTypeProperties(ctx.joplin.owner.account_type);
|
||||
|
||||
const view = defaultView('home', 'Home');
|
||||
view.content = {
|
||||
@ -26,7 +26,7 @@ router.get('home', async (_path: SubPath, ctx: AppContext) => {
|
||||
},
|
||||
{
|
||||
label: 'Is Admin',
|
||||
value: yesOrNo(ctx.owner.is_admin),
|
||||
value: yesOrNo(ctx.joplin.owner.is_admin),
|
||||
},
|
||||
{
|
||||
label: 'Max Item Size',
|
||||
|
@ -16,12 +16,12 @@ const router = new Router(RouteType.Web);
|
||||
|
||||
router.get('items', async (_path: SubPath, ctx: AppContext) => {
|
||||
const pagination = makeTablePagination(ctx.query, 'name', PaginationOrderDir.ASC);
|
||||
const paginatedItems = await ctx.models.item().children(ctx.owner.id, '', pagination, { fields: ['id', 'name', 'updated_time', 'mime_type', 'content_size'] });
|
||||
const paginatedItems = await ctx.joplin.models.item().children(ctx.joplin.owner.id, '', pagination, { fields: ['id', 'name', 'updated_time', 'mime_type', 'content_size'] });
|
||||
|
||||
const table: Table = {
|
||||
baseUrl: ctx.models.item().itemUrl(),
|
||||
baseUrl: ctx.joplin.models.item().itemUrl(),
|
||||
requestQuery: ctx.query,
|
||||
pageCount: Math.ceil((await ctx.models.item().childrenCount(ctx.owner.id, '')) / pagination.limit),
|
||||
pageCount: Math.ceil((await ctx.joplin.models.item().childrenCount(ctx.joplin.owner.id, '')) / pagination.limit),
|
||||
pagination,
|
||||
headers: [
|
||||
{
|
||||
@ -72,7 +72,7 @@ router.get('items', async (_path: SubPath, ctx: AppContext) => {
|
||||
});
|
||||
|
||||
router.get('items/:id/content', async (path: SubPath, ctx: AppContext) => {
|
||||
const itemModel = ctx.models.item();
|
||||
const itemModel = ctx.joplin.models.item();
|
||||
const item = await itemModel.loadWithContent(path.id);
|
||||
if (!item) throw new ErrorNotFound();
|
||||
return respondWithItemContent(ctx.response, item, item.content);
|
||||
@ -83,13 +83,13 @@ router.post('items', async (_path: SubPath, ctx: AppContext) => {
|
||||
const fields = body.fields;
|
||||
|
||||
if (fields.delete_all_button) {
|
||||
const itemModel = ctx.models.item();
|
||||
await itemModel.deleteAll(ctx.owner.id);
|
||||
const itemModel = ctx.joplin.models.item();
|
||||
await itemModel.deleteAll(ctx.joplin.owner.id);
|
||||
} else {
|
||||
throw new Error('Invalid form button');
|
||||
}
|
||||
|
||||
return redirect(ctx, await ctx.models.item().itemUrl());
|
||||
return redirect(ctx, await ctx.joplin.models.item().itemUrl());
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
@ -28,7 +28,7 @@ router.post('login', async (_path: SubPath, ctx: AppContext) => {
|
||||
try {
|
||||
const body = await formParse(ctx.req);
|
||||
|
||||
const session = await ctx.models.session().authenticate(body.fields.email, body.fields.password);
|
||||
const session = await ctx.joplin.models.session().authenticate(body.fields.email, body.fields.password);
|
||||
ctx.cookies.set('sessionId', session.id);
|
||||
return redirect(ctx, `${config().baseUrl}/home`);
|
||||
} catch (error) {
|
||||
|
@ -10,7 +10,7 @@ const router = new Router(RouteType.Web);
|
||||
router.post('logout', async (_path: SubPath, ctx: AppContext) => {
|
||||
const sessionId = contextSessionId(ctx, false);
|
||||
ctx.cookies.set('sessionId', '');
|
||||
await ctx.models.session().logout(sessionId);
|
||||
await ctx.joplin.models.session().logout(sessionId);
|
||||
return redirect(ctx, `${config().baseUrl}/login`);
|
||||
});
|
||||
|
||||
|
@ -11,7 +11,7 @@ const router = new Router(RouteType.Web);
|
||||
router.patch('notifications/:id', async (path: SubPath, ctx: AppContext) => {
|
||||
const fields: Notification = await bodyFields(ctx.req);
|
||||
const notificationId = path.id;
|
||||
const model = ctx.models.notification();
|
||||
const model = ctx.joplin.models.notification();
|
||||
const existingNotification = await model.load(notificationId);
|
||||
if (!existingNotification) throw new ErrorNotFound();
|
||||
|
||||
|
@ -24,19 +24,19 @@ const router: Router = new Router(RouteType.Web);
|
||||
router.public = true;
|
||||
|
||||
router.get('shares/:id', async (path: SubPath, ctx: AppContext) => {
|
||||
const shareModel = ctx.models.share();
|
||||
const shareModel = ctx.joplin.models.share();
|
||||
|
||||
const share = await shareModel.load(path.id);
|
||||
if (!share) throw new ErrorNotFound();
|
||||
|
||||
const itemModel = ctx.models.item();
|
||||
const itemModel = ctx.joplin.models.item();
|
||||
|
||||
const item = await itemModel.loadWithContent(share.item_id);
|
||||
if (!item) throw new ErrorNotFound();
|
||||
|
||||
const result = await renderItem(ctx, item, share);
|
||||
|
||||
ctx.models.share().checkShareUrl(share, ctx.URL.origin);
|
||||
ctx.joplin.models.share().checkShareUrl(share, ctx.URL.origin);
|
||||
|
||||
ctx.response.body = result.body;
|
||||
ctx.response.set('Content-Type', result.mime);
|
||||
|
@ -43,17 +43,17 @@ router.post('signup', async (_path: SubPath, ctx: AppContext) => {
|
||||
const formUser = await bodyFields<FormUser>(ctx.req);
|
||||
const password = checkPassword(formUser, true);
|
||||
|
||||
const user = await ctx.models.user().save({
|
||||
const user = await ctx.joplin.models.user().save({
|
||||
...accountTypeProperties(AccountType.Basic),
|
||||
email: formUser.email,
|
||||
full_name: formUser.full_name,
|
||||
password,
|
||||
});
|
||||
|
||||
const session = await ctx.models.session().createUserSession(user.id);
|
||||
const session = await ctx.joplin.models.session().createUserSession(user.id);
|
||||
ctx.cookies.set('sessionId', session.id);
|
||||
|
||||
await ctx.models.notification().add(user.id, NotificationKey.ConfirmEmail);
|
||||
await ctx.joplin.models.notification().add(user.id, NotificationKey.ConfirmEmail);
|
||||
|
||||
return redirect(ctx, `${config().baseUrl}/home`);
|
||||
} catch (error) {
|
||||
|
@ -137,7 +137,7 @@ const postHandlers: Record<string, StripeRouteHandler> = {
|
||||
const stripeUserId = checkoutSession.customer as string;
|
||||
const stripeSubscriptionId = checkoutSession.subscription as string;
|
||||
|
||||
await ctx.models.subscription().saveUserAndSubscription(
|
||||
await ctx.joplin.models.subscription().saveUserAndSubscription(
|
||||
checkoutSession.customer_details.email || checkoutSession.customer_email,
|
||||
AccountType.Pro,
|
||||
stripeUserId,
|
||||
@ -158,7 +158,7 @@ const postHandlers: Record<string, StripeRouteHandler> = {
|
||||
// saved in checkout.session.completed.
|
||||
|
||||
const invoice = event.data.object as Stripe.Invoice;
|
||||
await ctx.models.subscription().handlePayment(invoice.subscription as string, true);
|
||||
await ctx.joplin.models.subscription().handlePayment(invoice.subscription as string, true);
|
||||
},
|
||||
|
||||
'invoice.payment_failed': async () => {
|
||||
@ -170,7 +170,7 @@ const postHandlers: Record<string, StripeRouteHandler> = {
|
||||
|
||||
const invoice = event.data.object as Stripe.Invoice;
|
||||
const subId = invoice.subscription as string;
|
||||
await ctx.models.subscription().handlePayment(subId, false);
|
||||
await ctx.joplin.models.subscription().handlePayment(subId, false);
|
||||
},
|
||||
|
||||
};
|
||||
@ -202,9 +202,9 @@ const getHandlers: Record<string, StripeRouteHandler> = {
|
||||
},
|
||||
|
||||
portal: async (stripe: Stripe, _path: SubPath, ctx: AppContext) => {
|
||||
if (!ctx.owner) throw new ErrorForbidden('Please login to access the subscription portal');
|
||||
if (!ctx.joplin.owner) throw new ErrorForbidden('Please login to access the subscription portal');
|
||||
|
||||
const sub = await ctx.models.subscription().byUserId(ctx.owner.id);
|
||||
const sub = await ctx.joplin.models.subscription().byUserId(ctx.joplin.owner.id);
|
||||
if (!sub) throw new ErrorNotFound('Could not find subscription');
|
||||
|
||||
const billingPortalSession = await stripe.billingPortal.sessions.create({ customer: sub.stripe_user_id as string });
|
||||
|
@ -78,8 +78,8 @@ function userIsMe(path: SubPath): boolean {
|
||||
const router = new Router(RouteType.Web);
|
||||
|
||||
router.get('users', async (_path: SubPath, ctx: AppContext) => {
|
||||
const userModel = ctx.models.user();
|
||||
await userModel.checkIfAllowed(ctx.owner, AclAction.List);
|
||||
const userModel = ctx.joplin.models.user();
|
||||
await userModel.checkIfAllowed(ctx.joplin.owner, AclAction.List);
|
||||
|
||||
const users = await userModel.all();
|
||||
|
||||
@ -101,16 +101,16 @@ router.get('users', async (_path: SubPath, ctx: AppContext) => {
|
||||
});
|
||||
|
||||
router.get('users/:id', async (path: SubPath, ctx: AppContext, user: User = null, error: any = null) => {
|
||||
const owner = ctx.owner;
|
||||
const owner = ctx.joplin.owner;
|
||||
const isMe = userIsMe(path);
|
||||
const isNew = userIsNew(path);
|
||||
const userModel = ctx.models.user();
|
||||
const userModel = ctx.joplin.models.user();
|
||||
const userId = userIsMe(path) ? owner.id : path.id;
|
||||
|
||||
user = !isNew ? user || await userModel.load(userId) : null;
|
||||
if (isNew && !user) user = defaultUser();
|
||||
|
||||
await userModel.checkIfAllowed(ctx.owner, AclAction.Read, user);
|
||||
await userModel.checkIfAllowed(ctx.joplin.owner, AclAction.Read, user);
|
||||
|
||||
let postUrl = '';
|
||||
|
||||
@ -147,9 +147,9 @@ router.publicSchemas.push('users/:id/confirm');
|
||||
router.get('users/:id/confirm', async (path: SubPath, ctx: AppContext, error: Error = null) => {
|
||||
const userId = path.id;
|
||||
const token = ctx.query.token;
|
||||
if (token) await ctx.models.user().confirmEmail(userId, token);
|
||||
if (token) await ctx.joplin.models.user().confirmEmail(userId, token);
|
||||
|
||||
const user = await ctx.models.user().load(userId);
|
||||
const user = await ctx.joplin.models.user().load(userId);
|
||||
|
||||
if (user.must_set_password) {
|
||||
const view: View = {
|
||||
@ -158,17 +158,17 @@ router.get('users/:id/confirm', async (path: SubPath, ctx: AppContext, error: Er
|
||||
user,
|
||||
error,
|
||||
token,
|
||||
postUrl: ctx.models.user().confirmUrl(userId, token),
|
||||
postUrl: ctx.joplin.models.user().confirmUrl(userId, token),
|
||||
},
|
||||
navbar: false,
|
||||
};
|
||||
|
||||
return view;
|
||||
} else {
|
||||
await ctx.models.token().deleteByValue(userId, token);
|
||||
await ctx.models.notification().add(userId, NotificationKey.EmailConfirmed);
|
||||
await ctx.joplin.models.token().deleteByValue(userId, token);
|
||||
await ctx.joplin.models.notification().add(userId, NotificationKey.EmailConfirmed);
|
||||
|
||||
if (ctx.owner) {
|
||||
if (ctx.joplin.owner) {
|
||||
return redirect(ctx, `${config().baseUrl}/home`);
|
||||
} else {
|
||||
return redirect(ctx, `${config().baseUrl}/login`);
|
||||
@ -187,17 +187,17 @@ router.post('users/:id/confirm', async (path: SubPath, ctx: AppContext) => {
|
||||
|
||||
try {
|
||||
const fields = await bodyFields<SetPasswordFormData>(ctx.req);
|
||||
await ctx.models.token().checkToken(userId, fields.token);
|
||||
await ctx.joplin.models.token().checkToken(userId, fields.token);
|
||||
|
||||
const password = checkPassword(fields, true);
|
||||
|
||||
await ctx.models.user().save({ id: userId, password, must_set_password: 0 });
|
||||
await ctx.models.token().deleteByValue(userId, fields.token);
|
||||
await ctx.joplin.models.user().save({ id: userId, password, must_set_password: 0 });
|
||||
await ctx.joplin.models.token().deleteByValue(userId, fields.token);
|
||||
|
||||
const session = await ctx.models.session().createUserSession(userId);
|
||||
const session = await ctx.joplin.models.session().createUserSession(userId);
|
||||
ctx.cookies.set('sessionId', session.id);
|
||||
|
||||
await ctx.models.notification().add(userId, NotificationKey.PasswordSet);
|
||||
await ctx.joplin.models.notification().add(userId, NotificationKey.PasswordSet);
|
||||
|
||||
return redirect(ctx, `${config().baseUrl}/home`);
|
||||
} catch (error) {
|
||||
@ -217,7 +217,7 @@ interface FormFields {
|
||||
|
||||
router.post('users', async (path: SubPath, ctx: AppContext) => {
|
||||
let user: User = {};
|
||||
const userId = userIsMe(path) ? ctx.owner.id : path.id;
|
||||
const userId = userIsMe(path) ? ctx.joplin.owner.id : path.id;
|
||||
|
||||
try {
|
||||
const body = await formParse(ctx.req);
|
||||
@ -226,11 +226,11 @@ router.post('users', async (path: SubPath, ctx: AppContext) => {
|
||||
if (userIsMe(path)) fields.id = userId;
|
||||
user = makeUser(isNew, fields);
|
||||
|
||||
const userModel = ctx.models.user();
|
||||
const userModel = ctx.joplin.models.user();
|
||||
|
||||
if (fields.post_button) {
|
||||
const userToSave: User = userModel.fromApiInput(user);
|
||||
await userModel.checkIfAllowed(ctx.owner, isNew ? AclAction.Create : AclAction.Update, userToSave);
|
||||
await userModel.checkIfAllowed(ctx.joplin.owner, isNew ? AclAction.Create : AclAction.Update, userToSave);
|
||||
|
||||
if (isNew) {
|
||||
await userModel.save(userToSave);
|
||||
@ -239,7 +239,7 @@ router.post('users', async (path: SubPath, ctx: AppContext) => {
|
||||
}
|
||||
} else if (fields.delete_button) {
|
||||
const user = await userModel.load(path.id);
|
||||
await userModel.checkIfAllowed(ctx.owner, AclAction.Delete, user);
|
||||
await userModel.checkIfAllowed(ctx.joplin.owner, AclAction.Delete, user);
|
||||
await userModel.delete(path.id);
|
||||
} else if (fields.send_reset_password_email) {
|
||||
const user = await userModel.load(path.id);
|
||||
|
@ -10,7 +10,7 @@ interface RouteInfo {
|
||||
export default class Router {
|
||||
|
||||
// When the router is public, we do not check that a valid session is
|
||||
// available (that ctx.owner is defined). It means by default any user, even
|
||||
// available (that ctx.joplin.owner is defined). It means by default any user, even
|
||||
// not logged in, can access any route of this router. End points that
|
||||
// should not be publicly available should call ownerRequired(ctx);
|
||||
public public: boolean = false;
|
||||
|
@ -43,7 +43,7 @@ export async function bodyFields<T>(req: any/* , filter:string[] = null*/): Prom
|
||||
}
|
||||
|
||||
export function ownerRequired(ctx: AppContext) {
|
||||
if (!ctx.owner) throw new ErrorForbidden();
|
||||
if (!ctx.joplin.owner) throw new ErrorForbidden();
|
||||
}
|
||||
|
||||
export function headerSessionId(headers: any): string {
|
||||
|
@ -191,7 +191,7 @@ export async function execRequest(routes: Routers, ctx: AppContext) {
|
||||
// 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.isPublic(match.subPath.schema) && !ctx.owner) throw new ErrorForbidden();
|
||||
if (!match.route.isPublic(match.subPath.schema) && !ctx.joplin.owner) throw new ErrorForbidden();
|
||||
|
||||
return endPoint.handler(match.subPath, ctx);
|
||||
}
|
||||
|
@ -24,14 +24,19 @@ async function setupServices(env: Env, models: Models, config: Config): Promise<
|
||||
}
|
||||
|
||||
export default async function(appContext: AppContext, env: Env, dbConnection: DbConnection, appLogger: ()=> LoggerWrapper): Promise<AppContext> {
|
||||
appContext.env = env;
|
||||
appContext.db = dbConnection;
|
||||
appContext.models = newModelFactory(appContext.db, config());
|
||||
appContext.services = await setupServices(env, appContext.models, config());
|
||||
appContext.appLogger = appLogger;
|
||||
appContext.routes = { ...routes };
|
||||
const models = newModelFactory(dbConnection, config());
|
||||
|
||||
if (env === Env.Prod) delete appContext.routes['api/debug'];
|
||||
appContext.joplin = {
|
||||
...appContext.joplin,
|
||||
env: env,
|
||||
db: dbConnection,
|
||||
models: models,
|
||||
services: await setupServices(env, models, config()),
|
||||
appLogger: appLogger,
|
||||
routes: { ...routes },
|
||||
};
|
||||
|
||||
if (env === Env.Prod) delete appContext.joplin.routes['api/debug'];
|
||||
|
||||
return appContext;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { AppContext } from './types';
|
||||
|
||||
export default async function startServices(appContext: AppContext) {
|
||||
const services = appContext.services;
|
||||
const services = appContext.joplin.services;
|
||||
|
||||
void services.share.runInBackground();
|
||||
void services.email.runInBackground();
|
||||
|
@ -175,16 +175,20 @@ export async function koaAppContext(options: AppContextTestOptions = null): Prom
|
||||
|
||||
const appLogger = Logger.create('AppTest');
|
||||
|
||||
const baseAppContext = await setupAppContext({} as any, Env.Dev, db_, () => appLogger);
|
||||
|
||||
// Set type to "any" because the Koa context has many properties and we
|
||||
// don't need to mock all of them.
|
||||
const appContext: any = {
|
||||
...await setupAppContext({} as any, Env.Dev, db_, () => appLogger),
|
||||
env: Env.Dev,
|
||||
db: db_,
|
||||
models: models(),
|
||||
appLogger: () => appLogger,
|
||||
baseAppContext,
|
||||
joplin: {
|
||||
...baseAppContext.joplin,
|
||||
env: Env.Dev,
|
||||
db: db_,
|
||||
models: models(),
|
||||
owner: owner,
|
||||
},
|
||||
path: req.url,
|
||||
owner: owner,
|
||||
cookies: new FakeCookies(),
|
||||
request: new FakeRequest(req),
|
||||
response: new FakeResponse(),
|
||||
|
@ -18,7 +18,7 @@ export interface NotificationView {
|
||||
closeUrl: string;
|
||||
}
|
||||
|
||||
export interface AppContext extends Koa.Context {
|
||||
interface AppContextJoplin {
|
||||
env: Env;
|
||||
db: DbConnection;
|
||||
models: Models;
|
||||
@ -29,6 +29,22 @@ export interface AppContext extends Koa.Context {
|
||||
services: Services;
|
||||
}
|
||||
|
||||
export interface AppContext extends Koa.Context {
|
||||
joplin: AppContextJoplin;
|
||||
|
||||
// All the properties under `joplin` were previously at the root, so to make
|
||||
// sure they are no longer used anywhere we set them to "never", as that
|
||||
// would trigger the TypeScript compiler. Later on, all this can be removed.
|
||||
env: never;
|
||||
db: never;
|
||||
models: never;
|
||||
appLogger: never;
|
||||
notifications: never;
|
||||
owner: never;
|
||||
routes: never;
|
||||
services: never;
|
||||
}
|
||||
|
||||
export enum DatabaseConfigClient {
|
||||
PostgreSQL = 'pg',
|
||||
SQLite = 'sqlite3',
|
||||
|
Loading…
Reference in New Issue
Block a user