Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
paragor committed Aug 7, 2024
0 parents commit 8f2b54f
Show file tree
Hide file tree
Showing 66 changed files with 5,047 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
todolist
2 changes: 2 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build:
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o todolist main.go
35 changes: 35 additions & 0 deletions cmd/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package cmd

import (
"encoding/json"
"fmt"
"github.com/paragor/todo/pkg/db"
"github.com/spf13/cobra"
"log"
"net/http"
)

func init() {
rootCmd.AddCommand(clientCmd)
}

var clientCmd = &cobra.Command{
Use: "client",
Short: "Run todolist console client",
Run: func(cmd *cobra.Command, args []string) {
repo := db.NewRemoteRepository(cfg.Client.RemoteAddr, cfg.Client.ServerToken, http.DefaultClient)
if err := repo.Ping(); err != nil {
log.Fatalf("cant connect to server: %s", err.Error())
}
tasks, err := repo.All()
if err != nil {
log.Fatalf("cant get tasks: %s", err.Error())
}
data, err := json.MarshalIndent(tasks, "", " ")
if err != nil {
log.Fatalf("cant marshal tasks: %s", err.Error())

}
fmt.Print(string(data))
},
}
100 changes: 100 additions & 0 deletions cmd/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package cmd

import (
"github.com/spf13/cobra"
"github.com/zitadel/oidc/v3/pkg/oidc"
"gopkg.in/yaml.v3"
"log"
"os"
"path"
"time"
)

var cfg = newDefaultConfig()
var cfgFile = or(os.Getenv("TODOLIST_CONFIG_PATH"), path.Join(homeDir, "config.yaml"))

func init() {
rootCmd.AddCommand(configPersistCmd)
}

var configPersistCmd = &cobra.Command{
Use: "config-persist",
Short: "Persist config with defaults to filesystem",
Run: func(cmd *cobra.Command, args []string) {
data, err := yaml.Marshal(cfg)
if err != nil {
log.Fatalf("cant marshal config: %s", err.Error())
}
if err := os.WriteFile(cfgFile, data, 0600); err != nil {
log.Fatalf("cant write config: %s", err.Error())
}
log.Println("config written at", cfgFile)
},
}

type Config struct {
Server struct {
DiagnosticEndpointsEnabled bool `yaml:"diagnostic_endpoints_enabled"`
DatabaseFile string `yaml:"database_file"`
ListenAddr string `yaml:"listen_addr"`
PublicUrl string `yaml:"public_url"`
AuthEnabled bool `yaml:"auth_enabled"`
TokenAuth struct {
Enabled bool `yaml:"enabled"`
ClientToken string `yaml:"client_token"`
} `yaml:"token_auth"`
BaseAuth struct {
Enabled bool `yaml:"enabled"`
Login string `yaml:"login"`
Password string `yaml:"password"`
} `yaml:"base_auth"`
OidcAuth struct {
Enabled bool `yaml:"enabled"`
ClientId string `yaml:"client_id"`
ClientSecret string `yaml:"client_secret"`
IssuerUrl string `yaml:"issuer_url"`
Scopes []string `yaml:"scopes"`
CookieKey string `yaml:"cookie_key"`
WhitelistEmails []string `yaml:"whitelist_emails"`
} `yaml:"oidc_auth"`
Telegram struct {
Enabled bool `yaml:"enabled"`
Token string `yaml:"token"`
UserId int64 `yaml:"userId"`
EverydayAgenda struct {
Enabled bool `yaml:"enabled"`
At time.Time `yaml:"at"`
} `yaml:"everyday_agenda"`
} `yaml:"telegram"`
}
Client struct {
RemoteAddr string `yaml:"remote_addr"`
ServerToken string `yaml:"server_token"`
}
}

func newDefaultConfig() *Config {
c := &Config{}
c.Server.ListenAddr = ":8080"
c.Server.DatabaseFile = path.Join(homeDir, "database.json")
c.Server.DiagnosticEndpointsEnabled = true

c.Server.TokenAuth.ClientToken = "api_password"

c.Server.OidcAuth.Scopes = []string{oidc.ScopeOpenID, oidc.ScopeEmail, oidc.ScopeProfile}
c.Server.OidcAuth.IssuerUrl = "https://accounts.google.com"
c.Server.OidcAuth.CookieKey = "kiel4teof4Eoziheigiesh7ooquiepho" //pwgen 32

c.Client.RemoteAddr = "http://127.0.0.1:8080"
c.Client.ServerToken = "api_password"

return c
}

