Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
35be1e5
fix: refactor the checks to read and test easier
jrschumacher Dec 20, 2024
9e97412
Add tests
jrschumacher Dec 20, 2024
b2a583f
feat: refactor checks add tests
jrschumacher Dec 27, 2024
0e9942f
Add further tests
jrschumacher Dec 30, 2024
7977338
camel case
jrschumacher Dec 31, 2024
32aa020
resolve lint issue
jrschumacher Dec 31, 2024
3d72dc4
heartbeat must be greater than 0
jrschumacher Dec 31, 2024
a93490c
Expose validate and add tests
jrschumacher Dec 31, 2024
0a500ca
Move icsPrefix constant to main_test.go since its only used in tests …
jrschumacher Dec 31, 2024
213d047
Remove backwards compatibility for `-debug` argument
jrschumacher Dec 31, 2024
a38f145
add push on master and fix pull_request on master
jrschumacher Dec 31, 2024
89a9095
fix additional lint issues
jrschumacher Dec 31, 2024
cbab428
fix check condition and add better tests
jrschumacher Dec 31, 2024
5c7b214
fix structured log
jrschumacher Dec 31, 2024
37e4b98
fix lint issue
jrschumacher Dec 31, 2024
240e72d
add notification service as interface
jrschumacher Dec 31, 2024
5cb5126
Remove deprecated WebHook config
jrschumacher Jan 2, 2025
e24851e
unify log pattern
jrschumacher Jan 4, 2025
0329e62
Merge branch 'master' of github.com:Fesaa/iCalMerger into refactor-wi…
jrschumacher Jan 4, 2025
9f79064
Skip failing tests since I need to better understand the underlying f…
jrschumacher Jan 4, 2025
a7b3dc6
Update server/main.go
jrschumacher Jan 5, 2025
c358b33
conditional message
jrschumacher Jan 5, 2025
b30e13f
add healthcheck endpoint and command
jrschumacher Jan 5, 2025
809c79e
add healthcheck docs
jrschumacher Jan 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/validate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Validate

on:
push:
branches:
- master
pull_request:
branches:
- master

jobs:
job:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.21

- name: 🧹 Lint
uses: golangci/golangci-lint-action@v6

- name: 🧪 Test
run: go test -v ./...
16 changes: 15 additions & 1 deletion ReadME.MD
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ A docker container is provided at `ameliaah/ical-merger:latest`, mount the confi
Example:

```yaml
webhook: <discord url>
adress: "127.0.0.1"
port: "4040"
notifcation:
service: discord
url: <URL>
sources:
- end_point: filtered_calender
heartbeat: 60
Expand Down Expand Up @@ -71,3 +73,15 @@ sources:
These will now be accessible on `http://127.0.0.1:4040/filtered_calender.ics` and `http://127.0.0.1:4040/full_calender.ics`

Quickly made to make my school calender work because my school sucks 😝

## Production

### Health Check

The health check endpoint is available at `/health` and will return a 200 status code if the server is running.

Additionally, a command exists to check the health of the server:

```bash
./ical-merger -health
```
5 changes: 3 additions & 2 deletions config.example.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
# Discord URL
webhook:
# Hostname to bind the server to (default none)
hostname:
# Port to bind the server to (default 4040)
port: "4040"
notification:
service: discord
url: <URL>
sources:
- end_point: filtered_calender
heartbeat: 60
Expand Down
156 changes: 129 additions & 27 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package config

import (
"fmt"
"net/url"
"os"
"slices"
"strings"

"gopkg.in/yaml.v2"
"gopkg.in/yaml.v3"
)

