summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2024-09-21 14:03:45 +0300
committerPaul Buetow <paul@buetow.org>2024-09-21 14:03:45 +0300
commitdff4d455e07d639b82a0bed814f41d0656e9b6d0 (patch)
tree56289a6fd80a00a9724992ab9cb8b3d7215ebedf /internal
parent780ade3dc066afb8a43be824373414f3d316ebd6 (diff)
cleanup
Diffstat (limited to 'internal')
-rw-r--r--internal/client/tui/compose.go64
-rw-r--r--internal/client/tui/finishedmsg.go21
-rw-r--r--internal/client/tui/submit.go51
-rw-r--r--internal/client/tui/tui.go136
-rw-r--r--internal/config/client/client.go41
-rw-r--r--internal/config/config.go38
-rw-r--r--internal/config/config_test.go195
-rw-r--r--internal/config/enver.go104
-rw-r--r--internal/config/server/mastodon.go1
-rw-r--r--internal/config/server/secrets.go44
-rw-r--r--internal/config/server/server.go62
-rw-r--r--internal/easyhttp/easyhttp.go110
-rw-r--r--internal/easyhttp/saferrors.go31
-rw-r--r--internal/server/cron/cron.go45
-rw-r--r--internal/server/handler/handler.go72
-rw-r--r--internal/server/health/health.go107
-rw-r--r--internal/server/health/health_test.go24
-rw-r--r--internal/server/repository/pending.go57
-rw-r--r--internal/server/repository/pending_test.go66
-rw-r--r--internal/server/repository/repository.go320
-rw-r--r--internal/server/repository/repository_test.go363
-rw-r--r--internal/server/repository/stats.go22
-rw-r--r--internal/server/scheduler/scheduler.go16
-rw-r--r--internal/server/server.go49
-rw-r--r--internal/social/mastodon.go15
-rw-r--r--internal/types/entry.go199
-rw-r--r--internal/types/entry_test.go145
-rw-r--r--internal/types/platformname.go9
-rw-r--r--internal/types/shared.go29
-rw-r--r--internal/types/shared_test.go22
-rw-r--r--internal/vfs/memoryfs.go34
-rw-r--r--internal/vfs/memoryfs_test.go108
-rw-r--r--internal/vfs/realfs.go44
33 files changed, 0 insertions, 2644 deletions
diff --git a/internal/client/tui/compose.go b/internal/client/tui/compose.go
deleted file mode 100644
index 61883f8..0000000
--- a/internal/client/tui/compose.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package tui
-
-import (
- "context"
- "fmt"
- "log"
- "os"
- "os/exec"
- "time"
-
- config "codeberg.org/snonux/gos/internal/config/client"
- tea "github.com/charmbracelet/bubbletea"
-)
-
-type composePostAction int
-
-const (
- noPostAction composePostAction = iota
- queueAfterCompose
- submitAfterCompose
-)
-
-func composeActionCmd(ctx context.Context, conf config.ClientConfig, postAction composePostAction) tea.Cmd {
- err := ensureDirectoryExists(conf.DataDir)
- composeFile := fmt.Sprintf("%s/%s", conf.DataDir, conf.ComposeFile)
- log.Println("Composing", composeFile)
-
- return openEditorCmd(conf.Editor, composeFile, func() error {
- if err != nil {
- return err
- }
-
- switch postAction {
- case submitAfterCompose:
- return submitEntry(ctx, conf, composeFile)
- case queueAfterCompose:
- timestamp := time.Now().Format("20060102-150405")
- queuedFile := fmt.Sprintf("%s/queued-%s.txt", conf.DataDir, timestamp)
- return os.Rename(composeFile, queuedFile)
- }
-
- return nil
- })
-}
-
-func openEditorCmd(editor, filePath string, cb func() error) tea.Cmd {
- return tea.ExecProcess(exec.Command(editor, filePath), func(err error) tea.Msg {
- return finishedMsg{
- cb: cb,
- err: err,
- }
- })
-}
-
-func ensureDirectoryExists(dir string) error {
- info, err := os.Stat(dir)
- if err != nil && os.IsNotExist(err) {
- return os.MkdirAll(dir, os.ModePerm)
- }
- if info.IsDir() {
- return nil
- }
- return fmt.Errorf("path %s is not a directory", dir)
-}
diff --git a/internal/client/tui/finishedmsg.go b/internal/client/tui/finishedmsg.go
deleted file mode 100644
index d1e9165..0000000
--- a/internal/client/tui/finishedmsg.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package tui
-
-import tea "github.com/charmbracelet/bubbletea"
-
-type finishedMsg struct {
- cb func() error
- err error
-}
-
-func (f finishedMsg) Error() string {
- return f.err.Error()
-}
-
-func finishedCmd(cb func() error, err error) tea.Cmd {
- return func() tea.Msg {
- return finishedMsg{
- cb: cb,
- err: err,
- }
- }
-}
diff --git a/internal/client/tui/submit.go b/internal/client/tui/submit.go
deleted file mode 100644
index cc1ff0d..0000000
--- a/internal/client/tui/submit.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package tui
-
-import (
- "context"
- "errors"
- "fmt"
- "log"
- "os"
- "time"
-
- "codeberg.org/snonux/gos/internal/config/client"
- config "codeberg.org/snonux/gos/internal/config/client"
- "codeberg.org/snonux/gos/internal/easyhttp"
- "codeberg.org/snonux/gos/internal/types"
- tea "github.com/charmbracelet/bubbletea"
-)
-
-func submitActionCmd(ctx context.Context, conf config.ClientConfig) tea.Cmd {
- composeFile := fmt.Sprintf("%s/%s", conf.DataDir, conf.ComposeFile)
- log.Println("Submitting", composeFile)
-
- return submitEntryCmd(ctx, conf, composeFile, func() error {
- // This is the cb to call when the entry was submitted succesfully
- return nil
- })
-}
-
-func submitEntryCmd(ctx context.Context, conf client.ClientConfig, composeFile string, cb func() error) tea.Cmd {
- return finishedCmd(cb, submitEntry(ctx, conf, composeFile))
-}
-
-func submitEntry(ctx context.Context, conf client.ClientConfig, composeFile string) error {
- if len(conf.Servers) == 0 {
- return errors.New("no server configured")
- }
-
- entry, err := types.NewEntryFromTextFile(composeFile)
- if err != nil {
- return err
- }
-
- if err := easyhttp.PostData(ctx, "submit", conf.APIKey, &entry, conf.Servers...); err != nil {
- return err
- }
-
- timestamp := time.Now().Format("20060102-150405")
- submittedFile := fmt.Sprintf("%s/submitted-%s.txt", conf.DataDir, timestamp)
-
- log.Println("Renaming", composeFile, "to", submittedFile)
- return os.Rename(composeFile, submittedFile)
-}
diff --git a/internal/client/tui/tui.go b/internal/client/tui/tui.go
deleted file mode 100644
index c084d9c..0000000
--- a/internal/client/tui/tui.go
+++ /dev/null
@@ -1,136 +0,0 @@
-package tui
-
-import (
- "context"
- "fmt"
- "log"
-
- config "codeberg.org/snonux/gos/internal/config/client"
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
-)
-
-var style = lipgloss.NewStyle().
- Foreground(lipgloss.Color("#FFFFFF")).
- Background(lipgloss.Color("#0000FF")).
- PaddingTop(2).PaddingLeft(4).PaddingRight(4).
- BorderStyle(lipgloss.RoundedBorder()).
- BorderForeground(lipgloss.Color("#FFFFFF")).
- BorderBackground(lipgloss.Color("#0000FF"))
-
-var errroStyle = lipgloss.NewStyle().
- Bold(true).
- Align(lipgloss.Center).
- Foreground(lipgloss.Color("#FFFFFF")).
- Background(lipgloss.Color("#FF0000")).
- PaddingTop(0).PaddingBottom(0).PaddingLeft(2).PaddingRight(2).
- BorderStyle(lipgloss.RoundedBorder()).
- BorderForeground(lipgloss.Color("#FFFFFF")).
- BorderBackground(lipgloss.Color("#FF0000"))
-
-func Run(conf config.ClientConfig) error {
- p := tea.NewProgram(initModel(conf))
- _, err := p.Run()
- return err
-}
-
-type model struct {
- choices []string
- cursor int
- conf config.ClientConfig
- altscreenActive bool
- ctx context.Context
- err error
-}
-
-const (
- cursorCompose = iota
- cursorSubmit
- cursorComposeAndSubmit
-)
-
-func initModel(conf config.ClientConfig) model {
- return model{
- choices: []string{"Compose post", "Submit post", "Compose & submit post"},
- ctx: context.Background(),
- conf: conf,
- }
-}
-
-func (m model) Init() tea.Cmd {
- return nil
-}
-
-func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
- switch msg := msg.(type) {
- case tea.KeyMsg:
- switch msg.String() {
- case "up", "k":
- if m.cursor > 0 {
- m.cursor--
- }
- case "down", "j":
- if m.cursor < len(m.choices)-1 {
- m.cursor++
- }
- case "enter":
- switch m.cursor {
- case cursorCompose:
- return m, composeActionCmd(m.ctx, m.conf, noPostAction)
- case cursorSubmit:
- return m, submitActionCmd(m.ctx, m.conf)
- case cursorComposeAndSubmit:
- return m, composeActionCmd(m.ctx, m.conf, submitAfterCompose)
- }
- case "1":
- return m, composeActionCmd(m.ctx, m.conf, noPostAction)
- case "2":
- return m, submitActionCmd(m.ctx, m.conf)
- case "3":
- return m, composeActionCmd(m.ctx, m.conf, submitAfterCompose)
-
- case "a":
- m.altscreenActive = !m.altscreenActive
- cmd := tea.EnterAltScreen
- if !m.altscreenActive {
- cmd = tea.ExitAltScreen
- }
- return m, cmd
- case "ctrl+c", "q":
- return m, tea.Quit
- }
- case finishedMsg:
- m.err = msg.err
- if m.err != nil {
- return m, nil
- }
- if err := msg.cb(); err != nil {
- m.err = err
- }
- }
-
- return m, nil
-}
-
-func (m model) View() string {
- s := "Please choose your destiny\n\n"
-
- for i, choice := range m.choices {
- cursor := " " // no cursor
- if m.cursor == i {
- cursor = "==>"
- }
-
- s += fmt.Sprintf("%s %d. %s\n", cursor, i+1, choice)
- }
-
- if m.err != nil {
- log.Println(m.err)
- s += "\n"
- s += errroStyle.Render(fmt.Sprintf("\nERROR: %s\n", m.err))
- s += "\n"
- }
-
- s += "\nPress q to quit.\n"
- return style.Render(s)
-}
diff --git a/internal/config/client/client.go b/internal/config/client/client.go
deleted file mode 100644
index f551686..0000000
--- a/internal/config/client/client.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package client
-
-import (
- "fmt"
- "log"
- "os"
-
- "codeberg.org/snonux/gos/internal/config"
-)
-
-type ClientConfig struct {
- Servers []string `json:"Servers,omitempty"`
- APIKey string `json:"APIKey,omitempty"`
- Editor string `json:"Editor,omitempty"`
- DataDir string `json:"StateDir,omitempty"`
- ComposeFile string `json:"ComposeFile,omitempty"`
- LogFile string `json:"LogFile,omitempty"`
-}
-
-func New(configFile string) (ClientConfig, error) {
- conf, err := config.FromFile[ClientConfig](configFile)
- if err != nil {
- if _, ok := err.(*os.PathError); !ok {
- return conf, err
- }
- log.Println("Skipping config file:", err)
- }
-
- conf.Servers = config.StrSlice("GOS_SERVERS", conf.Servers)
- conf.APIKey = config.Str("GOS_API_KEY", conf.APIKey)
- conf.Editor = config.Str("GOS_EDITOR", "EDITOR", conf.Editor, "vi")
-
- defaultDataDir := fmt.Sprintf("%s/.gos/data", os.Getenv("HOME"))
- conf.DataDir = config.Str("GOS_DATA_DIR", conf.DataDir, defaultDataDir)
- conf.ComposeFile = config.Str("GOS_COMPOSE_FILE", conf.ComposeFile, "compose.txt")
-
- defaultLogFile := fmt.Sprintf("%s/.gos/gos.log", os.Getenv("HOME"))
- conf.LogFile = config.Str("GOS_LOG_FILE", conf.LogFile, defaultLogFile)
-
- return conf, nil
-}
diff --git a/internal/config/config.go b/internal/config/config.go
deleted file mode 100644
index 6f697e7..0000000
--- a/internal/config/config.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package config
-
-import (
- "encoding/json"
- "io"
- "os"
- "unicode"
-)
-
-func FromFile[T any](configFile string) (T, error) {
- var conf T
-
- file, err := os.Open(configFile)
- if err != nil {
- return conf, err
- }
- defer file.Close()
-
- bytes, err := io.ReadAll(file)
- if err != nil {
- return conf, err
- }
-
- err = json.Unmarshal(bytes, &conf)
- return conf, err
-}
-
-func isAllUpperCase(s string) bool {
- for _, r := range s {
- if unicode.IsLetter(r) && !unicode.IsUpper(r) {
- return false
- }
- if unicode.IsDigit(r) {
- return false
- }
- }
- return true
-}
diff --git a/internal/config/config_test.go b/internal/config/config_test.go
deleted file mode 100644
index eca163f..0000000
--- a/internal/config/config_test.go
+++ /dev/null
@@ -1,195 +0,0 @@
-package config
-
-import (
- "os"
- "slices"
- "testing"
-)
-
-func TestEnvToStr(t *testing.T) {
- t.Parallel()
-
- os.Unsetenv("NON_EXISTENT_ENV")
- os.Setenv("GOS_TEST_FROM_ENV", "foobarbaz")
-
- var (
- expected = "foobarbaz"
- got = Str("GOS_TEST_FROM_ENV")
- )
-
- if got != expected {
- t.Errorf("got '%s' but expected '%s'", got, expected)
- }
-
- expected = "default value"
- got = Str("NON_EXISTENT_ENV", expected)
- if got != expected {
- t.Errorf("got '%s' but expected '%s'", got, expected)
- }
-
- if got = Str("NON_EXISTENT_ENV"); got != "" {
- t.Errorf("got '%s' but expected empty string", got)
- }
-
- expected = "casio g-shock"
- os.Setenv("GOS_WATCH", expected)
- got = Str("GOS_WATCH", "", "", "", expected, "")
- if got != expected {
- t.Errorf("got '%s' but expected '%s'", got, expected)
- }
-}
-
-func TestEnvToStrSlice(t *testing.T) {
- t.Parallel()
-
- os.Setenv("GOS_TEST_SLICE_FROM_ENV", "foo,bar,baz")
-
- var (
- expected = []string{"foo", "bar", "baz"}
- got = StrSlice("GOS_TEST_SLICE_FROM_ENV")
- )
- if !slices.Equal(got, expected) {
- t.Errorf("got '%v' but expected '%v'", got, expected)
- }
-
- expected = []string{"default value"}
- got = StrSlice("NON_EXISTENT_ENV_SLICE", "default value")
- if !slices.Equal(got, expected) {
- t.Errorf("got '%v' but expected '%v'", got, expected)
- }
-
- os.Unsetenv("NON_EXISTENT_ENV")
- if got = StrSlice("NON_EXISTENT_ENV"); len(got) > 0 {
- t.Errorf("got '%s' of len '%d' but expected empty slice", got, len(got))
- }
-
- expected = []string{"casio", "g-shock"}
- got = StrSlice("NON_EXISTENT_ENV", "", "", "", "casio,g-shock", "")
- if !slices.Equal(got, expected) {
- t.Errorf("got '%v' but expected '%v'", got, expected)
- }
-}
-
-func TestEnvToInt(t *testing.T) {
- t.Parallel()
-
- os.Unsetenv("NON_EXISTENT_ENV")
- os.Setenv("GOS_TEST_INT_FROM_ENV", "1")
-
- var (
- expected = 1
- got = Int(t, "GOS_TEST_INT_FROM_ENV")
- )
-
- if got != expected {
- t.Errorf("got '%d' but expected '%d'", got, expected)
- }
-
- expected = 999
- got = Int("NON_EXISTENT_ENV", expected)
- if got != expected {
- t.Errorf("got '%d' but expected '%d'", got, expected)
- }
-
- if got = Int("NON_EXISTENT_ENV"); got != 0 {
- t.Errorf("got '%d' but expected zero", got)
- }
-
- expected = 1234
- got = Int("GOS_WATCH", "", "", "", expected, "")
- if got != expected {
- t.Errorf("got '%d' but expected '%d'", got, expected)
- }
-}
-
-func TestEnvToBool(t *testing.T) {
- t.Parallel()
-
- os.Unsetenv("NON_EXISTENT_ENV")
- os.Setenv("GOS_TEST_BOOL_FROM_ENV", "true")
-
- var (
- expected = true
- got = Bool("GOS_TEST_BOOL_FROM_ENV")
- )
-
- if got != expected {
- t.Errorf("got '%t' but expected '%t'", got, expected)
- }
-
- expected = false
- got = Bool("NON_EXISTENT_ENV", expected)
- if got != expected {
- t.Errorf("got '%t' but expected '%t'", got, expected)
- }
-
- if got = Bool("NON_EXISTENT_ENV"); got {
- t.Errorf("got '%t' but expected false", got)
- }
-
- expected = true
- got = Bool("NON_EXISTENT_ENV", "", "", "", expected, "")
- if got != expected {
- t.Errorf("got '%t' but expected '%t'", got, expected)
- }
-}
-
-func TestSecondENV(t *testing.T) {
- t.Parallel()
-
- os.Unsetenv("GOS_NONEXISTANT")
- os.Setenv("EDITOR", "hx")
-
- var (
- expected = "hx"
- got = Str("GOS_NONEXISTANT", "EDITOR", "notepad.exe")
- )
-
- if expected != got {
- t.Errorf("got '%s' but expected '%s'", got, expected)
- }
-}
-
-func TestIsAllUpperCase(t *testing.T) {
- if isAllUpperCase("foo_bar") {
- t.Errorf("lowercas letters in test case")
- }
- if isAllUpperCase("FOO123") {
- t.Errorf("numbers in string should not evaluate to is all upper")
- }
- if !isAllUpperCase("FOO_BAR") {
- t.Errorf("should be all upper")
- }
-}
-
-func TestDefaultStrCB(t *testing.T) {
- t.Parallel()
- os.Unsetenv("GOS_NONEXISTANT")
-
- var (
- expected = "hello"
- got = Str("GOS_NONEXISTANT", func() string {
- return "hello"
- })
- )
-
- if expected != got {
- t.Errorf("got '%s' but expected '%s'", got, expected)
- }
-}
-
-func TestDefaultIntCB(t *testing.T) {
- t.Parallel()
- os.Unsetenv("GOS_NONEXISTANT")
-
- var (
- expected = 666
- got = Int("GOS_NONEXISTANT", func() int {
- return 666
- })
- )
-
- if expected != got {
- t.Errorf("got '%d' but expected '%d'", got, expected)
- }
-}
diff --git a/internal/config/enver.go b/internal/config/enver.go
deleted file mode 100644
index f3f8d29..0000000
--- a/internal/config/enver.go
+++ /dev/null
@@ -1,104 +0,0 @@
-package config
-
-import (
- "os"
- "strconv"
- "strings"
-)
-
-type configTypes interface {
- ~int | ~bool | ~string | []string
-}
-
-type enver[T configTypes] interface {
- fromStr(value string) (T, error) // Return T value from input string
- zero() T // Return T's zero value
-}
-
-func Str(keys ...any) string {
- return fromEnv[toStr](keys...)
-}
-
-func StrSlice(keys ...any) []string {
- return fromEnv[toStrSlice](keys...)
-}
-
-func Int(keys ...any) int {
- return fromEnv[toInt](keys...)
-}
-
-func Bool(keys ...any) bool {
- return fromEnv[toBool](keys...)
-}
-
-func fromEnv[U enver[T], T configTypes](keys ...any) T {
- var enver U
-
- for _, key := range keys {
- switch key := key.(type) {
- case string:
- if key == "" {
- continue
- }
- if !isAllUpperCase(key) {
- if val, err := enver.fromStr(key); err == nil {
- return val
- }
- } else if strVal := os.Getenv(key); strVal != "" {
- if val, err := enver.fromStr(strVal); err == nil {
- return val
- }
- }
- case T:
- return key
- case func() T:
- return key()
- }
- }
-
- return enver.zero()
-}
-
-type toStr struct{}
-
-func (toStr) fromStr(str string) (string, error) {
- return str, nil
-}
-
-func (toStr) zero() string {
- return ""
-}
-
-type toStrSlice struct{}
-
-func (s toStrSlice) fromStr(str string) ([]string, error) {
- result := strings.Split(str, ",")
- if len(result) == 1 && result[0] == "" {
- return s.zero(), nil
- }
- return result, nil
-}
-
-func (toStrSlice) zero() []string {
- return []string{}
-}
-
-type toInt struct{}
-
-func (toInt) fromStr(str string) (int, error) {
- return strconv.Atoi(str)
-}
-
-func (toInt) zero() int {
- return 0
-}
-
-type toBool struct{}
-
-func (toBool) fromStr(str string) (bool, error) {
- return strconv.ParseBool(str)
-}
-
-func (toBool) zero() bool {
- return false
-}
diff --git a/internal/config/server/mastodon.go b/internal/config/server/mastodon.go
deleted file mode 100644
index abb4e43..0000000
--- a/internal/config/server/mastodon.go
+++ /dev/null
@@ -1 +0,0 @@
-package server
diff --git a/internal/config/server/secrets.go b/internal/config/server/secrets.go
deleted file mode 100644
index 0e8aab2..0000000
--- a/internal/config/server/secrets.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package server
-
-import (
- "fmt"
- "log"
- "os"
-
- "codeberg.org/snonux/gos/internal/config"
-)
-
-type SecretsConfig struct {
- MastodonEnable bool `json:"MastodonEnable,omitempty"`
- MastodonDomain string `json:"MastodonDomain,omitempty"`
- MastodonAccessToken string `json:"MastodonAccessToken,omitempty"`
-}
-
-func newSecretsConfig(secretsFile string) (SecretsConfig, error) {
- if isWorldReadable(secretsFile) {
- return SecretsConfig{}, fmt.Errorf("config '%s' is world readable", secretsFile)
- }
-
- conf, err := config.FromFile[SecretsConfig](secretsFile)
- if err != nil {
- if _, ok := err.(*os.PathError); !ok {
- return conf, err
- }
- log.Println("Skipping config file:", err)
- }
-
- conf.MastodonEnable = config.Bool("GOS_MASTODON_ENABLE", conf.MastodonEnable)
- conf.MastodonDomain = config.Str("GOS_MASTODON_DOMAIN", conf.MastodonDomain)
- conf.MastodonAccessToken = config.Str("GOS_MASTODON_ACCESS_TOKEN", conf.MastodonAccessToken)
-
- return conf, nil
-}
-
-func isWorldReadable(file string) bool {
- fileInfo, err := os.Stat(file)
- if err != nil {
- return false
- }
-
- return fileInfo.Mode().Perm()&00004 != 0
-}
diff --git a/internal/config/server/server.go b/internal/config/server/server.go
deleted file mode 100644
index 16da14c..0000000
--- a/internal/config/server/server.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package server
-
-import (
- "fmt"
- "log"
- "os"
-
- "codeberg.org/snonux/gos/internal/config"
- "codeberg.org/snonux/gos/internal/types"
-)
-
-type ServerConfig struct {
- ListenAddr string `json:"ListenAddr,omitempty"`
- Partners []string `json:"Partners,omitempty"`
- APIKey string `json:"APIKey,omitempty"`
- DataDir string `json:"StateDir,omitempty"`
- EmailTo string `json:"EmailTo,omitempty"`
- EmailFrom string `json:"EmailFrom,omitempty"`
- SMTPServer string `json:"SMTPServer,omitempty"`
- MergeIntervalS int `json:"MergeInterval,omitempty"`
- ScheduleIntervalS int `json:"ScheduleInterval,omitempty"`
- SocialPlatformsEnabled []string `json:"SocialPlatformsEnabled,omitempty"`
- PostsPerWeek int `json:"PostsPerWeek,omitempty"`
- Secrets SecretsConfig `json:"Secrets,omitempty"`
-}
-
-func New(configFile, secretsFile string) (ServerConfig, error) {
- conf, err := config.FromFile[ServerConfig](configFile)
- if err != nil {
- if _, ok := err.(*os.PathError); !ok {
- return conf, err
- }
- log.Println("Skipping config file:", err)
- }
-
- if conf.Secrets, err = newSecretsConfig(secretsFile); err != nil {
- return conf, err
- }
-
- conf.ListenAddr = config.Str("GOS_LISTEN_ADDR", conf.ListenAddr, "localhost:8080")
- conf.Partners = config.StrSlice("GOS_PARTNERS", conf.Partners)
- conf.APIKey = config.Str("GOS_API_KEY", conf.APIKey)
- conf.DataDir = config.Str("GOS_DATA_DIR", conf.DataDir, "data")
- conf.EmailTo = config.Str("GOS_EMAIL_TO", conf.EmailTo)
- conf.EmailFrom = config.Str("GOS_EMAIL_FROM", conf.EmailFrom)
- conf.SocialPlatformsEnabled = config.StrSlice("GOS_SOCIAL_PLATFORMS_ENABLED",
- []string{types.Mastodon, types.LinkedIn, types.Textfile})
- conf.PostsPerWeek = config.Int("GOS_POSTS_PER_WEEK", conf.PostsPerWeek, 2)
- conf.SMTPServer = config.Str("GOS_SMTP_SERVER", conf.SMTPServer, func() string {
- hostname, err := os.Hostname()
- if err != nil {
- log.Fatal(err)
- }
- return fmt.Sprintf("%s:25", hostname)
- })
-
- const oneHour = 3600
- conf.MergeIntervalS = config.Int("GOS_MERGE_INTERVAL", oneHour)
- conf.ScheduleIntervalS = config.Int("GOS_SCHEDULER_INTERVAL", oneHour*6)
-
- return conf, nil
-}
diff --git a/internal/easyhttp/easyhttp.go b/internal/easyhttp/easyhttp.go
deleted file mode 100644
index 2968454..0000000
--- a/internal/easyhttp/easyhttp.go
+++ /dev/null
@@ -1,110 +0,0 @@
-package easyhttp
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io"
- "log"
- "net/http"
- "sync"
-)
-
-func Get(ctx context.Context, uri, apiKey string) ([]byte, error) {
- var (
- client = &http.Client{}
- bytes []byte
- )
-
- req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
- if err != nil {
- return bytes, fmt.Errorf("%s: %w", uri, err)
- }
-
- req.Header.Set("X-API-KEY", apiKey)
-
- resp, err := client.Do(req)
- if err != nil {
- return bytes, fmt.Errorf("%s: %w", uri, err)
- }
- defer resp.Body.Close()
-
- bytes, err = io.ReadAll(resp.Body)
- if err != nil {
- return bytes, fmt.Errorf("%s: %w", uri, err)
- }
-
- return bytes, nil
-}
-
-// Get data from JSON
-func GetData[T any](ctx context.Context, uri, apiKey string, data *T) error {
- bytes, err := Get(ctx, uri, apiKey)
- if err != nil {
- return err
- }
-
- return json.Unmarshal(bytes, data)
-}
-
-func Post(ctx context.Context, uri, apiKey string, data []byte) ([]byte, error) {
- req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, bytes.NewBuffer(data))
- if err != nil {
- return []byte{}, fmt.Errorf("%s: %w", uri, err)
- }
-
- req.Header.Set("Content-Type", "application/json")
- req.Header.Set("X-API-KEY", apiKey)
-
- client := &http.Client{}
- resp, err := client.Do(req)
- if err != nil {
- return []byte{}, fmt.Errorf("%s: %w", uri, err)
- }
- defer resp.Body.Close()
-
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return []byte{}, fmt.Errorf("%s: %w", uri, err)
- }
-
- switch resp.StatusCode {
- case 200:
- return body, nil
- case 401:
- return body, fmt.Errorf("unauthorized, API key configured?")
- default:
- return body, fmt.Errorf("unexpected HTTP response code %d", resp.StatusCode)
- }
-}
-
-// Submit structure as JSON to API
-func PostData[T any](ctx context.Context, uri, apiKey string, data *T, servers ...string) error {
- if len(servers) == 0 {
- return fmt.Errorf("no server configured")
- }
- var errs safErrors
- var wg sync.WaitGroup
-
- for _, server := range servers {
- log.Println("Submitting data to", server)
- wg.Add(1)
- go func(server string) {
- defer wg.Done()
- errs.Append(postData[T](ctx, fmt.Sprintf("%s/%s", server, uri), apiKey, data))
- }(server)
- }
-
- wg.Wait()
- return errs.Join()
-}
-
-func postData[T any](ctx context.Context, uri, apiKey string, data *T) error {
- jsonData, err := json.Marshal(data)
- if err != nil {
- return err
- }
- _, err = Post(ctx, uri, apiKey, jsonData)
- return err
-}
diff --git a/internal/easyhttp/saferrors.go b/internal/easyhttp/saferrors.go
deleted file mode 100644
index 464c90d..0000000
--- a/internal/easyhttp/saferrors.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package easyhttp
-
-import (
- "errors"
- "sync"
-)
-
-// Safe errors
-type safErrors struct {
- errs []error
- mutex sync.Mutex
-}
-
-func (errs *safErrors) Append(err error) {
- if err == nil {
- return
- }
- errs.mutex.Lock()
- defer errs.mutex.Unlock()
- errs.errs = append(errs.errs, err)
-}
-
-func (errs *safErrors) Join() error {
- errs.mutex.Lock()
- defer errs.mutex.Unlock()
- return errors.Join(errs.errs...)
-}
-
-func (errs *safErrors) Error() string {
- return errs.Join().Error()
-}
diff --git a/internal/server/cron/cron.go b/internal/server/cron/cron.go