1import * as gmailpostmastertools from "@distilled.cloud/gcp/gmailpostmastertools_v2";
2import * as Effect from "effect/Effect";
3import * as Stream from "effect/Stream";
4import { createPhysicalName } from "../../PhysicalName.ts";
5
6export const DEFAULT_DOMAIN_SUFFIX = "example.com";
7export const DEFAULT_EMAIL_DOMAIN = "example.com";
8export const DEFAULT_PERMISSION = "READER";
9export const DOMAIN_PREFIX = "domains/";
10export const USERS_SEGMENT = "/users/";
11
12const emptyList = <A>() => Effect.succeed([] as A[]);
13
14export const lastSegment = (value: string) => {
15 const trimmed = value.replace(/\/+$/, "");
16 const parts = trimmed.split("/");
17 return parts[parts.length - 1] || trimmed;
18};
19
20export const sameText = (left: string | undefined, right: string | undefined) =>
21 (left ?? "") === (right ?? "");
22
23export const normalize = (value: string | undefined) =>
24 (value ?? "").trim().toLowerCase();
25
26export const toDomainName = (value: string) => {
27 const trimmed = value.trim();
28 if (trimmed.length === 0) return "";
29 if (trimmed.startsWith(DOMAIN_PREFIX)) return trimmed;
30 return `${DOMAIN_PREFIX}${trimmed}`;
31};
32
33export const domainIdOf = (value: string | undefined) => {
34 const trimmed = (value ?? "").trim();
35 if (trimmed.length === 0) return "";
36 if (trimmed.startsWith(DOMAIN_PREFIX)) {
37 return trimmed.slice(DOMAIN_PREFIX.length).split("/")[0] ?? trimmed;
38 }
39 return trimmed;
40};
41
42export const userIdOf = (value: string | undefined) => {
43 if (value === undefined || value.length === 0) return "";
44 const usersAt = value.indexOf(USERS_SEGMENT);
45 if (usersAt >= 0) return value.slice(usersAt + USERS_SEGMENT.length);
46 if (value.startsWith("users/")) return value.slice("users/".length);
47 return lastSegment(value);
48};
49
50export const toUserName = (parent: string, userId: string) => {
51 if (userId.startsWith(DOMAIN_PREFIX) && userId.includes(USERS_SEGMENT)) {
52 return userId;
53 }
54 const domain = toDomainName(parent);
55 const user = userId.startsWith("users/")
56 ? userId.slice("users/".length)
57 : userIdOf(userId) || userId;
58 if (domain.length === 0 || user.length === 0) return "";
59 return `${domain}${USERS_SEGMENT}${user}`;
60};
61
62export const parentOfUserName = (name: string) => {
63 const usersAt = name.indexOf(USERS_SEGMENT);
64 if (usersAt > 0) return name.slice(0, usersAt);
65 return "";
66};
67
68export const userEmailOf = (user: gmailpostmastertools.User) =>
69 user.user ?? userIdOf(user.name);
70
71export const fullUserName = (
72 parent: string,
73 user: gmailpostmastertools.User,
74) => {
75 const name = user.name ?? "";
76 if (name.startsWith(DOMAIN_PREFIX) && name.includes(USERS_SEGMENT)) {
77 return name;
78 }
79 return toUserName(parent, userEmailOf(user) || name);
80};
81
82const dnsLabel = (value: string, maxLength: number) => {
83 let label = /^[a-z]/.test(value) ? value : `a${value}`;
84 label = label.replace(/^-+|-+$/g, "");
85 if (!label.startsWith("alchemy-")) {
86 label = `alchemy-${label}`;
87 }
88 return label.slice(0, maxLength).replace(/-+$/g, "");
89};
90
91export const isAlchemyDomain = (name: string | undefined) => {
92 const id = domainIdOf(name).toLowerCase();
93 const first = id.split(".")[0] ?? "";
94 return first.startsWith("alchemy-") || id.includes("alchemy-");
95};
96
97export const isAlchemyEmail = (email: string | undefined) => {
98 const value = normalize(email);
99 const local = value.split("@")[0] ?? "";
100 return local.startsWith("alchemy-") || local.includes("+alchemy-");
101};
102
103export const isPatchablePermission = (permission: string | undefined) =>
104 permission === "READER" || permission === "ADMIN";
105
106export const toGeneratedDomainId = (
107 id: string,
108 requested: string | undefined,
109 existing: string | undefined,
110) =>
111 Effect.gen(function* () {
112 if (requested !== undefined && requested.length > 0) {
113 return requested.toLowerCase();
114 }
115 if (existing !== undefined && existing.length > 0) {
116 return existing.toLowerCase();
117 }
118 const generated = yield* createPhysicalName({
119 id,
120 maxLength: 48,
121 lowercase: true,
122 });
123 const label = dnsLabel(generated, 63);
124 return `${label}.${DEFAULT_DOMAIN_SUFFIX}`;
125 });
126
127export const toGeneratedUserId = (
128 id: string,
129 requested: string | undefined,
130 existing: string | undefined,
131) =>
132 Effect.gen(function* () {
133 if (requested !== undefined && requested.length > 0) {
134 return requested.toLowerCase();
135 }
136 if (existing !== undefined && existing.length > 0) {
137 return existing.toLowerCase();
138 }
139 const generated = yield* createPhysicalName({
140 id,
141 maxLength: 40,
142 lowercase: true,
143 });
144 const local = dnsLabel(generated, 64);
145 return `${local}@${DEFAULT_EMAIL_DOMAIN}`;
146 });
147
148export const catchMissing = <A, E extends { readonly _tag: string }, R>(
149 effect: Effect.Effect<A, E, R>,
150) =>
151 effect.pipe(
152 Effect.catchIf(
153 (error): error is E & { readonly _tag: "NotFound" } =>
154 error._tag === "NotFound",
155 () => Effect.succeed(undefined),
156 ),
157 );
158
159export const ignoreMissing = <E extends { readonly _tag: string }, R>(
160 effect: Effect.Effect<unknown, E, R>,
161) =>
162 effect.pipe(
163 Effect.catchIf(
164 (error): error is E & { readonly _tag: "NotFound" } =>
165 error._tag === "NotFound",
166 () => Effect.void,
167 ),
168 );
169
170export const getDomain = (name: string) =>
171 name.length === 0
172 ? Effect.succeed(undefined)
173 : catchMissing(
174 gmailpostmastertools.getDomains({ name: toDomainName(name) }),
175 );
176
177export const getUser = (name: string) =>
178 name.length === 0
179 ? Effect.succeed(undefined)
180 : catchMissing(gmailpostmastertools.getDomainsUsers({ name }));
181
182export const listDomains = () =>
183 gmailpostmastertools.listDomains.pages({ pageSize: 200 }).pipe(
184 Stream.flatMap((page) => Stream.fromIterable(page.domains ?? [])),
185 Stream.runCollect,
186 Effect.map((chunk) => Array.from(chunk)),
187 Effect.catchTag("NotFound", () => emptyList<gmailpostmastertools.Domain>()),
188 );
189
190export const listUsers = (parent: string) =>
191 parent.length === 0
192 ? emptyList<gmailpostmastertools.User>()
193 : gmailpostmastertools.listDomainsUsers
194 .pages({ parent: toDomainName(parent), pageSize: 200 })
195 .pipe(
196 Stream.flatMap((page) => Stream.fromIterable(page.users ?? [])),
197 Stream.runCollect,
198 Effect.map((chunk) => Array.from(chunk)),
199 Effect.catchTag("NotFound", () =>
200 emptyList<gmailpostmastertools.User>(),
201 ),
202 );
203
204export const listOwnedDomains = () =>
205 listDomains().pipe(
206 Effect.map((domains) =>
207 domains.filter((domain) => isAlchemyDomain(domain.name)),
208 ),
209 );
210
211export const listOwnedUsers = () =>
212 Effect.gen(function* () {
213 const domains = yield* listDomains();
214 const users = yield* Effect.forEach(
215 domains.filter((domain) => (domain.name ?? "").length > 0),
216 (domain) => listUsers(domain.name ?? ""),
217 { concurrency: 4 },
218 );
219 return users.flat().filter((user) => isAlchemyEmail(userEmailOf(user)));
220 });
221
222export const findUser = (parent: string, userId: string, name: string) =>
223 Effect.gen(function* () {
224 const existing = yield* getUser(name || toUserName(parent, userId));
225 if (existing !== undefined) return existing;
226 if (userId.length === 0 && name.length === 0) return undefined;
227 const users = yield* listUsers(parent);
228 const wanted = normalize(userId || userIdOf(name));
229 return users.find(
230 (user) =>
231 normalize(userEmailOf(user)) === wanted ||
232 normalize(user.name) === normalize(name),
233 );
234 });