fg-library/main.go

223 lines
6.3 KiB
Go
Raw Permalink Normal View History

2024-10-03 19:03:18 +01:00
package library
import (
"errors"
"github.com/go-chi/chi/v5"
2024-10-03 19:03:18 +01:00
"github.com/google/uuid"
"io/fs"
2025-01-01 12:23:29 +00:00
"math/big"
"sync"
2024-10-03 19:03:18 +01:00
"time"
)
type Permissions struct {
2025-01-01 12:23:29 +00:00
// Authenticate allows the service to register with the nucleus authentication service and use OAuth2
Authenticate 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
2024-10-03 19:03:18 +01:00
InterServiceCommunication bool `validate:"required"`
2025-01-01 12:23:29 +00:00
// Resources allows the service to access their resource directory
Resources bool `validate:"required"`
2024-10-03 19:03:18 +01:00
}
type Service struct {
Name string `validate:"required"`
Permissions Permissions `validate:"required"`
ServiceID uuid.UUID `validate:"required"`
}
type InterServiceMessage struct {
2025-01-01 12:23:29 +00:00
MessageID uuid.UUID `validate:"required"`
ServiceID uuid.UUID `validate:"required"`
ForServiceID uuid.UUID `validate:"required"`
MessageType MessageCode `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,
}
2024-10-03 19:03:18 +01:00
}
type ServiceInitializationInformation struct {
2025-01-01 12:23:29 +00:00
Service *Service `validate:"required"`
Domain *string
2024-10-03 19:03:18 +01:00
Outbox chan<- InterServiceMessage `validate:"required"`
2025-01-01 12:23:29 +00:00
inbox <-chan InterServiceMessage `validate:"required"`
Router *chi.Mux
2024-10-03 19:03:18 +01:00
Configuration map[string]interface{}
ResourceDir fs.FS
}
2024-10-05 19:33:00 +01:00
2025-01-01 12:23:29 +00:00
// 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
}
2024-10-05 19:33:00 +01:00
2025-01-01 12:23:29 +00:00
type ColumnType interface{}
type (
// String represents arbitrary sized text
String string
// Int32 represents a 32-bit signed integer
Int32 int32
// Int64 represents a 64-bit signed integer
Int64 int64
// IntInf represents an arbitrary sized signed integer
IntInf big.Int
// Float32 represents a 32-bit floating point number
Float32 float32
// Float64 represents a 64-bit floating point number
Float64 float64
// Boolean represents a boolean value
Boolean bool
// Blob represents an arbitrary sized binary object
Blob []byte
// UUID represents a UUID value
UUID uuid.UUID
// Time represents a time value
Time time.Time
2024-10-05 19:33:00 +01:00
)
2025-01-01 12:23:29 +00:00
type TableSchema map[string]ColumnType
type Row map[string]any
type Rows []Row
type QueryParameters struct {
Equal map[string]any
NotEqual map[string]any
GreaterThan map[string]any
LessThan map[string]any
GreaterThanOrEqual map[string]any
LessThanOrEqual map[string]any
2024-10-05 19:33:00 +01:00
}
2025-01-01 12:23:29 +00:00
type UpdateParameters map[string]any
type Database interface {
CreateTable(name string, schema TableSchema) error
DeleteTable(name string) error
InsertRow(name string, row Row) error
Delete(name string, params QueryParameters) error
Select(name string, params QueryParameters) (Rows, error)
Update(name string, params QueryParameters, update UpdateParameters) error
}
type MessageCode int
const (
2025-01-01 12:23:29 +00:00
Success MessageCode = iota
BadRequest
InternalError
Unauthorized
)
var buffer = make(map[uuid.UUID]InterServiceMessage)
var mutex = sync.Mutex{}
var arrived = make(chan uuid.UUID)
2025-01-01 12:23:29 +00:00
var ispStarted = false
2025-01-01 12:23:29 +00:00
func (s *ServiceInitializationInformation) StartISProcessor() {
if ispStarted {
return
} else {
ispStarted = true
}
listener := NewListener(s.inbox)
for {
2025-01-01 12:23:29 +00:00
msg := listener.AcceptMessage()
mutex.Lock()
buffer[msg.MessageID] = msg
mutex.Unlock()
arrived <- msg.MessageID
}
}
var (
ErrTimeout = errors.New("timeout")
)
func AwaitISMessage(id uuid.UUID, timeout time.Duration) (InterServiceMessage, error) {
for {
select {
case <-time.After(timeout):
return InterServiceMessage{}, ErrTimeout
case msgID := <-arrived:
if msgID == id {
mutex.Lock()
msg := buffer[id]
delete(buffer, id)
mutex.Unlock()
2025-01-01 12:23:29 +00:00
if msg.MessageType != Success {
return msg, msg.Message.(error)
}
return msg, nil
}
}
}
}
2025-01-01 12:23:29 +00:00
type Listener interface {
AcceptMessage() InterServiceMessage
}
type DefaultListener <-chan InterServiceMessage
func NewListener(c <-chan InterServiceMessage) Listener {
return DefaultListener(c)
}
func (l DefaultListener) AcceptMessage() InterServiceMessage {
return <-l
}
func (s *ServiceInitializationInformation) SendISMessage(forService uuid.UUID, messageType MessageCode, message any) uuid.UUID {
id := uuid.New()
msg := InterServiceMessage{
MessageID: id,
2025-01-01 12:23:29 +00:00
ServiceID: s.Service.ServiceID,
ForServiceID: forService,
MessageType: messageType,
SentAt: time.Now(),
Message: message,
}
s.Outbox <- msg
return id
}
2025-01-01 12:23:29 +00:00
func (s *ServiceInitializationInformation) SendAndAwaitISMessage(forService uuid.UUID, messageType MessageCode, message any, timeout time.Duration) (InterServiceMessage, error) {
id := s.SendISMessage(forService, messageType, message)
return AwaitISMessage(id, timeout)
}
2025-01-01 12:23:29 +00:00
func (s *ServiceInitializationInformation) GetDatabase() (Database, error) {
if !ispStarted {
go s.StartISProcessor()
}
response, err := s.SendAndAwaitISMessage(uuid.Nil, 0, nil, 5*time.Second)
if err != nil {
return nil, err
}
return response.Message.(Database), nil
}