Compare commits

...

20 commits
v1.0.0 ... main

Author SHA1 Message Date
e9c64bb56a ISM ISM ISM ISM ISM 2025-01-08 17:49:05 +00:00
ff28e23636 ISM rewrite part... 10? I think? God this is more complicated than I thought... 2025-01-08 17:48:03 +00:00
7e63d4d5c4 ISM rewrite part 9 2025-01-08 17:46:34 +00:00
f3cfc9e170 ISM rewrite part 8 2025-01-08 17:03:48 +00:00
b82928e820 ISM rewrite part 7 2025-01-08 16:42:05 +00:00
2ae6af81ab ISM rewrite part 6 2025-01-08 12:57:12 +00:00
b005f7f10a ISM rewrite part 5 2025-01-08 12:52:30 +00:00
a77529f4f7 ISM rewrite part 5 2025-01-08 10:28:58 +00:00
a02b0ba721 ISM rewrite part 4 2025-01-07 18:03:32 +00:00
4abda3c58f ISM rewrite part 3 2025-01-02 12:30:34 +00:00
d404caa114 ISM rewrite part 2 2025-01-01 12:23:29 +00:00
ba2582e851 Added some helper functions and nonces for the packets 2024-12-31 15:53:45 +00:00
9572989a86 Upgrade go version to match with fulgens 2024-11-08 18:06:27 +00:00
1794393fe9 Downgrade to Go 1.22 for compatiblity reasons 2024-10-23 19:29:58 +01:00
b840529d13 Try to fix the borked subdomain architecture 2024-10-20 19:45:28 +01:00
5ac3ec1753 Removed unused import 2024-10-15 19:07:44 +01:00
643732f349 Updated to v2 library 2024-10-15 19:00:05 +01:00
34f6393304 Accidentally added IDEA folder 2024-10-15 18:58:18 +01:00
f382f53485 Updated to match with new version of fulgens 2024-10-15 18:58:03 +01:00
1567237f5c Added the Database struct 2024-10-05 19:33:00 +01:00
3 changed files with 204 additions and 15 deletions

6
go.mod
View file

@ -1,8 +1,8 @@
module git.ailur.dev/ailur/fg-library module git.ailur.dev/ailur/fg-library/v3
go 1.23.1 go 1.23.3
require ( require (
github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/chi/v5 v5.2.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
) )

2
go.sum
View file

@ -1,4 +1,6 @@
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=

211
main.go
View file

