Added users. And forgot I was using git :/
This commit is contained in:
parent
e96bbdb0dd
commit
c9fe62e7fd
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
test.db
|
||||
user_dbs/
|
||||
todo-web
|
||||
|
12
api/api.go
12
api/api.go
@ -2,16 +2,18 @@ package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/Cameron-Reed1/todo-web/db"
|
||||
// "github.com/Cameron-Reed1/todo-web/db"
|
||||
"github.com/Cameron-Reed1/todo-web/types"
|
||||
)
|
||||
|
||||
func GetAll(w http.ResponseWriter, r *http.Request) {
|
||||
todos, err := db.GetAllTodos()
|
||||
// todos, err := db.GetAllTodos()
|
||||
todos, err := []types.Todo(nil), errors.New("Broken right now :/")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("{\"error\":\"Failed to get items\"}"))
|
||||
@ -44,7 +46,8 @@ func GetTodo(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
todo, err := db.GetTodo(id)
|
||||
// todo, err := db.GetTodo(id)
|
||||
todo, err := types.Todo{Id: int64(id)}, errors.New("Broken right now :/")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("{\"error\":\"No item for id\"}"))
|
||||
@ -79,7 +82,8 @@ func AddTodo(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = db.AddTodo(&todo)
|
||||
// err = db.AddTodo(&todo)
|
||||
err = errors.New("Broken right now :/")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("{\"error\":\"Failed to add item\"}"))
|
||||
|
133
auth/auth.go
Normal file
133
auth/auth.go
Normal file
@ -0,0 +1,133 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
|
||||
"github.com/Cameron-Reed1/todo-web/types"
|
||||
"golang.org/x/crypto/argon2"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
)
|
||||
|
||||
|
||||
var algorithm argon2idHasher = argon2idHasher{
|
||||
hashLen: 64,
|
||||
saltLen: 32,
|
||||
time: 6,
|
||||
memory: 24 * 1024,
|
||||
threads: 1,
|
||||
}
|
||||
|
||||
|
||||
func Hash(password, salt []byte) (*HashSalt, error) {
|
||||
return algorithm.Hash(password, salt)
|
||||
}
|
||||
|
||||
func Validate(hash, salt, password []byte) bool {
|
||||
return algorithm.Validate(hash, salt, password)
|
||||
}
|
||||
|
||||
func CreateSessionFor(user_id int64) (*types.Session, error) {
|
||||
buf := make([]byte, 32)
|
||||
_, err := rand.Read(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.Session{ SessionId: base64.StdEncoding.EncodeToString(buf), UserId: user_id }, nil
|
||||
}
|
||||
|
||||
|
||||
func generateSalt(length uint) ([]byte, error) {
|
||||
salt := make([]byte, length)
|
||||
|
||||
_, err := rand.Read(salt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return salt, nil
|
||||
}
|
||||
|
||||
type HashSalt struct {
|
||||
Hash []byte
|
||||
Salt []byte
|
||||
}
|
||||
|
||||
type hashAlgo interface {
|
||||
Hash(password, salt []byte) ([]byte, error)
|
||||
Validate(hash, salt, password []byte) bool
|
||||
}
|
||||
|
||||
type scryptHasher struct {
|
||||
hashLen int
|
||||
saltLen uint
|
||||
cost int
|
||||
blockSize int
|
||||
parallelism int
|
||||
}
|
||||
|
||||
type argon2idHasher struct {
|
||||
hashLen uint32
|
||||
saltLen uint
|
||||
time uint32
|
||||
memory uint32
|
||||
threads uint8
|
||||
}
|
||||
|
||||
func (s *scryptHasher) Hash(password, salt []byte) (*HashSalt, error) {
|
||||
var err error
|
||||
|
||||
if salt == nil || len(salt) == 0 {
|
||||
salt, err = generateSalt(s.saltLen)
|
||||
if err != nil {
|
||||
fmt.Println("\x1b[31mError: Failed to generate a password salt\x1b[0m")
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
hash, err := scrypt.Key(password, salt, s.cost, s.blockSize, s.parallelism, s.hashLen)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &HashSalt{Hash: hash, Salt: salt}, nil
|
||||
}
|
||||
|
||||
func (s *scryptHasher) Validate(hash, salt, password []byte) bool {
|
||||
hashed_password, err := s.Hash(password, salt)
|
||||
if err != nil {
|
||||
fmt.Println("\x1b[31mError: Failed to generate a password hash\x1b[0m")
|
||||
return false
|
||||
}
|
||||
|
||||
return bytes.Equal(hash, hashed_password.Hash)
|
||||
}
|
||||
|
||||
func (a *argon2idHasher) Hash(password, salt []byte) (*HashSalt, error) {
|
||||
var err error
|
||||
|
||||
if salt == nil || len(salt) == 0 {
|
||||
salt, err = generateSalt(a.saltLen)
|
||||
if err != nil {
|
||||
fmt.Println("\x1b[31mError: Failed to generate a password salt\x1b[0m")
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
hash := argon2.IDKey(password, salt, a.time, a.memory, a.threads, a.hashLen)
|
||||
|
||||
return &HashSalt{Hash: hash, Salt: salt}, nil
|
||||
}
|
||||
|
||||
func (s *argon2idHasher) Validate(hash, salt, password []byte) bool {
|
||||
hashed_password, err := s.Hash(password, salt)
|
||||
if err != nil {
|
||||
fmt.Println("\x1b[31mError: Failed to generate a password hash\x1b[0m")
|
||||
return false
|
||||
}
|
||||
|
||||
return bytes.Equal(hash, hashed_password.Hash)
|
||||
}
|
210
db/db.go
210
db/db.go
@ -2,220 +2,10 @@ package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
||||
"github.com/Cameron-Reed1/todo-web/types"
|
||||
)
|
||||
|
||||
var db *sql.DB
|
||||
|
||||
func Open(path string) {
|
||||
if db != nil {
|
||||
log.Fatal("Cannot init DB twice!")
|
||||
}
|
||||
|
||||
var err error
|
||||
db, err = sql.Open("sqlite3", path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
query := `
|
||||
CREATE TABLE IF NOT EXISTS items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
start INTEGER,
|
||||
due INTEGER,
|
||||
text TEXT NOT NULL,
|
||||
completed INTEGER NOT NULL
|
||||
);`
|
||||
|
||||
_, err = db.Exec(query)
|
||||
}
|
||||
|
||||
func AddTodo(todo *types.Todo) error {
|
||||
res, err := db.Exec("INSERT INTO items(start, due, text, completed) values(?, ?, ?, 0)", toNullInt64(todo.Start), toNullInt64(todo.Due), todo.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
todo.Id, err = res.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetTodo(id int) (types.Todo, error) {
|
||||
var todo types.Todo
|
||||
var start sql.NullInt64
|
||||
var due sql.NullInt64
|
||||
|
||||
row := db.QueryRow("SELECT * FROM items WHERE id=?", id)
|
||||
err := row.Scan(&todo.Id, &start, &due, &todo.Text, &todo.Completed)
|
||||
|
||||
todo.Start = fromNullInt64(start)
|
||||
todo.Due = fromNullInt64(due)
|
||||
|
||||
return todo, err
|
||||
}
|
||||
|
||||
func GetAllTodos() ([]types.Todo, error) {
|
||||
var todos []types.Todo
|
||||
|
||||
rows, err := db.Query("SELECT * FROM items")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var todo types.Todo
|
||||
var start sql.NullInt64
|
||||
var due sql.NullInt64
|
||||
|
||||
err = rows.Scan(&todo.Id, &start, &due, &todo.Text, &todo.Completed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
todo.Start = fromNullInt64(start)
|
||||
todo.Due = fromNullInt64(due)
|
||||
|
||||
todos = append(todos, todo)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return todos, nil
|
||||
}
|
||||
|
||||
func GetOverdueTodos() ([]types.Todo, error) {
|
||||
var todos []types.Todo
|
||||
|
||||
rows, err := db.Query("SELECT * FROM items WHERE due < ? AND due IS NOT NULL ORDER BY completed, due", time.Now().Unix())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var todo types.Todo
|
||||
var start sql.NullInt64
|
||||
var due sql.NullInt64
|
||||
|
||||
err = rows.Scan(&todo.Id, &start, &due, &todo.Text, &todo.Completed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
todo.Start = fromNullInt64(start)
|
||||
todo.Due = fromNullInt64(due)
|
||||
|
||||
todos = append(todos, todo)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return todos, nil
|
||||
}
|
||||
|
||||
func GetTodayTodos() ([]types.Todo, error) {
|
||||
var todos []types.Todo
|
||||
|
||||
now := time.Now()
|
||||
year, month, day := now.Date()
|
||||
today := time.Date(year, month, day, 0, 0, 0, 0, time.Local)
|
||||
rows, err := db.Query("SELECT * FROM items WHERE (start <= ? OR start IS NULL) AND (due >= ? OR due IS NULL) ORDER BY completed, due NULLS LAST", today.Unix(), now.Unix())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var todo types.Todo
|
||||
var start sql.NullInt64
|
||||
var due sql.NullInt64
|
||||
|
||||
err = rows.Scan(&todo.Id, &start, &due, &todo.Text, &todo.Completed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
todo.Start = fromNullInt64(start)
|
||||
todo.Due = fromNullInt64(due)
|
||||
|
||||
todos = append(todos, todo)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return todos, nil
|
||||
}
|
||||
|
||||
func GetUpcomingTodos() ([]types.Todo, error) {
|
||||
var todos []types.Todo
|
||||
|
||||
year, month, day := time.Now().Date()
|
||||
today := time.Date(year, month, day, 0, 0, 0, 0, time.Local)
|
||||
rows, err := db.Query("SELECT * FROM items WHERE start > ? ORDER BY completed, start", today.Unix())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var todo types.Todo
|
||||
var start sql.NullInt64
|
||||
var due sql.NullInt64
|
||||
|
||||
err = rows.Scan(&todo.Id, &start, &due, &todo.Text, &todo.Completed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
todo.Start = fromNullInt64(start)
|
||||
todo.Due = fromNullInt64(due)
|
||||
|
||||
todos = append(todos, todo)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return todos, nil
|
||||
}
|
||||
|
||||
func UpdateTodo(newValues types.Todo) error {
|
||||
_, err := db.Exec("UPDATE items SET start=?, due=?, text=? WHERE id=?", toNullInt64(newValues.Start), toNullInt64(newValues.Due), newValues.Text, newValues.Id)
|
||||
return err;
|
||||
}
|
||||
|
||||
func SetCompleted(id int, completed bool) error {
|
||||
_, err := db.Exec("UPDATE items SET completed=? WHERE id=?", completed, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteTodo(id int) error {
|
||||
_, err := db.Exec("DELETE FROM items WHERE id=?", id)
|
||||
return err
|
||||
}
|
||||
|
||||
func Close() {
|
||||
db.Close()
|
||||
}
|
||||
|
||||
func toNullInt64(num int64) sql.NullInt64 {
|
||||
if num == 0 {
|
||||
return sql.NullInt64{Int64: 0, Valid: false}
|
||||
|
133
db/main_db.go
Normal file
133
db/main_db.go
Normal file
@ -0,0 +1,133 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"log"
|
||||
|
||||
"github.com/Cameron-Reed1/todo-web/auth"
|
||||
"github.com/Cameron-Reed1/todo-web/types"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
|
||||
var main_db *sql.DB
|
||||
|
||||
|
||||
func OpenMainDB(path string) {
|
||||
if main_db != nil {
|
||||
log.Fatal("Cannot open main DB twice!")
|
||||
}
|
||||
|
||||
var err error
|
||||
main_db, err = sql.Open("sqlite3", path)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = main_db.Ping()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
query := `
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
password_salt TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
sessionId TEXT NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);`
|
||||
|
||||
_, err = main_db.Exec(query)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func CloseMainDB() {
|
||||
main_db.Close()
|
||||
}
|
||||
|
||||
func CreateUser(username string, password_hash, password_salt []byte) (int64, error) {
|
||||
hex_hash := hex.EncodeToString(password_hash)
|
||||
hex_salt := hex.EncodeToString(password_salt)
|
||||
|
||||
res, err := main_db.Exec("INSERT INTO users(username, password_hash, password_salt) values(?, ?, ?)", username, hex_hash, hex_salt)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return res.LastInsertId()
|
||||
}
|
||||
|
||||
func GetUserPassHash(username string) (int64, *auth.HashSalt, error) {
|
||||
hashSalt := auth.HashSalt{}
|
||||
var user_id int64
|
||||
var hex_hash string
|
||||
var hex_salt string
|
||||
|
||||
row := main_db.QueryRow("SELECT id, password_hash, password_salt FROM users WHERE username=?", username)
|
||||
err := row.Scan(&user_id, &hex_hash, &hex_salt)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
hashSalt.Hash, err = hex.DecodeString(hex_hash)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
hashSalt.Salt, err = hex.DecodeString(hex_salt)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
|
||||
return user_id, &hashSalt, nil
|
||||
}
|
||||
|
||||
func DeleteUser(username string) error {
|
||||
_, err := main_db.Exec("DELETE FROM users WHERE username=?", username)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
func AddSession(session *types.Session) error {
|
||||
// fmt.Printf("New session: %s, %d\n", session.SessionId, session.UserId)
|
||||
_, err := main_db.Exec("INSERT INTO sessions(sessionId, user_id) values(?, ?)", session.SessionId, session.UserId)
|
||||
// fmt.Printf("Err: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetUserFromSession(sessionId string) (string, error) {
|
||||
var username string
|
||||
|
||||
row := main_db.QueryRow("SELECT username FROM sessions INNER JOIN users ON sessions.user_id = users.id WHERE sessionId=?", sessionId)
|
||||
err := row.Scan(&username)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return username, nil
|
||||
}
|
||||
|
||||
func GetSession(sessionId string) (*types.Session, error) {
|
||||
var session types.Session
|
||||
|
||||
row := main_db.QueryRow("SELECT user_id FROM sessions WHERE sessionId=?", sessionId)
|
||||
session.SessionId = sessionId
|
||||
err := row.Scan(&session.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &session, nil
|
||||
}
|
||||
|
||||
func DeleteSession(sessionId string) error {
|
||||
_, err := main_db.Exec("DELETE FROM sessions WHERE sessionId=?", sessionId)
|
||||
return err
|
||||
}
|
233
db/user_db.go
Normal file
233
db/user_db.go
Normal file
@ -0,0 +1,233 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
||||
"github.com/Cameron-Reed1/todo-web/types"
|
||||
)
|
||||
|
||||
var userDbDir string
|
||||
|
||||
type UserDB struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
func SetUserDBDir(dir string) error {
|
||||
os.MkdirAll(dir, 0700)
|
||||
userDbDir = dir
|
||||
return nil
|
||||
}
|
||||
|
||||
func OpenUserDB(username string) (*UserDB, error) {
|
||||
if strings.Contains(username, ".") || strings.Contains(username, "/") {
|
||||
return nil, errors.New("Invalid username")
|
||||
}
|
||||
|
||||
path := path.Join(userDbDir, username + ".db")
|
||||
|
||||
db, err := sql.Open("sqlite3", path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := `
|
||||
CREATE TABLE IF NOT EXISTS items (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
start INTEGER,
|
||||
due INTEGER,
|
||||
text TEXT NOT NULL,
|
||||
completed INTEGER NOT NULL
|
||||
);`
|
||||
|
||||
_, err = db.Exec(query)
|
||||
|
||||
return &UserDB{DB: db}, err
|
||||
}
|
||||
|
||||
func (user_db *UserDB) Close() error {
|
||||
return user_db.DB.Close()
|
||||
}
|
||||
|
||||
func (user_db *UserDB) AddTodo(todo *types.Todo) error {
|
||||
res, err := user_db.DB.Exec("INSERT INTO items(start, due, text, completed) values(?, ?, ?, 0)", toNullInt64(todo.Start), toNullInt64(todo.Due), todo.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
todo.Id, err = res.LastInsertId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (user_db *UserDB) GetTodo(id int) (types.Todo, error) {
|
||||
var todo types.Todo
|
||||
var start sql.NullInt64
|
||||
var due sql.NullInt64
|
||||
|
||||
row := user_db.DB.QueryRow("SELECT * FROM items WHERE id=?", id)
|
||||
err := row.Scan(&todo.Id, &start, &due, &todo.Text, &todo.Completed)
|
||||
|
||||
todo.Start = fromNullInt64(start)
|
||||
todo.Due = fromNullInt64(due)
|
||||
|
||||
return todo, err
|
||||
}
|
||||
|
||||
func (user_db *UserDB) GetAllTodos() ([]types.Todo, error) {
|
||||
var todos []types.Todo
|
||||
|
||||
rows, err := user_db.DB.Query("SELECT * FROM items")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var todo types.Todo
|
||||
var start sql.NullInt64
|
||||
var due sql.NullInt64
|
||||
|
||||
err = rows.Scan(&todo.Id, &start, &due, &todo.Text, &todo.Completed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
todo.Start = fromNullInt64(start)
|
||||
todo.Due = fromNullInt64(due)
|
||||
|
||||
todos = append(todos, todo)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return todos, nil
|
||||
}
|
||||
|
||||
func (user_db *UserDB) GetOverdueTodos() ([]types.Todo, error) {
|
||||
var todos []types.Todo
|
||||
|
||||
rows, err := user_db.DB.Query("SELECT * FROM items WHERE due < ? AND due IS NOT NULL ORDER BY completed, due", time.Now().Unix())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var todo types.Todo
|
||||
var start sql.NullInt64
|
||||
var due sql.NullInt64
|
||||
|
||||
err = rows.Scan(&todo.Id, &start, &due, &todo.Text, &todo.Completed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
todo.Start = fromNullInt64(start)
|
||||
todo.Due = fromNullInt64(due)
|
||||
|
||||
todos = append(todos, todo)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return todos, nil
|
||||
}
|
||||
|
||||
func (user_db *UserDB) GetTodayTodos() ([]types.Todo, error) {
|
||||
var todos []types.Todo
|
||||
|
||||
now := time.Now()
|
||||
year, month, day := now.Date()
|
||||
today := time.Date(year, month, day, 0, 0, 0, 0, time.Local)
|
||||
rows, err := user_db.DB.Query("SELECT * FROM items WHERE (start <= ? OR start IS NULL) AND (due >= ? OR due IS NULL) ORDER BY completed, due NULLS LAST", today.Unix(), now.Unix())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var todo types.Todo
|
||||
var start sql.NullInt64
|
||||
var due sql.NullInt64
|
||||
|
||||
err = rows.Scan(&todo.Id, &start, &due, &todo.Text, &todo.Completed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
todo.Start = fromNullInt64(start)
|
||||
todo.Due = fromNullInt64(due)
|
||||
|
||||
todos = append(todos, todo)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return todos, nil
|
||||
}
|
||||
|
||||
func (user_db *UserDB) GetUpcomingTodos() ([]types.Todo, error) {
|
||||
var todos []types.Todo
|
||||
|
||||
year, month, day := time.Now().Date()
|
||||
today := time.Date(year, month, day, 0, 0, 0, 0, time.Local)
|
||||
rows, err := user_db.DB.Query("SELECT * FROM items WHERE start > ? ORDER BY completed, start", today.Unix())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
var todo types.Todo
|
||||
var start sql.NullInt64
|
||||
var due sql.NullInt64
|
||||
|
||||
err = rows.Scan(&todo.Id, &start, &due, &todo.Text, &todo.Completed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
todo.Start = fromNullInt64(start)
|
||||
todo.Due = fromNullInt64(due)
|
||||
|
||||
todos = append(todos, todo)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return todos, nil
|
||||
}
|
||||
|
||||
func (user_db *UserDB) UpdateTodo(newValues types.Todo) error {
|
||||
_, err := user_db.DB.Exec("UPDATE items SET start=?, due=?, text=? WHERE id=?", toNullInt64(newValues.Start), toNullInt64(newValues.Due), newValues.Text, newValues.Id)
|
||||
return err;
|
||||
}
|
||||
|
||||
func (user_db *UserDB) SetCompleted(id int, completed bool) error {
|
||||
_, err := user_db.DB.Exec("UPDATE items SET completed=? WHERE id=?", completed, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (user_db *UserDB) DeleteTodo(id int) error {
|
||||
_, err := user_db.DB.Exec("DELETE FROM items WHERE id=?", id)
|
||||
return err
|
||||
}
|
8
go.mod
8
go.mod
@ -2,6 +2,10 @@ module github.com/Cameron-Reed1/todo-web
|
||||
|
||||
go 1.22.6
|
||||
|
||||
require github.com/mattn/go-sqlite3 v1.14.22
|
||||
require (
|
||||
github.com/a-h/templ v0.2.747
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
golang.org/x/crypto v0.27.0
|
||||
)
|
||||
|
||||
require github.com/a-h/templ v0.2.747 // indirect
|
||||
require golang.org/x/sys v0.25.0 // indirect
|
||||
|
6
go.sum
6
go.sum
@ -1,4 +1,10 @@
|
||||
github.com/a-h/templ v0.2.747 h1:D0dQ2lxC3W7Dxl6fxQ/1zZHBQslSkTSvl5FxP/CfdKg=
|
||||
github.com/a-h/templ v0.2.747/go.mod h1:69ObQIbrcuwPCU32ohNaWce3Cb7qM5GMiqN1K+2yop4=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
|
23
main.go
23
main.go
@ -12,32 +12,26 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
db_path := flag.String("db", "./test.db", "Path to the sqlite3 database")
|
||||
db_path := flag.String("db", "./main.db", "Path to the main sqlite3 database")
|
||||
user_db_dir := flag.String("user-dbs", "./user_dbs", "Path to the directory containing per-user sqlite3 databases")
|
||||
bind_port := flag.Int("p", 8080, "Port to bind to")
|
||||
bind_addr := flag.String("a", "0.0.0.0", "Address to bind to")
|
||||
static_dir := flag.String("static", "./static", "Path to static files")
|
||||
noFront := flag.Bool("no-frontend", false, "Disable the frontend endpoints")
|
||||
a := false; noBack := &a // flag.Bool("no-backend", false, "Disable the backend endpoints")
|
||||
// a := false; noBack := &a // flag.Bool("no-backend", false, "Disable the backend endpoints") // This didn't really make sense
|
||||
|
||||
flag.Parse()
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
if *noFront && *noBack {
|
||||
fmt.Println("What do you want me to do?")
|
||||
return
|
||||
}
|
||||
|
||||
if !*noFront {
|
||||
addFrontendEndpoints(mux, *static_dir)
|
||||
}
|
||||
addBackendEndpoints(mux)
|
||||
|
||||
if !*noBack {
|
||||
addBackendEndpoints(mux)
|
||||
}
|
||||
|
||||
db.Open(*db_path)
|
||||
defer db.Close()
|
||||
db.SetUserDBDir(*user_db_dir)
|
||||
db.OpenMainDB(*db_path)
|
||||
defer db.CloseMainDB()
|
||||
|
||||
addr := fmt.Sprintf("%s:%d", *bind_addr, *bind_port)
|
||||
server := http.Server{ Addr: addr, Handler: mux }
|
||||
@ -60,6 +54,9 @@ func addFrontendEndpoints(mux *http.ServeMux, static_path string) {
|
||||
mux.HandleFunc("/overdue", pages.OverdueFragment)
|
||||
mux.HandleFunc("/today", pages.TodayFragment)
|
||||
mux.HandleFunc("/upcoming", pages.UpcomingFragment)
|
||||
mux.HandleFunc("/login", pages.Login)
|
||||
mux.HandleFunc("/create-account", pages.CreateAccount)
|
||||
mux.HandleFunc("POST /logout", pages.Logout)
|
||||
mux.HandleFunc("DELETE /delete/{id}", pages.DeleteItem)
|
||||
mux.HandleFunc("PATCH /set/{id}", pages.SetItemCompleted)
|
||||
mux.HandleFunc("PUT /update", pages.UpdateItem)
|
||||
|
@ -3,12 +3,18 @@ package pages
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/Cameron-Reed1/todo-web/db"
|
||||
"github.com/Cameron-Reed1/todo-web/pages/templates"
|
||||
)
|
||||
|
||||
func OverdueFragment(w http.ResponseWriter, r *http.Request) {
|
||||
items, err := db.GetOverdueTodos()
|
||||
user_db, err := validateSessionAndGetUserDB(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
defer user_db.Close()
|
||||
|
||||
items, err := user_db.GetOverdueTodos()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
@ -18,7 +24,14 @@ func OverdueFragment(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func TodayFragment(w http.ResponseWriter, r *http.Request) {
|
||||
items, err := db.GetTodayTodos()
|
||||
user_db, err := validateSessionAndGetUserDB(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
defer user_db.Close()
|
||||
|
||||
items, err := user_db.GetTodayTodos()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
@ -28,7 +41,14 @@ func TodayFragment(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func UpcomingFragment(w http.ResponseWriter, r *http.Request) {
|
||||
items, err := db.GetUpcomingTodos()
|
||||
user_db, err := validateSessionAndGetUserDB(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
defer user_db.Close()
|
||||
|
||||
items, err := user_db.GetUpcomingTodos()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
|
108
pages/login.go
Normal file
108
pages/login.go
Normal file
@ -0,0 +1,108 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/Cameron-Reed1/todo-web/auth"
|
||||
"github.com/Cameron-Reed1/todo-web/db"
|
||||
"github.com/Cameron-Reed1/todo-web/pages/templates"
|
||||
)
|
||||
|
||||
func Login(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" {
|
||||
if _, err := validateSession(r); err == nil {
|
||||
w.Header().Add("Location", "/")
|
||||
w.WriteHeader(http.StatusSeeOther)
|
||||
} else {
|
||||
templates.LoginPage().Render(r.Context(), w)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
username := r.FormValue("username")
|
||||
password := r.FormValue("password")
|
||||
stay_logged := r.FormValue("stay-logged-in") == "on"
|
||||
|
||||
if username == "" || password == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
userId, hashSalt, err := db.GetUserPassHash(username)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if auth.Validate(hashSalt.Hash, hashSalt.Salt, []byte(password)) {
|
||||
session, err := createSession(userId)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Add("Set-Cookie", session.ToCookie(stay_logged))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
|
||||
func CreateAccount(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "GET" {
|
||||
if _, err := validateSession(r); err == nil {
|
||||
w.Header().Add("Location", "/")
|
||||
w.WriteHeader(http.StatusSeeOther)
|
||||
} else {
|
||||
templates.CreateAccountBox().Render(r.Context(), w)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
username := r.FormValue("username")
|
||||
password := r.FormValue("password")
|
||||
|
||||
if username == "" || password == "" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: validate credentials
|
||||
// Ensure the username is valid and is not taken
|
||||
// Ensure that the password meets requirements
|
||||
|
||||
hashSalt, err := auth.Hash([]byte(password), nil)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
user_id, err := db.CreateUser(username, hashSalt.Hash, hashSalt.Salt)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
session, err := createSession(user_id)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Add("Set-Cookie", session.ToCookie(false))
|
||||
}
|
||||
|
||||
func Logout(w http.ResponseWriter, r *http.Request) {
|
||||
session, err := validateSession(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = db.DeleteSession(session)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Set-Cookie", "session=;expires=Thu, 01 Jan 1970 00:00:00 UTC;samesite=strict;secure;HTTPonly")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
@ -7,5 +7,12 @@ import (
|
||||
)
|
||||
|
||||
func RootPage(w http.ResponseWriter, r *http.Request) {
|
||||
templates.RootPage().Render(r.Context(), w)
|
||||
username, err := validateSessionAndGetUsername(r)
|
||||
if err != nil {
|
||||
w.Header().Add("Location", "/login")
|
||||
w.WriteHeader(http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
templates.RootPage(username).Render(r.Context(), w)
|
||||
}
|
||||
|
70
pages/templates/login.templ
Normal file
70
pages/templates/login.templ
Normal file
@ -0,0 +1,70 @@
|
||||
package templates
|
||||
|
||||
templ loginSkeleton() {
|
||||
<!Doctype HTML>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<title>Todo login</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
|
||||
<link rel="stylesheet" href="/css/login.css"/>
|
||||
|
||||
<script src="/js/login.js"></script>
|
||||
<script src="/js/lib/htmx.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{ children... }
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
|
||||
templ LoginPage() {
|
||||
@loginSkeleton() {
|
||||
<form id="login-box" hx-post="/login" hx-swap="none">
|
||||
<h1>Login</h1>
|
||||
|
||||
<div>
|
||||
<label for="username">Username</label><br/>
|
||||
<input id="username" name="username" type="text" required/>
|
||||
|
||||
<div style="margin: 20px"></div>
|
||||
|
||||
<label for="password">Password</label><br/>
|
||||
<input id="password" name="password" type="password" required/>
|
||||
|
||||
<label for="stay-logged-in">Keep me logged in</label>
|
||||
<input id="stay-logged-in" name="stay-logged-in" type="checkbox"/><br/>
|
||||
|
||||
<button type="submit">Log in</button>
|
||||
<a href="/create-account">Create Account</a>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
|
||||
templ CreateAccountBox() {
|
||||
@loginSkeleton() {
|
||||
<form id="login-box" hx-post="/create-account">
|
||||
<h1>Create Account</h1>
|
||||
|
||||
<div>
|
||||
<label for="username">Username</label><br/>
|
||||
<input id="username" name="username" type="text" required/>
|
||||
|
||||
<div style="margin: 20px"></div>
|
||||
|
||||
<label for="password">Password</label><br/>
|
||||
<input id="password" name="password" type="password" required/>
|
||||
|
||||
<div style="margin: 20px"></div>
|
||||
|
||||
<label for="confirm-password">Confirm Password</label><br/>
|
||||
<input id="confirm-password" name="confirm-password" type="password" required/>
|
||||
|
||||
<button type="submit">Create account</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
}
|
131
pages/templates/login_templ.go
Normal file
131
pages/templates/login_templ.go
Normal file
@ -0,0 +1,131 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.2.747
|
||||
package templates
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
func loginSkeleton() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype HTML><html lang=\"en-US\"><head><title>Todo login</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><link rel=\"stylesheet\" href=\"/css/login.css\"><script src=\"/js/login.js\"></script><script src=\"/js/lib/htmx.min.js\"></script></head><body>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func LoginPage() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var2 == nil {
|
||||
templ_7745c5c3_Var2 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var3 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form id=\"login-box\" hx-post=\"/login\" hx-swap=\"none\"><h1>Login</h1><div><label for=\"username\">Username</label><br><input id=\"username\" name=\"username\" type=\"text\" required><div style=\"margin: 20px\"></div><label for=\"password\">Password</label><br><input id=\"password\" name=\"password\" type=\"password\" required> <label for=\"stay-logged-in\">Keep me logged in</label> <input id=\"stay-logged-in\" name=\"stay-logged-in\" type=\"checkbox\"><br><button type=\"submit\">Log in</button> <a href=\"/create-account\">Create Account</a></div></form>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
templ_7745c5c3_Err = loginSkeleton().Render(templ.WithChildren(ctx, templ_7745c5c3_Var3), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
||||
|
||||
func CreateAccountBox() templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var4 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var4 == nil {
|
||||
templ_7745c5c3_Var4 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Var5 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<form id=\"login-box\" hx-post=\"/create-account\"><h1>Create Account</h1><div><label for=\"username\">Username</label><br><input id=\"username\" name=\"username\" type=\"text\" required><div style=\"margin: 20px\"></div><label for=\"password\">Password</label><br><input id=\"password\" name=\"password\" type=\"password\" required><div style=\"margin: 20px\"></div><label for=\"confirm-password\">Confirm Password</label><br><input id=\"confirm-password\" name=\"confirm-password\" type=\"password\" required> <button type=\"submit\">Create account</button></div></form>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
templ_7745c5c3_Err = loginSkeleton().Render(templ.WithChildren(ctx, templ_7745c5c3_Var5), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return templ_7745c5c3_Err
|
||||
})
|
||||
}
|
@ -6,7 +6,7 @@ import (
|
||||
"github.com/Cameron-Reed1/todo-web/types"
|
||||
)
|
||||
|
||||
templ RootPage() {
|
||||
templ RootPage(username string) {
|
||||
<!Doctype HTML>
|
||||
<html lang="en-US" data-show-completed="false">
|
||||
<head>
|
||||
@ -26,13 +26,25 @@ templ RootPage() {
|
||||
<body>
|
||||
<nav>
|
||||
<div id="nav-container">
|
||||
<div id="nav-left">
|
||||
<a id="new-button" href="#create-item">New</a>
|
||||
<div id="nav-left" class="nav-section">
|
||||
<a id="new-button" class="focus-highlight" href="#create-item">New</a>
|
||||
</div>
|
||||
<div id="nav-center"></div>
|
||||
<div id="nav-right">
|
||||
<label for="show-completed">Show completed</label>
|
||||
<input id="show-completed" type="checkbox" name="show-completed"/>
|
||||
<div id="nav-center" class="nav-section"></div>
|
||||
<div id="nav-right" class="nav-section">
|
||||
<div>
|
||||
<label for="show-completed">Show completed</label>
|
||||
<input id="show-completed" type="checkbox" name="show-completed"/>
|
||||
</div>
|
||||
<div id="profile">
|
||||
<div id="profile-icon" class="focus-highlight">
|
||||
<i class="fa-solid fa-user"></i>
|
||||
<i class="fa-solid fa-caret-down"></i>
|
||||
</div>
|
||||
<div id="profile-dropdown">
|
||||
<div id="profile-name" class="focus-highlight">{ username }</div>
|
||||
<div id="profile-logout" class="focus-highlight" hx-post="/logout" hx-swap="none">Log out</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
"github.com/Cameron-Reed1/todo-web/types"
|
||||
)
|
||||
|
||||
func RootPage() templ.Component {
|
||||
func RootPage(username string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
@ -32,7 +32,20 @@ func RootPage() templ.Component {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype HTML><html lang=\"en-US\" data-show-completed=\"false\"><head><title>Todo</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><link rel=\"stylesheet\" href=\"/css/styles.css\"><script src=\"/js/script.js\"></script><script src=\"/js/lib/htmx.min.js\"></script><!-- Font Awesome --><script src=\"https://kit.fontawesome.com/469cdddb31.js\" crossorigin=\"anonymous\"></script></head><body><nav><div id=\"nav-container\"><div id=\"nav-left\"><a id=\"new-button\" href=\"#create-item\">New</a></div><div id=\"nav-center\"></div><div id=\"nav-right\"><label for=\"show-completed\">Show completed</label> <input id=\"show-completed\" type=\"checkbox\" name=\"show-completed\"></div></div></nav><div id=\"main-content\"><div id=\"lists\"><div id=\"overdue-list\" class=\"todo-list\"><div class=\"todo-list-title\">Overdue</div><div class=\"todo-list-items\" hx-get=\"/overdue\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div><div class=\"new-item\"></div></div><div id=\"today-list\" class=\"todo-list\"><div class=\"todo-list-title\">Today</div><div class=\"todo-list-items\" hx-get=\"/today\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div><div class=\"new-item\"></div></div><div id=\"upcoming-list\" class=\"todo-list\"><div class=\"todo-list-title\">Upcoming</div><div class=\"todo-list-items\" hx-get=\"/upcoming\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div><div class=\"new-item\"></div></div></div><form id=\"create-item\" hx-post=\"/new\" hx-swap=\"none\"><div class=\"form-title\">Create new Todo</div><div class=\"form-container\"><div class=\"form-column\"><label for=\"name\">Name</label><br><input id=\"create-item-name\" type=\"text\" name=\"name\"><br><label for=\"start\">Start</label><br><input id=\"create-item-start\" name=\"start\" type=\"datetime-local\"><br><label for=\"due\">Due</label><br><input id=\"create-item-due\" name=\"due\" type=\"datetime-local\"></div><div class=\"form-column\"></div></div><div class=\"form-button-container\"><button id=\"create-save\" class=\"form-save-button button\" type=\"submit\">Save</button> <a class=\"form-close-button button\" href=\"#\">Close</a></div></form><form id=\"edit-item\" data-id=\"\" hx-put=\"/update\" hx-swap=\"outerHTML\"><div class=\"form-title\">Edit Todo</div><div class=\"form-container\"><div class=\"form-column\"><label for=\"name\">Name</label><br><input id=\"edit-item-name\" type=\"text\" name=\"name\"><br><label for=\"start\">Start</label><br><input id=\"edit-item-start\" name=\"start\" type=\"datetime-local\"><br><label for=\"due\">Due</label><br><input id=\"edit-item-due\" name=\"due\" type=\"datetime-local\"></div><div class=\"form-column\"></div></div><div class=\"form-button-container\"><button id=\"edit-save\" class=\"form-save-button button\" type=\"submit\">Save</button> <a class=\"form-close-button button\" href=\"#\">Close</a></div></form></div></body></html>")
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype HTML><html lang=\"en-US\" data-show-completed=\"false\"><head><title>Todo</title><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><link rel=\"stylesheet\" href=\"/css/styles.css\"><script src=\"/js/script.js\"></script><script src=\"/js/lib/htmx.min.js\"></script><!-- Font Awesome --><script src=\"https://kit.fontawesome.com/469cdddb31.js\" crossorigin=\"anonymous\"></script></head><body><nav><div id=\"nav-container\"><div id=\"nav-left\" class=\"nav-section\"><a id=\"new-button\" class=\"focus-highlight\" href=\"#create-item\">New</a></div><div id=\"nav-center\" class=\"nav-section\"></div><div id=\"nav-right\" class=\"nav-section\"><div><label for=\"show-completed\">Show completed</label> <input id=\"show-completed\" type=\"checkbox\" name=\"show-completed\"></div><div id=\"profile\"><div id=\"profile-icon\" class=\"focus-highlight\"><i class=\"fa-solid fa-user\"></i> <i class=\"fa-solid fa-caret-down\"></i></div><div id=\"profile-dropdown\"><div id=\"profile-name\" class=\"focus-highlight\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(username)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 44, Col: 85}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div><div id=\"profile-logout\" class=\"focus-highlight\" hx-post=\"/logout\" hx-swap=\"none\">Log out</div></div></div></div></div></nav><div id=\"main-content\"><div id=\"lists\"><div id=\"overdue-list\" class=\"todo-list\"><div class=\"todo-list-title\">Overdue</div><div class=\"todo-list-items\" hx-get=\"/overdue\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div><div class=\"new-item\"></div></div><div id=\"today-list\" class=\"todo-list\"><div class=\"todo-list-title\">Today</div><div class=\"todo-list-items\" hx-get=\"/today\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div><div class=\"new-item\"></div></div><div id=\"upcoming-list\" class=\"todo-list\"><div class=\"todo-list-title\">Upcoming</div><div class=\"todo-list-items\" hx-get=\"/upcoming\" hx-trigger=\"load\" hx-swap=\"outerHTML\"></div><div class=\"new-item\"></div></div></div><form id=\"create-item\" hx-post=\"/new\" hx-swap=\"none\"><div class=\"form-title\">Create new Todo</div><div class=\"form-container\"><div class=\"form-column\"><label for=\"name\">Name</label><br><input id=\"create-item-name\" type=\"text\" name=\"name\"><br><label for=\"start\">Start</label><br><input id=\"create-item-start\" name=\"start\" type=\"datetime-local\"><br><label for=\"due\">Due</label><br><input id=\"create-item-due\" name=\"due\" type=\"datetime-local\"></div><div class=\"form-column\"></div></div><div class=\"form-button-container\"><button id=\"create-save\" class=\"form-save-button button\" type=\"submit\">Save</button> <a class=\"form-close-button button\" href=\"#\">Close</a></div></form><form id=\"edit-item\" data-id=\"\" hx-put=\"/update\" hx-swap=\"outerHTML\"><div class=\"form-title\">Edit Todo</div><div class=\"form-container\"><div class=\"form-column\"><label for=\"name\">Name</label><br><input id=\"edit-item-name\" type=\"text\" name=\"name\"><br><label for=\"start\">Start</label><br><input id=\"edit-item-start\" name=\"start\" type=\"datetime-local\"><br><label for=\"due\">Due</label><br><input id=\"edit-item-due\" name=\"due\" type=\"datetime-local\"></div><div class=\"form-column\"></div></div><div class=\"form-button-container\"><button id=\"edit-save\" class=\"form-save-button button\" type=\"submit\">Save</button> <a class=\"form-close-button button\" href=\"#\">Close</a></div></form></div></body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -63,21 +76,21 @@ func TodoItem(item types.Todo) templ.Component {
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var2 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var2 == nil {
|
||||
templ_7745c5c3_Var2 = templ.NopComponent
|
||||
templ_7745c5c3_Var3 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var3 == nil {
|
||||
templ_7745c5c3_Var3 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div id=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("item-%d", item.Id))
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("item-%d", item.Id))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 116, Col: 45}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 128, Col: 45}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -85,12 +98,12 @@ func TodoItem(item types.Todo) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.Id))
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.Id))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 116, Col: 105}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 128, Col: 105}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -98,12 +111,12 @@ func TodoItem(item types.Todo) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.Start))
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.Start))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 116, Col: 150}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 128, Col: 150}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -111,12 +124,12 @@ func TodoItem(item types.Todo) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.Due))
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.Due))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 116, Col: 191}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 128, Col: 191}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -134,12 +147,12 @@ func TodoItem(item types.Todo) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(string(templ.URL(fmt.Sprintf("/set/%d", item.Id))))
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(string(templ.URL(fmt.Sprintf("/set/%d", item.Id))))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 117, Col: 137}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 129, Col: 137}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -147,12 +160,12 @@ func TodoItem(item types.Todo) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(item.Text)
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(item.Text)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 118, Col: 42}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 130, Col: 42}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -160,12 +173,12 @@ func TodoItem(item types.Todo) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/delete/%d", item.Id))
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("/delete/%d", item.Id))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 121, Col: 101}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 133, Col: 101}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -190,21 +203,21 @@ func OobTodoItem(targetSelector string, item types.Todo) templ.Component {
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var10 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var10 == nil {
|
||||
templ_7745c5c3_Var10 = templ.NopComponent
|
||||
templ_7745c5c3_Var11 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var11 == nil {
|
||||
templ_7745c5c3_Var11 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div hx-swap-oob=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s:%s", "afterend", targetSelector))
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%s:%s", "afterend", targetSelector))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 127, Col: 71}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 139, Col: 71}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -237,21 +250,21 @@ func TodoList(fillerText string, items []types.Todo) templ.Component {
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var12 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var12 == nil {
|
||||
templ_7745c5c3_Var12 = templ.NopComponent
|
||||
templ_7745c5c3_Var13 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var13 == nil {
|
||||
templ_7745c5c3_Var13 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"todo-list-items\" data-item-count=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var13 string
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(items)))
|
||||
var templ_7745c5c3_Var14 string
|
||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(items)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 133, Col: 80}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 145, Col: 80}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -259,12 +272,12 @@ func TodoList(fillerText string, items []types.Todo) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var14 string
|
||||
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fillerText)
|
||||
var templ_7745c5c3_Var15 string
|
||||
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fillerText)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 134, Col: 45}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `pages/templates/root.templ`, Line: 146, Col: 45}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
63
pages/utils.go
Normal file
63
pages/utils.go
Normal file
@ -0,0 +1,63 @@
|
||||
package pages
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/Cameron-Reed1/todo-web/auth"
|
||||
"github.com/Cameron-Reed1/todo-web/db"
|
||||
"github.com/Cameron-Reed1/todo-web/types"
|
||||
)
|
||||
|
||||
func createSession(user_id int64) (*types.Session, error) {
|
||||
session, err := auth.CreateSessionFor(user_id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = db.AddSession(session)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return session, nil
|
||||
}
|
||||
|
||||
func validateSession(r *http.Request) (string, error) {
|
||||
cookie, err := r.Cookie("session")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, err = db.GetUserFromSession(cookie.Value)
|
||||
// session, err := db.GetSession(cookie.Value)
|
||||
return cookie.Value, err
|
||||
}
|
||||
|
||||
func validateSessionAndGetUsername(r *http.Request) (string, error) {
|
||||
cookie, err := r.Cookie("session")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return db.GetUserFromSession(cookie.Value)
|
||||
}
|
||||
|
||||
func validateSessionAndGetUserDB(r *http.Request) (*db.UserDB, error) {
|
||||
cookie, err := r.Cookie("session")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
username, err := db.GetUserFromSession(cookie.Value)
|
||||
// session, err := db.GetSession(cookie.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user_db, err := db.OpenUserDB(username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user_db, nil
|
||||
}
|
@ -6,20 +6,25 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Cameron-Reed1/todo-web/db"
|
||||
"github.com/Cameron-Reed1/todo-web/pages/templates"
|
||||
"github.com/Cameron-Reed1/todo-web/types"
|
||||
)
|
||||
|
||||
func CreateItem(w http.ResponseWriter, r *http.Request) {
|
||||
user_db, err := validateSessionAndGetUserDB(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
defer user_db.Close()
|
||||
|
||||
var todo types.Todo
|
||||
var err error
|
||||
|
||||
todo.Text = r.FormValue("name")
|
||||
start := r.FormValue("start")
|
||||
due := r.FormValue("due")
|
||||
|
||||
fmt.Printf("Create item request: %s: %s - %s\n", todo.Text, start, due)
|
||||
// fmt.Printf("Create item request: %s: %s - %s\n", todo.Text, start, due)
|
||||
|
||||
if start != "" {
|
||||
todo.Start, err = strconv.ParseInt(start, 10, 64)
|
||||
@ -43,9 +48,9 @@ func CreateItem(w http.ResponseWriter, r *http.Request) {
|
||||
todo.Due = 0
|
||||
}
|
||||
|
||||
fmt.Printf("New item: %s: %d - %d\n", todo.Text, todo.Start, todo.Due)
|
||||
// fmt.Printf("New item: %s: %d - %d\n", todo.Text, todo.Start, todo.Due)
|
||||
|
||||
err = db.AddTodo(&todo)
|
||||
err = user_db.AddTodo(&todo)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
@ -64,6 +69,13 @@ func CreateItem(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func DeleteItem(w http.ResponseWriter, r *http.Request) {
|
||||
user_db, err := validateSessionAndGetUserDB(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
defer user_db.Close()
|
||||
|
||||
idStr := r.PathValue("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
|
||||
@ -72,7 +84,7 @@ func DeleteItem(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
err = db.DeleteTodo(id)
|
||||
err = user_db.DeleteTodo(id)
|
||||
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
@ -83,6 +95,13 @@ func DeleteItem(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func SetItemCompleted(w http.ResponseWriter, r *http.Request) {
|
||||
user_db, err := validateSessionAndGetUserDB(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
defer user_db.Close()
|
||||
|
||||
idStr := r.PathValue("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
|
||||
@ -92,7 +111,7 @@ func SetItemCompleted(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
completed := r.FormValue("completed") == "on"
|
||||
if err = db.SetCompleted(id, completed); err != nil {
|
||||
if err = user_db.SetCompleted(id, completed); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@ -101,8 +120,14 @@ func SetItemCompleted(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func UpdateItem(w http.ResponseWriter, r *http.Request) {
|
||||
user_db, err := validateSessionAndGetUserDB(r)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
defer user_db.Close()
|
||||
|
||||
var todo types.Todo
|
||||
var err error
|
||||
|
||||
idStr := r.FormValue("id")
|
||||
todo.Text = r.FormValue("name")
|
||||
@ -138,9 +163,9 @@ func UpdateItem(w http.ResponseWriter, r *http.Request) {
|
||||
todo.Due = 0
|
||||
}
|
||||
|
||||
fmt.Printf("New values:\n(%d) %s: %d - %d\n\n", todo.Id, todo.Text, todo.Start, todo.Due)
|
||||
// fmt.Printf("New values:\n(%d) %s: %d - %d\n\n", todo.Id, todo.Text, todo.Start, todo.Due)
|
||||
|
||||
err = db.UpdateTodo(todo)
|
||||
err = user_db.UpdateTodo(todo)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
|
56
static/css/login.css
Normal file
56
static/css/login.css
Normal file
@ -0,0 +1,56 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100vh;
|
||||
height: 100lvh;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#login-box {
|
||||
padding: 0 10px;
|
||||
width: 275px;
|
||||
height: min(calc(100lvh - 110px), 450px);
|
||||
border: 4px solid black;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"] {
|
||||
width: 267px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
text-decoration: none;
|
||||
border: 2px solid;
|
||||
border-radius: 6px;
|
||||
box-shadow: black 2px 2px 3px 0;
|
||||
background-color: white;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
margin: 35px 0 25px 0;
|
||||
color: blue;
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
font-size: .85rem;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
@ -5,6 +5,7 @@
|
||||
|
||||
nav {
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
border-bottom: 2px solid black;
|
||||
}
|
||||
|
||||
@ -16,12 +17,17 @@ nav {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.nav-section {
|
||||
padding: 0 13px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
#new-button {
|
||||
width: 100px;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
line-height: 48px;
|
||||
font-size: 2em;
|
||||
font-weight: 600;
|
||||
font-family: sans-serif;
|
||||
@ -30,10 +36,44 @@ nav {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#new-button:hover {
|
||||
.focus-highlight:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
#profile {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#profile-icon {
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#profile-dropdown {
|
||||
position: absolute;
|
||||
top: 48px;
|
||||
right: 0px;
|
||||
height: 0;
|
||||
width: 100px;
|
||||
z-index: 9999;
|
||||
border: 0px solid black;
|
||||
transition: height .5s, border-width 0s .5s;
|
||||
overflow: hidden;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#profile:hover > #profile-dropdown {
|
||||
height: 64px;
|
||||
border-width: 2px;
|
||||
transition: height .5s, border-width 0s;
|
||||
}
|
||||
|
||||
#profile-dropdown > div {
|
||||
cursor: pointer;
|
||||
line-height: 32px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
#main-content {
|
||||
height: calc(100vh - 100px);
|
||||
height: calc(100lvh - 100px);
|
||||
|
16
static/js/login.js
Normal file
16
static/js/login.js
Normal file
@ -0,0 +1,16 @@
|
||||
function on_load() {
|
||||
const login_box = document.getElementById("login-box");
|
||||
login_box.addEventListener("htmx:afterRequest", function(evt) {
|
||||
if (evt.detail.successful) {
|
||||
window.location.pathname = "/";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (document.readyState === "completed") {
|
||||
on_load();
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", on_load);
|
||||
}
|
@ -1,4 +1,12 @@
|
||||
function on_load() {
|
||||
let logout_button = document.getElementById("profile-logout");
|
||||
|
||||
logout_button.addEventListener("htmx:afterRequest", function(evt) {
|
||||
if (evt.detail.successful) {
|
||||
window.location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
let create_start_input = document.getElementById("create-item-start");
|
||||
let create_due_input = document.getElementById("create-item-due");
|
||||
|
||||
|
17
types/session.go
Normal file
17
types/session.go
Normal file
@ -0,0 +1,17 @@
|
||||
package types
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Session struct {
|
||||
UserId int64
|
||||
SessionId string
|
||||
}
|
||||
|
||||
func (session *Session) ToCookie(stay_logged bool) string {
|
||||
age := ""
|
||||
if stay_logged {
|
||||
age = "max-age=31536000;"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("session=%s;%ssamesite=strict;secure;HTTPonly", session.SessionId, age)
|
||||
}
|
Loading…
Reference in New Issue
Block a user