mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
(core) Add methods for quarantining documents
Summary: Adds a new CLI command, doc, with a subcommand that quarantines an active document. Adds a group query param to a housekeeping endpoint for updating the document group prior to checking if a doc needs to be reassigned. Both methods require support user credentials. Test Plan: Server tests. (Additional testing will be done manually on staging.) Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D3570
This commit is contained in:
parent
ee109e9186
commit
fbba6b8f52
@ -137,6 +137,10 @@ class DummyDocWorkerMap implements IDocWorkerMap {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async updateDocGroup(docId: string, docGroup: string): Promise<void> {
|
||||||
|
// nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
public getRedisClient() {
|
public getRedisClient() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -517,6 +521,10 @@ export class DocWorkerMap implements IDocWorkerMap {
|
|||||||
return this._client.getAsync(`doc-${docId}-group`);
|
return this._client.getAsync(`doc-${docId}-group`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async updateDocGroup(docId: string, docGroup: string): Promise<void> {
|
||||||
|
await this._client.setAsync(`doc-${docId}-group`, docGroup);
|
||||||
|
}
|
||||||
|
|
||||||
public getRedisClient(): RedisClient {
|
public getRedisClient(): RedisClient {
|
||||||
return this._client;
|
return this._client;
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import { GristServer } from 'app/server/lib/GristServer';
|
|||||||
import { IElectionStore } from 'app/server/lib/IElectionStore';
|
import { IElectionStore } from 'app/server/lib/IElectionStore';
|
||||||
import log from 'app/server/lib/log';
|
import log from 'app/server/lib/log';
|
||||||
import { IPermitStore } from 'app/server/lib/Permit';
|
import { IPermitStore } from 'app/server/lib/Permit';
|
||||||
import { stringParam } from 'app/server/lib/requestUtils';
|
import { optStringParam, stringParam } from 'app/server/lib/requestUtils';
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
import * as Fetch from 'node-fetch';
|
import * as Fetch from 'node-fetch';
|
||||||
@ -130,7 +130,7 @@ export class Housekeeper {
|
|||||||
// Remove unlisted snapshots that are not recorded in inventory.
|
// Remove unlisted snapshots that are not recorded in inventory.
|
||||||
// Once all such snapshots have been removed, there should be no
|
// Once all such snapshots have been removed, there should be no
|
||||||
// further need for this endpoint.
|
// further need for this endpoint.
|
||||||
app.post('/api/housekeeping/docs/:docId/snapshots/clean', this._withSupport(async (docId, headers) => {
|
app.post('/api/housekeeping/docs/:docId/snapshots/clean', this._withSupport(async (_req, docId, headers) => {
|
||||||
const url = await this._server.getHomeUrlByDocId(docId, `/api/docs/${docId}/snapshots/remove`);
|
const url = await this._server.getHomeUrlByDocId(docId, `/api/docs/${docId}/snapshots/remove`);
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -143,7 +143,7 @@ export class Housekeeper {
|
|||||||
// use, for allowing support to help users looking to purge some
|
// use, for allowing support to help users looking to purge some
|
||||||
// information that leaked into document history that they'd
|
// information that leaked into document history that they'd
|
||||||
// prefer not be there, until there's an alternative.
|
// prefer not be there, until there's an alternative.
|
||||||
app.post('/api/housekeeping/docs/:docId/states/remove', this._withSupport(async (docId, headers) => {
|
app.post('/api/housekeeping/docs/:docId/states/remove', this._withSupport(async (_req, docId, headers) => {
|
||||||
const url = await this._server.getHomeUrlByDocId(docId, `/api/docs/${docId}/states/remove`);
|
const url = await this._server.getHomeUrlByDocId(docId, `/api/docs/${docId}/states/remove`);
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -154,7 +154,7 @@ export class Housekeeper {
|
|||||||
|
|
||||||
// Force a document to reload. Can be useful during administrative
|
// Force a document to reload. Can be useful during administrative
|
||||||
// actions.
|
// actions.
|
||||||
app.post('/api/housekeeping/docs/:docId/force-reload', this._withSupport(async (docId, headers) => {
|
app.post('/api/housekeeping/docs/:docId/force-reload', this._withSupport(async (_req, docId, headers) => {
|
||||||
const url = await this._server.getHomeUrlByDocId(docId, `/api/docs/${docId}/force-reload`);
|
const url = await this._server.getHomeUrlByDocId(docId, `/api/docs/${docId}/force-reload`);
|
||||||
return fetch(url, {
|
return fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -164,13 +164,19 @@ export class Housekeeper {
|
|||||||
|
|
||||||
// Move a document to its assigned worker. Can be useful during administrative
|
// Move a document to its assigned worker. Can be useful during administrative
|
||||||
// actions.
|
// actions.
|
||||||
app.post('/api/housekeeping/docs/:docId/assign', this._withSupport(async (docId, headers) => {
|
//
|
||||||
const url = await this._server.getHomeUrlByDocId(docId, `/api/docs/${docId}/assign`);
|
// Optionally accepts a `group` query param for updating the document's group prior
|
||||||
return fetch(url, {
|
// to moving. This is useful for controlling which worker group the document is assigned
|
||||||
|
// a worker from.
|
||||||
|
app.post('/api/housekeeping/docs/:docId/assign', this._withSupport(async (req, docId, headers) => {
|
||||||
|
const url = new URL(await this._server.getHomeUrlByDocId(docId, `/api/docs/${docId}/assign`));
|
||||||
|
const group = optStringParam(req.query.group);
|
||||||
|
if (group) { url.searchParams.set('group', group); }
|
||||||
|
return fetch(url.toString(), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers,
|
headers,
|
||||||
});
|
});
|
||||||
}));
|
}, 'assign-doc'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -221,7 +227,8 @@ export class Housekeeper {
|
|||||||
// Call a document endpoint with a permit, cleaning up after the call.
|
// Call a document endpoint with a permit, cleaning up after the call.
|
||||||
// Checks that the user is the support user.
|
// Checks that the user is the support user.
|
||||||
private _withSupport(
|
private _withSupport(
|
||||||
callback: (docId: string, headers: Record<string, string>) => Promise<Fetch.Response>
|
callback: (req: express.Request, docId: string, headers: Record<string, string>) => Promise<Fetch.Response>,
|
||||||
|
permitAction?: string,
|
||||||
): express.RequestHandler {
|
): express.RequestHandler {
|
||||||
return expressWrap(async (req, res) => {
|
return expressWrap(async (req, res) => {
|
||||||
const userId = getAuthorizedUserId(req);
|
const userId = getAuthorizedUserId(req);
|
||||||
@ -229,9 +236,9 @@ export class Housekeeper {
|
|||||||
throw new ApiError('access denied', 403);
|
throw new ApiError('access denied', 403);
|
||||||
}
|
}
|
||||||
const docId = stringParam(req.params.docId, 'docId');
|
const docId = stringParam(req.params.docId, 'docId');
|
||||||
const permitKey = await this._permitStore.setPermit({docId});
|
const permitKey = await this._permitStore.setPermit({docId, action: permitAction});
|
||||||
try {
|
try {
|
||||||
const result = await callback(docId, {
|
const result = await callback(req, docId, {
|
||||||
Permit: permitKey,
|
Permit: permitKey,
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
});
|
});
|
||||||
|
@ -599,8 +599,15 @@ export class DocWorkerApi {
|
|||||||
// and frees it for reassignment if not. Has no effect if document is in the
|
// and frees it for reassignment if not. Has no effect if document is in the
|
||||||
// expected group. Does not require specific rights. Returns true if the document
|
// expected group. Does not require specific rights. Returns true if the document
|
||||||
// is freed up for reassignment, otherwise false.
|
// is freed up for reassignment, otherwise false.
|
||||||
|
//
|
||||||
|
// Optionally accepts a `group` query param for updating the document's group prior
|
||||||
|
// to (possible) reassignment. (Note: Requires special permit.)
|
||||||
this._app.post('/api/docs/:docId/assign', canEdit, throttled(async (req, res) => {
|
this._app.post('/api/docs/:docId/assign', canEdit, throttled(async (req, res) => {
|
||||||
const docId = getDocId(req);
|
const docId = getDocId(req);
|
||||||
|
const group = optStringParam(req.query.group);
|
||||||
|
if (group && req.specialPermit?.action === 'assign-doc') {
|
||||||
|
await this._docWorkerMap.updateDocGroup(docId, group);
|
||||||
|
}
|
||||||
const status = await this._docWorkerMap.getDocWorker(docId);
|
const status = await this._docWorkerMap.getDocWorker(docId);
|
||||||
if (!status) { res.json(false); return; }
|
if (!status) { res.json(false); return; }
|
||||||
const workerGroup = await this._docWorkerMap.getWorkerGroup(status.docWorker.id);
|
const workerGroup = await this._docWorkerMap.getWorkerGroup(status.docWorker.id);
|
||||||
|
@ -67,6 +67,7 @@ export interface IDocWorkerMap extends IPermitStores, IElectionStore, IChecksumS
|
|||||||
|
|
||||||
getWorkerGroup(workerId: string): Promise<string|null>;
|
getWorkerGroup(workerId: string): Promise<string|null>;
|
||||||
getDocGroup(docId: string): Promise<string|null>;
|
getDocGroup(docId: string): Promise<string|null>;
|
||||||
|
updateDocGroup(docId: string, docGroup: string): Promise<void>;
|
||||||
|
|
||||||
getRedisClient(): RedisClient|null;
|
getRedisClient(): RedisClient|null;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user