Remove stub user features and handlers.

This commit is contained in:
Kailash Nadh 2019-06-26 16:22:47 +05:30
parent c952b7a2e8
commit fe9102120c
5 changed files with 17 additions and 244 deletions

View file

@ -43,9 +43,6 @@ func registerHandlers(e *echo.Echo) {
e.GET("/", handleIndexPage)
e.GET("/api/config.js", handleGetConfigScript)
e.GET("/api/dashboard/stats", handleGetDashboardStats)
e.GET("/api/users", handleGetUsers)
e.POST("/api/users", handleCreateUser)
e.DELETE("/api/users/:id", handleDeleteUser)
e.GET("/api/subscribers/:id", handleGetSubscriber)
e.POST("/api/subscribers", handleCreateSubscriber)

View file

@ -1,90 +1,39 @@
package main
import (
"bytes"
"fmt"
"io/ioutil"
"regexp"
"syscall"
"github.com/lib/pq"
uuid "github.com/satori/go.uuid"
"strings"
"github.com/jmoiron/sqlx"
"github.com/knadh/goyesql"
"github.com/knadh/listmonk/models"
"github.com/lib/pq"
uuid "github.com/satori/go.uuid"
"github.com/spf13/viper"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/ssh/terminal"
)
// install runs the first time setup of creating and
// migrating the database and creating the super user.
func install(app *App, qMap goyesql.Queries) {
var (
email, pw, pw2 []byte
err error
// Pseudo e-mail validation using Regexp, well ...
emRegex, _ = regexp.Compile("(.+?)@(.+?)")
)
fmt.Println("")
fmt.Println("** First time installation. **")
fmt.Println("** IMPORTANT: This will wipe existing listmonk tables and types. **")
fmt.Println("** First time installation **")
fmt.Printf("** IMPORTANT: This will wipe existing listmonk tables and types in the DB '%s' **",
viper.GetString("db.database"))
fmt.Println("")
for len(email) == 0 {
fmt.Print("Enter the superadmin login e-mail: ")
if _, err = fmt.Scanf("%s", &email); err != nil {
logger.Fatalf("Error reading e-mail from the terminal: %v", err)
}
if !emRegex.Match(email) {
logger.Println("Please enter a valid e-mail")
email = []byte{}
}
var ok string
fmt.Print("Continue (y/n)? ")
if _, err := fmt.Scanf("%s", &ok); err != nil {
logger.Fatalf("Error reading value from terminal: %v", err)
}
for len(pw) < 8 {
fmt.Print("Enter the superadmin password (min 8 chars): ")
if pw, err = terminal.ReadPassword(int(syscall.Stdin)); err != nil {
logger.Fatalf("Error reading password from the terminal: %v", err)
}
fmt.Println("")
if len(pw) < 8 {
logger.Println("Password should be min 8 characters")
pw = []byte{}
}
}
for len(pw2) < 8 {
fmt.Print("Repeat the superadmin password: ")
if pw2, err = terminal.ReadPassword(int(syscall.Stdin)); err != nil {
logger.Fatalf("Error reading password from the terminal: %v", err)
}
fmt.Println("")
if len(pw2) < 8 {
logger.Println("Password should be min 8 characters")
pw2 = []byte{}
}
}
// Validate.
if !bytes.Equal(pw, pw2) {
logger.Fatalf("Passwords don't match")
}
// Hash the password.
hash, err := bcrypt.GenerateFromPassword(pw, bcrypt.DefaultCost)
if err != nil {
logger.Fatalf("Error hashing password: %v", err)
if strings.ToLower(ok) != "y" {
fmt.Println("Installation cancelled.")
return
}
// Migrate the tables.
err = installMigrate(app.DB, app)
err := installMigrate(app.DB, app)
if err != nil {
logger.Fatalf("Error migrating DB schema: %v", err)
}
@ -95,17 +44,6 @@ func install(app *App, qMap goyesql.Queries) {
logger.Fatalf("error loading SQL queries: %v", err)
}
// Create the superadmin user.
if _, err := q.CreateUser.Exec(
string(email),
models.UserTypeSuperadmin, // name
string(hash),
models.UserTypeSuperadmin,
models.UserStatusEnabled,
); err != nil {
logger.Fatalf("Error creating superadmin user: %v", err)
}
// Sample list.
var listID int
if err := q.CreateList.Get(&listID,
@ -118,11 +56,10 @@ func install(app *App, qMap goyesql.Queries) {
}
// Sample subscriber.
name := bytes.Split(email, []byte("@"))
if _, err := q.UpsertSubscriber.Exec(
uuid.NewV4(),
email,
bytes.Title(name[0]),
"test@test.com",
"Test Subscriber",
`{"type": "known", "good": true}`,
pq.Int64Array{int64(listID)},
); err != nil {
@ -147,8 +84,7 @@ func install(app *App, qMap goyesql.Queries) {
}
logger.Printf("Setup complete")
logger.Printf(`Run the program and login with the username "superadmin" and your password at %s`,
viper.GetString("server.address"))
logger.Printf(`Run the program view it at %s`, viper.GetString("app.address"))
}

View file

@ -57,11 +57,6 @@ type Queries struct {
RegisterCampaignView *sqlx.Stmt `query:"register-campaign-view"`
DeleteCampaign *sqlx.Stmt `query:"delete-campaign"`
CreateUser *sqlx.Stmt `query:"create-user"`
GetUsers *sqlx.Stmt `query:"get-users"`
UpdateUser *sqlx.Stmt `query:"update-user"`
DeleteUser *sqlx.Stmt `query:"delete-user"`
InsertMedia *sqlx.Stmt `query:"insert-media"`
GetMedia *sqlx.Stmt `query:"get-media"`
DeleteMedia *sqlx.Stmt `query:"delete-media"`

View file

@ -1,26 +1,9 @@
DROP TYPE IF EXISTS user_type CASCADE; CREATE TYPE user_type AS ENUM ('superadmin', 'user');
DROP TYPE IF EXISTS user_status CASCADE; CREATE TYPE user_status AS ENUM ('enabled', 'disabled');
DROP TYPE IF EXISTS list_type CASCADE; CREATE TYPE list_type AS ENUM ('public', 'private', 'temporary');
DROP TYPE IF EXISTS subscriber_status CASCADE; CREATE TYPE subscriber_status AS ENUM ('enabled', 'disabled', 'blacklisted');
DROP TYPE IF EXISTS subscription_status CASCADE; CREATE TYPE subscription_status AS ENUM ('unconfirmed', 'confirmed', 'unsubscribed');
DROP TYPE IF EXISTS campaign_status CASCADE; CREATE TYPE campaign_status AS ENUM ('draft', 'running', 'scheduled', 'paused', 'cancelled', 'finished');
DROP TYPE IF EXISTS content_type CASCADE; CREATE TYPE content_type AS ENUM ('richtext', 'html', 'plain');
-- users
DROP TABLE IF EXISTS users CASCADE;
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
password TEXT NOT NULL,
type user_type NOT NULL,
status user_status NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
DROP INDEX IF EXISTS idx_users_email; CREATE INDEX idx_users_email ON users(LOWER(email));
-- subscribers
DROP TABLE IF EXISTS subscribers CASCADE;
CREATE TABLE subscribers (

138
users.go
View file

@ -1,138 +0,0 @@
package main
import (
"errors"
"fmt"
"net/http"
"strconv"
"github.com/asaskevich/govalidator"
"github.com/knadh/listmonk/models"
"github.com/labstack/echo"
)
// handleGetUsers handles retrieval of users.
func handleGetUsers(c echo.Context) error {
var (
app = c.Get("app").(*App)
out []models.User
id, _ = strconv.Atoi(c.Param("id"))
single = false
)
// Fetch one list.
if id > 0 {
single = true
}
err := app.Queries.GetUsers.Select(&out, id)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError,
fmt.Sprintf("Error fetching users: %s", pqErrMsg(err)))
} else if single && len(out) == 0 {
return echo.NewHTTPError(http.StatusBadRequest, "User not found.")
} else if len(out) == 0 {
return c.JSON(http.StatusOK, okResp{[]struct{}{}})
}
if single {
return c.JSON(http.StatusOK, okResp{out[0]})
}
return c.JSON(http.StatusOK, okResp{out})
}
// handleCreateUser handles user creation.
func handleCreateUser(c echo.Context) error {
var (
app = c.Get("app").(*App)
o = models.User{}
)
if err := c.Bind(&o); err != nil {
return err
}
if !govalidator.IsEmail(o.Email) {
return errors.New("invalid `email`")
}
if !govalidator.IsByteLength(o.Name, 1, stdInputMaxLen) {
return errors.New("invalid length for `name`")
}
// Insert and read ID.
var newID int
if err := app.Queries.CreateUser.Get(&newID,
o.Email,
o.Name,
o.Password,
o.Type,
o.Status); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError,
fmt.Sprintf("Error creating user: %v", pqErrMsg(err)))
}
// Hand over to the GET handler to return the last insertion.
c.SetParamNames("id")
c.SetParamValues(fmt.Sprintf("%d", newID))
return c.JSON(http.StatusOK, handleGetLists(c))
}
// handleUpdateUser handles user modification.
func handleUpdateUser(c echo.Context) error {
var (
app = c.Get("app").(*App)
id, _ = strconv.Atoi(c.Param("id"))
)
if id < 1 {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid ID.")
} else if id == 1 {
return echo.NewHTTPError(http.StatusBadRequest,
"The primordial super admin cannot be deleted.")
}
var o models.User
if err := c.Bind(&o); err != nil {
return err
}
if !govalidator.IsEmail(o.Email) {
return errors.New("invalid `email`")
}
if !govalidator.IsByteLength(o.Name, 1, stdInputMaxLen) {
return errors.New("invalid length for `name`")
}
// TODO: PASSWORD HASHING.
res, err := app.Queries.UpdateUser.Exec(o.ID,
o.Email,
o.Name,
o.Password,
o.Type,
o.Status)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError,
fmt.Sprintf("Error updating user: %s", pqErrMsg(err)))
}
if n, _ := res.RowsAffected(); n == 0 {
return echo.NewHTTPError(http.StatusBadRequest, "User not found.")
}
return handleGetUsers(c)
}
// handleDeleteUser handles user deletion.
func handleDeleteUser(c echo.Context) error {
var (
id, _ = strconv.Atoi(c.Param("id"))
)
if id < 1 {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid ID.")
}
return c.JSON(http.StatusOK, okResp{true})
}