2023-05-22 01:01:10 +04:00
|
|
|
package justguestbook
|
2023-05-22 00:50:11 +04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
|
|
|
_ "embed"
|
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
_ "github.com/mattn/go-sqlite3"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
//go:embed sqlite_queries/schema.sql
|
|
|
|
sqlQuerySchema string
|
|
|
|
|
|
|
|
//go:embed sqlite_queries/entryGetAll.sql
|
|
|
|
sqlQueryGetAll string
|
|
|
|
//go:embed sqlite_queries/entryCount.sql
|
|
|
|
sqlQueryCount string
|
|
|
|
|
|
|
|
//go:embed sqlite_queries/entryNew.sql
|
|
|
|
sqlQueryNewEntry string
|
|
|
|
//go:embed sqlite_queries/entryUpdate.sql
|
|
|
|
sqlQueryUpdateEntry string
|
|
|
|
//go:embed sqlite_queries/entryDelete.sql
|
|
|
|
sqlQueryDeleteEntry string
|
|
|
|
//go:embed sqlite_queries/replyNew.sql
|
|
|
|
sqlQueryNewReply string
|
|
|
|
//go:embed sqlite_queries/replyUpdate.sql
|
|
|
|
sqlQueryUpdateReply string
|
|
|
|
//go:embed sqlite_queries/replyDelete.sql
|
|
|
|
sqlQueryDeleteReply string
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
sqlStmtGetAll *sql.Stmt
|
|
|
|
sqlStmtCount *sql.Stmt
|
|
|
|
sqlStmtNewEntry *sql.Stmt
|
|
|
|
sqlStmtUpdateEntry *sql.Stmt
|
|
|
|
sqlStmtDeleteEntry *sql.Stmt
|
|
|
|
sqlStmtNewReply *sql.Stmt
|
|
|
|
sqlStmtUpdateReply *sql.Stmt
|
|
|
|
sqlStmtDeleteReply *sql.Stmt
|
|
|
|
)
|
|
|
|
|
|
|
|
func initSQLiteStatements(db *sql.DB) error {
|
|
|
|
db.Exec("PRAGMA foreign_keys = ON;")
|
|
|
|
|
|
|
|
_, err := db.Exec(sqlQuerySchema)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to init schema")
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlStmtGetAll, err = db.Prepare(sqlQueryGetAll)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to prepare sqlQueryGetAll")
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlStmtCount, err = db.Prepare(sqlQueryCount)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to prepare sqlQueryCount")
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlStmtNewEntry, err = db.Prepare(sqlQueryNewEntry)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to prepare sqlQueryNewEntry")
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlStmtUpdateEntry, err = db.Prepare(sqlQueryUpdateEntry)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to prepare sqlQueryUpdateEntry")
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlStmtDeleteEntry, err = db.Prepare(sqlQueryDeleteEntry)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to prepare sqlQueryDeleteEntry")
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlStmtNewReply, err = db.Prepare(sqlQueryNewReply)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to prepare sqlQueryNewReply")
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlStmtUpdateReply, err = db.Prepare(sqlQueryUpdateReply)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to prepare sqlQueryUpdateReply")
|
|
|
|
}
|
|
|
|
|
|
|
|
sqlStmtDeleteReply, err = db.Prepare(sqlQueryDeleteReply)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "failed to prepare sqlQueryDeleteReply")
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type SQLiteDatabase struct {
|
|
|
|
db *sql.DB
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewSQLiteDB(filePath string) (Guestbook, error) {
|
|
|
|
db, err := sql.Open("sqlite3", sqliteDSN(filePath))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := initSQLiteStatements(db); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &SQLiteDatabase{db: db}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *SQLiteDatabase) Entries(page, pageSize int64) (entries []*Entry, err error) {
|
|
|
|
tx, err := d.db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer tx.Rollback()
|
|
|
|
|
|
|
|
rows, err := tx.Stmt(sqlStmtGetAll).Query(pageSize, (page-1)*pageSize)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
|
|
|
|
for rows.Next() {
|
|
|
|
var entry Entry
|
|
|
|
var entry_created int64
|
|
|
|
var reply_created int64
|
|
|
|
var reply_message string
|
|
|
|
if err = rows.Scan(
|
|
|
|
&entry.ID, &entry_created, &entry.Name,
|
|
|
|
&entry.Message, &entry.Website, &entry.HideWebsite,
|
|
|
|
&reply_created, &reply_message); err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
entry.Created = time.Unix(entry_created, 0)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if reply_message != "" {
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
entry.Reply = &Reply{
|
|
|
|
ID: entry.ID,
|
|
|
|
// Created: date,
|
|
|
|
Created: time.Unix(reply_created, 0),
|
|
|
|
Message: reply_message}
|
|
|
|
}
|
|
|
|
|
|
|
|
entries = append(entries, &entry)
|
|
|
|
}
|
|
|
|
|
|
|
|
tx.Commit()
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Count returns how much entries are in an `entry` table.
|
|
|
|
func (d *SQLiteDatabase) Count() (count int64, err error) {
|
|
|
|
tx, err := d.db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return -1, err
|
|
|
|
}
|
|
|
|
defer tx.Rollback()
|
|
|
|
|
|
|
|
err = tx.Stmt(sqlStmtCount).QueryRow().Scan(&count)
|
|
|
|
if err != nil {
|
|
|
|
return -1, err
|
|
|
|
}
|
|
|
|
|
|
|
|
tx.Commit()
|
|
|
|
|
|
|
|
return count, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewEntry inserts a passed Entry struct and fills its ID field if successful.
|
|
|
|
func (d *SQLiteDatabase) NewEntry(entry *Entry) error {
|
|
|
|
tx, err := d.db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer tx.Rollback()
|
|
|
|
|
|
|
|
r, err := tx.Stmt(sqlStmtNewEntry).Exec(entry.Created.UTC().Unix(), entry.Name, entry.Message,
|
|
|
|
entry.Website, entry.HideWebsite)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
entry.ID, err = r.LastInsertId()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
tx.Commit()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *SQLiteDatabase) EditEntry(entry *Entry) error {
|
|
|
|
tx, err := d.db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer tx.Rollback()
|
|
|
|
|
|
|
|
_, err = tx.Stmt(sqlStmtUpdateEntry).Exec(entry.Name, entry.Message, entry.Website, entry.HideWebsite, entry.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
tx.Commit()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *SQLiteDatabase) DeleteEntry(entryID int64) error {
|
|
|
|
tx, err := d.db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer tx.Rollback()
|
|
|
|
|
|
|
|
if _, err = tx.Stmt(sqlStmtDeleteEntry).Exec(entryID); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
tx.Commit()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *SQLiteDatabase) NewReply(reply *Reply) error {
|
|
|
|
tx, err := d.db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer tx.Rollback()
|
|
|
|
|
|
|
|
_, err = tx.Stmt(sqlStmtNewReply).Exec(reply.ID, reply.Created.UTC().Unix(), reply.Message)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
tx.Commit()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *SQLiteDatabase) EditReply(reply *Reply) error {
|
|
|
|
tx, err := d.db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer tx.Rollback()
|
|
|
|
|
|
|
|
_, err = tx.Stmt(sqlStmtUpdateReply).Exec(reply.Message, reply.ID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
tx.Commit()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *SQLiteDatabase) DeleteReply(entryID int64) error {
|
|
|
|
tx, err := d.db.Begin()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer tx.Rollback()
|
|
|
|
|
|
|
|
if _, err = tx.Stmt(sqlStmtDeleteReply).Exec(entryID); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
tx.Commit()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *SQLiteDatabase) Close() error {
|
|
|
|
sqlStmtCount.Close()
|
|
|
|
sqlStmtDeleteEntry.Close()
|
|
|
|
sqlStmtDeleteReply.Close()
|
|
|
|
sqlStmtGetAll.Close()
|
|
|
|
sqlStmtNewEntry.Close()
|
|
|
|
sqlStmtNewReply.Close()
|
|
|
|
sqlStmtUpdateEntry.Close()
|
|
|
|
sqlStmtUpdateReply.Close()
|
|
|
|
|
|
|
|
return d.db.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
func sqliteDSN(filePath string) string {
|
|
|
|
return fmt.Sprintf("file:%s?_journal=WAL&_mutex=full", filePath)
|
|
|
|
}
|