feat: groups
This commit is contained in:
parent
04dc7e9376
commit
e86fed2f00
|
|
@ -97,6 +97,7 @@ func (auth *AuthMiddleware) RegisterPost(w http.ResponseWriter, r *http.Request)
|
|||
|
||||
_, err = db.CreateUser(username, hashedPasswordBytes)
|
||||
if err != nil {
|
||||
log.Println("Registration error:", err)
|
||||
props.GeneralError = "Something went wrong. Error code: Ozai"
|
||||
auth.RedirectWithFlash(w, r, "/register", "register_props", &props)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
package context
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"lishwist/db"
|
||||
"lishwist/error"
|
||||
"lishwist/templates"
|
||||
)
|
||||
|
||||
type GroupProps struct {
|
||||
Name string
|
||||
Members []db.User
|
||||
CurrentUsername string
|
||||
}
|
||||
|
||||
func (ctx *Context) GroupPage(w http.ResponseWriter, r *http.Request) {
|
||||
user := ctx.Auth.ExpectUser(r)
|
||||
groupReference := r.PathValue("groupReference")
|
||||
group, err := user.GetGroupByReference(groupReference)
|
||||
if err != nil {
|
||||
error.Page(w, "An error occurred while fetching this group :(", http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
if group == nil {
|
||||
error.Page(w, "Group not found. (It might be because you're not a member)", http.StatusNotFound, nil)
|
||||
return
|
||||
}
|
||||
peers, err := user.GetPeers(group.Id)
|
||||
if err != nil {
|
||||
error.Page(w, "An error occurred while fetching this group :(", http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
p := GroupProps{
|
||||
Name: group.Name,
|
||||
Members: peers,
|
||||
CurrentUsername: user.Name,
|
||||
}
|
||||
templates.Execute(w, "group_page.gotmpl", p)
|
||||
}
|
||||
|
||||
func (ctx *Context) PublicGroupPage(w http.ResponseWriter, r *http.Request) {
|
||||
groupReference := r.PathValue("groupReference")
|
||||
group, err := db.GetGroupByReference(groupReference)
|
||||
if err != nil {
|
||||
error.Page(w, "An error occurred while fetching this group :(", http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
members, err := group.GetMembers()
|
||||
if err != nil {
|
||||
error.Page(w, "An error occurred while fetching this group :(", http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
p := GroupProps{
|
||||
Name: group.Name,
|
||||
Members: members,
|
||||
}
|
||||
templates.Execute(w, "public_group_page.gotmpl", p)
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ type HomeProps struct {
|
|||
Todo []db.Gift
|
||||
Reference string
|
||||
HostUrl string
|
||||
Groups []db.Group
|
||||
}
|
||||
|
||||
func (ctx *Context) Home(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -29,7 +30,12 @@ func (ctx *Context) Home(w http.ResponseWriter, r *http.Request) {
|
|||
error.Page(w, "An error occurred while fetching your wishlist :(", http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
p := HomeProps{Username: user.Name, Gifts: gifts, Todo: todo, Reference: user.Reference, HostUrl: env.HostUrl.String()}
|
||||
groups, err := user.GetGroups()
|
||||
if err != nil {
|
||||
error.Page(w, "An error occurred while fetching your wishlist :(", http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
p := HomeProps{Username: user.Name, Gifts: gifts, Todo: todo, Reference: user.Reference, HostUrl: env.HostUrl.String(), Groups: groups}
|
||||
templates.Execute(w, "home.gotmpl", p)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
package db
|
||||
|
||||
import "database/sql"
|
||||
|
||||
type Group struct {
|
||||
Id string
|
||||
Name string
|
||||
Reference string
|
||||
}
|
||||
|
||||
func queryForGroup(query string, args ...any) (*Group, error) {
|
||||
var id string
|
||||
var name string
|
||||
var reference string
|
||||
err := database.QueryRow(query, args...).Scan(&id, &name, &reference)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
group := Group{
|
||||
Id: id,
|
||||
Name: name,
|
||||
Reference: reference,
|
||||
}
|
||||
return &group, nil
|
||||
}
|
||||
|
||||
func GetGroupByReference(reference string) (*Group, error) {
|
||||
stmt := "SELECT [group].id, [group].name, [group].reference FROM [group] WHERE [group].reference = ?"
|
||||
return queryForGroup(stmt, reference)
|
||||
}
|
||||
|
||||
func (g *Group) GetMembers() ([]User, error) {
|
||||
stmt := "SELECT user.id, user.name, user.reference FROM user JOIN group_member ON group_member.user_id = user.id JOIN [group] ON [group].id = group_member.group_id WHERE [group].id = ?"
|
||||
rows, err := database.Query(stmt, g.Id)
|
||||
users := []User{}
|
||||
if err != nil {
|
||||
return users, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var id string
|
||||
var name string
|
||||
var reference string
|
||||
err := rows.Scan(&id, &name, &reference)
|
||||
if err != nil {
|
||||
return users, err
|
||||
}
|
||||
users = append(users, User{
|
||||
Id: id,
|
||||
Name: name,
|
||||
Reference: reference,
|
||||
})
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return users, err
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
13
db/init.sql
13
db/init.sql
|
|
@ -19,4 +19,17 @@ CREATE TABLE IF NOT EXISTS "gift" (
|
|||
FOREIGN KEY("creator_id") REFERENCES "user"("id"),
|
||||
FOREIGN KEY("claimant_id") REFERENCES "user"("id")
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "group" (
|
||||
"id" INTEGER NOT NULL UNIQUE,
|
||||
"name" TEXT NOT NULL UNIQUE,
|
||||
"reference" TEXT NOT NULL UNIQUE,
|
||||
PRIMARY KEY("id" AUTOINCREMENT)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "group_member" (
|
||||
"group_id" INTEGER NOT NULL,
|
||||
"user_id" INTEGER NOT NULL,
|
||||
UNIQUE("user_id","group_id"),
|
||||
FOREIGN KEY("group_id") REFERENCES "group"("id"),
|
||||
FOREIGN KEY("user_id") REFERENCES "user"("id")
|
||||
);
|
||||
COMMIT;
|
||||
|
|
|
|||
63
db/user.go
63
db/user.go
|
|
@ -365,3 +365,66 @@ func (u *User) AddGiftToUser(otherUserReference string, giftName string) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) GetGroups() ([]Group, error) {
|
||||
stmt := "SELECT [group].id, [group].name, [group].reference FROM [group] JOIN group_member ON group_member.group_id = [group].id JOIN user ON user.id = group_member.user_id WHERE user.id = ?"
|
||||
rows, err := database.Query(stmt, u.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
groups := []Group{}
|
||||
for rows.Next() {
|
||||
var id string
|
||||
var name string
|
||||
var reference string
|
||||
err := rows.Scan(&id, &name, &reference)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
groups = append(groups, Group{
|
||||
Id: id,
|
||||
Name: name,
|
||||
Reference: reference,
|
||||
})
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func (u *User) GetPeers(groupId string) ([]User, error) {
|
||||
stmt := "SELECT user.id, user.name, user.reference FROM user JOIN group_member ON group_member.user_id = user.id JOIN [group] ON [group].id = group_member.group_id WHERE [group].id = ? AND user.id != ?"
|
||||
rows, err := database.Query(stmt, groupId, u.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
users := []User{}
|
||||
for rows.Next() {
|
||||
var id string
|
||||
var name string
|
||||
var reference string
|
||||
err := rows.Scan(&id, &name, &reference)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
users = append(users, User{
|
||||
Id: id,
|
||||
Name: name,
|
||||
Reference: reference,
|
||||
})
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (u *User) GetGroupByReference(reference string) (*Group, error) {
|
||||
stmt := "SELECT [group].id, [group].name, [group].reference FROM [group] JOIN group_member ON [group].id == group_member.group_id WHERE [group].reference = ? AND group_member.user_id = ?"
|
||||
return queryForGroup(stmt, reference, u.Id)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,9 @@ type pageProps struct {
|
|||
}
|
||||
|
||||
func Page(w http.ResponseWriter, publicMessage string, status int, err error) {
|
||||
if err != nil {
|
||||
log.Printf("%s --- %s\n", publicMessage, err)
|
||||
}
|
||||
templates.Execute(w, "error_page.gotmpl", pageProps{publicMessage})
|
||||
http.Error(w, "", http.StatusInternalServerError)
|
||||
}
|
||||
|
|
|
|||
2
main.go
2
main.go
|
|
@ -34,11 +34,13 @@ func main() {
|
|||
publicMux.HandleFunc("GET /", authMiddleware.Login)
|
||||
publicMux.HandleFunc("POST /", authMiddleware.LoginPost)
|
||||
publicMux.HandleFunc("GET /list/{userReference}", ctx.PublicWishlist)
|
||||
publicMux.HandleFunc("GET /group/{groupReference}", ctx.PublicGroupPage)
|
||||
|
||||
protectedMux.HandleFunc("GET /{$}", ctx.Home)
|
||||
protectedMux.HandleFunc("POST /{$}", ctx.HomePost)
|
||||
protectedMux.HandleFunc("GET /list/{userReference}", ctx.ForeignWishlist)
|
||||
protectedMux.HandleFunc("POST /list/{userReference}", ctx.ForeignWishlistPost)
|
||||
protectedMux.HandleFunc("GET /group/{groupReference}", ctx.GroupPage)
|
||||
protectedMux.HandleFunc("POST /logout", authMiddleware.LogoutPost)
|
||||
|
||||
http.Handle("/", authMiddleware)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
{{define "navbar"}}
|
||||
<nav>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="flex-grow-1"></div>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<div class="dropdown">
|
||||
<button class="nav-link dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
Logged in as '{{.CurrentUsername}}'
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li>
|
||||
<form class="d-contents" method="post" action="/logout">
|
||||
<button class="dropdown-item" type="submit">Logout</button>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
{{end}}
|
||||
|
||||
{{define "body"}}
|
||||
<div class="overflow-y-scroll flex-grow-1">
|
||||
<div class="container py-5">
|
||||
<section class="card">
|
||||
<div class="card-body">
|
||||
<h2><em>{{.Name}}</em> group members</h2>
|
||||
{{with .Members}}
|
||||
<ul class="list-group">
|
||||
{{range .}}
|
||||
<li class="list-group-item">
|
||||
<a href="/list/{{.Reference}}">{{.Name}}</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<p>There's nobody else in this group.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<section class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h2>Your todo list</h2>
|
||||
{{with .Todo}}
|
||||
|
|
@ -91,6 +91,23 @@
|
|||
{{end}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<div class="card-body">
|
||||
<h2>Your groups</h2>
|
||||
{{with .Groups}}
|
||||
<ul class="list-group">
|
||||
{{range .}}
|
||||
<li class="list-group-item">
|
||||
<a href="/group/{{.Reference}}">{{.Name}}</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<p>You don't belong to any groups</p>
|
||||
{{end}}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
{{define "navbar"}}
|
||||
<nav>
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
{{end}}
|
||||
|
||||
{{define "login_prompt"}}
|
||||
<a href="/">Login</a> or <a href="/register">register</a>
|
||||
{{end}}
|
||||
|
||||
{{define "body"}}
|
||||
<div class="overflow-y-scroll flex-grow-1">
|
||||
<div class="container py-5">
|
||||
<section class="card">
|
||||
<div class="card-body">
|
||||
<h2><em>{{.Name}}</em> group members</h2>
|
||||
<p>{{template "login_prompt"}} to see your groups</p>
|
||||
{{with .Members}}
|
||||
<ul class="list-group">
|
||||
{{range .}}
|
||||
<li class="list-group-item">
|
||||
{{.Name}}
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<p>There's nobody else in this group.</p>
|
||||
{{end}}
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
|
@ -32,6 +32,8 @@ func loadTemplates() map[string]*template.Template {
|
|||
foreignTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/foreign_wishlist.gotmpl"))
|
||||
publicWishlistTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/public_foreign_wishlist.gotmpl"))
|
||||
errorTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/error_page.gotmpl"))
|
||||
groupTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/group_page.gotmpl"))
|
||||
publicGroupTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/public_group_page.gotmpl"))
|
||||
return map[string]*template.Template{
|
||||
"home.gotmpl": homeTmpl,
|
||||
"login.gotmpl": loginTmpl,
|
||||
|
|
@ -39,5 +41,7 @@ func loadTemplates() map[string]*template.Template {
|
|||
"foreign_wishlist.gotmpl": foreignTmpl,
|
||||
"public_foreign_wishlist.gotmpl": publicWishlistTmpl,
|
||||
"error_page.gotmpl": errorTmpl,
|
||||
"group_page.gotmpl": groupTmpl,
|
||||
"public_group_page.gotmpl": publicGroupTmpl,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue