Openstatus www.openstatus.dev
6
fork

Configure Feed

Select the types of activity you want to include in your feed.

๐Ÿ› headers and checker (#877)

authored by

Thibault Le Ouay and committed by
GitHub
30c7961a d59c4ea8

+152 -104
+31 -3
apps/checker/cmd/main.go
··· 24 24 25 25 type statusCode int 26 26 27 + // We should export it 28 + type PingResponse struct { 29 + RequestId int64 `json:"requestId,omitempty"` 30 + WorkspaceId int64 `json:"workspaceId,omitempty"` 31 + Status int `json:"status,omitempty"` 32 + Latency int64 `json:"latency"` 33 + Body string `json:"body,omitempty"` 34 + Headers string `json:"headers,omitempty"` 35 + Time int64 `json:"time"` 36 + Timing checker.Timing `json:"timing"` 37 + Region string `json:"region"` 38 + } 39 + 27 40 func (s statusCode) IsSuccessful() bool { 28 41 return s >= 200 && s < 300 29 42 } ··· 263 276 264 277 r.Region = flyRegion 265 278 279 + headersAsString, err := json.Marshal(r.Headers) 280 + if err != nil { 281 + return nil 282 + } 283 + 284 + tbData := PingResponse{ 285 + RequestId: req.RequestId, 286 + WorkspaceId: req.WorkspaceId, 287 + Status: r.Status, 288 + Latency: r.Latency, 289 + Body: r.Body, 290 + Headers: string(headersAsString), 291 + Time: r.Time, 292 + Timing: r.Timing, 293 + Region: r.Region, 294 + } 295 + 266 296 res = r 267 - res.RequestId = req.RequestId 268 - res.WorkspaceId = req.WorkspaceId 269 - if err := tinybirdClient.SendEvent(ctx, res, dataSourceName); err != nil { 297 + if err := tinybirdClient.SendEvent(ctx, tbData, dataSourceName); err != nil { 270 298 log.Ctx(ctx).Error().Err(err).Msg("failed to send event to tinybird") 271 299 } 272 300 return nil
+3 -1
apps/checker/request/request.go
··· 1 1 package request 2 2 3 - import "encoding/json" 3 + import ( 4 + "encoding/json" 5 + ) 4 6 5 7 type AssertionType string 6 8
+10 -10
apps/server/src/v1/check/post.ts
··· 107 107 if (aggregated) { 108 108 // This is ugly 109 109 const dnsArray = fulfilledRequest.map( 110 - (r) => r.timing.dnsDone - r.timing.dnsStart, 110 + (r) => r.timing.dnsDone - r.timing.dnsStart 111 111 ); 112 112 const connectArray = fulfilledRequest.map( 113 - (r) => r.timing.connectDone - r.timing.connectStart, 113 + (r) => r.timing.connectDone - r.timing.connectStart 114 114 ); 115 115 const tlsArray = fulfilledRequest.map( 116 - (r) => r.timing.tlsHandshakeDone - r.timing.tlsHandshakeStart, 116 + (r) => r.timing.tlsHandshakeDone - r.timing.tlsHandshakeStart 117 117 ); 118 118 const firstArray = fulfilledRequest.map( 119 - (r) => r.timing.firstByteDone - r.timing.firstByteStart, 119 + (r) => r.timing.firstByteDone - r.timing.firstByteStart 120 120 ); 121 121 const transferArray = fulfilledRequest.map( 122 - (r) => r.timing.transferDone - r.timing.transferStart, 122 + (r) => r.timing.transferDone - r.timing.transferStart 123 123 ); 124 124 const latencyArray = fulfilledRequest.map((r) => r.latency); 125 125 126 126 const dnsPercentile = percentile([50, 75, 95, 99], dnsArray) as number[]; 127 127 const connectPercentile = percentile( 128 128 [50, 75, 95, 99], 129 - connectArray, 129 + connectArray 130 130 ) as number[]; 131 131 const tlsPercentile = percentile([50, 75, 95, 99], tlsArray) as number[]; 132 132 const firstPercentile = percentile( 133 133 [50, 75, 95, 99], 134 - firstArray, 134 + firstArray 135 135 ) as number[]; 136 136 137 137 const transferPercentile = percentile( 138 138 [50, 75, 95, 99], 139 - transferArray, 139 + transferArray 140 140 ) as number[]; 141 141 const latencyPercentile = percentile( 142 142 [50, 75, 95, 99], 143 - latencyArray, 143 + latencyArray 144 144 ) as number[]; 145 145 146 146 const aggregatedDNS = AggregatedResponseSchema.parse({ ··· 212 212 aggregated: aggregatedResponse ? aggregatedResponse : undefined, 213 213 }); 214 214 215 - return c.json(responseResult); 215 + return c.json(responseResult, 200); 216 216 }); 217 217 }
+3 -3
apps/server/src/v1/incidents/get.ts
··· 40 40 .where( 41 41 and( 42 42 eq(incidentTable.workspaceId, Number(workspaceId)), 43 - eq(incidentTable.id, Number(id)), 44 - ), 43 + eq(incidentTable.id, Number(id)) 44 + ) 45 45 ) 46 46 .get(); 47 47 ··· 51 51 52 52 const data = IncidentSchema.parse(_incident); 53 53 54 - return c.json(data); 54 + return c.json(data, 200); 55 55 }); 56 56 }
+1 -1
apps/server/src/v1/incidents/get_all.ts
··· 42 42 } 43 43 44 44 const returnValues = z.array(IncidentSchema).parse(_incidents); // TODO: think of using safeParse with SchemaError.fromZod 45 - return c.json(returnValues); 45 + return c.json(returnValues, 200); 46 46 }); 47 47 }
+3 -3
apps/server/src/v1/incidents/put.ts
··· 59 59 .where( 60 60 and( 61 61 eq(incidentTable.id, Number(id)), 62 - eq(incidentTable.workspaceId, Number(workspaceId)), 63 - ), 62 + eq(incidentTable.workspaceId, Number(workspaceId)) 63 + ) 64 64 ) 65 65 .get(); 66 66 ··· 81 81 82 82 const data = IncidentSchema.parse(_newIncident); 83 83 84 - return c.json(data); 84 + return c.json(data, 200); 85 85 }); 86 86 }
+15
apps/server/src/v1/middleware.test.ts
··· 1 + import { expect, test } from "bun:test"; 2 + 3 + import { api } from "./index"; 4 + 5 + test("Middleware error should return json", async () => { 6 + const res = await api.request("/status_report/1", {}); 7 + 8 + const json = await res.json(); 9 + expect(res.status).toBe(401); 10 + expect(json).toMatchObject({ 11 + code: "UNAUTHORIZED", 12 + message: "Unauthorized", 13 + docs: "https://docs.openstatus.dev/api-references/errors/code/UNAUTHORIZED", 14 + }); 15 + });
+8 -6
apps/server/src/v1/middleware.ts
··· 5 5 import { workspace } from "@openstatus/db/src/schema"; 6 6 import { getPlanConfig } from "@openstatus/plans"; 7 7 import type { Variables } from "./index"; 8 + import { HTTPException } from "hono/http-exception"; 8 9 9 10 export async function middleware( 10 11 c: Context<{ Variables: Variables }, "/*">, 11 - next: Next, 12 + next: Next 12 13 ) { 13 14 const key = c.req.header("x-openstatus-key"); 14 - if (!key) return c.text("Unauthorized", 401); 15 + if (!key) throw new HTTPException(401, { message: "Unauthorized" }); 15 16 16 17 const { error, result } = 17 18 process.env.NODE_ENV === "production" 18 19 ? await verifyKey(key) 19 20 : { result: { valid: true, ownerId: "1" }, error: null }; 20 21 21 - if (error) return c.text("Internal Server Error", 500); 22 - if (!result.valid) return c.text("Unauthorized", 401); 23 - if (!result.ownerId) return c.text("Unauthorized", 401); 22 + if (error) throw new HTTPException(500, { message: error.message }); 23 + if (!result.valid) throw new HTTPException(401, { message: "Unauthorized" }); 24 + if (!result.ownerId) 25 + throw new HTTPException(401, { message: "Unauthorized" }); 24 26 25 27 const _workspace = await db 26 28 .select() ··· 30 32 31 33 if (!_workspace) { 32 34 console.error("Workspace not found"); 33 - return c.text("Unauthorized", 401); 35 + throw new HTTPException(401, { message: "Unauthorized" }); 34 36 } 35 37 36 38 c.set("workspacePlan", getPlanConfig(_workspace.plan));
+1 -1
apps/server/src/v1/monitors/delete.ts
··· 56 56 57 57 // FIXME: Remove all relations of the monitor from all notifications, pages,.... 58 58 59 - return c.json({}); 59 + return c.json({}, 200); 60 60 }); 61 61 }
+3 -3
apps/server/src/v1/monitors/get.ts
··· 41 41 and( 42 42 eq(monitor.id, Number(id)), 43 43 eq(monitor.workspaceId, Number(workspaceId)), 44 - isNull(monitor.deletedAt), 45 - ), 44 + isNull(monitor.deletedAt) 45 + ) 46 46 ) 47 47 .get(); 48 48 ··· 52 52 53 53 const data = MonitorSchema.parse(_monitor); 54 54 55 - return c.json(data); 55 + return c.json(data, 200); 56 56 }); 57 57 }
+3 -3
apps/server/src/v1/monitors/get_all.ts
··· 37 37 .where( 38 38 and( 39 39 eq(monitor.workspaceId, Number(workspaceId)), 40 - isNull(monitor.deletedAt), 41 - ), 40 + isNull(monitor.deletedAt) 41 + ) 42 42 ) 43 43 .all(); 44 44 ··· 48 48 49 49 const data = z.array(MonitorSchema).parse(_monitors); 50 50 51 - return c.json(data); 51 + return c.json(data, 200); 52 52 }); 53 53 }
+3 -3
apps/server/src/v1/monitors/post.ts
··· 54 54 .where( 55 55 and( 56 56 eq(monitor.workspaceId, Number(workspaceId)), 57 - isNull(monitor.deletedAt), 58 - ), 57 + isNull(monitor.deletedAt) 58 + ) 59 59 ) 60 60 .all() 61 61 )[0].count; ··· 97 97 98 98 const data = MonitorSchema.parse(_newMonitor); 99 99 100 - return c.json(data); 100 + return c.json(data, 200); 101 101 }); 102 102 }
+1 -1
apps/server/src/v1/monitors/put.ts
··· 81 81 82 82 const data = MonitorSchema.parse(_newMonitor); 83 83 84 - return c.json(data); 84 + return c.json(data, 200); 85 85 }); 86 86 }
+8 -7
apps/server/src/v1/monitors/summary/get.ts
··· 65 65 and( 66 66 eq(monitor.id, Number(id)), 67 67 eq(monitor.workspaceId, Number(workspaceId)), 68 - isNull(monitor.deletedAt), 69 - ), 68 + isNull(monitor.deletedAt) 69 + ) 70 70 ) 71 71 .get(); 72 72 ··· 75 75 } 76 76 77 77 const cache = await redis.get<z.infer<typeof dailyStatsSchemaArray>>( 78 - `${id}-daily-stats`, 78 + `${id}-daily-stats` 79 79 ); 80 80 if (cache) { 81 81 console.log("fetching from cache"); 82 - return c.json({ 83 - data: cache, 84 - }); 82 + return c.json({ data: cache }, 200); 85 83 } 86 84 87 85 // FIXME: we should use the OSTinybird client ··· 90 88 monitorId: id, 91 89 }); 92 90 91 + if (res === undefined) { 92 + throw new HTTPException(404, { message: "Not Found" }); 93 + } 93 94 await redis.set(`${id}-daily-stats`, res, { ex: 600 }); 94 95 95 - return c.json({ data: res }); 96 + return c.json({ data: res }, 200); 96 97 }); 97 98 }
+3 -3
apps/server/src/v1/notifications/get.ts
··· 43 43 .where( 44 44 and( 45 45 eq(page.workspaceId, Number(workspaceId)), 46 - eq(notification.id, Number(id)), 47 - ), 46 + eq(notification.id, Number(id)) 47 + ) 48 48 ) 49 49 .get(); 50 50 ··· 66 66 monitors, 67 67 }); 68 68 69 - return c.json(data); 69 + return c.json(data, 200); 70 70 }); 71 71 }
+1 -1
apps/server/src/v1/notifications/get_all.ts
··· 64 64 data.push(p); 65 65 } 66 66 67 - return c.json(data); 67 + return c.json(data, 200); 68 68 }); 69 69 }
+3 -3
apps/server/src/v1/notifications/post.ts
··· 75 75 and( 76 76 inArray(monitor.id, monitors), 77 77 eq(monitor.workspaceId, Number(workspaceId)), 78 - isNull(monitor.deletedAt), 79 - ), 78 + isNull(monitor.deletedAt) 79 + ) 80 80 ) 81 81 .all(); 82 82 ··· 113 113 monitors, 114 114 payload: _payload, 115 115 }); 116 - return c.json(data); 116 + return c.json(data, 200); 117 117 }); 118 118 }
+4 -4
apps/server/src/v1/pageSubscribers/post.ts
··· 49 49 .select() 50 50 .from(page) 51 51 .where( 52 - and(eq(page.id, Number(id)), eq(page.workspaceId, Number(workspaceId))), 52 + and(eq(page.id, Number(id)), eq(page.workspaceId, Number(workspaceId))) 53 53 ) 54 54 .get(); 55 55 ··· 63 63 .where( 64 64 and( 65 65 eq(pageSubscriber.email, input.email), 66 - eq(pageSubscriber.pageId, Number(id)), 67 - ), 66 + eq(pageSubscriber.pageId, Number(id)) 67 + ) 68 68 ) 69 69 .get(); 70 70 ··· 101 101 102 102 const data = PageSubscriberSchema.parse(_statusReportSubscriberUpdate); 103 103 104 - return c.json(data); 104 + return c.json(data, 200); 105 105 }); 106 106 }
+2 -2
apps/server/src/v1/pages/get.ts
··· 38 38 .select() 39 39 .from(page) 40 40 .where( 41 - and(eq(page.workspaceId, Number(workspaceId)), eq(page.id, Number(id))), 41 + and(eq(page.workspaceId, Number(workspaceId)), eq(page.id, Number(id))) 42 42 ) 43 43 .get(); 44 44 ··· 48 48 49 49 const data = PageSchema.parse(_page); 50 50 51 - return c.json(data); 51 + return c.json(data, 200); 52 52 }); 53 53 }
+1 -1
apps/server/src/v1/pages/get_all.ts
··· 40 40 41 41 const data = z.array(PageSchema).parse(_pages); 42 42 43 - return c.json(data); 43 + return c.json(data, 200); 44 44 }); 45 45 }
+3 -3
apps/server/src/v1/pages/post.ts
··· 95 95 and( 96 96 inArray(monitor.id, monitorIds), 97 97 eq(monitor.workspaceId, Number(workspaceId)), 98 - isNull(monitor.deletedAt), 99 - ), 98 + isNull(monitor.deletedAt) 99 + ) 100 100 ) 101 101 .all(); 102 102 ··· 128 128 } 129 129 } 130 130 const data = PageSchema.parse(_page); 131 - return c.json(data); 131 + return c.json(data, 200); 132 132 }); 133 133 }
+6 -6
apps/server/src/v1/pages/put.ts
··· 59 59 .select() 60 60 .from(page) 61 61 .where( 62 - and(eq(page.id, Number(id)), eq(page.workspaceId, Number(workspaceId))), 62 + and(eq(page.id, Number(id)), eq(page.workspaceId, Number(workspaceId))) 63 63 ) 64 64 .get(); 65 65 ··· 98 98 and( 99 99 inArray(monitor.id, monitorIds), 100 100 eq(monitor.workspaceId, Number(workspaceId)), 101 - isNull(monitor.deletedAt), 102 - ), 101 + isNull(monitor.deletedAt) 102 + ) 103 103 ) 104 104 .all(); 105 105 ··· 132 132 .where( 133 133 and( 134 134 inArray(monitorsToPages.monitorId, removedMonitors), 135 - eq(monitorsToPages.pageId, newPage.id), 136 - ), 135 + eq(monitorsToPages.pageId, newPage.id) 136 + ) 137 137 ); 138 138 } 139 139 ··· 154 154 155 155 const data = PageSchema.parse(newPage); 156 156 157 - return c.json(data); 157 + return c.json(data, 200); 158 158 }); 159 159 }
+5 -5
apps/server/src/v1/statusReportUpdates/get.ts
··· 30 30 }); 31 31 32 32 export function registerGetStatusReportUpdate( 33 - api: typeof statusReportUpdatesApi, 33 + api: typeof statusReportUpdatesApi 34 34 ) { 35 35 return api.openapi(getRoute, async (c) => { 36 36 const workspaceId = c.get("workspaceId"); ··· 43 43 statusReport, 44 44 and( 45 45 eq(statusReport.id, statusReportUpdate.statusReportId), 46 - eq(statusReport.workspaceId, Number(workspaceId)), 47 - ), 46 + eq(statusReport.workspaceId, Number(workspaceId)) 47 + ) 48 48 ) 49 49 .where(eq(statusReportUpdate.id, Number(id))) 50 50 .get(); ··· 54 54 } 55 55 56 56 const data = StatusReportUpdateSchema.parse( 57 - _statusReportJoin.status_report_update, 57 + _statusReportJoin.status_report_update 58 58 ); 59 59 60 - return c.json(data); 60 + return c.json(data, 200); 61 61 }); 62 62 }
+7 -7
apps/server/src/v1/statusReportUpdates/post.ts
··· 44 44 }); 45 45 46 46 export function registerPostStatusReportUpdate( 47 - api: typeof statusReportUpdatesApi, 47 + api: typeof statusReportUpdatesApi 48 48 ) { 49 49 return api.openapi(createStatusUpdate, async (c) => { 50 50 const workspaceId = c.get("workspaceId"); ··· 57 57 .where( 58 58 and( 59 59 eq(statusReport.id, input.statusReportId), 60 - eq(statusReport.workspaceId, Number(workspaceId)), 61 - ), 60 + eq(statusReport.workspaceId, Number(workspaceId)) 61 + ) 62 62 ) 63 63 .get(); 64 64 ··· 95 95 .where( 96 96 and( 97 97 eq(pageSubscriber.pageId, currentPage.pageId), 98 - isNotNull(pageSubscriber.acceptedAt), 99 - ), 98 + isNotNull(pageSubscriber.acceptedAt) 99 + ) 100 100 ) 101 101 .all(); 102 102 ··· 107 107 .get(); 108 108 if (!pageInfo) continue; 109 109 const subscribersEmails = subscribers.map( 110 - (subscriber) => subscriber.email, 110 + (subscriber) => subscriber.email 111 111 ); 112 112 113 113 // TODO: verify if we leak any email data here ··· 123 123 124 124 const data = StatusReportUpdateSchema.parse(_statusReportUpdate); 125 125 126 - return c.json(data); 126 + return c.json(data, 200); 127 127 }); 128 128 }
+3 -3
apps/server/src/v1/statusReports/delete.ts
··· 40 40 .where( 41 41 and( 42 42 eq(statusReport.id, Number(id)), 43 - eq(statusReport.workspaceId, Number(workspaceId)), 44 - ), 43 + eq(statusReport.workspaceId, Number(workspaceId)) 44 + ) 45 45 ) 46 46 .get(); 47 47 ··· 54 54 .where(eq(statusReport.id, Number(id))) 55 55 .run(); 56 56 57 - return c.json({}); 57 + return c.json({}, 200); 58 58 }); 59 59 }
+2 -2
apps/server/src/v1/statusReports/get.ts
··· 42 42 }, 43 43 where: and( 44 44 eq(statusReport.workspaceId, Number(workspaceId)), 45 - eq(statusReport.id, Number(id)), 45 + eq(statusReport.id, Number(id)) 46 46 ), 47 47 }); 48 48 ··· 73 73 statusReportUpdateIds: statusReportUpdates.map((update) => update.id), 74 74 }); 75 75 76 - return c.json(data); 76 + return c.json(data, 200); 77 77 }); 78 78 }
+2 -2
apps/server/src/v1/statusReports/get_all.ts
··· 50 50 statusReportUpdateIds: r.statusReportUpdates.map((u) => u.id), 51 51 pageIds: r.pagesToStatusReports.map((p) => p.pageId), 52 52 monitorIds: r.monitorsToStatusReports.map((m) => m.monitorId), 53 - })), 53 + })) 54 54 ); 55 55 56 - return c.json(data); 56 + return c.json(data, 200); 57 57 }); 58 58 }
+11 -11
apps/server/src/v1/statusReports/post.ts
··· 72 72 and( 73 73 eq(monitor.workspaceId, Number(workspaceId)), 74 74 inArray(monitor.id, monitorIds), 75 - isNull(monitor.deletedAt), 76 - ), 75 + isNull(monitor.deletedAt) 76 + ) 77 77 ) 78 78 .all(); 79 79 ··· 89 89 .where( 90 90 and( 91 91 eq(page.workspaceId, Number(workspaceId)), 92 - inArray(page.id, pageIds), 93 - ), 92 + inArray(page.id, pageIds) 93 + ) 94 94 ) 95 95 .all(); 96 96 ··· 127 127 pageId: id, 128 128 statusReportId: _newStatusReport.id, 129 129 }; 130 - }), 130 + }) 131 131 ) 132 132 .returning(); 133 133 } ··· 141 141 monitorId: id, 142 142 statusReportId: _newStatusReport.id, 143 143 }; 144 - }), 144 + }) 145 145 ) 146 146 .returning(); 147 147 } ··· 151 151 .select() 152 152 .from(pagesToStatusReports) 153 153 .where( 154 - eq(pagesToStatusReports.statusReportId, Number(_newStatusReport.id)), 154 + eq(pagesToStatusReports.statusReportId, Number(_newStatusReport.id)) 155 155 ) 156 156 .all(); 157 157 for (const currentPage of allPages) { ··· 161 161 .where( 162 162 and( 163 163 eq(pageSubscriber.pageId, currentPage.pageId), 164 - isNotNull(pageSubscriber.acceptedAt), 165 - ), 164 + isNotNull(pageSubscriber.acceptedAt) 165 + ) 166 166 ) 167 167 .all(); 168 168 const pageInfo = await db ··· 172 172 .get(); 173 173 if (!pageInfo) continue; 174 174 const subscribersEmails = subscribers.map( 175 - (subscriber) => subscriber.email, 175 + (subscriber) => subscriber.email 176 176 ); 177 177 await sendEmailHtml({ 178 178 to: subscribersEmails, ··· 191 191 statusReportUpdateIds: [_newStatusReportUpdate.id], 192 192 }); 193 193 194 - return c.json(data); 194 + return c.json(data, 200); 195 195 }); 196 196 }
+6 -6
apps/server/src/v1/statusReports/update/post.ts
··· 57 57 .where( 58 58 and( 59 59 eq(statusReport.id, Number(id)), 60 - eq(statusReport.workspaceId, Number(workspaceId)), 61 - ), 60 + eq(statusReport.workspaceId, Number(workspaceId)) 61 + ) 62 62 ) 63 63 .returning() 64 64 .get(); ··· 90 90 .where( 91 91 and( 92 92 eq(pageSubscriber.pageId, currentPage.pageId), 93 - isNotNull(pageSubscriber.acceptedAt), 94 - ), 93 + isNotNull(pageSubscriber.acceptedAt) 94 + ) 95 95 ) 96 96 .all(); 97 97 const pageInfo = await db ··· 101 101 .get(); 102 102 if (!pageInfo) continue; 103 103 const subscribersEmails = subscribers.map( 104 - (subscriber) => subscriber.email, 104 + (subscriber) => subscriber.email 105 105 ); 106 106 await sendEmailHtml({ 107 107 to: subscribersEmails, ··· 115 115 116 116 const data = StatusReportUpdateSchema.parse(_statusReportUpdate); 117 117 118 - return c.json(data); 118 + return c.json(data, 200); 119 119 }); 120 120 }