1import * as account from "@distilled.cloud/aws/account";
2import * as Effect from "effect/Effect";
3import { isResolved } from "../../Diff.ts";
4import * as Provider from "../../Provider.ts";
5import { Resource } from "../../Resource.ts";
6import type { Providers } from "../Providers.ts";
7import { unwrapSensitive } from "./internal.ts";
8
9export interface ContactInformationProps {
10 /** Full name of the primary contact. */
11 fullName: string;
12 /** First line of the primary contact's mailing address. */
13 addressLine1: string;
14 /** Second line of the mailing address, if any. */
15 addressLine2?: string;
16 /** Third line of the mailing address, if any. */
17 addressLine3?: string;
18 /** City of the primary contact address. */
19 city: string;
20 /** State or region of the primary contact address, if applicable. */
21 stateOrRegion?: string;
22 /** District or county of the primary contact address, if applicable. */
23 districtOrCounty?: string;
24 /** Postal code of the primary contact address. */
25 postalCode: string;
26 /** ISO-3166 two-letter country code of the primary contact address. */
27 countryCode: string;
28 /** Phone number of the primary contact. */
29 phoneNumber: string;
30 /** Company name associated with the primary contact, if any. */
31 companyName?: string;
32 /** Website URL associated with the primary contact, if any. */
33 websiteUrl?: string;
34 /**
35 * Account ID to operate on. Only usable from an Organizations management or
36 * delegated-admin account with trusted access enabled; omit to target the
37 * calling account.
38 */
39 accountId?: string;
40}
41
42export interface ContactInformation extends Resource<
43 "AWS.Account.ContactInformation",
44 ContactInformationProps,
45 {
46 fullName: string;
47 addressLine1: string;
48 addressLine2?: string;
49 addressLine3?: string;
50 city: string;
51 stateOrRegion?: string;
52 districtOrCounty?: string;
53 postalCode: string;
54 countryCode: string;
55 phoneNumber: string;
56 companyName?: string;
57 websiteUrl?: string;
58 },
59 never,
60 Providers
61> {}
62
63/**
64 * The primary contact information of an AWS account — the account-global
65 * mailing address and phone number AWS uses to reach the account owner. Every
66 * account has exactly one primary contact; this resource upserts it via
67 * `account:PutContactInformation`. AWS does not allow deleting the primary
68 * contact, so destroying the resource stops managing it and leaves the last
69 * value in place.
70 *
71 * ### Setting the Primary Contact
72 * **Example:** Primary Contact for the Calling Account
73 * ```typescript
74 * const contact = yield* ContactInformation("PrimaryContact", {
75 * fullName: "Jane Doe",
76 * addressLine1: "123 Any Street",
77 * city: "Seattle",
78 * stateOrRegion: "WA",
79 * postalCode: "98101",
80 * countryCode: "US",
81 * phoneNumber: "+12065551234",
82 * companyName: "Acme Corp",
83 * websiteUrl: "https://acme.example.com",
84 * });
85 * ```
86 *
87 * **Example:** Primary Contact for an Organizations Member Account
88 * ```typescript
89 * const contact = yield* ContactInformation("MemberContact", {
90 * fullName: "Acme Ops",
91 * addressLine1: "123 Any Street",
92 * city: "Seattle",
93 * postalCode: "98101",
94 * countryCode: "US",
95 * phoneNumber: "+12065551234",
96 * accountId: "123456789012",
97 * });
98 * ```
99 *
100 * @resource
101 */
102export const ContactInformation = Resource<ContactInformation>(
103 "AWS.Account.ContactInformation",
104);
105
106export const ContactInformationProvider = () =>
107 Provider.effect(
108 ContactInformation,
109 Effect.gen(function* () {
110 const toAttributes = (contact: account.ContactInformation) => ({
111 fullName: unwrapSensitive(contact.FullName) ?? "",
112 addressLine1: unwrapSensitive(contact.AddressLine1) ?? "",
113 addressLine2: unwrapSensitive(contact.AddressLine2),
114 addressLine3: unwrapSensitive(contact.AddressLine3),
115 city: unwrapSensitive(contact.City) ?? "",
116 stateOrRegion: unwrapSensitive(contact.StateOrRegion),
117 districtOrCounty: unwrapSensitive(contact.DistrictOrCounty),
118 postalCode: unwrapSensitive(contact.PostalCode) ?? "",
119 countryCode: unwrapSensitive(contact.CountryCode) ?? "",
120 phoneNumber: unwrapSensitive(contact.PhoneNumber) ?? "",
121 companyName: unwrapSensitive(contact.CompanyName),
122 websiteUrl: unwrapSensitive(contact.WebsiteUrl),
123 });
124
125 const observe = (accountId: string | undefined) =>
126 account.getContactInformation({ AccountId: accountId }).pipe(
127 Effect.map((r) =>
128 r.ContactInformation
129 ? toAttributes(r.ContactInformation)
130 : undefined,
131 ),
132 Effect.catchTag("ResourceNotFoundException", () =>
133 Effect.succeed(undefined),
134 ),
135 );
136
137 return {
138 // Account-global singleton setting: nuke must not wipe the real
139 // primary contact of the account.
140 nuke: { skip: true },
141 stables: [],
142 // Targeting a different account manages a different singleton, so an
143 // accountId change replaces rather than mutating the old target.
144 diff: Effect.fn(function* ({ olds, news }) {
145 if (!isResolved(news)) return;
146 if ((olds?.accountId ?? news.accountId) !== news.accountId) {
147 return { action: "replace" } as const;
148 }
149 }),
150 read: Effect.fn(function* ({ olds }) {
151 return yield* observe(olds?.accountId);
152 }),
153 reconcile: Effect.fn(function* ({ news, session }) {
154 // The primary contact is a singleton driven entirely by
155 // `putContactInformation`, which is a full upsert — the request
156 // payload *is* the desired state.
157 yield* account.putContactInformation({
158 ContactInformation: {
159 FullName: news.fullName,
160 AddressLine1: news.addressLine1,
161 AddressLine2: news.addressLine2,
162 AddressLine3: news.addressLine3,
163 City: news.city,
164 StateOrRegion: news.stateOrRegion,
165 DistrictOrCounty: news.districtOrCounty,
166 PostalCode: news.postalCode,
167 CountryCode: news.countryCode,
168 PhoneNumber: news.phoneNumber,
169 CompanyName: news.companyName,
170 WebsiteUrl: news.websiteUrl,
171 },
172 AccountId: news.accountId,
173 });
174 yield* session.note(news.fullName);
175 return {
176 fullName: news.fullName,
177 addressLine1: news.addressLine1,
178 addressLine2: news.addressLine2,
179 addressLine3: news.addressLine3,
180 city: news.city,
181 stateOrRegion: news.stateOrRegion,
182 districtOrCounty: news.districtOrCounty,
183 postalCode: news.postalCode,
184 countryCode: news.countryCode,
185 phoneNumber: news.phoneNumber,
186 companyName: news.companyName,
187 websiteUrl: news.websiteUrl,
188 };
189 }),
190 // Account-global singleton: return the one contact if it exists.
191 list: () =>
192 observe(undefined).pipe(
193 Effect.map((contact) => (contact ? [contact] : [])),
194 ),
195 // AWS does not allow deleting the primary contact of an account —
196 // destroy just stops managing it and leaves the last value in place.
197 delete: Effect.fn(function* () {}),
198 };
199 }),
200 );