Create cmd for managing accounts

This commit is contained in:
Radhi Fadlillah 2018-02-20 16:48:02 +07:00
parent d74d545b49
commit b555d1b5a4
5 changed files with 205 additions and 4 deletions

128
cmd/account.go Normal file
View file

@ -0,0 +1,128 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"golang.org/x/crypto/ssh/terminal"
"syscall"
)
var (
accountCmd = &cobra.Command{
Use: "account",
Short: "Manage account for accessing web interface.",
}
addAccountCmd = &cobra.Command{
Use: "add username",
Short: "Create new account.",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
username := args[0]
fmt.Println("Username: " + username)
fmt.Print("Password: ")
bytePassword, err := terminal.ReadPassword(int(syscall.Stdin))
if err != nil {
cError.Println(err)
return
}
fmt.Println()
err = addAccount(username, string(bytePassword))
if err != nil {
cError.Println(err)
return
}
},
}
printAccountCmd = &cobra.Command{
Use: "print",
Short: "Print the saved accounts.",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
keyword, _ := cmd.Flags().GetString("search")
err := printAccounts(keyword)
if err != nil {
cError.Println(err)
return
}
},
}
deleteAccountCmd = &cobra.Command{
Use: "delete [usernames]",
Short: "Delete the saved accounts.",
Long: "Delete accounts. " +
"Accepts space-separated list of usernames. " +
"If no arguments, all records will be deleted.",
Run: func(cmd *cobra.Command, args []string) {
// Read flags
skipConfirmation, _ := cmd.Flags().GetBool("yes")
// If no arguments, confirm to user
if len(args) == 0 && !skipConfirmation {
confirmDelete := ""
fmt.Print("Remove ALL accounts? (y/n): ")
fmt.Scanln(&confirmDelete)
if confirmDelete != "y" {
fmt.Println("No accounts deleted")
return
}
}
err := DB.DeleteAccounts(args...)
if err != nil {
cError.Println(err)
return
}
fmt.Println("Accounts has been deleted")
},
}
)
func init() {
// Create flags
printAccountCmd.Flags().StringP("search", "s", "", "Search accounts by username")
deleteAccountCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt and delete ALL accounts")
accountCmd.AddCommand(addAccountCmd)
accountCmd.AddCommand(printAccountCmd)
accountCmd.AddCommand(deleteAccountCmd)
rootCmd.AddCommand(accountCmd)
}
func addAccount(username, password string) error {
if username == "" {
return fmt.Errorf("Username must not empty")
}
if len(password) < 8 {
return fmt.Errorf("Password must be at least 8 characters")
}
err := DB.CreateAccount(username, password)
if err != nil {
return err
}
return nil
}
func printAccounts(keyword string) error {
accounts, err := DB.GetAccounts(keyword)
if err != nil {
return err
}
for _, account := range accounts {
cIndex.Print("- ")
fmt.Println(account.Username)
}
return nil
}

View file

@ -84,7 +84,7 @@ func addBookmark(url, title, excerpt string, tags []string, offline bool) (book
}
// Save to database
bookmark.ID, err = DB.SaveBookmark(bookmark)
bookmark.ID, err = DB.CreateBookmark(bookmark)
if err != nil {
return book, err
}

View file

@ -8,7 +8,7 @@ import (
// Database is interface for manipulating data in database.
type Database interface {
// SaveBookmark saves new bookmark to database.
SaveBookmark(bookmark model.Bookmark) (int64, error)
CreateBookmark(bookmark model.Bookmark) (int64, error)
// GetBookmarks fetch list of bookmarks based on submitted indices.
GetBookmarks(options GetBookmarksOptions, indices ...string) ([]model.Bookmark, error)
@ -21,6 +21,15 @@ type Database interface {
// UpdateBookmarks updates the saved bookmark in database.
UpdateBookmarks(bookmarks []model.Bookmark) error
// CreateAccount creates new account in database
CreateAccount(username, password string) error
// GetAccounts fetch list of accounts in database
GetAccounts(keyword string) ([]model.Account, error)
// DeleteAccounts removes all record with matching usernames
DeleteAccounts(usernames ...string) error
}
type GetBookmarksOptions struct {

View file

@ -5,6 +5,7 @@ import (
"fmt"
"github.com/RadhiFadlillah/shiori/model"
"github.com/jmoiron/sqlx"
"golang.org/x/crypto/bcrypt"
"sort"
"strconv"
"strings"
@ -75,8 +76,8 @@ func OpenSQLiteDatabase() (*SQLiteDatabase, error) {
return &SQLiteDatabase{*db}, err
}
// SaveBookmark saves new bookmark to database. Returns new ID and error if any happened.
func (db *SQLiteDatabase) SaveBookmark(bookmark model.Bookmark) (bookmarkID int64, err error) {
// CreateBookmark saves new bookmark to database. Returns new ID and error if any happened.
func (db *SQLiteDatabase) CreateBookmark(bookmark model.Bookmark) (bookmarkID int64, err error) {
// Check URL and title
if bookmark.URL == "" {
return -1, fmt.Errorf("URL must not empty")
@ -536,3 +537,59 @@ func (db *SQLiteDatabase) UpdateBookmarks(bookmarks []model.Bookmark) (err error
return err
}
// CreateAccount saves new account to database. Returns new ID and error if any happened.
func (db *SQLiteDatabase) CreateAccount(username, password string) (err error) {
// Hash password with bcrypt
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 10)
if err != nil {
return err
}
// Insert account to database
_, err = db.Exec(`INSERT INTO account
(username, password) VALUES (?, ?)`,
username, hashedPassword)
if err != nil {
return err
}
return nil
}
// GetAccounts fetch list of accounts in database
func (db *SQLiteDatabase) GetAccounts(keyword string) ([]model.Account, error) {
query := `SELECT id, username, password FROM account`
args := []interface{}{}
if keyword != "" {
query += ` WHERE username LIKE ?`
args = append(args, "%"+keyword+"%")
}
query += ` ORDER BY username`
accounts := []model.Account{}
err := db.Select(&accounts, query, args...)
return accounts, err
}
// DeleteAccounts removes all record with matching usernames
func (db *SQLiteDatabase) DeleteAccounts(usernames ...string) error {
// Prepare where clause
args := []interface{}{}
whereClause := " WHERE 1"
if len(usernames) > 0 {
whereClause = " WHERE username IN ("
for _, username := range usernames {
args = append(args, username)
whereClause += "?,"
}
whereClause = whereClause[:len(whereClause)-1]
whereClause += ")"
}
// Delete usernames
_,err := db.Exec(`DELETE FROM account `+whereClause, args...)
return err
}

View file

@ -24,3 +24,10 @@ type Bookmark struct {
HTML template.HTML `db:"html" json:"-"`
Tags []Tag `json:"tags"`
}
// Account is account for accessing bookmarks from web interface
type Account struct {
ID int64 `db:"id" json:"id"`
Username string `db:"username" json:"username"`
Password string `db:"password" json:"password"`
}