func or[T any](x, y T) T {
var zero T
if any(x) != any(zero) {
return x
}
return y
}
49 changes: 49 additions & 0 deletions cmd/import-taskwarrior.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package cmd

import (
"github.com/paragor/todo/pkg/db"
"github.com/paragor/todo/pkg/taskwarrior"
"github.com/spf13/cobra"
"log"
"net/http"
)

var taskwarriorImportConfig = &taskwarrior.ImportConfig{
Filepath: "",
SkipDeleted: true,
SkipCompleted: true,
SkipRecur: true,
}

func init() {
rootCmd.AddCommand(importTaskWarriorCmd)
importTaskWarriorCmd.Flags().StringVar(&taskwarriorImportConfig.Filepath, "tasks-export-file", taskwarriorImportConfig.Filepath, "Path to file with stdout of eval 'task export'. If not set - 'task export' will evaluated")
importTaskWarriorCmd.Flags().BoolVar(&taskwarriorImportConfig.SkipCompleted, "skip-completed", taskwarriorImportConfig.SkipCompleted, "skip completed tasks")
importTaskWarriorCmd.Flags().BoolVar(&taskwarriorImportConfig.SkipDeleted, "skip-deleted", taskwarriorImportConfig.SkipDeleted, "skip deleted tasks")
importTaskWarriorCmd.Flags().BoolVar(&taskwarriorImportConfig.SkipRecur, "skip-recur", taskwarriorImportConfig.SkipRecur, "skip recur tasks")
}

var importTaskWarriorCmd = &cobra.Command{
Use: "import-taskwarrior",
Short: "import tasks from taskwarrior",
RunE: func(cmd *cobra.Command, args []string) error {
repo := db.NewRemoteRepository(cfg.Client.RemoteAddr, cfg.Client.ServerToken, http.DefaultClient)
if err := repo.Ping(); err != nil {
log.Fatalf("error on connect to remote server: %s", err)
}

tasks, err := taskwarrior.Import(taskwarriorImportConfig)
if err != nil {
log.Fatalf("error on export tasks: %s", err)
}

for _, t := range tasks {
if err := repo.Insert(t); err != nil {
log.Fatalf("cant import task: (%s) %s", t.UUID.String(), err)
}
}

log.Printf("imported %d tasks\n", len(tasks))
return nil
},
}
52 changes: 52 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package cmd

import (
"fmt"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
"io"
"os"
"path"
"strings"
)

var homeDir = path.Join(os.Getenv("HOME"), ".config/todolist")

func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", cfgFile, "config file (env TODOLIST_CONFIG_PATH)")
}

