Skip to content
Snippets Groups Projects
Verified Commit 836a8bc2 authored by Gwenn Le Bihan's avatar Gwenn Le Bihan :sparkling_heart:
Browse files

wip: continue

parent ea23cc3a
Branches
Tags v0.0.1
No related merge requests found
current_version := `git describe --tags --abbrev=0 | cut -c 2-`
dev:
go run server/main.go
go mod tidy
go run server/*.go
build:
go build -o bin/server server/main.go
go mod tidy
go build -ldflags="-X notella.Version={{current_version}}" -o bin/server server/*.go
updateschema:
curl -fsSL https://git.inpt.fr/churros/churros/-/raw/main/packages/db/prisma/schema.prisma -o schema.prisma
updateopenapi:
go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=openapi-config.yaml openapi.yaml
generate:
just updateschema
just updateopenapi
package notella
import (
"context"
"fmt"
"strings"
"git.inpt.fr/churros/notella/db"
"github.com/SherClockHolmes/webpush-go"
)
var prisma = db.NewClient()
......@@ -46,28 +44,3 @@ func (id *ChurrosId) UnmarshalText(text []byte) error {
return nil
}
func notificationSubscriptionsOf(userUid string) (subscriptions []webpush.Subscription, err error) {
if err := prisma.Prisma.Connect(); err != nil {
return nil, fmt.Errorf("could not connect to prisma: %w", err)
}
subs, err := prisma.NotificationSubscription.FindMany(
db.NotificationSubscription.Owner.Where(db.User.UID.Equals(userUid)),
).Exec(context.Background())
if err != nil {
return subscriptions, fmt.Errorf("while getting notification subscriptions from database: %w", err)
}
for _, sub := range subs {
subscriptions = append(subscriptions, webpush.Subscription{
Endpoint: sub.Endpoint,
Keys: webpush.Keys{
Auth: sub.AuthKey,
P256dh: sub.P256DhKey,
},
})
}
return subscriptions, nil
}
......@@ -7,14 +7,12 @@ require (
github.com/ewen-lbh/label-logger-go v0.0.0-20241011201023-2c63f6a50d58
github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1
github.com/segmentio/encoding v0.4.0
github.com/shopspring/decimal v1.4.0
github.com/steebchen/prisma-client-go v0.42.0
)
require (
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/segmentio/asm v1.1.3 // indirect
golang.org/x/crypto v0.9.0 // indirect
)
......@@ -25,5 +23,5 @@ require (
github.com/gosuri/uiprogress v0.0.1 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/sys v0.20.0 // indirect
)
......@@ -24,10 +24,6 @@ github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2Em
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
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/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
github.com/segmentio/encoding v0.4.0 h1:MEBYvRqiUB2nfR2criEXWqwdY6HJOUrCn5hboVOVmy8=
github.com/segmentio/encoding v0.4.0/go.mod h1:/d03Cd8PoaDeceuhUUUQWjU0KhWjrmYrWPgtJHYZSnI=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/steebchen/prisma-client-go v0.42.0 h1:83keN+4jGvoTccCKCk74UU5JQj6pOwPcg3/zkoqxKJE=
......@@ -56,8 +52,9 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
......
package notella
import (
"fmt"
"time"
)
......@@ -17,13 +16,6 @@ func (job ScheduledJob) ShouldRun() bool {
}
func (job ScheduledJob) Run() error {
subscriptions, err := notificationSubscriptionsOf("versairea")
if err != nil {
return fmt.Errorf("while getting notification subscriptions for %s: %w", "versairea", err)
}
fmt.Printf("%+v\n", subscriptions)
switch job.Event {
// TODO
}
......
package notella
var Version string = "DEV"
# yaml-language-server: $schema=https://raw.githubusercontent.com/oapi-codegen/oapi-codegen/main/configuration-schema.json
package: openapi
generate:
std-http-server: true
models: true
output: openapi/openapi.go
openapi: "3.1.1"
info:
version: 1.0.0
title: churros/notella
paths:
/schedule:
post:
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ScheduleRequest"
responses:
"201":
description: created
components:
schemas:
ScheduleRequest:
type: object
required:
- when
- resource
- event
properties:
when:
type: string
format: date-time
resource:
type: string
x-go-type: notella.ChurrosId
x-go-type-import:
path: git.inpt.fr/churros/notella
name: notella
event:
type: string
x-go-type: notella.Event
x-go-type-import:
path: git.inpt.fr/churros/notella
name: notella
//go:build go1.22
// Package openapi provides primitives to interact with the openapi HTTP API.
//
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT.
package openapi
import (
"fmt"
"net/http"
"time"
notella "git.inpt.fr/churros/notella"
)
// ScheduleRequest defines model for ScheduleRequest.
type ScheduleRequest struct {
Event notella.Event `json:"event"`
Resource notella.ChurrosId `json:"resource"`
When time.Time `json:"when"`
}
// PostScheduleJSONRequestBody defines body for PostSchedule for application/json ContentType.
type PostScheduleJSONRequestBody = ScheduleRequest
// ServerInterface represents all server handlers.
type ServerInterface interface {
// (POST /schedule)
PostSchedule(w http.ResponseWriter, r *http.Request)
}
// ServerInterfaceWrapper converts contexts to parameters.
type ServerInterfaceWrapper struct {
Handler ServerInterface
HandlerMiddlewares []MiddlewareFunc
ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
}
type MiddlewareFunc func(http.Handler) http.Handler
// PostSchedule operation middleware
func (siw *ServerInterfaceWrapper) PostSchedule(w http.ResponseWriter, r *http.Request) {
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
siw.Handler.PostSchedule(w, r)
}))
for _, middleware := range siw.HandlerMiddlewares {
handler = middleware(handler)
}
handler.ServeHTTP(w, r)
}
type UnescapedCookieParamError struct {
ParamName string
Err error
}
func (e *UnescapedCookieParamError) Error() string {
return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName)
}
func (e *UnescapedCookieParamError) Unwrap() error {
return e.Err
}
type UnmarshalingParamError struct {
ParamName string
Err error
}
func (e *UnmarshalingParamError) Error() string {
return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error())
}
func (e *UnmarshalingParamError) Unwrap() error {
return e.Err
}
type RequiredParamError struct {
ParamName string
}
func (e *RequiredParamError) Error() string {
return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName)
}
type RequiredHeaderError struct {
ParamName string
Err error
}
func (e *RequiredHeaderError) Error() string {
return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName)
}
func (e *RequiredHeaderError) Unwrap() error {
return e.Err
}
type InvalidParamFormatError struct {
ParamName string
Err error
}
func (e *InvalidParamFormatError) Error() string {
return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error())
}
func (e *InvalidParamFormatError) Unwrap() error {
return e.Err
}
type TooManyValuesForParamError struct {
ParamName string
Count int
}
func (e *TooManyValuesForParamError) Error() string {
return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count)
}
// Handler creates http.Handler with routing matching OpenAPI spec.
func Handler(si ServerInterface) http.Handler {
return HandlerWithOptions(si, StdHTTPServerOptions{})
}
// ServeMux is an abstraction of http.ServeMux.
type ServeMux interface {
HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
ServeHTTP(w http.ResponseWriter, r *http.Request)
}
type StdHTTPServerOptions struct {
BaseURL string
BaseRouter ServeMux
Middlewares []MiddlewareFunc
ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
}
// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux.
func HandlerFromMux(si ServerInterface, m ServeMux) http.Handler {
return HandlerWithOptions(si, StdHTTPServerOptions{
BaseRouter: m,
})
}
func HandlerFromMuxWithBaseURL(si ServerInterface, m ServeMux, baseURL string) http.Handler {
return HandlerWithOptions(si, StdHTTPServerOptions{
BaseURL: baseURL,
BaseRouter: m,
})
}
// HandlerWithOptions creates http.Handler with additional options
func HandlerWithOptions(si ServerInterface, options StdHTTPServerOptions) http.Handler {
m := options.BaseRouter
if m == nil {
m = http.NewServeMux()
}
if options.ErrorHandlerFunc == nil {
options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) {
http.Error(w, err.Error(), http.StatusBadRequest)
}
}
wrapper := ServerInterfaceWrapper{
Handler: si,
HandlerMiddlewares: options.Middlewares,
ErrorHandlerFunc: options.ErrorHandlerFunc,
}
m.HandleFunc("POST "+options.BaseURL+"/schedule", wrapper.PostSchedule)
return m
}
......@@ -5,32 +5,38 @@ package main
import (
"fmt"
"net/http"
"time"
"os"
"strings"
"git.inpt.fr/churros/notella"
"git.inpt.fr/churros/notella/openapi"
"github.com/caarlos0/env/v11"
"github.com/common-nighthawk/go-figure"
ll "github.com/ewen-lbh/label-logger-go"
"github.com/google/uuid"
"github.com/segmentio/encoding/json"
"github.com/joho/godotenv"
)
type Configuration struct {
Port int `env:"PORT" envDefault:"8080"`
ChurrosApiUrl string `env:"CHURROS_API_URL" envDefault:"http://localhost:4000/graphql"`
PollInterval int `env:"POLL_INTERVAL_MS" envDefault:"500"`
}
type PostScheduleRequest struct {
When time.Time `json:"when"`
Ressource notella.ChurrosId `json:"ressource"`
Event notella.Event `json:"event"`
RedisURL string `env:"REDIS_URL" envDefault:"redis://localhost:6379"`
ChurrosDatabaseURL string `env:"DATABASE_URL"`
}
func main() {
figure.NewColorFigure("Notella", "", "yellow", true).Print()
fmt.Println("%40s", fmt.Sprintf("v%s", notella.Version))
fmt.Println()
if _, err := os.Stat(".env"); err == nil {
err := godotenv.Load()
if err != nil {
ll.ErrorDisplay("could not load .env file", err)
}
ll.Info("loaded .env file")
}
config := Configuration{}
err := env.Parse(&config)
if err != nil {
......@@ -39,32 +45,20 @@ func main() {
ll.Info("Running with config ")
ll.Log("", "reset", "port: [bold]%d[reset]", config.Port)
ll.Log("", "reset", "Churros API URL: [bold]%s[reset]", config.ChurrosApiUrl)
ll.Log("", "reset", "Churros API URL: [bold]%s[reset]", redactURL(config.ChurrosApiUrl))
ll.Log("", "reset", "Churros DB URL: [bold]%s[reset]", redactURL(config.ChurrosDatabaseURL))
ll.Log("", "reset", "Redis URL: [bold]%s[reset]", redactURL(config.RedisURL))
ll.Log("", "reset", "Poll interval: [bold]%d[reset] ms", config.PollInterval)
fmt.Println()
http.HandleFunc("POST /schedule", func(w http.ResponseWriter, r *http.Request) {
var req PostScheduleRequest
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
ll.ErrorDisplay("could not decode json", err)
http.Error(w, "could not decode json", http.StatusBadRequest)
return
}
job := notella.ScheduledJob{
ID: uuid.New().String(),
When: req.When,
Object: req.Ressource,
Event: req.Event,
}
job.Schedule()
w.WriteHeader(http.StatusCreated)
})
ll.Info("starting scheduler")
go notella.StartScheduler()
ll.Info("starting server on port %d", config.Port)
http.ListenAndServe(fmt.Sprintf(":%d", config.Port), nil)
mux := http.NewServeMux()
server := &http.Server{
Addr: fmt.Sprintf(":%d", config.Port),
Handler: openapi.HandlerFromMux(NewServer(), mux),
}
server.ListenAndServe()
}
package main
import (
"encoding/json"
"net/http"
"git.inpt.fr/churros/notella"
"git.inpt.fr/churros/notella/openapi"
ll "github.com/ewen-lbh/label-logger-go"
"github.com/google/uuid"
)
type Server struct{}
func NewServer() Server {
return Server{}
}
func (Server) PostSchedule(w http.ResponseWriter, r *http.Request) {
var req openapi.PostScheduleJSONRequestBody
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
ll.ErrorDisplay("could not decode json", err)
http.Error(w, "could not decode json", http.StatusBadRequest)
return
}
job := notella.ScheduledJob{
ID: uuid.New().String(),
When: req.When,
Object: req.Resource,
Event: req.Event,
}
job.Schedule()
w.WriteHeader(http.StatusCreated)
}
package main
import "net/url"
func redactURL(rawURL string) string {
u, err := url.Parse(rawURL)
if err != nil {
panic(err)
}
if u.User == nil {
return u.String()
}
u.User = url.UserPassword(u.User.Username(), "REDACTED")
return u.String()
}
package notella
import (
"context"
"fmt"
"git.inpt.fr/churros/notella/db"
"github.com/SherClockHolmes/webpush-go"
)
type SubscriptionOwner struct {
Id string `json:"id"`
Uid string `json:"uid"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
}
type Subscription struct {
Webpush webpush.Subscription `json:"webpush"`
Owner SubscriptionOwner `json:"owner"`
}
var subscriptions []Subscription
func notificationSubscriptionsFromDatabase() (subscriptions []Subscription, err error) {
if err := prisma.Prisma.Connect(); err != nil {
return nil, fmt.Errorf("could not connect to prisma: %w", err)
}
subs, err := prisma.NotificationSubscription.FindMany().With(db.NotificationSubscription.Owner.Fetch()).Exec(context.Background())
if err != nil {
return subscriptions, fmt.Errorf("while getting notification subscriptions from database: %w", err)
}
for _, sub := range subs {
subscriptions = append(subscriptions, Subscription{
Webpush: webpush.Subscription{
Endpoint: sub.Endpoint,
Keys: webpush.Keys{
Auth: sub.AuthKey,
P256dh: sub.P256DhKey,
},
},
Owner: SubscriptionOwner{
Id: sub.OwnerID,
Uid: sub.Owner().UID,
FirstName: sub.Owner().FirstName,
LastName: sub.Owner().LastName,
},
})
}
return subscriptions, nil
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment