From a56540618f3f707712c7087c8009874141b17696 Mon Sep 17 00:00:00 2001 From: Ali Date: Tue, 14 Apr 2026 21:28:14 +0200 Subject: [PATCH 1/2] Totp --- .../src/sdk/modules/abac/AbacActionsDto.ts | 28 - .../abac/ConfirmClassicPassportTotp.ts | 526 ++++++++++++++++++ .../abac/usePostPassportTotpConfirm.ts | 92 --- modules/abac/AbacCustomActions.dyno.go | 164 ++---- modules/abac/AbacModule3.yml | 58 +- modules/abac/AuthFlow.go | 25 +- .../ConfirmClassicPassportTotpAction.dyno.go | 326 +++++++++++ .../abac/ConfirmClassicPassportTotpAction.go | 17 +- .../modules/selfservice/TotpEnter.screen.tsx | 8 +- .../selfservice/TotpSetup.presenter.tsx | 20 +- .../modules/selfservice/TotpSetup.screen.tsx | 8 +- .../sdk/modules/abac/AbacActionsDto.ts | 28 - .../abac/ConfirmClassicPassportTotp.ts | 526 ++++++++++++++++++ .../abac/usePostPassportTotpConfirm.ts | 92 --- 14 files changed, 1489 insertions(+), 429 deletions(-) create mode 100644 e2e/react-bed/src/sdk/modules/abac/ConfirmClassicPassportTotp.ts delete mode 100644 e2e/react-bed/src/sdk/modules/abac/usePostPassportTotpConfirm.ts create mode 100644 modules/abac/ConfirmClassicPassportTotpAction.dyno.go create mode 100644 modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/ConfirmClassicPassportTotp.ts delete mode 100644 modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/usePostPassportTotpConfirm.ts diff --git a/e2e/react-bed/src/sdk/modules/abac/AbacActionsDto.ts b/e2e/react-bed/src/sdk/modules/abac/AbacActionsDto.ts index c4b679e8e..53fab5345 100644 --- a/e2e/react-bed/src/sdk/modules/abac/AbacActionsDto.ts +++ b/e2e/react-bed/src/sdk/modules/abac/AbacActionsDto.ts @@ -61,34 +61,6 @@ public static Fields = { next: 'next', } } -export class ConfirmClassicPassportTotpActionReqDto { - /** - Passport value, email or phone number which is already successfully registered. - */ - public value?: string | null; - /** - Password related to the passport. Totp is only available for passports with a password. Basically totp is protecting passport, not otp over email or sms. - */ - public password?: string | null; - /** - The totp code generated by authenticator such as google or microsft apps. - */ - public totpCode?: string | null; -public static Fields = { - value: 'value', - password: 'password', - totpCode: 'totpCode', -} -} -export class ConfirmClassicPassportTotpActionResDto { - public session?: UserSessionDto | null; - sessionId?: string | null; -public static Fields = { - sessionId: 'sessionId', - session$: 'session', - session: UserSessionDto.Fields, -} -} export class QueryUserRoleWorkspacesActionResDto { public name?: string | null; /** diff --git a/e2e/react-bed/src/sdk/modules/abac/ConfirmClassicPassportTotp.ts b/e2e/react-bed/src/sdk/modules/abac/ConfirmClassicPassportTotp.ts new file mode 100644 index 000000000..6282acb5d --- /dev/null +++ b/e2e/react-bed/src/sdk/modules/abac/ConfirmClassicPassportTotp.ts @@ -0,0 +1,526 @@ +import { UserSessionDto } from "./UserSessionDto"; +import { buildUrl } from "../../sdk/common/buildUrl"; +import { + fetchx, + handleFetchResponse, + type FetchxContext, + type PartialDeep, + type TypedRequestInit, + type TypedResponse, +} from "../../sdk/common/fetchx"; +import { type UseMutationOptions, useMutation } from "react-query"; +import { useFetchxContext } from "../../sdk/react/useFetchx"; +import { useState } from "react"; +import { withPrefix } from "../../sdk/common/withPrefix"; +/** + * Action to communicate with the action ConfirmClassicPassportTotp + */ +export type ConfirmClassicPassportTotpActionOptions = { + queryKey?: unknown[]; + qs?: URLSearchParams; +}; +export type ConfirmClassicPassportTotpActionMutationOptions = Omit< + UseMutationOptions, + "mutationFn" +> & + ConfirmClassicPassportTotpActionOptions & { + ctx?: FetchxContext; + onMessage?: (ev: MessageEvent) => void; + overrideUrl?: string; + headers?: Headers; + } & Partial<{ + creatorFn: (item: unknown) => ConfirmClassicPassportTotpActionRes; + }>; +export const useConfirmClassicPassportTotpAction = ( + options?: ConfirmClassicPassportTotpActionMutationOptions, +) => { + const globalCtx = useFetchxContext(); + const ctx = options?.ctx ?? globalCtx ?? undefined; + const [isCompleted, setCompleteState] = useState(false); + const [response, setResponse] = useState>(); + const fn = (body: ConfirmClassicPassportTotpActionReq) => { + setCompleteState(false); + return ConfirmClassicPassportTotpAction.Fetch( + { + body, + headers: options?.headers, + }, + { + creatorFn: options?.creatorFn, + qs: options?.qs, + ctx, + onMessage: options?.onMessage, + overrideUrl: options?.overrideUrl, + }, + ).then((x) => { + x.done.then(() => { + setCompleteState(true); + }); + setResponse(x.response); + return x.response.result; + }); + }; + const result = useMutation({ + mutationFn: fn, + ...(options || {}), + }); + return { + ...result, + isCompleted, + response, + }; +}; +/** + * ConfirmClassicPassportTotpAction + */ +export class ConfirmClassicPassportTotpAction { + // + static URL = "/passport/totp/confirm"; + static NewUrl = (qs?: URLSearchParams) => + buildUrl(ConfirmClassicPassportTotpAction.URL, undefined, qs); + static Method = "post"; + static Fetch$ = async ( + qs?: URLSearchParams, + ctx?: FetchxContext, + init?: TypedRequestInit, + overrideUrl?: string, + ) => { + return fetchx< + ConfirmClassicPassportTotpActionRes, + ConfirmClassicPassportTotpActionReq, + unknown + >( + overrideUrl ?? ConfirmClassicPassportTotpAction.NewUrl(qs), + { + method: ConfirmClassicPassportTotpAction.Method, + ...(init || {}), + }, + ctx, + ); + }; + static Fetch = async ( + init?: TypedRequestInit, + { + creatorFn, + qs, + ctx, + onMessage, + overrideUrl, + }: { + creatorFn?: + | ((item: unknown) => ConfirmClassicPassportTotpActionRes) + | undefined; + qs?: URLSearchParams; + ctx?: FetchxContext; + onMessage?: (ev: MessageEvent) => void; + overrideUrl?: string; + } = { + creatorFn: (item) => new ConfirmClassicPassportTotpActionRes(item), + }, + ) => { + creatorFn = + creatorFn || ((item) => new ConfirmClassicPassportTotpActionRes(item)); + const res = await ConfirmClassicPassportTotpAction.Fetch$( + qs, + ctx, + init, + overrideUrl, + ); + return handleFetchResponse( + res, + (item) => (creatorFn ? creatorFn(item) : item), + onMessage, + init?.signal, + ); + }; + static Definition = { + name: "ConfirmClassicPassportTotp", + url: "/passport/totp/confirm", + method: "post", + description: + "When user requires to setup the totp for an specifc passport, they can use this endpoint to confirm it.", + in: { + fields: [ + { + name: "value", + description: + "Passport value, email or phone number which is already successfully registered.", + type: "string", + tags: { + validate: "required", + }, + }, + { + name: "password", + description: + "Password related to the passport. Totp is only available for passports with a password. Basically totp is protecting passport, not otp over email or sms.", + type: "string", + tags: { + validate: "required", + }, + }, + { + name: "totpCode", + description: + "The totp code generated by authenticator such as google or microsft apps.", + type: "string", + tags: { + validate: "required", + }, + }, + ], + }, + out: { + fields: [ + { + name: "session", + type: "one", + target: "UserSessionDto", + }, + ], + }, + }; +} +/** + * The base class definition for confirmClassicPassportTotpActionReq + **/ +export class ConfirmClassicPassportTotpActionReq { + /** + * Passport value, email or phone number which is already successfully registered. + * @type {string} + **/ + #value: string = ""; + /** + * Passport value, email or phone number which is already successfully registered. + * @returns {string} + **/ + get value() { + return this.#value; + } + /** + * Passport value, email or phone number which is already successfully registered. + * @type {string} + **/ + set value(value: string) { + this.#value = String(value); + } + setValue(value: string) { + this.value = value; + return this; + } + /** + * Password related to the passport. Totp is only available for passports with a password. Basically totp is protecting passport, not otp over email or sms. + * @type {string} + **/ + #password: string = ""; + /** + * Password related to the passport. Totp is only available for passports with a password. Basically totp is protecting passport, not otp over email or sms. + * @returns {string} + **/ + get password() { + return this.#password; + } + /** + * Password related to the passport. Totp is only available for passports with a password. Basically totp is protecting passport, not otp over email or sms. + * @type {string} + **/ + set password(value: string) { + this.#password = String(value); + } + setPassword(value: string) { + this.password = value; + return this; + } + /** + * The totp code generated by authenticator such as google or microsft apps. + * @type {string} + **/ + #totpCode: string = ""; + /** + * The totp code generated by authenticator such as google or microsft apps. + * @returns {string} + **/ + get totpCode() { + return this.#totpCode; + } + /** + * The totp code generated by authenticator such as google or microsft apps. + * @type {string} + **/ + set totpCode(value: string) { + this.#totpCode = String(value); + } + setTotpCode(value: string) { + this.totpCode = value; + return this; + } + constructor(data: unknown = undefined) { + if (data === null || data === undefined) { + return; + } + if (typeof data === "string") { + this.applyFromObject(JSON.parse(data)); + } else if (this.#isJsonAppliable(data)) { + this.applyFromObject(data); + } else { + throw new Error( + "Instance cannot be created on an unknown value, check the content being passed. got: " + + typeof data, + ); + } + } + #isJsonAppliable(obj: unknown) { + const g = globalThis as unknown as { Buffer: any; Blob: any }; + const isBuffer = + typeof g.Buffer !== "undefined" && + typeof g.Buffer.isBuffer === "function" && + g.Buffer.isBuffer(obj); + const isBlob = typeof g.Blob !== "undefined" && obj instanceof g.Blob; + return ( + obj && + typeof obj === "object" && + !Array.isArray(obj) && + !isBuffer && + !(obj instanceof ArrayBuffer) && + !isBlob + ); + } + /** + * casts the fields of a javascript object into the class properties one by one + **/ + applyFromObject(data = {}) { + const d = data as Partial; + if (d.value !== undefined) { + this.value = d.value; + } + if (d.password !== undefined) { + this.password = d.password; + } + if (d.totpCode !== undefined) { + this.totpCode = d.totpCode; + } + } + /** + * Special toJSON override, since the field are private, + * Json stringify won't see them unless we mention it explicitly. + **/ + toJSON() { + return { + value: this.#value, + password: this.#password, + totpCode: this.#totpCode, + }; + } + toString() { + return JSON.stringify(this); + } + static get Fields() { + return { + value: "value", + password: "password", + totpCode: "totpCode", + }; + } + /** + * Creates an instance of ConfirmClassicPassportTotpActionReq, and possibleDtoObject + * needs to satisfy the type requirement fully, otherwise typescript compile would + * be complaining. + **/ + static from(possibleDtoObject: ConfirmClassicPassportTotpActionReqType) { + return new ConfirmClassicPassportTotpActionReq(possibleDtoObject); + } + /** + * Creates an instance of ConfirmClassicPassportTotpActionReq, and partialDtoObject + * needs to satisfy the type, but partially, and rest of the content would + * be constructed according to data types and nullability. + **/ + static with( + partialDtoObject: PartialDeep, + ) { + return new ConfirmClassicPassportTotpActionReq(partialDtoObject); + } + copyWith( + partial: PartialDeep, + ): InstanceType { + return new ConfirmClassicPassportTotpActionReq({ + ...this.toJSON(), + ...partial, + }); + } + clone(): InstanceType { + return new ConfirmClassicPassportTotpActionReq(this.toJSON()); + } +} +export abstract class ConfirmClassicPassportTotpActionReqFactory { + abstract create(data: unknown): ConfirmClassicPassportTotpActionReq; +} +/** + * The base type definition for confirmClassicPassportTotpActionReq + **/ +export type ConfirmClassicPassportTotpActionReqType = { + /** + * Passport value, email or phone number which is already successfully registered. + * @type {string} + **/ + value: string; + /** + * Password related to the passport. Totp is only available for passports with a password. Basically totp is protecting passport, not otp over email or sms. + * @type {string} + **/ + password: string; + /** + * The totp code generated by authenticator such as google or microsft apps. + * @type {string} + **/ + totpCode: string; +}; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace ConfirmClassicPassportTotpActionReqType {} +/** + * The base class definition for confirmClassicPassportTotpActionRes + **/ +export class ConfirmClassicPassportTotpActionRes { + /** + * + * @type {UserSessionDto} + **/ + #session!: UserSessionDto; + /** + * + * @returns {UserSessionDto} + **/ + get session() { + return this.#session; + } + /** + * + * @type {UserSessionDto} + **/ + set session(value: UserSessionDto) { + // For objects, the sub type needs to always be instance of the sub class. + if (value instanceof UserSessionDto) { + this.#session = value; + } else { + this.#session = new UserSessionDto(value); + } + } + setSession(value: UserSessionDto) { + this.session = value; + return this; + } + constructor(data: unknown = undefined) { + if (data === null || data === undefined) { + this.#lateInitFields(); + return; + } + if (typeof data === "string") { + this.applyFromObject(JSON.parse(data)); + } else if (this.#isJsonAppliable(data)) { + this.applyFromObject(data); + } else { + throw new Error( + "Instance cannot be created on an unknown value, check the content being passed. got: " + + typeof data, + ); + } + } + #isJsonAppliable(obj: unknown) { + const g = globalThis as unknown as { Buffer: any; Blob: any }; + const isBuffer = + typeof g.Buffer !== "undefined" && + typeof g.Buffer.isBuffer === "function" && + g.Buffer.isBuffer(obj); + const isBlob = typeof g.Blob !== "undefined" && obj instanceof g.Blob; + return ( + obj && + typeof obj === "object" && + !Array.isArray(obj) && + !isBuffer && + !(obj instanceof ArrayBuffer) && + !isBlob + ); + } + /** + * casts the fields of a javascript object into the class properties one by one + **/ + applyFromObject(data = {}) { + const d = data as Partial; + if (d.session !== undefined) { + this.session = d.session; + } + this.#lateInitFields(data); + } + /** + * These are the class instances, which need to be initialised, regardless of the constructor incoming data + **/ + #lateInitFields(data = {}) { + const d = data as Partial; + if (!(d.session instanceof UserSessionDto)) { + this.session = new UserSessionDto(d.session || {}); + } + } + /** + * Special toJSON override, since the field are private, + * Json stringify won't see them unless we mention it explicitly. + **/ + toJSON() { + return { + session: this.#session, + }; + } + toString() { + return JSON.stringify(this); + } + static get Fields() { + return { + session$: "session", + get session() { + return withPrefix("session", UserSessionDto.Fields); + }, + }; + } + /** + * Creates an instance of ConfirmClassicPassportTotpActionRes, and possibleDtoObject + * needs to satisfy the type requirement fully, otherwise typescript compile would + * be complaining. + **/ + static from(possibleDtoObject: ConfirmClassicPassportTotpActionResType) { + return new ConfirmClassicPassportTotpActionRes(possibleDtoObject); + } + /** + * Creates an instance of ConfirmClassicPassportTotpActionRes, and partialDtoObject + * needs to satisfy the type, but partially, and rest of the content would + * be constructed according to data types and nullability. + **/ + static with( + partialDtoObject: PartialDeep, + ) { + return new ConfirmClassicPassportTotpActionRes(partialDtoObject); + } + copyWith( + partial: PartialDeep, + ): InstanceType { + return new ConfirmClassicPassportTotpActionRes({ + ...this.toJSON(), + ...partial, + }); + } + clone(): InstanceType { + return new ConfirmClassicPassportTotpActionRes(this.toJSON()); + } +} +export abstract class ConfirmClassicPassportTotpActionResFactory { + abstract create(data: unknown): ConfirmClassicPassportTotpActionRes; +} +/** + * The base type definition for confirmClassicPassportTotpActionRes + **/ +export type ConfirmClassicPassportTotpActionResType = { + /** + * + * @type {UserSessionDto} + **/ + session: UserSessionDto; +}; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace ConfirmClassicPassportTotpActionResType {} diff --git a/e2e/react-bed/src/sdk/modules/abac/usePostPassportTotpConfirm.ts b/e2e/react-bed/src/sdk/modules/abac/usePostPassportTotpConfirm.ts deleted file mode 100644 index 8b07015d4..000000000 --- a/e2e/react-bed/src/sdk/modules/abac/usePostPassportTotpConfirm.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* -* Generated by fireback 1.2.5 -* Written by Ali Torabi. -* The code is generated for react-query@v3.39.3 -* Checkout the repository for licenses and contribution: https://github.com/torabian/fireback -*/ -import { type FormikHelpers } from "formik"; -import { useContext, useState, useRef } from "react"; -import { useMutation } from "react-query"; -import { - execApiFn, - type IResponse, - mutationErrorsToFormik, - type IResponseList -} from "../../core/http-tools"; -import { - RemoteQueryContext, - type UseRemoteQuery, - queryBeforeSend, -} from "../../core/react-tools"; - import { - ConfirmClassicPassportTotpActionReqDto, - ConfirmClassicPassportTotpActionResDto, - } from "../abac/AbacActionsDto" -export function usePostPassportTotpConfirm( - props?: UseRemoteQuery & { - } -) { - let {queryClient, query, execFnOverride} = props || {}; - query = query || {} - const { options, execFn } = useContext(RemoteQueryContext); - // Calculare the function which will do the remote calls. - // We consider to use global override, this specific override, or default which - // comes with the sdk. - const rpcFn = execFnOverride - ? execFnOverride(options) - : execFn - ? execFn(options) - : execApiFn(options); - // Url of the remote affix. - const url = "/passport/totp/confirm".substr(1); - let computedUrl = `${url}?${new URLSearchParams( - queryBeforeSend(query) - ).toString()}`; - let completeRouteUrls = true; - // Attach the details of the request to the fn - const fn = (body: any) => rpcFn("POST", computedUrl, body); - const mutation = useMutation< - IResponse, - IResponse, - Partial - >(fn); - // Only entities are having a store in front-end - const fnUpdater = ( - data: IResponseList | undefined, - item: IResponse - ) => { - if (!data) { - return { - data: { items: [] }, - }; - } - // To me it seems this is not a good or any correct strategy to update the store. - // When we are posting, we want to add it there, that's it. Not updating it. - // We have patch, but also posting with ID is possible. - if (data.data && item?.data) { - data.data.items = [item.data, ...(data?.data?.items || [])]; - } - return data; - }; - const submit = ( - values: Partial, - formikProps?: FormikHelpers> - ): Promise> => { - return new Promise((resolve, reject) => { - mutation.mutate(values, { - onSuccess(response: IResponse) { - queryClient?.setQueryData>( - "*abac.ConfirmClassicPassportTotpActionResDto", - (data) => fnUpdater(data, response) as any - ); - resolve(response); - }, - onError(error: any) { - formikProps?.setErrors(mutationErrorsToFormik(error)); - reject(error); - }, - }); - }); - }; - return { mutation, submit, fnUpdater }; -} diff --git a/modules/abac/AbacCustomActions.dyno.go b/modules/abac/AbacCustomActions.dyno.go index 1d0cd4728..494e64bb8 100644 --- a/modules/abac/AbacCustomActions.dyno.go +++ b/modules/abac/AbacCustomActions.dyno.go @@ -231,108 +231,6 @@ var UserInvitationsActionCmd cli.Command = cli.Command{ ) }, } -var ConfirmClassicPassportTotpSecurityModel *fireback.SecurityModel = nil - -type ConfirmClassicPassportTotpActionReqDto struct { - // Passport value, email or phone number which is already successfully registered. - Value string `json:"value" xml:"value" yaml:"value" validate:"required" ` - // Password related to the passport. Totp is only available for passports with a password. Basically totp is protecting passport, not otp over email or sms. - Password string `json:"password" xml:"password" yaml:"password" validate:"required" ` - // The totp code generated by authenticator such as google or microsft apps. - TotpCode string `json:"totpCode" xml:"totpCode" yaml:"totpCode" validate:"required" ` -} - -func (x *ConfirmClassicPassportTotpActionReqDto) RootObjectName() string { - return "Abac" -} - -var ConfirmClassicPassportTotpCommonCliFlagsOptional = []cli.Flag{ - &cli.StringFlag{ - Name: "x-src", - Required: false, - Usage: `Import the body of the request from a file (e.g. json/yaml) on the disk`, - }, - &cli.StringFlag{ - Name: "x-accept", - Usage: "Return type of the the content, such as json or yaml", - }, - &cli.StringFlag{ - Name: "value", - Required: true, - Usage: `Passport value, email or phone number which is already successfully registered. (string)`, - }, - &cli.StringFlag{ - Name: "password", - Required: true, - Usage: `Password related to the passport. Totp is only available for passports with a password. Basically totp is protecting passport, not otp over email or sms. (string)`, - }, - &cli.StringFlag{ - Name: "totp-code", - Required: true, - Usage: `The totp code generated by authenticator such as google or microsft apps. (string)`, - }, -} - -func ConfirmClassicPassportTotpActionReqValidator(dto *ConfirmClassicPassportTotpActionReqDto) *fireback.IError { - err := fireback.CommonStructValidatorPointer(dto, false) - return err -} -func CastConfirmClassicPassportTotpFromCli(c *cli.Context) *ConfirmClassicPassportTotpActionReqDto { - template := &ConfirmClassicPassportTotpActionReqDto{} - fireback.HandleXsrc(c, template) - if c.IsSet("value") { - template.Value = c.String("value") - } - if c.IsSet("password") { - template.Password = c.String("password") - } - if c.IsSet("totp-code") { - template.TotpCode = c.String("totp-code") - } - return template -} - -type ConfirmClassicPassportTotpActionResDto struct { - Session *UserSessionDto `json:"session" xml:"session" yaml:"session" gorm:"foreignKey:SessionId;references:UniqueId" ` - SessionId fireback.String `json:"sessionId" yaml:"sessionId" xml:"sessionId" ` -} - -func (x *ConfirmClassicPassportTotpActionResDto) RootObjectName() string { - return "Abac" -} - -type confirmClassicPassportTotpActionImpSig func( - req *ConfirmClassicPassportTotpActionReqDto, - q fireback.QueryDSL) (*ConfirmClassicPassportTotpActionResDto, - *fireback.IError, -) - -var ConfirmClassicPassportTotpActionImp confirmClassicPassportTotpActionImpSig - -func ConfirmClassicPassportTotpActionFn( - req *ConfirmClassicPassportTotpActionReqDto, - q fireback.QueryDSL, -) ( - *ConfirmClassicPassportTotpActionResDto, - *fireback.IError, -) { - if ConfirmClassicPassportTotpActionImp == nil { - return nil, nil - } - return ConfirmClassicPassportTotpActionImp(req, q) -} - -var ConfirmClassicPassportTotpActionCmd cli.Command = cli.Command{ - Name: "confirm-classic-passport-totp", - Usage: `When user requires to setup the totp for an specifc passport, they can use this endpoint to confirm it.`, - Flags: ConfirmClassicPassportTotpCommonCliFlagsOptional, - Action: func(c *cli.Context) { - query := fireback.CommonCliQueryDSLBuilderAuthorize(c, ConfirmClassicPassportTotpSecurityModel) - dto := CastConfirmClassicPassportTotpFromCli(c) - result, err := ConfirmClassicPassportTotpActionFn(dto, query) - fireback.HandleActionInCli(c, result, err, map[string]map[string]string{}) - }, -} var QueryUserRoleWorkspacesSecurityModel = &fireback.SecurityModel{ ActionRequires: []fireback.PermissionInfo{}, ResolveStrategy: "user", @@ -911,6 +809,41 @@ var GsmSendSmsWithProviderActionCmd cli.Command = cli.Command{ /// For emi, we also need to print the handlers, and also print security model, which is a part of Fireback /// and not available in Emi (won't be) +var ConfirmClassicPassportTotpImpl func(c ConfirmClassicPassportTotpActionRequest, query fireback.QueryDSL) (*ConfirmClassicPassportTotpActionResponse, error) = nil +var ConfirmClassicPassportTotpSecurityModel *fireback.SecurityModel = nil + +// This can be both used as cli and http +var ConfirmClassicPassportTotpActionDef fireback.Module3Action = fireback.Module3Action{ + // Temporary until fireback code gen is deleted. + Skip: true, + CliName: ConfirmClassicPassportTotpActionMeta().CliName, + Description: ConfirmClassicPassportTotpActionMeta().Description, + Name: ConfirmClassicPassportTotpActionMeta().Name, + Method: ConfirmClassicPassportTotpActionMeta().Method, + Url: ConfirmClassicPassportTotpActionMeta().URL, + SecurityModel: ConfirmClassicPassportTotpSecurityModel, + // post + Handlers: []gin.HandlerFunc{ + func(m *gin.Context) { + req := ConfirmClassicPassportTotpActionRequest{ + QueryParams: m.Request.URL.Query(), + Headers: m.Request.Header, + GinCtx: m, + } + query := fireback.ExtractQueryDslFromGinContext(m) + fireback.ReadGinRequestBodyAndCastToGoStruct(m, &req.Body, query) + resp, err := ConfirmClassicPassportTotpImpl(req, query) + fireback.WriteActionResponseToGin(m, resp, err) + }, + }, + CliAction: func(c *cli.Context, security *fireback.SecurityModel) error { + query := fireback.CommonCliQueryDSLBuilderAuthorize(c, ConfirmClassicPassportTotpSecurityModel) + req := ConfirmClassicPassportTotpActionRequest{} + resp, err := ConfirmClassicPassportTotpImpl(req, query) + fireback.HandleActionInCli2(c, resp, err, map[string]map[string]string{}) + return nil + }, +} var ChangePasswordImpl func(c ChangePasswordActionRequest, query fireback.QueryDSL) (*ChangePasswordActionResponse, error) = nil var ChangePasswordSecurityModel = &fireback.SecurityModel{ ActionRequires: []fireback.PermissionInfo{}, @@ -1306,6 +1239,7 @@ var OsLoginAuthenticateActionDef fireback.Module3Action = fireback.Module3Action func AbacCustomActions() []fireback.Module3Action { routes := []fireback.Module3Action{ //// Let's add actions for emi acts + ConfirmClassicPassportTotpActionDef, ChangePasswordActionDef, UserPassportsActionDef, CreateWorkspaceActionDef, @@ -1387,29 +1321,6 @@ func AbacCustomActions() []fireback.Module3Action { Entity: "UserInvitationsQueryColumns", }, }, - { - Method: "POST", - Url: "/passport/totp/confirm", - SecurityModel: ConfirmClassicPassportTotpSecurityModel, - Name: "confirmClassicPassportTotp", - Description: "When user requires to setup the totp for an specifc passport, they can use this endpoint to confirm it.", - Handlers: []gin.HandlerFunc{ - func(c *gin.Context) { - // POST_ONE - post - fireback.HttpPostEntity(c, ConfirmClassicPassportTotpActionFn) - }, - }, - Format: "POST_ONE", - Action: ConfirmClassicPassportTotpActionFn, - ResponseEntity: &ConfirmClassicPassportTotpActionResDto{}, - Out: &fireback.Module3ActionBody{ - Entity: "ConfirmClassicPassportTotpActionResDto", - }, - RequestEntity: &ConfirmClassicPassportTotpActionReqDto{}, - In: &fireback.Module3ActionBody{ - Entity: "ConfirmClassicPassportTotpActionReqDto", - }, - }, { Method: "GET", Url: "/urw/query", @@ -1613,7 +1524,6 @@ var AbacCustomActionsCli = []cli.Command{ AcceptInviteActionCmd, OauthAuthenticateActionCmd, UserInvitationsActionCmd, - ConfirmClassicPassportTotpActionCmd, QueryUserRoleWorkspacesActionCmd, SignoutActionCmd, ReactiveSearchActionCmd, @@ -1638,6 +1548,7 @@ var AbacCliActionsBundle = &fireback.CliActionsBundle{ Usage: `Fireback ABAC module provides user authentication, basic support for most projects, including advanced role, permission module on top of fireback core module. Using this module is not essential to create fireback projects, but provides a great possibility to avoid building most user management flow. Some other helpers, such as timezone are added here.`, // Here we will include entities actions, as well as module level actions Subcommands: cli.Commands{ + ConfirmClassicPassportTotpActionDef.ToCli(), ChangePasswordActionDef.ToCli(), UserPassportsActionDef.ToCli(), CreateWorkspaceActionDef.ToCli(), @@ -1652,7 +1563,6 @@ var AbacCliActionsBundle = &fireback.CliActionsBundle{ AcceptInviteActionCmd, OauthAuthenticateActionCmd, UserInvitationsActionCmd, - ConfirmClassicPassportTotpActionCmd, QueryUserRoleWorkspacesActionCmd, SignoutActionCmd, ReactiveSearchActionCmd, diff --git a/modules/abac/AbacModule3.yml b/modules/abac/AbacModule3.yml index 14de9915f..bfdf2ee43 100644 --- a/modules/abac/AbacModule3.yml +++ b/modules/abac/AbacModule3.yml @@ -172,6 +172,37 @@ dtom: type: string? acts: + - name: ConfirmClassicPassportTotp + description: + When user requires to setup the totp for an specifc passport, they can use this endpoint to confirm + it. + method: post + url: /passport/totp/confirm + in: + fields: + - name: value + type: string + tags: + validate: required + description: Passport value, email or phone number which is already successfully registered. + - name: password + type: string + tags: + validate: required + description: + Password related to the passport. Totp is only available for passports with a password. + Basically totp is protecting passport, not otp over email or sms. + - name: totpCode + type: string + tags: + validate: required + description: The totp code generated by authenticator such as google or microsft apps. + out: + envelope: GResponse + fields: + - name: session + type: one + target: UserSessionDto - name: ChangePassword url: /passport/change-password security: @@ -587,33 +618,6 @@ actions: resolveStrategy: user out: dto: UserInvitationsQueryColumns - - name: confirmClassicPassportTotp - description: - When user requires to setup the totp for an specifc passport, they can use this endpoint to confirm - it. - method: post - url: /passport/totp/confirm - in: - fields: - - name: value - type: string - validate: required - description: Passport value, email or phone number which is already successfully registered. - - name: password - type: string - validate: required - description: - Password related to the passport. Totp is only available for passports with a password. - Basically totp is protecting passport, not otp over email or sms. - - name: totpCode - type: string - validate: required - description: The totp code generated by authenticator such as google or microsft apps. - out: - fields: - - name: session - type: one - target: UserSessionDto - name: queryUserRoleWorkspaces format: query diff --git a/modules/abac/AuthFlow.go b/modules/abac/AuthFlow.go index 59a9e6e4a..9f4b0b60c 100644 --- a/modules/abac/AuthFlow.go +++ b/modules/abac/AuthFlow.go @@ -269,16 +269,29 @@ func IntegrateAuthFlow(c *cli.Context) error { return err } - m, err := ConfirmClassicPassportTotpAction(&ConfirmClassicPassportTotpActionReqDto{ - Value: value, - Password: dto.Password, - TotpCode: code, + m, err := ConfirmClassicPassportTotpAction(ConfirmClassicPassportTotpActionRequest{ + Body: ConfirmClassicPassportTotpActionReq{ + Value: value, + Password: dto.Password, + TotpCode: code, + }, }, query) - if m.Session != nil { - authenticateCliWithSession(m.Session, workspaceType.UniqueId) + if err != nil { + fmt.Println("Totp action failed:", err) + os.Exit(2) + } + + var session *UserSessionDto + + if cast, ok := m.Payload.(fireback.GoogleResponse[ConfirmClassicPassportTotpActionRes]); ok { + session = &cast.Data.Item.Session + } else { + fmt.Println("Process successful, but casting has failed:", err) + os.Exit(2) } + authenticateCliWithSession(session, workspaceType.UniqueId) } if workspaceType.UniqueId == ROOT_VAR && res2.Session.Token != "" { diff --git a/modules/abac/ConfirmClassicPassportTotpAction.dyno.go b/modules/abac/ConfirmClassicPassportTotpAction.dyno.go new file mode 100644 index 000000000..106108eca --- /dev/null +++ b/modules/abac/ConfirmClassicPassportTotpAction.dyno.go @@ -0,0 +1,326 @@ +package abac + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/gin-gonic/gin" + "github.com/torabian/emi/emigo" + "github.com/urfave/cli" + "io" + "net/http" + "net/url" +) + +/** +* Action to communicate with the action ConfirmClassicPassportTotpAction + */ +func ConfirmClassicPassportTotpActionMeta() struct { + Name string + CliName string + URL string + Method string + Description string +} { + return struct { + Name string + CliName string + URL string + Method string + Description string + }{ + Name: "ConfirmClassicPassportTotpAction", + CliName: "confirm-classic-passport-totp-action", + URL: "/passport/totp/confirm", + Method: "POST", + Description: `When user requires to setup the totp for an specifc passport, they can use this endpoint to confirm it.`, + } +} +func GetConfirmClassicPassportTotpActionReqCliFlags(prefix string) []emigo.CliFlag { + return []emigo.CliFlag{ + { + Name: prefix + "value", + Type: "string", + }, + { + Name: prefix + "password", + Type: "string", + }, + { + Name: prefix + "totp-code", + Type: "string", + }, + } +} +func CastConfirmClassicPassportTotpActionReqFromCli(c emigo.CliCastable) ConfirmClassicPassportTotpActionReq { + data := ConfirmClassicPassportTotpActionReq{} + if c.IsSet("value") { + data.Value = c.String("value") + } + if c.IsSet("password") { + data.Password = c.String("password") + } + if c.IsSet("totp-code") { + data.TotpCode = c.String("totp-code") + } + return data +} + +// The base class definition for confirmClassicPassportTotpActionReq +type ConfirmClassicPassportTotpActionReq struct { + // Passport value, email or phone number which is already successfully registered. + Value string `json:"value" validate:"required" yaml:"value"` + // Password related to the passport. Totp is only available for passports with a password. Basically totp is protecting passport, not otp over email or sms. + Password string `json:"password" validate:"required" yaml:"password"` + // The totp code generated by authenticator such as google or microsft apps. + TotpCode string `json:"totpCode" validate:"required" yaml:"totpCode"` +} + +func (x *ConfirmClassicPassportTotpActionReq) Json() string { + if x != nil { + str, _ := json.MarshalIndent(x, "", " ") + return string(str) + } + return "" +} +func GetConfirmClassicPassportTotpActionResCliFlags(prefix string) []emigo.CliFlag { + return []emigo.CliFlag{ + { + Name: prefix + "session", + Type: "one", + }, + } +} +func CastConfirmClassicPassportTotpActionResFromCli(c emigo.CliCastable) ConfirmClassicPassportTotpActionRes { + data := ConfirmClassicPassportTotpActionRes{} + return data +} + +// The base class definition for confirmClassicPassportTotpActionRes +type ConfirmClassicPassportTotpActionRes struct { + Session UserSessionDto `json:"session" yaml:"session"` +} + +func (x *ConfirmClassicPassportTotpActionRes) Json() string { + if x != nil { + str, _ := json.MarshalIndent(x, "", " ") + return string(str) + } + return "" +} + +type ConfirmClassicPassportTotpActionResponse struct { + StatusCode int + Headers map[string]string + Payload interface{} +} + +func (x *ConfirmClassicPassportTotpActionResponse) SetContentType(contentType string) *ConfirmClassicPassportTotpActionResponse { + if x.Headers == nil { + x.Headers = make(map[string]string) + } + x.Headers["Content-Type"] = contentType + return x +} +func (x *ConfirmClassicPassportTotpActionResponse) AsStream(r io.Reader, contentType string) *ConfirmClassicPassportTotpActionResponse { + x.Payload = r + x.SetContentType(contentType) + return x +} +func (x *ConfirmClassicPassportTotpActionResponse) AsJSON(payload any) *ConfirmClassicPassportTotpActionResponse { + x.Payload = payload + x.SetContentType("application/json") + return x +} +func (x *ConfirmClassicPassportTotpActionResponse) AsHTML(payload string) *ConfirmClassicPassportTotpActionResponse { + x.Payload = payload + x.SetContentType("text/html; charset=utf-8") + return x +} +func (x *ConfirmClassicPassportTotpActionResponse) AsBytes(payload []byte) *ConfirmClassicPassportTotpActionResponse { + x.Payload = payload + x.SetContentType("application/octet-stream") + return x +} +func (x ConfirmClassicPassportTotpActionResponse) GetStatusCode() int { + return x.StatusCode +} +func (x ConfirmClassicPassportTotpActionResponse) GetRespHeaders() map[string]string { + return x.Headers +} +func (x ConfirmClassicPassportTotpActionResponse) GetPayload() interface{} { + return x.Payload +} + +// ConfirmClassicPassportTotpActionRaw registers a raw Gin route for the ConfirmClassicPassportTotpAction action. +// This gives the developer full control over middleware, handlers, and response handling. +func ConfirmClassicPassportTotpActionRaw(r *gin.Engine, handlers ...gin.HandlerFunc) { + meta := ConfirmClassicPassportTotpActionMeta() + r.Handle(meta.Method, meta.URL, handlers...) +} + +type ConfirmClassicPassportTotpActionRequestSig = func(c ConfirmClassicPassportTotpActionRequest) (*ConfirmClassicPassportTotpActionResponse, error) + +// ConfirmClassicPassportTotpActionHandler returns the HTTP method, route URL, and a typed Gin handler for the ConfirmClassicPassportTotpAction action. +// Developers implement their business logic as a function that receives a typed request object +// and returns either an *ActionResponse or nil. JSON marshalling, headers, and errors are handled automatically. +func ConfirmClassicPassportTotpActionHandler( + handler ConfirmClassicPassportTotpActionRequestSig, +) (method, url string, h gin.HandlerFunc) { + meta := ConfirmClassicPassportTotpActionMeta() + return meta.Method, meta.URL, func(m *gin.Context) { + var body ConfirmClassicPassportTotpActionReq + if err := m.ShouldBindJSON(&body); err != nil { + m.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON: " + err.Error()}) + return + } + // Build typed request wrapper + req := ConfirmClassicPassportTotpActionRequest{ + Body: body, + QueryParams: m.Request.URL.Query(), + Headers: m.Request.Header, + GinCtx: m, + } + resp, err := handler(req) + if err != nil { + m.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + // If the handler returned nil (and no error), it means the response was handled manually. + if resp == nil { + return + } + // Apply headers + for k, v := range resp.Headers { + m.Header(k, v) + } + // Apply status and payload + status := resp.StatusCode + if status == 0 { + status = http.StatusOK + } + if resp.Payload != nil { + m.JSON(status, resp.Payload) + } else { + m.Status(status) + } + } +} + +// ConfirmClassicPassportTotpAction is a high-level convenience wrapper around ConfirmClassicPassportTotpActionHandler. +// It automatically constructs and registers the typed route on the Gin engine. +// Use this when you don't need custom middleware or route grouping. +func ConfirmClassicPassportTotpActionGin(r gin.IRoutes, handler ConfirmClassicPassportTotpActionRequestSig) { + method, url, h := ConfirmClassicPassportTotpActionHandler(handler) + r.Handle(method, url, h) +} + +/** + * Query parameters for ConfirmClassicPassportTotpAction + */ +// Query wrapper with private fields +type ConfirmClassicPassportTotpActionQuery struct { + values url.Values + mapped map[string]interface{} + // Typesafe fields +} + +func ConfirmClassicPassportTotpActionQueryFromString(rawQuery string) ConfirmClassicPassportTotpActionQuery { + v := ConfirmClassicPassportTotpActionQuery{} + values, _ := url.ParseQuery(rawQuery) + mapped := map[string]interface{}{} + if result, err := emigo.UnmarshalQs(rawQuery); err == nil { + mapped = result + } + decoder, err := emigo.NewDecoder(&emigo.DecoderConfig{ + TagName: "json", // reuse json tags + WeaklyTypedInput: true, // "1" -> int, "true" -> bool + Result: &v, + }) + if err == nil { + _ = decoder.Decode(mapped) + } + v.values = values + v.mapped = mapped + return v +} +func ConfirmClassicPassportTotpActionQueryFromGin(c *gin.Context) ConfirmClassicPassportTotpActionQuery { + return ConfirmClassicPassportTotpActionQueryFromString(c.Request.URL.RawQuery) +} +func ConfirmClassicPassportTotpActionQueryFromHttp(r *http.Request) ConfirmClassicPassportTotpActionQuery { + return ConfirmClassicPassportTotpActionQueryFromString(r.URL.RawQuery) +} +func (q ConfirmClassicPassportTotpActionQuery) Values() url.Values { + return q.values +} +func (q ConfirmClassicPassportTotpActionQuery) Mapped() map[string]interface{} { + return q.mapped +} +func (q *ConfirmClassicPassportTotpActionQuery) SetValues(v url.Values) { + q.values = v +} +func (q *ConfirmClassicPassportTotpActionQuery) SetMapped(m map[string]interface{}) { + q.mapped = m +} + +type ConfirmClassicPassportTotpActionRequest struct { + Body ConfirmClassicPassportTotpActionReq + QueryParams url.Values + Headers http.Header + GinCtx *gin.Context + CliCtx *cli.Context +} +type ConfirmClassicPassportTotpActionResult struct { + resp *http.Response // embed original response + Payload interface{} +} + +func ConfirmClassicPassportTotpActionCall( + req ConfirmClassicPassportTotpActionRequest, + config *emigo.APIClient, // optional pre-built request +) (*ConfirmClassicPassportTotpActionResult, error) { + var httpReq *http.Request + if config == nil || config.Httpr == nil { + meta := ConfirmClassicPassportTotpActionMeta() + baseURL := meta.URL + // Build final URL with query string + u, err := url.Parse(baseURL) + if err != nil { + return nil, err + } + // if UrlValues present, encode and append + if len(req.QueryParams) > 0 { + u.RawQuery = req.QueryParams.Encode() + } + bodyBytes, err := json.Marshal(req.Body) + if err != nil { + return nil, err + } + req0, err := http.NewRequest(meta.Method, u.String(), bytes.NewReader(bodyBytes)) + if err != nil { + return nil, err + } + httpReq = req0 + } else { + httpReq = config.Httpr + } + httpReq.Header = req.Headers + resp, err := http.DefaultClient.Do(httpReq) + if err != nil { + return nil, err + } + var result ConfirmClassicPassportTotpActionResult + result.resp = resp + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return &result, err + } + if resp.StatusCode >= 400 { + return &result, fmt.Errorf("request failed: %s", respBody) + } + if err := json.Unmarshal(respBody, &result.Payload); err != nil { + return &result, err + } + return &result, nil +} diff --git a/modules/abac/ConfirmClassicPassportTotpAction.go b/modules/abac/ConfirmClassicPassportTotpAction.go index b33908a61..ba46307c0 100644 --- a/modules/abac/ConfirmClassicPassportTotpAction.go +++ b/modules/abac/ConfirmClassicPassportTotpAction.go @@ -7,15 +7,12 @@ import ( func init() { // Override the implementation with our actual code. - ConfirmClassicPassportTotpActionImp = ConfirmClassicPassportTotpAction + ConfirmClassicPassportTotpImpl = ConfirmClassicPassportTotpAction } -func ConfirmClassicPassportTotpAction( - req *ConfirmClassicPassportTotpActionReqDto, - q fireback.QueryDSL) (*ConfirmClassicPassportTotpActionResDto, - *fireback.IError, -) { - if err := ConfirmClassicPassportTotpActionReqValidator(req); err != nil { +func ConfirmClassicPassportTotpAction(c ConfirmClassicPassportTotpActionRequest, q fireback.QueryDSL) (*ConfirmClassicPassportTotpActionResponse, error) { + req := c.Body + if err := fireback.CommonStructValidatorPointer(&req, false); err != nil { return nil, err } @@ -46,7 +43,9 @@ func ConfirmClassicPassportTotpAction( } // Implement the logic here. - return &ConfirmClassicPassportTotpActionResDto{ - Session: &singinResult.Session, + return &ConfirmClassicPassportTotpActionResponse{ + Payload: ConfirmClassicPassportTotpActionRes{ + Session: singinResult.Session, + }, }, nil } diff --git a/modules/fireback/codegen/react-new/src/modules/fireback/modules/selfservice/TotpEnter.screen.tsx b/modules/fireback/codegen/react-new/src/modules/fireback/modules/selfservice/TotpEnter.screen.tsx index 5602beaa1..07a3ac3e0 100644 --- a/modules/fireback/codegen/react-new/src/modules/fireback/modules/selfservice/TotpEnter.screen.tsx +++ b/modules/fireback/codegen/react-new/src/modules/fireback/modules/selfservice/TotpEnter.screen.tsx @@ -6,9 +6,9 @@ import { useS } from "../../hooks/useS"; import ReactCodeInput from "../../thirdparty/react-verification-code-input"; import { strings } from "./strings/translations"; import { usePresenter } from "./TotpEnter.presenter"; -import { ConfirmClassicPassportTotpActionReqDto } from "../../sdk/modules/abac/AbacActionsDto"; +import { ConfirmClassicPassportTotpActionReq } from "../../sdk/modules/abac/ConfirmClassicPassportTotp"; -export const TotpEnter = ({}: {}) => { +export const TotpEnter = ({ }: {}) => { const { goBack, mutation, form } = usePresenter(); const s = useS(strings); @@ -35,7 +35,7 @@ const Form = ({ form, mutation, }: { - form: FormikProps>; + form: FormikProps>; mutation: UseMutationResult, any>; }) => { const disabled = !form.values.totpCode || form.values.totpCode.length != 6; @@ -52,7 +52,7 @@ const Form = ({ values={form.values.totpCode?.split("")} onChange={(value) => form.setFieldValue( - ConfirmClassicPassportTotpActionReqDto.Fields.totpCode, + ConfirmClassicPassportTotpActionReq.Fields.totpCode, value, false ) diff --git a/modules/fireback/codegen/react-new/src/modules/fireback/modules/selfservice/TotpSetup.presenter.tsx b/modules/fireback/codegen/react-new/src/modules/fireback/modules/selfservice/TotpSetup.presenter.tsx index ca12e7cf5..9e8a7289b 100644 --- a/modules/fireback/codegen/react-new/src/modules/fireback/modules/selfservice/TotpSetup.presenter.tsx +++ b/modules/fireback/codegen/react-new/src/modules/fireback/modules/selfservice/TotpSetup.presenter.tsx @@ -1,17 +1,13 @@ import { useFormik } from "formik"; import { mutationErrorsToFormik } from "../../hooks/api"; import { useRouter } from "../../hooks/useRouter"; -import { type IResponse } from "../../sdk/core/http-tools"; -import { usePostPassportTotpConfirm } from "../../sdk/modules/abac/usePostPassportTotpConfirm"; +import { ConfirmClassicPassportTotpActionReq, useConfirmClassicPassportTotpAction, type ConfirmClassicPassportTotpActionRes } from "../../sdk/modules/abac/ConfirmClassicPassportTotp"; +import type { GResponse } from "../../sdk/sdk/envelopes"; import { useCompleteAuth } from "./auth.common"; -import { - ConfirmClassicPassportTotpActionReqDto, - ConfirmClassicPassportTotpActionResDto, -} from "../../sdk/modules/abac/AbacActionsDto"; export const usePresenter = () => { const { goBack, state } = useRouter(); - const { submit: confirm, mutation } = usePostPassportTotpConfirm(); + const mutation = useConfirmClassicPassportTotpAction(); const { onComplete } = useCompleteAuth(); const totpUrl = state?.totpUrl; @@ -19,23 +15,23 @@ export const usePresenter = () => { const password = state?.password; const value = state?.value; - const submit = (values: Partial) => { - confirm({ ...values, password, value }) + const submit = (values: Partial) => { + mutation.mutateAsync(new ConfirmClassicPassportTotpActionReq({ ...values, password, value })) .then(successful) .catch((error) => { form?.setErrors(mutationErrorsToFormik(error)); }); }; - const form = useFormik>({ + const form = useFormik>({ initialValues: {}, onSubmit: submit, }); const successful = ( - res: IResponse + res: GResponse ) => { - if (res.data?.session) { + if (res.data?.item?.session) { onComplete(res); } }; diff --git a/modules/fireback/codegen/react-new/src/modules/fireback/modules/selfservice/TotpSetup.screen.tsx b/modules/fireback/codegen/react-new/src/modules/fireback/modules/selfservice/TotpSetup.screen.tsx index f12cb2cd2..a1e9c9874 100644 --- a/modules/fireback/codegen/react-new/src/modules/fireback/modules/selfservice/TotpSetup.screen.tsx +++ b/modules/fireback/codegen/react-new/src/modules/fireback/modules/selfservice/TotpSetup.screen.tsx @@ -7,9 +7,9 @@ import { useS } from "../../hooks/useS"; import ReactCodeInput from "../../thirdparty/react-verification-code-input"; import { strings } from "./strings/translations"; import { usePresenter } from "./TotpSetup.presenter"; -import { ConfirmClassicPassportTotpActionReqDto } from "../../sdk/modules/abac/AbacActionsDto"; +import { ConfirmClassicPassportTotpActionReq } from "../../sdk/modules/abac/ConfirmClassicPassportTotp"; -export const TotpSetup = ({}: {}) => { +export const TotpSetup = ({ }: {}) => { const { goBack, submit, mutation, form, totpUrl, forcedTotp } = usePresenter(); @@ -45,7 +45,7 @@ const Form = ({ forcedTotp, totpUrl, }: { - form: FormikProps>; + form: FormikProps>; mutation: UseMutationResult, any>; totpUrl: string; forcedTotp: boolean; @@ -68,7 +68,7 @@ const Form = ({ values={form.values.totpCode?.split("")} onChange={(value) => form.setFieldValue( - ConfirmClassicPassportTotpActionReqDto.Fields.totpCode, + ConfirmClassicPassportTotpActionReq.Fields.totpCode, value, false ) diff --git a/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/AbacActionsDto.ts b/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/AbacActionsDto.ts index c4b679e8e..53fab5345 100644 --- a/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/AbacActionsDto.ts +++ b/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/AbacActionsDto.ts @@ -61,34 +61,6 @@ public static Fields = { next: 'next', } } -export class ConfirmClassicPassportTotpActionReqDto { - /** - Passport value, email or phone number which is already successfully registered. - */ - public value?: string | null; - /** - Password related to the passport. Totp is only available for passports with a password. Basically totp is protecting passport, not otp over email or sms. - */ - public password?: string | null; - /** - The totp code generated by authenticator such as google or microsft apps. - */ - public totpCode?: string | null; -public static Fields = { - value: 'value', - password: 'password', - totpCode: 'totpCode', -} -} -export class ConfirmClassicPassportTotpActionResDto { - public session?: UserSessionDto | null; - sessionId?: string | null; -public static Fields = { - sessionId: 'sessionId', - session$: 'session', - session: UserSessionDto.Fields, -} -} export class QueryUserRoleWorkspacesActionResDto { public name?: string | null; /** diff --git a/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/ConfirmClassicPassportTotp.ts b/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/ConfirmClassicPassportTotp.ts new file mode 100644 index 000000000..6282acb5d --- /dev/null +++ b/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/ConfirmClassicPassportTotp.ts @@ -0,0 +1,526 @@ +import { UserSessionDto } from "./UserSessionDto"; +import { buildUrl } from "../../sdk/common/buildUrl"; +import { + fetchx, + handleFetchResponse, + type FetchxContext, + type PartialDeep, + type TypedRequestInit, + type TypedResponse, +} from "../../sdk/common/fetchx"; +import { type UseMutationOptions, useMutation } from "react-query"; +import { useFetchxContext } from "../../sdk/react/useFetchx"; +import { useState } from "react"; +import { withPrefix } from "../../sdk/common/withPrefix"; +/** + * Action to communicate with the action ConfirmClassicPassportTotp + */ +export type ConfirmClassicPassportTotpActionOptions = { + queryKey?: unknown[]; + qs?: URLSearchParams; +}; +export type ConfirmClassicPassportTotpActionMutationOptions = Omit< + UseMutationOptions, + "mutationFn" +> & + ConfirmClassicPassportTotpActionOptions & { + ctx?: FetchxContext; + onMessage?: (ev: MessageEvent) => void; + overrideUrl?: string; + headers?: Headers; + } & Partial<{ + creatorFn: (item: unknown) => ConfirmClassicPassportTotpActionRes; + }>; +export const useConfirmClassicPassportTotpAction = ( + options?: ConfirmClassicPassportTotpActionMutationOptions, +) => { + const globalCtx = useFetchxContext(); + const ctx = options?.ctx ?? globalCtx ?? undefined; + const [isCompleted, setCompleteState] = useState(false); + const [response, setResponse] = useState>(); + const fn = (body: ConfirmClassicPassportTotpActionReq) => { + setCompleteState(false); + return ConfirmClassicPassportTotpAction.Fetch( + { + body, + headers: options?.headers, + }, + { + creatorFn: options?.creatorFn, + qs: options?.qs, + ctx, + onMessage: options?.onMessage, + overrideUrl: options?.overrideUrl, + }, + ).then((x) => { + x.done.then(() => { + setCompleteState(true); + }); + setResponse(x.response); + return x.response.result; + }); + }; + const result = useMutation({ + mutationFn: fn, + ...(options || {}), + }); + return { + ...result, + isCompleted, + response, + }; +}; +/** + * ConfirmClassicPassportTotpAction + */ +export class ConfirmClassicPassportTotpAction { + // + static URL = "/passport/totp/confirm"; + static NewUrl = (qs?: URLSearchParams) => + buildUrl(ConfirmClassicPassportTotpAction.URL, undefined, qs); + static Method = "post"; + static Fetch$ = async ( + qs?: URLSearchParams, + ctx?: FetchxContext, + init?: TypedRequestInit, + overrideUrl?: string, + ) => { + return fetchx< + ConfirmClassicPassportTotpActionRes, + ConfirmClassicPassportTotpActionReq, + unknown + >( + overrideUrl ?? ConfirmClassicPassportTotpAction.NewUrl(qs), + { + method: ConfirmClassicPassportTotpAction.Method, + ...(init || {}), + }, + ctx, + ); + }; + static Fetch = async ( + init?: TypedRequestInit, + { + creatorFn, + qs, + ctx, + onMessage, + overrideUrl, + }: { + creatorFn?: + | ((item: unknown) => ConfirmClassicPassportTotpActionRes) + | undefined; + qs?: URLSearchParams; + ctx?: FetchxContext; + onMessage?: (ev: MessageEvent) => void; + overrideUrl?: string; + } = { + creatorFn: (item) => new ConfirmClassicPassportTotpActionRes(item), + }, + ) => { + creatorFn = + creatorFn || ((item) => new ConfirmClassicPassportTotpActionRes(item)); + const res = await ConfirmClassicPassportTotpAction.Fetch$( + qs, + ctx, + init, + overrideUrl, + ); + return handleFetchResponse( + res, + (item) => (creatorFn ? creatorFn(item) : item), + onMessage, + init?.signal, + ); + }; + static Definition = { + name: "ConfirmClassicPassportTotp", + url: "/passport/totp/confirm", + method: "post", + description: + "When user requires to setup the totp for an specifc passport, they can use this endpoint to confirm it.", + in: { + fields: [ + { + name: "value", + description: + "Passport value, email or phone number which is already successfully registered.", + type: "string", + tags: { + validate: "required", + }, + }, + { + name: "password", + description: + "Password related to the passport. Totp is only available for passports with a password. Basically totp is protecting passport, not otp over email or sms.", + type: "string", + tags: { + validate: "required", + }, + }, + { + name: "totpCode", + description: + "The totp code generated by authenticator such as google or microsft apps.", + type: "string", + tags: { + validate: "required", + }, + }, + ], + }, + out: { + fields: [ + { + name: "session", + type: "one", + target: "UserSessionDto", + }, + ], + }, + }; +} +/** + * The base class definition for confirmClassicPassportTotpActionReq + **/ +export class ConfirmClassicPassportTotpActionReq { + /** + * Passport value, email or phone number which is already successfully registered. + * @type {string} + **/ + #value: string = ""; + /** + * Passport value, email or phone number which is already successfully registered. + * @returns {string} + **/ + get value() { + return this.#value; + } + /** + * Passport value, email or phone number which is already successfully registered. + * @type {string} + **/ + set value(value: string) { + this.#value = String(value); + } + setValue(value: string) { + this.value = value; + return this; + } + /** + * Password related to the passport. Totp is only available for passports with a password. Basically totp is protecting passport, not otp over email or sms. + * @type {string} + **/ + #password: string = ""; + /** + * Password related to the passport. Totp is only available for passports with a password. Basically totp is protecting passport, not otp over email or sms. + * @returns {string} + **/ + get password() { + return this.#password; + } + /** + * Password related to the passport. Totp is only available for passports with a password. Basically totp is protecting passport, not otp over email or sms. + * @type {string} + **/ + set password(value: string) { + this.#password = String(value); + } + setPassword(value: string) { + this.password = value; + return this; + } + /** + * The totp code generated by authenticator such as google or microsft apps. + * @type {string} + **/ + #totpCode: string = ""; + /** + * The totp code generated by authenticator such as google or microsft apps. + * @returns {string} + **/ + get totpCode() { + return this.#totpCode; + } + /** + * The totp code generated by authenticator such as google or microsft apps. + * @type {string} + **/ + set totpCode(value: string) { + this.#totpCode = String(value); + } + setTotpCode(value: string) { + this.totpCode = value; + return this; + } + constructor(data: unknown = undefined) { + if (data === null || data === undefined) { + return; + } + if (typeof data === "string") { + this.applyFromObject(JSON.parse(data)); + } else if (this.#isJsonAppliable(data)) { + this.applyFromObject(data); + } else { + throw new Error( + "Instance cannot be created on an unknown value, check the content being passed. got: " + + typeof data, + ); + } + } + #isJsonAppliable(obj: unknown) { + const g = globalThis as unknown as { Buffer: any; Blob: any }; + const isBuffer = + typeof g.Buffer !== "undefined" && + typeof g.Buffer.isBuffer === "function" && + g.Buffer.isBuffer(obj); + const isBlob = typeof g.Blob !== "undefined" && obj instanceof g.Blob; + return ( + obj && + typeof obj === "object" && + !Array.isArray(obj) && + !isBuffer && + !(obj instanceof ArrayBuffer) && + !isBlob + ); + } + /** + * casts the fields of a javascript object into the class properties one by one + **/ + applyFromObject(data = {}) { + const d = data as Partial; + if (d.value !== undefined) { + this.value = d.value; + } + if (d.password !== undefined) { + this.password = d.password; + } + if (d.totpCode !== undefined) { + this.totpCode = d.totpCode; + } + } + /** + * Special toJSON override, since the field are private, + * Json stringify won't see them unless we mention it explicitly. + **/ + toJSON() { + return { + value: this.#value, + password: this.#password, + totpCode: this.#totpCode, + }; + } + toString() { + return JSON.stringify(this); + } + static get Fields() { + return { + value: "value", + password: "password", + totpCode: "totpCode", + }; + } + /** + * Creates an instance of ConfirmClassicPassportTotpActionReq, and possibleDtoObject + * needs to satisfy the type requirement fully, otherwise typescript compile would + * be complaining. + **/ + static from(possibleDtoObject: ConfirmClassicPassportTotpActionReqType) { + return new ConfirmClassicPassportTotpActionReq(possibleDtoObject); + } + /** + * Creates an instance of ConfirmClassicPassportTotpActionReq, and partialDtoObject + * needs to satisfy the type, but partially, and rest of the content would + * be constructed according to data types and nullability. + **/ + static with( + partialDtoObject: PartialDeep, + ) { + return new ConfirmClassicPassportTotpActionReq(partialDtoObject); + } + copyWith( + partial: PartialDeep, + ): InstanceType { + return new ConfirmClassicPassportTotpActionReq({ + ...this.toJSON(), + ...partial, + }); + } + clone(): InstanceType { + return new ConfirmClassicPassportTotpActionReq(this.toJSON()); + } +} +export abstract class ConfirmClassicPassportTotpActionReqFactory { + abstract create(data: unknown): ConfirmClassicPassportTotpActionReq; +} +/** + * The base type definition for confirmClassicPassportTotpActionReq + **/ +export type ConfirmClassicPassportTotpActionReqType = { + /** + * Passport value, email or phone number which is already successfully registered. + * @type {string} + **/ + value: string; + /** + * Password related to the passport. Totp is only available for passports with a password. Basically totp is protecting passport, not otp over email or sms. + * @type {string} + **/ + password: string; + /** + * The totp code generated by authenticator such as google or microsft apps. + * @type {string} + **/ + totpCode: string; +}; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace ConfirmClassicPassportTotpActionReqType {} +/** + * The base class definition for confirmClassicPassportTotpActionRes + **/ +export class ConfirmClassicPassportTotpActionRes { + /** + * + * @type {UserSessionDto} + **/ + #session!: UserSessionDto; + /** + * + * @returns {UserSessionDto} + **/ + get session() { + return this.#session; + } + /** + * + * @type {UserSessionDto} + **/ + set session(value: UserSessionDto) { + // For objects, the sub type needs to always be instance of the sub class. + if (value instanceof UserSessionDto) { + this.#session = value; + } else { + this.#session = new UserSessionDto(value); + } + } + setSession(value: UserSessionDto) { + this.session = value; + return this; + } + constructor(data: unknown = undefined) { + if (data === null || data === undefined) { + this.#lateInitFields(); + return; + } + if (typeof data === "string") { + this.applyFromObject(JSON.parse(data)); + } else if (this.#isJsonAppliable(data)) { + this.applyFromObject(data); + } else { + throw new Error( + "Instance cannot be created on an unknown value, check the content being passed. got: " + + typeof data, + ); + } + } + #isJsonAppliable(obj: unknown) { + const g = globalThis as unknown as { Buffer: any; Blob: any }; + const isBuffer = + typeof g.Buffer !== "undefined" && + typeof g.Buffer.isBuffer === "function" && + g.Buffer.isBuffer(obj); + const isBlob = typeof g.Blob !== "undefined" && obj instanceof g.Blob; + return ( + obj && + typeof obj === "object" && + !Array.isArray(obj) && + !isBuffer && + !(obj instanceof ArrayBuffer) && + !isBlob + ); + } + /** + * casts the fields of a javascript object into the class properties one by one + **/ + applyFromObject(data = {}) { + const d = data as Partial; + if (d.session !== undefined) { + this.session = d.session; + } + this.#lateInitFields(data); + } + /** + * These are the class instances, which need to be initialised, regardless of the constructor incoming data + **/ + #lateInitFields(data = {}) { + const d = data as Partial; + if (!(d.session instanceof UserSessionDto)) { + this.session = new UserSessionDto(d.session || {}); + } + } + /** + * Special toJSON override, since the field are private, + * Json stringify won't see them unless we mention it explicitly. + **/ + toJSON() { + return { + session: this.#session, + }; + } + toString() { + return JSON.stringify(this); + } + static get Fields() { + return { + session$: "session", + get session() { + return withPrefix("session", UserSessionDto.Fields); + }, + }; + } + /** + * Creates an instance of ConfirmClassicPassportTotpActionRes, and possibleDtoObject + * needs to satisfy the type requirement fully, otherwise typescript compile would + * be complaining. + **/ + static from(possibleDtoObject: ConfirmClassicPassportTotpActionResType) { + return new ConfirmClassicPassportTotpActionRes(possibleDtoObject); + } + /** + * Creates an instance of ConfirmClassicPassportTotpActionRes, and partialDtoObject + * needs to satisfy the type, but partially, and rest of the content would + * be constructed according to data types and nullability. + **/ + static with( + partialDtoObject: PartialDeep, + ) { + return new ConfirmClassicPassportTotpActionRes(partialDtoObject); + } + copyWith( + partial: PartialDeep, + ): InstanceType { + return new ConfirmClassicPassportTotpActionRes({ + ...this.toJSON(), + ...partial, + }); + } + clone(): InstanceType { + return new ConfirmClassicPassportTotpActionRes(this.toJSON()); + } +} +export abstract class ConfirmClassicPassportTotpActionResFactory { + abstract create(data: unknown): ConfirmClassicPassportTotpActionRes; +} +/** + * The base type definition for confirmClassicPassportTotpActionRes + **/ +export type ConfirmClassicPassportTotpActionResType = { + /** + * + * @type {UserSessionDto} + **/ + session: UserSessionDto; +}; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace ConfirmClassicPassportTotpActionResType {} diff --git a/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/usePostPassportTotpConfirm.ts b/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/usePostPassportTotpConfirm.ts deleted file mode 100644 index 8b07015d4..000000000 --- a/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/usePostPassportTotpConfirm.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* -* Generated by fireback 1.2.5 -* Written by Ali Torabi. -* The code is generated for react-query@v3.39.3 -* Checkout the repository for licenses and contribution: https://github.com/torabian/fireback -*/ -import { type FormikHelpers } from "formik"; -import { useContext, useState, useRef } from "react"; -import { useMutation } from "react-query"; -import { - execApiFn, - type IResponse, - mutationErrorsToFormik, - type IResponseList -} from "../../core/http-tools"; -import { - RemoteQueryContext, - type UseRemoteQuery, - queryBeforeSend, -} from "../../core/react-tools"; - import { - ConfirmClassicPassportTotpActionReqDto, - ConfirmClassicPassportTotpActionResDto, - } from "../abac/AbacActionsDto" -export function usePostPassportTotpConfirm( - props?: UseRemoteQuery & { - } -) { - let {queryClient, query, execFnOverride} = props || {}; - query = query || {} - const { options, execFn } = useContext(RemoteQueryContext); - // Calculare the function which will do the remote calls. - // We consider to use global override, this specific override, or default which - // comes with the sdk. - const rpcFn = execFnOverride - ? execFnOverride(options) - : execFn - ? execFn(options) - : execApiFn(options); - // Url of the remote affix. - const url = "/passport/totp/confirm".substr(1); - let computedUrl = `${url}?${new URLSearchParams( - queryBeforeSend(query) - ).toString()}`; - let completeRouteUrls = true; - // Attach the details of the request to the fn - const fn = (body: any) => rpcFn("POST", computedUrl, body); - const mutation = useMutation< - IResponse, - IResponse, - Partial - >(fn); - // Only entities are having a store in front-end - const fnUpdater = ( - data: IResponseList | undefined, - item: IResponse - ) => { - if (!data) { - return { - data: { items: [] }, - }; - } - // To me it seems this is not a good or any correct strategy to update the store. - // When we are posting, we want to add it there, that's it. Not updating it. - // We have patch, but also posting with ID is possible. - if (data.data && item?.data) { - data.data.items = [item.data, ...(data?.data?.items || [])]; - } - return data; - }; - const submit = ( - values: Partial, - formikProps?: FormikHelpers> - ): Promise> => { - return new Promise((resolve, reject) => { - mutation.mutate(values, { - onSuccess(response: IResponse) { - queryClient?.setQueryData>( - "*abac.ConfirmClassicPassportTotpActionResDto", - (data) => fnUpdater(data, response) as any - ); - resolve(response); - }, - onError(error: any) { - formikProps?.setErrors(mutationErrorsToFormik(error)); - reject(error); - }, - }); - }); - }; - return { mutation, submit, fnUpdater }; -} From 3ff9e03e0236d9193c2657e973e7eb021463f142 Mon Sep 17 00:00:00 2001 From: Ali Date: Tue, 14 Apr 2026 21:34:55 +0200 Subject: [PATCH 2/2] Migrate couple of more apis --- .../src/sdk/modules/abac/AbacActionsDto.ts | 9 - .../src/sdk/modules/abac/AcceptInvite.ts | 410 ++++++++++++++++++ .../abac/ConfirmClassicPassportTotp.ts | 13 +- .../abac/usePostUserInvitationAccept.ts | 91 ---- modules/abac/AbacCustomActions.dyno.go | 141 ++---- modules/abac/AbacModule3.yml | 45 +- modules/abac/AcceptInviteAction.dyno.go | 311 +++++++++++++ modules/abac/AcceptInviteAction.go | 18 +- modules/abac/UserEntity.go | 2 +- .../user-invitations/UserInvitationList.tsx | 10 +- .../sdk/modules/abac/AbacActionsDto.ts | 9 - .../fireback/sdk/modules/abac/AcceptInvite.ts | 410 ++++++++++++++++++ .../abac/ConfirmClassicPassportTotp.ts | 13 +- .../abac/usePostUserInvitationAccept.ts | 91 ---- 14 files changed, 1228 insertions(+), 345 deletions(-) create mode 100644 e2e/react-bed/src/sdk/modules/abac/AcceptInvite.ts delete mode 100644 e2e/react-bed/src/sdk/modules/abac/usePostUserInvitationAccept.ts create mode 100644 modules/abac/AcceptInviteAction.dyno.go create mode 100644 modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/AcceptInvite.ts delete mode 100644 modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/usePostUserInvitationAccept.ts diff --git a/e2e/react-bed/src/sdk/modules/abac/AbacActionsDto.ts b/e2e/react-bed/src/sdk/modules/abac/AbacActionsDto.ts index 53fab5345..2d7a572a4 100644 --- a/e2e/react-bed/src/sdk/modules/abac/AbacActionsDto.ts +++ b/e2e/react-bed/src/sdk/modules/abac/AbacActionsDto.ts @@ -24,15 +24,6 @@ import { */ public capabilities?: string[] | null; } -export class AcceptInviteActionReqDto { - /** - The invitation id which will be used to process - */ - public invitationUniqueId?: string | null; -public static Fields = { - invitationUniqueId: 'invitationUniqueId', -} -} export class OauthAuthenticateActionReqDto { /** The token that Auth2 provider returned to the front-end, which will be used to validate the backend diff --git a/e2e/react-bed/src/sdk/modules/abac/AcceptInvite.ts b/e2e/react-bed/src/sdk/modules/abac/AcceptInvite.ts new file mode 100644 index 000000000..ee5139179 --- /dev/null +++ b/e2e/react-bed/src/sdk/modules/abac/AcceptInvite.ts @@ -0,0 +1,410 @@ +import { GResponse } from "../../sdk/envelopes/index"; +import { buildUrl } from "../../sdk/common/buildUrl"; +import { + fetchx, + handleFetchResponse, + type FetchxContext, + type PartialDeep, + type TypedRequestInit, + type TypedResponse, +} from "../../sdk/common/fetchx"; +import { type UseMutationOptions, useMutation } from "react-query"; +import { useFetchxContext } from "../../sdk/react/useFetchx"; +import { useState } from "react"; +/** + * Action to communicate with the action AcceptInvite + */ +export type AcceptInviteActionOptions = { + queryKey?: unknown[]; + qs?: URLSearchParams; +}; +export type AcceptInviteActionMutationOptions = Omit< + UseMutationOptions, + "mutationFn" +> & + AcceptInviteActionOptions & { + ctx?: FetchxContext; + onMessage?: (ev: MessageEvent) => void; + overrideUrl?: string; + headers?: Headers; + } & Partial<{ + creatorFn: (item: unknown) => AcceptInviteActionRes; + }>; +export const useAcceptInviteAction = ( + options?: AcceptInviteActionMutationOptions, +) => { + const globalCtx = useFetchxContext(); + const ctx = options?.ctx ?? globalCtx ?? undefined; + const [isCompleted, setCompleteState] = useState(false); + const [response, setResponse] = useState>(); + const fn = (body: AcceptInviteActionReq) => { + setCompleteState(false); + return AcceptInviteAction.Fetch( + { + body, + headers: options?.headers, + }, + { + creatorFn: options?.creatorFn, + qs: options?.qs, + ctx, + onMessage: options?.onMessage, + overrideUrl: options?.overrideUrl, + }, + ).then((x) => { + x.done.then(() => { + setCompleteState(true); + }); + setResponse(x.response); + return x.response.result; + }); + }; + const result = useMutation({ + mutationFn: fn, + ...(options || {}), + }); + return { + ...result, + isCompleted, + response, + }; +}; +/** + * AcceptInviteAction + */ +export class AcceptInviteAction { + // + static URL = "/user/invitation/accept"; + static NewUrl = (qs?: URLSearchParams) => + buildUrl(AcceptInviteAction.URL, undefined, qs); + static Method = "post"; + static Fetch$ = async ( + qs?: URLSearchParams, + ctx?: FetchxContext, + init?: TypedRequestInit, + overrideUrl?: string, + ) => { + return fetchx< + GResponse, + AcceptInviteActionReq, + unknown + >( + overrideUrl ?? AcceptInviteAction.NewUrl(qs), + { + method: AcceptInviteAction.Method, + ...(init || {}), + }, + ctx, + ); + }; + static Fetch = async ( + init?: TypedRequestInit, + { + creatorFn, + qs, + ctx, + onMessage, + overrideUrl, + }: { + creatorFn?: ((item: unknown) => AcceptInviteActionRes) | undefined; + qs?: URLSearchParams; + ctx?: FetchxContext; + onMessage?: (ev: MessageEvent) => void; + overrideUrl?: string; + } = { + creatorFn: (item) => new AcceptInviteActionRes(item), + }, + ) => { + creatorFn = creatorFn || ((item) => new AcceptInviteActionRes(item)); + const res = await AcceptInviteAction.Fetch$(qs, ctx, init, overrideUrl); + return handleFetchResponse( + res, + (data) => { + const resp = new GResponse(); + if (creatorFn) { + resp.setCreator(creatorFn); + } + resp.inject(data); + return resp; + }, + onMessage, + init?.signal, + ); + }; + static Definition = { + name: "AcceptInvite", + url: "/user/invitation/accept", + method: "post", + description: + "Use it when user accepts an invitation, and it will complete the joining process", + in: { + fields: [ + { + name: "invitationUniqueId", + description: "The invitation id which will be used to process", + type: "string", + tags: { + validate: "required", + }, + }, + ], + }, + out: { + envelope: "GResponse", + fields: [ + { + name: "accepted", + type: "bool", + }, + ], + }, + }; +} +/** + * The base class definition for acceptInviteActionReq + **/ +export class AcceptInviteActionReq { + /** + * The invitation id which will be used to process + * @type {string} + **/ + #invitationUniqueId: string = ""; + /** + * The invitation id which will be used to process + * @returns {string} + **/ + get invitationUniqueId() { + return this.#invitationUniqueId; + } + /** + * The invitation id which will be used to process + * @type {string} + **/ + set invitationUniqueId(value: string) { + this.#invitationUniqueId = String(value); + } + setInvitationUniqueId(value: string) { + this.invitationUniqueId = value; + return this; + } + constructor(data: unknown = undefined) { + if (data === null || data === undefined) { + return; + } + if (typeof data === "string") { + this.applyFromObject(JSON.parse(data)); + } else if (this.#isJsonAppliable(data)) { + this.applyFromObject(data); + } else { + throw new Error( + "Instance cannot be created on an unknown value, check the content being passed. got: " + + typeof data, + ); + } + } + #isJsonAppliable(obj: unknown) { + const g = globalThis as unknown as { Buffer: any; Blob: any }; + const isBuffer = + typeof g.Buffer !== "undefined" && + typeof g.Buffer.isBuffer === "function" && + g.Buffer.isBuffer(obj); + const isBlob = typeof g.Blob !== "undefined" && obj instanceof g.Blob; + return ( + obj && + typeof obj === "object" && + !Array.isArray(obj) && + !isBuffer && + !(obj instanceof ArrayBuffer) && + !isBlob + ); + } + /** + * casts the fields of a javascript object into the class properties one by one + **/ + applyFromObject(data = {}) { + const d = data as Partial; + if (d.invitationUniqueId !== undefined) { + this.invitationUniqueId = d.invitationUniqueId; + } + } + /** + * Special toJSON override, since the field are private, + * Json stringify won't see them unless we mention it explicitly. + **/ + toJSON() { + return { + invitationUniqueId: this.#invitationUniqueId, + }; + } + toString() { + return JSON.stringify(this); + } + static get Fields() { + return { + invitationUniqueId: "invitationUniqueId", + }; + } + /** + * Creates an instance of AcceptInviteActionReq, and possibleDtoObject + * needs to satisfy the type requirement fully, otherwise typescript compile would + * be complaining. + **/ + static from(possibleDtoObject: AcceptInviteActionReqType) { + return new AcceptInviteActionReq(possibleDtoObject); + } + /** + * Creates an instance of AcceptInviteActionReq, and partialDtoObject + * needs to satisfy the type, but partially, and rest of the content would + * be constructed according to data types and nullability. + **/ + static with(partialDtoObject: PartialDeep) { + return new AcceptInviteActionReq(partialDtoObject); + } + copyWith( + partial: PartialDeep, + ): InstanceType { + return new AcceptInviteActionReq({ ...this.toJSON(), ...partial }); + } + clone(): InstanceType { + return new AcceptInviteActionReq(this.toJSON()); + } +} +export abstract class AcceptInviteActionReqFactory { + abstract create(data: unknown): AcceptInviteActionReq; +} +/** + * The base type definition for acceptInviteActionReq + **/ +export type AcceptInviteActionReqType = { + /** + * The invitation id which will be used to process + * @type {string} + **/ + invitationUniqueId: string; +}; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace AcceptInviteActionReqType {} +/** + * The base class definition for acceptInviteActionRes + **/ +export class AcceptInviteActionRes { + /** + * + * @type {boolean} + **/ + #accepted!: boolean; + /** + * + * @returns {boolean} + **/ + get accepted() { + return this.#accepted; + } + /** + * + * @type {boolean} + **/ + set accepted(value: boolean) { + this.#accepted = Boolean(value); + } + setAccepted(value: boolean) { + this.accepted = value; + return this; + } + constructor(data: unknown = undefined) { + if (data === null || data === undefined) { + return; + } + if (typeof data === "string") { + this.applyFromObject(JSON.parse(data)); + } else if (this.#isJsonAppliable(data)) { + this.applyFromObject(data); + } else { + throw new Error( + "Instance cannot be created on an unknown value, check the content being passed. got: " + + typeof data, + ); + } + } + #isJsonAppliable(obj: unknown) { + const g = globalThis as unknown as { Buffer: any; Blob: any }; + const isBuffer = + typeof g.Buffer !== "undefined" && + typeof g.Buffer.isBuffer === "function" && + g.Buffer.isBuffer(obj); + const isBlob = typeof g.Blob !== "undefined" && obj instanceof g.Blob; + return ( + obj && + typeof obj === "object" && + !Array.isArray(obj) && + !isBuffer && + !(obj instanceof ArrayBuffer) && + !isBlob + ); + } + /** + * casts the fields of a javascript object into the class properties one by one + **/ + applyFromObject(data = {}) { + const d = data as Partial; + if (d.accepted !== undefined) { + this.accepted = d.accepted; + } + } + /** + * Special toJSON override, since the field are private, + * Json stringify won't see them unless we mention it explicitly. + **/ + toJSON() { + return { + accepted: this.#accepted, + }; + } + toString() { + return JSON.stringify(this); + } + static get Fields() { + return { + accepted: "accepted", + }; + } + /** + * Creates an instance of AcceptInviteActionRes, and possibleDtoObject + * needs to satisfy the type requirement fully, otherwise typescript compile would + * be complaining. + **/ + static from(possibleDtoObject: AcceptInviteActionResType) { + return new AcceptInviteActionRes(possibleDtoObject); + } + /** + * Creates an instance of AcceptInviteActionRes, and partialDtoObject + * needs to satisfy the type, but partially, and rest of the content would + * be constructed according to data types and nullability. + **/ + static with(partialDtoObject: PartialDeep) { + return new AcceptInviteActionRes(partialDtoObject); + } + copyWith( + partial: PartialDeep, + ): InstanceType { + return new AcceptInviteActionRes({ ...this.toJSON(), ...partial }); + } + clone(): InstanceType { + return new AcceptInviteActionRes(this.toJSON()); + } +} +export abstract class AcceptInviteActionResFactory { + abstract create(data: unknown): AcceptInviteActionRes; +} +/** + * The base type definition for acceptInviteActionRes + **/ +export type AcceptInviteActionResType = { + /** + * + * @type {boolean} + **/ + accepted: boolean; +}; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace AcceptInviteActionResType {} diff --git a/e2e/react-bed/src/sdk/modules/abac/ConfirmClassicPassportTotp.ts b/e2e/react-bed/src/sdk/modules/abac/ConfirmClassicPassportTotp.ts index 6282acb5d..623466639 100644 --- a/e2e/react-bed/src/sdk/modules/abac/ConfirmClassicPassportTotp.ts +++ b/e2e/react-bed/src/sdk/modules/abac/ConfirmClassicPassportTotp.ts @@ -1,3 +1,4 @@ +import { GResponse } from "../../sdk/envelopes/index"; import { UserSessionDto } from "./UserSessionDto"; import { buildUrl } from "../../sdk/common/buildUrl"; import { @@ -86,7 +87,7 @@ export class ConfirmClassicPassportTotpAction { overrideUrl?: string, ) => { return fetchx< - ConfirmClassicPassportTotpActionRes, + GResponse, ConfirmClassicPassportTotpActionReq, unknown >( @@ -128,7 +129,14 @@ export class ConfirmClassicPassportTotpAction { ); return handleFetchResponse( res, - (item) => (creatorFn ? creatorFn(item) : item), + (data) => { + const resp = new GResponse(); + if (creatorFn) { + resp.setCreator(creatorFn); + } + resp.inject(data); + return resp; + }, onMessage, init?.signal, ); @@ -171,6 +179,7 @@ export class ConfirmClassicPassportTotpAction { ], }, out: { + envelope: "GResponse", fields: [ { name: "session", diff --git a/e2e/react-bed/src/sdk/modules/abac/usePostUserInvitationAccept.ts b/e2e/react-bed/src/sdk/modules/abac/usePostUserInvitationAccept.ts deleted file mode 100644 index 797302535..000000000 --- a/e2e/react-bed/src/sdk/modules/abac/usePostUserInvitationAccept.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* -* Generated by fireback 1.2.5 -* Written by Ali Torabi. -* The code is generated for react-query@v3.39.3 -* Checkout the repository for licenses and contribution: https://github.com/torabian/fireback -*/ -import { type FormikHelpers } from "formik"; -import { useContext, useState, useRef } from "react"; -import { useMutation } from "react-query"; -import { - execApiFn, - type IResponse, - mutationErrorsToFormik, - type IResponseList -} from "../../core/http-tools"; -import { - RemoteQueryContext, - type UseRemoteQuery, - queryBeforeSend, -} from "../../core/react-tools"; - import { - AcceptInviteActionReqDto, - } from "../abac/AbacActionsDto" -export function usePostUserInvitationAccept( - props?: UseRemoteQuery & { - } -) { - let {queryClient, query, execFnOverride} = props || {}; - query = query || {} - const { options, execFn } = useContext(RemoteQueryContext); - // Calculare the function which will do the remote calls. - // We consider to use global override, this specific override, or default which - // comes with the sdk. - const rpcFn = execFnOverride - ? execFnOverride(options) - : execFn - ? execFn(options) - : execApiFn(options); - // Url of the remote affix. - const url = "/user/invitation/accept".substr(1); - let computedUrl = `${url}?${new URLSearchParams( - queryBeforeSend(query) - ).toString()}`; - let completeRouteUrls = true; - // Attach the details of the request to the fn - const fn = (body: any) => rpcFn("POST", computedUrl, body); - const mutation = useMutation< - IResponse, - IResponse, - Partial - >(fn); - // Only entities are having a store in front-end - const fnUpdater = ( - data: IResponseList | undefined, - item: IResponse - ) => { - if (!data) { - return { - data: { items: [] }, - }; - } - // To me it seems this is not a good or any correct strategy to update the store. - // When we are posting, we want to add it there, that's it. Not updating it. - // We have patch, but also posting with ID is possible. - if (data.data && item?.data) { - data.data.items = [item.data, ...(data?.data?.items || [])]; - } - return data; - }; - const submit = ( - values: Partial, - formikProps?: FormikHelpers> - ): Promise> => { - return new Promise((resolve, reject) => { - mutation.mutate(values, { - onSuccess(response: IResponse) { - queryClient?.setQueryData>( - "string", - (data) => fnUpdater(data, response) as any - ); - resolve(response); - }, - onError(error: any) { - formikProps?.setErrors(mutationErrorsToFormik(error)); - reject(error); - }, - }); - }); - }; - return { mutation, submit, fnUpdater }; -} diff --git a/modules/abac/AbacCustomActions.dyno.go b/modules/abac/AbacCustomActions.dyno.go index 494e64bb8..1ba108539 100644 --- a/modules/abac/AbacCustomActions.dyno.go +++ b/modules/abac/AbacCustomActions.dyno.go @@ -22,82 +22,6 @@ type QueryUserRoleWorkspacesResDtoRoles struct { Capabilities []string `json:"capabilities" xml:"capabilities" yaml:"capabilities" ` } -var AcceptInviteSecurityModel = &fireback.SecurityModel{ - ActionRequires: []fireback.PermissionInfo{}, - ResolveStrategy: "user", -} - -type AcceptInviteActionReqDto struct { - // The invitation id which will be used to process - InvitationUniqueId string `json:"invitationUniqueId" xml:"invitationUniqueId" yaml:"invitationUniqueId" validate:"required" ` -} - -func (x *AcceptInviteActionReqDto) RootObjectName() string { - return "Abac" -} - -var AcceptInviteCommonCliFlagsOptional = []cli.Flag{ - &cli.StringFlag{ - Name: "x-src", - Required: false, - Usage: `Import the body of the request from a file (e.g. json/yaml) on the disk`, - }, - &cli.StringFlag{ - Name: "x-accept", - Usage: "Return type of the the content, such as json or yaml", - }, - &cli.StringFlag{ - Name: "invitation-unique-id", - Required: true, - Usage: `The invitation id which will be used to process (string)`, - }, -} - -func AcceptInviteActionReqValidator(dto *AcceptInviteActionReqDto) *fireback.IError { - err := fireback.CommonStructValidatorPointer(dto, false) - return err -} -func CastAcceptInviteFromCli(c *cli.Context) *AcceptInviteActionReqDto { - template := &AcceptInviteActionReqDto{} - fireback.HandleXsrc(c, template) - if c.IsSet("invitation-unique-id") { - template.InvitationUniqueId = c.String("invitation-unique-id") - } - return template -} - -type acceptInviteActionImpSig func( - req *AcceptInviteActionReqDto, - q fireback.QueryDSL) (string, - *fireback.IError, -) - -var AcceptInviteActionImp acceptInviteActionImpSig - -func AcceptInviteActionFn( - req *AcceptInviteActionReqDto, - q fireback.QueryDSL, -) ( - string, - *fireback.IError, -) { - if AcceptInviteActionImp == nil { - return "", nil - } - return AcceptInviteActionImp(req, q) -} - -var AcceptInviteActionCmd cli.Command = cli.Command{ - Name: "accept-invite", - Usage: `Use it when user accepts an invitation, and it will complete the joining process`, - Flags: AcceptInviteCommonCliFlagsOptional, - Action: func(c *cli.Context) { - query := fireback.CommonCliQueryDSLBuilderAuthorize(c, AcceptInviteSecurityModel) - dto := CastAcceptInviteFromCli(c) - result, err := AcceptInviteActionFn(dto, query) - fireback.HandleActionInCli(c, result, err, map[string]map[string]string{}) - }, -} var OauthAuthenticateSecurityModel *fireback.SecurityModel = nil type OauthAuthenticateActionReqDto struct { @@ -809,6 +733,44 @@ var GsmSendSmsWithProviderActionCmd cli.Command = cli.Command{ /// For emi, we also need to print the handlers, and also print security model, which is a part of Fireback /// and not available in Emi (won't be) +var AcceptInviteImpl func(c AcceptInviteActionRequest, query fireback.QueryDSL) (*AcceptInviteActionResponse, error) = nil +var AcceptInviteSecurityModel = &fireback.SecurityModel{ + ActionRequires: []fireback.PermissionInfo{}, + ResolveStrategy: "user", +} + +// This can be both used as cli and http +var AcceptInviteActionDef fireback.Module3Action = fireback.Module3Action{ + // Temporary until fireback code gen is deleted. + Skip: true, + CliName: AcceptInviteActionMeta().CliName, + Description: AcceptInviteActionMeta().Description, + Name: AcceptInviteActionMeta().Name, + Method: AcceptInviteActionMeta().Method, + Url: AcceptInviteActionMeta().URL, + SecurityModel: AcceptInviteSecurityModel, + // post + Handlers: []gin.HandlerFunc{ + func(m *gin.Context) { + req := AcceptInviteActionRequest{ + QueryParams: m.Request.URL.Query(), + Headers: m.Request.Header, + GinCtx: m, + } + query := fireback.ExtractQueryDslFromGinContext(m) + fireback.ReadGinRequestBodyAndCastToGoStruct(m, &req.Body, query) + resp, err := AcceptInviteImpl(req, query) + fireback.WriteActionResponseToGin(m, resp, err) + }, + }, + CliAction: func(c *cli.Context, security *fireback.SecurityModel) error { + query := fireback.CommonCliQueryDSLBuilderAuthorize(c, AcceptInviteSecurityModel) + req := AcceptInviteActionRequest{} + resp, err := AcceptInviteImpl(req, query) + fireback.HandleActionInCli2(c, resp, err, map[string]map[string]string{}) + return nil + }, +} var ConfirmClassicPassportTotpImpl func(c ConfirmClassicPassportTotpActionRequest, query fireback.QueryDSL) (*ConfirmClassicPassportTotpActionResponse, error) = nil var ConfirmClassicPassportTotpSecurityModel *fireback.SecurityModel = nil @@ -1239,6 +1201,7 @@ var OsLoginAuthenticateActionDef fireback.Module3Action = fireback.Module3Action func AbacCustomActions() []fireback.Module3Action { routes := []fireback.Module3Action{ //// Let's add actions for emi acts + AcceptInviteActionDef, ConfirmClassicPassportTotpActionDef, ChangePasswordActionDef, UserPassportsActionDef, @@ -1252,29 +1215,6 @@ func AbacCustomActions() []fireback.Module3Action { CheckPassportMethodsActionDef, OsLoginAuthenticateActionDef, /// End for emi actions - { - Method: "POST", - Url: "/user/invitation/accept", - SecurityModel: AcceptInviteSecurityModel, - Name: "acceptInvite", - Description: "Use it when user accepts an invitation, and it will complete the joining process", - Handlers: []gin.HandlerFunc{ - func(c *gin.Context) { - // POST_ONE - post - fireback.HttpPostEntity(c, AcceptInviteActionFn) - }, - }, - Format: "POST_ONE", - Action: AcceptInviteActionFn, - ResponseEntity: string(""), - Out: &fireback.Module3ActionBody{ - Entity: "", - }, - RequestEntity: &AcceptInviteActionReqDto{}, - In: &fireback.Module3ActionBody{ - Entity: "AcceptInviteActionReqDto", - }, - }, { Method: "POST", Url: "/passport/via-oauth", @@ -1521,7 +1461,6 @@ func AbacCustomActions() []fireback.Module3Action { } var AbacCustomActionsCli = []cli.Command{ - AcceptInviteActionCmd, OauthAuthenticateActionCmd, UserInvitationsActionCmd, QueryUserRoleWorkspacesActionCmd, @@ -1548,6 +1487,7 @@ var AbacCliActionsBundle = &fireback.CliActionsBundle{ Usage: `Fireback ABAC module provides user authentication, basic support for most projects, including advanced role, permission module on top of fireback core module. Using this module is not essential to create fireback projects, but provides a great possibility to avoid building most user management flow. Some other helpers, such as timezone are added here.`, // Here we will include entities actions, as well as module level actions Subcommands: cli.Commands{ + AcceptInviteActionDef.ToCli(), ConfirmClassicPassportTotpActionDef.ToCli(), ChangePasswordActionDef.ToCli(), UserPassportsActionDef.ToCli(), @@ -1560,7 +1500,6 @@ var AbacCliActionsBundle = &fireback.CliActionsBundle{ QueryWorkspaceTypesPubliclyActionDef.ToCli(), CheckPassportMethodsActionDef.ToCli(), OsLoginAuthenticateActionDef.ToCli(), - AcceptInviteActionCmd, OauthAuthenticateActionCmd, UserInvitationsActionCmd, QueryUserRoleWorkspacesActionCmd, diff --git a/modules/abac/AbacModule3.yml b/modules/abac/AbacModule3.yml index bfdf2ee43..4479b244e 100644 --- a/modules/abac/AbacModule3.yml +++ b/modules/abac/AbacModule3.yml @@ -172,6 +172,26 @@ dtom: type: string? acts: + - name: AcceptInvite + method: post + url: /user/invitation/accept + description: Use it when user accepts an invitation, and it will complete the joining process + security: + resolveStrategy: user + + out: + envelope: GResponse + fields: + - name: accepted + type: bool + in: + fields: + - name: invitationUniqueId + type: string + tags: + validate: required + description: The invitation id which will be used to process + - name: ConfirmClassicPassportTotp description: When user requires to setup the totp for an specifc passport, they can use this endpoint to confirm @@ -553,20 +573,6 @@ acts: out: dto: UserSessionDto actions: - - name: acceptInvite - method: post - url: /user/invitation/accept - description: Use it when user accepts an invitation, and it will complete the joining process - security: - resolveStrategy: user - - in: - fields: - - name: invitationUniqueId - type: string - validate: required - description: The invitation id which will be used to process - - name: oauthAuthenticate description: When a token is got from a oauth service such as google, we send the token here to authenticate the user. @@ -596,17 +602,6 @@ actions: primitive: string description: The next possible action which is suggested. - # - name: addPassport - # url: /users/passports/add - # security: - # resolveStrategy: user - # method: post - # in: - # fields: - # - name: value - # description: - # The new passport value - - name: userInvitations description: Shows the invitations for an specific user, if the invited member already has a account. diff --git a/modules/abac/AcceptInviteAction.dyno.go b/modules/abac/AcceptInviteAction.dyno.go new file mode 100644 index 000000000..57ccb17ef --- /dev/null +++ b/modules/abac/AcceptInviteAction.dyno.go @@ -0,0 +1,311 @@ +package abac + +import ( + "bytes" + "encoding/json" + "fmt" + "github.com/gin-gonic/gin" + "github.com/torabian/emi/emigo" + "github.com/urfave/cli" + "io" + "net/http" + "net/url" +) + +/** +* Action to communicate with the action AcceptInviteAction + */ +func AcceptInviteActionMeta() struct { + Name string + CliName string + URL string + Method string + Description string +} { + return struct { + Name string + CliName string + URL string + Method string + Description string + }{ + Name: "AcceptInviteAction", + CliName: "accept-invite-action", + URL: "/user/invitation/accept", + Method: "POST", + Description: `Use it when user accepts an invitation, and it will complete the joining process`, + } +} +func GetAcceptInviteActionReqCliFlags(prefix string) []emigo.CliFlag { + return []emigo.CliFlag{ + { + Name: prefix + "invitation-unique-id", + Type: "string", + }, + } +} +func CastAcceptInviteActionReqFromCli(c emigo.CliCastable) AcceptInviteActionReq { + data := AcceptInviteActionReq{} + if c.IsSet("invitation-unique-id") { + data.InvitationUniqueId = c.String("invitation-unique-id") + } + return data +} + +// The base class definition for acceptInviteActionReq +type AcceptInviteActionReq struct { + // The invitation id which will be used to process + InvitationUniqueId string `json:"invitationUniqueId" validate:"required" yaml:"invitationUniqueId"` +} + +func (x *AcceptInviteActionReq) Json() string { + if x != nil { + str, _ := json.MarshalIndent(x, "", " ") + return string(str) + } + return "" +} +func GetAcceptInviteActionResCliFlags(prefix string) []emigo.CliFlag { + return []emigo.CliFlag{ + { + Name: prefix + "accepted", + Type: "bool", + }, + } +} +func CastAcceptInviteActionResFromCli(c emigo.CliCastable) AcceptInviteActionRes { + data := AcceptInviteActionRes{} + if c.IsSet("accepted") { + data.Accepted = bool(c.Bool("accepted")) + } + return data +} + +// The base class definition for acceptInviteActionRes +type AcceptInviteActionRes struct { + Accepted bool `json:"accepted" yaml:"accepted"` +} + +func (x *AcceptInviteActionRes) Json() string { + if x != nil { + str, _ := json.MarshalIndent(x, "", " ") + return string(str) + } + return "" +} + +type AcceptInviteActionResponse struct { + StatusCode int + Headers map[string]string + Payload interface{} +} + +func (x *AcceptInviteActionResponse) SetContentType(contentType string) *AcceptInviteActionResponse { + if x.Headers == nil { + x.Headers = make(map[string]string) + } + x.Headers["Content-Type"] = contentType + return x +} +func (x *AcceptInviteActionResponse) AsStream(r io.Reader, contentType string) *AcceptInviteActionResponse { + x.Payload = r + x.SetContentType(contentType) + return x +} +func (x *AcceptInviteActionResponse) AsJSON(payload any) *AcceptInviteActionResponse { + x.Payload = payload + x.SetContentType("application/json") + return x +} +func (x *AcceptInviteActionResponse) AsHTML(payload string) *AcceptInviteActionResponse { + x.Payload = payload + x.SetContentType("text/html; charset=utf-8") + return x +} +func (x *AcceptInviteActionResponse) AsBytes(payload []byte) *AcceptInviteActionResponse { + x.Payload = payload + x.SetContentType("application/octet-stream") + return x +} +func (x AcceptInviteActionResponse) GetStatusCode() int { + return x.StatusCode +} +func (x AcceptInviteActionResponse) GetRespHeaders() map[string]string { + return x.Headers +} +func (x AcceptInviteActionResponse) GetPayload() interface{} { + return x.Payload +} + +// AcceptInviteActionRaw registers a raw Gin route for the AcceptInviteAction action. +// This gives the developer full control over middleware, handlers, and response handling. +func AcceptInviteActionRaw(r *gin.Engine, handlers ...gin.HandlerFunc) { + meta := AcceptInviteActionMeta() + r.Handle(meta.Method, meta.URL, handlers...) +} + +type AcceptInviteActionRequestSig = func(c AcceptInviteActionRequest) (*AcceptInviteActionResponse, error) + +// AcceptInviteActionHandler returns the HTTP method, route URL, and a typed Gin handler for the AcceptInviteAction action. +// Developers implement their business logic as a function that receives a typed request object +// and returns either an *ActionResponse or nil. JSON marshalling, headers, and errors are handled automatically. +func AcceptInviteActionHandler( + handler AcceptInviteActionRequestSig, +) (method, url string, h gin.HandlerFunc) { + meta := AcceptInviteActionMeta() + return meta.Method, meta.URL, func(m *gin.Context) { + var body AcceptInviteActionReq + if err := m.ShouldBindJSON(&body); err != nil { + m.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON: " + err.Error()}) + return + } + // Build typed request wrapper + req := AcceptInviteActionRequest{ + Body: body, + QueryParams: m.Request.URL.Query(), + Headers: m.Request.Header, + GinCtx: m, + } + resp, err := handler(req) + if err != nil { + m.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + // If the handler returned nil (and no error), it means the response was handled manually. + if resp == nil { + return + } + // Apply headers + for k, v := range resp.Headers { + m.Header(k, v) + } + // Apply status and payload + status := resp.StatusCode + if status == 0 { + status = http.StatusOK + } + if resp.Payload != nil { + m.JSON(status, resp.Payload) + } else { + m.Status(status) + } + } +} + +// AcceptInviteAction is a high-level convenience wrapper around AcceptInviteActionHandler. +// It automatically constructs and registers the typed route on the Gin engine. +// Use this when you don't need custom middleware or route grouping. +func AcceptInviteActionGin(r gin.IRoutes, handler AcceptInviteActionRequestSig) { + method, url, h := AcceptInviteActionHandler(handler) + r.Handle(method, url, h) +} + +/** + * Query parameters for AcceptInviteAction + */ +// Query wrapper with private fields +type AcceptInviteActionQuery struct { + values url.Values + mapped map[string]interface{} + // Typesafe fields +} + +func AcceptInviteActionQueryFromString(rawQuery string) AcceptInviteActionQuery { + v := AcceptInviteActionQuery{} + values, _ := url.ParseQuery(rawQuery) + mapped := map[string]interface{}{} + if result, err := emigo.UnmarshalQs(rawQuery); err == nil { + mapped = result + } + decoder, err := emigo.NewDecoder(&emigo.DecoderConfig{ + TagName: "json", // reuse json tags + WeaklyTypedInput: true, // "1" -> int, "true" -> bool + Result: &v, + }) + if err == nil { + _ = decoder.Decode(mapped) + } + v.values = values + v.mapped = mapped + return v +} +func AcceptInviteActionQueryFromGin(c *gin.Context) AcceptInviteActionQuery { + return AcceptInviteActionQueryFromString(c.Request.URL.RawQuery) +} +func AcceptInviteActionQueryFromHttp(r *http.Request) AcceptInviteActionQuery { + return AcceptInviteActionQueryFromString(r.URL.RawQuery) +} +func (q AcceptInviteActionQuery) Values() url.Values { + return q.values +} +func (q AcceptInviteActionQuery) Mapped() map[string]interface{} { + return q.mapped +} +func (q *AcceptInviteActionQuery) SetValues(v url.Values) { + q.values = v +} +func (q *AcceptInviteActionQuery) SetMapped(m map[string]interface{}) { + q.mapped = m +} + +type AcceptInviteActionRequest struct { + Body AcceptInviteActionReq + QueryParams url.Values + Headers http.Header + GinCtx *gin.Context + CliCtx *cli.Context +} +type AcceptInviteActionResult struct { + resp *http.Response // embed original response + Payload interface{} +} + +func AcceptInviteActionCall( + req AcceptInviteActionRequest, + config *emigo.APIClient, // optional pre-built request +) (*AcceptInviteActionResult, error) { + var httpReq *http.Request + if config == nil || config.Httpr == nil { + meta := AcceptInviteActionMeta() + baseURL := meta.URL + // Build final URL with query string + u, err := url.Parse(baseURL) + if err != nil { + return nil, err + } + // if UrlValues present, encode and append + if len(req.QueryParams) > 0 { + u.RawQuery = req.QueryParams.Encode() + } + bodyBytes, err := json.Marshal(req.Body) + if err != nil { + return nil, err + } + req0, err := http.NewRequest(meta.Method, u.String(), bytes.NewReader(bodyBytes)) + if err != nil { + return nil, err + } + httpReq = req0 + } else { + httpReq = config.Httpr + } + httpReq.Header = req.Headers + resp, err := http.DefaultClient.Do(httpReq) + if err != nil { + return nil, err + } + var result AcceptInviteActionResult + result.resp = resp + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return &result, err + } + if resp.StatusCode >= 400 { + return &result, fmt.Errorf("request failed: %s", respBody) + } + if err := json.Unmarshal(respBody, &result.Payload); err != nil { + return &result, err + } + return &result, nil +} diff --git a/modules/abac/AcceptInviteAction.go b/modules/abac/AcceptInviteAction.go index ec366cf20..e5db469b6 100644 --- a/modules/abac/AcceptInviteAction.go +++ b/modules/abac/AcceptInviteAction.go @@ -7,13 +7,11 @@ import ( func init() { // Override the implementation with our actual code. - AcceptInviteActionImp = AcceptInviteAction + AcceptInviteImpl = AcceptInviteAction } -func AcceptInviteAction( - req *AcceptInviteActionReqDto, - q fireback.QueryDSL) (string, - *fireback.IError, -) { + +func AcceptInviteAction(c AcceptInviteActionRequest, q fireback.QueryDSL) (*AcceptInviteActionResponse, error) { + req := c.Body // First of all, we will find the invitation and gather some information. q.UniqueId = req.InvitationUniqueId @@ -21,11 +19,11 @@ func AcceptInviteAction( invite, err := WorkspaceInviteActions.GetOne(q) if err != nil { - return "", err + return nil, err } if invite == nil { - return "", fireback.Create401Error(&AbacMessages.InvitationNotFound, []string{}) + return nil, fireback.Create401Error(&AbacMessages.InvitationNotFound, []string{}) } err2d := fireback.GetDbRef().Transaction(func(tx *gorm.DB) error { @@ -69,9 +67,9 @@ func AcceptInviteAction( }) if err2d != nil { - return "", err2d.(*fireback.IError) + return nil, err2d.(*fireback.IError) } // Implement the logic here. - return "", nil + return nil, nil } diff --git a/modules/abac/UserEntity.go b/modules/abac/UserEntity.go index 02ba4aadf..fe315f021 100644 --- a/modules/abac/UserEntity.go +++ b/modules/abac/UserEntity.go @@ -103,7 +103,7 @@ func init() { UserCliCommands = append( UserCliCommands, TokenCliFn(), - AcceptInviteActionCmd, + AcceptInviteActionDef.ToCli(), UserInvitationsActionCmd, ) } diff --git a/modules/fireback/codegen/react-new/src/modules/fireback/modules/selfservice/user-invitations/UserInvitationList.tsx b/modules/fireback/codegen/react-new/src/modules/fireback/modules/selfservice/user-invitations/UserInvitationList.tsx index de3b5839c..fc8256847 100644 --- a/modules/fireback/codegen/react-new/src/modules/fireback/modules/selfservice/user-invitations/UserInvitationList.tsx +++ b/modules/fireback/codegen/react-new/src/modules/fireback/modules/selfservice/user-invitations/UserInvitationList.tsx @@ -7,14 +7,14 @@ import { userInvitationColumns } from "./UserInvitationColumns"; import { ModalContext } from "@/modules/fireback/components/modal/Modal"; import { useContext } from "react"; import { UserInvitationsQueryColumns } from "@/modules/fireback/sdk/modules/abac/UserInvitationsQueryColumns"; -import { usePostUserInvitationAccept } from "@/modules/fireback/sdk/modules/abac/usePostUserInvitationAccept"; +import { useAcceptInviteAction, AcceptInviteActionReq } from "@/modules/fireback/sdk/modules/abac/AcceptInvite"; export const UserInvitationList = () => { const s = useS(strings); const useModal = useContext(ModalContext); - const { submit: acceptInvite } = usePostUserInvitationAccept(); + const mutation = useAcceptInviteAction(); const onAccept = (dto: UserInvitationsQueryColumns) => { useModal.openModal({ @@ -22,8 +22,10 @@ export const UserInvitationList = () => { confirmButtonLabel: s.acceptBtn, component: () =>
{s.confirmAcceptDescription}
, onSubmit: async () => { - return acceptInvite({ invitationUniqueId: dto.uniqueId }).then( - (res) => {} + return mutation.mutateAsync(new AcceptInviteActionReq({ invitationUniqueId: dto.uniqueId })).then( + (res) => { + alert("Successful.") + } ); }, }) diff --git a/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/AbacActionsDto.ts b/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/AbacActionsDto.ts index 53fab5345..2d7a572a4 100644 --- a/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/AbacActionsDto.ts +++ b/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/AbacActionsDto.ts @@ -24,15 +24,6 @@ import { */ public capabilities?: string[] | null; } -export class AcceptInviteActionReqDto { - /** - The invitation id which will be used to process - */ - public invitationUniqueId?: string | null; -public static Fields = { - invitationUniqueId: 'invitationUniqueId', -} -} export class OauthAuthenticateActionReqDto { /** The token that Auth2 provider returned to the front-end, which will be used to validate the backend diff --git a/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/AcceptInvite.ts b/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/AcceptInvite.ts new file mode 100644 index 000000000..ee5139179 --- /dev/null +++ b/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/AcceptInvite.ts @@ -0,0 +1,410 @@ +import { GResponse } from "../../sdk/envelopes/index"; +import { buildUrl } from "../../sdk/common/buildUrl"; +import { + fetchx, + handleFetchResponse, + type FetchxContext, + type PartialDeep, + type TypedRequestInit, + type TypedResponse, +} from "../../sdk/common/fetchx"; +import { type UseMutationOptions, useMutation } from "react-query"; +import { useFetchxContext } from "../../sdk/react/useFetchx"; +import { useState } from "react"; +/** + * Action to communicate with the action AcceptInvite + */ +export type AcceptInviteActionOptions = { + queryKey?: unknown[]; + qs?: URLSearchParams; +}; +export type AcceptInviteActionMutationOptions = Omit< + UseMutationOptions, + "mutationFn" +> & + AcceptInviteActionOptions & { + ctx?: FetchxContext; + onMessage?: (ev: MessageEvent) => void; + overrideUrl?: string; + headers?: Headers; + } & Partial<{ + creatorFn: (item: unknown) => AcceptInviteActionRes; + }>; +export const useAcceptInviteAction = ( + options?: AcceptInviteActionMutationOptions, +) => { + const globalCtx = useFetchxContext(); + const ctx = options?.ctx ?? globalCtx ?? undefined; + const [isCompleted, setCompleteState] = useState(false); + const [response, setResponse] = useState>(); + const fn = (body: AcceptInviteActionReq) => { + setCompleteState(false); + return AcceptInviteAction.Fetch( + { + body, + headers: options?.headers, + }, + { + creatorFn: options?.creatorFn, + qs: options?.qs, + ctx, + onMessage: options?.onMessage, + overrideUrl: options?.overrideUrl, + }, + ).then((x) => { + x.done.then(() => { + setCompleteState(true); + }); + setResponse(x.response); + return x.response.result; + }); + }; + const result = useMutation({ + mutationFn: fn, + ...(options || {}), + }); + return { + ...result, + isCompleted, + response, + }; +}; +/** + * AcceptInviteAction + */ +export class AcceptInviteAction { + // + static URL = "/user/invitation/accept"; + static NewUrl = (qs?: URLSearchParams) => + buildUrl(AcceptInviteAction.URL, undefined, qs); + static Method = "post"; + static Fetch$ = async ( + qs?: URLSearchParams, + ctx?: FetchxContext, + init?: TypedRequestInit, + overrideUrl?: string, + ) => { + return fetchx< + GResponse, + AcceptInviteActionReq, + unknown + >( + overrideUrl ?? AcceptInviteAction.NewUrl(qs), + { + method: AcceptInviteAction.Method, + ...(init || {}), + }, + ctx, + ); + }; + static Fetch = async ( + init?: TypedRequestInit, + { + creatorFn, + qs, + ctx, + onMessage, + overrideUrl, + }: { + creatorFn?: ((item: unknown) => AcceptInviteActionRes) | undefined; + qs?: URLSearchParams; + ctx?: FetchxContext; + onMessage?: (ev: MessageEvent) => void; + overrideUrl?: string; + } = { + creatorFn: (item) => new AcceptInviteActionRes(item), + }, + ) => { + creatorFn = creatorFn || ((item) => new AcceptInviteActionRes(item)); + const res = await AcceptInviteAction.Fetch$(qs, ctx, init, overrideUrl); + return handleFetchResponse( + res, + (data) => { + const resp = new GResponse(); + if (creatorFn) { + resp.setCreator(creatorFn); + } + resp.inject(data); + return resp; + }, + onMessage, + init?.signal, + ); + }; + static Definition = { + name: "AcceptInvite", + url: "/user/invitation/accept", + method: "post", + description: + "Use it when user accepts an invitation, and it will complete the joining process", + in: { + fields: [ + { + name: "invitationUniqueId", + description: "The invitation id which will be used to process", + type: "string", + tags: { + validate: "required", + }, + }, + ], + }, + out: { + envelope: "GResponse", + fields: [ + { + name: "accepted", + type: "bool", + }, + ], + }, + }; +} +/** + * The base class definition for acceptInviteActionReq + **/ +export class AcceptInviteActionReq { + /** + * The invitation id which will be used to process + * @type {string} + **/ + #invitationUniqueId: string = ""; + /** + * The invitation id which will be used to process + * @returns {string} + **/ + get invitationUniqueId() { + return this.#invitationUniqueId; + } + /** + * The invitation id which will be used to process + * @type {string} + **/ + set invitationUniqueId(value: string) { + this.#invitationUniqueId = String(value); + } + setInvitationUniqueId(value: string) { + this.invitationUniqueId = value; + return this; + } + constructor(data: unknown = undefined) { + if (data === null || data === undefined) { + return; + } + if (typeof data === "string") { + this.applyFromObject(JSON.parse(data)); + } else if (this.#isJsonAppliable(data)) { + this.applyFromObject(data); + } else { + throw new Error( + "Instance cannot be created on an unknown value, check the content being passed. got: " + + typeof data, + ); + } + } + #isJsonAppliable(obj: unknown) { + const g = globalThis as unknown as { Buffer: any; Blob: any }; + const isBuffer = + typeof g.Buffer !== "undefined" && + typeof g.Buffer.isBuffer === "function" && + g.Buffer.isBuffer(obj); + const isBlob = typeof g.Blob !== "undefined" && obj instanceof g.Blob; + return ( + obj && + typeof obj === "object" && + !Array.isArray(obj) && + !isBuffer && + !(obj instanceof ArrayBuffer) && + !isBlob + ); + } + /** + * casts the fields of a javascript object into the class properties one by one + **/ + applyFromObject(data = {}) { + const d = data as Partial; + if (d.invitationUniqueId !== undefined) { + this.invitationUniqueId = d.invitationUniqueId; + } + } + /** + * Special toJSON override, since the field are private, + * Json stringify won't see them unless we mention it explicitly. + **/ + toJSON() { + return { + invitationUniqueId: this.#invitationUniqueId, + }; + } + toString() { + return JSON.stringify(this); + } + static get Fields() { + return { + invitationUniqueId: "invitationUniqueId", + }; + } + /** + * Creates an instance of AcceptInviteActionReq, and possibleDtoObject + * needs to satisfy the type requirement fully, otherwise typescript compile would + * be complaining. + **/ + static from(possibleDtoObject: AcceptInviteActionReqType) { + return new AcceptInviteActionReq(possibleDtoObject); + } + /** + * Creates an instance of AcceptInviteActionReq, and partialDtoObject + * needs to satisfy the type, but partially, and rest of the content would + * be constructed according to data types and nullability. + **/ + static with(partialDtoObject: PartialDeep) { + return new AcceptInviteActionReq(partialDtoObject); + } + copyWith( + partial: PartialDeep, + ): InstanceType { + return new AcceptInviteActionReq({ ...this.toJSON(), ...partial }); + } + clone(): InstanceType { + return new AcceptInviteActionReq(this.toJSON()); + } +} +export abstract class AcceptInviteActionReqFactory { + abstract create(data: unknown): AcceptInviteActionReq; +} +/** + * The base type definition for acceptInviteActionReq + **/ +export type AcceptInviteActionReqType = { + /** + * The invitation id which will be used to process + * @type {string} + **/ + invitationUniqueId: string; +}; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace AcceptInviteActionReqType {} +/** + * The base class definition for acceptInviteActionRes + **/ +export class AcceptInviteActionRes { + /** + * + * @type {boolean} + **/ + #accepted!: boolean; + /** + * + * @returns {boolean} + **/ + get accepted() { + return this.#accepted; + } + /** + * + * @type {boolean} + **/ + set accepted(value: boolean) { + this.#accepted = Boolean(value); + } + setAccepted(value: boolean) { + this.accepted = value; + return this; + } + constructor(data: unknown = undefined) { + if (data === null || data === undefined) { + return; + } + if (typeof data === "string") { + this.applyFromObject(JSON.parse(data)); + } else if (this.#isJsonAppliable(data)) { + this.applyFromObject(data); + } else { + throw new Error( + "Instance cannot be created on an unknown value, check the content being passed. got: " + + typeof data, + ); + } + } + #isJsonAppliable(obj: unknown) { + const g = globalThis as unknown as { Buffer: any; Blob: any }; + const isBuffer = + typeof g.Buffer !== "undefined" && + typeof g.Buffer.isBuffer === "function" && + g.Buffer.isBuffer(obj); + const isBlob = typeof g.Blob !== "undefined" && obj instanceof g.Blob; + return ( + obj && + typeof obj === "object" && + !Array.isArray(obj) && + !isBuffer && + !(obj instanceof ArrayBuffer) && + !isBlob + ); + } + /** + * casts the fields of a javascript object into the class properties one by one + **/ + applyFromObject(data = {}) { + const d = data as Partial; + if (d.accepted !== undefined) { + this.accepted = d.accepted; + } + } + /** + * Special toJSON override, since the field are private, + * Json stringify won't see them unless we mention it explicitly. + **/ + toJSON() { + return { + accepted: this.#accepted, + }; + } + toString() { + return JSON.stringify(this); + } + static get Fields() { + return { + accepted: "accepted", + }; + } + /** + * Creates an instance of AcceptInviteActionRes, and possibleDtoObject + * needs to satisfy the type requirement fully, otherwise typescript compile would + * be complaining. + **/ + static from(possibleDtoObject: AcceptInviteActionResType) { + return new AcceptInviteActionRes(possibleDtoObject); + } + /** + * Creates an instance of AcceptInviteActionRes, and partialDtoObject + * needs to satisfy the type, but partially, and rest of the content would + * be constructed according to data types and nullability. + **/ + static with(partialDtoObject: PartialDeep) { + return new AcceptInviteActionRes(partialDtoObject); + } + copyWith( + partial: PartialDeep, + ): InstanceType { + return new AcceptInviteActionRes({ ...this.toJSON(), ...partial }); + } + clone(): InstanceType { + return new AcceptInviteActionRes(this.toJSON()); + } +} +export abstract class AcceptInviteActionResFactory { + abstract create(data: unknown): AcceptInviteActionRes; +} +/** + * The base type definition for acceptInviteActionRes + **/ +export type AcceptInviteActionResType = { + /** + * + * @type {boolean} + **/ + accepted: boolean; +}; +// eslint-disable-next-line @typescript-eslint/no-namespace +export namespace AcceptInviteActionResType {} diff --git a/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/ConfirmClassicPassportTotp.ts b/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/ConfirmClassicPassportTotp.ts index 6282acb5d..623466639 100644 --- a/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/ConfirmClassicPassportTotp.ts +++ b/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/ConfirmClassicPassportTotp.ts @@ -1,3 +1,4 @@ +import { GResponse } from "../../sdk/envelopes/index"; import { UserSessionDto } from "./UserSessionDto"; import { buildUrl } from "../../sdk/common/buildUrl"; import { @@ -86,7 +87,7 @@ export class ConfirmClassicPassportTotpAction { overrideUrl?: string, ) => { return fetchx< - ConfirmClassicPassportTotpActionRes, + GResponse, ConfirmClassicPassportTotpActionReq, unknown >( @@ -128,7 +129,14 @@ export class ConfirmClassicPassportTotpAction { ); return handleFetchResponse( res, - (item) => (creatorFn ? creatorFn(item) : item), + (data) => { + const resp = new GResponse(); + if (creatorFn) { + resp.setCreator(creatorFn); + } + resp.inject(data); + return resp; + }, onMessage, init?.signal, ); @@ -171,6 +179,7 @@ export class ConfirmClassicPassportTotpAction { ], }, out: { + envelope: "GResponse", fields: [ { name: "session", diff --git a/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/usePostUserInvitationAccept.ts b/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/usePostUserInvitationAccept.ts deleted file mode 100644 index 797302535..000000000 --- a/modules/fireback/codegen/react-new/src/modules/fireback/sdk/modules/abac/usePostUserInvitationAccept.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* -* Generated by fireback 1.2.5 -* Written by Ali Torabi. -* The code is generated for react-query@v3.39.3 -* Checkout the repository for licenses and contribution: https://github.com/torabian/fireback -*/ -import { type FormikHelpers } from "formik"; -import { useContext, useState, useRef } from "react"; -import { useMutation } from "react-query"; -import { - execApiFn, - type IResponse, - mutationErrorsToFormik, - type IResponseList -} from "../../core/http-tools"; -import { - RemoteQueryContext, - type UseRemoteQuery, - queryBeforeSend, -} from "../../core/react-tools"; - import { - AcceptInviteActionReqDto, - } from "../abac/AbacActionsDto" -export function usePostUserInvitationAccept( - props?: UseRemoteQuery & { - } -) { - let {queryClient, query, execFnOverride} = props || {}; - query = query || {} - const { options, execFn } = useContext(RemoteQueryContext); - // Calculare the function which will do the remote calls. - // We consider to use global override, this specific override, or default which - // comes with the sdk. - const rpcFn = execFnOverride - ? execFnOverride(options) - : execFn - ? execFn(options) - : execApiFn(options); - // Url of the remote affix. - const url = "/user/invitation/accept".substr(1); - let computedUrl = `${url}?${new URLSearchParams( - queryBeforeSend(query) - ).toString()}`; - let completeRouteUrls = true; - // Attach the details of the request to the fn - const fn = (body: any) => rpcFn("POST", computedUrl, body); - const mutation = useMutation< - IResponse, - IResponse, - Partial - >(fn); - // Only entities are having a store in front-end - const fnUpdater = ( - data: IResponseList | undefined, - item: IResponse - ) => { - if (!data) { - return { - data: { items: [] }, - }; - } - // To me it seems this is not a good or any correct strategy to update the store. - // When we are posting, we want to add it there, that's it. Not updating it. - // We have patch, but also posting with ID is possible. - if (data.data && item?.data) { - data.data.items = [item.data, ...(data?.data?.items || [])]; - } - return data; - }; - const submit = ( - values: Partial, - formikProps?: FormikHelpers> - ): Promise> => { - return new Promise((resolve, reject) => { - mutation.mutate(values, { - onSuccess(response: IResponse) { - queryClient?.setQueryData>( - "string", - (data) => fnUpdater(data, response) as any - ); - resolve(response); - }, - onError(error: any) { - formikProps?.setErrors(mutationErrorsToFormik(error)); - reject(error); - }, - }); - }); - }; - return { mutation, submit, fnUpdater }; -}