unleash-server
Version:
Unleash is an enterprise ready feature toggles service. It provides different strategies for handling feature toggles.
307 lines • 11.1 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const notfound_error_1 = __importDefault(require("../error/notfound-error"));
const constants_1 = require("../util/constants");
const metrics_helper_1 = __importDefault(require("../util/metrics-helper"));
const metric_events_1 = require("../metric-events");
const COLUMNS = [
'id',
'name',
'description',
'created_at',
'health',
'updated_at',
];
const TABLE = 'projects';
class ProjectStore {
constructor(db, eventBus, getLogger, flagResolver) {
this.db = db;
this.logger = getLogger('project-store.ts');
this.timer = (action) => metrics_helper_1.default.wrapTimer(eventBus, metric_events_1.DB_TIME, {
store: 'project',
action,
});
this.flagResolver = flagResolver;
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
fieldToRow(data) {
return {
id: data.id,
name: data.name,
description: data.description,
};
}
destroy() { }
async exists(id) {
const result = await this.db.raw(`SELECT EXISTS(SELECT 1 FROM ${TABLE} WHERE id = ?) AS present`, [id]);
const { present } = result.rows[0];
return present;
}
async getProjectsWithCounts(query, userId) {
const projectTimer = this.timer('getProjectsWithCount');
let projects = this.db(TABLE)
.leftJoin('features', 'features.project', 'projects.id')
.orderBy('projects.name', 'asc');
if (query) {
projects = projects.where(query);
}
let selectColumns = [
this.db.raw('projects.id, projects.name, projects.description, projects.health, projects.updated_at, count(features.name) AS number_of_features'),
];
let groupByColumns = ['projects.id'];
if (userId && this.flagResolver.isEnabled('favorites')) {
projects = projects.leftJoin(`favorite_projects`, function () {
this.on('favorite_projects.project', 'projects.id').andOnVal('favorite_projects.user_id', '=', userId);
});
selectColumns = [
...selectColumns,
this.db.raw('favorite_projects.project is not null as favorite'),
];
groupByColumns = [...groupByColumns, 'favorite_projects.project'];
}
const projectAndFeatureCount = await projects
.select(selectColumns)
.groupBy(groupByColumns);
const projectsWithFeatureCount = projectAndFeatureCount.map(this.mapProjectWithCountRow);
projectTimer();
const memberTimer = this.timer('getMemberCount');
const memberCount = await this.getMembersCount();
memberTimer();
const memberMap = new Map(memberCount.map((c) => [c.project, Number(c.count)]));
return projectsWithFeatureCount.map((r) => {
return { ...r, memberCount: memberMap.get(r.id) };
});
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
mapProjectWithCountRow(row) {
return {
name: row.name,
id: row.id,
description: row.description,
health: row.health,
favorite: row.favorite,
featureCount: Number(row.number_of_features) || 0,
memberCount: Number(row.number_of_users) || 0,
updatedAt: row.updated_at,
};
}
async getAll(query = {}) {
const rows = await this.db
.select(COLUMNS)
.from(TABLE)
.where(query)
.orderBy('name', 'asc');
return rows.map(this.mapRow);
}
async get(id) {
return this.db
.first(COLUMNS)
.from(TABLE)
.where({ id })
.then(this.mapRow);
}
async hasProject(id) {
const result = await this.db.raw(`SELECT EXISTS(SELECT 1 FROM ${TABLE} WHERE id = ?) AS present`, [id]);
const { present } = result.rows[0];
return present;
}
async updateHealth(healthUpdate) {
await this.db(TABLE)
.where({ id: healthUpdate.id })
.update({ health: healthUpdate.health, updated_at: new Date() });
}
async create(project) {
const row = await this.db(TABLE)
.insert(this.fieldToRow(project))
.returning('*');
return this.mapRow(row[0]);
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
async update(data) {
try {
await this.db(TABLE)
.where({ id: data.id })
.update(this.fieldToRow(data));
}
catch (err) {
this.logger.error('Could not update project, error: ', err);
}
}
async importProjects(projects, environments) {
const rows = await this.db(TABLE)
.insert(projects.map(this.fieldToRow))
.returning(COLUMNS)
.onConflict('id')
.ignore();
if (environments && rows.length > 0) {
environments.forEach((env) => {
projects.forEach(async (project) => {
await this.addEnvironmentToProject(project.id, env.name);
});
});
return rows.map(this.mapRow);
}
return [];
}
async addDefaultEnvironment(projects) {
const environments = projects.map((p) => ({
project_id: p.id,
environment_name: constants_1.DEFAULT_ENV,
}));
await this.db('project_environments')
.insert(environments)
.onConflict(['project_id', 'environment_name'])
.ignore();
}
async deleteAll() {
await this.db(TABLE).del();
}
async delete(id) {
try {
await this.db(TABLE).where({ id }).del();
}
catch (err) {
this.logger.error('Could not delete project, error: ', err);
}
}
async getProjectLinksForEnvironments(environments) {
let rows = await this.db('project_environments')
.select(['project_id', 'environment_name'])
.whereIn('environment_name', environments);
return rows.map(this.mapLinkRow);
}
async deleteEnvironmentForProject(id, environment) {
await this.db('project_environments')
.where({
project_id: id,
environment_name: environment,
})
.del();
}
async addEnvironmentToProject(id, environment) {
await this.db('project_environments')
.insert({
project_id: id,
environment_name: environment,
})
.onConflict(['project_id', 'environment_name'])
.ignore();
}
async addEnvironmentToProjects(environment, projects) {
const rows = await Promise.all(projects.map(async (projectId) => {
return {
project_id: projectId,
environment_name: environment,
};
}));
await this.db('project_environments')
.insert(rows)
.onConflict(['project_id', 'environment_name'])
.ignore();
}
async getEnvironmentsForProject(id) {
return this.db('project_environments')
.where({
project_id: id,
})
.innerJoin('environments', 'project_environments.environment_name', 'environments.name')
.orderBy('environments.sort_order', 'asc')
.orderBy('project_environments.environment_name', 'asc')
.pluck('project_environments.environment_name');
}
async getMembersCount() {
const members = await this.db
.select('project')
.from((db) => {
db.select('user_id', 'project')
.from('role_user')
.leftJoin('roles', 'role_user.role_id', 'roles.id')
.where((builder) => builder.whereNot('type', 'root'))
.union((queryBuilder) => {
queryBuilder
.select('user_id', 'project')
.from('group_role')
.leftJoin('group_user', 'group_user.group_id', 'group_role.group_id');
})
.as('query');
})
.groupBy('project')
.count('user_id');
return members;
}
async getProjectsByUser(userId) {
const members = await this.db
.from((db) => {
db.select('project')
.from('role_user')
.leftJoin('roles', 'role_user.role_id', 'roles.id')
.where('type', 'root')
.andWhere('name', 'Editor')
.andWhere('user_id', userId)
.union((queryBuilder) => {
queryBuilder
.select('project')
.from('group_role')
.leftJoin('group_user', 'group_user.group_id', 'group_role.group_id')
.where('user_id', userId);
})
.as('query');
})
.pluck('project');
return members;
}
async getMembersCountByProject(projectId) {
const members = await this.db
.from((db) => {
db.select('user_id')
.from('role_user')
.leftJoin('roles', 'role_user.role_id', 'roles.id')
.where((builder) => builder
.where('project', projectId)
.whereNot('type', 'root'))
.union((queryBuilder) => {
queryBuilder
.select('user_id')
.from('group_role')
.leftJoin('group_user', 'group_user.group_id', 'group_role.group_id')
.where('project', projectId);
})
.as('query');
})
.count()
.first();
return Number(members.count);
}
async count() {
return this.db
.from(TABLE)
.count('*')
.then((res) => Number(res[0].count));
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
mapLinkRow(row) {
return {
environmentName: row.environment_name,
projectId: row.project_id,
};
}
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
mapRow(row) {
if (!row) {
throw new notfound_error_1.default('No project found');
}
return {
id: row.id,
name: row.name,
description: row.description,
createdAt: row.created_at,
health: row.health || 100,
updatedAt: row.updated_at || new Date(),
};
}
}
exports.default = ProjectStore;
//# sourceMappingURL=project-store.js.map