Openstatus
www.openstatus.dev
1import { z } from "zod";
2
3import { TRPCError } from "@trpc/server";
4import { env } from "../env";
5import { createTRPCRouter, protectedProcedure } from "../trpc";
6
7export const domainConfigResponseSchema = z.object({
8 configuredBy: z
9 .union([z.literal("CNAME"), z.literal("A"), z.literal("http")])
10 .optional()
11 .nullable(),
12 acceptedChallenges: z
13 .array(z.union([z.literal("dns-01"), z.literal("http-01")]))
14 .optional()
15 .nullable(),
16 misconfigured: z.boolean().prefault(true).optional(),
17});
18
19export const domainResponseSchema = z.object({
20 name: z.string().optional(),
21 apexName: z.string().optional(),
22 projectId: z.string().optional(),
23 redirect: z.string().optional().nullable(),
24 redirectStatusCode: z
25 .union([z.literal(307), z.literal(301), z.literal(302), z.literal(308)])
26 .optional()
27 .nullable(),
28 gitBranch: z.string().optional().nullable(),
29 updatedAt: z.number().optional(),
30 createdAt: z.number().optional(),
31 verified: z.boolean().optional(),
32 verification: z
33 .array(
34 z.object({
35 type: z.string(),
36 domain: z.string(),
37 value: z.string(),
38 reason: z.string(),
39 }),
40 )
41 .optional(),
42});
43
44export type DomainVerificationResponse = z.infer<typeof domainResponseSchema>;
45export type DomainConfigResponse = z.infer<typeof domainConfigResponseSchema>;
46export type DomainResponse = z.infer<typeof domainResponseSchema>;
47export type DomainVerificationStatusProps =
48 | "Valid Configuration"
49 | "Invalid Configuration"
50 | "Pending Verification"
51 | "Domain Not Found"
52 | "Unknown Error";
53
54export const domainRouter = createTRPCRouter({
55 addDomainToVercel: protectedProcedure
56 .input(z.object({ domain: z.string() }))
57 .mutation(async (opts) => {
58 if (opts.input.domain.toLowerCase().includes("openstatus")) {
59 throw new TRPCError({
60 code: "BAD_REQUEST",
61 message: "Domain cannot contain 'openstatus'",
62 });
63 }
64
65 const data = await fetch(
66 `https://api.vercel.com/v9/projects/${env.PROJECT_ID_VERCEL}/domains?teamId=${env.TEAM_ID_VERCEL}`,
67 {
68 body: `{\n "name": "${opts.input.domain}"\n}`,
69 headers: {
70 Authorization: `Bearer ${env.VERCEL_AUTH_BEARER_TOKEN}`,
71 "Content-Type": "application/json",
72 },
73 method: "POST",
74 },
75 );
76 const json = await data.json();
77 console.log({ json });
78 return domainResponseSchema.parse(json);
79 }),
80 removeDomainFromVercelProject: protectedProcedure
81 .input(z.object({ domain: z.string() }))
82 .mutation(async (opts) => {
83 const data = await fetch(
84 `https://api.vercel.com/v9/projects/${env.PROJECT_ID_VERCEL}/domains/${opts.input.domain}?teamId=${env.TEAM_ID_VERCEL}`,
85 {
86 headers: {
87 Authorization: `Bearer ${env.VERCEL_AUTH_BEARER_TOKEN}`,
88 },
89 method: "DELETE",
90 },
91 );
92 return await data.json();
93 }),
94 // removeDomainFromVercelTeam: protectedProcedure
95 // .input(z.object({ domain: z.string() }))
96 // .mutation(async (opts) => {
97 // const data = await fetch(
98 // `https://api.vercel.com/v6/domains/${opts.input.domain}?teamId=${env.TEAM_ID_VERCEL}`,
99 // {
100 // headers: {
101 // Authorization: `Bearer ${env.VERCEL_AUTH_BEARER_TOKEN}`,
102 // },
103 // method: "DELETE",
104 // },
105 // );
106 // return await data.json();
107 // }),
108 getDomainResponse: protectedProcedure
109 .input(z.object({ domain: z.string().optional() }))
110 .query(async (opts) => {
111 if (!opts.input.domain) {
112 return null;
113 }
114 const data = await fetch(
115 `https://api.vercel.com/v9/projects/${env.PROJECT_ID_VERCEL}/domains/${opts.input.domain}?teamId=${env.TEAM_ID_VERCEL}`,
116 {
117 method: "GET",
118 headers: {
119 Authorization: `Bearer ${env.VERCEL_AUTH_BEARER_TOKEN}`,
120 "Content-Type": "application/json",
121 },
122 },
123 );
124 const json = await data.json();
125 const result = domainResponseSchema
126 .extend({
127 error: z
128 .object({
129 code: z.string(),
130 message: z.string(),
131 })
132 .optional(),
133 })
134 .parse(json);
135 console.log({ result });
136 return result;
137 }),
138 getConfigResponse: protectedProcedure
139 .input(z.object({ domain: z.string().optional() }))
140 .query(async (opts) => {
141 if (!opts.input.domain) {
142 return null;
143 }
144 const data = await fetch(
145 `https://api.vercel.com/v6/domains/${opts.input.domain}/config?teamId=${env.TEAM_ID_VERCEL}`,
146 {
147 method: "GET",
148 headers: {
149 Authorization: `Bearer ${env.VERCEL_AUTH_BEARER_TOKEN}`,
150 "Content-Type": "application/json",
151 },
152 },
153 );
154 const json = await data.json();
155 const result = domainConfigResponseSchema.parse(json);
156 return result;
157 }),
158 verifyDomain: protectedProcedure
159 .input(z.object({ domain: z.string().optional() }))
160 .query(async (opts) => {
161 if (!opts.input.domain) {
162 return null;
163 }
164 const data = await fetch(
165 `https://api.vercel.com/v9/projects/${env.PROJECT_ID_VERCEL}/domains/${opts.input.domain}/verify?teamId=${env.TEAM_ID_VERCEL}`,
166 {
167 method: "POST",
168 headers: {
169 Authorization: `Bearer ${env.VERCEL_AUTH_BEARER_TOKEN}`,
170 "Content-Type": "application/json",
171 },
172 },
173 );
174 const json = await data.json();
175 const result = domainResponseSchema.parse(json);
176 return result;
177 }),
178});