simplify router and db interface
Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
@@ -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 },
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user