Integrate Nick's local S3 server
Works like a charm! Small tweak for clarity
This commit is contained in:
parent
4187a3b6ca
commit
c1fa6cae13
|
@ -13,3 +13,4 @@ hmn.conf
|
||||||
adminmailer/config.go
|
adminmailer/config.go
|
||||||
adminmailer/adminmailer
|
adminmailer/adminmailer
|
||||||
local/backups
|
local/backups
|
||||||
|
/tmp
|
||||||
|
|
|
@ -67,7 +67,7 @@ func SanitizeFilename(filename string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func AssetKey(id, filename string) string {
|
func AssetKey(id, filename string) string {
|
||||||
return fmt.Sprintf("%s%s/%s", config.Config.DigitalOcean.AssetsPathPrefix, id, filename)
|
return fmt.Sprintf("%s/%s", id, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
type InvalidAssetError error
|
type InvalidAssetError error
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.handmade.network/hmn/hmn/src/db"
|
"git.handmade.network/hmn/hmn/src/db"
|
||||||
|
"git.handmade.network/hmn/hmn/src/jobs"
|
||||||
"git.handmade.network/hmn/hmn/src/logging"
|
"git.handmade.network/hmn/hmn/src/logging"
|
||||||
"git.handmade.network/hmn/hmn/src/models"
|
"git.handmade.network/hmn/hmn/src/models"
|
||||||
"git.handmade.network/hmn/hmn/src/oops"
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
|
@ -234,10 +235,10 @@ func DeleteExpiredPasswordResets(ctx context.Context, conn *pgxpool.Pool) (int64
|
||||||
return tag.RowsAffected(), nil
|
return tag.RowsAffected(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func PeriodicallyDeleteInactiveUsers(ctx context.Context, conn *pgxpool.Pool) <-chan struct{} {
|
func PeriodicallyDeleteInactiveUsers(ctx context.Context, conn *pgxpool.Pool) jobs.Job {
|
||||||
done := make(chan struct{})
|
job := jobs.New()
|
||||||
go func() {
|
go func() {
|
||||||
defer close(done)
|
defer job.Done()
|
||||||
|
|
||||||
t := time.NewTicker(1 * time.Hour)
|
t := time.NewTicker(1 * time.Hour)
|
||||||
for {
|
for {
|
||||||
|
@ -265,5 +266,5 @@ func PeriodicallyDeleteInactiveUsers(ctx context.Context, conn *pgxpool.Pool) <-
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return done
|
return job
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
|
|
||||||
"git.handmade.network/hmn/hmn/src/config"
|
"git.handmade.network/hmn/hmn/src/config"
|
||||||
"git.handmade.network/hmn/hmn/src/db"
|
"git.handmade.network/hmn/hmn/src/db"
|
||||||
|
"git.handmade.network/hmn/hmn/src/jobs"
|
||||||
"git.handmade.network/hmn/hmn/src/logging"
|
"git.handmade.network/hmn/hmn/src/logging"
|
||||||
"git.handmade.network/hmn/hmn/src/models"
|
"git.handmade.network/hmn/hmn/src/models"
|
||||||
"git.handmade.network/hmn/hmn/src/oops"
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
|
@ -132,10 +133,10 @@ func DeleteExpiredSessions(ctx context.Context, conn *pgxpool.Pool) (int64, erro
|
||||||
return tag.RowsAffected(), nil
|
return tag.RowsAffected(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func PeriodicallyDeleteExpiredSessions(ctx context.Context, conn *pgxpool.Pool) <-chan struct{} {
|
func PeriodicallyDeleteExpiredSessions(ctx context.Context, conn *pgxpool.Pool) jobs.Job {
|
||||||
done := make(chan struct{})
|
job := jobs.New()
|
||||||
go func() {
|
go func() {
|
||||||
defer close(done)
|
defer job.Done()
|
||||||
|
|
||||||
t := time.NewTicker(1 * time.Minute)
|
t := time.NewTicker(1 * time.Minute)
|
||||||
for {
|
for {
|
||||||
|
@ -154,5 +155,5 @@ func PeriodicallyDeleteExpiredSessions(ctx context.Context, conn *pgxpool.Pool)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return done
|
return job
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,13 +38,20 @@ var Config = HMNConfig{
|
||||||
OverrideRecipientEmail: "override@handmade.network", // NOTE(asaf): If this is not empty, all emails will be redirected to this address.
|
OverrideRecipientEmail: "override@handmade.network", // NOTE(asaf): If this is not empty, all emails will be redirected to this address.
|
||||||
},
|
},
|
||||||
DigitalOcean: DigitalOceanConfig{
|
DigitalOcean: DigitalOceanConfig{
|
||||||
AssetsSpacesKey: "",
|
AssetsSpacesKey: "dummy",
|
||||||
AssetsSpacesSecret: "",
|
AssetsSpacesSecret: "dummy",
|
||||||
AssetsSpacesRegion: "",
|
AssetsSpacesRegion: "dummy",
|
||||||
AssetsSpacesEndpoint: "",
|
AssetsSpacesEndpoint: "http://handmade.local:9003/",
|
||||||
AssetsSpacesBucket: "",
|
AssetsSpacesBucket: "assets",
|
||||||
AssetsPathPrefix: "", // Empty is fine for production, but may be necessary for dev
|
AssetsPublicUrlRoot: "http://handmade.local:9003/assets/",
|
||||||
AssetsPublicUrlRoot: "", // e.g. "https://bucket-name.region.cdn.digitaloceanspaces.com/". Note the trailing slash...
|
// In prod, AssetsPublicUrlRoot will probably look something like:
|
||||||
|
//
|
||||||
|
// "https://bucket-name.region.cdn.digitaloceanspaces.com/"
|
||||||
|
//
|
||||||
|
// Note the trailing slash...
|
||||||
|
|
||||||
|
RunFakeServer: true,
|
||||||
|
FakeAddr: "localhost:9003",
|
||||||
},
|
},
|
||||||
Discord: DiscordConfig{
|
Discord: DiscordConfig{
|
||||||
BotToken: "",
|
BotToken: "",
|
||||||
|
|
|
@ -53,8 +53,10 @@ type DigitalOceanConfig struct {
|
||||||
AssetsSpacesRegion string
|
AssetsSpacesRegion string
|
||||||
AssetsSpacesEndpoint string
|
AssetsSpacesEndpoint string
|
||||||
AssetsSpacesBucket string
|
AssetsSpacesBucket string
|
||||||
AssetsPathPrefix string
|
|
||||||
AssetsPublicUrlRoot string
|
AssetsPublicUrlRoot string
|
||||||
|
|
||||||
|
RunFakeServer bool
|
||||||
|
FakeAddr string
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmailConfig struct {
|
type EmailConfig struct {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
"git.handmade.network/hmn/hmn/src/config"
|
"git.handmade.network/hmn/hmn/src/config"
|
||||||
"git.handmade.network/hmn/hmn/src/db"
|
"git.handmade.network/hmn/hmn/src/db"
|
||||||
|
"git.handmade.network/hmn/hmn/src/jobs"
|
||||||
"git.handmade.network/hmn/hmn/src/logging"
|
"git.handmade.network/hmn/hmn/src/logging"
|
||||||
"git.handmade.network/hmn/hmn/src/models"
|
"git.handmade.network/hmn/hmn/src/models"
|
||||||
"git.handmade.network/hmn/hmn/src/oops"
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
|
@ -22,22 +23,20 @@ import (
|
||||||
"github.com/jpillora/backoff"
|
"github.com/jpillora/backoff"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunDiscordBot(ctx context.Context, dbConn *pgxpool.Pool) <-chan struct{} {
|
func RunDiscordBot(ctx context.Context, dbConn *pgxpool.Pool) jobs.Job {
|
||||||
log := logging.ExtractLogger(ctx).With().Str("module", "discord").Logger()
|
log := logging.ExtractLogger(ctx).With().Str("module", "discord").Logger()
|
||||||
ctx = logging.AttachLoggerToContext(&log, ctx)
|
ctx = logging.AttachLoggerToContext(&log, ctx)
|
||||||
|
|
||||||
if config.Config.Discord.BotToken == "" {
|
if config.Config.Discord.BotToken == "" {
|
||||||
log.Warn().Msg("No Discord bot token was provided, so the Discord bot cannot run.")
|
log.Warn().Msg("No Discord bot token was provided, so the Discord bot cannot run.")
|
||||||
done := make(chan struct{}, 1)
|
return jobs.Noop()
|
||||||
done <- struct{}{}
|
|
||||||
return done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
done := make(chan struct{})
|
job := jobs.New()
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Debug().Msg("shut down Discord bot")
|
log.Debug().Msg("shut down Discord bot")
|
||||||
done <- struct{}{}
|
job.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
boff := backoff.Backoff{
|
boff := backoff.Backoff{
|
||||||
|
@ -88,7 +87,7 @@ func RunDiscordBot(ctx context.Context, dbConn *pgxpool.Pool) <-chan struct{} {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return done
|
return job
|
||||||
}
|
}
|
||||||
|
|
||||||
var outgoingMessagesReady = make(chan struct{}, 1)
|
var outgoingMessagesReady = make(chan struct{}, 1)
|
||||||
|
|
|
@ -7,27 +7,26 @@ import (
|
||||||
|
|
||||||
"git.handmade.network/hmn/hmn/src/config"
|
"git.handmade.network/hmn/hmn/src/config"
|
||||||
"git.handmade.network/hmn/hmn/src/db"
|
"git.handmade.network/hmn/hmn/src/db"
|
||||||
|
"git.handmade.network/hmn/hmn/src/jobs"
|
||||||
"git.handmade.network/hmn/hmn/src/logging"
|
"git.handmade.network/hmn/hmn/src/logging"
|
||||||
"git.handmade.network/hmn/hmn/src/models"
|
"git.handmade.network/hmn/hmn/src/models"
|
||||||
"github.com/jackc/pgx/v4/pgxpool"
|
"github.com/jackc/pgx/v4/pgxpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunHistoryWatcher(ctx context.Context, dbConn *pgxpool.Pool) <-chan struct{} {
|
func RunHistoryWatcher(ctx context.Context, dbConn *pgxpool.Pool) jobs.Job {
|
||||||
log := logging.ExtractLogger(ctx).With().Str("discord goroutine", "history watcher").Logger()
|
log := logging.ExtractLogger(ctx).With().Str("discord goroutine", "history watcher").Logger()
|
||||||
ctx = logging.AttachLoggerToContext(&log, ctx)
|
ctx = logging.AttachLoggerToContext(&log, ctx)
|
||||||
|
|
||||||
if config.Config.Discord.BotToken == "" {
|
if config.Config.Discord.BotToken == "" {
|
||||||
log.Warn().Msg("No Discord bot token was provided, so the Discord history bot cannot run.")
|
log.Warn().Msg("No Discord bot token was provided, so the Discord history bot cannot run.")
|
||||||
done := make(chan struct{}, 1)
|
return jobs.Noop()
|
||||||
done <- struct{}{}
|
|
||||||
return done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
done := make(chan struct{})
|
job := jobs.New()
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Debug().Msg("shut down Discord history watcher")
|
log.Debug().Msg("shut down Discord history watcher")
|
||||||
done <- struct{}{}
|
job.Done()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
newUserTicker := time.NewTicker(5 * time.Second)
|
newUserTicker := time.NewTicker(5 * time.Second)
|
||||||
|
@ -67,7 +66,7 @@ func RunHistoryWatcher(ctx context.Context, dbConn *pgxpool.Pool) <-chan struct{
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return done
|
return job
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchMissingContent(ctx context.Context, dbConn *pgxpool.Pool) {
|
func fetchMissingContent(ctx context.Context, dbConn *pgxpool.Pool) {
|
||||||
|
|
|
@ -803,7 +803,7 @@ func HandleSnippetForInternedMessage(ctx context.Context, dbConn db.ConnOrTx, in
|
||||||
`
|
`
|
||||||
SELECT $columns{tag}
|
SELECT $columns{tag}
|
||||||
FROM
|
FROM
|
||||||
tags
|
tag
|
||||||
JOIN project ON project.tag = tag.id
|
JOIN project ON project.tag = tag.id
|
||||||
WHERE
|
WHERE
|
||||||
project.id = ANY ($1)
|
project.id = ANY ($1)
|
||||||
|
|
|
@ -1,78 +1,101 @@
|
||||||
package hmns3
|
package hmns3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"git.handmade.network/hmn/hmn/src/website"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"git.handmade.network/hmn/hmn/src/config"
|
||||||
|
"git.handmade.network/hmn/hmn/src/jobs"
|
||||||
|
"git.handmade.network/hmn/hmn/src/logging"
|
||||||
|
"git.handmade.network/hmn/hmn/src/utils"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
const dir = "./tmp/s3"
|
||||||
s3Command := &cobra.Command{
|
|
||||||
Use: "hmns3 [storage folder]",
|
type server struct {
|
||||||
Short: "Run a local s3 server that stores in the filesystem",
|
log zerolog.Logger
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
targetFolder := "./tmp"
|
|
||||||
if len(args) > 0 {
|
|
||||||
targetFolder = args[0]
|
|
||||||
}
|
|
||||||
err := os.MkdirAll(targetFolder, fs.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
func StartServer(ctx context.Context) jobs.Job {
|
||||||
bucket, key := bucket_key(r)
|
if !config.Config.DigitalOcean.RunFakeServer {
|
||||||
|
return jobs.Noop()
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println("\n\nIncoming request path:", r.URL.Path)
|
utils.Must0(os.MkdirAll(dir, fs.ModePerm))
|
||||||
bodyBytes, err := io.ReadAll(r.Body)
|
|
||||||
fmt.Println("Bucket: ", bucket, " key: ", key, " method: ", r.Method, " len(body): ", len(bodyBytes))
|
s := server{
|
||||||
if err != nil {
|
log: logging.ExtractLogger(ctx).With().
|
||||||
panic(err)
|
Str("module", "S3 server").
|
||||||
|
Logger(),
|
||||||
}
|
}
|
||||||
if r.Method == http.MethodPut {
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Location", fmt.Sprintf("/%s", bucket))
|
if r.Method == http.MethodGet {
|
||||||
err := os.MkdirAll(fmt.Sprintf("%s/%s", targetFolder, bucket), fs.ModePerm)
|
s.getObject(w, r)
|
||||||
if err != nil {
|
} else if r.Method == http.MethodPut {
|
||||||
panic(err)
|
s.putObject(w, r)
|
||||||
}
|
|
||||||
if key != "" {
|
|
||||||
err = os.WriteFile(fmt.Sprintf("%s/%s/%s",targetFolder, bucket, key), bodyBytes, fs.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if r.Method == http.MethodGet {
|
|
||||||
fileBytes, err := os.ReadFile(fmt.Sprintf("%s/%s/%s", targetFolder, bucket, key))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
w.Write(fileBytes)
|
|
||||||
} else {
|
} else {
|
||||||
panic("Unimplemented method!")
|
panic("Unimplemented method!")
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
job := jobs.New()
|
||||||
|
srv := http.Server{
|
||||||
|
Addr: config.Config.DigitalOcean.FakeAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
http.HandleFunc("/", handler)
|
s.log.Info().Msg("Starting local S3 server")
|
||||||
log.Fatal(http.ListenAndServe(":80", nil))
|
go func() {
|
||||||
},
|
defer job.Done()
|
||||||
|
err := srv.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, http.ErrServerClosed) {
|
||||||
|
// This is normal and fine
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
s.log.Info().Msg("Shutting down local S3 server")
|
||||||
|
srv.Shutdown(context.Background())
|
||||||
|
}()
|
||||||
|
|
||||||
|
return job
|
||||||
}
|
}
|
||||||
|
|
||||||
website.WebsiteCommand.AddCommand(s3Command)
|
func (s *server) getObject(w http.ResponseWriter, r *http.Request) {
|
||||||
|
bucket, key := bucketKey(r)
|
||||||
|
|
||||||
|
file := utils.Must1(os.Open(filepath.Join(dir, bucket, key)))
|
||||||
|
io.Copy(w, file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *server) putObject(w http.ResponseWriter, r *http.Request) {
|
||||||
|
bucket, key := bucketKey(r)
|
||||||
|
|
||||||
func bucket_key(r *http.Request) (string, string) {
|
w.Header().Set("Location", fmt.Sprintf("/%s", bucket))
|
||||||
|
utils.Must0(os.MkdirAll(filepath.Join(dir, bucket), fs.ModePerm))
|
||||||
|
if key != "" {
|
||||||
|
file := utils.Must1(os.Create(filepath.Join(dir, bucket, key)))
|
||||||
|
io.Copy(file, r.Body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func bucketKey(r *http.Request) (string, string) {
|
||||||
slashIdx := strings.IndexByte(r.URL.Path[1:], '/')
|
slashIdx := strings.IndexByte(r.URL.Path[1:], '/')
|
||||||
if slashIdx == -1 {
|
if slashIdx == -1 {
|
||||||
return r.URL.Path[1:], ""
|
return r.URL.Path[1:], ""
|
||||||
} else {
|
} else {
|
||||||
return r.URL.Path[1 : 1+slashIdx], strings.Replace(r.URL.Path[2+slashIdx:], "/", "~", -1)
|
return r.URL.Path[1 : 1+slashIdx], strings.ReplaceAll(r.URL.Path[2+slashIdx:], "/", "~")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
package jobs
|
||||||
|
|
||||||
|
type Job struct {
|
||||||
|
C <-chan struct{}
|
||||||
|
rawC chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() Job {
|
||||||
|
return newFromChannel(make(chan struct{}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *Job) Done() {
|
||||||
|
close(j.rawC)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combines multiple jobs into one.
|
||||||
|
func Zip(jobs ...Job) Job {
|
||||||
|
out := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
for _, job := range jobs {
|
||||||
|
<-job.C
|
||||||
|
}
|
||||||
|
close(out)
|
||||||
|
}()
|
||||||
|
return newFromChannel(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a job that is already done.
|
||||||
|
func Noop() Job {
|
||||||
|
job := New()
|
||||||
|
job.Done()
|
||||||
|
return job
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFromChannel(c chan struct{}) Job {
|
||||||
|
return Job{
|
||||||
|
C: c,
|
||||||
|
rawC: c,
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "git.handmade.network/hmn/hmn/src/admintools"
|
_ "git.handmade.network/hmn/hmn/src/admintools"
|
||||||
_ "git.handmade.network/hmn/hmn/src/hmns3"
|
|
||||||
_ "git.handmade.network/hmn/hmn/src/assets"
|
_ "git.handmade.network/hmn/hmn/src/assets"
|
||||||
_ "git.handmade.network/hmn/hmn/src/buildscss"
|
_ "git.handmade.network/hmn/hmn/src/buildscss"
|
||||||
_ "git.handmade.network/hmn/hmn/src/discord/cmd"
|
_ "git.handmade.network/hmn/hmn/src/discord/cmd"
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.handmade.network/hmn/hmn/src/jobs"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -109,20 +110,20 @@ type PerfStorage struct {
|
||||||
|
|
||||||
type PerfCollector struct {
|
type PerfCollector struct {
|
||||||
In chan<- RequestPerf
|
In chan<- RequestPerf
|
||||||
Done <-chan struct{}
|
Job jobs.Job
|
||||||
RequestCopy chan<- (chan<- PerfStorage)
|
RequestCopy chan<- (chan<- PerfStorage)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RunPerfCollector(ctx context.Context) *PerfCollector {
|
func RunPerfCollector(ctx context.Context) *PerfCollector {
|
||||||
in := make(chan RequestPerf)
|
in := make(chan RequestPerf)
|
||||||
done := make(chan struct{})
|
job := jobs.New()
|
||||||
requestCopy := make(chan (chan<- PerfStorage))
|
requestCopy := make(chan (chan<- PerfStorage))
|
||||||
|
|
||||||
var storage PerfStorage
|
var storage PerfStorage
|
||||||
// TODO(asaf): Load history from file
|
// TODO(asaf): Load history from file
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer close(done)
|
defer job.Done()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
@ -139,7 +140,7 @@ func RunPerfCollector(ctx context.Context) *PerfCollector {
|
||||||
|
|
||||||
perfCollector := PerfCollector{
|
perfCollector := PerfCollector{
|
||||||
In: in,
|
In: in,
|
||||||
Done: done,
|
Job: job,
|
||||||
RequestCopy: requestCopy,
|
RequestCopy: requestCopy,
|
||||||
}
|
}
|
||||||
return &perfCollector
|
return &perfCollector
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"git.handmade.network/hmn/hmn/src/db"
|
"git.handmade.network/hmn/hmn/src/db"
|
||||||
"git.handmade.network/hmn/hmn/src/discord"
|
"git.handmade.network/hmn/hmn/src/discord"
|
||||||
"git.handmade.network/hmn/hmn/src/hmndata"
|
"git.handmade.network/hmn/hmn/src/hmndata"
|
||||||
|
"git.handmade.network/hmn/hmn/src/jobs"
|
||||||
"git.handmade.network/hmn/hmn/src/logging"
|
"git.handmade.network/hmn/hmn/src/logging"
|
||||||
"git.handmade.network/hmn/hmn/src/models"
|
"git.handmade.network/hmn/hmn/src/models"
|
||||||
"git.handmade.network/hmn/hmn/src/oops"
|
"git.handmade.network/hmn/hmn/src/oops"
|
||||||
|
@ -25,25 +26,23 @@ type twitchNotification struct {
|
||||||
var twitchNotificationChannel chan twitchNotification
|
var twitchNotificationChannel chan twitchNotification
|
||||||
var linksChangedChannel chan struct{}
|
var linksChangedChannel chan struct{}
|
||||||
|
|
||||||
func MonitorTwitchSubscriptions(ctx context.Context, dbConn *pgxpool.Pool) <-chan struct{} {
|
func MonitorTwitchSubscriptions(ctx context.Context, dbConn *pgxpool.Pool) jobs.Job {
|
||||||
log := logging.ExtractLogger(ctx).With().Str("twitch goroutine", "stream monitor").Logger()
|
log := logging.ExtractLogger(ctx).With().Str("twitch goroutine", "stream monitor").Logger()
|
||||||
ctx = logging.AttachLoggerToContext(&log, ctx)
|
ctx = logging.AttachLoggerToContext(&log, ctx)
|
||||||
|
|
||||||
if config.Config.Twitch.ClientID == "" {
|
if config.Config.Twitch.ClientID == "" {
|
||||||
log.Warn().Msg("No twitch config provided.")
|
log.Warn().Msg("No twitch config provided.")
|
||||||
done := make(chan struct{}, 1)
|
return jobs.Noop()
|
||||||
done <- struct{}{}
|
|
||||||
return done
|
|
||||||
}
|
}
|
||||||
|
|
||||||
twitchNotificationChannel = make(chan twitchNotification, 100)
|
twitchNotificationChannel = make(chan twitchNotification, 100)
|
||||||
linksChangedChannel = make(chan struct{}, 10)
|
linksChangedChannel = make(chan struct{}, 10)
|
||||||
done := make(chan struct{})
|
job := jobs.New()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
log.Info().Msg("Shutting down twitch monitor")
|
log.Info().Msg("Shutting down twitch monitor")
|
||||||
done <- struct{}{}
|
job.Done()
|
||||||
}()
|
}()
|
||||||
log.Info().Msg("Running twitch monitor...")
|
log.Info().Msg("Running twitch monitor...")
|
||||||
|
|
||||||
|
@ -114,7 +113,7 @@ func MonitorTwitchSubscriptions(ctx context.Context, dbConn *pgxpool.Pool) <-cha
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return done
|
return job
|
||||||
}
|
}
|
||||||
|
|
||||||
type twitchNotificationType int
|
type twitchNotificationType int
|
||||||
|
|
|
@ -14,6 +14,8 @@ import (
|
||||||
"git.handmade.network/hmn/hmn/src/config"
|
"git.handmade.network/hmn/hmn/src/config"
|
||||||
"git.handmade.network/hmn/hmn/src/db"
|
"git.handmade.network/hmn/hmn/src/db"
|
||||||
"git.handmade.network/hmn/hmn/src/discord"
|
"git.handmade.network/hmn/hmn/src/discord"
|
||||||
|
"git.handmade.network/hmn/hmn/src/hmns3"
|
||||||
|
"git.handmade.network/hmn/hmn/src/jobs"
|
||||||
"git.handmade.network/hmn/hmn/src/logging"
|
"git.handmade.network/hmn/hmn/src/logging"
|
||||||
"git.handmade.network/hmn/hmn/src/perf"
|
"git.handmade.network/hmn/hmn/src/perf"
|
||||||
"git.handmade.network/hmn/hmn/src/templates"
|
"git.handmade.network/hmn/hmn/src/templates"
|
||||||
|
@ -41,13 +43,14 @@ var WebsiteCommand = &cobra.Command{
|
||||||
Handler: NewWebsiteRoutes(longRequestContext, conn),
|
Handler: NewWebsiteRoutes(longRequestContext, conn),
|
||||||
}
|
}
|
||||||
|
|
||||||
backgroundJobsDone := zipJobs(
|
backgroundJobsDone := jobs.Zip(
|
||||||
auth.PeriodicallyDeleteExpiredSessions(backgroundJobContext, conn),
|
auth.PeriodicallyDeleteExpiredSessions(backgroundJobContext, conn),
|
||||||
auth.PeriodicallyDeleteInactiveUsers(backgroundJobContext, conn),
|
auth.PeriodicallyDeleteInactiveUsers(backgroundJobContext, conn),
|
||||||
perfCollector.Done,
|
perfCollector.Job,
|
||||||
discord.RunDiscordBot(backgroundJobContext, conn),
|
discord.RunDiscordBot(backgroundJobContext, conn),
|
||||||
discord.RunHistoryWatcher(backgroundJobContext, conn),
|
discord.RunHistoryWatcher(backgroundJobContext, conn),
|
||||||
twitch.MonitorTwitchSubscriptions(backgroundJobContext, conn),
|
twitch.MonitorTwitchSubscriptions(backgroundJobContext, conn),
|
||||||
|
hmns3.StartServer(backgroundJobContext),
|
||||||
)
|
)
|
||||||
|
|
||||||
signals := make(chan os.Signal, 1)
|
signals := make(chan os.Signal, 1)
|
||||||
|
@ -81,17 +84,6 @@ var WebsiteCommand = &cobra.Command{
|
||||||
logging.Error().Err(serverErr).Msg("Server shut down unexpectedly")
|
logging.Error().Err(serverErr).Msg("Server shut down unexpectedly")
|
||||||
}
|
}
|
||||||
|
|
||||||
<-backgroundJobsDone
|
<-backgroundJobsDone.C
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func zipJobs(cs ...<-chan struct{}) <-chan struct{} {
|
|
||||||
out := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
for _, c := range cs {
|
|
||||||
<-c
|
|
||||||
}
|
|
||||||
close(out)
|
|
||||||
}()
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue