Merge pull request #15755 from Budibase/BUDI-9127/edit-delete-configs
Implement edit and delete config endpoints
This commit is contained in:
commit
e4eb09b203
|
@ -1,3 +1,3 @@
|
||||||
import { CreateOAuth2ConfigRequest } from "@budibase/types"
|
import { UpsertOAuth2ConfigRequest } from "@budibase/types"
|
||||||
|
|
||||||
export interface CreateOAuth2Config extends CreateOAuth2ConfigRequest {}
|
export interface CreateOAuth2Config extends UpsertOAuth2ConfigRequest {}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import {
|
import {
|
||||||
FetchOAuth2ConfigsResponse,
|
FetchOAuth2ConfigsResponse,
|
||||||
CreateOAuth2ConfigResponse,
|
|
||||||
OAuth2ConfigResponse,
|
OAuth2ConfigResponse,
|
||||||
CreateOAuth2ConfigRequest,
|
UpsertOAuth2ConfigRequest,
|
||||||
|
UpsertOAuth2ConfigResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { BaseAPIClient } from "./types"
|
import { BaseAPIClient } from "./types"
|
||||||
|
|
||||||
export interface OAuth2Endpoints {
|
export interface OAuth2Endpoints {
|
||||||
fetch: () => Promise<OAuth2ConfigResponse[]>
|
fetch: () => Promise<OAuth2ConfigResponse[]>
|
||||||
create: (
|
create: (
|
||||||
config: CreateOAuth2ConfigRequest
|
config: UpsertOAuth2ConfigRequest
|
||||||
) => Promise<CreateOAuth2ConfigResponse>
|
) => Promise<UpsertOAuth2ConfigResponse>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const buildOAuth2Endpoints = (API: BaseAPIClient): OAuth2Endpoints => ({
|
export const buildOAuth2Endpoints = (API: BaseAPIClient): OAuth2Endpoints => ({
|
||||||
|
@ -33,8 +33,8 @@ export const buildOAuth2Endpoints = (API: BaseAPIClient): OAuth2Endpoints => ({
|
||||||
*/
|
*/
|
||||||
create: async config => {
|
create: async config => {
|
||||||
return await API.post<
|
return await API.post<
|
||||||
CreateOAuth2ConfigRequest,
|
UpsertOAuth2ConfigRequest,
|
||||||
CreateOAuth2ConfigResponse
|
UpsertOAuth2ConfigResponse
|
||||||
>({
|
>({
|
||||||
url: `/api/oauth2`,
|
url: `/api/oauth2`,
|
||||||
body: {
|
body: {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
CreateOAuth2ConfigRequest,
|
UpsertOAuth2ConfigRequest,
|
||||||
CreateOAuth2ConfigResponse,
|
UpsertOAuth2ConfigResponse,
|
||||||
Ctx,
|
Ctx,
|
||||||
FetchOAuth2ConfigsResponse,
|
FetchOAuth2ConfigsResponse,
|
||||||
OAuth2Config,
|
OAuth2Config,
|
||||||
|
@ -22,7 +22,7 @@ export async function fetch(ctx: Ctx<void, FetchOAuth2ConfigsResponse>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function create(
|
export async function create(
|
||||||
ctx: Ctx<CreateOAuth2ConfigRequest, CreateOAuth2ConfigResponse>
|
ctx: Ctx<UpsertOAuth2ConfigRequest, UpsertOAuth2ConfigResponse>
|
||||||
) {
|
) {
|
||||||
const { body } = ctx.request
|
const { body } = ctx.request
|
||||||
const newConfig: RequiredKeys<Omit<OAuth2Config, "id">> = {
|
const newConfig: RequiredKeys<Omit<OAuth2Config, "id">> = {
|
||||||
|
@ -36,3 +36,28 @@ export async function create(
|
||||||
ctx.status = 201
|
ctx.status = 201
|
||||||
ctx.body = { config }
|
ctx.body = { config }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function edit(
|
||||||
|
ctx: Ctx<UpsertOAuth2ConfigRequest, UpsertOAuth2ConfigResponse>
|
||||||
|
) {
|
||||||
|
const { body } = ctx.request
|
||||||
|
const toUpdate: RequiredKeys<OAuth2Config> = {
|
||||||
|
id: ctx.params.id,
|
||||||
|
name: body.name,
|
||||||
|
url: body.url,
|
||||||
|
clientId: ctx.clientId,
|
||||||
|
clientSecret: ctx.clientSecret,
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = await sdk.oauth2.update(toUpdate)
|
||||||
|
ctx.body = { config }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function remove(
|
||||||
|
ctx: Ctx<UpsertOAuth2ConfigRequest, UpsertOAuth2ConfigResponse>
|
||||||
|
) {
|
||||||
|
const configToRemove = ctx.params.id
|
||||||
|
|
||||||
|
await sdk.oauth2.remove(configToRemove)
|
||||||
|
ctx.status = 204
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,22 @@
|
||||||
import Router from "@koa/router"
|
import Router from "@koa/router"
|
||||||
import { PermissionType } from "@budibase/types"
|
import { PermissionType } from "@budibase/types"
|
||||||
|
import { middleware } from "@budibase/backend-core"
|
||||||
import authorized from "../../middleware/authorized"
|
import authorized from "../../middleware/authorized"
|
||||||
|
|
||||||
import * as controller from "../controllers/oauth2"
|
import * as controller from "../controllers/oauth2"
|
||||||
|
import Joi from "joi"
|
||||||
|
|
||||||
|
function oAuth2ConfigValidator() {
|
||||||
|
return middleware.joiValidator.body(
|
||||||
|
Joi.object({
|
||||||
|
name: Joi.string().required(),
|
||||||
|
url: Joi.string().required(),
|
||||||
|
clientId: Joi.string().required(),
|
||||||
|
clientSecret: Joi.string().required(),
|
||||||
|
}),
|
||||||
|
{ allowUnknown: false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const router: Router = new Router()
|
const router: Router = new Router()
|
||||||
|
|
||||||
|
@ -10,7 +24,19 @@ router.get("/api/oauth2", authorized(PermissionType.BUILDER), controller.fetch)
|
||||||
router.post(
|
router.post(
|
||||||
"/api/oauth2",
|
"/api/oauth2",
|
||||||
authorized(PermissionType.BUILDER),
|
authorized(PermissionType.BUILDER),
|
||||||
|
oAuth2ConfigValidator(),
|
||||||
controller.create
|
controller.create
|
||||||
)
|
)
|
||||||
|
router.put(
|
||||||
|
"/api/oauth2/:id",
|
||||||
|
authorized(PermissionType.BUILDER),
|
||||||
|
oAuth2ConfigValidator(),
|
||||||
|
controller.edit
|
||||||
|
)
|
||||||
|
router.delete(
|
||||||
|
"/api/oauth2/:id",
|
||||||
|
authorized(PermissionType.BUILDER),
|
||||||
|
controller.remove
|
||||||
|
)
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
import { CreateOAuth2ConfigRequest, VirtualDocumentType } from "@budibase/types"
|
import {
|
||||||
|
OAuth2Config,
|
||||||
|
UpsertOAuth2ConfigRequest,
|
||||||
|
VirtualDocumentType,
|
||||||
|
} from "@budibase/types"
|
||||||
import * as setup from "./utilities"
|
import * as setup from "./utilities"
|
||||||
import { generator } from "@budibase/backend-core/tests"
|
import { generator } from "@budibase/backend-core/tests"
|
||||||
|
import _ from "lodash/fp"
|
||||||
|
|
||||||
describe("/oauth2", () => {
|
describe("/oauth2", () => {
|
||||||
let config = setup.getConfig()
|
let config = setup.getConfig()
|
||||||
|
|
||||||
function makeOAuth2Config(): CreateOAuth2ConfigRequest {
|
function makeOAuth2Config(): UpsertOAuth2ConfigRequest {
|
||||||
return {
|
return {
|
||||||
name: generator.guid(),
|
name: generator.guid(),
|
||||||
url: generator.url(),
|
url: generator.url(),
|
||||||
|
@ -92,4 +97,96 @@ describe("/oauth2", () => {
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("update", () => {
|
||||||
|
let existingConfigs: OAuth2Config[] = []
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
existingConfigs = []
|
||||||
|
for (let i = 0; i < 10; i++) {
|
||||||
|
const oauth2Config = makeOAuth2Config()
|
||||||
|
const result = await config.api.oauth2.create(oauth2Config)
|
||||||
|
|
||||||
|
existingConfigs.push({ ...oauth2Config, id: result.config.id })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can update an existing configuration", async () => {
|
||||||
|
const { id: configId, ...configData } = _.sample(existingConfigs)!
|
||||||
|
|
||||||
|
await config.api.oauth2.update(configId, {
|
||||||
|
...configData,
|
||||||
|
name: "updated name",
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await config.api.oauth2.fetch()
|
||||||
|
expect(response.configs).toHaveLength(existingConfigs.length)
|
||||||
|
expect(response.configs).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
|
{
|
||||||
|
id: configId,
|
||||||
|
name: "updated name",
|
||||||
|
url: configData.url,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throw if config not found", async () => {
|
||||||
|
await config.api.oauth2.update("unexisting", makeOAuth2Config(), {
|
||||||
|
status: 404,
|
||||||
|
body: { message: "OAuth2 config with id 'unexisting' not found." },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throws if trying to use an existing name", async () => {
|
||||||
|
const [config1, config2] = _.sampleSize(2, existingConfigs)
|
||||||
|
const { id: configId, ...configData } = config1
|
||||||
|
|
||||||
|
await config.api.oauth2.update(
|
||||||
|
configId,
|
||||||
|
{
|
||||||
|
...configData,
|
||||||
|
name: config2.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: 400,
|
||||||
|
body: {
|
||||||
|
message: `OAuth2 config with name '${config2.name}' is already taken.`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("delete", () => {
|
||||||
|
let existingConfigs: OAuth2Config[] = []
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
existingConfigs = []
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
const oauth2Config = makeOAuth2Config()
|
||||||
|
const result = await config.api.oauth2.create(oauth2Config)
|
||||||
|
|
||||||
|
existingConfigs.push({ ...oauth2Config, id: result.config.id })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("can delete an existing configuration", async () => {
|
||||||
|
const { id: configId } = _.sample(existingConfigs)!
|
||||||
|
|
||||||
|
await config.api.oauth2.delete(configId, { status: 204 })
|
||||||
|
|
||||||
|
const response = await config.api.oauth2.fetch()
|
||||||
|
expect(response.configs).toHaveLength(existingConfigs.length - 1)
|
||||||
|
expect(response.configs.find(c => c.id === configId)).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throw if config not found", async () => {
|
||||||
|
await config.api.oauth2.delete("unexisting", {
|
||||||
|
status: 404,
|
||||||
|
body: { message: "OAuth2 config with id 'unexisting' not found." },
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -48,3 +48,49 @@ export async function get(id: string): Promise<OAuth2Config | undefined> {
|
||||||
const doc = await getDocument()
|
const doc = await getDocument()
|
||||||
return doc?.configs?.[id]
|
return doc?.configs?.[id]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function update(config: OAuth2Config): Promise<OAuth2Config> {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
const doc: OAuth2Configs = (await getDocument(db)) ?? {
|
||||||
|
_id: DocumentType.OAUTH2_CONFIG,
|
||||||
|
configs: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doc.configs[config.id]) {
|
||||||
|
throw new HTTPError(`OAuth2 config with id '${config.id}' not found.`, 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
Object.values(doc.configs).find(
|
||||||
|
c => c.name === config.name && c.id !== config.id
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new HTTPError(
|
||||||
|
`OAuth2 config with name '${config.name}' is already taken.`,
|
||||||
|
400
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.configs[config.id] = {
|
||||||
|
...config,
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.put(doc)
|
||||||
|
return doc.configs[config.id]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function remove(configId: string): Promise<void> {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
const doc: OAuth2Configs = (await getDocument(db)) ?? {
|
||||||
|
_id: DocumentType.OAUTH2_CONFIG,
|
||||||
|
configs: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doc.configs[configId]) {
|
||||||
|
throw new HTTPError(`OAuth2 config with id '${configId}' not found.`, 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete doc.configs[configId]
|
||||||
|
|
||||||
|
await db.put(doc)
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {
|
import {
|
||||||
CreateOAuth2ConfigRequest,
|
UpsertOAuth2ConfigRequest,
|
||||||
CreateOAuth2ConfigResponse,
|
UpsertOAuth2ConfigResponse,
|
||||||
FetchOAuth2ConfigsResponse,
|
FetchOAuth2ConfigsResponse,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { Expectations, TestAPI } from "./base"
|
import { Expectations, TestAPI } from "./base"
|
||||||
|
@ -13,10 +13,10 @@ export class OAuth2API extends TestAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
create = async (
|
create = async (
|
||||||
body: CreateOAuth2ConfigRequest,
|
body: UpsertOAuth2ConfigRequest,
|
||||||
expectations?: Expectations
|
expectations?: Expectations
|
||||||
) => {
|
) => {
|
||||||
return await this._post<CreateOAuth2ConfigResponse>("/api/oauth2", {
|
return await this._post<UpsertOAuth2ConfigResponse>("/api/oauth2", {
|
||||||
body,
|
body,
|
||||||
expectations: {
|
expectations: {
|
||||||
status: expectations?.status ?? 201,
|
status: expectations?.status ?? 201,
|
||||||
|
@ -24,4 +24,21 @@ export class OAuth2API extends TestAPI {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update = async (
|
||||||
|
id: string,
|
||||||
|
body: UpsertOAuth2ConfigRequest,
|
||||||
|
expectations?: Expectations
|
||||||
|
) => {
|
||||||
|
return await this._put<UpsertOAuth2ConfigResponse>(`/api/oauth2/${id}`, {
|
||||||
|
body,
|
||||||
|
expectations,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
delete = async (id: string, expectations?: Expectations) => {
|
||||||
|
return await this._delete<void>(`/api/oauth2/${id}`, {
|
||||||
|
expectations,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,13 @@ export interface FetchOAuth2ConfigsResponse {
|
||||||
configs: OAuth2ConfigResponse[]
|
configs: OAuth2ConfigResponse[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateOAuth2ConfigRequest {
|
export interface UpsertOAuth2ConfigRequest {
|
||||||
name: string
|
name: string
|
||||||
url: string
|
url: string
|
||||||
clientId: string
|
clientId: string
|
||||||
clientSecret: string
|
clientSecret: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateOAuth2ConfigResponse {
|
export interface UpsertOAuth2ConfigResponse {
|
||||||
config: OAuth2ConfigResponse
|
config: OAuth2ConfigResponse
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue