diff --git a/apps/checker/cmd/main.go b/apps/checker/cmd/main.go
index 4f587805..d324e44f 100644
--- a/apps/checker/cmd/main.go
+++ b/apps/checker/cmd/main.go
@@ -92,7 +92,6 @@ func main() {
if err != nil {
return fmt.Errorf("unable to ping: %w", err)
}
-
statusCode := statusCode(res.StatusCode)
// let's retry at least once if the status code is not successful.
if !statusCode.IsSuccessful() && called < 2 {
@@ -109,7 +108,9 @@ func main() {
Message: res.Message,
CronTimestamp: req.CronTimestamp,
})
- } else if req.Status == "error" && statusCode.IsSuccessful() {
+ }
+
+ if req.Status == "error" && statusCode.IsSuccessful() {
// Q: Why here we check the data before updating the status in this scenario?
checker.UpdateStatus(ctx, checker.UpdateData{
MonitorId: req.MonitorID,
@@ -120,7 +121,6 @@ func main() {
})
}
-
if err := tinybirdClient.SendEvent(ctx, res); err != nil {
log.Ctx(ctx).Error().Err(err).Msg("failed to send event to tinybird")
}
@@ -141,8 +141,6 @@ func main() {
log.Ctx(ctx).Error().Err(err).Msg("failed to send event to tinybird")
}
- // If the status was previously active, we update it to error.
- // Q: Why not always updating the status? My idea is that the checker should be dumb and only check the status and return it.
if req.Status == "active" {
checker.UpdateStatus(ctx, checker.UpdateData{
MonitorId: req.MonitorID,
@@ -151,6 +149,7 @@ func main() {
Region: flyRegion,
})
}
+
}
c.JSON(http.StatusOK, gin.H{"message": "ok"})
diff --git a/apps/checker/go.mod b/apps/checker/go.mod
index d3767120..ef21dbf1 100644
--- a/apps/checker/go.mod
+++ b/apps/checker/go.mod
@@ -10,6 +10,10 @@ require (
)
require (
+ cloud.google.com/go/cloudtasks v1.12.4 // indirect
+ cloud.google.com/go/compute v1.23.1 // indirect
+ cloud.google.com/go/compute/metadata v0.2.3 // indirect
+ cloud.google.com/go/iam v1.1.3 // indirect
github.com/WilfredAlmeida/unkey-go v0.2.0 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
@@ -20,6 +24,11 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
+ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
+ github.com/golang/protobuf v1.5.3 // indirect
+ github.com/google/s2a-go v0.1.7 // indirect
+ github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
+ github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
@@ -32,11 +41,20 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
+ go.opencensus.io v0.24.0 // indirect
golang.org/x/arch v0.3.0 // indirect
- golang.org/x/crypto v0.9.0 // indirect
- golang.org/x/net v0.10.0 // indirect
+ golang.org/x/crypto v0.14.0 // indirect
+ golang.org/x/net v0.17.0 // indirect
+ golang.org/x/oauth2 v0.13.0 // indirect
+ golang.org/x/sync v0.4.0 // indirect
golang.org/x/sys v0.16.0 // indirect
- golang.org/x/text v0.9.0 // indirect
- google.golang.org/protobuf v1.30.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
+ google.golang.org/api v0.149.0 // indirect
+ google.golang.org/appengine v1.6.7 // indirect
+ google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
+ google.golang.org/grpc v1.59.0 // indirect
+ google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/apps/checker/go.sum b/apps/checker/go.sum
index 41d86795..32b9d648 100644
--- a/apps/checker/go.sum
+++ b/apps/checker/go.sum
@@ -1,3 +1,13 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go/cloudtasks v1.12.4 h1:5xXuFfAjg0Z5Wb81j2GAbB3e0bwroCeSF+5jBn/L650=
+cloud.google.com/go/cloudtasks v1.12.4/go.mod h1:BEPu0Gtt2dU6FxZHNqqNdGqIG86qyWKBPGnsb7udGY0=
+cloud.google.com/go/compute v1.23.1 h1:V97tBoDaZHb6leicZ1G6DLK2BAaZLJ/7+9BB/En3hR0=
+cloud.google.com/go/compute v1.23.1/go.mod h1:CqB3xpmPKKt3OJpW2ndFIXnA9A4xAy/F3Xp1ixncW78=
+cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
+cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
+cloud.google.com/go/iam v1.1.3 h1:18tKG7DzydKWUnLjonWcJO6wjSCAtzh4GcRKlH/Hrzc=
+cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/WilfredAlmeida/unkey-go v0.2.0 h1:WnPDbfrwgiI2uZQmFhSATY5Q1FU8NCGtQ+h1VjJWfoI=
github.com/WilfredAlmeida/unkey-go v0.2.0/go.mod h1:KZZ52al64FQUnWx0Zf2fIFmZvznwUiHqIg0nak/BGMw=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
@@ -5,13 +15,20 @@ github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
@@ -29,10 +46,40 @@ github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QX
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
+github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
+github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
+github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
+github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -58,6 +105,7 @@ github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNc
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
@@ -76,29 +124,106 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
+go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
+golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
+golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
+golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.149.0 h1:b2CqT6kG+zqJIVKRQ3ELJVLN1PwHZ6DJ3dW8yl82rgY=
+google.golang.org/api v0.149.0/go.mod h1:Mwn1B7JTXrzXtnvmzQE2BD6bYZQ8DShKZDZbeN9I7qI=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b h1:+YaDE2r2OG8t/z5qmsh7Y+XXwCbvadxxZ0YY6mTdrVA=
+google.golang.org/genproto v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:CgAqfJo+Xmu0GwA0411Ht3OU3OntXwsGmrmjI8ioGXI=
+google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b h1:CIC2YMXmIhYw6evmhPxBKJ4fmLbOFtXQN/GV3XOZR8k=
+google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:IBQ646DjkDkvUIsVq/cc03FUFQ9wbZu7yE396YcL870=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
+google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/apps/server/src/checker/index.ts b/apps/server/src/checker/index.ts
index e529cc87..24fc47e3 100644
--- a/apps/server/src/checker/index.ts
+++ b/apps/server/src/checker/index.ts
@@ -1,8 +1,8 @@
import { Hono } from "hono";
import { z } from "zod";
-import { eq, schema } from "@openstatus/db";
-import { db } from "@openstatus/db/src/db";
+import { and, db, eq, isNotNull, schema } from "@openstatus/db";
+import { incidentTable } from "@openstatus/db/src/schema";
import { flyRegions } from "@openstatus/db/src/schema/monitors/constants";
import { selectMonitorSchema } from "@openstatus/db/src/schema/monitors/validation";
import { Redis } from "@openstatus/upstash";
@@ -24,63 +24,68 @@ checkerRoute.post("/updateStatus", async (c) => {
const json = await c.req.json();
const payloadSchema = z.object({
monitorId: z.string(),
- status: z.enum(["active", "error"]), // that's the new status
message: z.string().optional(),
statusCode: z.number().optional(),
region: z.enum(flyRegions),
cronTimestamp: z.number().optional(),
+ // status: z.enum(["active", "error"]),
});
const result = payloadSchema.safeParse(json);
if (!result.success) {
- // console.error(result.error);
return c.text("Unprocessable Entity", 422);
}
- const { monitorId, status, message, region, statusCode, cronTimestamp } =
- result.data;
+ const { monitorId, message, region, statusCode, cronTimestamp } = result.data;
console.log(`📝 update monitor status ${JSON.stringify(result.data)}`);
- switch (status) {
- case "active":
- if (!statusCode) {
- return;
- }
- await upsertMonitorStatus({
- monitorId: monitorId,
- status: "active",
- region: region,
- });
-
- await checkerAudit.publishAuditLog({
- id: `monitor:${monitorId}`,
- action: "monitor.recovered",
- targets: [{ id: monitorId, type: "monitor" }],
- metadata: {
- region: region,
- statusCode: statusCode,
- cronTimestamp: cronTimestamp,
- },
- });
- break;
- case "error": {
- await upsertMonitorStatus({
- monitorId: monitorId,
- status: "error",
+ // we check if it's an error
+ // If status not in 200> and <300
+ // if there's no incident create one and notify
+ // publish event to TB
+
+ // if status is ok checked if there's an open incident
+ // if open incident publish incident recovered
+ const incident = await db
+ .select()
+ .from(incidentTable)
+ .where(
+ and(
+ eq(incidentTable.monitorId, Number(monitorId)),
+ isNotNull(incidentTable.resolvedAt),
+ isNotNull(incidentTable.acknowledgedAt),
+ ),
+ );
+
+ // if we are in error
+ if (statusCode && statusCode < 200 && statusCode > 300) {
+ // create incident
+ // trigger alerting
+ await checkerAudit.publishAuditLog({
+ id: `monitor:${monitorId}`,
+ action: "monitor.failed",
+ targets: [{ id: monitorId, type: "monitor" }],
+ metadata: {
region: region,
- });
- // ALPHA
- await checkerAudit.publishAuditLog({
- id: `monitor:${monitorId}`,
- action: "monitor.failed",
- targets: [{ id: monitorId, type: "monitor" }],
- metadata: {
- region: region,
- statusCode: statusCode,
- message,
- cronTimestamp,
- },
- });
+ statusCode: statusCode,
+ message,
+ },
+ });
+ // We upsert the status of the monitor
+ await upsertMonitorStatus({
+ monitorId: monitorId,
+ status: "error",
+ region: region,
+ });
+
+ if (!incident) {
+ const redisKey = `${monitorId}-${cronTimestamp}-error`;
+ // We add the new region to the set
+ await redis.sadd(redisKey, region);
+ // let's add an expire to the set
+ await redis.expire(redisKey, 60 * 60 * 24);
+ // We get the number of regions affected
+ const nbAffectedRegion = await redis.scard(redisKey);
const currentMonitor = await db
.select()
@@ -94,30 +99,71 @@ checkerRoute.post("/updateStatus", async (c) => {
console.log("cronTimestamp is undefined");
}
- const redisKey = `${monitorId}-${cronTimestamp}`;
- // We add the new region to the set
- await redis.sadd(redisKey, region);
- // let's add an expire to the set
- await redis.expire(redisKey, 60 * 60 * 24);
- // We get the number of regions affected
- const nbAffectedRegion = await redis.scard(redisKey);
+ const numberOfRegions = monitor.regions.length;
// If the number of affected regions is greater than half of the total region, we trigger the alerting
- if (nbAffectedRegion < monitor.regions.length / 2) {
- console.log(
- `Not enough affected regions (${nbAffectedRegion}/${flyRegions.length})`,
- );
- break;
+ // 4 of 6 monitor need to fail to trigger an alerting
+ if (nbAffectedRegion > numberOfRegions / 2) {
+ await triggerAlerting({ monitorId, statusCode, message, region });
+ // create the incident and trigger the alerting
+ await db.insert(incidentTable).values({
+ monitorId: Number(monitorId),
+ workspaceId: monitor.workspaceId,
+ startedAt: new Date(),
+ });
}
- // Add a
- await triggerAlerting({
- monitorId: monitorId,
- region: env.FLY_REGION,
- statusCode,
- message,
- });
- break;
}
}
+
+ if (statusCode && statusCode >= 200 && statusCode < 300) {
+ await upsertMonitorStatus({
+ monitorId: monitorId,
+ status: "active",
+ region: region,
+ });
+
+ await checkerAudit.publishAuditLog({
+ id: `monitor:${monitorId}`,
+ action: "monitor.recovered",
+ targets: [{ id: monitorId, type: "monitor" }],
+ metadata: { region: region, statusCode: Number(statusCode) },
+ });
+
+ // FIX: TO BE IMPROVED
+ // if (incident) {
+ // const redisKey = `${monitorId}-${cronTimestamp}-resolved`;
+ // // We add the new region to the set
+ // await redis.sadd(redisKey, region);
+ // // let's add an expire to the set
+ // await redis.expire(redisKey, 60 * 60 * 24);
+ // // We get the number of regions affected
+ // const nbAffectedRegion = await redis.scard(redisKey);
+
+ // const currentMonitor = await db
+ // .select()
+ // .from(schema.monitor)
+ // .where(eq(schema.monitor.id, Number(monitorId)))
+ // .get();
+
+ // const monitor = selectMonitorSchema.parse(currentMonitor);
+
+ // if (!cronTimestamp) {
+ // console.log("cronTimestamp is undefined");
+ // }
+
+ // const numberOfRegions = monitor.regions.length;
+
+ // // If the number of affected regions is greater than half of the total region, we trigger the alerting
+ // // 4 of 6 monitor need to fail to trigger an alerting
+ // if (nbAffectedRegion > numberOfRegions / 2) {
+ // // Trigger recovery notification
+ // // await triggerRecovery({ monitorId, statusCode, message, region });
+ // await db.update(incidentTable).set({
+ // resolvedAt: new Date(),
+ // });
+ // }
+ // }
+ }
+
return c.text("Ok", 200);
});
diff --git a/apps/web/src/app/api/checker/cron/_cron.ts b/apps/web/src/app/api/checker/cron/_cron.ts
index 4c7e2978..cc69fe17 100644
--- a/apps/web/src/app/api/checker/cron/_cron.ts
+++ b/apps/web/src/app/api/checker/cron/_cron.ts
@@ -64,8 +64,7 @@ export const cron = async ({
for (const region of selectedRegions) {
const status =
- monitorStatus.find(({ region }) => region === region)?.status ||
- "active";
+ monitorStatus.find((m) => region === m.region)?.status || "active";
const response = await createCronTask({
row,
timestamp,
diff --git a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/(overview)/layout.tsx b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/(overview)/layout.tsx
new file mode 100644
index 00000000..e7b30568
--- /dev/null
+++ b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/(overview)/layout.tsx
@@ -0,0 +1,24 @@
+import * as React from "react";
+
+import { Header } from "@/components/dashboard/header";
+import { HelpCallout } from "@/components/dashboard/help-callout";
+
+export default async function Layout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return (
+
+ );
+}
diff --git a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/(overview)/loading.tsx b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/(overview)/loading.tsx
new file mode 100644
index 00000000..ae51d6a5
--- /dev/null
+++ b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/(overview)/loading.tsx
@@ -0,0 +1,5 @@
+import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton";
+
+export default function Loading() {
+ return ;
+}
diff --git a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/(overview)/page.tsx b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/(overview)/page.tsx
new file mode 100644
index 00000000..a5dd2d63
--- /dev/null
+++ b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/(overview)/page.tsx
@@ -0,0 +1,26 @@
+import * as React from "react";
+
+import { EmptyState } from "@/components/dashboard/empty-state";
+import { columns } from "@/components/data-table/incident/columns";
+import { DataTable } from "@/components/data-table/incident/data-table";
+import { api } from "@/trpc/server";
+
+export default async function IncidentPage() {
+ const incidents = await api.incident.getAllIncidents.query();
+
+ if (incidents?.length === 0)
+ return (
+
+ );
+
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/[id]/layout.tsx b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/[id]/layout.tsx
new file mode 100644
index 00000000..487a8396
--- /dev/null
+++ b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/[id]/layout.tsx
@@ -0,0 +1,23 @@
+import { notFound } from "next/navigation";
+
+import { api } from "@/trpc/server";
+
+export default async function Layout({
+ children,
+ params,
+}: {
+ children: React.ReactNode;
+ params: { workspaceSlug: string; id: string };
+}) {
+ const id = params.id;
+
+ const monitor = await api.incident.getIncidentById.query({
+ id: Number(id),
+ });
+
+ if (!monitor) {
+ return notFound();
+ }
+
+ return {children}
;
+}
diff --git a/apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/[id]/page.tsx b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/[id]/page.tsx
new file mode 100644
index 00000000..cf2eed2f
--- /dev/null
+++ b/apps/web/src/app/app/[workspaceSlug]/(dashboard)/incidents/[id]/page.tsx
@@ -0,0 +1,3 @@
+export default async function IncidentPage() {
+ return <>>;
+}
diff --git a/apps/web/src/components/data-table/incident/columns.tsx b/apps/web/src/components/data-table/incident/columns.tsx
new file mode 100644
index 00000000..96e8e5e2
--- /dev/null
+++ b/apps/web/src/components/data-table/incident/columns.tsx
@@ -0,0 +1,85 @@
+"use client";
+
+import Link from "next/link";
+import type { ColumnDef } from "@tanstack/react-table";
+
+import type { Incident } from "@openstatus/db/src/schema";
+
+import { DataTableRowActions } from "./data-table-row-actions";
+
+export const columns: ColumnDef[] = [
+ {
+ accessorKey: "monitorId",
+ header: "Monitor",
+ cell: ({ row }) => {
+ return (
+
+
+ {row.original.monitorName}
+
+
+ );
+ },
+ },
+ {
+ accessorKey: "startedAt",
+ header: "Started At",
+ cell: ({ row }) => {
+ const { startedAt } = row.original;
+ const date = startedAt ? new Date(startedAt).toLocaleString() : "-";
+ return (
+
+
+ {date}
+
+
+ );
+ },
+ },
+ {
+ accessorKey: "acknowledgetAt",
+ header: "Acknowledged At",
+ cell: ({ row }) => {
+ const { acknowledgedAt } = row.original;
+ const date = acknowledgedAt
+ ? new Date(acknowledgedAt).toLocaleString()
+ : "-";
+ return (
+
+
+ {date}
+
+
+ );
+ },
+ },
+ {
+ accessorKey: "resolvedAt",
+ header: "Resolved At",
+ cell: ({ row }) => {
+ const { resolvedAt } = row.original;
+ const date = resolvedAt ? new Date(resolvedAt).toLocaleString() : "-";
+ return (
+
+
+ {date}
+
+
+ );
+ },
+ },
+
+ {
+ id: "actions",
+ cell: ({ row }) => {
+ return (
+
+
+
+ );
+ },
+ },
+];
diff --git a/apps/web/src/components/data-table/incident/data-table-row-actions.tsx b/apps/web/src/components/data-table/incident/data-table-row-actions.tsx
new file mode 100644
index 00000000..ecd5158d
--- /dev/null
+++ b/apps/web/src/components/data-table/incident/data-table-row-actions.tsx
@@ -0,0 +1,107 @@
+"use client";
+
+import * as React from "react";
+import Link from "next/link";
+import { useRouter } from "next/navigation";
+import type { Row } from "@tanstack/react-table";
+import { MoreHorizontal } from "lucide-react";
+
+import { selectIncidentSchema } from "@openstatus/db/src/schema";
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+ AlertDialogTrigger,
+ Button,
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@openstatus/ui";
+
+import { LoadingAnimation } from "@/components/loading-animation";
+import { useToastAction } from "@/hooks/use-toast-action";
+import { api } from "@/trpc/client";
+
+interface DataTableRowActionsProps {
+ row: Row;
+}
+
+export function DataTableRowActions({
+ row,
+}: DataTableRowActionsProps) {
+ const incident = selectIncidentSchema.parse(row.original);
+ const router = useRouter();
+ const { toast } = useToastAction();
+ const [isPending, startTransition] = React.useTransition();
+
+ async function resolved() {
+ startTransition(async () => {
+ try {
+ if (!incident.id) return;
+ await api.incident.resolvedIncident.mutate({ id: incident.id });
+ toast("success");
+ router.refresh();
+ } catch {
+ toast("error");
+ }
+ });
+ }
+
+ async function acknowledge() {
+ startTransition(async () => {
+ try {
+ // const { jobType, ...rest } = monitor;
+ // if (!monitor.id) return;
+ // await api.monitor.update.mutate({
+ // ...rest,
+ // active: !monitor.active,
+ // });
+ if (!incident.id) return;
+ await api.incident.acknowledgeIncident.mutate({ id: incident.id });
+ toast("success");
+ router.refresh();
+ } catch {
+ toast("error");
+ }
+ });
+ }
+
+ return (
+
+
+
+
+
+
+ Acknowledge
+
+
+
+
+ Resolved
+
+
+
+ );
+}
diff --git a/apps/web/src/components/data-table/incident/data-table.tsx b/apps/web/src/components/data-table/incident/data-table.tsx
new file mode 100644
index 00000000..fbfabd02
--- /dev/null
+++ b/apps/web/src/components/data-table/incident/data-table.tsx
@@ -0,0 +1,84 @@
+"use client";
+
+import * as React from "react";
+import type { ColumnDef } from "@tanstack/react-table";
+import {
+ flexRender,
+ getCoreRowModel,
+ useReactTable,
+} from "@tanstack/react-table";
+
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@openstatus/ui";
+
+interface DataTableProps {
+ columns: ColumnDef[];
+ data: TData[];
+}
+
+// FIXME: right now, the mobile layout is messed up
+// https://github.com/TanStack/table/discussions/3192#discussioncomment-6458134
+
+export function DataTable({
+ columns,
+ data,
+}: DataTableProps) {
+ const table = useReactTable({
+ data,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ });
+
+ return (
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => {
+ return (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext(),
+ )}
+
+ );
+ })}
+
+ ))}
+
+
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+
+ ))}
+
+ ))
+ ) : (
+
+
+ No results.
+
+
+ )}
+
+
+
+ );
+}
diff --git a/apps/web/src/config/pages.ts b/apps/web/src/config/pages.ts
index 244cea17..c7f009ca 100644
--- a/apps/web/src/config/pages.ts
+++ b/apps/web/src/config/pages.ts
@@ -16,6 +16,12 @@ export const pagesConfig: Page[] = [
href: "/monitors",
icon: "activity",
},
+ {
+ title: "Incidents",
+ description: "All your incidents.",
+ href: "/incidents",
+ icon: "siren",
+ },
{
title: "Status Pages",
description: "Where you can see all the pages.",
diff --git a/packages/api/src/edge.ts b/packages/api/src/edge.ts
index 59646bb7..f52cfb1f 100644
--- a/packages/api/src/edge.ts
+++ b/packages/api/src/edge.ts
@@ -1,4 +1,5 @@
import { domainRouter } from "./router/domain";
+import { incidentRouter } from "./router/incident";
import { integrationRouter } from "./router/integration";
import { invitationRouter } from "./router/invitation";
import { monitorRouter } from "./router/monitor";
@@ -20,4 +21,5 @@ export const edgeRouter = createTRPCRouter({
user: userRouter,
notification: notificationRouter,
invitation: invitationRouter,
+ incident: incidentRouter,
});
diff --git a/packages/api/src/router/incident.ts b/packages/api/src/router/incident.ts
new file mode 100644
index 00000000..10006635
--- /dev/null
+++ b/packages/api/src/router/incident.ts
@@ -0,0 +1,114 @@
+import { z } from "zod";
+
+import { and, eq, schema } from "@openstatus/db";
+import { selectIncidentSchema } from "@openstatus/db/src/schema";
+
+import { createTRPCRouter, protectedProcedure } from "../trpc";
+
+export const incidentRouter = createTRPCRouter({
+ getAllIncidents: protectedProcedure
+ .output(z.array(selectIncidentSchema))
+ .query(async (opts) => {
+ const result = await opts.ctx.db
+ .select()
+ .from(schema.incidentTable)
+ .where(eq(schema.incidentTable.workspaceId, opts.ctx.workspace.id))
+ .leftJoin(
+ schema.monitor,
+ eq(schema.incidentTable.monitorId, schema.monitor.id),
+ )
+ .all();
+ return z
+ .array(selectIncidentSchema)
+ .parse(
+ result.map((r) => ({ ...r.incident, monitorName: r.monitor?.name })),
+ );
+ }),
+
+ getIncidentById: protectedProcedure
+ .input(z.object({ id: z.number() }))
+ .output(selectIncidentSchema)
+ .query(async (opts) => {
+ const currentIncident = await opts.ctx.db
+ .select()
+ .from(schema.incidentTable)
+ .where(
+ and(
+ eq(schema.incidentTable.id, opts.input.id),
+ eq(schema.incidentTable.workspaceId, opts.ctx.workspace.id),
+ ),
+ )
+ .get();
+ return selectIncidentSchema.parse(currentIncident);
+ }),
+
+ acknowledgeIncident: protectedProcedure
+ .input(z.object({ id: z.number() }))
+ .mutation(async (opts) => {
+ const currentIncident = await opts.ctx.db
+ .select()
+ .from(schema.incidentTable)
+ .where(
+ and(
+ eq(schema.incidentTable.id, opts.input.id),
+ eq(schema.incidentTable.workspaceId, opts.ctx.workspace.id),
+ ),
+ )
+ .get();
+ if (!currentIncident) {
+ throw new Error("Incident not found");
+ }
+ if (currentIncident.acknowledgedAt) {
+ throw new Error("Incident already acknowledged");
+ }
+ await opts.ctx.db
+ .update(schema.incidentTable)
+ .set({
+ acknowledgedAt: new Date(),
+ acknowledgedBy: opts.ctx.user.id,
+ })
+ .where(
+ and(
+ eq(schema.incidentTable.id, opts.input.id),
+ eq(schema.incidentTable.workspaceId, opts.ctx.workspace.id),
+ ),
+ );
+ return true;
+ }),
+ resolvedIncident: protectedProcedure
+ .input(z.object({ id: z.number() }))
+ .mutation(async (opts) => {
+ const currentIncident = await opts.ctx.db
+ .select()
+ .from(schema.incidentTable)
+ .where(
+ and(
+ eq(schema.incidentTable.id, opts.input.id),
+ eq(schema.incidentTable.workspaceId, opts.ctx.workspace.id),
+ ),
+ )
+ .get();
+ if (!currentIncident) {
+ throw new Error("Incident not found");
+ }
+ if (!currentIncident.acknowledgedAt) {
+ throw new Error("Incident not acknowledged");
+ }
+ if (currentIncident.resolvedAt) {
+ throw new Error("Incident already resolved");
+ }
+ await opts.ctx.db
+ .update(schema.incidentTable)
+ .set({
+ resolvedAt: new Date(),
+ resolvedBy: opts.ctx.user.id,
+ })
+ .where(
+ and(
+ eq(schema.incidentTable.id, opts.input.id),
+ eq(schema.incidentTable.workspaceId, opts.ctx.workspace.id),
+ ),
+ );
+ return true;
+ }),
+});
diff --git a/packages/api/src/router/statusReport.ts b/packages/api/src/router/statusReport.ts
index ae482ca8..7ce2efac 100644
--- a/packages/api/src/router/statusReport.ts
+++ b/packages/api/src/router/statusReport.ts
@@ -37,7 +37,7 @@ export const statusReportRouter = createTRPCRouter({
.returning()
.get();
- if (Boolean(monitors.length)) {
+ if (monitors.length > 0) {
await opts.ctx.db
.insert(monitorsToStatusReport)
.values(
@@ -50,7 +50,7 @@ export const statusReportRouter = createTRPCRouter({
.get();
}
- if (Boolean(pages.length)) {
+ if (pages.length > 0) {
await opts.ctx.db
.insert(pagesToStatusReports)
.values(
diff --git a/packages/db/drizzle/0015_bent_sister_grimm.sql b/packages/db/drizzle/0015_bent_sister_grimm.sql
new file mode 100644
index 00000000..fdf48ec0
--- /dev/null
+++ b/packages/db/drizzle/0015_bent_sister_grimm.sql
@@ -0,0 +1,19 @@
+CREATE TABLE `incident` (
+ `id` integer PRIMARY KEY NOT NULL,
+ `title` text DEFAULT '' NOT NULL,
+ `summary` text DEFAULT '' NOT NULL,
+ `status` text DEFAULT 'triage' NOT NULL,
+ `monitor_id` integer,
+ `workspace_id` integer,
+ `started_at` integer,
+ `acknowledged_at` integer,
+ `acknowledged_by` integer,
+ `resolved_at` integer,
+ `resolved_by` integer,
+ `created_at` integer DEFAULT (strftime('%s', 'now')),
+ `updated_at` integer DEFAULT (strftime('%s', 'now')),
+ FOREIGN KEY (`monitor_id`) REFERENCES `monitor`(`id`) ON UPDATE no action ON DELETE no action,
+ FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE no action,
+ FOREIGN KEY (`acknowledged_by`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action,
+ FOREIGN KEY (`resolved_by`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE no action
+);
diff --git a/packages/db/drizzle/meta/0015_snapshot.json b/packages/db/drizzle/meta/0015_snapshot.json
new file mode 100644
index 00000000..626b8af5
--- /dev/null
+++ b/packages/db/drizzle/meta/0015_snapshot.json
@@ -0,0 +1,1408 @@
+{
+ "version": "5",
+ "dialect": "sqlite",
+ "id": "f5a77857-319d-4575-b570-f0aacb9eab35",
+ "prevId": "4cc8ce9b-ed70-47c1-8218-2cbc02743563",
+ "tables": {
+ "status_report_to_monitors": {
+ "name": "status_report_to_monitors",
+ "columns": {
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status_report_id": {
+ "name": "status_report_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "status_report_to_monitors_monitor_id_monitor_id_fk": {
+ "name": "status_report_to_monitors_monitor_id_monitor_id_fk",
+ "tableFrom": "status_report_to_monitors",
+ "tableTo": "monitor",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "status_report_to_monitors_status_report_id_status_report_id_fk": {
+ "name": "status_report_to_monitors_status_report_id_status_report_id_fk",
+ "tableFrom": "status_report_to_monitors",
+ "tableTo": "status_report",
+ "columnsFrom": [
+ "status_report_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "status_report_to_monitors_monitor_id_status_report_id_pk": {
+ "columns": [
+ "monitor_id",
+ "status_report_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "status_reports_to_pages": {
+ "name": "status_reports_to_pages",
+ "columns": {
+ "page_id": {
+ "name": "page_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status_report_id": {
+ "name": "status_report_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "status_reports_to_pages_page_id_page_id_fk": {
+ "name": "status_reports_to_pages_page_id_page_id_fk",
+ "tableFrom": "status_reports_to_pages",
+ "tableTo": "page",
+ "columnsFrom": [
+ "page_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "status_reports_to_pages_status_report_id_status_report_id_fk": {
+ "name": "status_reports_to_pages_status_report_id_status_report_id_fk",
+ "tableFrom": "status_reports_to_pages",
+ "tableTo": "status_report",
+ "columnsFrom": [
+ "status_report_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "status_reports_to_pages_page_id_status_report_id_pk": {
+ "columns": [
+ "page_id",
+ "status_report_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "status_report": {
+ "name": "status_report",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "status_report_workspace_id_workspace_id_fk": {
+ "name": "status_report_workspace_id_workspace_id_fk",
+ "tableFrom": "status_report",
+ "tableTo": "workspace",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "status_report_update": {
+ "name": "status_report_update",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "text(4)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "date": {
+ "name": "date",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "status_report_id": {
+ "name": "status_report_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "status_report_update_status_report_id_status_report_id_fk": {
+ "name": "status_report_update_status_report_id_status_report_id_fk",
+ "tableFrom": "status_report_update",
+ "tableTo": "status_report",
+ "columnsFrom": [
+ "status_report_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "integration": {
+ "name": "integration",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "credential": {
+ "name": "credential",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ },
+ "data": {
+ "name": "data",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "integration_workspace_id_workspace_id_fk": {
+ "name": "integration_workspace_id_workspace_id_fk",
+ "tableFrom": "integration",
+ "tableTo": "workspace",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "page": {
+ "name": "page",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "custom_domain": {
+ "name": "custom_domain",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "published": {
+ "name": "published",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ }
+ },
+ "indexes": {
+ "page_slug_unique": {
+ "name": "page_slug_unique",
+ "columns": [
+ "slug"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {
+ "page_workspace_id_workspace_id_fk": {
+ "name": "page_workspace_id_workspace_id_fk",
+ "tableFrom": "page",
+ "tableTo": "workspace",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "monitor": {
+ "name": "monitor",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "job_type": {
+ "name": "job_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'other'"
+ },
+ "periodicity": {
+ "name": "periodicity",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'other'"
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'active'"
+ },
+ "active": {
+ "name": "active",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": false
+ },
+ "regions": {
+ "name": "regions",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "url": {
+ "name": "url",
+ "type": "text(2048)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "headers": {
+ "name": "headers",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "body": {
+ "name": "body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "method": {
+ "name": "method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'GET'"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "monitor_workspace_id_workspace_id_fk": {
+ "name": "monitor_workspace_id_workspace_id_fk",
+ "tableFrom": "monitor",
+ "tableTo": "workspace",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "monitors_to_pages": {
+ "name": "monitors_to_pages",
+ "columns": {
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "page_id": {
+ "name": "page_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "monitors_to_pages_monitor_id_monitor_id_fk": {
+ "name": "monitors_to_pages_monitor_id_monitor_id_fk",
+ "tableFrom": "monitors_to_pages",
+ "tableTo": "monitor",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "monitors_to_pages_page_id_page_id_fk": {
+ "name": "monitors_to_pages_page_id_page_id_fk",
+ "tableFrom": "monitors_to_pages",
+ "tableTo": "page",
+ "columnsFrom": [
+ "page_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "monitors_to_pages_monitor_id_page_id_pk": {
+ "columns": [
+ "monitor_id",
+ "page_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "user": {
+ "name": "user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "tenant_id": {
+ "name": "tenant_id",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "first_name": {
+ "name": "first_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "last_name": {
+ "name": "last_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "photo_url": {
+ "name": "photo_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ }
+ },
+ "indexes": {
+ "user_tenant_id_unique": {
+ "name": "user_tenant_id_unique",
+ "columns": [
+ "tenant_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "users_to_workspaces": {
+ "name": "users_to_workspaces",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'member'"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "users_to_workspaces_user_id_user_id_fk": {
+ "name": "users_to_workspaces_user_id_user_id_fk",
+ "tableFrom": "users_to_workspaces",
+ "tableTo": "user",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "users_to_workspaces_workspace_id_workspace_id_fk": {
+ "name": "users_to_workspaces_workspace_id_workspace_id_fk",
+ "tableFrom": "users_to_workspaces",
+ "tableTo": "workspace",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "users_to_workspaces_user_id_workspace_id_pk": {
+ "columns": [
+ "user_id",
+ "workspace_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "page_subscriber": {
+ "name": "page_subscriber",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "page_id": {
+ "name": "page_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "accepted_at": {
+ "name": "accepted_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "page_subscriber_page_id_page_id_fk": {
+ "name": "page_subscriber_page_id_page_id_fk",
+ "tableFrom": "page_subscriber",
+ "tableTo": "page",
+ "columnsFrom": [
+ "page_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "workspace": {
+ "name": "workspace",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "slug": {
+ "name": "slug",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "stripe_id": {
+ "name": "stripe_id",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "subscription_id": {
+ "name": "subscription_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "plan": {
+ "name": "plan",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "ends_at": {
+ "name": "ends_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "paid_until": {
+ "name": "paid_until",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ }
+ },
+ "indexes": {
+ "workspace_slug_unique": {
+ "name": "workspace_slug_unique",
+ "columns": [
+ "slug"
+ ],
+ "isUnique": true
+ },
+ "workspace_stripe_id_unique": {
+ "name": "workspace_stripe_id_unique",
+ "columns": [
+ "stripe_id"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "notification": {
+ "name": "notification",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "data": {
+ "name": "data",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'{}'"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "notification_workspace_id_workspace_id_fk": {
+ "name": "notification_workspace_id_workspace_id_fk",
+ "tableFrom": "notification",
+ "tableTo": "workspace",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "notifications_to_monitors": {
+ "name": "notifications_to_monitors",
+ "columns": {
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "notification_id": {
+ "name": "notification_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "notifications_to_monitors_monitor_id_monitor_id_fk": {
+ "name": "notifications_to_monitors_monitor_id_monitor_id_fk",
+ "tableFrom": "notifications_to_monitors",
+ "tableTo": "monitor",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notifications_to_monitors_notification_id_notification_id_fk": {
+ "name": "notifications_to_monitors_notification_id_notification_id_fk",
+ "tableFrom": "notifications_to_monitors",
+ "tableTo": "notification",
+ "columnsFrom": [
+ "notification_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "notifications_to_monitors_monitor_id_notification_id_pk": {
+ "columns": [
+ "monitor_id",
+ "notification_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "monitor_status": {
+ "name": "monitor_status",
+ "columns": {
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "region": {
+ "name": "region",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'active'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ }
+ },
+ "indexes": {
+ "monitor_status_idx": {
+ "name": "monitor_status_idx",
+ "columns": [
+ "monitor_id",
+ "region"
+ ],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "monitor_status_monitor_id_monitor_id_fk": {
+ "name": "monitor_status_monitor_id_monitor_id_fk",
+ "tableFrom": "monitor_status",
+ "tableTo": "monitor",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "monitor_status_monitor_id_region_pk": {
+ "columns": [
+ "monitor_id",
+ "region"
+ ]
+ }
+ },
+ "uniqueConstraints": {}
+ },
+ "invitation": {
+ "name": "invitation",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'member'"
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ },
+ "accepted_at": {
+ "name": "accepted_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "incident": {
+ "name": "incident",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "summary": {
+ "name": "summary",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "''"
+ },
+ "status": {
+ "name": "status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'triage'"
+ },
+ "monitor_id": {
+ "name": "monitor_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "workspace_id": {
+ "name": "workspace_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "started_at": {
+ "name": "started_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "acknowledged_at": {
+ "name": "acknowledged_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "acknowledged_by": {
+ "name": "acknowledged_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "resolved_by": {
+ "name": "resolved_by",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "(strftime('%s', 'now'))"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "incident_monitor_id_monitor_id_fk": {
+ "name": "incident_monitor_id_monitor_id_fk",
+ "tableFrom": "incident",
+ "tableTo": "monitor",
+ "columnsFrom": [
+ "monitor_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "incident_workspace_id_workspace_id_fk": {
+ "name": "incident_workspace_id_workspace_id_fk",
+ "tableFrom": "incident",
+ "tableTo": "workspace",
+ "columnsFrom": [
+ "workspace_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "incident_acknowledged_by_user_id_fk": {
+ "name": "incident_acknowledged_by_user_id_fk",
+ "tableFrom": "incident",
+ "tableTo": "user",
+ "columnsFrom": [
+ "acknowledged_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "incident_resolved_by_user_id_fk": {
+ "name": "incident_resolved_by_user_id_fk",
+ "tableFrom": "incident",
+ "tableTo": "user",
+ "columnsFrom": [
+ "resolved_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ }
+ },
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ }
+}
\ No newline at end of file
diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json
index 61f0d24a..3c0cda53 100644
--- a/packages/db/drizzle/meta/_journal.json
+++ b/packages/db/drizzle/meta/_journal.json
@@ -106,6 +106,13 @@
"when": 1702227904130,
"tag": "0014_adorable_skaar",
"breakpoints": true
+ },
+ {
+ "idx": 15,
+ "version": "5",
+ "when": 1705856545397,
+ "tag": "0015_bent_sister_grimm",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/packages/db/src/schema/incidents/incident.ts b/packages/db/src/schema/incidents/incident.ts
new file mode 100644
index 00000000..730e683c
--- /dev/null
+++ b/packages/db/src/schema/incidents/incident.ts
@@ -0,0 +1,44 @@
+import { sql } from "drizzle-orm";
+import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
+
+import { monitor } from "../monitors";
+import { user } from "../users/user";
+import { workspace } from "../workspaces";
+
+export const statusIncident = [
+ "triage",
+ "investigating",
+ "identified",
+ "monitoring",
+ "resolved",
+ "duplicated",
+] as const;
+
+export const incidentTable = sqliteTable("incident", {
+ id: integer("id").primaryKey(),
+ title: text("title").default("").notNull(),
+ summary: text("summary").default("").notNull(),
+ status: text("status", { enum: statusIncident }).default("triage").notNull(),
+
+ // Service affected by incident
+ monitorId: integer("monitor_id").references(() => monitor.id),
+
+ // Workspace where the incident happened
+ workspaceId: integer("workspace_id").references(() => workspace.id),
+ // Data related to incident timeline
+ startedAt: integer("started_at", { mode: "timestamp" }),
+ // Who has acknoledge the incident
+ acknowledgedAt: integer("acknowledged_at", { mode: "timestamp" }),
+ acknowledgedBy: integer("acknowledged_by").references(() => user.id),
+
+ // Who has resolved it
+ resolvedAt: integer("resolved_at", { mode: "timestamp" }),
+ resolvedBy: integer("resolved_by").references(() => user.id),
+
+ createdAt: integer("created_at", { mode: "timestamp" }).default(
+ sql`(strftime('%s', 'now'))`,
+ ),
+ updatedAt: integer("updated_at", { mode: "timestamp" }).default(
+ sql`(strftime('%s', 'now'))`,
+ ),
+});
diff --git a/packages/db/src/schema/incidents/index.ts b/packages/db/src/schema/incidents/index.ts
new file mode 100644
index 00000000..eff8f2af
--- /dev/null
+++ b/packages/db/src/schema/incidents/index.ts
@@ -0,0 +1,2 @@
+export * from "./incident";
+export * from "./validation";
diff --git a/packages/db/src/schema/incidents/validation.ts b/packages/db/src/schema/incidents/validation.ts
new file mode 100644
index 00000000..72655c06
--- /dev/null
+++ b/packages/db/src/schema/incidents/validation.ts
@@ -0,0 +1,10 @@
+import { createSelectSchema } from "drizzle-zod";
+import { z } from "zod";
+
+import { incidentTable } from "./incident";
+
+export const selectIncidentSchema = createSelectSchema(incidentTable).extend({
+ monitorName: z.string().optional(),
+});
+
+export type Incident = z.infer;
diff --git a/packages/db/src/schema/index.ts b/packages/db/src/schema/index.ts
index e189a4a8..bdd284b9 100644
--- a/packages/db/src/schema/index.ts
+++ b/packages/db/src/schema/index.ts
@@ -9,3 +9,4 @@ export * from "./shared";
export * from "./notifications";
export * from "./monitor_status";
export * from "./invitations";
+export * from "./incidents";
diff --git a/packages/db/src/schema/monitors/validation.ts b/packages/db/src/schema/monitors/validation.ts
index 954bffbe..b5260e2d 100644
--- a/packages/db/src/schema/monitors/validation.ts
+++ b/packages/db/src/schema/monitors/validation.ts
@@ -24,9 +24,8 @@ function stringToArrayProcess(string: T) {}
const regionsToArraySchema = z.preprocess((val) => {
if (String(val).length > 0) {
return String(val).split(",");
- } else {
- return [];
}
+ return [];
}, z.array(monitorRegionSchema));
const bodyToStringSchema = z.preprocess((val) => {
@@ -41,9 +40,8 @@ const headersToArraySchema = z.preprocess(
}
if (String(val).length > 0) {
return JSON.parse(String(val));
- } else {
- return [];
}
+ return [];
},
z.array(z.object({ key: z.string(), value: z.string() })).default([]),
);