d45111645c
- REST API for calculating age breakdowns (years, months, weeks, days, hours, minutes, seconds)
- Birthtime configured as Unix timestamps
- Sleeps until next birthday countdown
- Per-person lookup via GET /age/{name}
- Docker and Makefile build support
- Woodpecker CI pipelines
157 lines
3.8 KiB
Go
157 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type Config struct {
|
|
People []Person `yaml:"people"`
|
|
}
|
|
|
|
type Person struct {
|
|
Name string `yaml:"name"`
|
|
Birthtime int64 `yaml:"birthtime"`
|
|
}
|
|
|
|
type AgeBreakdown struct {
|
|
Years int `json:"years"`
|
|
Months int `json:"months"`
|
|
Weeks int `json:"weeks"`
|
|
Days int `json:"days"`
|
|
Hours int64 `json:"hours"`
|
|
Minutes int64 `json:"minutes"`
|
|
Seconds int64 `json:"seconds"`
|
|
}
|
|
|
|
type PersonAge struct {
|
|
Name string `json:"name"`
|
|
DOB string `json:"dob"`
|
|
Birthtime int64 `json:"birthtime"`
|
|
SleepsUntilBirthday int `json:"sleeps_until_birthday"`
|
|
Age AgeBreakdown `json:"age"`
|
|
}
|
|
|
|
func sleepsUntilBirthday(dob time.Time, now time.Time) int {
|
|
next := time.Date(now.Year(), dob.Month(), dob.Day(), 0, 0, 0, 0, now.Location())
|
|
if !next.After(now) {
|
|
next = time.Date(now.Year()+1, dob.Month(), dob.Day(), 0, 0, 0, 0, now.Location())
|
|
}
|
|
return int(next.Sub(now).Hours()/24) + 1
|
|
}
|
|
|
|
func calculateAge(dob time.Time, now time.Time) AgeBreakdown {
|
|
// Calendar-based years and months
|
|
years := now.Year() - dob.Year()
|
|
months := int(now.Month()) - int(dob.Month())
|
|
if now.Day() < dob.Day() {
|
|
months--
|
|
}
|
|
if months < 0 {
|
|
years--
|
|
months += 12
|
|
}
|
|
|
|
totalMonths := years*12 + months
|
|
|
|
// Total duration for weeks/days/hours/minutes/seconds
|
|
dur := now.Sub(dob)
|
|
totalDays := int(dur.Hours() / 24)
|
|
totalWeeks := totalDays / 7
|
|
|
|
return AgeBreakdown{
|
|
Years: years,
|
|
Months: totalMonths,
|
|
Weeks: totalWeeks,
|
|
Days: totalDays,
|
|
Hours: int64(dur.Hours()),
|
|
Minutes: int64(dur.Minutes()),
|
|
Seconds: int64(dur.Seconds()),
|
|
}
|
|
}
|
|
|
|
func loadConfig(path string) (*Config, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading config: %w", err)
|
|
}
|
|
var cfg Config
|
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
|
return nil, fmt.Errorf("parsing config: %w", err)
|
|
}
|
|
return &cfg, nil
|
|
}
|
|
|
|
func main() {
|
|
configPath := "config.yaml"
|
|
if v := os.Getenv("CONFIG_PATH"); v != "" {
|
|
configPath = v
|
|
}
|
|
|
|
cfg, err := loadConfig(configPath)
|
|
if err != nil {
|
|
log.Fatalf("Failed to load config: %v", err)
|
|
}
|
|
|
|
// Index people by lowercase name
|
|
people := make(map[string]Person)
|
|
for _, p := range cfg.People {
|
|
people[strings.ToLower(p.Name)] = p
|
|
}
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
// GET /age - all people
|
|
mux.HandleFunc("GET /age", func(w http.ResponseWriter, r *http.Request) {
|
|
now := time.Now()
|
|
var results []PersonAge
|
|
for _, p := range cfg.People {
|
|
dob := time.Unix(p.Birthtime, 0)
|
|
results = append(results, PersonAge{
|
|
Name: p.Name,
|
|
DOB: dob.Format("2006-01-02"),
|
|
Birthtime: p.Birthtime,
|
|
SleepsUntilBirthday: sleepsUntilBirthday(dob, now),
|
|
Age: calculateAge(dob, now),
|
|
})
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(results)
|
|
})
|
|
|
|
// GET /age/{name} - single person
|
|
mux.HandleFunc("GET /age/{name}", func(w http.ResponseWriter, r *http.Request) {
|
|
name := strings.ToLower(r.PathValue("name"))
|
|
p, ok := people[name]
|
|
if !ok {
|
|
http.Error(w, fmt.Sprintf("person %q not found", name), http.StatusNotFound)
|
|
return
|
|
}
|
|
now := time.Now()
|
|
dob := time.Unix(p.Birthtime, 0)
|
|
result := PersonAge{
|
|
Name: p.Name,
|
|
DOB: dob.Format("2006-01-02"),
|
|
Birthtime: p.Birthtime,
|
|
SleepsUntilBirthday: sleepsUntilBirthday(dob, now),
|
|
Age: calculateAge(dob, now),
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(result)
|
|
})
|
|
|
|
addr := ":8080"
|
|
if v := os.Getenv("LISTEN_ADDR"); v != "" {
|
|
addr = v
|
|
}
|
|
log.Printf("Listening on %s", addr)
|
|
log.Fatal(http.ListenAndServe(addr, mux))
|
|
}
|