lots of stuff

This commit is contained in:
m15o 2021-12-22 10:21:14 +01:00
parent 79d73d12f5
commit 4a9e788f29
10 changed files with 326 additions and 103 deletions

BIN
assets/button.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -4,26 +4,28 @@ import "os"
type (
Config struct {
DatabaseURL string
SessionKey string
Env string
CertFile string
KeyFile string
AssetsDir string
EmailUsername string
EmailPassword string
EmailHost string
EmailHostAddr string
DatabaseURL string
SessionKey string
Env string
CertFile string
KeyFile string
AssetsDir string
EmailUsername string
EmailPassword string
EmailHost string
EmailHostAddr string
ManualRegistration bool
}
)
func New() *Config {
return &Config{
DatabaseURL: os.Getenv("DATABASE_URL"),
SessionKey: os.Getenv("SESSION_KEY"),
Env: os.Getenv("ENV"),
CertFile: os.Getenv("CERT_FILE"),
KeyFile: os.Getenv("CERT_KEY_FILE"),
AssetsDir: os.Getenv("ASSETS_DIR"),
DatabaseURL: os.Getenv("DATABASE_URL"),
SessionKey: os.Getenv("SESSION_KEY"),
Env: os.Getenv("ENV"),
CertFile: os.Getenv("CERT_FILE"),
KeyFile: os.Getenv("CERT_KEY_FILE"),
AssetsDir: os.Getenv("ASSETS_DIR"),
ManualRegistration: len(os.Getenv("MANUAL_REGISTRATION")) > 0,
}
}

View file

@ -41,12 +41,12 @@ func (s *Storage) CreateUser(user model.User) error {
if err != nil {
return err
}
insertUser := `INSERT INTO users (name, hash, email, signup_email, signup_msg) VALUES (lower($1), $2, $3, $4, $5)`
insertUser := `INSERT INTO users (name, hash, email, signup_email, signup_msg, active) VALUES (lower($1), $2, $3, $4, $5, $6)`
statement, err := s.db.Prepare(insertUser)
if err != nil {
return err
}
_, err = statement.Exec(user.Name, hash, user.Email, user.SignupEmail, user.SignupMsg)
_, err = statement.Exec(user.Name, hash, user.Email, user.SignupEmail, user.SignupMsg, user.Active)
return err
}

View file

