diff --git a/fern/apis/fdr/definition/docs/v2/read/__package__.yml b/fern/apis/fdr/definition/docs/v2/read/__package__.yml index 2f2d33f370..4ac8a51bf8 100644 --- a/fern/apis/fdr/definition/docs/v2/read/__package__.yml +++ b/fern/apis/fdr/definition/docs/v2/read/__package__.yml @@ -91,7 +91,7 @@ service: indexSegmentId: string response: GetSearchApiKeyForIndexSegmentResponse errors: - - IndexSegmentNotFoundError + - IndexSegmentNotFoundError types: GetDocsConfigByIdResponse: diff --git a/fern/apis/fdr/definition/docs/v2/write/__package__.yml b/fern/apis/fdr/definition/docs/v2/write/__package__.yml index 3821f2eeda..9ce7d713b3 100644 --- a/fern/apis/fdr/definition/docs/v2/write/__package__.yml +++ b/fern/apis/fdr/definition/docs/v2/write/__package__.yml @@ -87,6 +87,21 @@ service: errors: - DocsNotFoundError - ReindexNotAllowedError + + transferOwnershipOfDomain: + auth: true + method: POST + path: /transfer-ownership + request: + name: TransferDomainOwnershipRequest + body: + properties: + domain: string + toOrgId: string + errors: + - DocsNotFoundError + - rootCommons.UnauthorizedError + types: ImageFilePath: properties: diff --git a/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/Client.ts b/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/Client.ts index cc955c9a0a..36508f000d 100644 --- a/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/Client.ts +++ b/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/Client.ts @@ -617,6 +617,63 @@ export class Write { }; } + /** + * @param {FernRegistry.docs.v2.write.TransferDomainOwnershipRequest} request + * @param {Write.RequestOptions} requestOptions - Request-specific configuration. + * + * @example + * await client.docs.v2.write.transferOwnershipOfDomain({ + * domain: "string", + * toOrgId: "string" + * }) + */ + public async transferOwnershipOfDomain( + request: FernRegistry.docs.v2.write.TransferDomainOwnershipRequest, + requestOptions?: Write.RequestOptions + ): Promise> { + const _response = await core.fetcher({ + url: urlJoin( + (await core.Supplier.get(this._options.environment)) ?? environments.FernRegistryEnvironment.Prod, + "/v2/registry/docs/transfer-ownership" + ), + method: "POST", + headers: { + Authorization: await this._getAuthorizationHeader(), + "X-Fern-Language": "JavaScript", + "X-Fern-Runtime": core.RUNTIME.type, + "X-Fern-Runtime-Version": core.RUNTIME.version, + }, + contentType: "application/json", + requestType: "json", + body: request, + timeoutMs: requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : undefined, + maxRetries: requestOptions?.maxRetries, + abortSignal: requestOptions?.abortSignal, + }); + if (_response.ok) { + return { + ok: true, + body: undefined, + }; + } + + if (_response.error.reason === "status-code") { + switch ((_response.error.body as FernRegistry.docs.v2.write.transferOwnershipOfDomain.Error)?.error) { + case "DocsNotFoundError": + case "UnauthorizedError": + return { + ok: false, + error: _response.error.body as FernRegistry.docs.v2.write.transferOwnershipOfDomain.Error, + }; + } + } + + return { + ok: false, + error: FernRegistry.docs.v2.write.transferOwnershipOfDomain.Error._unknown(_response.error), + }; + } + protected async _getAuthorizationHeader(): Promise { const bearer = await core.Supplier.get(this._options.token); if (bearer != null) { diff --git a/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/index.ts b/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/index.ts index a84db233d1..0555584067 100644 --- a/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/index.ts +++ b/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/index.ts @@ -3,3 +3,4 @@ export * as startDocsRegister from "./startDocsRegister"; export * as startDocsPreviewRegister from "./startDocsPreviewRegister"; export * as finishDocsRegister from "./finishDocsRegister"; export * as reindexAlgoliaSearchRecords from "./reindexAlgoliaSearchRecords"; +export * as transferOwnershipOfDomain from "./transferOwnershipOfDomain"; diff --git a/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/requests/TransferDomainOwnershipRequest.ts b/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/requests/TransferDomainOwnershipRequest.ts new file mode 100644 index 0000000000..0f8254e3e8 --- /dev/null +++ b/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/requests/TransferDomainOwnershipRequest.ts @@ -0,0 +1,15 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +/** + * @example + * { + * domain: "string", + * toOrgId: "string" + * } + */ +export interface TransferDomainOwnershipRequest { + domain: string; + toOrgId: string; +} diff --git a/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/requests/index.ts b/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/requests/index.ts index 662a85f7d9..d71b20f8ad 100644 --- a/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/requests/index.ts +++ b/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/requests/index.ts @@ -2,3 +2,4 @@ export { type StartDocsRegisterRequestV2 } from "./StartDocsRegisterRequestV2"; export { type StartDocsPreviewRegisterRequestV2 } from "./StartDocsPreviewRegisterRequestV2"; export { type RegisterDocsRequest } from "./RegisterDocsRequest"; export { type ReindexAlgoliaRecordsRequest } from "./ReindexAlgoliaRecordsRequest"; +export { type TransferDomainOwnershipRequest } from "./TransferDomainOwnershipRequest"; diff --git a/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/transferOwnershipOfDomain.ts b/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/transferOwnershipOfDomain.ts new file mode 100644 index 0000000000..0cc90b0374 --- /dev/null +++ b/packages/fdr-sdk/src/client/generated/api/resources/docs/resources/v2/resources/write/client/transferOwnershipOfDomain.ts @@ -0,0 +1,73 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ + +import * as FernRegistry from "../../../../../../../index"; +import * as core from "../../../../../../../../core"; + +export type Error = + | FernRegistry.docs.v2.write.transferOwnershipOfDomain.Error.DocsNotFoundError + | FernRegistry.docs.v2.write.transferOwnershipOfDomain.Error.UnauthorizedError + | FernRegistry.docs.v2.write.transferOwnershipOfDomain.Error._Unknown; + +export declare namespace Error { + interface DocsNotFoundError { + error: "DocsNotFoundError"; + } + + interface UnauthorizedError { + error: "UnauthorizedError"; + content: string; + } + + interface _Unknown { + error: void; + content: core.Fetcher.Error; + } + + interface _Visitor<_Result> { + docsNotFoundError: () => _Result; + unauthorizedError: (value: string) => _Result; + _other: (value: core.Fetcher.Error) => _Result; + } +} + +export const Error = { + docsNotFoundError: (): FernRegistry.docs.v2.write.transferOwnershipOfDomain.Error.DocsNotFoundError => { + return { + error: "DocsNotFoundError", + }; + }, + + unauthorizedError: ( + value: string + ): FernRegistry.docs.v2.write.transferOwnershipOfDomain.Error.UnauthorizedError => { + return { + content: value, + error: "UnauthorizedError", + }; + }, + + _unknown: ( + fetcherError: core.Fetcher.Error + ): FernRegistry.docs.v2.write.transferOwnershipOfDomain.Error._Unknown => { + return { + error: undefined, + content: fetcherError, + }; + }, + + _visit: <_Result>( + value: FernRegistry.docs.v2.write.transferOwnershipOfDomain.Error, + visitor: FernRegistry.docs.v2.write.transferOwnershipOfDomain.Error._Visitor<_Result> + ): _Result => { + switch (value.error) { + case "DocsNotFoundError": + return visitor.docsNotFoundError(); + case "UnauthorizedError": + return visitor.unauthorizedError(value.content); + default: + return visitor._other(value as any); + } + }, +} as const; diff --git a/servers/fdr/src/__test__/local/services/ownership.test.ts b/servers/fdr/src/__test__/local/services/ownership.test.ts new file mode 100644 index 0000000000..50e8f04217 --- /dev/null +++ b/servers/fdr/src/__test__/local/services/ownership.test.ts @@ -0,0 +1,36 @@ +import { DocsV1Write, FdrAPI } from "@fern-api/fdr-sdk"; +import { inject } from "vitest"; +import { getClient } from "../util"; + +it("change domain ownership", async () => { + const fdr = getClient({ authed: true, url: inject("url") }); + + const domain = `docs-${Math.random()}.fern.com`; + + // register docs + await fdr.docs.v1.write.startDocsRegister({ + orgId: FdrAPI.OrgId("fern"), + domain, + filepaths: [DocsV1Write.FilePath("logo.png"), DocsV1Write.FilePath("guides/guide.mdx")], + }); + + const response = await fdr.docs.v2.write.transferOwnershipOfDomain({ + domain, + toOrgId: FdrAPI.OrgId("acme"), + }); + + if (!response.ok) { + throw new Error(`Failed to transfer ownership of domain: ${response.error}`); + } + expect(response.ok).toBe(true); + + // verify ownership + const registerResponse = await fdr.docs.v1.write.startDocsRegister({ + orgId: FdrAPI.OrgId("acme"), + domain, + filepaths: [DocsV1Write.FilePath("logo.png"), DocsV1Write.FilePath("guides/guide.mdx")], + }); + if (!registerResponse.ok) { + throw new Error(`Failed to reregister domain: ${registerResponse.error}`); + } +}); diff --git a/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/WriteService.d.ts b/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/WriteService.d.ts index 54ab9b16cc..4bde70322c 100644 --- a/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/WriteService.d.ts +++ b/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/WriteService.d.ts @@ -26,6 +26,11 @@ export interface WriteServiceMethods { cookie: (cookie: string, value: string, options?: express.CookieOptions) => void; locals: any; }, next: express.NextFunction): void | Promise; + transferOwnershipOfDomain(req: express.Request, res: { + send: () => Promise; + cookie: (cookie: string, value: string, options?: express.CookieOptions) => void; + locals: any; + }, next: express.NextFunction): void | Promise; } export declare class WriteService { private readonly methods; diff --git a/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/WriteService.js b/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/WriteService.js index 4d7e9d444e..77b2e3226c 100644 --- a/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/WriteService.js +++ b/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/WriteService.js @@ -151,6 +151,36 @@ export class WriteService { next(error); } })); + this.router.post("/transfer-ownership", (req, res, next) => __awaiter(this, void 0, void 0, function* () { + try { + yield this.methods.transferOwnershipOfDomain(req, { + send: () => __awaiter(this, void 0, void 0, function* () { + res.sendStatus(204); + }), + cookie: res.cookie.bind(res), + locals: res.locals, + }, next); + next(); + } + catch (error) { + if (error instanceof errors.FernRegistryError) { + switch (error.errorName) { + case "DocsNotFoundError": + case "UnauthorizedError": + break; + default: + console.warn(`Endpoint 'transferOwnershipOfDomain' unexpectedly threw ${error.constructor.name}.` + + ` If this was intentional, please add ${error.constructor.name} to` + + " the endpoint's errors list in your Fern Definition."); + } + yield error.send(res); + } + else { + res.status(500).json("Internal Server Error"); + } + next(error); + } + })); return this.router; } } diff --git a/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/requests/TransferDomainOwnershipRequest.d.ts b/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/requests/TransferDomainOwnershipRequest.d.ts new file mode 100644 index 0000000000..e9a0e6d5c2 --- /dev/null +++ b/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/requests/TransferDomainOwnershipRequest.d.ts @@ -0,0 +1,7 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +export interface TransferDomainOwnershipRequest { + domain: string; + toOrgId: string; +} diff --git a/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/requests/TransferDomainOwnershipRequest.js b/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/requests/TransferDomainOwnershipRequest.js new file mode 100644 index 0000000000..0b46289f5b --- /dev/null +++ b/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/requests/TransferDomainOwnershipRequest.js @@ -0,0 +1,4 @@ +/** + * This file was auto-generated by Fern from our API Definition. + */ +export {}; diff --git a/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/requests/index.d.ts b/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/requests/index.d.ts index 4a1c8f3d4a..a5bad0c306 100644 --- a/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/requests/index.d.ts +++ b/servers/fdr/src/api/generated/api/resources/docs/resources/v2/resources/write/service/requests/index.d.ts @@ -2,3 +2,4 @@ export { StartDocsRegisterRequestV2 } from "./StartDocsRegisterRequestV2"; export { StartDocsPreviewRegisterRequestV2 } from "./StartDocsPreviewRegisterRequestV2"; export { RegisterDocsRequest } from "./RegisterDocsRequest"; export { ReindexAlgoliaRecordsRequest } from "./ReindexAlgoliaRecordsRequest"; +export { TransferDomainOwnershipRequest } from "./TransferDomainOwnershipRequest"; diff --git a/servers/fdr/src/controllers/docs/v2/getDocsWriteV2Service.ts b/servers/fdr/src/controllers/docs/v2/getDocsWriteV2Service.ts index b73de9e319..6daa069759 100644 --- a/servers/fdr/src/controllers/docs/v2/getDocsWriteV2Service.ts +++ b/servers/fdr/src/controllers/docs/v2/getDocsWriteV2Service.ts @@ -262,6 +262,22 @@ export function getDocsWriteV2Service(app: FdrApplication): DocsV2WriteService { res.send(); }, + transferOwnershipOfDomain: async (req, res) => { + // only fern users can transfer domain ownership + await app.services.auth.checkUserBelongsToOrg({ + authHeader: req.headers.authorization, + orgId: "fern", + }); + + const parsedUrl = ParsedBaseUrl.parse(req.body.domain); + + await app.dao.docsV2().transferDomainOwner({ + domain: parsedUrl.getFullUrl(), + toOrgId: req.body.toOrgId, + }); + + return res.send(); + }, }); } diff --git a/servers/fdr/src/db/docs/DocsV2Dao.ts b/servers/fdr/src/db/docs/DocsV2Dao.ts index d1273a5515..aaef719eba 100644 --- a/servers/fdr/src/db/docs/DocsV2Dao.ts +++ b/servers/fdr/src/db/docs/DocsV2Dao.ts @@ -66,10 +66,24 @@ export interface DocsV2Dao { customOnly?: boolean; domainSuffix: string; }): Promise; + + transferDomainOwner({ domain, toOrgId }: { domain: string; toOrgId: string }): Promise; } export class DocsV2DaoImpl implements DocsV2Dao { constructor(private readonly prisma: PrismaClient) {} + + public async transferDomainOwner({ domain, toOrgId }: { domain: string; toOrgId: string }): Promise { + await this.prisma.docsV2.updateMany({ + where: { + domain, + }, + data: { + orgID: toOrgId, + }, + }); + } + public async checkDomainsDontBelongToAnotherOrg(domains: string[], orgId: string): Promise { const matchedDomains = await this.prisma.docsV2.findMany({ select: {