var rootCmd = &cobra.Command{
Use: "todolist",
Short: "Todolist is todo list :) https://github.com/paragor/todolist",
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

func initConfig() {
if strings.Contains(cfgFile, homeDir) {
if err := os.MkdirAll(homeDir, 0755); err != nil {
panic(fmt.Errorf("cant init home dir: %w", err).Error())
}
}
f, err := os.Open(cfgFile)
if err != nil && os.IsNotExist(err) {
return
} else if err != nil {
panic(fmt.Errorf("cant open config file: %w", err).Error())
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
panic(fmt.Errorf("cant read config file: %w", err).Error())
}
if err := yaml.Unmarshal(data, cfg); err != nil {
panic(fmt.Errorf("cant unmarshal config file: %w", err).Error())
}
}
131 changes: 131 additions & 0 deletions cmd/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package cmd

import (
"fmt"
"github.com/paragor/todo/pkg/cron"
"github.com/paragor/todo/pkg/db"
"github.com/paragor/todo/pkg/httpserver"
"github.com/paragor/todo/pkg/service"
"github.com/paragor/todo/pkg/telegram"
"github.com/spf13/cobra"
"log"
)

func init() {
rootCmd.AddCommand(serverCmd)
}

var serverCmd = &cobra.Command{
Use: "server",
Short: "Run todolist server",
Run: func(cmd *cobra.Command, args []string) {
repo := db.NewInMemoryTasksRepository(cfg.Server.DatabaseFile)

runner := service.NewRunner()
runnable := []service.Runnable{
repo,
}
authConfig := &httpserver.AuthChainConfig{
AuthBaseConfig: nil,
AuthTelegramConfig: nil,
AuthTokenConfig: nil,
}
if cfg.Server.Telegram.Enabled {
if cfg.Server.Telegram.Token == "" {
log.Fatalln("telegram token is empty")
}
if cfg.Server.Telegram.UserId == 0 {
log.Fatalln("telegram user id is empty")
}
authConfig.AuthTelegramConfig = &httpserver.AuthTelegramConfig{
Token: cfg.Server.Telegram.Token,
TrustedId: cfg.Server.Telegram.UserId,
}
telegramServer := telegram.NewTelegramServer(cfg.Server.Telegram.Token, cfg.Server.Telegram.UserId, cfg.Server.PublicUrl, repo)
runnable = append(runnable, telegramServer)

if cfg.Server.Telegram.EverydayAgenda.Enabled {
runnable = append(runnable, cron.NewRepeatableCron(func() error {
if err := telegramServer.TriggerAgenda(); err != nil {
return fmt.Errorf("cant trigger agenda: %w", err)
}
return nil
}, cron.RepeatEveryDayAt(cfg.Server.Telegram.EverydayAgenda.At)))
}
}
if cfg.Server.TokenAuth.Enabled {
if cfg.Server.TokenAuth.ClientToken == "" {
log.Fatalln("TokenAuth.ClientToken is empty")
}
authConfig.AuthTokenConfig = &httpserver.AuthTokenConfig{Token: cfg.Server.TokenAuth.ClientToken}
}
if cfg.Server.BaseAuth.Enabled {
if cfg.Server.BaseAuth.Login == "" {
log.Fatalln("BaseAuth.Login is empty")
}
if cfg.Server.BaseAuth.Password == "" {
log.Fatalln("BaseAuth.Password is empty")
}
authConfig.AuthBaseConfig = &httpserver.AuthBaseConfig{
Login: cfg.Server.BaseAuth.Login,
Password: cfg.Server.BaseAuth.Password,
}
}
if cfg.Server.OidcAuth.Enabled {
if cfg.Server.OidcAuth.ClientId == "" {
log.Fatalln("OidcAuth.ClientId is empty")
}
if cfg.Server.OidcAuth.ClientSecret == "" {
log.Fatalln("OidcAuth.ClientSecret is empty")
}
if cfg.Server.OidcAuth.IssuerUrl == "" {
log.Fatalln("OidcAuth.IssuerUrl is empty")
}
if cfg.Server.OidcAuth.CookieKey == "" {
log.Fatalln("OidcAuth.CookieKey is empty")
}
if len(cfg.Server.OidcAuth.WhitelistEmails) == 0 {
log.Fatalln("OidcAuth.CookieKey is empty")
}
if len([]byte(cfg.Server.OidcAuth.CookieKey)) != 32 {
log.Fatalln("OidcAuth.CookieKey should be base64 of 32 bytes. example: 'pwgen 32'")
}
authConfig.AuthOidcConfig = &httpserver.AuthOidcConfig{
ClientId: cfg.Server.OidcAuth.ClientId,
ClientSecret: cfg.Server.OidcAuth.ClientSecret,
IssuerUrl: cfg.Server.OidcAuth.IssuerUrl,
CookieKey: cfg.Server.OidcAuth.CookieKey,
WhitelistEmails: cfg.Server.OidcAuth.WhitelistEmails,
Scopes: cfg.Server.OidcAuth.Scopes,
}
}
if !cfg.Server.AuthEnabled {
authConfig = nil
}
httpServer, err := httpserver.NewHttpServer(
cfg.Server.ListenAddr,
repo,
authConfig,
cfg.Server.PublicUrl,
cfg.Server.DiagnosticEndpointsEnabled,
)
if err != nil {
log.Fatalln("cant create http server: %w", err)
}

runnable = append(runnable, httpServer)
errChan := runner.Run(runnable...)
log.Println("started!")
haveAnyError := false
for err := range errChan {
if err != nil {
haveAnyError = true
}
log.Println("application error chan", err)
}
if haveAnyError {
log.Fatalln("something happened...")
}
log.Println("graceful exit")
},
}
Loading

0 comments on commit 8f2b54f

Please sign in to comment.