quite a few things

This commit is contained in:
m15o 2021-11-26 23:36:48 +01:00
parent e3238c54ad
commit 755e314a5f
24 changed files with 165 additions and 40 deletions

View file

@ -15,8 +15,11 @@ section {
row-gap: 1em;
}
.status-username {
a {
font-weight: bold;
}
.status-username {
margin-bottom: .5em;
}
@ -28,6 +31,26 @@ section {
margin-bottom: 1em;
}
dt {
font-weight: bold;
}
dd {
margin-bottom: 1em;
}
.profile-picture {
float: right;
}
nav {
margin-bottom: 1em;
}
h1, h2 {
margin-top: 0;
}
@media (min-width: 650px) {
.cols {
grid-template-columns: repeat(2, 1fr);

View file

@ -13,6 +13,8 @@ type User struct {
Hash []byte
Homepage string
About string
Style string
Picture string
CreatedAt time.Time
}

View file

@ -7,7 +7,7 @@ import (
"strconv"
)
const schemaVersion = 3
const schemaVersion = 6
func Migrate(db *sql.DB) {
var currentVersion int

View file

@ -32,4 +32,10 @@ create table statuses
add column homepage varchar(500) not null DEFAULT '',
add column about varchar(500) not null DEFAULT '';
`,
"schema_version_4": `alter table users
alter column about TYPE TEXT;`,
"schema_version_5": `alter table users
add column style TEXT not null DEFAULT '';`,
"schema_version_6": `alter table users
add column picture varchar(500) not null DEFAULT '';`,
}

View file

@ -0,0 +1,2 @@
alter table users
alter column about TYPE TEXT;

View file

@ -0,0 +1,2 @@
alter table users
add column style TEXT not null DEFAULT '';

View file

@ -0,0 +1,2 @@
alter table users
add column picture varchar(500) not null DEFAULT '';

View file

@ -70,27 +70,27 @@ func (s *Storage) StatusById(id int64) (model.Status, error) {
return status, err
}
func (s *Storage) StatusByUsername(user string, perPage int, page int64) ([]model.Status, error) {
func (s *Storage) StatusByUsername(user string, perPage int, page int64) ([]model.Status, bool, error) {
rows, err := s.db.Query(statusQueryBuilder{
where: `author = $1`,
limit: strconv.Itoa(perPage + 1),
offset: `$2`,
}.build(), user, page*int64(perPage))
if err != nil {
return nil, err
return nil, false, err
}
var statuses []model.Status
for rows.Next() {
post, err := s.populateStatus(rows)
if err != nil {
return statuses, err
return nil, false, err
}
statuses = append(statuses, post)
}
if len(statuses) > perPage {
return statuses[0:perPage], err
return statuses[0:perPage], true, err
}
return statuses, err
return statuses, false, err
}
func (s *Storage) Statuses(page int64, perPage int) ([]model.Status, bool, error) {

View file

@ -4,10 +4,10 @@ import (
"status/model"
)
const queryFindName = `SELECT name, hash, created_at, homepage, about FROM users WHERE name=lower($1);`
const queryFindName = `SELECT name, hash, created_at, homepage, about, style, picture FROM users WHERE name=lower($1);`
func (s *Storage) queryUser(q string, params ...interface{}) (user model.User, err error) {
err = s.db.QueryRow(q, params...).Scan(&user.Name, &user.Hash, &user.CreatedAt, &user.Homepage, &user.About)
err = s.db.QueryRow(q, params...).Scan(&user.Name, &user.Hash, &user.CreatedAt, &user.Homepage, &user.About, &user.Style, &user.Picture)
return
}
@ -77,11 +77,11 @@ func (s *Storage) DeleteUser(username string) error {
return err
}
func (s *Storage) UpdateSettings(username, homepage, about string) error {
stmt, err := s.db.Prepare(`UPDATE users SET homepage = $1, about = $2 WHERE name = $3;`)
func (s *Storage) UpdateSettings(username, homepage, about, style, picture string) error {
stmt, err := s.db.Prepare(`UPDATE users SET homepage = $1, about = $2, style = $3, picture = $4 WHERE name = $5;`)
if err != nil {
return err
}
_, err = stmt.Exec(homepage, about, username)
_, err = stmt.Exec(homepage, about, style, picture, username)
return err
}

View file

@ -16,7 +16,13 @@ var TplCommonMap = map[string]string{
</head>
<body>
<header>
<p>status </p>
<nav>
<a href="/">status </a>
{{ if .logged }}
<a href="/settings">settings</a> <a href="/users/{{ .logged }}">{{ .logged }}</a>(<a href="/logout">logout</a>)
{{ else }}
<a href="/register">register</a> <a href="/login">login</a>
{{ end }}</nav>
</header>
<main>
{{ template "content" . }}
@ -32,7 +38,7 @@ var TplCommonMap = map[string]string{
`,
"status": `{{ define "status" }}
<article class="status">
<div class="status-username"><a href="/users/{{ .User }}">{{ .User }}</a>'s status, updated {{ .TimeAgo }}</div>
<div class="status-username"><a href="/users/{{ .User }}">{{ .User }}</a>, {{ .TimeAgo }}</div>
<p class="status-content">{{ .Content }}</p>
</article>
{{ end }}`,

View file

@ -7,11 +7,15 @@ import (
type SettingsForm struct {
Homepage string
About string
Style string
Picture string
}
func NewSettingsForm(r *http.Request) *SettingsForm {
return &SettingsForm{
Homepage: r.FormValue("homepage"),
About: r.FormValue("about"),
Style: r.FormValue("style"),
Picture: r.FormValue("picture"),
}
}

View file

@ -84,7 +84,7 @@ Are you sure you you want to delete the following status?
{{ end }}
<form action="/status-save" method="post">
<div class="field">
<textarea name="content" placeholder="What's new?" required style="width: 100%; box-sizing: border-box; height: 100px;" autofocus></textarea>
<textarea name="content" maxlength="140" placeholder="What's new?" required style="width: 100%; box-sizing: border-box; height: 100px;" autofocus></textarea>
</div>
<input type="submit" value="Submit">
<h3>Update status with bookmarklet</h3>
@ -154,17 +154,31 @@ Are you sure you you want to delete the following status?
</div>
<div class="field">
<label for="about">about</label>
<textarea name="about" id="about">{{ .About }}</textarea>
<label for="picture">picture URL</label>
<input type="text" name="picture" id="picture" value="{{ .Picture }}" autocomplete="off"/>
</div>
<div class="field">
<label for="about">about</label>
<textarea name="about" id="about" rows="20">{{ .About }}</textarea>
</div>
<div class="field">
<label for="style">style</label>
<textarea name="style" id="style" rows="20">{{ .Style }}</textarea>
</div>
<input type="submit" value="Submit">
</form>
</section>
{{ end }}`,
"user": `{{ define "content" }}
"user": `{{ define "head" }}
<style>{{ .style }}</style>
{{ end }}
{{ define "content" }}
<div class="cols">
<section>
<img src="{{ .picture }}" width="100" class="profile-picture"/>
<h2>{{ .user }}</h2>
<dl>
<dt>Homepage</dt>
@ -178,6 +192,20 @@ Are you sure you you want to delete the following status?
{{ range .statuses }}
{{ template "status" . }}
{{ end }}
{{ if or .showMore (ne 0 .page) }}
<p>
{{ if ne 0 .page }}
{{ if eq 0 .prev_page }}
<a href="{{ .user }}">Newer statuses</a>
{{ else }}
<a href="{{ .user }}?page={{ .prev_page }}">Newer statuses</a>
{{ end }}
{{ end }}
{{ if .showMore }}
<a href="{{ .user }}?page={{ .next_page }}">Older statuses</a>
{{- end }}
</p>
{{ end }}
</section>
</div>
{{ end }}`,

View file

@ -11,7 +11,13 @@
</head>
<body>
<header>
<p>status ☕</p>
<nav>
<a href="/">status ☕</a>
{{ if .logged }}
<a href="/settings">settings</a> <a href="/users/{{ .logged }}">{{ .logged }}</a>(<a href="/logout">logout</a>)
{{ else }}
<a href="/register">register</a> <a href="/login">login</a>
{{ end }}</nav>
</header>
<main>
{{ template "content" . }}

View file

@ -1,6 +1,6 @@
{{ define "status" }}
<article class="status">
<div class="status-username"><a href="/users/{{ .User }}">{{ .User }}</a>'s status, updated {{ .TimeAgo }}</div>
<div class="status-username"><a href="/users/{{ .User }}">{{ .User }}</a>, {{ .TimeAgo }}</div>
<p class="status-content">{{ .Content }}</p>
</article>
{{ end }}

View file

@ -10,7 +10,7 @@
{{ end }}
<form action="/status-save" method="post">
<div class="field">
<textarea name="content" placeholder="What's new?" required style="width: 100%; box-sizing: border-box; height: 100px;" autofocus></textarea>
<textarea name="content" maxlength="140" placeholder="What's new?" required style="width: 100%; box-sizing: border-box; height: 100px;" autofocus></textarea>
</div>
<input type="submit" value="Submit">
<h3>Update status with bookmarklet</h3>

View file

@ -11,10 +11,19 @@
</div>
<div class="field">
<label for="about">about</label>
<textarea name="about" id="about">{{ .About }}</textarea>
<label for="picture">picture URL</label>
<input type="text" name="picture" id="picture" value="{{ .Picture }}" autocomplete="off"/>
</div>
<div class="field">
<label for="about">about</label>
<textarea name="about" id="about" rows="20">{{ .About }}</textarea>
</div>
<div class="field">
<label for="style">style</label>
<textarea name="style" id="style" rows="20">{{ .Style }}</textarea>
</div>
<input type="submit" value="Submit">
</form>
</section>

View file

@ -1,6 +1,11 @@
{{ define "head" }}
<style>{{ .style }}</style>
{{ end }}
{{ define "content" }}
<div class="cols">
<section>
<img src="{{ .picture }}" width="100" class="profile-picture"/>
<h2>{{ .user }}</h2>
<dl>
<dt>Homepage</dt>
@ -14,6 +19,20 @@
{{ range .statuses }}
{{ template "status" . }}
{{ end }}
{{ if or .showMore (ne 0 .page) }}
<p>
{{ if ne 0 .page }}
{{ if eq 0 .prev_page }}
<a href="{{ .user }}">Newer statuses</a>
{{ else }}
<a href="{{ .user }}?page={{ .prev_page }}">Newer statuses</a>
{{ end }}
{{ end }}
{{ if .showMore }}
<a href="{{ .user }}?page={{ .next_page }}">Older statuses</a>
{{- end }}
</p>
{{ end }}
</section>
</div>
{{ end }}

View file

@ -10,12 +10,13 @@ type Update struct {
}
func (h *Handler) showIndexView(w http.ResponseWriter, r *http.Request) {
user, _ := h.sess.Get(r)
statuses, err := h.storage.LatestStatuses()
if err != nil {
serverError(w, err)
return
}
session, err := h.sess.Store.Get(r, "ichi")
session, err := h.sess.Store.Get(r, "status")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@ -28,5 +29,5 @@ func (h *Handler) showIndexView(w http.ResponseWriter, r *http.Request) {
h.renderLayout(w, "index", map[string]interface{}{
"statuses": statuses,
"flash": flash,
}, "")
}, user)
}

View file

@ -13,7 +13,7 @@ func (h *Handler) showSettingsView(w http.ResponseWriter, r *http.Request) {
unauthorized(w)
return
}
session, err := h.sess.Store.Get(r, "ichi")
session, err := h.sess.Store.Get(r, "status")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@ -27,5 +27,7 @@ func (h *Handler) showSettingsView(w http.ResponseWriter, r *http.Request) {
"flash": flash,
"About": user.About,
"Homepage": user.Homepage,
"Style": user.Style,
"Picture": user.Picture,
}, username)
}

View file

@ -13,11 +13,11 @@ func (h *Handler) updateSettings(w http.ResponseWriter, r *http.Request) {
return
}
f := form.NewSettingsForm(r)
if err := h.storage.UpdateSettings(user, f.Homepage, f.About); err != nil {
if err := h.storage.UpdateSettings(user, f.Homepage, f.About, f.Style, f.Picture); err != nil {
serverError(w, err)
return
}
session, err := h.sess.Store.Get(r, "ichi")
session, err := h.sess.Store.Get(r, "status")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return

View file

@ -33,7 +33,7 @@ func (h *Handler) showEditStatusView(w http.ResponseWriter, r *http.Request) {
unauthorized(w)
return
}
session, err := h.sess.Store.Get(r, "ichi")
session, err := h.sess.Store.Get(r, "status")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return

View file

@ -29,7 +29,7 @@ func (h *Handler) saveStatus(w http.ResponseWriter, r *http.Request) {
return
}
if r.URL.Query().Get("silent") != "1" {
session, err := h.sess.Store.Get(r, "ichi")
session, err := h.sess.Store.Get(r, "status")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return

View file

@ -35,7 +35,7 @@ func (h *Handler) updateStatus(w http.ResponseWriter, r *http.Request) {
serverError(w, err)
return
}
session, err := h.sess.Store.Get(r, "ichi")
session, err := h.sess.Store.Get(r, "status")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return

View file

@ -9,30 +9,43 @@ import (
"golang.org/x/image/font"
"golang.org/x/image/font/gofont/goregular"
"golang.org/x/image/math/fixed"
"html/template"
"image"
"image/color"
"image/draw"
"strconv"
//"image/png"
"net/http"
)
func (h *Handler) showUserView(w http.ResponseWriter, r *http.Request) {
var page int64 = 0
if val, ok := r.URL.Query()["page"]; ok && len(val[0]) == 1 {
page, _ = strconv.ParseInt(val[0], 10, 64)
}
username := mux.Vars(r)["user"]
user, err := h.storage.UserByName(username)
if err != nil {
notFound(w)
return
}
statuses, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 20, 0)
statuses, showMore, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 20, page)
if err != nil {
serverError(w, err)
return
}
h.renderLayout(w, "user", map[string]interface{}{
"user": username,
"statuses": statuses,
"homepage": user.Homepage,
"about": user.About,
"user": username,
"statuses": statuses,
"homepage": user.Homepage,
"about": template.HTML(user.About),
"picture": user.Picture,
"style": template.CSS(user.Style),
"showMore": showMore,
"page": page,
"next_page": page + 1,
"prev_page": page - 1,
}, username)
}
@ -48,7 +61,7 @@ func (h *Handler) showUserStatusView(w http.ResponseWriter, r *http.Request) {
notFound(w)
return
}
statuses, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 1, 0)
statuses, _, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 1, 0)
if err != nil {
serverError(w, err)
return
@ -113,7 +126,7 @@ func (h *Handler) showUserStatusImageView(w http.ResponseWriter, r *http.Request
notFound(w)
return
}
statuses, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 1, 0)
statuses, _, err := h.storage.StatusByUsername(mux.Vars(r)["user"], 1, 0)
if err != nil {
serverError(w, err)
return
@ -137,11 +150,11 @@ func (h *Handler) showUserStatusImageView(w http.ResponseWriter, r *http.Request
}
width := 350
dc := gg.NewContext(width, 80)
dc := gg.NewContext(width, 84)
dc.SetHexColor(background)
dc.Clear()
dc.SetHexColor(foreground)
dc.DrawStringWrapped(text, 19, 16, 0, 0, float64(width-(2*16)), 1.5, gg.AlignLeft)
dc.DrawStringWrapped(text, 8, 4, 0, 0, float64(width-(2*16)), 1.5, gg.AlignLeft)
dc.EncodePNG(w)
//dc.SavePNG("out.png")