@ -1,18 +1,28 @@
package library package library
import ( import (
"database/sql"
"errors"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/google/uuid" "github.com/google/uuid"
"io/fs" "io/fs"
"sync"
"time" "time"
) )
type Permissions struct { type Permissions struct {
Authenticate bool `validate:"required"` // Authenticate allows the service to register with the nucleus authentication service and use OAuth2
Database bool `validate:"required"` Authenticate bool `validate:"required"`
BlobStorage bool `validate:"required"` // Router allows the service to serve web pages
Router bool `validate:"required"`
// Database allows the service to ask for a centralised database connection
Database bool `validate:"required"`
// BlobStorage allows the service to use the blob storage service
BlobStorage bool `validate:"required"`
// InterServiceCommunication allows the service to send and receive messages from other services
InterServiceCommunication bool `validate:"required"` InterServiceCommunication bool `validate:"required"`
Resources bool `validate:"required"` // Resources allows the service to access their resource directory
Resources bool `validate:"required"`
} }
type Service struct { type Service struct {
@ -22,18 +32,195 @@ type Service struct {
} }
type InterServiceMessage struct { type InterServiceMessage struct {
ServiceID uuid.UUID `validate:"required"` MessageID uuid.UUID `validate:"required"`
ForServiceID uuid.UUID `validate:"required"` ServiceID uuid.UUID `validate:"required"`
MessageType uint64 `validate:"required"` ForServiceID uuid.UUID `validate:"required"`
SentAt time.Time `validate:"required"` MessageType MessageCode `validate:"required"`
Message any `validate:"required"` SentAt time.Time `validate:"required"`
Message any `validate:"required"`
}
// NewServiceInitializationInformation creates a new ServiceInitializationInformation and is only ever meant to be called
// by fulgens or a compliant implementation of fulgens.
func NewServiceInitializationInformation(domain *string, outbox chan<- InterServiceMessage, inbox <-chan InterServiceMessage, router *chi.Mux, configuration map[string]interface{}, resourceDir fs.FS) *ServiceInitializationInformation {
return &ServiceInitializationInformation{
Domain: domain,
Outbox: outbox,
inbox: inbox,
Router: router,
Configuration: configuration,
ResourceDir: resourceDir,
internal: internal{
buffer: make(map[uuid.UUID]InterServiceMessage),
waitingList: make(map[uuid.UUID]struct{}),
arrived: make(chan uuid.UUID),
},
}
}
type internal struct {
buffer map[uuid.UUID]InterServiceMessage
waitingList map[uuid.UUID]struct{}
mutex sync.Mutex
arrived chan uuid.UUID
ispStarted bool
} }
type ServiceInitializationInformation struct { type ServiceInitializationInformation struct {
Domain string `validate:"required"` Service *Service `validate:"required"`
Domain *string
Outbox chan<- InterServiceMessage `validate:"required"` Outbox chan<- InterServiceMessage `validate:"required"`
Inbox <-chan InterServiceMessage `validate:"required"` inbox <-chan InterServiceMessage `validate:"required"`
Router *chi.Mux `validate:"required"` Router *chi.Mux
Configuration map[string]interface{} Configuration map[string]interface{}
ResourceDir fs.FS ResourceDir fs.FS
internal internal
}
// YesIAbsolutelyKnowWhatIAmDoingAndIWantToAccessTheRawInbox returns a channel that can be used to read messages from
// the inbox. This is a dangerous operation, can and will break the buffer, and you should most absolutely not use this
// unless you would like to handle the messages yourself with no outside help or synchronization.
//
// If you think you know what you're doing, **you probably don't**.
func (s *ServiceInitializationInformation) YesIAbsolutelyKnowWhatIAmDoingAndIWantToAccessTheRawInbox() <-chan InterServiceMessage {
return s.inbox
}
type DBType int
const (
Sqlite DBType = iota
Postgres
)
type Database struct {
DB *sql.DB
DBType DBType
}
type MessageCode int
const (
Success MessageCode = iota
BadRequest
InternalError
Unauthorized
)
func (s *ServiceInitializationInformation) StartISProcessor() {
if s.internal.ispStarted {
return
} else {
s.internal.ispStarted = true
}
listener := NewListener(s.inbox)
for {
msg := listener.AcceptMessage()
s.internal.mutex.Lock()
s.internal.buffer[msg.MessageID] = msg
s.internal.mutex.Unlock()
s.internal.arrived <- msg.MessageID
}
}
var (
ErrTimeout = errors.New("timeout")
)
func (s *ServiceInitializationInformation) AwaitISMessage(id uuid.UUID, timeout time.Duration) (InterServiceMessage, error) {
for {
select {
case <-time.After(timeout):
return InterServiceMessage{}, ErrTimeout
case msgID := <-s.internal.arrived:
if msgID == id {
s.internal.mutex.Lock()
msg := s.internal.buffer[id]
delete(s.internal.buffer, id)
delete(s.internal.waitingList, id)
s.internal.mutex.Unlock()
if msg.MessageType != Success {
return msg, msg.Message.(error)
}
return msg, nil
}
}
}
}
type Listener interface {
AcceptMessage() InterServiceMessage
}
type DefaultListener <-chan InterServiceMessage
func NewListener(c <-chan InterServiceMessage) Listener {
return DefaultListener(c)
}
func (l DefaultListener) AcceptMessage() InterServiceMessage {
msg := <-l
return msg
}
func (s *ServiceInitializationInformation) SendISMessage(forService uuid.UUID, messageType MessageCode, message any) uuid.UUID {
id := uuid.New()
msg := InterServiceMessage{
MessageID: id,
ServiceID: s.Service.ServiceID,
ForServiceID: forService,
MessageType: messageType,
SentAt: time.Now(),
Message: message,
}
s.internal.mutex.Lock()
s.internal.waitingList[id] = struct{}{}
s.internal.mutex.Unlock()
s.Outbox <- msg
return id
}
func (s *InterServiceMessage) Respond(messageType MessageCode, message any, information *ServiceInitializationInformation) {
n := *s
n.ServiceID, n.ForServiceID = n.ForServiceID, n.ServiceID
n.MessageType = messageType
n.Message = message
n.SentAt = time.Now()
information.Outbox <- n
}
func (s *ServiceInitializationInformation) SendAndAwaitISMessage(forService uuid.UUID, messageType MessageCode, message any, timeout time.Duration) (InterServiceMessage, error) {
id := s.SendISMessage(forService, messageType, message)
return s.AwaitISMessage(id, timeout)
}
var databaseService = uuid.MustParse("00000000-0000-0000-0000-000000000001")
func (s *ServiceInitializationInformation) GetDatabase() (Database, error) {
if !s.internal.ispStarted {
go s.StartISProcessor()
}
response, err := s.SendAndAwaitISMessage(databaseService, 0, nil, 5*time.Second)
if err != nil {
return Database{}, err
}
return response.Message.(Database), nil
}
func (s *ServiceInitializationInformation) AcceptMessage() InterServiceMessage {
for {
<-s.internal.arrived
s.internal.mutex.Lock()
for id, msg := range s.internal.buffer {
_, ok := s.internal.waitingList[id]
if !ok {
delete(s.internal.buffer, id)
s.internal.mutex.Unlock()
return msg
}
}
s.internal.mutex.Unlock()
}
} }