type Rule struct {
Expand All @@ -23,13 +26,6 @@ func (r *Rule) Transform(s string) string {
return strings.ToLower(s)
}

type SourceInfo struct {
Name string `yaml:"name"`
Url string `yaml:"url"`
Rules []Rule `yaml:"rules,omitempty"`
Modifiers []Modifier `yaml:"modifiers,omitempty"`
}

type Action string

const (
Expand All @@ -39,6 +35,12 @@ const (
ALARM Action = "ALARM"
)

type NotificationService string

const (
NotifyDiscord NotificationService = "DISCORD"
)

type Modifier struct {
Name string `yaml:"name"`
Component string `yaml:"component,omitempty"`
Expand All @@ -47,40 +49,140 @@ type Modifier struct {
Filters []Rule `yaml:"rules,omitempty"`
}

type Source struct {
EndPoint string `yaml:"end_point"`
Heartbeat int `yaml:"heartbeat"`
XWRName string `yaml:"xwr_name"`
Info []SourceInfo `yaml:"info"`
}

type Config struct {
WebHook string `yaml:"webhook"`
Hostname string `yaml:"hostname"`
Port string `yaml:"port"`
Sources []Source `yaml:"sources"`
Hostname string `yaml:"hostname"`
Port string `yaml:"port"`

Notification Notification `yaml:"notification"`
Sources []Source `yaml:"sources"`
}

var defaultConfig = Config{
Port: "4040",
}

func LoadConfig(file_path string) (*Config, error) {
content, e := os.ReadFile(file_path)
if e != nil {
return nil, e
}
func LoadConfig(filePath string) (*Config, error) {
config := &Config{}

var config Config
if filePath == "" {
filePath = "./config.yaml"
}

e = yaml.Unmarshal(content, &config)
content, e := os.ReadFile(filePath)
if e != nil {
return nil, e
}

if err := yaml.Unmarshal(content, &config); err != nil {
return nil, err
}

if config.Port == "" {
config.Port = defaultConfig.Port
}

return &config, nil
if err := config.Validate(); err != nil {
return nil, err
}

return config, nil
}

func (c *Config) Validate() error {
var endpoints []string

// Validate notification if set - not required
if c.Notification != (Notification{}) {
if err := c.Notification.Validate(); err != nil {
return fmt.Errorf(".Notification: %s", err)
}
}

for i, source := range c.Sources {
// Ensure that the endpoint is unique
if slices.Contains(endpoints, source.EndPoint) {
return fmt.Errorf(".Source.%d: EndPoint is not unique", i)
}
endpoints = append(endpoints, source.EndPoint)

if err := source.Validate(); err != nil {
return fmt.Errorf(".Source.%d: %s", i, err)
}
}

return nil
}

type Notification struct {
Url string `yaml:"url"`
Service string `yaml:"service"`
}

func (n *Notification) Validate() error {
if n.Url == "" {
return fmt.Errorf("url is missing")
}

if n.Service == "" {
return fmt.Errorf("service is missing")
}

n.Service = strings.ToUpper(n.Service)
switch NotificationService(n.Service) {
case NotifyDiscord:
break
default:
return fmt.Errorf("service is invalid")
}

return nil
}

type Source struct {
EndPoint string `yaml:"end_point"`
Heartbeat int `yaml:"heartbeat"`
Name string `yaml:"xwr_name"`
Info []SourceInfo `yaml:"info"`
}

func (c *Source) Validate() error {
if c.Heartbeat <= 0 {
return fmt.Errorf("heartbeat must be greater than 0")
}

for i, info := range c.Info {
if err := info.Validate(); err != nil {
return fmt.Errorf(".Info.%d: %s", i, err)
}
}

return nil
}

type SourceInfo struct {
Name string `yaml:"name"`
Url string `yaml:"url"`
Rules []Rule `yaml:"rules,omitempty"`
Modifiers []Modifier `yaml:"modifiers,omitempty"`
}

func (c *SourceInfo) Validate() error {
if c.Name == "" {
return fmt.Errorf("name is missing")
}

if c.Url == "" {
return fmt.Errorf("URL is missing")
}

u, err := url.Parse(c.Url)
if err != nil {
return fmt.Errorf("URL is invalid")
}

if u.Hostname() == "" {
return fmt.Errorf("URL is invalid (hostname)")
}

return nil
}
Loading
Loading