-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 8f2b54f
Showing
66 changed files
with
5,047 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
todolist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
}, | ||
} |
Oops, something went wrong.