mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2025-12-31 04:00:03 +01:00
(feature): account patch operations init and webapi cleanup
This commit is contained in:
parent
3e0c25158e
commit
8aa535447e
@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { ApiRoutes, GetApiRoute } from '@/ApiRoutes'
|
import { ApiRoutes, GetApiRoute } from '@/ApiRoutes'
|
||||||
import { httpService } from '@/services/httpService'
|
import { httpService } from '@/services/HttpService'
|
||||||
import { FormEvent, useEffect, useRef, useState } from 'react'
|
import { FormEvent, useEffect, useRef, useState } from 'react'
|
||||||
import {
|
import {
|
||||||
CustomButton,
|
CustomButton,
|
||||||
@ -10,7 +10,10 @@ import {
|
|||||||
CustomInput,
|
CustomInput,
|
||||||
CustomRadioGroup
|
CustomRadioGroup
|
||||||
} from '@/controls'
|
} from '@/controls'
|
||||||
import { GetAccountResponse } from '@/models/letsEncryptServer/account/responses/GetAccountResponse'
|
import {
|
||||||
|
GetAccountResponse,
|
||||||
|
toCacheAccount
|
||||||
|
} from '@/models/letsEncryptServer/account/responses/GetAccountResponse'
|
||||||
import { deepCopy, enumToArray } from '../functions'
|
import { deepCopy, enumToArray } from '../functions'
|
||||||
import { CacheAccount } from '@/entities/CacheAccount'
|
import { CacheAccount } from '@/entities/CacheAccount'
|
||||||
import { ChallengeTypes } from '@/entities/ChallengeTypes'
|
import { ChallengeTypes } from '@/entities/ChallengeTypes'
|
||||||
@ -42,22 +45,7 @@ export default function Page() {
|
|||||||
if (!gatAccountsResult.isSuccess) return
|
if (!gatAccountsResult.isSuccess) return
|
||||||
|
|
||||||
gatAccountsResult.data?.forEach((account) => {
|
gatAccountsResult.data?.forEach((account) => {
|
||||||
newAccounts.push({
|
newAccounts.push(toCacheAccount(account))
|
||||||
accountId: account.accountId,
|
|
||||||
isDisabled: account.isDisabled,
|
|
||||||
description: account.description,
|
|
||||||
contacts: account.contacts.map((contact) => contact),
|
|
||||||
challengeType: account.challengeType,
|
|
||||||
hostnames:
|
|
||||||
account.hostnames?.map((hostname) => ({
|
|
||||||
hostname: hostname.hostname,
|
|
||||||
expires: new Date(hostname.expires),
|
|
||||||
isUpcomingExpire: hostname.isUpcomingExpire,
|
|
||||||
isDisabled: hostname.isDisabled
|
|
||||||
})) ?? [],
|
|
||||||
isStaging: account.isStaging,
|
|
||||||
isEditMode: false
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
setAccounts(newAccounts)
|
setAccounts(newAccounts)
|
||||||
@ -144,10 +132,6 @@ export default function Page() {
|
|||||||
title="Challenge Type"
|
title="Challenge Type"
|
||||||
enumType={ChallengeTypes}
|
enumType={ChallengeTypes}
|
||||||
selectedValue={account.challengeType}
|
selectedValue={account.challengeType}
|
||||||
onChange={(option) =>
|
|
||||||
//handleChallengeTypeChange(account.accountId, option)
|
|
||||||
console.log('')
|
|
||||||
}
|
|
||||||
disabled={true}
|
disabled={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -206,9 +190,12 @@ export default function Page() {
|
|||||||
{editingAccount && (
|
{editingAccount && (
|
||||||
<AccountEdit
|
<AccountEdit
|
||||||
account={editingAccount}
|
account={editingAccount}
|
||||||
setAccount={setEditingAccount}
|
|
||||||
onCancel={() => setEditingAccount(null)}
|
onCancel={() => setEditingAccount(null)}
|
||||||
onDelete={deleteAccount}
|
onDelete={deleteAccount}
|
||||||
|
onSubmit={(account) => {
|
||||||
|
setEditingAccount(null)
|
||||||
|
handleAccountUpdate(account)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</OffCanvas>
|
</OffCanvas>
|
||||||
|
|||||||
@ -18,12 +18,12 @@ import { deepCopy } from '../../functions'
|
|||||||
import {
|
import {
|
||||||
PostAccountRequest,
|
PostAccountRequest,
|
||||||
validatePostAccountRequest
|
validatePostAccountRequest
|
||||||
} from '@/models/letsEncryptServer/certsFlow/PostAccountRequest'
|
} from '@/models/letsEncryptServer/account/requests/PostAccountRequest'
|
||||||
import { useAppDispatch } from '@/redux/store'
|
import { useAppDispatch } from '@/redux/store'
|
||||||
import { showToast } from '@/redux/slices/toastSlice'
|
import { showToast } from '@/redux/slices/toastSlice'
|
||||||
import { ChallengeTypes } from '@/entities/ChallengeTypes'
|
import { ChallengeTypes } from '@/entities/ChallengeTypes'
|
||||||
import { GetAccountResponse } from '@/models/letsEncryptServer/account/responses/GetAccountResponse'
|
import { GetAccountResponse } from '@/models/letsEncryptServer/account/responses/GetAccountResponse'
|
||||||
import { httpService } from '@/services/httpService'
|
import { httpService } from '@/services/HttpService'
|
||||||
import { ApiRoutes, GetApiRoute } from '@/ApiRoutes'
|
import { ApiRoutes, GetApiRoute } from '@/ApiRoutes'
|
||||||
import { PageContainer } from '@/components/pageContainer'
|
import { PageContainer } from '@/components/pageContainer'
|
||||||
|
|
||||||
|
|||||||
@ -71,27 +71,41 @@ const CustomSelect: React.FC<CustomSelectProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex flex-col ${className}`} ref={selectBoxRef}>
|
<div className={`flex flex-col ${className}`} ref={selectBoxRef}>
|
||||||
{title && <label className="mb-1">{title}</label>}
|
{title && <label className="mb-1 text-gray-700">{title}</label>}
|
||||||
<div
|
<div
|
||||||
className={`relative w-64 ${disabled ? 'opacity-50 cursor-not-allowed' : ''}`}
|
className={`relative w-64 ${disabled ? 'opacity-50 cursor-not-allowed' : readOnly ? 'cursor-not-allowed' : ''}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`p-2 border ${disabled ? 'border-gray-200' : 'border-gray-300'} rounded cursor-pointer flex justify-between items-center ${disabled ? 'cursor-not-allowed' : ''} ${selectBoxClassName}`}
|
className={`p-2 border rounded flex justify-between items-center ${
|
||||||
|
disabled
|
||||||
|
? 'border-gray-200 bg-gray-100 text-gray-500'
|
||||||
|
: readOnly
|
||||||
|
? 'border-gray-300 bg-white cursor-not-allowed'
|
||||||
|
: 'border-gray-300 bg-white cursor-pointer hover:border-gray-400'
|
||||||
|
} ${selectBoxClassName}`}
|
||||||
onClick={handleToggle}
|
onClick={handleToggle}
|
||||||
>
|
>
|
||||||
|
<span className={`${disabled ? 'text-gray-500' : ''}`}>
|
||||||
{selectedOption ? selectedOption.label : 'Select an option'}
|
{selectedOption ? selectedOption.label : 'Select an option'}
|
||||||
|
</span>
|
||||||
|
|
||||||
{isOpen ? (
|
{isOpen ? (
|
||||||
<FaChevronUp className="ml-2" />
|
<FaChevronUp
|
||||||
|
className={`ml-2 ${disabled ? 'text-gray-500' : ''}`}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<FaChevronDown className="ml-2" />
|
<FaChevronDown
|
||||||
|
className={`ml-2 ${disabled ? 'text-gray-500' : ''}`}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<ul className="absolute z-10 w-full mt-1 overflow-y-auto bg-white border border-gray-300 max-h-60">
|
<ul className="absolute z-10 w-full mt-1 bg-white border border-gray-300 rounded shadow-lg max-h-60 overflow-y-auto">
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<li
|
<li
|
||||||
key={option.value}
|
key={option.value}
|
||||||
className={`p-2 hover:bg-gray-200 ${readOnly || disabled ? 'cursor-not-allowed' : 'cursor-pointer'}`}
|
className={'p-2'}
|
||||||
onClick={() => handleOptionClick(option)}
|
onClick={() => handleOptionClick(option)}
|
||||||
>
|
>
|
||||||
{option.label}
|
{option.label}
|
||||||
@ -100,6 +114,7 @@ const CustomSelect: React.FC<CustomSelectProps> = ({
|
|||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<p className={`text-red-500 mt-1 ${errorClassName}`}>{error}</p>
|
<p className={`text-red-500 mt-1 ${errorClassName}`}>{error}</p>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
|
import { PatchOperation } from '@/models/PatchOperation'
|
||||||
import { CacheAccountHostname } from './CacheAccountHostname'
|
import { CacheAccountHostname } from './CacheAccountHostname'
|
||||||
|
import { PatchAccountRequest } from '@/models/letsEncryptServer/account/requests/PatchAccountRequest'
|
||||||
|
|
||||||
export interface CacheAccount {
|
export interface CacheAccount {
|
||||||
accountId: string
|
accountId: string
|
||||||
@ -10,3 +12,29 @@ export interface CacheAccount {
|
|||||||
isEditMode: boolean
|
isEditMode: boolean
|
||||||
isStaging: boolean
|
isStaging: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toPatchAccountRequest = (account: CacheAccount): PatchAccountRequest => {
|
||||||
|
return {
|
||||||
|
description: { op: PatchOperation.None, value: account.description },
|
||||||
|
isDisabled: { op: PatchOperation.None, value: account.isDisabled },
|
||||||
|
contacts: account.contacts.map((contact, index) => ({
|
||||||
|
index: index,
|
||||||
|
op: PatchOperation.None,
|
||||||
|
value: contact
|
||||||
|
})),
|
||||||
|
hostnames: account.hostnames?.map((hostname, index) => ({
|
||||||
|
hostname: {
|
||||||
|
index: index,
|
||||||
|
op: PatchOperation.None,
|
||||||
|
value: hostname.hostname
|
||||||
|
},
|
||||||
|
isDisabled: {
|
||||||
|
index: index,
|
||||||
|
op: PatchOperation.None,
|
||||||
|
value: hostname.isDisabled
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { toPatchAccountRequest }
|
||||||
|
|||||||
@ -1,9 +1,5 @@
|
|||||||
import { PatchAction } from '@/models/PatchAction'
|
import { PatchAction } from '@/models/PatchAction'
|
||||||
|
import { PatchHostnameRequest } from './PatchHostnameRequest'
|
||||||
export interface PatchHostnameRequest {
|
|
||||||
hostname?: PatchAction<string>
|
|
||||||
isDisabled?: PatchAction<boolean>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PatchAccountRequest {
|
export interface PatchAccountRequest {
|
||||||
description?: PatchAction<string>
|
description?: PatchAction<string>
|
||||||
|
|||||||
@ -1,5 +0,0 @@
|
|||||||
import { PatchAction } from '@/models/PatchAction'
|
|
||||||
|
|
||||||
export interface PatchContactsRequest {
|
|
||||||
contacts: PatchAction<string>[]
|
|
||||||
}
|
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
import { PatchAction } from '@/models/PatchAction'
|
||||||
|
|
||||||
|
export interface PatchHostnameRequest {
|
||||||
|
hostname?: PatchAction<string>
|
||||||
|
isDisabled?: PatchAction<boolean>
|
||||||
|
}
|
||||||
@ -1,3 +0,0 @@
|
|||||||
export interface PostContactsRequest {
|
|
||||||
contacts: string[]
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
export interface PutAccountRequest {
|
|
||||||
description: string
|
|
||||||
contacts: string[]
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
export interface PutContactsRequest {
|
|
||||||
contacts: string[]
|
|
||||||
}
|
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import { HostnameResponse } from './HostnameResponse'
|
import { CacheAccount } from '@/entities/CacheAccount'
|
||||||
|
import { GetHostnameResponse } from './GetHostnameResponse'
|
||||||
|
|
||||||
export interface GetAccountResponse {
|
export interface GetAccountResponse {
|
||||||
accountId: string
|
accountId: string
|
||||||
@ -6,6 +7,27 @@ export interface GetAccountResponse {
|
|||||||
description: string
|
description: string
|
||||||
contacts: string[]
|
contacts: string[]
|
||||||
challengeType?: string
|
challengeType?: string
|
||||||
hostnames?: HostnameResponse[]
|
hostnames?: GetHostnameResponse[]
|
||||||
isStaging: boolean
|
isStaging: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toCacheAccount = (account: GetAccountResponse): CacheAccount => {
|
||||||
|
return {
|
||||||
|
accountId: account.accountId,
|
||||||
|
isDisabled: account.isDisabled,
|
||||||
|
description: account.description,
|
||||||
|
contacts: account.contacts.map((contact) => contact),
|
||||||
|
challengeType: account.challengeType,
|
||||||
|
hostnames:
|
||||||
|
account.hostnames?.map((hostname) => ({
|
||||||
|
hostname: hostname.hostname,
|
||||||
|
expires: new Date(hostname.expires),
|
||||||
|
isUpcomingExpire: hostname.isUpcomingExpire,
|
||||||
|
isDisabled: hostname.isDisabled
|
||||||
|
})) ?? [],
|
||||||
|
isStaging: account.isStaging,
|
||||||
|
isEditMode: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { toCacheAccount }
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
export interface GetContactsResponse {
|
|
||||||
contacts: string[]
|
|
||||||
}
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
export interface HostnameResponse {
|
export interface GetHostnameResponse {
|
||||||
hostname: string
|
hostname: string
|
||||||
expires: string
|
expires: string
|
||||||
isUpcomingExpire: boolean
|
isUpcomingExpire: boolean
|
||||||
@ -1,5 +0,0 @@
|
|||||||
import { HostnameResponse } from './HostnameResponse'
|
|
||||||
|
|
||||||
export interface GetHostnamesResponse {
|
|
||||||
hostnames: HostnameResponse[]
|
|
||||||
}
|
|
||||||
@ -14,54 +14,40 @@ import {
|
|||||||
CustomInput,
|
CustomInput,
|
||||||
CustomRadioGroup
|
CustomRadioGroup
|
||||||
} from '@/controls'
|
} from '@/controls'
|
||||||
import { CacheAccount } from '@/entities/CacheAccount'
|
import { CacheAccount, toPatchAccountRequest } from '@/entities/CacheAccount'
|
||||||
import { FaPlus, FaTrash } from 'react-icons/fa'
|
import { FaPlus, FaTrash } from 'react-icons/fa'
|
||||||
import { ChallengeTypes } from '@/entities/ChallengeTypes'
|
import { ChallengeTypes } from '@/entities/ChallengeTypes'
|
||||||
import { deepCopy } from '@/functions'
|
import { deepCopy } from '@/functions'
|
||||||
import { ApiRoutes, GetApiRoute } from '@/ApiRoutes'
|
import { ApiRoutes, GetApiRoute } from '@/ApiRoutes'
|
||||||
import { httpService } from '@/services/httpService'
|
import { httpService } from '@/services/HttpService'
|
||||||
import { PatchAccountRequest } from '@/models/letsEncryptServer/account/requests/PatchAccountRequest'
|
import { PatchAccountRequest } from '@/models/letsEncryptServer/account/requests/PatchAccountRequest'
|
||||||
import { PatchOperation } from '@/models/PatchOperation'
|
import { PatchOperation } from '@/models/PatchOperation'
|
||||||
import { useAppDispatch } from '@/redux/store'
|
import { useAppDispatch } from '@/redux/store'
|
||||||
import { showToast } from '@/redux/slices/toastSlice'
|
import { showToast } from '@/redux/slices/toastSlice'
|
||||||
|
import {
|
||||||
|
GetAccountResponse,
|
||||||
|
toCacheAccount
|
||||||
|
} from '@/models/letsEncryptServer/account/responses/GetAccountResponse'
|
||||||
|
|
||||||
interface AccountEditProps {
|
interface AccountEditProps {
|
||||||
account: CacheAccount
|
account: CacheAccount
|
||||||
setAccount: Dispatch<SetStateAction<CacheAccount | null>>
|
|
||||||
onCancel?: () => void
|
onCancel?: () => void
|
||||||
onSubmit?: (account: CacheAccount) => void
|
|
||||||
onDelete: (accountId: string) => void
|
onDelete: (accountId: string) => void
|
||||||
|
onSubmit: (account: CacheAccount) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const AccountEdit: React.FC<AccountEditProps> = ({
|
const AccountEdit: React.FC<AccountEditProps> = (props) => {
|
||||||
account,
|
const { account, onCancel, onDelete, onSubmit } = props
|
||||||
setAccount,
|
|
||||||
onCancel,
|
|
||||||
onSubmit,
|
|
||||||
onDelete
|
|
||||||
}) => {
|
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
const [newAccount, setNewAccount] = useState<PatchAccountRequest>({
|
const [newAccount, setNewAccount] = useState<PatchAccountRequest>(
|
||||||
description: { op: PatchOperation.None, value: account.description },
|
toPatchAccountRequest(account)
|
||||||
isDisabled: { op: PatchOperation.None, value: account.isDisabled },
|
)
|
||||||
contacts: account.contacts.map((contact) => ({
|
|
||||||
op: PatchOperation.None,
|
|
||||||
value: contact
|
|
||||||
})),
|
|
||||||
hostnames: account.hostnames?.map((hostname) => ({
|
|
||||||
hostname: { op: PatchOperation.None, value: hostname.hostname },
|
|
||||||
isDisabled: { op: PatchOperation.None, value: hostname.isDisabled }
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
const [newContact, setNewContact] = useState('')
|
const [newContact, setNewContact] = useState('')
|
||||||
const [newHostname, setNewHostname] = useState('')
|
const [newHostname, setNewHostname] = useState('')
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log(newAccount)
|
|
||||||
}, [newAccount])
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
value: description,
|
value: description,
|
||||||
error: descriptionError,
|
error: descriptionError,
|
||||||
@ -163,6 +149,9 @@ const AccountEdit: React.FC<AccountEditProps> = ({
|
|||||||
return c
|
return c
|
||||||
})
|
})
|
||||||
.filter((c) => !(c.value === contact && c.op === PatchOperation.Add))
|
.filter((c) => !(c.value === contact && c.op === PatchOperation.Add))
|
||||||
|
|
||||||
|
console.log(newAccount.contacts)
|
||||||
|
|
||||||
return newAccount
|
return newAccount
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -246,11 +235,24 @@ const AccountEdit: React.FC<AccountEditProps> = ({
|
|||||||
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
httpService.patch<PatchAccountRequest, CacheAccount>(
|
if (!newAccount) return
|
||||||
GetApiRoute(ApiRoutes.ACCOUNT_ID, account.accountId),
|
|
||||||
newAccount
|
httpService
|
||||||
|
.patch<
|
||||||
|
PatchAccountRequest,
|
||||||
|
GetAccountResponse
|
||||||
|
>(GetApiRoute(ApiRoutes.ACCOUNT_ID, account.accountId), newAccount)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.isSuccess && response.data) {
|
||||||
|
onSubmit?.(toCacheAccount(response.data))
|
||||||
|
} else {
|
||||||
|
// Optionally, handle the error case, e.g., show an error message
|
||||||
|
dispatch(
|
||||||
|
showToast({ message: 'Failed to update account.', type: 'error' })
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const handleDelete = (accountId: string) => {
|
const handleDelete = (accountId: string) => {
|
||||||
onDelete?.(accountId)
|
onDelete?.(accountId)
|
||||||
@ -268,7 +270,7 @@ const AccountEdit: React.FC<AccountEditProps> = ({
|
|||||||
title="Description"
|
title="Description"
|
||||||
inputClassName="border p-2 rounded w-full"
|
inputClassName="border p-2 rounded w-full"
|
||||||
errorClassName="text-red-500 text-sm mt-1"
|
errorClassName="text-red-500 text-sm mt-1"
|
||||||
className="mr-2 flex-grow"
|
className="mr-2 w-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -277,14 +279,16 @@ const AccountEdit: React.FC<AccountEditProps> = ({
|
|||||||
checked={newAccount.isDisabled?.value ?? false}
|
checked={newAccount.isDisabled?.value ?? false}
|
||||||
label="Disabled"
|
label="Disabled"
|
||||||
onChange={handleIsDisabledChange}
|
onChange={handleIsDisabledChange}
|
||||||
className="mr-2 flex-grow"
|
className="mr-2"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
<h3 className="text-xl font-medium mb-2">Contacts:</h3>
|
<h3 className="text-xl font-medium mb-2">Contacts:</h3>
|
||||||
<ul className="list-disc list-inside pl-4 mb-2">
|
<ul className="list-disc list-inside pl-4 mb-2">
|
||||||
{newAccount.contacts?.map((contact) => (
|
{newAccount.contacts
|
||||||
|
?.filter((contact) => contact.op !== PatchOperation.Remove)
|
||||||
|
.map((contact) => (
|
||||||
<li key={contact.value} className="text-gray-700 mb-2">
|
<li key={contact.value} className="text-gray-700 mb-2">
|
||||||
<div className="inline-flex">
|
<div className="inline-flex">
|
||||||
{contact.value}
|
{contact.value}
|
||||||
@ -309,7 +313,7 @@ const AccountEdit: React.FC<AccountEditProps> = ({
|
|||||||
title="New Contact"
|
title="New Contact"
|
||||||
inputClassName="border p-2 rounded w-full"
|
inputClassName="border p-2 rounded w-full"
|
||||||
errorClassName="text-red-500 text-sm mt-1"
|
errorClassName="text-red-500 text-sm mt-1"
|
||||||
className="mr-2 flex-grow"
|
className="mr-2 w-full"
|
||||||
/>
|
/>
|
||||||
<CustomButton
|
<CustomButton
|
||||||
type="button"
|
type="button"
|
||||||
@ -326,7 +330,7 @@ const AccountEdit: React.FC<AccountEditProps> = ({
|
|||||||
title="Challenge Type"
|
title="Challenge Type"
|
||||||
enumType={ChallengeTypes}
|
enumType={ChallengeTypes}
|
||||||
selectedValue={account.challengeType}
|
selectedValue={account.challengeType}
|
||||||
className="mr-2 flex-grow"
|
className="mr-2 w-full"
|
||||||
disabled={true}
|
disabled={true}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -334,9 +338,13 @@ const AccountEdit: React.FC<AccountEditProps> = ({
|
|||||||
<div>
|
<div>
|
||||||
<h3 className="text-xl font-medium mb-2">Hostnames:</h3>
|
<h3 className="text-xl font-medium mb-2">Hostnames:</h3>
|
||||||
<ul className="list-disc list-inside pl-4 mb-2">
|
<ul className="list-disc list-inside pl-4 mb-2">
|
||||||
{newAccount.hostnames?.map((hostname) => (
|
{newAccount.hostnames
|
||||||
|
?.filter(
|
||||||
|
(hostname) => hostname.hostname?.op !== PatchOperation.Remove
|
||||||
|
)
|
||||||
|
.map((hostname) => (
|
||||||
<li key={hostname.hostname?.value} className="text-gray-700 mb-2">
|
<li key={hostname.hostname?.value} className="text-gray-700 mb-2">
|
||||||
<div className="inline-flex">
|
<div className="inline-flex items-center">
|
||||||
{hostname.hostname?.value} -{' '}
|
{hostname.hostname?.value} -{' '}
|
||||||
<CustomCheckbox
|
<CustomCheckbox
|
||||||
className="ml-2"
|
className="ml-2"
|
||||||
@ -373,7 +381,7 @@ const AccountEdit: React.FC<AccountEditProps> = ({
|
|||||||
title="New Hostname"
|
title="New Hostname"
|
||||||
inputClassName="border p-2 rounded w-full"
|
inputClassName="border p-2 rounded w-full"
|
||||||
errorClassName="text-red-500 text-sm mt-1"
|
errorClassName="text-red-500 text-sm mt-1"
|
||||||
className="mr-2 flex-grow"
|
className="mr-2 w-full"
|
||||||
/>
|
/>
|
||||||
<CustomButton
|
<CustomButton
|
||||||
type="button"
|
type="button"
|
||||||
@ -392,7 +400,7 @@ const AccountEdit: React.FC<AccountEditProps> = ({
|
|||||||
]}
|
]}
|
||||||
initialValue={account.isStaging ? 'staging' : 'production'}
|
initialValue={account.isStaging ? 'staging' : 'production'}
|
||||||
title="LetsEncrypt Environment"
|
title="LetsEncrypt Environment"
|
||||||
className="mr-2 flex-grow"
|
className="mr-2 w-full"
|
||||||
radioClassName=""
|
radioClassName=""
|
||||||
errorClassName="text-red-500 text-sm mt-1"
|
errorClassName="text-red-500 text-sm mt-1"
|
||||||
disabled={true}
|
disabled={true}
|
||||||
@ -402,20 +410,20 @@ const AccountEdit: React.FC<AccountEditProps> = ({
|
|||||||
<CustomButton
|
<CustomButton
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleDelete(account.accountId)}
|
onClick={() => handleDelete(account.accountId)}
|
||||||
className="bg-red-500 text-white p-2 rounded ml-2"
|
className="bg-red-500 text-white p-2 rounded"
|
||||||
>
|
>
|
||||||
<FaTrash />
|
<FaTrash />
|
||||||
</CustomButton>
|
</CustomButton>
|
||||||
<CustomButton
|
<CustomButton
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleCancel}
|
onClick={handleCancel}
|
||||||
className="bg-yellow-500 text-white p-2 rounded ml-2"
|
className="bg-yellow-500 text-white p-2 rounded"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</CustomButton>
|
</CustomButton>
|
||||||
<CustomButton
|
<CustomButton
|
||||||
type="submit"
|
type="submit"
|
||||||
className="bg-green-500 text-white p-2 rounded ml-2"
|
className="bg-green-500 text-white p-2 rounded"
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</CustomButton>
|
</CustomButton>
|
||||||
|
|||||||
@ -73,6 +73,29 @@ class HttpService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private cleanObject = (obj: any): any => {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
const cleanedArray = obj
|
||||||
|
.map(this.cleanObject)
|
||||||
|
.filter((item) => item !== null && item !== undefined)
|
||||||
|
return cleanedArray.length > 0 ? cleanedArray : null
|
||||||
|
} else if (typeof obj === 'object' && obj !== null) {
|
||||||
|
if (obj.op !== undefined && obj.op === PatchOperation.None) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanedObject: any = {}
|
||||||
|
Object.keys(obj).forEach((key) => {
|
||||||
|
const cleanedValue = this.cleanObject(obj[key])
|
||||||
|
if (cleanedValue !== null) {
|
||||||
|
cleanedObject[key] = cleanedValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return Object.keys(cleanedObject).length > 0 ? cleanedObject : null
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
private handleRequestInterceptors(xhr: XMLHttpRequest): void {
|
private handleRequestInterceptors(xhr: XMLHttpRequest): void {
|
||||||
this.requestInterceptors.forEach((interceptor) => {
|
this.requestInterceptors.forEach((interceptor) => {
|
||||||
try {
|
try {
|
||||||
@ -223,34 +246,12 @@ class HttpService {
|
|||||||
return await this.request<TResponse>('PUT', url, data)
|
return await this.request<TResponse>('PUT', url, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
private cleanPatchRequest(obj: any): any {
|
|
||||||
if (Array.isArray(obj)) {
|
|
||||||
const cleanedArray = obj
|
|
||||||
.map(this.cleanPatchRequest)
|
|
||||||
.filter((item) => item !== null && item !== undefined)
|
|
||||||
return cleanedArray.length > 0 ? cleanedArray : null
|
|
||||||
} else if (typeof obj === 'object' && obj !== null) {
|
|
||||||
if (obj.op !== undefined && obj.op === PatchOperation.None) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const cleanedObject: any = {}
|
|
||||||
Object.keys(obj).forEach((key) => {
|
|
||||||
const cleanedValue = this.cleanPatchRequest(obj[key])
|
|
||||||
if (cleanedValue !== null) {
|
|
||||||
cleanedObject[key] = cleanedValue
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return Object.keys(cleanedObject).length > 0 ? cleanedObject : null
|
|
||||||
}
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
public async patch<TRequest, TResponse>(
|
public async patch<TRequest, TResponse>(
|
||||||
url: string,
|
url: string,
|
||||||
data: TRequest
|
data: TRequest
|
||||||
): Promise<HttpResponse<TResponse>> {
|
): Promise<HttpResponse<TResponse>> {
|
||||||
const cleanedData = this.cleanPatchRequest(data)
|
// Clean the data before sending the patch request
|
||||||
|
const cleanedData = this.cleanObject(data)
|
||||||
return await this.request<TResponse>('PATCH', url, cleanedData)
|
return await this.request<TResponse>('PATCH', url, cleanedData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,11 +15,10 @@ public class RegistrationCache {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public required Guid AccountId { get; set; }
|
public required Guid AccountId { get; set; }
|
||||||
public bool IsDisabled { get; set; }
|
public bool IsDisabled { get; set; }
|
||||||
public string? Description { get; set; }
|
public required string Description { get; set; }
|
||||||
public required string[] Contacts { get; set; }
|
public required string[] Contacts { get; set; }
|
||||||
public string? ChallengeType { get; set; }
|
|
||||||
|
|
||||||
public required bool IsStaging { get; set; }
|
public required bool IsStaging { get; set; }
|
||||||
|
public string? ChallengeType { get; set; }
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -36,12 +36,6 @@ public class AccountController : ControllerBase {
|
|||||||
return result.ToActionResult();
|
return result.ToActionResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("account/{accountId:guid}")]
|
|
||||||
public async Task<IActionResult> PutAccount(Guid accountId, [FromBody] PutAccountRequest requestData) {
|
|
||||||
var result = await _accountService.PutAccountAsync(accountId, requestData);
|
|
||||||
return result.ToActionResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPatch("account/{accountId:guid}")]
|
[HttpPatch("account/{accountId:guid}")]
|
||||||
public async Task<IActionResult> PatchAccount(Guid accountId, [FromBody] PatchAccountRequest requestData) {
|
public async Task<IActionResult> PatchAccount(Guid accountId, [FromBody] PatchAccountRequest requestData) {
|
||||||
var result = await _accountService.PatchAccountAsync(accountId, requestData);
|
var result = await _accountService.PatchAccountAsync(accountId, requestData);
|
||||||
@ -55,82 +49,4 @@ public class AccountController : ControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Account Contacts
|
|
||||||
|
|
||||||
[HttpGet("account/{accountId:guid}/contacts")]
|
|
||||||
public async Task<IActionResult> GetContacts(Guid accountId) {
|
|
||||||
var result = await _accountService.GetContactsAsync(accountId);
|
|
||||||
return result.ToActionResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("account/{accountId:guid}/contacts")]
|
|
||||||
public async Task<IActionResult> PostContacts(Guid accountId, [FromBody] PostContactsRequest requestData) {
|
|
||||||
//var result = await _accountService.PostContactsAsync(accountId, requestData);
|
|
||||||
//return result.ToActionResult();
|
|
||||||
|
|
||||||
return BadRequest("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPut("account/{accountId:guid}/contacts")]
|
|
||||||
public async Task<IActionResult> PutContacts(Guid accountId, [FromBody] PutContactsRequest requestData) {
|
|
||||||
var result = await _accountService.PutContactsAsync(accountId, requestData);
|
|
||||||
return result.ToActionResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPatch("account/{accountId:guid}/contacts")]
|
|
||||||
public async Task<IActionResult> PatchContacts(Guid accountId, [FromBody] PatchContactsRequest requestData) {
|
|
||||||
var result = await _accountService.PatchContactsAsync(accountId, requestData);
|
|
||||||
return result.ToActionResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpDelete("account/{accountId:guid}/contact/{index:int}")]
|
|
||||||
public async Task<IActionResult> DeleteContact(Guid accountId, int index) {
|
|
||||||
var result = await _accountService.DeleteContactAsync(accountId, index);
|
|
||||||
return result.ToActionResult();
|
|
||||||
}
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Account Hostnames
|
|
||||||
|
|
||||||
[HttpGet("{accountId:guid}/hostnames")]
|
|
||||||
public async Task<IActionResult> GetHostnames(Guid accountId) {
|
|
||||||
var result = await _accountService.GetHostnames(accountId);
|
|
||||||
return result.ToActionResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("account/{accountId:guid}/hostnames")]
|
|
||||||
public async Task<IActionResult> PostHostname(Guid accountId, [FromBody] PostHostnamesRequest requestData) {
|
|
||||||
//var result = await _cacheService.PostHostnameAsync(accountId, requestData);
|
|
||||||
//return result.ToActionResult();
|
|
||||||
|
|
||||||
return BadRequest("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPut("account/{accountId:guid}/hostnames")]
|
|
||||||
public async Task<IActionResult> PutHostname(Guid accountId, [FromBody] PutHostnamesRequest requestData) {
|
|
||||||
//var result = await _cacheService.PutHostnameAsync(accountId, requestData);
|
|
||||||
//return result.ToActionResult();
|
|
||||||
|
|
||||||
return BadRequest("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPatch("account/{accountId:guid}/hostnames")]
|
|
||||||
public async Task<IActionResult> PatchHostname(Guid accountId, [FromBody] PatchHostnamesRequest requestData) {
|
|
||||||
//var result = await _cacheService.PatchHostnameAsync(accountId, requestData);
|
|
||||||
//return result.ToActionResult();
|
|
||||||
|
|
||||||
return BadRequest("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[HttpDelete("account/{accountId:guid}/hostname/{index:int}")]
|
|
||||||
public async Task<IActionResult> DeleteHostname(Guid accountId, int index) {
|
|
||||||
//var result = await _cacheService.DeleteHostnameAsync(accountId, index);
|
|
||||||
//return result.ToActionResult();
|
|
||||||
|
|
||||||
return BadRequest("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,15 +17,8 @@ public interface IAccountRestService {
|
|||||||
Task<(GetAccountResponse[]?, IDomainResult)> GetAccountsAsync();
|
Task<(GetAccountResponse[]?, IDomainResult)> GetAccountsAsync();
|
||||||
Task<(GetAccountResponse?, IDomainResult)> GetAccountAsync(Guid accountId);
|
Task<(GetAccountResponse?, IDomainResult)> GetAccountAsync(Guid accountId);
|
||||||
Task<(GetAccountResponse?, IDomainResult)> PostAccountAsync(PostAccountRequest requestData);
|
Task<(GetAccountResponse?, IDomainResult)> PostAccountAsync(PostAccountRequest requestData);
|
||||||
Task<(GetAccountResponse?, IDomainResult)> PutAccountAsync(Guid accountId, PutAccountRequest requestData);
|
|
||||||
Task<(GetAccountResponse?, IDomainResult)> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData);
|
Task<(GetAccountResponse?, IDomainResult)> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData);
|
||||||
Task<IDomainResult> DeleteAccountAsync(Guid accountId);
|
Task<IDomainResult> DeleteAccountAsync(Guid accountId);
|
||||||
Task<(GetContactsResponse?, IDomainResult)> GetContactsAsync(Guid accountId);
|
|
||||||
Task<(GetContactsResponse?, IDomainResult)> PostContactsAsync(Guid accountId, PostContactsRequest requestData);
|
|
||||||
Task<(GetAccountResponse?, IDomainResult)> PutContactsAsync(Guid accountId, PutContactsRequest requestData);
|
|
||||||
Task<(GetAccountResponse?, IDomainResult)> PatchContactsAsync(Guid accountId, PatchContactsRequest requestData);
|
|
||||||
Task<IDomainResult> DeleteContactAsync(Guid accountId, int index);
|
|
||||||
Task<(GetHostnamesResponse?, IDomainResult)> GetHostnames(Guid accountId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IAccountService : IAccountInternalService, IAccountRestService { }
|
public interface IAccountService : IAccountInternalService, IAccountRestService { }
|
||||||
@ -75,67 +68,26 @@ public class AccountService : IAccountService {
|
|||||||
|
|
||||||
// TODO: check for overlapping hostnames in already existing accounts
|
// TODO: check for overlapping hostnames in already existing accounts
|
||||||
|
|
||||||
var (sessionId, configureClientResult) = await _certsFlowService.ConfigureClientAsync(requestData.IsStaging);
|
var (accountId, newCertsResult) = await _certsFlowService.FullFlow(
|
||||||
if (!configureClientResult.IsSuccess || sessionId == null) {
|
requestData.IsStaging,
|
||||||
//LogErrors(configureClientResult.Errors);
|
null,
|
||||||
return (null, configureClientResult);
|
requestData.Description,
|
||||||
}
|
requestData.Contacts,
|
||||||
var sessionIdValue = sessionId.Value;
|
requestData.ChallengeType,
|
||||||
|
requestData.Hostnames
|
||||||
|
);
|
||||||
|
|
||||||
var (_, initResult) = await _certsFlowService.InitAsync(sessionIdValue, null, requestData.Description, requestData.Contacts);
|
if (!newCertsResult.IsSuccess || accountId == null)
|
||||||
if (!initResult.IsSuccess) {
|
return (null, newCertsResult);
|
||||||
//LogErrors(initResult.Errors);
|
|
||||||
return (null, initResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
var (_, newOrderResult) = await _certsFlowService.NewOrderAsync(sessionIdValue, requestData.Hostnames, requestData.ChallengeType);
|
|
||||||
if (!newOrderResult.IsSuccess) {
|
|
||||||
//LogErrors(newOrderResult.Errors);
|
|
||||||
return (null, newOrderResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
var challengeResult = await _certsFlowService.CompleteChallengesAsync(sessionIdValue);
|
|
||||||
if (!challengeResult.IsSuccess) {
|
|
||||||
//LogErrors(challengeResult.Errors);
|
|
||||||
return (null, challengeResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
var getOrderResult = await _certsFlowService.GetOrderAsync(sessionIdValue, requestData.Hostnames);
|
var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId.Value);
|
||||||
if (!getOrderResult.IsSuccess) {
|
|
||||||
//LogErrors(getOrderResult.Errors);
|
|
||||||
return (null, getOrderResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
var certs = await _certsFlowService.GetCertificatesAsync(sessionIdValue, requestData.Hostnames);
|
|
||||||
if (!certs.IsSuccess) {
|
|
||||||
//LogErrors(certs.Errors);
|
|
||||||
return (null, certs);
|
|
||||||
}
|
|
||||||
|
|
||||||
var (_, applyCertsResult) = await _certsFlowService.ApplyCertificatesAsync(sessionIdValue, requestData.Hostnames);
|
|
||||||
if (!applyCertsResult.IsSuccess) {
|
|
||||||
//LogErrors(applyCertsResult.Errors);
|
|
||||||
return (null, applyCertsResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
return IDomainResult.Success<GetAccountResponse?>(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<(GetAccountResponse?, IDomainResult)> PutAccountAsync(Guid accountId, PutAccountRequest requestData) {
|
|
||||||
var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId);
|
|
||||||
if (!loadResult.IsSuccess || cache == null) {
|
if (!loadResult.IsSuccess || cache == null) {
|
||||||
return (null, loadResult);
|
return (null, loadResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.Description = requestData.Description;
|
return IDomainResult.Success(CreateGetAccountResponse(accountId.Value, cache));
|
||||||
cache.Contacts = requestData.Contacts;
|
|
||||||
|
|
||||||
var saveResult = await _cacheService.SaveToCacheAsync(accountId, cache);
|
|
||||||
if (!saveResult.IsSuccess) {
|
|
||||||
return (null, saveResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
return IDomainResult.Success(CreateGetAccountResponse(accountId, cache));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(GetAccountResponse?, IDomainResult)> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData) {
|
public async Task<(GetAccountResponse?, IDomainResult)> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData) {
|
||||||
@ -152,7 +104,15 @@ public class AccountService : IAccountService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (requestData.Contacts != null && requestData.Contacts.Any()) {
|
if (requestData.IsDisabled != null) {
|
||||||
|
switch (requestData.IsDisabled.Op) {
|
||||||
|
case PatchOperation.Replace:
|
||||||
|
cache.IsDisabled = requestData.IsDisabled.Value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestData.Contacts?.Any() == true) {
|
||||||
var contacts = cache.Contacts?.ToList() ?? new List<string>();
|
var contacts = cache.Contacts?.ToList() ?? new List<string>();
|
||||||
foreach (var action in requestData.Contacts) {
|
foreach (var action in requestData.Contacts) {
|
||||||
switch (action.Op)
|
switch (action.Op)
|
||||||
@ -173,11 +133,81 @@ public class AccountService : IAccountService {
|
|||||||
cache.Contacts = contacts.ToArray();
|
cache.Contacts = contacts.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var hostnamesToAdd = new List<string>();
|
||||||
|
var hostnamesToRemove = new List<string>();
|
||||||
|
|
||||||
|
if (requestData.Hostnames?.Any() == true) {
|
||||||
|
var hostnames = cache.GetHosts().ToList();
|
||||||
|
foreach (var action in requestData.Hostnames) {
|
||||||
|
|
||||||
|
if (action.Hostname != null) {
|
||||||
|
switch (action.Hostname.Op) {
|
||||||
|
case PatchOperation.Add:
|
||||||
|
hostnamesToAdd.Add(action.Hostname.Value);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PatchOperation.Replace:
|
||||||
|
if (action.Hostname.Index != null && action.Hostname.Index >= 0 && action.Hostname.Index < hostnames.Count)
|
||||||
|
hostnames[action.Hostname.Index.Value].Hostname = action.Hostname.Value;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PatchOperation.Remove:
|
||||||
|
hostnamesToRemove.Add(action.Hostname.Value);
|
||||||
|
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.IsDisabled != null) {
|
||||||
|
switch (action.IsDisabled.Op) {
|
||||||
|
case PatchOperation.Replace:
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var saveResult = await _cacheService.SaveToCacheAsync(accountId, cache);
|
var saveResult = await _cacheService.SaveToCacheAsync(accountId, cache);
|
||||||
if (!saveResult.IsSuccess) {
|
if (!saveResult.IsSuccess) {
|
||||||
return (null, saveResult);
|
return (null, saveResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (hostnamesToAdd.Count > 0) {
|
||||||
|
var (_, newCertsResult) = await _certsFlowService.FullFlow(
|
||||||
|
cache.IsStaging,
|
||||||
|
cache.AccountId,
|
||||||
|
cache.Description,
|
||||||
|
cache.Contacts,
|
||||||
|
cache.ChallengeType,
|
||||||
|
hostnamesToAdd.ToArray()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!newCertsResult.IsSuccess)
|
||||||
|
return (null, newCertsResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (hostnamesToRemove.Count > 0) {
|
||||||
|
hostnamesToRemove.ForEach(hostname => {
|
||||||
|
cache.CachedCerts?.Remove(hostname);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
(cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId);
|
||||||
|
if (!loadResult.IsSuccess || cache == null) {
|
||||||
|
return (null, loadResult);
|
||||||
|
}
|
||||||
|
|
||||||
return IDomainResult.Success(CreateGetAccountResponse(accountId, cache));
|
return IDomainResult.Success(CreateGetAccountResponse(accountId, cache));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,117 +219,10 @@ public class AccountService : IAccountService {
|
|||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Contacts Operations
|
#region Helper Methods
|
||||||
|
|
||||||
public async Task<(GetContactsResponse?, IDomainResult)> GetContactsAsync(Guid accountId) {
|
private List<GetHostnameResponse> GetHostnamesFromCache(RegistrationCache cache) {
|
||||||
var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId);
|
var hosts = cache.GetHosts().Select(x => new GetHostnameResponse {
|
||||||
if (!loadResult.IsSuccess || cache == null) {
|
|
||||||
return (null, loadResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
return IDomainResult.Success(new GetContactsResponse {
|
|
||||||
Contacts = cache.Contacts ?? Array.Empty<string>()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<(GetContactsResponse?, IDomainResult)> PostContactsAsync(Guid accountId, PostContactsRequest requestData) {
|
|
||||||
return IDomainResult.Failed<GetContactsResponse?>("Not implemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<(GetAccountResponse?, IDomainResult)> PutContactsAsync(Guid accountId, PutContactsRequest requestData) {
|
|
||||||
var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId);
|
|
||||||
if (!loadResult.IsSuccess || cache == null) {
|
|
||||||
return (null, loadResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
cache.Contacts = requestData.Contacts;
|
|
||||||
var saveResult = await _cacheService.SaveToCacheAsync(accountId, cache);
|
|
||||||
if (!saveResult.IsSuccess) {
|
|
||||||
return (null, saveResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
return IDomainResult.Success(CreateGetAccountResponse(accountId, cache));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<(GetAccountResponse?, IDomainResult)> PatchContactsAsync(Guid accountId, PatchContactsRequest requestData) {
|
|
||||||
var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId);
|
|
||||||
if (!loadResult.IsSuccess || cache == null) {
|
|
||||||
return (null, loadResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
var contacts = cache.Contacts?.ToList() ?? new List<string>();
|
|
||||||
|
|
||||||
foreach (var contact in requestData.Contacts) {
|
|
||||||
switch (contact.Op) {
|
|
||||||
case PatchOperation.Add:
|
|
||||||
if (contact.Value != null) {
|
|
||||||
contacts.Add(contact.Value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PatchOperation.Replace:
|
|
||||||
if (contact.Index.HasValue && contact.Index.Value >= 0 && contact.Index.Value < contacts.Count && contact.Value != null) {
|
|
||||||
contacts[contact.Index.Value] = contact.Value;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case PatchOperation.Remove:
|
|
||||||
if (contact.Index.HasValue && contact.Index.Value >= 0 && contact.Index.Value < contacts.Count) {
|
|
||||||
contacts.RemoveAt(contact.Index.Value);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return (null, IDomainResult.Failed("Invalid patch operation."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cache.Contacts = contacts.ToArray();
|
|
||||||
var saveResult = await _cacheService.SaveToCacheAsync(accountId, cache);
|
|
||||||
if (!saveResult.IsSuccess) {
|
|
||||||
return (null, saveResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
return IDomainResult.Success(CreateGetAccountResponse(accountId, cache));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IDomainResult> DeleteContactAsync(Guid accountId, int index) {
|
|
||||||
var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId);
|
|
||||||
if (!loadResult.IsSuccess || cache == null) {
|
|
||||||
return loadResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
var contacts = cache.Contacts?.ToList() ?? new List<string>();
|
|
||||||
|
|
||||||
if (index >= 0 && index < contacts.Count) {
|
|
||||||
contacts.RemoveAt(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
cache.Contacts = contacts.ToArray();
|
|
||||||
var saveResult = await _cacheService.SaveToCacheAsync(accountId, cache);
|
|
||||||
if (!saveResult.IsSuccess) {
|
|
||||||
return saveResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
return IDomainResult.Success();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Hostnames Operations
|
|
||||||
|
|
||||||
public async Task<(GetHostnamesResponse?, IDomainResult)> GetHostnames(Guid accountId) {
|
|
||||||
var (cache, loadResult) = await _cacheService.LoadAccountFromCacheAsync(accountId);
|
|
||||||
if (!loadResult.IsSuccess || cache?.CachedCerts == null) {
|
|
||||||
return (null, loadResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
var hostnames = GetHostnamesFromCache(cache);
|
|
||||||
|
|
||||||
return IDomainResult.Success(new GetHostnamesResponse {
|
|
||||||
Hostnames = hostnames
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<HostnameResponse> GetHostnamesFromCache(RegistrationCache cache) {
|
|
||||||
var hosts = cache.GetHosts().Select(x => new HostnameResponse {
|
|
||||||
Hostname = x.Hostname,
|
Hostname = x.Hostname,
|
||||||
Expires = x.Expires,
|
Expires = x.Expires,
|
||||||
IsUpcomingExpire = x.IsUpcomingExpire,
|
IsUpcomingExpire = x.IsUpcomingExpire,
|
||||||
@ -309,10 +232,6 @@ public class AccountService : IAccountService {
|
|||||||
return hosts;
|
return hosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Helper Methods
|
|
||||||
|
|
||||||
private GetAccountResponse CreateGetAccountResponse(Guid accountId, RegistrationCache cache) {
|
private GetAccountResponse CreateGetAccountResponse(Guid accountId, RegistrationCache cache) {
|
||||||
var hostnames = GetHostnamesFromCache(cache) ?? [];
|
var hostnames = GetHostnamesFromCache(cache) ?? [];
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,7 @@ public interface ICertsInternalService : ICertsCommonService {
|
|||||||
Task<IDomainResult> GetOrderAsync(Guid sessionId, string[] hostnames);
|
Task<IDomainResult> GetOrderAsync(Guid sessionId, string[] hostnames);
|
||||||
Task<IDomainResult> GetCertificatesAsync(Guid sessionId, string[] hostnames);
|
Task<IDomainResult> GetCertificatesAsync(Guid sessionId, string[] hostnames);
|
||||||
Task<(Dictionary<string, string>?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, string[] hostnames);
|
Task<(Dictionary<string, string>?, IDomainResult)> ApplyCertificatesAsync(Guid sessionId, string[] hostnames);
|
||||||
|
Task<(Guid?, IDomainResult)> FullFlow(bool isStaging, Guid? accountId, string description, string[] contacts, string challengeType, string[] hostnames);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ICertsRestService : ICertsCommonService {
|
public interface ICertsRestService : ICertsCommonService {
|
||||||
@ -188,6 +189,39 @@ public class CertsFlowService : ICertsFlowService {
|
|||||||
return IDomainResult.Success(results);
|
return IDomainResult.Success(results);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<(Guid?, IDomainResult)> FullFlow(bool isStaging, Guid? accountId, string description, string[] contacts, string challengeType, string[]hostnames) {
|
||||||
|
var (sessionId, configureClientResult) = await ConfigureClientAsync(isStaging);
|
||||||
|
if (!configureClientResult.IsSuccess || sessionId == null)
|
||||||
|
return (null, configureClientResult);
|
||||||
|
|
||||||
|
(accountId, var initResult) = await InitAsync(sessionId.Value, accountId, description, contacts);
|
||||||
|
if (!initResult.IsSuccess)
|
||||||
|
return (null, initResult);
|
||||||
|
|
||||||
|
var (_, newOrderResult) = await NewOrderAsync(sessionId.Value, hostnames, challengeType);
|
||||||
|
if (!newOrderResult.IsSuccess)
|
||||||
|
return (null, newOrderResult);
|
||||||
|
|
||||||
|
var challengeResult = await CompleteChallengesAsync(sessionId.Value);
|
||||||
|
if (!challengeResult.IsSuccess)
|
||||||
|
return (null, challengeResult);
|
||||||
|
|
||||||
|
var getOrderResult = await GetOrderAsync(sessionId.Value, hostnames);
|
||||||
|
if (!getOrderResult.IsSuccess)
|
||||||
|
return (null, getOrderResult);
|
||||||
|
|
||||||
|
var certs = await GetCertificatesAsync(sessionId.Value, hostnames);
|
||||||
|
if (!certs.IsSuccess)
|
||||||
|
return (null, certs);
|
||||||
|
|
||||||
|
var (_, applyCertsResult) = await ApplyCertificatesAsync(sessionId.Value, hostnames);
|
||||||
|
if (!applyCertsResult.IsSuccess)
|
||||||
|
return (null, applyCertsResult);
|
||||||
|
|
||||||
|
return IDomainResult.Success(accountId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region REST methods
|
#region REST methods
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
namespace MaksIT.Models.LetsEncryptServer.Account.Requests;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MaksIT.Models.LetsEncryptServer.Account.Requests {
|
|
||||||
public class PatchAccountRequest {
|
public class PatchAccountRequest {
|
||||||
|
|
||||||
public PatchAction<string>? Description { get; set; }
|
public PatchAction<string>? Description { get; set; }
|
||||||
|
|
||||||
|
public PatchAction<bool>? IsDisabled { get; set; }
|
||||||
|
|
||||||
public List<PatchAction<string>>? Contacts { get; set; }
|
public List<PatchAction<string>>? Contacts { get; set; }
|
||||||
}
|
|
||||||
|
public List<PatchHostnameRequest>? Hostnames { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MaksIT.Models.LetsEncryptServer.Account.Requests {
|
|
||||||
|
|
||||||
public class PatchContactsRequest {
|
|
||||||
public List<PatchAction<string>> Contacts { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
|
namespace MaksIT.Models.LetsEncryptServer.Account.Requests;
|
||||||
|
public class PatchHostnameRequest {
|
||||||
|
public PatchAction<string> Hostname { get; set; }
|
||||||
|
|
||||||
|
public PatchAction<bool> IsDisabled { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MaksIT.Models.LetsEncryptServer.Account.Requests {
|
|
||||||
|
|
||||||
public class PatchHostnamesRequest {
|
|
||||||
public List<PatchAction<string>> Hostnames { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace MaksIT.Models.LetsEncryptServer.Account.Requests {
|
namespace MaksIT.Models.LetsEncryptServer.Account.Requests;
|
||||||
public class PostAccountRequest : IValidatableObject {
|
public class PostAccountRequest : IValidatableObject {
|
||||||
public required string Description { get; set; }
|
public required string Description { get; set; }
|
||||||
public required string[] Contacts { get; set; }
|
public required string[] Contacts { get; set; }
|
||||||
@ -22,4 +22,3 @@ namespace MaksIT.Models.LetsEncryptServer.Account.Requests {
|
|||||||
yield return new ValidationResult("ChallengeType is required", new[] { nameof(ChallengeType) });
|
yield return new ValidationResult("ChallengeType is required", new[] { nameof(ChallengeType) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MaksIT.Models.LetsEncryptServer.Account.Requests {
|
|
||||||
public class PostContactsRequest : IValidatableObject {
|
|
||||||
|
|
||||||
public required string[] Contacts { get; set; }
|
|
||||||
|
|
||||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
|
||||||
|
|
||||||
if (Contacts == null || Contacts.Length == 0)
|
|
||||||
yield return new ValidationResult("Contacts is required", new[] { nameof(Contacts) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MaksIT.Models.LetsEncryptServer.Account.Requests {
|
|
||||||
public class PostHostnamesRequest : IValidatableObject {
|
|
||||||
public required string[] Hostnames { get; set; }
|
|
||||||
|
|
||||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
|
||||||
if (Hostnames == null || Hostnames.Length == 0)
|
|
||||||
yield return new ValidationResult("Hostnames is required", new[] { nameof(Hostnames) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MaksIT.Models.LetsEncryptServer.Account.Requests {
|
|
||||||
public class PutAccountRequest : IValidatableObject {
|
|
||||||
public required string Description { get; set; }
|
|
||||||
public required string[] Contacts { get; set; }
|
|
||||||
|
|
||||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
|
||||||
if (string.IsNullOrWhiteSpace(Description))
|
|
||||||
yield return new ValidationResult("Description is required", new[] { nameof(Description) });
|
|
||||||
|
|
||||||
if (Contacts == null || Contacts.Length == 0)
|
|
||||||
yield return new ValidationResult("Contacts is required", new[] { nameof(Contacts) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
|
|
||||||
|
|
||||||
namespace MaksIT.Models.LetsEncryptServer.Account.Requests {
|
|
||||||
public class PutContactsRequest : IValidatableObject {
|
|
||||||
|
|
||||||
public required string[] Contacts { get; set; }
|
|
||||||
|
|
||||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
|
||||||
|
|
||||||
if (Contacts == null || Contacts.Length == 0)
|
|
||||||
yield return new ValidationResult("Contacts is required", new[] { nameof(Contacts) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MaksIT.Models.LetsEncryptServer.Account.Requests {
|
|
||||||
public class PutHostnamesRequest : IValidatableObject {
|
|
||||||
public string[] Hostnames { get; set; }
|
|
||||||
|
|
||||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
|
||||||
if (Hostnames == null || Hostnames.Length == 0)
|
|
||||||
yield return new ValidationResult("Hostnames is required", new[] { nameof(Hostnames) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -15,7 +15,7 @@ namespace MaksIT.Models.LetsEncryptServer.Account.Responses {
|
|||||||
|
|
||||||
public string? ChallengeType { get; set; }
|
public string? ChallengeType { get; set; }
|
||||||
|
|
||||||
public HostnameResponse[]? Hostnames { get; set; }
|
public GetHostnameResponse[]? Hostnames { get; set; }
|
||||||
|
|
||||||
public required bool IsStaging { get; set; }
|
public required bool IsStaging { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MaksIT.Models.LetsEncryptServer.Account.Responses {
|
|
||||||
public class GetContactsResponse {
|
|
||||||
public string[] Contacts { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -5,7 +5,7 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MaksIT.Models.LetsEncryptServer.Account.Responses {
|
namespace MaksIT.Models.LetsEncryptServer.Account.Responses {
|
||||||
public class HostnameResponse {
|
public class GetHostnameResponse {
|
||||||
public required string Hostname { get; set; }
|
public required string Hostname { get; set; }
|
||||||
public DateTime Expires { get; set; }
|
public DateTime Expires { get; set; }
|
||||||
public bool IsUpcomingExpire { get; set; }
|
public bool IsUpcomingExpire { get; set; }
|
||||||
@ -1,12 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MaksIT.Models.LetsEncryptServer.Account.Responses {
|
|
||||||
|
|
||||||
public class GetHostnamesResponse {
|
|
||||||
public List<HostnameResponse> Hostnames { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user