Merge branch 'develop' into rename-account-suspend

This commit is contained in:
Matt Baer 2020-02-09 11:14:51 -05:00
commit f70c1dfaa5
7 changed files with 49 additions and 11 deletions

View File

@ -1,7 +1,7 @@
// +build !sqlite,!wflib // +build !sqlite,!wflib
/* /*
* Copyright © 2019 A Bunch Tell LLC. * Copyright © 2019-2020 A Bunch Tell LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -28,3 +28,15 @@ func (db *datastore) isDuplicateKeyErr(err error) bool {
return false return false
} }
func (db *datastore) isIgnorableError(err error) bool {
if db.driverName == driverMySQL {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
return mysqlErr.Number == mySQLErrCollationMix
}
} else {
log.Error("isIgnorableError: failed check for unrecognized driver '%s'", db.driverName)
}
return false
}

View File

@ -48,3 +48,15 @@ func (db *datastore) isDuplicateKeyErr(err error) bool {
return false return false
} }
func (db *datastore) isIgnorableError(err error) bool {
if db.driverName == driverMySQL {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
return mysqlErr.Number == mySQLErrCollationMix
}
} else {
log.Error("isIgnorableError: failed check for unrecognized driver '%s'", db.driverName)
}
return false
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018 A Bunch Tell LLC. * Copyright © 2018-2020 A Bunch Tell LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -38,6 +38,7 @@ import (
const ( const (
mySQLErrDuplicateKey = 1062 mySQLErrDuplicateKey = 1062
mySQLErrCollationMix = 1267
driverMySQL = "mysql" driverMySQL = "mysql"
driverSQLite = "sqlite3" driverSQLite = "sqlite3"
@ -2328,7 +2329,7 @@ func (db *datastore) GetUserInvite(id string) (*Invite, error) {
var i Invite var i Invite
err := db.QueryRow("SELECT id, max_uses, created, expires, inactive FROM userinvites WHERE id = ?", id).Scan(&i.ID, &i.MaxUses, &i.Created, &i.Expires, &i.Inactive) err := db.QueryRow("SELECT id, max_uses, created, expires, inactive FROM userinvites WHERE id = ?", id).Scan(&i.ID, &i.MaxUses, &i.Created, &i.Expires, &i.Inactive)
switch { switch {
case err == sql.ErrNoRows: case err == sql.ErrNoRows, db.isIgnorableError(err):
return nil, impart.HTTPError{http.StatusNotFound, "Invite doesn't exist."} return nil, impart.HTTPError{http.StatusNotFound, "Invite doesn't exist."}
case err != nil: case err != nil:
log.Error("Failed selecting invite: %v", err) log.Error("Failed selecting invite: %v", err)

View File

@ -57,11 +57,18 @@ func handleViewUserInvites(app *App, u *User, w http.ResponseWriter, r *http.Req
p := struct { p := struct {
*UserPage *UserPage
Invites *[]Invite Invites *[]Invite
Suspended bool
}{ }{
UserPage: NewUserPage(app, r, u, "Invite People", f), UserPage: NewUserPage(app, r, u, "Invite People", f),
} }
var err error var err error
p.Suspended, err = app.db.IsUserSuspended(u.ID)
if err != nil {
log.Error("view invites: %v", err)
}
p.Invites, err = app.db.GetUserInvites(u.ID) p.Invites, err = app.db.GetUserInvites(u.ID)
if err != nil { if err != nil {
return err return err

View File

@ -164,7 +164,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, UserLevelOptional)).Methods("GET") write.HandleFunc("/invite/{code:[a-zA-Z0-9]+}", 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())
@ -178,8 +178,8 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
} }
// All the existing stuff // All the existing stuff
write.HandleFunc(draftEditPrefix+"/{action}/edit", handler.Web(handleViewPad, UserLevelOptional)).Methods("GET") write.HandleFunc(draftEditPrefix+"/{action}/edit", handler.Web(handleViewPad, UserLevelUser)).Methods("GET")
write.HandleFunc(draftEditPrefix+"/{action}/meta", handler.Web(handleViewMeta, UserLevelOptional)).Methods("GET") write.HandleFunc(draftEditPrefix+"/{action}/meta", handler.Web(handleViewMeta, UserLevelUser)).Methods("GET")
// Collections // Collections
if apper.App().cfg.App.SingleUser { if apper.App().cfg.App.SingleUser {
RouteCollections(handler, write.PathPrefix("/").Subrouter()) RouteCollections(handler, write.PathPrefix("/").Subrouter())

View File

@ -42,13 +42,16 @@
</select> </select>
</label> </label>
<script> <script>
// timezone offset in seconds
const tzOffsetSec = new Date().getTimezoneOffset() * 60;
const fileInput = document.getElementById('fileInput'); const fileInput = document.getElementById('fileInput');
const fileDates = document.getElementById('fileDates'); const fileDates = document.getElementById('fileDates');
fileInput.addEventListener('change', (e) => { fileInput.addEventListener('change', (e) => {
const files = e.target.files; const files = e.target.files;
let dateMap = {}; let dateMap = {};
for (let file of files) { for (let file of files) {
dateMap[file.name] = file.lastModified / 1000; // convert from milliseconds to seconds and adjust for tz
dateMap[file.name] = Math.round(file.lastModified / 1000) + tzOffsetSec;
} }
fileDates.value = JSON.stringify(dateMap); fileDates.value = JSON.stringify(dateMap);
}) })

View File

@ -20,6 +20,9 @@ table td {
</style> </style>
<div class="snug content-container"> <div class="snug content-container">
{{if .Suspended}}
{{template "user-suspended"}}
{{end}}
<h1>Invite people</h1> <h1>Invite people</h1>
<p>Invite others to join <em>{{.SiteName}}</em> by generating and sharing invite links below.</p> <p>Invite others to join <em>{{.SiteName}}</em> by generating and sharing invite links below.</p>
@ -27,7 +30,7 @@ table td {
<div class="row"> <div class="row">
<div class="half"> <div class="half">
<label for="uses">Maximum number of uses:</label> <label for="uses">Maximum number of uses:</label>
<select id="uses" name="uses"> <select id="uses" name="uses" {{if .Suspended}}disabled{{end}}>
<option value="0">No limit</option> <option value="0">No limit</option>
<option value="1">1 use</option> <option value="1">1 use</option>
<option value="5">5 uses</option> <option value="5">5 uses</option>
@ -39,7 +42,7 @@ table td {
</div> </div>
<div class="half"> <div class="half">
<label for="expires">Expire after:</label> <label for="expires">Expire after:</label>
<select id="expires" name="expires"> <select id="expires" name="expires" {{if .Suspended}}disabled{{end}}>
<option value="0">Never</option> <option value="0">Never</option>
<option value="30">30 minutes</option> <option value="30">30 minutes</option>
<option value="60">1 hour</option> <option value="60">1 hour</option>
@ -52,7 +55,7 @@ table td {
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<input type="submit" value="Generate" /> <input type="submit" value="Generate" {{if .Suspended}}disabled title="You cannot generate invites while your account is silenced."{{end}} />
</div> </div>
</form> </form>