2026-09-19 21:58:24 +02:00
|
|
|
package eventbus
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
"sync"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type InterviewNoteEvent struct {
|
|
|
|
|
ID uint `json:"id"`
|
|
|
|
|
AuthorUserID uint `json:"authorUserId"`
|
|
|
|
|
AuthorName string `json:"authorName"`
|
|
|
|
|
Body string `json:"body"`
|
|
|
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-19 22:22:42 +02:00
|
|
|
type InterviewEvent struct {
|
|
|
|
|
Type string `json:"type"`
|
|
|
|
|
InterviewID uint `json:"interviewId"`
|
|
|
|
|
Note *InterviewNoteEvent `json:"note,omitempty"`
|
|
|
|
|
SharedDocument string `json:"sharedDocument,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-19 21:58:24 +02:00
|
|
|
type IEventBus interface {
|
2026-09-19 22:22:42 +02:00
|
|
|
PublishInterviewEvent(context.Context, InterviewEvent) error
|
|
|
|
|
SubscribeInterviewEvents(context.Context) (<-chan InterviewEvent, error)
|
2026-09-19 21:58:24 +02:00
|
|
|
Close() error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var ErrClosed = errors.New("event bus is closed")
|
|
|
|
|
|
|
|
|
|
type MemoryBus struct {
|
|
|
|
|
mu sync.RWMutex
|
2026-09-19 22:22:42 +02:00
|
|
|
subscribers map[chan InterviewEvent]struct{}
|
2026-09-19 21:58:24 +02:00
|
|
|
closed bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewMemoryBus() *MemoryBus {
|
2026-09-19 22:22:42 +02:00
|
|
|
return &MemoryBus{subscribers: make(map[chan InterviewEvent]struct{})}
|
2026-09-19 21:58:24 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-19 22:22:42 +02:00
|
|
|
func (b *MemoryBus) PublishInterviewEvent(ctx context.Context, event InterviewEvent) error {
|
2026-09-19 21:58:24 +02:00
|
|
|
b.mu.RLock()
|
|
|
|
|
defer b.mu.RUnlock()
|
|
|
|
|
if b.closed {
|
|
|
|
|
return ErrClosed
|
|
|
|
|
}
|
|
|
|
|
for subscriber := range b.subscribers {
|
|
|
|
|
select {
|
|
|
|
|
case subscriber <- event:
|
|
|
|
|
case <-ctx.Done():
|
|
|
|
|
return ctx.Err()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-19 22:22:42 +02:00
|
|
|
func (b *MemoryBus) SubscribeInterviewEvents(ctx context.Context) (<-chan InterviewEvent, error) {
|
2026-09-19 21:58:24 +02:00
|
|
|
b.mu.Lock()
|
|
|
|
|
if b.closed {
|
|
|
|
|
b.mu.Unlock()
|
|
|
|
|
return nil, ErrClosed
|
|
|
|
|
}
|
2026-09-19 22:22:42 +02:00
|
|
|
ch := make(chan InterviewEvent, 16)
|
2026-09-19 21:58:24 +02:00
|
|
|
b.subscribers[ch] = struct{}{}
|
|
|
|
|
b.mu.Unlock()
|
|
|
|
|
go func() {
|
|
|
|
|
<-ctx.Done()
|
|
|
|
|
b.mu.Lock()
|
|
|
|
|
if _, ok := b.subscribers[ch]; ok {
|
|
|
|
|
delete(b.subscribers, ch)
|
|
|
|
|
close(ch)
|
|
|
|
|
}
|
|
|
|
|
b.mu.Unlock()
|
|
|
|
|
}()
|
|
|
|
|
return ch, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (b *MemoryBus) Close() error {
|
|
|
|
|
b.mu.Lock()
|
|
|
|
|
defer b.mu.Unlock()
|
|
|
|
|
if b.closed {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
b.closed = true
|
|
|
|
|
for subscriber := range b.subscribers {
|
|
|
|
|
close(subscriber)
|
|
|
|
|
}
|
|
|
|
|
clear(b.subscribers)
|
|
|
|
|
return nil
|
|
|
|
|
}
|