@ -24,6 +24,9 @@ func (f *RegisterForm) Validate() error {
if len(f.Username) < 3 {
return errors.New("username needs to be at least 3 characters")
}
if len(f.Username) > 20 {
return errors.New("username should be 20 characters or less")
}
match, _ := regexp.MatchString("^[a-z0-9-_]+$", f.Username)
if !match {
return errors.New("only lowercase letters and digits are accepted for username")

View file

@ -82,8 +82,8 @@ func New(cfg *config.Config, sess *session.Session, data *storage.Storage) (http
router.HandleFunc("/users/{user}/status", h.showUserStatusView).Methods(http.MethodGet)
router.HandleFunc("/users/{user}/status.json", h.showUserStatusJSONView).Methods(http.MethodGet)
router.HandleFunc("/users/{user}/status.png", h.showUserStatusImageView).Methods(http.MethodGet)
router.HandleFunc("/users/{user}/badge.png", h.showUserStatusBadgeView).Methods(http.MethodGet)
//router.HandleFunc("/users/{user}/status.png", h.showUserStatusImageViewEmoji).Methods(http.MethodGet)
//router.HandleFunc("/users/{user}/badge.png", h.showUserStatusBadgeView).Methods(http.MethodGet)
router.PathPrefix("/assets/").Handler(
http.StripPrefix("/assets/",
http.FileServer(

View file

@ -165,6 +165,7 @@ var TplMap = map[string]string{
<p>status.cafe is a place to share your current status.</p>
<p><a href="/register">Register now!</a></p>
{{ end }}
<p><img src="/assets/button.png"/></p>
</section>
<section>
<h2>Status stream</h2>
@ -257,47 +258,37 @@ var TplMap = map[string]string{
{{ end }}`,
"register": `{{ define "content" }}
<section>
<div class="cols">
<div>
<h1>Register</h1>
<div class="info">
<p>Registrations are manually approved to prevent spam and keep this little cafe a cool place to hang out.</p>
<p>You should receive a confirmation within a few hours at the email you have provided. Make sure to enter a valid address!</p>
</div>
<h1>Register</h1>
{{ if .form.Error }}
<p>{{ .form.Error }}</p>
{{ end }}
<form action="/register" method="post" class="auth-form">
<div class="field">
<label for="name">Username</label>
<input type="text" id="name" name="name" autocomplete="off" required maxlength="20" autofocus/>
</div>
<div>
{{ if .form.Error }}
<p>{{ .form.Error }}</p>
{{ end }}
<form action="/register" method="post" class="auth-form">
<div class="field">
<label for="name">Username</label>
<input type="text" id="name" name="name" autocomplete="off" required autofocus/>
</div>
<div class="field">
<label for="email">Email</label>
<input type="email" id="email" name="email" autocomplete="off" required autofocus/>
</div>
<div class="field">
<label for="show-email">Show e-mail</label>
<input type="checkbox" name="show-email" value="1" id="show-email" style="width: inherit;">
</div>
<div class="field">
<label for="password">Password</label>
<input type="password" id="password" name="password" required/>
</div>
<div class="field">
<label for="password-confirm">Confirm password</label>
<input type="password" id="password-confirm" name="password-confirm" required/>
</div>
<div class="field">
<label for="answer">How did you discover status.cafe?</label>
<textarea id="answer" name="answer" required></textarea>
</div>
<input type="submit" value="Submit">
</form>
<div class="field">
<label for="email">Email</label>
<input type="email" id="email" name="email" autocomplete="off" required/>
</div>
</div>
<div class="field">
<label for="show-email">Show e-mail</label>
<input type="checkbox" name="show-email" value="1" id="show-email" style="width: inherit;">
</div>
<div class="field">
<label for="password">Password</label>
<input type="password" id="password" name="password" required/>
</div>
<div class="field">
<label for="password-confirm">Confirm password</label>
<input type="password" id="password-confirm" name="password-confirm" required/>
</div>
<div class="field">
<label for="answer">How did you discover status.cafe?</label>
<textarea id="answer" name="answer" required></textarea>
</div>
<input type="submit" value="Submit">
</form>
</section>
{{ end }}`,
"register-success": `{{ define "content" }}

View file

@ -17,6 +17,7 @@
<p>status.cafe is a place to share your current status.</p>
<p><a href="/register">Register now!</a></p>
{{ end }}
<p><img src="/assets/button.png"/></p>
</section>
<section>
<h2>Status stream</h2>

View file

@ -1,45 +1,35 @@
{{ define "content" }}
<section>
<div class="cols">
<div>
<h1>Register</h1>
<div class="info">
<p>Registrations are manually approved to prevent spam and keep this little cafe a cool place to hang out.</p>
<p>You should receive a confirmation within a few hours at the email you have provided. Make sure to enter a valid address!</p>
</div>
<h1>Register</h1>
{{ if .form.Error }}
<p>{{ .form.Error }}</p>
{{ end }}
<form action="/register" method="post" class="auth-form">
<div class="field">
<label for="name">Username</label>
<input type="text" id="name" name="name" autocomplete="off" required maxlength="20" autofocus/>
</div>
<div>
{{ if .form.Error }}
<p>{{ .form.Error }}</p>
{{ end }}
<form action="/register" method="post" class="auth-form">
<div class="field">
<label for="name">Username</label>
<input type="text" id="name" name="name" autocomplete="off" required autofocus/>
</div>
<div class="field">
<label for="email">Email</label>
<input type="email" id="email" name="email" autocomplete="off" required autofocus/>
</div>
<div class="field">
<label for="show-email">Show e-mail</label>
<input type="checkbox" name="show-email" value="1" id="show-email" style="width: inherit;">
</div>
<div class="field">
<label for="password">Password</label>
<input type="password" id="password" name="password" required/>
</div>
<div class="field">
<label for="password-confirm">Confirm password</label>
<input type="password" id="password-confirm" name="password-confirm" required/>
</div>
<div class="field">
<label for="answer">How did you discover status.cafe?</label>
<textarea id="answer" name="answer" required></textarea>
</div>
<input type="submit" value="Submit">
</form>
<div class="field">
<label for="email">Email</label>
<input type="email" id="email" name="email" autocomplete="off" required/>
</div>
</div>
<div class="field">
<label for="show-email">Show e-mail</label>
<input type="checkbox" name="show-email" value="1" id="show-email" style="width: inherit;">
</div>
<div class="field">
<label for="password">Password</label>
<input type="password" id="password" name="password" required/>
</div>
<div class="field">
<label for="password-confirm">Confirm password</label>
<input type="password" id="password-confirm" name="password-confirm" required/>
</div>
<div class="field">
<label for="answer">How did you discover status.cafe?</label>
<textarea id="answer" name="answer" required></textarea>
</div>
<input type="submit" value="Submit">
</form>
</section>
{{ end }}

View file

@ -109,13 +109,24 @@ func (h *Handler) register(w http.ResponseWriter, r *http.Request) {
showError(errors.New("username already exists"))
return
}
if !h.cfg.ManualRegistration {
user.Active = true
}
if err := h.storage.CreateUser(user); err != nil {
showError(err)
return
}
h.renderLayout(w, "register-success", map[string]interface{}{
"name": user.Name,
"email": user.SignupEmail,
}, "")
if h.cfg.ManualRegistration {
h.renderLayout(w, "register-success", map[string]interface{}{
"name": user.Name,
"email": user.SignupEmail,
}, "")
} else {
if err := h.sess.Save(r, w, r.FormValue("name")); err != nil {
serverError(w, err)
return
}
http.Redirect(w, r, "/", http.StatusFound)
}
}
}

View file

@ -1,28 +1,207 @@
package handler
import (
"bytes"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"github.com/fogleman/gg"
"github.com/golang/freetype"
"github.com/golang/freetype/truetype"
"github.com/gorilla/mux"
"golang.org/x/image/draw"
"golang.org/x/image/font"
_ "golang.org/x/image/font/gofont/gobold"
"golang.org/x/image/font/gofont/goregular"
"golang.org/x/image/math/fixed"
"html/template"
"image"
"image/color"
"image/draw"
"image/jpeg"
"io"
"os"
"status/model"
"strconv"
"strings"
"time"
//"image/png"
"net/http"
)
const (
exitSuccess = 0
exitFailure = 1
)
const (
imagePath = "/home/m15o/Downloads/noto-emoji-main/png/128"
aliasFile = "/home/m15o/Downloads/noto-emoji-main/emoji_aliases.txt"
fontSize = 64 // point
imageWidth = 640 // pixel
imageHeight = 120 // pixel
textTopMargin = 80 // fixed.I
lineHeight = 70 // fixed.I
)
// emoji alias
var alias = map[string]string{}
//
//func init() {
// fp, err := os.Open(aliasFile)
// if err != nil {
// panic(err)
// }
//
// s := bufio.NewScanner(fp)
// for s.Scan() {
// line := s.Text()
//
// /*
// alias file format:
// # 'fe0f' is not in these sequences
// 1f3c3;1f3c3_200d_2642 # RUNNER -> man running
// */
//
// if strings.HasPrefix(line, "#") {
// continue
// }
//
// s := strings.Split(line, ";")
// if len(s) != 2 {
// continue
// }
//
// i := strings.Index(s[1], " ")
// if i < 0 {
// continue
// }
//
// from := s[0]
// to := s[1][:i]
//
// alias[from] = to
// }
//
// if err := s.Err(); err != nil {
// fmt.Fprintln(os.Stderr, "reading alias file:", err)
// }
//
//}
func exist(path string) bool {
_, err := os.Stat(path)
return err == nil
}
// GetPath if unicode codepoint is included in list and file exists return file path
func getPath(r rune) (string, error) {
if r > 47 && r < 58 {
return "", errors.New("numeric char")
}
name := fmt.Sprintf("%.4x", r)
var path string
if v, ok := alias[name]; ok {
path = fmt.Sprintf("%s/emoji_u%s.png", imagePath, v)
} else {
path = fmt.Sprintf("%s/emoji_u%s.png", imagePath, name)
}
if !exist(path) {
return "", fmt.Errorf("%s does NOT exist", path)
}
return path, nil
}
func loadEmoji(r rune, size int) (image.Image, bool) {
var img image.Image
path, err := getPath(r)
if err != nil {
//fmt.Fprintln(os.Stderr, err)
return img, false
}
fp, err := os.Open(path)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return img, false
}
defer fp.Close()
img, _, err = image.Decode(fp)
if err != nil {
fmt.Fprintln(os.Stderr, err)
return img, false
}
rect := image.Rect(0, 0, size, size)
dst := image.NewRGBA(rect)
draw.ApproxBiLinear.Scale(dst, rect, img, img.Bounds(), draw.Over, nil)
return dst, true
}
func renderLine(img draw.Image, dr *font.Drawer, s string) {
size := dr.Face.Metrics().Ascent.Floor() + dr.Face.Metrics().Descent.Floor()
for _, r := range s {
emoji, ok := loadEmoji(r, size)
if ok {
// Drawer.Dot is glyph baseline of next glyph
// get left/top coordinates for draw.Draw().
p := image.Pt(dr.Dot.X.Floor(), dr.Dot.Y.Floor()-dr.Face.Metrics().Ascent.Floor())
rect := image.Rect(0, 0, size, size).Add(p)
// draw emoji and ascend baseline
draw.Draw(img, rect, emoji, image.ZP, draw.Over)
dr.Dot.X += fixed.I(size)
} else {
// fallback: use normal glyph
dr.DrawString(string(r))
}
}
}
func renderText(img draw.Image, face font.Face, text string) error {
dr := &font.Drawer{
Dst: img,
Src: image.White,
Face: face,
Dot: fixed.Point26_6{},
}
for i, s := range strings.Split(text, "\n") {
dr.Dot.X = (fixed.I(imageWidth) - dr.MeasureString(s)) / 2
dr.Dot.Y = fixed.I(textTopMargin + i*lineHeight)
renderLine(img, dr, s)
}
return nil
}
func outputJPEG(img image.Image, w io.Writer) error {
buf := new(bytes.Buffer)
err := jpeg.Encode(buf, img, nil)
if err != nil {
return err
}
_, err = io.Copy(w, buf)
if err != nil {
return err
}
return nil
}
func (h *Handler) showManageView(w http.ResponseWriter, r *http.Request) {
logged, err := h.sess.Get(r)
if err != nil {
@ -220,6 +399,52 @@ func (h *Handler) showUserStatusBadgeView(w http.ResponseWriter, r *http.Request
dc.EncodePNG(w)
}
//
//func (h *Handler) showUserStatusImageViewEmoji(w http.ResponseWriter, r *http.Request) {
// user := mux.Vars(r)["user"]
// if !h.storage.UserExists(user) {
// notFound(w)
// return
// }
// statuses, _, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 1, 0)
// if err != nil {
// serverError(w, err)
// return
// }
//
// var username string
// var face string
// if len(statuses) > 0 {
// username = statuses[0].User
// face = statuses[0].Face
// }
//
// img := image.NewRGBA(image.Rect(0, 0, imageWidth, imageHeight))
//
// ft, err := truetype.Parse(gobold.TTF)
// if err != nil {
// fmt.Fprintln(os.Stderr, err)
// os.Exit(exitFailure)
// }
//
// opt := truetype.Options{
// Size: fontSize,
// DPI: 0,
// Hinting: 0,
// GlyphCacheEntries: 0,
// SubPixelsX: 0,
// SubPixelsY: 0,
// }
//
// renderText(img, truetype.NewFace(ft, &opt), fmt.Sprintf("%s %s", username, face))
//
// err = outputJPEG(img, w)
// if err != nil {
// fmt.Fprintln(os.Stderr, err)
// os.Exit(exitFailure)
// }
//}
func (h *Handler) showUserStatusImageView(w http.ResponseWriter, r *http.Request) {
user := mux.Vars(r)["user"]
if !h.storage.UserExists(user) {