Merge pull request #183 from writeas/T690-invite-instructions

add user invite instructions

Resolves T690
This commit is contained in:
Matt Baer 2019-09-23 10:52:26 -04:00 committed by GitHub
commit 3759f16ed3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 86 additions and 15 deletions

View file

@ -2257,6 +2257,19 @@ func (db *datastore) GetUserInvite(id string) (*Invite, error) {
return &i, nil return &i, nil
} }
// IsUsersInvite returns true if the user with ID created the invite with code
// and an error other than sql no rows, if any. Will return false in the event
// of an error.
func (db *datastore) IsUsersInvite(code string, userID int64) (bool, error) {
var id string
err := db.QueryRow("SELECT id FROM userinvites WHERE id = ? AND owner_id = ?", code, userID).Scan(&id)
if err != nil && err != sql.ErrNoRows {
log.Error("Failed selecting invite: %v", err)
return false, err
}
return id != "", nil
}
func (db *datastore) GetUsersInvitedCount(id string) int64 { func (db *datastore) GetUsersInvitedCount(id string) int64 {
var count int64 var count int64
err := db.QueryRow("SELECT COUNT(*) FROM usersinvited WHERE invite_id = ?", id).Scan(&count) err := db.QueryRow("SELECT COUNT(*) FROM usersinvited WHERE invite_id = ?", id).Scan(&count)

View file

@ -12,15 +12,16 @@ package writefreely
import ( import (
"database/sql" "database/sql"
"html/template"
"net/http"
"strconv"
"time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/writeas/impart" "github.com/writeas/impart"
"github.com/writeas/nerds/store" "github.com/writeas/nerds/store"
"github.com/writeas/web-core/log" "github.com/writeas/web-core/log"
"github.com/writeas/writefreely/page" "github.com/writeas/writefreely/page"
"html/template"
"net/http"
"strconv"
"time"
) )
type Invite struct { type Invite struct {
@ -114,6 +115,36 @@ func handleViewInvite(app *App, w http.ResponseWriter, r *http.Request) error {
return err return err
} }
expired := i.Expired()
if !expired && i.MaxUses.Valid && i.MaxUses.Int64 > 0 {
// Invite has a max-use number, so check if we're past that limit
i.uses = app.db.GetUsersInvitedCount(inviteCode)
expired = i.uses >= i.MaxUses.Int64
}
if u := getUserSession(app, r); u != nil {
// check if invite belongs to another user
// error can be ignored as not important in this case
if ownInvite, _ := app.db.IsUsersInvite(inviteCode, u.ID); !ownInvite {
addSessionFlash(app, w, r, "You're already registered and logged in.", nil)
// show homepage
return impart.HTTPError{http.StatusFound, "/me/settings"}
}
// show invite instructions
p := struct {
*UserPage
Invite *Invite
Expired bool
}{
UserPage: NewUserPage(app, r, u, "Invite to "+app.cfg.App.SiteName, nil),
Invite: i,
Expired: expired,
}
showUserPage(w, "invite-help", p)
return nil
}
p := struct { p := struct {
page.StaticPage page.StaticPage
Error string Error string
@ -124,16 +155,10 @@ func handleViewInvite(app *App, w http.ResponseWriter, r *http.Request) error {
Invite: inviteCode, Invite: inviteCode,
} }
if i.Expired() { if expired {
p.Error = "This invite link has expired." p.Error = "This invite link has expired."
} }
if i.MaxUses.Valid && i.MaxUses.Int64 > 0 {
if c := app.db.GetUsersInvitedCount(inviteCode); c >= i.MaxUses.Int64 {
p.Error = "This invite link has expired."
}
}
// Get error messages // Get error messages
session, err := app.sessionStore.Get(r, cookieName) session, err := app.sessionStore.Get(r, cookieName)
if err != nil { if err != nil {

View file

@ -11,13 +11,14 @@
package writefreely package writefreely
import ( import (
"net/http"
"path/filepath"
"strings"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/writeas/go-webfinger" "github.com/writeas/go-webfinger"
"github.com/writeas/web-core/log" "github.com/writeas/web-core/log"
"github.com/writefreely/go-nodeinfo" "github.com/writefreely/go-nodeinfo"
"net/http"
"path/filepath"
"strings"
) )
// InitStaticRoutes adds routes for serving static files. // InitStaticRoutes adds routes for serving static files.
@ -151,7 +152,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
// Handle special pages first // Handle special pages first
write.HandleFunc("/login", handler.Web(viewLogin, UserLevelNoneRequired)) write.HandleFunc("/login", handler.Web(viewLogin, UserLevelNoneRequired))
write.HandleFunc("/signup", handler.Web(handleViewLanding, UserLevelNoneRequired)) write.HandleFunc("/signup", handler.Web(handleViewLanding, UserLevelNoneRequired))
write.HandleFunc("/invite/{code}", handler.Web(handleViewInvite, UserLevelNoneRequired)).Methods("GET") write.HandleFunc("/invite/{code}", handler.Web(handleViewInvite, UserLevelOptional)).Methods("GET")
// TODO: show a reader-specific 404 page if the function is disabled // TODO: show a reader-specific 404 page if the function is disabled
write.HandleFunc("/read", handler.Web(viewLocalTimeline, UserLevelReader)) write.HandleFunc("/read", handler.Web(viewLocalTimeline, UserLevelReader))
RouteRead(handler, UserLevelReader, write.PathPrefix("/read").Subrouter()) RouteRead(handler, UserLevelReader, write.PathPrefix("/read").Subrouter())

View file

@ -0,0 +1,32 @@
{{define "invite-help"}}
{{template "header" .}}
<style>
.copy-link {
width: 100%;
margin: 1rem 0;
text-align: center;
font-size: 1.2em;
color: #555;
}
</style>
<div class="snug content-container">
<h1>Invite to {{.SiteName}}</h1>
{{ if .Expired }}
<p style="font-style: italic">This invite link is expired.</p>
{{ else }}
<p>Copy the link below and send it to anyone that you want to join <em>{{ .SiteName }}</em>. You could paste it into an email, instant message, text message, or write it down on paper. Anyone who navigates to this special page will be able to create an account.</p>
<input class="copy-link" type="text" name="invite-url" value="{{$.Host}}/invite/{{.Invite.ID}}" onfocus="if (this.select) this.select(); else this.setSelectionRange(0, this.value.length);" readonly />
<p>
{{ if gt .Invite.MaxUses.Int64 0 }}
{{if eq .Invite.MaxUses.Int64 1}}Only <strong>one</strong> user{{else}}Up to <strong>{{.Invite.MaxUses.Int64}}</strong> users{{end}} can sign up with this link.
{{if gt .Invite.Uses 0}}So far, <strong>{{.Invite.Uses}}</strong> {{pluralize "person has" "people have" .Invite.Uses}} used it.{{end}}
{{if .Invite.Expires}}It expires on <strong>{{.Invite.ExpiresFriendly}}</strong>.{{end}}
{{ else }}
It can be used as many times as you like{{if .Invite.Expires}} before <strong>{{.Invite.ExpiresFriendly}}</strong>, when it expires{{end}}.
{{ end }}
</p>
{{ end }}
</div>
{{template "footer" .}}
{{end}}