package user
import (
"errors"
"log"
"net/http"
"net/url"
"strconv"
"git.sr.ht/~evanj/cms/internal/c"
"git.sr.ht/~evanj/cms/internal/m/space"
"git.sr.ht/~evanj/cms/internal/m/tier"
"git.sr.ht/~evanj/cms/internal/m/user"
"git.sr.ht/~evanj/cms/internal/v"
)
var (
indexHTML = v.MustParse("html/index.html")
ErrNoSignup = errors.New("signups are forbidden at this time")
ErrNoTier = errors.New("invalid tier")
)
type User struct {
*c.Controller
log *log.Logger
db dber
signupEnabled bool
stripe Striper
}
type dber interface {
UserNew(username, password, verifyPassword string) (user.User, error)
UserGet(username, password string) (user.User, error)
UserGetFromToken(token string) (user.User, error)
UserSetEmail(u user.User, email string) (user.User, error)
SpacesPerUser(user user.User, before int) (space.SpaceList, error)
}
type Striper interface {
StartCheckout(user user.User, t tier.Tier) (string, string, error)
CancelSubscription(user user.User) error
}
func New(c *c.Controller, log *log.Logger, db dber, signupEnabled bool, s Striper) *User {
return &User{
c,
log,
db,
signupEnabled,
s,
}
}
func (l *User) logout(w http.ResponseWriter, r *http.Request) {
l.SetCookieUser(w, r, nil)
l.Redirect(w, r, "/")
}
func (l *User) login(w http.ResponseWriter, r *http.Request) {
username := r.FormValue("username")
password := r.FormValue("password")
user, err := l.db.UserGet(username, password)
if err != nil {
l.Error2(w, r, http.StatusBadRequest, err)
return
}
l.SetCookieUser(w, r, user)
l.Redirect(w, r, "/")
}
func (l *User) signup(w http.ResponseWriter, r *http.Request) {
if !l.signupEnabled {
l.Error2(w, r, http.StatusForbidden, ErrNoSignup)
return
}
username := r.FormValue("username")
password := r.FormValue("password")
verify := r.FormValue("verify")
t, ok := tier.ByName(r.FormValue("tier"))
if !ok {
l.Error2(w, r, http.StatusBadRequest, ErrNoTier)
return
}
user, err := l.db.UserNew(username, password, verify)
if err != nil {
l.Error2(w, r, http.StatusBadRequest, err)
return
}
l.SetCookieUser(w, r, user)
if t.Is(tier.Free) {
l.Redirect(w, r, "/")
return
}
stripeCheckoutSessionID, stripePK, err := l.stripe.StartCheckout(user, t)
if err != nil {
l.Error2(w, r, http.StatusInternalServerError, err)
return
}
form := url.Values{
"StripeCheckoutSessionID": {stripeCheckoutSessionID},
"StripePK": {stripePK},
}
l.Redirect(w, r, "/page/stripe?"+form.Encode())
}
func (l *User) home(w http.ResponseWriter, r *http.Request) {
user, err := l.GetCookieUser(w, r)
if err != nil {
l.HTML(w, r, indexHTML, map[string]interface{}{
"Tiers": tier.Tiers,
})
return
}
// Don't care about the error value here. When error occurs before is zero
// value.
before, _ := strconv.Atoi(r.URL.Query().Get("before"))
spaces, err := l.db.SpacesPerUser(user, before)
if err != nil {
l.Error(w, r, http.StatusInternalServerError, "failed to find spaces for user")
return
}
l.HTML(w, r, indexHTML, map[string]interface{}{
"Tiers": tier.Tiers,
"User": user,
"Spaces": spaces,
})
}
func (l *User) updateEmail(w http.ResponseWriter, r *http.Request) {
u, err := l.GetCookieUser(w, r)
if err != nil {
l.Error2(w, r, http.StatusInternalServerError, c.ErrNoLogin)
return
}
if _, err := l.db.UserSetEmail(u, r.FormValue("email")); err != nil {
l.Error2(w, r, http.StatusInternalServerError, err)
return
}
l.Redirect(w, r, "/page/billing")
}
func (l *User) updateBilling(w http.ResponseWriter, r *http.Request) {
u, err := l.GetCookieUser(w, r)
if err != nil {
l.Error2(w, r, http.StatusInternalServerError, c.ErrNoLogin)
return
}
t, ok := tier.ByName(r.FormValue("tier"))
if !ok || !(t.Is(tier.Business) || t.Is(tier.Enterprise)) {
l.Error2(w, r, http.StatusBadRequest, ErrNoTier)
return
}
if t.Is(tier.Free) {
l.Redirect(w, r, "/")
return
}
stripeCheckoutSessionID, stripePK, err := l.stripe.StartCheckout(u, t)
if err != nil {
l.Error2(w, r, http.StatusInternalServerError, err)
return
}
form := url.Values{
"StripeCheckoutSessionID": {stripeCheckoutSessionID},
"StripePK": {stripePK},
}
l.Redirect(w, r, "/page/stripe?"+form.Encode())
}
func (l *User) cancelBilling(w http.ResponseWriter, r *http.Request) {
u, err := l.GetCookieUser(w, r)
if err != nil {
l.Error2(w, r, http.StatusInternalServerError, c.ErrNoLogin)
return
}
if err := l.stripe.CancelSubscription(u); err != nil {
l.Error2(w, r, http.StatusInternalServerError, err)
return
}
l.Redirect(w, r, "/page/billing")
}
func (l *User) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/":
l.home(w, r)
return
case "/user/login":
l.login(w, r)
return
case "/user/logout":
l.logout(w, r)
return
case "/user/signup":
l.signup(w, r)
return
case "/user/update/email":
l.updateEmail(w, r)
return
case "/user/update/billing":
l.updateBilling(w, r)
return
case "/user/cancel/billing":
l.cancelBilling(w, r)
return
}
http.NotFound(w, r)
}