diff --git a/.golangci.yml b/.golangci.yml index 85cfb6df..0610c4ae 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -32,3 +32,8 @@ linters: - unconvert - unparam - whitespace + +rules: + - linters: + - godox + severity: warn diff --git a/client/auth.go b/client/auth.go index 3b93c86b..453e1cfe 100644 --- a/client/auth.go +++ b/client/auth.go @@ -19,7 +19,7 @@ func (cc *Client) Auth() (*charm.Auth, error) { if err != nil { return nil, charm.ErrAuthFailed{Err: err} } - defer s.Close() + defer s.Close() // nolint:errcheck b, err := s.Output("api-auth") if err != nil { diff --git a/client/client.go b/client/client.go index 664ea7c9..2e7a9690 100644 --- a/client/client.go +++ b/client/client.go @@ -109,6 +109,7 @@ func NewClient(cfg *Config) (*Client, error) { return cc, nil } +// NewClientWithDefaults creates a new Charm client with default values. func NewClientWithDefaults() (*Client, error) { cfg, err := ConfigFromEnv() if err != nil { @@ -127,7 +128,7 @@ func (cc *Client) JWT(aud ...string) (string, error) { if err != nil { return "", err } - defer s.Close() + defer s.Close() // nolint:errcheck jwt, err := s.Output(strings.Join(append([]string{"jwt"}, aud...), " ")) if err != nil { return "", err @@ -141,7 +142,7 @@ func (cc *Client) ID() (string, error) { if err != nil { return "", err } - defer s.Close() + defer s.Close() // nolint:errcheck id, err := s.Output("id") if err != nil { return "", err @@ -155,7 +156,7 @@ func (cc *Client) AuthorizedKeys() (string, error) { if err != nil { return "", err } - defer s.Close() + defer s.Close() // nolint:errcheck keys, err := s.Output("keys") if err != nil { return "", err @@ -169,7 +170,7 @@ func (cc *Client) AuthorizedKeysWithMetadata() (*charm.Keys, error) { if err != nil { return nil, err } - defer s.Close() + defer s.Close() // nolint:errcheck b, err := s.Output("api-keys") if err != nil { @@ -192,7 +193,7 @@ func (cc *Client) UnlinkAuthorizedKey(key string) error { if err != nil { return err } - defer s.Close() + defer s.Close() // nolint:errcheck k := charm.PublicKey{Key: key} in, err := s.StdinPipe() if err != nil { @@ -215,6 +216,7 @@ func (cc *Client) UnlinkAuthorizedKey(key string) error { return nil } +// KeygenType returns the keygen key type. func (cfg *Config) KeygenType() keygen.KeyType { switch cfg.KeyType { case "Ed25519", "ed25519": @@ -280,14 +282,14 @@ func (cc *Client) sshSession() (*ssh.Session, error) { func (cc *Client) DataPath() (string, error) { if cc.Config.DataDir != "" { return filepath.Join(cc.Config.DataDir, cc.Config.Host), nil - } else { - scope := gap.NewScope(gap.User, filepath.Join("charm", cc.Config.Host)) - dataPath, err := scope.DataPath("") - if err != nil { - return "", err - } - return dataPath, nil } + + scope := gap.NewScope(gap.User, filepath.Join("charm", cc.Config.Host)) + dataPath, err := scope.DataPath("") + if err != nil { + return "", err + } + return dataPath, nil } // FindAuthKeys looks in a user's XDG charm-dir for possible auth keys. diff --git a/client/crypt.go b/client/crypt.go index 028e145d..1b836a85 100644 --- a/client/crypt.go +++ b/client/crypt.go @@ -58,8 +58,7 @@ func (cc *Client) findIdentities() ([]sasquatch.Identity, error) { // EncryptKeys returns all of the symmetric encrypt keys for the authed user. func (cc *Client) EncryptKeys() ([]*charm.EncryptKey, error) { - err := cc.cryptCheck() - if err != nil { + if err := cc.cryptCheck(); err != nil { return nil, err } return cc.plainTextEncryptKeys, nil @@ -75,8 +74,12 @@ func (cc *Client) addEncryptKey(pk string, gid string, key string, createdAt *ti if err != nil { return err } - w.Write([]byte(key)) - w.Close() + if _, err := w.Write([]byte(key)); err != nil { + return err + } + if err := w.Close(); err != nil { + return err + } encKey := base64.StdEncoding.EncodeToString(buf.Bytes()) ek := charm.EncryptKey{} diff --git a/client/http.go b/client/http.go index 82a793bb..44709adb 100644 --- a/client/http.go +++ b/client/http.go @@ -37,7 +37,7 @@ func (cc *Client) AuthedJSONRequest(method string, path string, reqBody interfac return err } if respBody != nil { - defer resp.Body.Close() + defer resp.Body.Close() // nolint:errcheck dec := json.NewDecoder(resp.Body) return dec.Decode(respBody) } diff --git a/client/link.go b/client/link.go index 5fcb1697..dfdd769d 100644 --- a/client/link.go +++ b/client/link.go @@ -14,7 +14,7 @@ func (cc *Client) LinkGen(lh charm.LinkHandler) error { if err != nil { return err } - defer s.Close() + defer s.Close() // nolint:errcheck out, err := s.StdoutPipe() if err != nil { return err @@ -83,7 +83,7 @@ func (cc *Client) Link(lh charm.LinkHandler, code string) error { if err != nil { return err } - defer s.Close() + defer s.Close() // nolint:errcheck out, err := s.StdoutPipe() if err != nil { return err diff --git a/client/news.go b/client/news.go index f1324962..48ea51c8 100644 --- a/client/news.go +++ b/client/news.go @@ -8,6 +8,7 @@ import ( charm "github.com/charmbracelet/charm/proto" ) +// NewsList lists the server news. func (cc *Client) NewsList(tags []string, page int) ([]*charm.News, error) { var nl []*charm.News @@ -22,6 +23,7 @@ func (cc *Client) NewsList(tags []string, page int) ([]*charm.News, error) { return nl, nil } +// News shows a given news. func (cc *Client) News(id string) (*charm.News, error) { var n *charm.News err := cc.AuthedJSONRequest("GET", fmt.Sprintf("/v1/news/%s", url.QueryEscape(id)), nil, &n) diff --git a/cmd/backup_keys.go b/cmd/backup_keys.go index 8028c71f..996e790d 100644 --- a/cmd/backup_keys.go +++ b/cmd/backup_keys.go @@ -99,7 +99,6 @@ func validateDirectory(path string) error { // Everything looks OK! return nil - } else if os.IsNotExist(err) { return fmt.Errorf("'%v' does not exist", path) } else { @@ -112,10 +111,10 @@ func createTar(source string, target string) error { if err != nil { return err } - defer tarfile.Close() + defer tarfile.Close() // nolint:errcheck tarball := tar.NewWriter(tarfile) - defer tarball.Close() + defer tarball.Close() // nolint:errcheck info, err := os.Stat(source) if err != nil { @@ -158,7 +157,7 @@ func createTar(source string, target string) error { if err != nil { return err } - defer file.Close() + defer file.Close() // nolint:errcheck _, err = io.Copy(tarball, file) return err }) diff --git a/cmd/crypt.go b/cmd/crypt.go index 43c4d949..3aa8f977 100644 --- a/cmd/crypt.go +++ b/cmd/crypt.go @@ -76,7 +76,7 @@ func cryptEncrypt(cmd *cobra.Command, args []string) error { if err != nil { return err } - eb.Close() + eb.Close() // nolint:errcheck cf := cryptFile{ Data: base64.StdEncoding.EncodeToString(buf.Bytes()), } @@ -99,7 +99,7 @@ func cryptDecrypt(cmd *cobra.Command, args []string) error { r = os.Stdin default: f, err := os.Open(args[0]) - defer f.Close() + defer f.Close() // nolint:errcheck r = f if err != nil { return err diff --git a/cmd/fs.go b/cmd/fs.go index 059051e8..23be7143 100644 --- a/cmd/fs.go +++ b/cmd/fs.go @@ -39,7 +39,7 @@ var ( Use: "fs", Hidden: false, Short: "Use the Charm file system.", - Long: paragraph(fmt.Sprintf("Commands to set, get and delete data from your Charm Cloud backed file system.")), + Long: paragraph("Commands to set, get and delete data from your Charm Cloud backed file system."), Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return nil @@ -120,12 +120,7 @@ func newLocalRemotePath(rawPath string) localRemotePath { } func (lrp *localRemotePath) separator() string { - switch lrp.pathType { - case localPath: - return string(os.PathSeparator) - default: - return "/" - } + return "/" } func (lrfs *localRemoteFS) Open(name string) (fs.File, error) { @@ -173,7 +168,7 @@ func (lrfs *localRemoteFS) write(name string, src fs.File) error { if err != nil { return err } - defer f.Close() + defer f.Close() // nolint:errcheck _, err = io.Copy(f, src) if err != nil { return err @@ -194,7 +189,7 @@ func (lrfs *localRemoteFS) copy(srcName string, dstName string, recursive bool) if err != nil { return err } - defer src.Close() + defer src.Close() // nolint:errcheck stat, err := src.Stat() if err != nil { return err @@ -216,7 +211,7 @@ func (lrfs *localRemoteFS) copy(srcName string, dstName string, recursive bool) if err != nil { return err } - defer wsrc.Close() + defer wsrc.Close() // nolint:errcheck wp := newLocalRemotePath(wps) wpp := strings.Split(filepath.Clean(wp.path), wp.separator()) rp := path.Join(wpp[parents:]...) @@ -235,17 +230,20 @@ func fsCat(cmd *cobra.Command, args []string) error { if err != nil { return err } - defer f.Close() + defer f.Close() // nolint:errcheck + fi, err := f.Stat() if err != nil { return err } + if fi.IsDir() { fmt.Printf("cat: %s: Is a directory\n", args[0]) - } else { - io.Copy(os.Stdout, f) + return nil } - return nil + + _, err = io.Copy(os.Stdout, f) + return err } func fsMove(cmd *cobra.Command, args []string) error { @@ -297,7 +295,7 @@ func fsList(cmd *cobra.Command, args []string) error { if err != nil { return err } - defer f.Close() + defer f.Close() // nolint:errcheck fi, err := f.Stat() if err != nil { return err @@ -350,8 +348,7 @@ func printDir(f fs.ReadDirFile) error { } fprintFileInfo(w, dfi) } - w.Flush() - return nil + return w.Flush() } func init() { diff --git a/cmd/import_keys.go b/cmd/import_keys.go index 8653f573..228bde3f 100644 --- a/cmd/import_keys.go +++ b/cmd/import_keys.go @@ -52,7 +52,7 @@ var ( return err } - if err := os.MkdirAll(dd, 0700); err != nil { + if err := os.MkdirAll(dd, 0o700); err != nil { return err } @@ -160,7 +160,7 @@ func isEmpty(name string) (bool, error) { if err != nil { return false, err } - defer f.Close() + defer f.Close() // nolint:errcheck _, err = f.Readdirnames(1) if err == io.EOF { @@ -174,7 +174,7 @@ func untar(tarball, targetDir string) error { if err != nil { return err } - defer reader.Close() + defer reader.Close() // nolint:errcheck tarReader := tar.NewReader(reader) for { @@ -208,10 +208,16 @@ func untar(tarball, targetDir string) error { if err != nil { return err } - defer file.Close() - _, err = io.Copy(file, tarReader) - if err != nil { - return err + defer file.Close() // nolint:errcheck + + for { + _, err := io.CopyN(file, tarReader, 1024) + if err != nil { + if err == io.EOF { + break + } + return err + } } } return nil diff --git a/cmd/keys.go b/cmd/keys.go index 6ccac6bb..60f0a40e 100644 --- a/cmd/keys.go +++ b/cmd/keys.go @@ -10,8 +10,10 @@ import ( "github.com/spf13/cobra" ) -var randomart bool -var simpleOutput bool +var ( + randomart bool + simpleOutput bool +) // KeysCmd is the cobra.Command for a user to browser and print their linked // SSH keys. @@ -29,7 +31,7 @@ var KeysCmd = &cobra.Command{ if err != nil { return err } - defer f.Close() + defer f.Close() // nolint:errcheck } return keys.NewProgram(cfg).Start() } diff --git a/cmd/kv.go b/cmd/kv.go index 42522cbb..a8d18f34 100644 --- a/cmd/kv.go +++ b/cmd/kv.go @@ -24,7 +24,7 @@ var ( Use: "kv", Hidden: false, Short: "Use the Charm key value store.", - Long: paragraph(fmt.Sprintf("Commands to set, get and delete data from your Charm Cloud backed key value store.")), + Long: paragraph("Commands to set, get and delete data from your Charm Cloud backed key value store."), Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { return nil @@ -143,7 +143,9 @@ func kvList(cmd *cobra.Command, args []string) error { if err != nil { return err } - db.Sync() + if err := db.Sync(); err != nil { + return err + } return db.View(func(txn *badger.Txn) error { opts := badger.DefaultIteratorOptions opts.PrefetchSize = 10 diff --git a/cmd/post_news.go b/cmd/post_news.go index 352757ca..ccb8c4e8 100644 --- a/cmd/post_news.go +++ b/cmd/post_news.go @@ -10,11 +10,11 @@ import ( "github.com/spf13/cobra" ) -var newsSubject string -var newsTagList string - var ( - //PostNewsCmd is the cobra.Command to self-host the Charm Cloud. + newsTagList string + newsSubject string + + // PostNewsCmd is the cobra.Command to self-host the Charm Cloud. PostNewsCmd = &cobra.Command{ Use: "post-news", Hidden: true, diff --git a/cmd/serve.go b/cmd/serve.go index 5367aa11..9241c6aa 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -20,7 +20,7 @@ var ( serverHealthPort int serverDataDir string - //ServeCmd is the cobra.Command to self-host the Charm Cloud. + // ServeCmd is the cobra.Command to self-host the Charm Cloud. ServeCmd = &cobra.Command{ Use: "serve", Aliases: []string{"server"}, diff --git a/cmd/serve_migrate.go b/cmd/serve_migrate.go index 568e3f1c..fec7be08 100644 --- a/cmd/serve_migrate.go +++ b/cmd/serve_migrate.go @@ -11,44 +11,44 @@ import ( "github.com/charmbracelet/charm/server/db/sqlite" "github.com/charmbracelet/charm/server/db/sqlite/migration" "github.com/spf13/cobra" - _ "modernc.org/sqlite" + + _ "modernc.org/sqlite" // sqlite driver ) -var ( - ServeMigrationCmd = &cobra.Command{ - Use: "migrate", - Aliases: []string{"migration"}, - Hidden: true, - Short: "Run the server migration tool.", - Long: paragraph("Run the server migration tool to migrate the database."), - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - cfg := server.DefaultConfig() - dp := filepath.Join(cfg.DataDir, "db", sqlite.DbName) - _, err := os.Stat(dp) - if err != nil { - return fmt.Errorf("database does not exist: %s", err) - } - db := sqlite.NewDB(dp) - for _, m := range []migration.Migration{ - migration.Migration0001, - } { - log.Printf("Running migration: %04d %s\n", m.ID, m.Name) - err = db.WrapTransaction(func(tx *sql.Tx) error { - _, err := tx.Exec(m.Sql) - if err != nil { - return err - } - return nil - }) +// ServeMigrationCmd migrate server db. +var ServeMigrationCmd = &cobra.Command{ + Use: "migrate", + Aliases: []string{"migration"}, + Hidden: true, + Short: "Run the server migration tool.", + Long: paragraph("Run the server migration tool to migrate the database."), + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + cfg := server.DefaultConfig() + dp := filepath.Join(cfg.DataDir, "db", sqlite.DbName) + _, err := os.Stat(dp) + if err != nil { + return fmt.Errorf("database does not exist: %s", err) + } + db := sqlite.NewDB(dp) + for _, m := range []migration.Migration{ + migration.Migration0001, + } { + log.Printf("Running migration: %04d %s\n", m.ID, m.Name) + err = db.WrapTransaction(func(tx *sql.Tx) error { + _, err := tx.Exec(m.SQL) if err != nil { - break + return err } - } + return nil + }) if err != nil { - return err + break } - return nil - }, - } -) + } + if err != nil { + return err + } + return nil + }, +} diff --git a/crypt/crypt.go b/crypt/crypt.go index 41ed4c23..db77500e 100644 --- a/crypt/crypt.go +++ b/crypt/crypt.go @@ -14,7 +14,7 @@ import ( // ErrIncorrectEncryptKeys is returned when the encrypt keys are missing or // incorrect for the encrypted data. -var ErrIncorrectEncryptKeys error = fmt.Errorf("incorrect or missing encrypt keys") +var ErrIncorrectEncryptKeys = fmt.Errorf("incorrect or missing encrypt keys") // Crypt manages the account and encryption keys used for encrypting and // decrypting. diff --git a/fs/fs.go b/fs/fs.go index cabd9a75..40a23a5c 100644 --- a/fs/fs.go +++ b/fs/fs.go @@ -59,12 +59,12 @@ func (df *DirFile) Stat() (fs.FileInfo, error) { return df.FileInfo, nil } -// Read reads from the DirFile and satisfies fs.FS +// Read reads from the DirFile and satisfies fs.FS. func (df *DirFile) Read(buf []byte) (int, error) { return df.Buffer.Read(buf) } -// Close is a no-op but satisfies fs.FS +// Close is a no-op but satisfies fs.FS. func (df *DirFile) Close() error { return nil } @@ -103,7 +103,7 @@ func (cfs *FS) Open(name string) (fs.File, error) { } else if err != nil { return nil, pathError(name, err) } - defer resp.Body.Close() + defer resp.Body.Close() // nolint:errcheck switch resp.Header.Get("Content-Type") { case "application/json": @@ -191,11 +191,12 @@ func (cfs *FS) WriteFile(name string, src fs.File) error { if err != nil { return err } - _, err = io.Copy(eb, src) - if err != nil { + if _, err := io.Copy(eb, src); err != nil { + return err + } + if err := eb.Close(); err != nil { return err } - eb.Close() // To calculate the Content Length of a multipart request, we need to split // the multipart into header, data body, and boundary footer and then // calculate the length of each. @@ -203,34 +204,32 @@ func (cfs *FS) WriteFile(name string, src fs.File) error { // https://go.dev/src/net/http/request.go#L891 databuf := bytes.NewBuffer(nil) w := multipart.NewWriter(databuf) - _, err = w.CreateFormFile("data", name) - if err != nil { + if _, err := w.CreateFormFile("data", name); err != nil { return err } headlen := databuf.Len() header := make([]byte, headlen) - _, err = databuf.Read(header) - if err != nil { + if _, err := databuf.Read(header); err != nil { + return err + } + if err := w.Close(); err != nil { return err } - w.Close() bounlen := databuf.Len() boun := make([]byte, bounlen) - _, err = databuf.Read(boun) - if err != nil { + if _, err := databuf.Read(boun); err != nil { return err } // headlen is the length of the multipart part header, bounlen is the length of the multipart boundary footer. contentLength := int64(headlen) + int64(ebuf.Len()) + int64(bounlen) // pipe the multipart request to the server rr, rw := io.Pipe() - defer rr.Close() + defer rr.Close() // nolint:errcheck go func() { - defer rw.Close() + defer rw.Close() // nolint:errcheck // write multipart header - _, err = rw.Write(header) - if err != nil { + if _, err := rw.Write(header); err != nil { log.Printf("WriteFile %s error: %v", name, err) return } @@ -241,15 +240,13 @@ func (cfs *FS) WriteFile(name string, src fs.File) error { if err != nil { break } - _, err = rw.Write(buf[:n]) - if err != nil { + if _, err := rw.Write(buf[:n]); err != nil { log.Printf("WriteFile %s error: %v", name, err) return } } // write multipart boundary - _, err = rw.Write(boun) - if err != nil { + if _, err := rw.Write(boun); err != nil { log.Printf("WriteFile %s error: %v", name, err) return } @@ -263,8 +260,11 @@ func (cfs *FS) WriteFile(name string, src fs.File) error { "Content-Type": {w.FormDataContentType()}, "Content-Length": {fmt.Sprintf("%d", contentLength)}, } - _, err = cfs.cc.AuthedRequest("POST", path, headers, rr) - return err + resp, err := cfs.cc.AuthedRequest("POST", path, headers, rr) + if err != nil { + return err + } + return resp.Body.Close() } // Remove deletes a file from the Charm Cloud server. @@ -274,8 +274,11 @@ func (cfs *FS) Remove(name string) error { return err } path := fmt.Sprintf("/v1/fs/%s", ep) - _, err = cfs.cc.AuthedRequest("DELETE", path, nil, nil) - return err + resp, err := cfs.cc.AuthedRequest("DELETE", path, nil, nil) + if err != nil { + return err + } + return resp.Body.Close() } // ReadDir reads the named directory and returns a list of directory entries. @@ -287,7 +290,7 @@ func (cfs *FS) ReadDir(name string) ([]fs.DirEntry, error) { if err != nil { return nil, err } - defer f.Close() + defer f.Close() // nolint:errcheck return f.(*File).ReadDir(0) } @@ -389,6 +392,7 @@ func (fi *FileInfo) Info() (fs.FileInfo, error) { return fi, nil } +// EncryptPath returns the encrypted path for a given path. func (cfs *FS) EncryptPath(path string) (string, error) { eps := make([]string, 0) path = strings.TrimPrefix(path, "charm:") @@ -403,6 +407,7 @@ func (cfs *FS) EncryptPath(path string) (string, error) { return strings.Join(eps, "/"), nil } +// DecryptPath returns the unencrypted path for a given path. func (cfs *FS) DecryptPath(path string) (string, error) { dps := make([]string, 0) ps := strings.Split(path, "/") @@ -421,7 +426,7 @@ func (sf sysFuture) resolve() ([]fs.DirEntry, error) { if err != nil { return nil, err } - defer f.Close() + defer f.Close() // nolint:errcheck fi, err := f.Stat() if err != nil { return nil, err diff --git a/kv/client.go b/kv/client.go index b13b6b99..f5bcca89 100644 --- a/kv/client.go +++ b/kv/client.go @@ -23,7 +23,6 @@ type kvFileInfo struct { name string size int64 mode fs.FileMode - isDir bool modTime time.Time } @@ -83,7 +82,7 @@ func (kv *KV) backupSeq(from uint64, at uint64) error { info: &kvFileInfo{ name: name, size: int64(size), - mode: fs.FileMode(0660), + mode: fs.FileMode(0o660), modTime: time.Now(), }, } @@ -99,7 +98,7 @@ func (kv *KV) restoreSeq(seq uint64) error { if err != nil { return err } - defer r.Close() + defer r.Close() // nolint:errcheck // TODO DB.Load() should be called on a database that is not running any // other concurrent transactions while it is running. return kv.DB.Load(r, 1) @@ -157,10 +156,10 @@ func encryptKeyToBadgerKey(k *charm.EncryptKey) ([]byte, error) { if len(ek) < 32 { return nil, fmt.Errorf("Encryption key is too short") } - return []byte(ek)[0:32], nil + return ek[0:32], nil } -func openDB(cc *client.Client, name string, opt badger.Options) (*badger.DB, error) { +func openDB(cc *client.Client, opt badger.Options) (*badger.DB, error) { var db *badger.DB eks, err := cc.EncryptKeys() if err != nil { diff --git a/kv/kv.go b/kv/kv.go index 667d9696..f574a2ea 100644 --- a/kv/kv.go +++ b/kv/kv.go @@ -32,7 +32,7 @@ type KV struct { // Open a Charm Cloud managed Badger DB instance with badger.Options and // *client.Client. func Open(cc *client.Client, name string, opt badger.Options) (*KV, error) { - db, err := openDB(cc, name, opt) + db, err := openDB(cc, opt) if err != nil { return nil, err } @@ -219,7 +219,7 @@ func (kv *KV) Client() *client.Client { } // Reset deletes the local copy of the Badger DB and rebuilds with a fresh sync -// from the Charm Cloud +// from the Charm Cloud. func (kv *KV) Reset() error { opts := kv.DB.Opts() err := kv.DB.Close() @@ -236,7 +236,7 @@ func (kv *KV) Reset() error { return err } } - db, err := openDB(kv.cc, kv.name, opts) + db, err := openDB(kv.cc, opts) if err != nil { return err } diff --git a/proto/news.go b/proto/news.go index a0775032..8ef0807b 100644 --- a/proto/news.go +++ b/proto/news.go @@ -2,6 +2,7 @@ package proto import "time" +// News entity. type News struct { ID string `json:"id"` Subject string `json:"subject"` diff --git a/server/db/sqlite/migration/0001_foreign_keys.go b/server/db/sqlite/migration/0001_foreign_keys.go index 916a7585..fd4f88df 100644 --- a/server/db/sqlite/migration/0001_foreign_keys.go +++ b/server/db/sqlite/migration/0001_foreign_keys.go @@ -1,10 +1,10 @@ package migration -var ( - Migration0001 = Migration{ - ID: 1, - Name: "foreign keys", - Sql: ` +// Migration0001 is the initial migration. +var Migration0001 = Migration{ + ID: 1, + Name: "foreign keys", + SQL: ` PRAGMA foreign_keys=off; /* public_key */ @@ -88,5 +88,4 @@ DROP TABLE _news_tag; PRAGMA foreign_keys=on; `, - } -) +} diff --git a/server/db/sqlite/migration/migration.go b/server/db/sqlite/migration/migration.go index e8e17143..5def2692 100644 --- a/server/db/sqlite/migration/migration.go +++ b/server/db/sqlite/migration/migration.go @@ -1,7 +1,8 @@ package migration +// Migration is a db migration script. type Migration struct { ID int Name string - Sql string + SQL string } diff --git a/server/db/sqlite/sql.go b/server/db/sqlite/sql.go index 0ac9e4d4..0f36606e 100644 --- a/server/db/sqlite/sql.go +++ b/server/db/sqlite/sql.go @@ -83,8 +83,7 @@ const ( sqlSelectEncryptKeys = `SELECT global_id, encrypted_key, created_at FROM encrypt_key WHERE public_key_id = ? ORDER BY created_at ASC` sqlSelectNamedSeq = `SELECT seq FROM named_seq WHERE user_id = ? AND name = ?` - sqlInsertUser = `INSERT INTO charm_user (charm_id) VALUES (?)` - sqlInsertUserWithName = `INSERT INTO charm_user (charm_id, name) VALUES (?, ?)` + sqlInsertUser = `INSERT INTO charm_user (charm_id) VALUES (?)` sqlInsertPublicKey = `INSERT INTO public_key (user_id, public_key) VALUES (?, ?) ON CONFLICT (user_id, public_key) DO UPDATE SET diff --git a/server/db/sqlite/storage.go b/server/db/sqlite/storage.go index 173b227d..83cba472 100644 --- a/server/db/sqlite/storage.go +++ b/server/db/sqlite/storage.go @@ -11,8 +11,9 @@ import ( charm "github.com/charmbracelet/charm/proto" "github.com/google/uuid" "modernc.org/sqlite" - _ "modernc.org/sqlite" sqlitelib "modernc.org/sqlite/lib" + + _ "modernc.org/sqlite" // sqlite driver ) const ( @@ -22,11 +23,12 @@ const ( DbOptions = "?_pragma=busy_timeout(5000)&_pragma=foreign_keys(1)" ) +// DB is the database struct. type DB struct { - host string - db *sql.DB + db *sql.DB } +// NewDB creates a new DB in the given path. func NewDB(path string) *DB { var err error log.Printf("Opening SQLite db: %s\n", path) @@ -42,26 +44,27 @@ func NewDB(path string) *DB { return d } +// UserCount returns the number of users. func (me *DB) UserCount() (int, error) { var c int r := me.db.QueryRow(sqlCountUsers) - err := r.Scan(&c) - if err != nil { + if err := r.Scan(&c); err != nil { return 0, err } return c, nil } +// UserNameCount returns the number of users with a user name set. func (me *DB) UserNameCount() (int, error) { var c int r := me.db.QueryRow(sqlCountUserNames) - err := r.Scan(&c) - if err != nil { + if err := r.Scan(&c); err != nil { return 0, err } return c, nil } +// GetUserWithID returns the user for the given id. func (me *DB) GetUserWithID(charmID string) (*charm.User, error) { r := me.db.QueryRow(sqlSelectUserWithCharmID, charmID) u, err := me.scanUser(r) @@ -71,6 +74,7 @@ func (me *DB) GetUserWithID(charmID string) (*charm.User, error) { return u, nil } +// GetUserWithName returns the user for the given name. func (me *DB) GetUserWithName(name string) (*charm.User, error) { r := me.db.QueryRow(sqlSelectUserWithName, name) u, err := me.scanUser(r) @@ -80,10 +84,12 @@ func (me *DB) GetUserWithName(name string) (*charm.User, error) { return u, nil } +// SetUserName sets a user name for the given user id. func (me *DB) SetUserName(charmID string, name string) (*charm.User, error) { var u *charm.User log.Printf("Setting name `%s` for user %s\n", name, charmID) err := me.WrapTransaction(func(tx *sql.Tx) error { + // TODO: this should be handled with unique constraints in the database instead. var err error r := me.selectUserWithName(tx, name) u, err = me.scanUser(r) @@ -122,6 +128,7 @@ func (me *DB) SetUserName(charmID string, name string) (*charm.User, error) { return u, nil } +// UserForKey returns the user for the given key, or optionally creates a new user with it. func (me *DB) UserForKey(key string, create bool) (*charm.User, error) { pk := &charm.PublicKey{} u := &charm.User{} @@ -165,6 +172,7 @@ func (me *DB) UserForKey(key string, create bool) (*charm.User, error) { return u, nil } +// AddEncryptKeyForPublicKey adds an ecrypted key to the user. func (me *DB) AddEncryptKeyForPublicKey(u *charm.User, pk string, gid string, ek string, ca *time.Time) error { log.Printf("Adding encrypted key %s %s for user %s\n", gid, ca, u.CharmID) return me.WrapTransaction(func(tx *sql.Tx) error { @@ -190,6 +198,7 @@ func (me *DB) AddEncryptKeyForPublicKey(u *charm.User, pk string, gid string, ek }) } +// EncryptKeysForPublicKey returns the encrypt keys for the given user. func (me *DB) EncryptKeysForPublicKey(pk *charm.PublicKey) ([]*charm.EncryptKey, error) { var ks []*charm.EncryptKey err := me.WrapTransaction(func(tx *sql.Tx) error { @@ -197,6 +206,10 @@ func (me *DB) EncryptKeysForPublicKey(pk *charm.PublicKey) ([]*charm.EncryptKey, if err != nil { return err } + if rs.Err() != nil { + return rs.Err() + } + defer rs.Close() // nolint:errcheck for rs.Next() { k := &charm.EncryptKey{} err := rs.Scan(&k.ID, &k.Key, &k.CreatedAt) @@ -213,6 +226,7 @@ func (me *DB) EncryptKeysForPublicKey(pk *charm.PublicKey) ([]*charm.EncryptKey, return ks, nil } +// LinkUserKey links a user to a key. func (me *DB) LinkUserKey(user *charm.User, key string) error { ks := charm.PublicKeySha(key) log.Printf("Linking user %s and key %s\n", user.CharmID, ks) @@ -221,6 +235,7 @@ func (me *DB) LinkUserKey(user *charm.User, key string) error { }) } +// UnlinkUserKey unlinks the key from the user. func (me *DB) UnlinkUserKey(user *charm.User, key string) error { ks := charm.PublicKeySha(key) log.Printf("Unlinking user %s key %s\n", user.CharmID, ks) @@ -248,6 +263,7 @@ func (me *DB) UnlinkUserKey(user *charm.User, key string) error { }) } +// KeysForUser returns all user's public keys. func (me *DB) KeysForUser(user *charm.User) ([]*charm.PublicKey, error) { var keys []*charm.PublicKey log.Printf("Getting keys for user %s\n", user.CharmID) @@ -256,7 +272,7 @@ func (me *DB) KeysForUser(user *charm.User) ([]*charm.PublicKey, error) { if err != nil { return err } - defer rs.Close() + defer rs.Close() // nolint:errcheck for rs.Next() { k := &charm.PublicKey{} @@ -279,6 +295,7 @@ func (me *DB) KeysForUser(user *charm.User) ([]*charm.PublicKey, error) { return keys, nil } +// GetSeq returns the named sequence. func (me *DB) GetSeq(u *charm.User, name string) (uint64, error) { var seq uint64 var err error @@ -295,6 +312,7 @@ func (me *DB) GetSeq(u *charm.User, name string) (uint64, error) { return seq, nil } +// NextSeq increments the sequence and returns. func (me *DB) NextSeq(u *charm.User, name string) (uint64, error) { var seq uint64 var err error @@ -311,6 +329,7 @@ func (me *DB) NextSeq(u *charm.User, name string) (uint64, error) { return seq, nil } +// GetNews returns the server news. func (me *DB) GetNews(id string) (*charm.News, error) { n := &charm.News{} i, err := strconv.Atoi(id) @@ -327,6 +346,7 @@ func (me *DB) GetNews(id string) (*charm.News, error) { return n, nil } +// GetNewsList returns the list of server news. func (me *DB) GetNewsList(tag string, page int) ([]*charm.News, error) { var ns []*charm.News err := me.WrapTransaction(func(tx *sql.Tx) error { @@ -334,6 +354,10 @@ func (me *DB) GetNewsList(tag string, page int) ([]*charm.News, error) { if err != nil { return err } + if rs.Err() != nil { + return rs.Err() + } + defer rs.Close() // nolint:errcheck for rs.Next() { n := &charm.News{} err := rs.Scan(&n.ID, &n.Subject, &n.CreatedAt) @@ -347,12 +371,14 @@ func (me *DB) GetNewsList(tag string, page int) ([]*charm.News, error) { return ns, err } +// PostNews publish news to the server. func (me *DB) PostNews(subject string, body string, tags []string) error { return me.WrapTransaction(func(tx *sql.Tx) error { return me.insertNews(tx, subject, body, tags) }) } +// MergeUsers merge two users into a single one. func (me *DB) MergeUsers(userID1 int, userID2 int) error { return me.WrapTransaction(func(tx *sql.Tx) error { err := me.updateMergePublicKeys(tx, userID1, userID2) @@ -364,6 +390,7 @@ func (me *DB) MergeUsers(userID1 int, userID2 int) error { }) } +// SetToken creates the given token. func (me *DB) SetToken(token charm.Token) error { return me.WrapTransaction(func(tx *sql.Tx) error { err := me.insertToken(tx, string(token)) @@ -377,12 +404,14 @@ func (me *DB) SetToken(token charm.Token) error { }) } +// DeleteToken deletes the given token. func (me *DB) DeleteToken(token charm.Token) error { return me.WrapTransaction(func(tx *sql.Tx) error { return me.deleteToken(tx, string(token)) }) } +// CreateDB creates the database. func (me *DB) CreateDB() error { return me.WrapTransaction(func(tx *sql.Tx) error { err := me.createUserTable(tx) @@ -417,6 +446,7 @@ func (me *DB) CreateDB() error { }) } +// Close the db. func (me *DB) Close() error { log.Println("Closing db") return me.db.Close() @@ -441,11 +471,6 @@ func (me *DB) insertUser(tx *sql.Tx, charmID string) error { return err } -func (me *DB) insertUserWithName(tx *sql.Tx, charmID string, name string) error { - _, err := tx.Exec(sqlInsertUserWithName, charmID, name) - return err -} - func (me *DB) insertPublicKey(tx *sql.Tx, userID int, key string) error { _, err := tx.Exec(sqlInsertPublicKey, userID, key) return err @@ -463,6 +488,9 @@ func (me *DB) insertEncryptKey(tx *sql.Tx, key string, globalID string, publicKe func (me *DB) insertNews(tx *sql.Tx, subject string, body string, tags []string) error { r, err := tx.Exec(sqlInsertNews, subject, body) + if err != nil { + return err + } nid, err := r.LastInsertId() if err != nil { return err @@ -484,8 +512,7 @@ func (me *DB) insertToken(tx *sql.Tx, token string) error { func (me *DB) selectNamedSeq(tx *sql.Tx, userID int, name string) (uint64, error) { var seq uint64 r := tx.QueryRow(sqlSelectNamedSeq, userID, name) - err := r.Scan(&seq) - if err != nil { + if err := r.Scan(&seq); err != nil { return 0, err } return seq, nil @@ -622,6 +649,7 @@ func (me *DB) scanUser(r *sql.Row) (*charm.User, error) { return u, nil } +// WrapTransaction runs the given function within a transaction. func (me *DB) WrapTransaction(f func(tx *sql.Tx) error) error { ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() diff --git a/server/http.go b/server/http.go index 30874ed6..d17d3b82 100644 --- a/server/http.go +++ b/server/http.go @@ -287,7 +287,7 @@ func (s *HTTPServer) handlePostFile(w http.ResponseWriter, r *http.Request) { s.renderError(w) return } - defer f.Close() + defer f.Close() // nolint:errcheck if s.cfg.UserMaxStorage > 0 { stat, err := s.cfg.FileStore.Stat(u.CharmID, "") if err != nil { @@ -321,7 +321,7 @@ func (s *HTTPServer) handleGetFile(w http.ResponseWriter, r *http.Request) { s.renderError(w) return } - defer f.Close() + defer f.Close() // nolint:errcheck fi, err := f.Stat() if err != nil { log.Printf("cannot get file info: %s", err) diff --git a/server/link.go b/server/link.go index f559ff36..f3ca22c7 100644 --- a/server/link.go +++ b/server/link.go @@ -115,7 +115,7 @@ func (sl *SSHLinker) User() *charm.User { func (me *SSHServer) LinkGen(lt charm.LinkTransport) error { u := lt.User() tok := me.NewToken() - defer me.db.DeleteToken(tok) + defer me.db.DeleteToken(tok) // nolint:errcheck me.linkQueue.InitLinkRequest(tok) defer me.linkQueue.DeleteLinkRequest(tok) linkRequest, err := me.linkQueue.WaitLinkRequest(tok) @@ -317,8 +317,7 @@ func (me *SSHServer) handleLinkRequestAPI(s ssh.Session) { } func (me *SSHServer) handleAPILink(s ssh.Session) { - args := s.Command()[1:] - if len(args) == 0 { + if args := s.Command()[1:]; len(args) == 0 { me.handleLinkGenAPI(s) } else { me.handleLinkRequestAPI(s) @@ -368,8 +367,7 @@ type channelLinkQueue struct { // InitLinkRequest implements the proto.LinkQueue interface for the channelLinkQueue. func (s *channelLinkQueue) InitLinkRequest(t charm.Token) { - _, ok := s.linkRequests[t] - if !ok { + if _, ok := s.linkRequests[t]; !ok { log.Printf("Making new link for token: %s\n", t) lr := make(chan *charm.Link) s.linkRequests[t] = lr diff --git a/server/stats/sqlite/sqlite.go b/server/stats/sqlite/sqlite.go index 5a31698b..f4782593 100644 --- a/server/stats/sqlite/sqlite.go +++ b/server/stats/sqlite/sqlite.go @@ -8,8 +8,9 @@ import ( "time" "modernc.org/sqlite" - _ "modernc.org/sqlite" sqlitelib "modernc.org/sqlite/lib" + + _ "modernc.org/sqlite" // sqlite driver ) const ( @@ -35,7 +36,7 @@ const ( )` ) -// Stats implements the stats.Stats interface for SQLite +// Stats implements the stats.Stats interface for SQLite. type Stats struct { db *sql.DB } @@ -66,7 +67,7 @@ func (s *Stats) createDB() error { func (s *Stats) increment(field string) { err := s.wrapTransaction(func(tx *sql.Tx) error { // SQLite doesn't use placeholders for table or field names - stmt := fmt.Sprintf("UPDATE stats SET %s = %s+1 WHERE id = (SELECT MAX(id) from stats)", field, field) + stmt := fmt.Sprintf("UPDATE stats SET %s = %s+1 WHERE id = (SELECT MAX(id) from stats)", field, field) // nolint:gosec _, err := s.db.Exec(stmt) return err }) @@ -155,7 +156,7 @@ func (s *Stats) GetNewsList() { s.increment("get_news_list") } -// Close SQLite DB +// Close SQLite DB. func (s *Stats) Close() error { return s.db.Close() } diff --git a/server/storage/local/storage.go b/server/storage/local/storage.go index e6b7c946..8aa97a03 100644 --- a/server/storage/local/storage.go +++ b/server/storage/local/storage.go @@ -24,7 +24,7 @@ type LocalFileStore struct { // will be encrypted client-side and stored as regular file system files and // folders. func NewLocalFileStore(path string) (*LocalFileStore, error) { - err := storage.EnsureDir(path, 0700) + err := storage.EnsureDir(path, 0o700) if err != nil { return nil, err } @@ -142,7 +142,7 @@ func (lfs *LocalFileStore) Put(charmID string, path string, r io.Reader, mode fs if err != nil { return err } - defer f.Close() + defer f.Close() // nolint:errcheck _, err = io.Copy(f, r) if err != nil { return err @@ -156,9 +156,5 @@ func (lfs *LocalFileStore) Put(charmID string, path string, r io.Reader, mode fs // Delete deletes the file at the given path for the provided Charm ID. func (lfs *LocalFileStore) Delete(charmID string, path string) error { fp := filepath.Join(lfs.Path, charmID, path) - err := os.RemoveAll(fp) - if err != nil { - return err - } - return nil + return os.RemoveAll(fp) } diff --git a/server/storage/local/storage_test.go b/server/storage/local/storage_test.go index d1afc6ad..cef08691 100644 --- a/server/storage/local/storage_test.go +++ b/server/storage/local/storage_test.go @@ -22,7 +22,7 @@ func TestPut(t *testing.T) { paths := []string{"/", "///"} for _, path := range paths { - err = lfs.Put(charmID, path, buf, fs.FileMode(0644)) + err = lfs.Put(charmID, path, buf, fs.FileMode(0o644)) if err == nil { t.Fatalf("expected error when file path is %s", path) } @@ -33,7 +33,7 @@ func TestPut(t *testing.T) { path := "/hello.txt" t.Run(path, func(t *testing.T) { buf = bytes.NewBufferString(content) - err = lfs.Put(charmID, path, buf, fs.FileMode(0644)) + err = lfs.Put(charmID, path, buf, fs.FileMode(0o644)) if err != nil { t.Fatalf("expected no error when file path is %s, %v", path, err) } @@ -56,7 +56,6 @@ func TestPut(t *testing.T) { read, err := ioutil.ReadAll(file) if err != nil { t.Fatalf("expected no error when reading file %s", path) - } if string(read) != content { t.Fatalf("expected content to be %s, got %s", content, string(read)) @@ -67,7 +66,7 @@ func TestPut(t *testing.T) { path = "/foo/hello.txt" t.Run(path, func(t *testing.T) { buf = bytes.NewBufferString(content) - err = lfs.Put(charmID, path, buf, fs.FileMode(0644)) + err = lfs.Put(charmID, path, buf, fs.FileMode(0o644)) if err != nil { t.Fatalf("expected no error when file path is %s, %v", path, err) } @@ -90,7 +89,6 @@ func TestPut(t *testing.T) { read, err := ioutil.ReadAll(file) if err != nil { t.Fatalf("expected no error when reading file %s", path) - } if string(read) != content { t.Fatalf("expected content to be %s, got %s", content, string(read)) diff --git a/ui/common/common.go b/ui/common/common.go index 87ebc133..c4dbb262 100644 --- a/ui/common/common.go +++ b/ui/common/common.go @@ -12,7 +12,7 @@ var ( checkTTY sync.Once ) -// Returns true if standard out is a terminal +// Returns true if standard out is a terminal. func IsTTY() bool { checkTTY.Do(func() { isTTY = isatty.IsTerminal(os.Stdout.Fd()) diff --git a/ui/common/styles.go b/ui/common/styles.go index b478b73d..41e74904 100644 --- a/ui/common/styles.go +++ b/ui/common/styles.go @@ -9,7 +9,6 @@ var ( indigo = lipgloss.AdaptiveColor{Light: "#5A56E0", Dark: "#7571F9"} subtleIndigo = lipgloss.AdaptiveColor{Light: "#7D79F6", Dark: "#514DC1"} cream = lipgloss.AdaptiveColor{Light: "#FFFDF5", Dark: "#FFFDF5"} - yellowGreen = lipgloss.AdaptiveColor{Light: "#04B575", Dark: "#ECFD65"} fuschia = lipgloss.AdaptiveColor{Light: "#EE6FF8", Dark: "#EE6FF8"} green = lipgloss.Color("#04B575") red = lipgloss.AdaptiveColor{Light: "#FF4672", Dark: "#ED567A"} diff --git a/ui/common/views.go b/ui/common/views.go index dad2956a..d01cfcb5 100644 --- a/ui/common/views.go +++ b/ui/common/views.go @@ -124,7 +124,7 @@ func YesButtonView(focused bool) string { } else { st = blurredButtonStyle } - return underlineInitialCharButton("Yes", st, focused) + return underlineInitialCharButton("Yes", st) } // NoButtonView returns a button reading "No.". @@ -138,10 +138,10 @@ func NoButtonView(focused bool) string { st = st.Copy(). PaddingLeft(st.GetPaddingLeft() + 1). PaddingRight(st.GetPaddingRight() + 1) - return underlineInitialCharButton("No", st, focused) + return underlineInitialCharButton("No", st) } -func underlineInitialCharButton(str string, style lipgloss.Style, focused bool) string { +func underlineInitialCharButton(str string, style lipgloss.Style) string { if len(str) == 0 { return "" }