simplify router and db interface

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2022-09-06 17:11:01 +02:00
parent 9895e6bfb2
commit 46cda8068d
8 changed files with 155 additions and 729 deletions
+4 -156
View File
@@ -5,169 +5,17 @@
```ts
import express from 'express';
import { IdentityClient } from '@backstage/plugin-auth-node';
import { Knex } from 'knex';
import { PluginDatabaseManager } from '@backstage/backend-common';
// @public
export function createRouter<T>(
options: RouterOptions<T>,
): Promise<express.Router>;
// @public
export class DatabaseUserSettingsStore
implements UserSettingsStore<Knex.Transaction>
{
// (undocumented)
static create(options: {
database: PluginDatabaseManager;
}): Promise<DatabaseUserSettingsStore>;
// (undocumented)
delete(
tx: Knex.Transaction<any, any[]>,
opts: {
userEntityRef: string;
bucket: string;
key: string;
},
): Promise<void>;
// (undocumented)
deleteAll(
tx: Knex.Transaction<any, any[]>,
opts: {
userEntityRef: string;
},
): Promise<void>;
// (undocumented)
deleteBucket(
tx: Knex.Transaction<any, any[]>,
opts: {
userEntityRef: string;
bucket: string;
},
): Promise<void>;
// (undocumented)
get(
tx: Knex.Transaction<any, any[]>,
opts: {
userEntityRef: string;
bucket: string;
key: string;
},
): Promise<UserSetting>;
// (undocumented)
getAll(
tx: Knex.Transaction,
opts: {
userEntityRef: string;
},
): Promise<Pick<RawDbUserSettingsRow, 'key' | 'value' | 'bucket'>[]>;
// (undocumented)
getBucket(
tx: Knex.Transaction<any, any[]>,
opts: {
userEntityRef: string;
bucket: string;
},
): Promise<UserSetting[]>;
// (undocumented)
set(
tx: Knex.Transaction<any, any[]>,
opts: {
userEntityRef: string;
bucket: string;
key: string;
value: string;
},
): Promise<void>;
// (undocumented)
transaction<T>(fn: (tx: Knex.Transaction) => Promise<T>): Promise<T>;
}
export function createRouter(options: RouterOptions): Promise<express.Router>;
// @public (undocumented)
export type RawDbUserSettingsRow = {
user_entity_ref: string;
bucket: string;
key: string;
value: string;
};
// @public (undocumented)
export interface RouterOptions<T> {
export interface RouterOptions {
// (undocumented)
database: PluginDatabaseManager;
// (undocumented)
identity: IdentityClient;
// (undocumented)
userSettingsStore: UserSettingsStore<T>;
}
// @public (undocumented)
export type UserSetting = {
bucket: string;
key: string;
value: string;
};
// @public
export interface UserSettingsStore<Transaction> {
// (undocumented)
delete(
tx: Transaction,
opts: {
userEntityRef: string;
bucket: string;
key: string;
},
): Promise<void>;
// (undocumented)
deleteAll(
tx: Transaction,
opts: {
userEntityRef: string;
},
): Promise<void>;
// (undocumented)
deleteBucket(
tx: Transaction,
opts: {
userEntityRef: string;
bucket: string;
},
): Promise<void>;
// (undocumented)
get(
tx: Transaction,
opts: {
userEntityRef: string;
bucket: string;
key: string;
},
): Promise<UserSetting>;
// (undocumented)
getAll(
tx: Transaction,
opts: {
userEntityRef: string;
},
): Promise<UserSetting[]>;
// (undocumented)
getBucket(
tx: Transaction,
opts: {
userEntityRef: string;
bucket: string;
},
): Promise<UserSetting[]>;
// (undocumented)
set(
tx: Transaction,
opts: {
userEntityRef: string;
bucket: string;
key: string;
value: string;
},
): Promise<void>;
// (undocumented)
transaction<T>(fn: (tx: Transaction) => Promise<T>): Promise<T>;
}
// (No @packageDocumentation comment for this package)
@@ -67,189 +67,14 @@ describe.each(databases.eachSupportedId())(
.orderBy('user_entity_ref')
.select();
describe('getAll', () => {
it('should return empty user settings', async () => {
expect(
await storage.transaction(tx =>
storage.getAll(tx, { userEntityRef: 'user-1' }),
),
).toEqual([]);
});
it('should return all user settings', async () => {
await insert([
{
user_entity_ref: 'user-1',
bucket: 'bucket-a',
key: 'key-a',
value: 'value-a',
},
{
user_entity_ref: 'user-1',
bucket: 'bucket-a',
key: 'key-b',
value: 'value-b',
},
{
user_entity_ref: 'user-1',
bucket: 'bucket-c',
key: 'key-c',
value: 'value-c',
},
]);
expect(
await storage.transaction(tx =>
storage.getAll(tx, { userEntityRef: 'user-1' }),
),
).toEqual([
{ bucket: 'bucket-a', key: 'key-a', value: 'value-a' },
{ bucket: 'bucket-a', key: 'key-b', value: 'value-b' },
{ bucket: 'bucket-c', key: 'key-c', value: 'value-c' },
]);
});
});
describe('deleteAll', () => {
it('should delete all user settings', async () => {
await insert([
{
user_entity_ref: 'user-1',
bucket: 'bucket-a',
key: 'key-a',
value: 'value-a',
},
{
user_entity_ref: 'user-1',
bucket: 'bucket-c',
key: 'key-c',
value: 'value-c',
},
{
user_entity_ref: 'user-2',
bucket: 'bucket-c',
key: 'key-c',
value: 'value-c',
},
]);
await storage.transaction(tx =>
storage.deleteAll(tx, { userEntityRef: 'user-1' }),
);
expect(await query()).toEqual([
{
user_entity_ref: 'user-2',
bucket: 'bucket-c',
key: 'key-c',
value: 'value-c',
},
]);
});
});
describe('getBucket', () => {
it('should return an empty bucket', async () => {
expect(
await storage.transaction(tx =>
storage.getBucket(tx, {
userEntityRef: 'user-1',
bucket: 'bucket-c',
}),
),
).toEqual([]);
});
it('should return the settings of the bucket', async () => {
await insert([
{
user_entity_ref: 'user-1',
bucket: 'bucket-a',
key: 'key-a',
value: 'value-a',
},
{
user_entity_ref: 'user-1',
bucket: 'bucket-c',
key: 'key-c',
value: 'value-c',
},
{
user_entity_ref: 'user-2',
bucket: 'bucket-c',
key: 'key-c',
value: 'value-c',
},
]);
expect(
await storage.transaction(tx =>
storage.getBucket(tx, {
userEntityRef: 'user-1',
bucket: 'bucket-a',
}),
),
).toEqual([{ bucket: 'bucket-a', key: 'key-a', value: 'value-a' }]);
});
});
describe('deleteBucket', () => {
it('should delete a bucket', async () => {
await insert([
{
user_entity_ref: 'user-1',
bucket: 'bucket-a',
key: 'key-a',
value: 'value-a',
},
{
user_entity_ref: 'user-1',
bucket: 'bucket-c',
key: 'key-c',
value: 'value-c',
},
{
user_entity_ref: 'user-2',
bucket: 'bucket-c',
key: 'key-c',
value: 'value-c',
},
]);
await storage.transaction(tx =>
storage.deleteBucket(tx, {
userEntityRef: 'user-1',
bucket: 'bucket-a',
}),
);
expect(await query()).toEqual([
{
user_entity_ref: 'user-1',
bucket: 'bucket-c',
key: 'key-c',
value: 'value-c',
},
{
user_entity_ref: 'user-2',
bucket: 'bucket-c',
key: 'key-c',
value: 'value-c',
},
]);
});
});
describe('get', () => {
it('should throw an error', async () => {
await expect(() =>
storage.transaction(tx =>
storage.get(tx, {
userEntityRef: 'user-1',
bucket: 'bucket-c',
key: 'key-c',
}),
),
storage.get({
userEntityRef: 'user-1',
bucket: 'bucket-c',
key: 'key-c',
}),
).rejects.toThrow(`Unable to find 'key-c' in bucket 'bucket-c'`);
});
@@ -269,30 +94,30 @@ describe.each(databases.eachSupportedId())(
},
]);
expect(
await storage.transaction(tx =>
storage.get(tx, {
userEntityRef: 'user-1',
bucket: 'bucket-a',
key: 'key-a',
}),
),
).toEqual({ bucket: 'bucket-a', key: 'key-a', value: 'value-a' });
await expect(
storage.get({
userEntityRef: 'user-1',
bucket: 'bucket-a',
key: 'key-a',
}),
).resolves.toEqual({
bucket: 'bucket-a',
key: 'key-a',
value: 'value-a',
});
});
});
describe('set', () => {
it('should insert a new setting', async () => {
await storage.transaction(tx =>
storage.set(tx, {
userEntityRef: 'user-1',
bucket: 'bucket-a',
key: 'key-a',
value: 'value-a',
}),
);
await storage.set({
userEntityRef: 'user-1',
bucket: 'bucket-a',
key: 'key-a',
value: 'value-a',
});
expect(await query()).toEqual([
await expect(query()).resolves.toEqual([
{
user_entity_ref: 'user-1',
bucket: 'bucket-a',
@@ -303,25 +128,21 @@ describe.each(databases.eachSupportedId())(
});
it('should overwrite an existing setting', async () => {
await storage.transaction(tx =>
storage.set(tx, {
userEntityRef: 'user-1',
bucket: 'bucket-a',
key: 'key-a',
value: 'value-a',
}),
);
await storage.set({
userEntityRef: 'user-1',
bucket: 'bucket-a',
key: 'key-a',
value: 'value-a',
});
await storage.transaction(tx =>
storage.set(tx, {
userEntityRef: 'user-1',
bucket: 'bucket-a',
key: 'key-a',
value: 'value-b',
}),
);
await storage.set({
userEntityRef: 'user-1',
bucket: 'bucket-a',
key: 'key-a',
value: 'value-b',
});
expect(await query()).toEqual([
await expect(query()).resolves.toEqual([
{
user_entity_ref: 'user-1',
bucket: 'bucket-a',
@@ -334,15 +155,13 @@ describe.each(databases.eachSupportedId())(
describe('delete', () => {
it('should not throw an error if the entry does not exist', async () => {
await expect(() =>
storage.transaction(tx =>
storage.delete(tx, {
userEntityRef: 'user-1',
bucket: 'bucket-c',
key: 'key-c',
}),
),
).not.toThrow();
await expect(
storage.delete({
userEntityRef: 'user-1',
bucket: 'bucket-c',
key: 'key-c',
}),
).resolves.toBeUndefined();
});
it('should return the setting', async () => {
@@ -361,14 +180,12 @@ describe.each(databases.eachSupportedId())(
},
]);
await storage.transaction(tx =>
storage.delete(tx, {
userEntityRef: 'user-1',
bucket: 'bucket-a',
key: 'key-a',
}),
);
expect(await query()).toEqual([
await storage.delete({
userEntityRef: 'user-1',
bucket: 'bucket-a',
key: 'key-a',
});
await expect(query()).resolves.toEqual([
{
user_entity_ref: 'user-2',
bucket: 'bucket-c',
@@ -42,9 +42,7 @@ export type RawDbUserSettingsRow = {
*
* @public
*/
export class DatabaseUserSettingsStore
implements UserSettingsStore<Knex.Transaction>
{
export class DatabaseUserSettingsStore implements UserSettingsStore {
static async create(options: {
database: PluginDatabaseManager;
}): Promise<DatabaseUserSettingsStore> {
@@ -62,93 +60,56 @@ export class DatabaseUserSettingsStore
private constructor(private readonly db: Knex) {}
async getAll(tx: Knex.Transaction, opts: { userEntityRef: string }) {
const settings = await tx<RawDbUserSettingsRow>('user_settings')
.where({ user_entity_ref: opts.userEntityRef })
.select('bucket', 'key', 'value');
return settings;
}
async deleteAll(
tx: Knex.Transaction<any, any[]>,
opts: { userEntityRef: string },
): Promise<void> {
await tx('user_settings')
.where({ user_entity_ref: opts.userEntityRef })
.delete();
}
async getBucket(
tx: Knex.Transaction<any, any[]>,
opts: { userEntityRef: string; bucket: string },
): Promise<UserSetting[]> {
const settings = await tx<RawDbUserSettingsRow>('user_settings')
.where({ user_entity_ref: opts.userEntityRef, bucket: opts.bucket })
.select(['bucket', 'key', 'value']);
return settings;
}
async deleteBucket(
tx: Knex.Transaction<any, any[]>,
opts: { userEntityRef: string; bucket: string },
): Promise<void> {
await tx('user_settings')
.where({ user_entity_ref: opts.userEntityRef, bucket: opts.bucket })
.delete();
}
async get(
tx: Knex.Transaction<any, any[]>,
opts: { userEntityRef: string; bucket: string; key: string },
): Promise<UserSetting> {
const setting = await tx<RawDbUserSettingsRow>('user_settings')
async get(options: {
userEntityRef: string;
bucket: string;
key: string;
}): Promise<UserSetting> {
const rows = await this.db<RawDbUserSettingsRow>('user_settings')
.where({
user_entity_ref: opts.userEntityRef,
bucket: opts.bucket,
key: opts.key,
user_entity_ref: options.userEntityRef,
bucket: options.bucket,
key: options.key,
})
.select(['bucket', 'key', 'value']);
if (!setting.length) {
if (!rows.length) {
throw new NotFoundError(
`Unable to find '${opts.key}' in bucket '${opts.bucket}'`,
`Unable to find '${options.key}' in bucket '${options.bucket}'`,
);
}
return setting[0];
return rows[0];
}
async set(
tx: Knex.Transaction<any, any[]>,
opts: { userEntityRef: string; bucket: string; key: string; value: string },
): Promise<void> {
await tx<RawDbUserSettingsRow>('user_settings')
async set(options: {
userEntityRef: string;
bucket: string;
key: string;
value: string;
}): Promise<void> {
await this.db<RawDbUserSettingsRow>('user_settings')
.insert({
user_entity_ref: opts.userEntityRef,
bucket: opts.bucket,
key: opts.key,
value: opts.value,
user_entity_ref: options.userEntityRef,
bucket: options.bucket,
key: options.key,
value: options.value,
})
.onConflict(['user_entity_ref', 'bucket', 'key'])
.merge({ value: opts.value });
.merge(['value']);
}
async delete(
tx: Knex.Transaction<any, any[]>,
opts: { userEntityRef: string; bucket: string; key: string },
): Promise<void> {
await tx<RawDbUserSettingsRow>('user_settings')
async delete(options: {
userEntityRef: string;
bucket: string;
key: string;
}): Promise<void> {
await this.db<RawDbUserSettingsRow>('user_settings')
.where({
user_entity_ref: opts.userEntityRef,
bucket: opts.bucket,
key: opts.key,
user_entity_ref: options.userEntityRef,
bucket: options.bucket,
key: options.key,
})
.delete();
}
async transaction<T>(fn: (tx: Knex.Transaction) => Promise<T>) {
return await this.db.transaction(fn);
}
}
@@ -15,7 +15,7 @@
*/
/**
* @public
* A single setting in a bucket
*/
export type UserSetting = {
bucket: string;
@@ -25,41 +25,24 @@ export type UserSetting = {
/**
* Store definition for the user settings.
*
* @public
*/
export interface UserSettingsStore<Transaction> {
transaction<T>(fn: (tx: Transaction) => Promise<T>): Promise<T>;
export interface UserSettingsStore {
get(options: {
userEntityRef: string;
bucket: string;
key: string;
}): Promise<UserSetting>;
get(
tx: Transaction,
opts: { userEntityRef: string; bucket: string; key: string },
): Promise<UserSetting>;
set(options: {
userEntityRef: string;
bucket: string;
key: string;
value: string;
}): Promise<void>;
set(
tx: Transaction,
opts: { userEntityRef: string; bucket: string; key: string; value: string },
): Promise<void>;
delete(
tx: Transaction,
opts: { userEntityRef: string; bucket: string; key: string },
): Promise<void>;
getBucket(
tx: Transaction,
opts: { userEntityRef: string; bucket: string },
): Promise<UserSetting[]>;
deleteBucket(
tx: Transaction,
opts: { userEntityRef: string; bucket: string },
): Promise<void>;
getAll(
tx: Transaction,
opts: { userEntityRef: string },
): Promise<UserSetting[]>;
deleteAll(tx: Transaction, opts: { userEntityRef: string }): Promise<void>;
delete(options: {
userEntityRef: string;
bucket: string;
key: string;
}): Promise<void>;
}
@@ -14,8 +14,4 @@
* limitations under the License.
*/
export {
DatabaseUserSettingsStore,
type RawDbUserSettingsRow,
} from './DatabaseUserSettingsStore';
export type { UserSettingsStore, UserSetting } from './UserSettingsStore';
export {};
@@ -14,20 +14,14 @@
* limitations under the License.
*/
import { AuthenticationError } from '@backstage/errors';
import { IdentityClient } from '@backstage/plugin-auth-node';
import express from 'express';
import request from 'supertest';
import { UserSettingsStore } from '../database';
import { createRouter } from './router';
import { UserSettingsStore } from '../database/UserSettingsStore';
import { createRouterInternal } from './router';
describe('createRouter', () => {
const userSettingsStore: jest.Mocked<UserSettingsStore<'tx'>> = {
transaction: jest.fn(),
deleteAll: jest.fn(),
getAll: jest.fn(),
getBucket: jest.fn(),
deleteBucket: jest.fn(),
const userSettingsStore: jest.Mocked<UserSettingsStore> = {
get: jest.fn(),
set: jest.fn(),
delete: jest.fn(),
@@ -40,9 +34,7 @@ describe('createRouter', () => {
let app: express.Express;
beforeEach(async () => {
userSettingsStore.transaction.mockImplementation(fn => fn('tx'));
const router = await createRouter({
const router = await createRouterInternal({
userSettingsStore,
identity: identityClient as IdentityClient,
});
@@ -54,147 +46,6 @@ describe('createRouter', () => {
jest.resetAllMocks();
});
describe('GET /buckets/', () => {
it('returns ok', async () => {
const settings = [
{ bucket: 'a', key: 'a', value: 'a' },
{ bucket: 'b', key: 'b', value: 'b' },
];
authenticateMock.mockResolvedValue({
identity: { userEntityRef: 'user-1' },
});
userSettingsStore.getAll.mockResolvedValue(settings);
const responses = await request(app)
.get('/buckets/')
.set('Authorization', 'Bearer foo');
expect(responses.status).toEqual(200);
expect(responses.body).toEqual(settings);
expect(authenticateMock).toHaveBeenCalledWith('foo');
expect(userSettingsStore.getAll).toHaveBeenCalledTimes(1);
expect(userSettingsStore.getAll).toHaveBeenCalledWith('tx', {
userEntityRef: 'user-1',
});
});
it('returns an error if the Authorization header is missing', async () => {
const responses = await request(app).get('/buckets/');
expect(responses.status).toEqual(401);
expect(userSettingsStore.getAll).not.toHaveBeenCalled();
});
it('returns an error if the token is not valid', async () => {
authenticateMock.mockRejectedValue(
new AuthenticationError('Invalid token'),
);
const responses = await request(app)
.get('/buckets/')
.set('Authorization', 'Bearer foo');
expect(responses.status).toEqual(401);
expect(userSettingsStore.getAll).not.toHaveBeenCalled();
});
});
describe('DELETE /buckets/', () => {
it('returns ok', async () => {
authenticateMock.mockResolvedValue({
identity: { userEntityRef: 'user-1' },
});
userSettingsStore.deleteAll.mockResolvedValue();
const responses = await request(app)
.delete('/buckets/')
.set('Authorization', 'Bearer foo');
expect(responses.status).toEqual(204);
expect(authenticateMock).toHaveBeenCalledWith('foo');
expect(userSettingsStore.deleteAll).toHaveBeenCalledTimes(1);
expect(userSettingsStore.deleteAll).toHaveBeenCalledWith('tx', {
userEntityRef: 'user-1',
});
});
it('returns an error if the Authorization header is missing', async () => {
const responses = await request(app).delete('/buckets/');
expect(responses.status).toEqual(401);
expect(userSettingsStore.getAll).not.toHaveBeenCalled();
});
});
describe('GET /buckets/:bucket', () => {
it('returns ok', async () => {
const settings = [
{ bucket: 'my-bucket', key: 'a', value: 'a' },
{ bucket: 'my-bucket', key: 'b', value: 'b' },
];
authenticateMock.mockResolvedValue({
identity: { userEntityRef: 'user-1' },
});
userSettingsStore.getBucket.mockResolvedValue(settings);
const responses = await request(app)
.get('/buckets/my-bucket')
.set('Authorization', 'Bearer foo');
expect(responses.status).toEqual(200);
expect(responses.body).toEqual(settings);
expect(authenticateMock).toHaveBeenCalledWith('foo');
expect(userSettingsStore.getBucket).toHaveBeenCalledTimes(1);
expect(userSettingsStore.getBucket).toHaveBeenCalledWith('tx', {
userEntityRef: 'user-1',
bucket: 'my-bucket',
});
});
it('returns an error if the Authorization header is missing', async () => {
const responses = await request(app).get('/buckets/my-bucket');
expect(responses.status).toEqual(401);
expect(userSettingsStore.getBucket).not.toHaveBeenCalled();
});
});
describe('DELETE /buckets/:bucket', () => {
it('returns ok', async () => {
authenticateMock.mockResolvedValue({
identity: { userEntityRef: 'user-1' },
});
userSettingsStore.deleteBucket.mockResolvedValue();
const responses = await request(app)
.delete('/buckets/my-bucket')
.set('Authorization', 'Bearer foo');
expect(responses.status).toEqual(204);
expect(authenticateMock).toHaveBeenCalledWith('foo');
expect(userSettingsStore.deleteBucket).toHaveBeenCalledTimes(1);
expect(userSettingsStore.deleteBucket).toHaveBeenCalledWith('tx', {
userEntityRef: 'user-1',
bucket: 'my-bucket',
});
});
it('returns an error if the Authorization header is missing', async () => {
const responses = await request(app).delete('/buckets/my-bucket');
expect(responses.status).toEqual(401);
expect(userSettingsStore.deleteBucket).not.toHaveBeenCalled();
});
});
describe('GET /buckets/:bucket/keys/:key', () => {
it('returns ok', async () => {
const setting = { bucket: 'my-bucket', key: 'my-key', value: 'a' };
@@ -213,7 +64,7 @@ describe('createRouter', () => {
expect(authenticateMock).toHaveBeenCalledWith('foo');
expect(userSettingsStore.get).toHaveBeenCalledTimes(1);
expect(userSettingsStore.get).toHaveBeenCalledWith('tx', {
expect(userSettingsStore.get).toHaveBeenCalledWith({
userEntityRef: 'user-1',
bucket: 'my-bucket',
key: 'my-key',
@@ -246,7 +97,7 @@ describe('createRouter', () => {
expect(authenticateMock).toHaveBeenCalledWith('foo');
expect(userSettingsStore.delete).toHaveBeenCalledTimes(1);
expect(userSettingsStore.delete).toHaveBeenCalledWith('tx', {
expect(userSettingsStore.delete).toHaveBeenCalledWith({
userEntityRef: 'user-1',
bucket: 'my-bucket',
key: 'my-key',
@@ -283,14 +134,14 @@ describe('createRouter', () => {
expect(authenticateMock).toHaveBeenCalledWith('foo');
expect(userSettingsStore.set).toHaveBeenCalledTimes(1);
expect(userSettingsStore.set).toHaveBeenCalledWith('tx', {
expect(userSettingsStore.set).toHaveBeenCalledWith({
userEntityRef: 'user-1',
bucket: 'my-bucket',
key: 'my-key',
value: 'a',
});
expect(userSettingsStore.get).toHaveBeenCalledTimes(1);
expect(userSettingsStore.get).toHaveBeenCalledWith('tx', {
expect(userSettingsStore.get).toHaveBeenCalledWith({
userEntityRef: 'user-1',
bucket: 'my-bucket',
key: 'my-key',
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { errorHandler } from '@backstage/backend-common';
import { errorHandler, PluginDatabaseManager } from '@backstage/backend-common';
import { AuthenticationError, InputError } from '@backstage/errors';
import {
getBearerTokenFromAuthorizationHeader,
@@ -22,13 +22,14 @@ import {
} from '@backstage/plugin-auth-node';
import express, { Request } from 'express';
import Router from 'express-promise-router';
import { UserSettingsStore } from '../database';
import { DatabaseUserSettingsStore } from '../database/DatabaseUserSettingsStore';
import { UserSettingsStore } from '../database/UserSettingsStore';
/**
* @public
*/
export interface RouterOptions<T> {
userSettingsStore: UserSettingsStore<T>;
export interface RouterOptions {
database: PluginDatabaseManager;
identity: IdentityClient;
}
@@ -37,10 +38,23 @@ export interface RouterOptions<T> {
*
* @public
*/
export async function createRouter<T>(
options: RouterOptions<T>,
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const { userSettingsStore, identity } = options;
const userSettingsStore = await DatabaseUserSettingsStore.create({
database: options.database,
});
return await createRouterInternal({
userSettingsStore,
identity: options.identity,
});
}
export async function createRouterInternal(options: {
identity: IdentityClient;
userSettingsStore: UserSettingsStore;
}): Promise<express.Router> {
const router = Router();
router.use(express.json());
@@ -57,65 +71,21 @@ export async function createRouter<T>(
}
// throws an AuthenticationError in case the token is invalid
const user = await identity.authenticate(token);
const user = await options.identity.authenticate(token);
return user.identity.userEntityRef;
};
// get all user related settings
router.get('/buckets', async (req, res) => {
const userEntityRef = await getUserEntityRef(req);
const settings = await userSettingsStore.transaction(tx =>
userSettingsStore.getAll(tx, { userEntityRef }),
);
res.json(settings);
});
// remove all user related settings
router.delete('/buckets', async (req, res) => {
const userEntityRef = await getUserEntityRef(req);
await userSettingsStore.transaction(tx =>
userSettingsStore.deleteAll(tx, { userEntityRef }),
);
res.send(204).end();
});
// get a single bucket
router.get('/buckets/:bucket', async (req, res) => {
const userEntityRef = await getUserEntityRef(req);
const { bucket } = req.params;
const settings = await userSettingsStore.transaction(tx =>
userSettingsStore.getBucket(tx, { userEntityRef, bucket }),
);
res.json(settings);
});
// delete a whole bucket
router.delete('/buckets/:bucket', async (req, res) => {
const userEntityRef = await getUserEntityRef(req);
const { bucket } = req.params;
await userSettingsStore.transaction(tx =>
userSettingsStore.deleteBucket(tx, { userEntityRef, bucket }),
);
res.status(204).end();
});
// get a single value
router.get('/buckets/:bucket/keys/:key', async (req, res) => {
const userEntityRef = await getUserEntityRef(req);
const { bucket, key } = req.params;
const setting = await userSettingsStore.transaction(tx =>
userSettingsStore.get(tx, { userEntityRef, bucket, key }),
);
const setting = await options.userSettingsStore.get({
userEntityRef,
bucket,
key,
});
res.json(setting);
});
@@ -130,14 +100,16 @@ export async function createRouter<T>(
throw new InputError('Value must be a string');
}
const setting = await userSettingsStore.transaction(async tx => {
await userSettingsStore.set(tx, {
userEntityRef,
bucket,
key,
value,
});
return userSettingsStore.get(tx, { userEntityRef, bucket, key });
await options.userSettingsStore.set({
userEntityRef,
bucket,
key,
value,
});
const setting = await options.userSettingsStore.get({
userEntityRef,
bucket,
key,
});
res.json(setting);
@@ -148,9 +120,7 @@ export async function createRouter<T>(
const userEntityRef = await getUserEntityRef(req);
const { bucket, key } = req.params;
await userSettingsStore.transaction(tx =>
userSettingsStore.delete(tx, { userEntityRef, bucket, key }),
);
await options.userSettingsStore.delete({ userEntityRef, bucket, key });
res.send(204).end();
});
@@ -20,8 +20,8 @@ import { Router } from 'express';
import { Server } from 'http';
import Knex from 'knex';
import { Logger } from 'winston';
import { DatabaseUserSettingsStore } from '../database';
import { createRouter } from './router';
import { DatabaseUserSettingsStore } from '../database/DatabaseUserSettingsStore';
import { createRouterInternal } from './router';
export interface ServerOptions {
port: number;
@@ -50,7 +50,7 @@ export async function startStandaloneServer(
}),
} as IdentityClient;
const router = await createRouter({
const router = await createRouterInternal({
userSettingsStore: await DatabaseUserSettingsStore.create({
database: { getClient: async () => database },
}),