This repository has no description
0

Configure Feed

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

feat: add health checks, export more types to npm package

+163 -13
+1
config.go
··· 20 20 FirebaseServiceAccount string `env:"FIREBASE_SERVICE_ACCOUNT"` 21 21 StartupScheduleRestoration string `env:"STARTUP_SCHEDULE_RESTORATION" envDefault:"enabled"` 22 22 AppPackageId string `env:"APP_PACKAGE_ID" envDefault:"app.churros"` 23 + HealthCheckPort int `env:"HEALTH_CHECK_PORT" envDefault:"8080"` 23 24 } 24 25 25 26 func LoadConfiguration() (Configuration, error) {
constants.ts typescript/constants.ts
+111
health.go
··· 1 + package notella 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "fmt" 7 + "log" 8 + "net/http" 9 + 10 + "firebase.google.com/go/v4/messaging" 11 + ll "github.com/ewen-lbh/label-logger-go" 12 + "github.com/nats-io/nats.go" 13 + ) 14 + 15 + type HealthResponse struct { 16 + Redis bool `json:"redis"` 17 + NATS bool `json:"nats"` 18 + ChurrosDatabase bool `json:"churros_db"` 19 + Firebase bool `json:"firebase"` 20 + } 21 + 22 + func healthHandler(w http.ResponseWriter, r *http.Request) { 23 + ll.Log("Checking", "cyan", "health due to request from %s", r.RemoteAddr) 24 + // Set the content type to JSON 25 + w.Header().Set("Content-Type", "application/json") 26 + 27 + // Example response (you can modify this with your own business logic) 28 + response := HealthResponse{} 29 + 30 + if err := CheckRedisHealth(); err != nil { 31 + ll.ErrorDisplay("while checking Redis health", err) 32 + } else { 33 + response.Redis = true 34 + } 35 + 36 + if err := CheckNATSHealth(); err != nil { 37 + ll.ErrorDisplay("while checking NATS health", err) 38 + } else { 39 + response.NATS = true 40 + } 41 + 42 + if err := CheckChurrosDatabaseHealth(); err != nil { 43 + ll.ErrorDisplay("while checking Churros database health", err) 44 + } else { 45 + response.ChurrosDatabase = true 46 + } 47 + 48 + if err := CheckFirebaseHealth(); err != nil { 49 + ll.ErrorDisplay("while checking Firebase Cloud Messaging health", err) 50 + } else { 51 + response.Firebase = true 52 + } 53 + 54 + // Marshal the response to JSON and write it to the response writer 55 + if err := json.NewEncoder(w).Encode(response); err != nil { 56 + http.Error(w, "Unable to encode JSON", http.StatusInternalServerError) 57 + return 58 + } 59 + } 60 + 61 + func StartHealthCheckEndpoint(port int) { 62 + // Set up route for the /health endpoint 63 + http.HandleFunc("/health", healthHandler) 64 + 65 + // Start the server and log any errors 66 + ll.Log("Starting", "cyan", "health check endpoint on :%d/health", port) 67 + if err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil); err != nil { 68 + log.Fatalf("Server failed to start: %v", err) 69 + } 70 + } 71 + 72 + func CheckRedisHealth() error { 73 + return redisClient.Ping(context.Background()).Err() 74 + } 75 + 76 + func CheckNATSHealth() error { 77 + nc, err := nats.Connect(nats.DefaultURL) 78 + if err != nil { 79 + return fmt.Errorf("could not connect to NATS at %s: %w", nats.DefaultURL, err) 80 + } 81 + 82 + _, err = nc.JetStream() 83 + if err != nil { 84 + return fmt.Errorf("could not connect to Jetstream: %w", err) 85 + } 86 + 87 + return nil 88 + } 89 + 90 + func CheckChurrosDatabaseHealth() error { 91 + return ConnectToDababase() 92 + } 93 + 94 + func CheckFirebaseHealth() error { 95 + fcm, err := firebaseClient.Messaging(firebaseCtx) 96 + if err != nil { 97 + return fmt.Errorf("while initializing messaging client: %w", err) 98 + } 99 + 100 + _, err = fcm.SendDryRun(firebaseCtx, &messaging.Message{ 101 + Notification: &messaging.Notification{ 102 + Title: "Health check attempt", 103 + Body: "This is a health check attempt to ensure that the FCM service is working properly. The notification is not supposed to be displayed to the user.", 104 + }, 105 + Token: "invalid", 106 + }) 107 + if err != nil && err.Error() == "The registration token is not a valid FCM registration token" { 108 + return nil 109 + } 110 + return err 111 + }
-2
index.ts
··· 1 - export * from './types.js'; 2 - export * from './constants.js';
+3 -5
package.json
··· 35 35 }, 36 36 "files": [ 37 37 "README.md", 38 - "constants.ts", 39 - "types.ts", 40 - "index.ts" 38 + "typescript/*.ts" 41 39 ], 42 40 "exports": { 43 41 ".": { 44 - "default": "./index.ts", 45 - "types": "./index.ts" 42 + "default": "./typescript/index.ts", 43 + "types": "./typescript/index.ts" 46 44 } 47 45 } 48 46 }
+23 -6
scripts/typing.go
··· 6 6 "io" 7 7 "os" 8 8 "os/exec" 9 + "strings" 9 10 10 11 "git.inpt.fr/churros/notella" 11 12 "github.com/invopop/jsonschema" ··· 16 17 if err := reflector.AddGoComments("git.inpt.fr/churros/notella", "./"); err != nil { 17 18 fmt.Printf("Error adding Go comments: %v\n", err) 18 19 } 19 - schema := reflector.Reflect(&notella.Message{}) 20 + 21 + writeTypescriptDefinition(reflector, "Message", &notella.Message{}, "typescript/message.ts") 22 + writeTypescriptDefinition(reflector, "HealthResponse", &notella.HealthResponse{}, "typescript/health.ts") 23 + reflector.FieldNameTag = "env" 24 + writeTypescriptDefinition(reflector, "Configuration", &notella.Configuration{}, "typescript/configuration.ts") 25 + 26 + // Also save useful constants 27 + os.WriteFile("typescript/constants.ts", []byte(fmt.Sprintf("export const STREAM_NAME = '%s';\nexport const SUBJECT_NAME = '%s';\n", notella.StreamName, notella.SubjectName)), 0644) 28 + 29 + // Write barrel 30 + os.WriteFile("typescript/index.ts", []byte(strings.Join([]string{ 31 + "export * from './message.js';", 32 + "export * from './configuration.js';", 33 + "export * from './health.js';", 34 + "export * from './constants.js';", 35 + }, "\n")), 0644) 36 + } 37 + 38 + func writeTypescriptDefinition(reflector *jsonschema.Reflector, typename string, typ interface{}, filename string) { 39 + schema := reflector.Reflect(typ) 20 40 schemaJSON, err := json.Marshal(schema) 21 41 if err != nil { 22 42 fmt.Printf("Error generating schema: %v\n", err) ··· 24 44 } 25 45 26 46 // Set up quicktype command to read from stdin 27 - cmd := exec.Command("npm", "exec", "quicktype", "--", "--lang=ts", "--src-lang=schema", "--just-types", "--top-level=Message") 47 + cmd := exec.Command("npm", "exec", "quicktype", "--", "--lang=ts", "--src-lang=schema", "--just-types", fmt.Sprintf("--top-level=%s", typename)) 28 48 29 49 // Create a pipe to stdin for the quicktype command 30 50 stdin, err := cmd.StdinPipe() ··· 81 101 } 82 102 83 103 // Print or save the TypeScript output 84 - os.WriteFile("types.ts", output, 0644) 85 - 86 - // Also save useful constants 87 - os.WriteFile("constants.ts", []byte(fmt.Sprintf("export const STREAM_NAME = '%s';\nexport const SUBJECT_NAME = '%s';\n", notella.StreamName, notella.SubjectName)), 0644) 104 + os.WriteFile(filename, output, 0644) 88 105 }
+4
server/main.go
··· 31 31 ll.Log("", "reset", "contact email: [bold]%s[reset]", config.ContactEmail) 32 32 ll.Log("", "reset", "Churros DB URL: [bold]%s[reset]", redactURL(config.ChurrosDatabaseURL)) 33 33 ll.Log("", "reset", "Redis URL: [bold]%s[reset]", redactURL(config.RedisURL)) 34 + ll.Log("", "reset", "Health check on: [bold]:%d/health[reset]", config.HealthCheckPort) 34 35 ll.Log("", "reset", "App Package ID: [bold]%s[reset]", config.AppPackageId) 35 36 if config.VapidPublicKey != "" && config.VapidPrivateKey != "" { 36 37 ll.Log("", "reset", "VAPID keys: [bold][green]set[reset]") ··· 113 114 ll.Log("Shuting down", "magenta", "because of signal received") 114 115 cancel() 115 116 }() 117 + 118 + // Start healthcheck endpoint 119 + go notella.StartHealthCheckEndpoint(config.HealthCheckPort) 116 120 117 121 // Send EventShowScheduledJobs to the stream every 5 minutes and save schedule to redis 118 122 go func() {
types.ts typescript/message.ts
+11
typescript/configuration.ts
··· 1 + export interface Configuration { 2 + APP_PACKAGE_ID: string; 3 + CONTACT_EMAIL: string; 4 + DATABASE_URL: string; 5 + FIREBASE_SERVICE_ACCOUNT: string; 6 + HEALTH_CHECK_PORT: number; 7 + PUBLIC_VAPID_KEY: string; 8 + REDIS_URL: string; 9 + STARTUP_SCHEDULE_RESTORATION: string; 10 + VAPID_PRIVATE_KEY: string; 11 + }
+6
typescript/health.ts
··· 1 + export interface HealthResponse { 2 + churros_db: boolean; 3 + firebase: boolean; 4 + nats: boolean; 5 + redis: boolean; 6 + }
+4
typescript/index.ts
··· 1 + export * from './message.js'; 2 + export * from './configuration.js'; 3 + export * from './health.js'; 4 + export * from './constants.js';