quite a few things
This commit is contained in:
parent
e3238c54ad
commit
755e314a5f
24 changed files with 165 additions and 40 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ type User struct {
|
|||
Hash []byte
|
||||
Homepage string
|
||||
About string
|
||||
Style string
|
||||
Picture string
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"strconv"
|
||||
)
|
||||
|
||||
const schemaVersion = 3
|
||||
const schemaVersion = 6
|
||||
|
||||
func Migrate(db *sql.DB) {
|
||||
var currentVersion int
|
||||
|
|
|
|||
|
|
@ -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 '';`,
|
||||
}
|
||||
|
|
|
|||
2
storage/sql/schema_version_4.sql
Normal file
2
storage/sql/schema_version_4.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
alter table users
|
||||
alter column about TYPE TEXT;
|
||||
2
storage/sql/schema_version_5.sql
Normal file
2
storage/sql/schema_version_5.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
alter table users
|
||||
add column style TEXT not null DEFAULT '';
|
||||
2
storage/sql/schema_version_6.sql
Normal file
2
storage/sql/schema_version_6.sql
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
alter table users
|
||||
add column picture varchar(500) not null DEFAULT '';
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }}`,
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }}`,
|
||||
|
|
|
|||
|
|
@ -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" . }}
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 }}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue