forest-cli
Version:
The Lumberjacks' toolbelt is the Forest CLI which makes easy to manage your back office application directly from the terminal.
150 lines • 13.5 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const context_1 = __importDefault(require("@forestadmin/context"));
const superagent_1 = __importDefault(require("superagent"));
// No bulk endpoint for teams/role, so enrich per-user — bound the fan-out.
const ENRICH_CONCURRENCY = 5;
/** Run `fn` over `items` with at most `limit` in flight, preserving order. */
async function mapWithConcurrency(items, limit, fn) {
const results = new Array(items.length);
let cursor = 0;
const worker = async () => {
while (cursor < items.length) {
const index = cursor;
cursor += 1;
// eslint-disable-next-line no-await-in-loop -- bounded worker: one task at a time per worker
results[index] = await fn(items[index]);
}
};
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
return results;
}
class UserManager {
constructor(config) {
this.config = config;
const { assertPresent, authenticator, env, logger } = context_1.default.inject();
assertPresent({ authenticator, env, logger });
this.authenticator = authenticator;
this.env = env;
this.logger = logger;
}
headers() {
return {
Authorization: `Bearer ${this.authenticator.getAuthToken()}`,
'forest-project-id': String(this.config.projectId),
};
}
async listForProject() {
const users = await this.requestUsers();
return mapWithConcurrency(users, ENRICH_CONCURRENCY, user => this.toProjectUser(user));
}
/** Raw project-users list (one call, no enrichment). Failures surface to the caller. */
async requestUsers() {
const response = await superagent_1.default
.get(`${this.env.FOREST_SERVER_URL}/api/projects/${this.config.projectId}/users`)
.set(this.headers())
.send();
return (response.body?.data ?? []);
}
async toProjectUser(user, strict = false) {
const attributes = user.attributes ?? {};
const userId = user.id ? String(user.id) : '';
const [teams, role] = await Promise.all([
this.fetchTeams(userId, strict),
this.fetchRole(userId, strict),
]);
return {
id: userId,
email: String(attributes.email ?? ''),
name: [attributes.first_name, attributes.last_name].filter(Boolean).join(' '),
permissionLevel: String(attributes.permission_level ?? ''),
roleId: role.id,
role: role.name,
teams,
};
}
/**
* Resolve a project user by email for the edit path. Returns undefined when no
* user matches; transport/API failures throw. The role/teams are read in `strict`
* mode (never degraded): the edit reassigns the role and replaces the teams from
* these values, so a failed read must abort the edit, not silently wipe them.
*/
async findByEmail(email) {
const users = await this.requestUsers();
const match = users.find(user => String(user.attributes?.email ?? '') === email);
if (!match)
return undefined;
return this.toProjectUser(match, true);
}
async editUser(userId, currentPermissionLevel, currentRoleId, changes) {
const effectiveRoleId = changes.roleId ?? currentRoleId;
// Ids are sent as integers: the server's update validator requires
// `id: number` for data/role/teams/projects on this route.
const relationships = {
projects: { data: [{ type: 'projects', id: Number(this.config.projectId) }] },
...(effectiveRoleId && { role: { data: { type: 'roles', id: Number(effectiveRoleId) } } }),
};
if (changes.teamIds) {
relationships.teams = {
data: changes.teamIds.map(id => ({ type: 'teams', id: Number(id) })),
};
}
await superagent_1.default
.put(`${this.env.FOREST_SERVER_URL}/api/users/${userId}`)
.set(this.headers())
.send({
data: {
type: 'users',
id: Number(userId),
attributes: { permission_level: changes.permissionLevel ?? currentPermissionLevel },
relationships,
},
});
}
// `strict` is used by the edit path: a failed read must surface (abort the edit)
// rather than degrade, since the edit writes these values back.
async fetchTeams(userId, strict = false) {
if (!userId)
return [];
try {
const response = await superagent_1.default
.get(`${this.env.FOREST_SERVER_URL}/api/users/${userId}/teams`)
.set(this.headers())
.send();
return (response.body?.data ?? [])
.map(team => String(team.attributes?.name ?? ''))
.filter(Boolean);
}
catch (error) {
if (strict)
throw error;
this.logger.warn(`Could not fetch teams for user ${userId} (showing none): ${error}`);
return [];
}
}
async fetchRole(userId, strict = false) {
if (!userId)
return { id: null, name: null };
try {
const response = await superagent_1.default
.get(`${this.env.FOREST_SERVER_URL}/api/users`)
.query({ projectId: this.config.projectId, id: userId, include: 'role' })
.set(this.headers())
.send();
const role = (response.body?.included ?? []).find(resource => resource.type === 'roles');
const name = role?.attributes?.name;
return { id: role?.id ?? null, name: typeof name === 'string' ? name : null };
}
catch (error) {
if (strict)
throw error;
this.logger.warn(`Could not fetch role for user ${userId} (showing none): ${error}`);
return { id: null, name: null };
}
}
}
exports.default = UserManager;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXNlci1tYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlcnZpY2VzL3VzZXItbWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLG1FQUEyQztBQUMzQyw0REFBK0I7QUEyQi9CLDJFQUEyRTtBQUMzRSxNQUFNLGtCQUFrQixHQUFHLENBQUMsQ0FBQztBQUU3Qiw4RUFBOEU7QUFDOUUsS0FBSyxVQUFVLGtCQUFrQixDQUMvQixLQUFVLEVBQ1YsS0FBYSxFQUNiLEVBQTJCO0lBRTNCLE1BQU0sT0FBTyxHQUFRLElBQUksS0FBSyxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUM3QyxJQUFJLE1BQU0sR0FBRyxDQUFDLENBQUM7SUFFZixNQUFNLE1BQU0sR0FBRyxLQUFLLElBQW1CLEVBQUU7UUFDdkMsT0FBTyxNQUFNLEdBQUcsS0FBSyxDQUFDLE1BQU0sRUFBRTtZQUM1QixNQUFNLEtBQUssR0FBRyxNQUFNLENBQUM7WUFDckIsTUFBTSxJQUFJLENBQUMsQ0FBQztZQUNaLDZGQUE2RjtZQUM3RixPQUFPLENBQUMsS0FBSyxDQUFDLEdBQUcsTUFBTSxFQUFFLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7U0FDekM7SUFDSCxDQUFDLENBQUM7SUFFRixNQUFNLE9BQU8sQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFLE1BQU0sRUFBRSxJQUFJLENBQUMsR0FBRyxDQUFDLEtBQUssRUFBRSxLQUFLLENBQUMsTUFBTSxDQUFDLEVBQUUsRUFBRSxNQUFNLENBQUMsQ0FBQyxDQUFDO0lBRWpGLE9BQU8sT0FBTyxDQUFDO0FBQ2pCLENBQUM7QUFFRCxNQUFxQixXQUFXO0lBUzlCLFlBQVksTUFBc0M7UUFDaEQsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDckIsTUFBTSxFQUFFLGFBQWEsRUFBRSxhQUFhLEVBQUUsR0FBRyxFQUFFLE1B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