(core) Clean up and refactor uses of HomeDBManager.getDoc

Summary:
Firstly I just wanted some more consistency and less repetition in places where Documents are retrieved from the DB, so it's more obvious when code differs from the norm. Main changes for that part:

- Let HomeDBManager accept a `Request` directly and convert it to a `Scope`, and use this in a few places.
- `getScope` tries `req.docAuth.docId` if `req.params` doesn't have a docId.

I also refactored how `_createActiveDoc` gets the document URL, separating out getting the document from getting a URL for it. This is because I want to use that document object in a future diff, but I also just find it cleaner. Notable changes for that:

- Extracted a new method `HomeDBManager.getRawDocById` as an alternative to `getDoc` that's explicitly for when you only have a document ID.
- Removed the interface method `GristServer.getDocUrl` and its two implementations because it wasn't used elsewhere and it didn't really add anything on top of getting a doc (now done by `getRawDocById`) and `getResourceUrl`.
- Between `cachedDoc` and `getRawDocById` (which represent previously existing code paths) also try `getDoc(getScope(docSession.req))`, which is new, because it seems better to only `getRawDocById` as a last resort.

Test Plan: Existing tests

Reviewers: georgegevoian

Reviewed By: georgegevoian

Differential Revision: https://phab.getgrist.com/D3328
This commit is contained in:
Alex Hall
2022-03-24 12:59:47 +02:00
parent b1c3943bf4
commit 546096fcc9
8 changed files with 147 additions and 92 deletions

View File

@@ -287,7 +287,7 @@ export class ApiServer {
// GET /api/docs/:did
// Get information about a document.
this._app.get('/api/docs/:did', expressWrap(async (req, res) => {
const query = await this._dbManager.getDoc(getDocScope(req));
const query = await this._dbManager.getDoc(req);
return sendOkReply(req, res, query);
}));

View File

@@ -7,12 +7,23 @@ import {FullUser, UserProfile} from 'app/common/LoginSessionAPI';
import {checkSubdomainValidity} from 'app/common/orgNameUtils';
import {UserOrgPrefs} from 'app/common/Prefs';
import * as roles from 'app/common/roles';
// TODO: API should implement UserAPI
import {ANONYMOUS_USER_EMAIL, DocumentProperties, EVERYONE_EMAIL, getRealAccess,
ManagerDelta, NEW_DOCUMENT_CODE, OrganizationProperties,
Organization as OrgInfo, PermissionData, PermissionDelta, SUPPORT_EMAIL, UserAccessData,
UserOptions,
WorkspaceProperties} from "app/common/UserAPI";
import {StringUnion} from 'app/common/StringUnion';
import {
ANONYMOUS_USER_EMAIL,
DocumentProperties,
EVERYONE_EMAIL,
getRealAccess,
ManagerDelta,
NEW_DOCUMENT_CODE,
OrganizationProperties,
Organization as OrgInfo,
PermissionData,
PermissionDelta,
SUPPORT_EMAIL,
UserAccessData,
UserOptions,
WorkspaceProperties
} from "app/common/UserAPI";
import {AclRule, AclRuleDoc, AclRuleOrg, AclRuleWs} from "app/gen-server/entity/AclRule";
import {Alias} from "app/gen-server/entity/Alias";
import {BillingAccount, ExternalBillingOptions} from "app/gen-server/entity/BillingAccount";
@@ -29,19 +40,34 @@ import {Workspace} from "app/gen-server/entity/Workspace";
import {Permissions} from 'app/gen-server/lib/Permissions';
import {scrubUserFromOrg} from "app/gen-server/lib/scrubUserFromOrg";
import {applyPatch} from 'app/gen-server/lib/TypeORMPatches';
import {bitOr, getRawAndEntities, hasAtLeastOneOfTheseIds, hasOnlyTheseIdsOrNull,
now, readJson} from 'app/gen-server/sqlUtils';
import {
bitOr,
getRawAndEntities,
hasAtLeastOneOfTheseIds,
hasOnlyTheseIdsOrNull,
now,
readJson
} from 'app/gen-server/sqlUtils';
import {makeId} from 'app/server/lib/idUtils';
import * as log from 'app/server/lib/log';
import {Permit} from 'app/server/lib/Permit';
import {getScope} from 'app/server/lib/requestUtils';
import {WebHookSecret} from "app/server/lib/Triggers";
import {StringUnion} from 'app/common/StringUnion';
import {EventEmitter} from 'events';
import {Request} from "express";
import {
Brackets,
Connection,
createConnection,
DatabaseType,
EntityManager,
getConnection,
SelectQueryBuilder,
WhereExpression
} from "typeorm";
import * as uuidv4 from "uuid/v4";
import flatten = require('lodash/flatten');
import pick = require('lodash/pick');
import {Brackets, Connection, createConnection, DatabaseType, EntityManager,
getConnection, SelectQueryBuilder, WhereExpression} from "typeorm";
import * as uuidv4 from "uuid/v4";
// Support transactions in Sqlite in async code. This is a monkey patch, affecting
// the prototypes of various TypeORM classes.
@@ -1038,7 +1064,8 @@ export class HomeDBManager extends EventEmitter {
// Calls getDocImpl() and returns the Document from that, caching a fresh DocAuthResult along
// the way. Note that we only cache the access level, not Document itself.
public async getDoc(scope: Scope): Promise<Document> {
public async getDoc(reqOrScope: Request | Scope): Promise<Document> {
const scope = "params" in reqOrScope ? getScope(reqOrScope) : reqOrScope;
const key = getDocAuthKeyFromScope(scope);
const promise = this.getDocImpl(key);
await mapSetOrClear(this._docAuthCache, stringifyDocAuthKey(key), makeDocAuthResult(promise));
@@ -1052,6 +1079,10 @@ export class HomeDBManager extends EventEmitter {
return doc;
}
public async getRawDocById(docId: string) {
return await this.getDoc({urlId: docId, userId: this.getPreviewerUserId(), showAll: true});
}
// Returns access info for the given doc and user, caching the results for DOC_AUTH_CACHE_TTL
// ms. This helps reduce database load created by liberal authorization requests.
public async getDocAuthCached(key: DocAuthKey): Promise<DocAuthResult> {