Remove stub user features and handlers.
This commit is contained in:
parent
c952b7a2e8
commit
fe9102120c
|
@ -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)
|
||||
|
|
98
install.go
98
install.go
|
@ -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)
|
||||
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)
|
||||
}
|
||||
|
||||
if !emRegex.Match(email) {
|
||||
logger.Println("Please enter a valid e-mail")
|
||||
email = []byte{}
|
||||
}
|
||||
}
|
||||
|
||||
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"))
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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"`
|
||||
|
|
17
schema.sql
17
schema.sql
|
@ -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
138
users.go
|
@ -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})
|
||||
}
|
Loading…
Reference in New Issue