delete-domain route added (#267)
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
openapi: delete /v1/domains/{id}
|
||||||
|
---
|
||||||
@@ -620,6 +620,181 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"schema": {
|
||||||
|
"type": "number",
|
||||||
|
"nullable": false,
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"name": "id",
|
||||||
|
"in": "path"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Domain deleted successfully",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "The ID of the domain",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "The name of the domain",
|
||||||
|
"example": "example.com"
|
||||||
|
},
|
||||||
|
"teamId": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "The ID of the team",
|
||||||
|
"example": 1
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"NOT_STARTED",
|
||||||
|
"PENDING",
|
||||||
|
"SUCCESS",
|
||||||
|
"FAILED",
|
||||||
|
"TEMPORARY_FAILURE"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "us-east-1"
|
||||||
|
},
|
||||||
|
"clickTracking": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"openTracking": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"publicKey": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"dkimStatus": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"spfDetails": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"dmarcAdded": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"isVerifying": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"errorMessage": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"subdomain": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"verificationError": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"lastCheckedTime": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"dnsRecords": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"MX",
|
||||||
|
"TXT"
|
||||||
|
],
|
||||||
|
"description": "DNS record type",
|
||||||
|
"example": "TXT"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "DNS record name",
|
||||||
|
"example": "mail"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "DNS record value",
|
||||||
|
"example": "v=spf1 include:amazonses.com ~all"
|
||||||
|
},
|
||||||
|
"ttl": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "DNS record TTL",
|
||||||
|
"example": "Auto"
|
||||||
|
},
|
||||||
|
"priority": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true,
|
||||||
|
"description": "DNS record priority",
|
||||||
|
"example": "10"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"NOT_STARTED",
|
||||||
|
"PENDING",
|
||||||
|
"SUCCESS",
|
||||||
|
"FAILED",
|
||||||
|
"TEMPORARY_FAILURE"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"recommended": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Whether the record is recommended"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"type",
|
||||||
|
"name",
|
||||||
|
"value",
|
||||||
|
"ttl",
|
||||||
|
"status"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"teamId",
|
||||||
|
"status",
|
||||||
|
"publicKey",
|
||||||
|
"createdAt",
|
||||||
|
"updatedAt",
|
||||||
|
"dnsRecords"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/v1/emails/{emailId}": {
|
"/v1/emails/{emailId}": {
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { createRoute, z } from "@hono/zod-openapi";
|
||||||
|
import { PublicAPIApp } from "../../hono";
|
||||||
|
import { db } from "~/server/db";
|
||||||
|
import { UnsendApiError } from "../../api-error";
|
||||||
|
import { deleteDomain as deleteDomainService } from "~/server/service/domain-service";
|
||||||
|
|
||||||
|
const route = createRoute({
|
||||||
|
method: "delete",
|
||||||
|
path: "/v1/domains/{id}",
|
||||||
|
request: {
|
||||||
|
params: z.object({
|
||||||
|
id: z.coerce.number().openapi({
|
||||||
|
param: {
|
||||||
|
name: "id",
|
||||||
|
in: "path",
|
||||||
|
},
|
||||||
|
example: 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
success: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: "Domain deleted successfully",
|
||||||
|
},
|
||||||
|
403: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
error: z.string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
description: "Forbidden - API key doesn't have access",
|
||||||
|
},
|
||||||
|
404: {
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
error: z.string(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: "Domain not found",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function deleteDomain(app: PublicAPIApp) {
|
||||||
|
app.openapi(route, async (c) => {
|
||||||
|
const team = c.var.team;
|
||||||
|
const domainId = c.req.valid("param").id;
|
||||||
|
|
||||||
|
// Enforce API key domain restriction
|
||||||
|
if (team.apiKey.domainId && team.apiKey.domainId !== domainId) {
|
||||||
|
throw new UnsendApiError({
|
||||||
|
code: "FORBIDDEN",
|
||||||
|
message: "API key doesn't have access to this domain",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const domain = await db.domain.findFirst({
|
||||||
|
where: {
|
||||||
|
id: domainId,
|
||||||
|
teamId: team.id
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!domain) {
|
||||||
|
throw new UnsendApiError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Domain not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedDomain = await deleteDomainService(domainId);
|
||||||
|
|
||||||
|
return c.json(deletedDomain);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default deleteDomain;
|
||||||
@@ -14,6 +14,7 @@ import createDomain from "./api/domains/create-domain";
|
|||||||
import deleteContact from "./api/contacts/delete-contact";
|
import deleteContact from "./api/contacts/delete-contact";
|
||||||
import verifyDomain from "./api/domains/verify-domain";
|
import verifyDomain from "./api/domains/verify-domain";
|
||||||
import getDomain from "./api/domains/get-domain";
|
import getDomain from "./api/domains/get-domain";
|
||||||
|
import deleteDomain from "./api/domains/delete-domain";
|
||||||
import sendBatch from "./api/emails/batch-email";
|
import sendBatch from "./api/emails/batch-email";
|
||||||
|
|
||||||
export const app = getApp();
|
export const app = getApp();
|
||||||
@@ -23,6 +24,7 @@ getDomains(app);
|
|||||||
createDomain(app);
|
createDomain(app);
|
||||||
verifyDomain(app);
|
verifyDomain(app);
|
||||||
getDomain(app);
|
getDomain(app);
|
||||||
|
deleteDomain(app);
|
||||||
|
|
||||||
/**Email related APIs */
|
/**Email related APIs */
|
||||||
getEmail(app);
|
getEmail(app);
|
||||||
|
|||||||
@@ -34,4 +34,8 @@ class Domains:
|
|||||||
data, err = self.usesend.get(f"/domains/{domain_id}")
|
data, err = self.usesend.get(f"/domains/{domain_id}")
|
||||||
return (data, err) # type: ignore[return-value]
|
return (data, err) # type: ignore[return-value]
|
||||||
|
|
||||||
|
def delete(self, domain_id: int) -> Tuple[Optional[Domain], Optional[APIError]]:
|
||||||
|
data, err = self.usesend.delete(f"/domains/{domain_id}")
|
||||||
|
return (data, err) # type: ignore[return-value]
|
||||||
|
|
||||||
from .usesend import UseSend # noqa: E402 pylint: disable=wrong-import-position
|
from .usesend import UseSend # noqa: E402 pylint: disable=wrong-import-position
|
||||||
|
|||||||
@@ -37,6 +37,14 @@ type GetDomainResponse = {
|
|||||||
type GetDomainResponseSuccess =
|
type GetDomainResponseSuccess =
|
||||||
paths["/v1/domains/{id}"]["get"]["responses"]["200"]["content"]["application/json"];
|
paths["/v1/domains/{id}"]["get"]["responses"]["200"]["content"]["application/json"];
|
||||||
|
|
||||||
|
type DeleteDomainResponse = {
|
||||||
|
data: DeleteDomainResponseSuccess | null;
|
||||||
|
error: ErrorResponse | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DeleteDomainResponseSuccess =
|
||||||
|
paths["/v1/domains/{id}"]["delete"]["responses"]["200"]["content"]["application/json"];
|
||||||
|
|
||||||
export class Domains {
|
export class Domains {
|
||||||
constructor(private readonly usesend: UseSend) {
|
constructor(private readonly usesend: UseSend) {
|
||||||
this.usesend = usesend;
|
this.usesend = usesend;
|
||||||
@@ -50,7 +58,7 @@ export class Domains {
|
|||||||
async create(payload: CreateDomainPayload): Promise<CreateDomainResponse> {
|
async create(payload: CreateDomainPayload): Promise<CreateDomainResponse> {
|
||||||
const data = await this.usesend.post<CreateDomainResponseSuccess>(
|
const data = await this.usesend.post<CreateDomainResponseSuccess>(
|
||||||
"/domains",
|
"/domains",
|
||||||
payload
|
payload,
|
||||||
);
|
);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@@ -58,14 +66,22 @@ export class Domains {
|
|||||||
async verify(id: number): Promise<VerifyDomainResponse> {
|
async verify(id: number): Promise<VerifyDomainResponse> {
|
||||||
const data = await this.usesend.put<VerifyDomainResponseSuccess>(
|
const data = await this.usesend.put<VerifyDomainResponseSuccess>(
|
||||||
`/domains/${id}/verify`,
|
`/domains/${id}/verify`,
|
||||||
{}
|
{},
|
||||||
);
|
);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(id: number): Promise<GetDomainResponse> {
|
async get(id: number): Promise<GetDomainResponse> {
|
||||||
const data = await this.usesend.get<GetDomainResponseSuccess>(
|
const data = await this.usesend.get<GetDomainResponseSuccess>(
|
||||||
`/domains/${id}`
|
`/domains/${id}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(id: number): Promise<DeleteDomainResponse> {
|
||||||
|
const data = await this.usesend.delete<DeleteDomainResponseSuccess>(
|
||||||
|
`/domains/${id}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ErrorResponse } from "../types";
|
import { ErrorResponse } from "../types";
|
||||||
import { Contacts } from "./contact";
|
import { Contacts } from "./contact";
|
||||||
import { Emails } from "./email";
|
import { Emails } from "./email";
|
||||||
|
import { Domains } from "./domain";
|
||||||
|
|
||||||
const defaultBaseUrl = "https://app.usesend.com";
|
const defaultBaseUrl = "https://app.usesend.com";
|
||||||
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
||||||
@@ -15,12 +16,13 @@ export class UseSend {
|
|||||||
|
|
||||||
// readonly domains = new Domains(this);
|
// readonly domains = new Domains(this);
|
||||||
readonly emails = new Emails(this);
|
readonly emails = new Emails(this);
|
||||||
|
readonly domains = new Domains(this);
|
||||||
readonly contacts = new Contacts(this);
|
readonly contacts = new Contacts(this);
|
||||||
url = baseUrl;
|
url = baseUrl;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly key?: string,
|
readonly key?: string,
|
||||||
url?: string
|
url?: string,
|
||||||
) {
|
) {
|
||||||
if (!key) {
|
if (!key) {
|
||||||
if (typeof process !== "undefined" && process.env) {
|
if (typeof process !== "undefined" && process.env) {
|
||||||
@@ -29,7 +31,7 @@ export class UseSend {
|
|||||||
|
|
||||||
if (!this.key) {
|
if (!this.key) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Missing API key. Pass it to the constructor `new UseSend("us_123")`'
|
'Missing API key. Pass it to the constructor `new UseSend("us_123")`',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -46,7 +48,7 @@ export class UseSend {
|
|||||||
|
|
||||||
async fetchRequest<T>(
|
async fetchRequest<T>(
|
||||||
path: string,
|
path: string,
|
||||||
options = {}
|
options = {},
|
||||||
): Promise<{ data: T | null; error: ErrorResponse | null }> {
|
): Promise<{ data: T | null; error: ErrorResponse | null }> {
|
||||||
const response = await fetch(`${this.url}${path}`, options);
|
const response = await fetch(`${this.url}${path}`, options);
|
||||||
const defaultError = {
|
const defaultError = {
|
||||||
|
|||||||
Vendored
+113
-1
@@ -364,7 +364,119 @@ export interface paths {
|
|||||||
};
|
};
|
||||||
put?: never;
|
put?: never;
|
||||||
post?: never;
|
post?: never;
|
||||||
delete?: never;
|
delete: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
id: number;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description Domain deleted successfully */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
/**
|
||||||
|
* @description The ID of the domain
|
||||||
|
* @example 1
|
||||||
|
*/
|
||||||
|
id: number;
|
||||||
|
/**
|
||||||
|
* @description The name of the domain
|
||||||
|
* @example example.com
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* @description The ID of the team
|
||||||
|
* @example 1
|
||||||
|
*/
|
||||||
|
teamId: number;
|
||||||
|
/** @enum {string} */
|
||||||
|
status: "NOT_STARTED" | "PENDING" | "SUCCESS" | "FAILED" | "TEMPORARY_FAILURE";
|
||||||
|
/** @default us-east-1 */
|
||||||
|
region: string;
|
||||||
|
/** @default false */
|
||||||
|
clickTracking: boolean;
|
||||||
|
/** @default false */
|
||||||
|
openTracking: boolean;
|
||||||
|
publicKey: string;
|
||||||
|
dkimStatus?: string | null;
|
||||||
|
spfDetails?: string | null;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
/** @default false */
|
||||||
|
dmarcAdded: boolean;
|
||||||
|
/** @default false */
|
||||||
|
isVerifying: boolean;
|
||||||
|
errorMessage?: string | null;
|
||||||
|
subdomain?: string | null;
|
||||||
|
verificationError?: string | null;
|
||||||
|
lastCheckedTime?: string | null;
|
||||||
|
dnsRecords: {
|
||||||
|
/**
|
||||||
|
* @description DNS record type
|
||||||
|
* @example TXT
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
type: "MX" | "TXT";
|
||||||
|
/**
|
||||||
|
* @description DNS record name
|
||||||
|
* @example mail
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* @description DNS record value
|
||||||
|
* @example v=spf1 include:amazonses.com ~all
|
||||||
|
*/
|
||||||
|
value: string;
|
||||||
|
/**
|
||||||
|
* @description DNS record TTL
|
||||||
|
* @example Auto
|
||||||
|
*/
|
||||||
|
ttl: string;
|
||||||
|
/**
|
||||||
|
* @description DNS record priority
|
||||||
|
* @example 10
|
||||||
|
*/
|
||||||
|
priority?: string | null;
|
||||||
|
/** @enum {string} */
|
||||||
|
status: "NOT_STARTED" | "PENDING" | "SUCCESS" | "FAILED" | "TEMPORARY_FAILURE";
|
||||||
|
/** @description Whether the record is recommended */
|
||||||
|
recommended?: boolean;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Forbidden - API key doesn't have access to this domain */
|
||||||
|
403: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
/** @description Domain not found */
|
||||||
|
404: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
options?: never;
|
options?: never;
|
||||||
head?: never;
|
head?: never;
|
||||||
patch?: never;
|
patch?: never;
|
||||||
|
|||||||
Reference in New Issue
Block a user