358 lines
8.5 KiB
Go
358 lines
8.5 KiB
Go
/*
|
|
YetAnotherToDoList
|
|
Copyright © 2023 gilex-dev gilex-dev@proton.me
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, version 3.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package database
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"strconv"
|
|
|
|
"somepi.ddns.net/gitea/gilex-dev/YetAnotherToDoList/graph/model"
|
|
)
|
|
|
|
type CustomDB struct {
|
|
connection *sql.DB
|
|
logger *log.Logger
|
|
schema uint
|
|
}
|
|
|
|
func InitSQLite3(path string, schema uint, logger *log.Logger) *CustomDB {
|
|
|
|
db := CustomDB{logger: logger, schema: schema}
|
|
var err error
|
|
|
|
db.connection, err = sql.Open("sqlite3", path)
|
|
if err != nil {
|
|
db.logger.Fatalln("Unable to open:", err)
|
|
}
|
|
|
|
if err = db.connection.Ping(); err != nil {
|
|
db.logger.Fatalln("Unable to connect:", err)
|
|
}
|
|
|
|
var user_version uint
|
|
err = db.connection.QueryRow("PRAGMA user_version").Scan(&user_version)
|
|
if err != nil {
|
|
db.logger.Fatalln("Failed to get database schema version")
|
|
}
|
|
|
|
switch {
|
|
case user_version == 0:
|
|
db.logger.Println("Initializing empty database")
|
|
if err = db.createSQLite3Tables(); err != nil {
|
|
db.logger.Fatalln("Error in creating table: ", err)
|
|
}
|
|
case user_version > db.schema:
|
|
db.logger.Fatalln("Incompatible database schema version. Try updating this software.")
|
|
case user_version < db.schema:
|
|
db.logger.Fatalln("Upgrading database schema currently not supported")
|
|
}
|
|
return &db
|
|
}
|
|
|
|
func (db CustomDB) createSQLite3Tables() error {
|
|
tables := []struct {
|
|
name string
|
|
sql string
|
|
}{
|
|
{"User", "userId INTEGER PRIMARY KEY NOT NULL, userName VARCHAR NOT NULL UNIQUE, fullName VARCHAR"},
|
|
{"Todo", "todoId INTEGER PRIMARY KEY NOT NULL, text VARCHAR NOT NULL, IS_done BOOL NOT NULL DEFAULT false, FK_User_userId INTEGER NOT NULL, FOREIGN KEY(FK_User_userId) REFERENCES User(userId) ON UPDATE CASCADE ON DELETE CASCADE"},
|
|
}
|
|
for _, table := range tables {
|
|
_, err := db.connection.Exec("CREATE TABLE IF NOT EXISTS " + table.name + " (" + table.sql + ")")
|
|
if err != nil {
|
|
return err
|
|
} else {
|
|
db.logger.Println("Successfully created", table.name)
|
|
}
|
|
}
|
|
|
|
_, err := db.connection.Exec("PRAGMA foreign_keys = ON")
|
|
if err != nil {
|
|
db.logger.Fatalln("Failed to enable foreign_keys:", err)
|
|
}
|
|
|
|
_, err = db.connection.Exec("PRAGMA user_version = " + fmt.Sprintf("%d", db.schema))
|
|
if err != nil {
|
|
db.logger.Fatalln("Failed to set user_version:", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (db CustomDB) GetUser(user *model.User) (*model.User, error) {
|
|
id, err := strconv.Atoi(user.ID)
|
|
if err != nil {
|
|
return nil, errors.New("invalid userId")
|
|
}
|
|
statement, err := db.connection.Prepare("SELECT userName, fullName FROM User WHERE userId = ?")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := statement.QueryRow(id)
|
|
if err := result.Scan(&user.UserName, &user.FullName); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (db CustomDB) GetTodo(todo *model.Todo) (*model.Todo, error) {
|
|
id, err := strconv.Atoi(todo.ID)
|
|
if err != nil {
|
|
return nil, errors.New("invalid todoId")
|
|
}
|
|
statement, err := db.connection.Prepare("SELECT text, IS_done FROM Todo WHERE todoId = ?")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result := statement.QueryRow(id)
|
|
if err := result.Scan(&todo.Text, &todo.Done); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return todo, nil
|
|
}
|
|
|
|
func (db CustomDB) GetTodosFrom(user *model.User) ([]*model.Todo, error) {
|
|
id, err := strconv.Atoi(user.ID)
|
|
if err != nil {
|
|
return nil, errors.New("invalid userId")
|
|
}
|
|
statement, err := db.connection.Prepare("SELECT todoId, text, IS_done FROM Todo WHERE FK_User_userId = ?")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rows, err := statement.Query(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
var all []*model.Todo
|
|
for rows.Next() {
|
|
todo := model.Todo{User: user}
|
|
if err := rows.Scan(&todo.ID, &todo.Text, &todo.Done); err != nil {
|
|
return nil, err
|
|
}
|
|
all = append(all, &todo)
|
|
}
|
|
return all, nil
|
|
}
|
|
|
|
func (db CustomDB) GetAllUsers() ([]*model.User, error) {
|
|
rows, err := db.connection.Query("SELECT userId, userName, fullName FROM User")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
var all []*model.User
|
|
for rows.Next() {
|
|
var user model.User
|
|
if err := rows.Scan(&user.ID, &user.UserName, &user.FullName); err != nil {
|
|
return nil, err
|
|
}
|
|
all = append(all, &user)
|
|
}
|
|
return all, nil
|
|
}
|
|
|
|
func (db CustomDB) GetAllTodos() ([]*model.Todo, error) {
|
|
rows, err := db.connection.Query("SELECT Todo.todoID, Todo.text, Todo.IS_done, User.userID FROM Todo INNER JOIN User ON Todo.FK_User_userID=User.userID")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer rows.Close()
|
|
|
|
var todos []*model.Todo
|
|
for rows.Next() {
|
|
var todo = model.Todo{User: &model.User{}}
|
|
if err := rows.Scan(&todo.ID, &todo.Text, &todo.Done, &todo.User.ID); err != nil {
|
|
return nil, err
|
|
}
|
|
todos = append(todos, &todo)
|
|
}
|
|
return todos, nil
|
|
}
|
|
|
|
func (db CustomDB) AddUser(newUser model.NewUser) (*model.User, error) {
|
|
statement, err := db.connection.Prepare("INSERT INTO User (userName, fullName) VALUES (?, ?)")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rows, err := statement.Exec(newUser.UserName, newUser.FullName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
num, err := rows.RowsAffected()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if num < 1 {
|
|
return nil, errors.New("no rows affected")
|
|
}
|
|
|
|
insertId, err := rows.LastInsertId()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &model.User{ID: strconv.FormatInt(insertId, 10), UserName: newUser.UserName, FullName: newUser.FullName}, nil
|
|
}
|
|
|
|
func (db CustomDB) AddTodo(newTodo model.NewTodo) (*model.Todo, error) {
|
|
statement, err := db.connection.Prepare("INSERT INTO Todo (text, FK_User_userID) VALUES (?, ?)")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rows, err := statement.Exec(newTodo.Text, newTodo.UserID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
num, err := rows.RowsAffected()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if num < 1 {
|
|
return nil, errors.New("no rows affected")
|
|
}
|
|
|
|
insertId, err := rows.LastInsertId()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &model.Todo{ID: strconv.FormatInt(insertId, 10), Text: newTodo.Text, Done: false}, nil
|
|
}
|
|
|
|
func (db CustomDB) UpdateUser(userId string, changes *model.UpdateUser) (*model.User, error) {
|
|
|
|
id, err := strconv.Atoi(userId)
|
|
if err != nil {
|
|
return nil, errors.New("invalid userId")
|
|
}
|
|
|
|
statement, err := db.connection.Prepare("UPDATE User SET userName = IFNULL(?, userName), fullName = IFNULL(?, fullName) WHERE userId = ?")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rows, err := statement.Exec(changes.UserName, changes.FullName, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
num, err := rows.RowsAffected()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if num < 1 {
|
|
return nil, errors.New("no rows affected")
|
|
}
|
|
|
|
return db.GetUser(&model.User{ID: userId})
|
|
}
|
|
|
|
func (db CustomDB) UpdateTodo(todoId string, changes *model.UpdateTodo) (*model.Todo, error) {
|
|
|
|
id, err := strconv.Atoi(todoId)
|
|
if err != nil {
|
|
return nil, errors.New("invalid userId")
|
|
}
|
|
|
|
statement, err := db.connection.Prepare("UPDATE Todo SET text = IFNULL(?, text), IS_done = IFNULL(?, IS_done) WHERE todoId = ?")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rows, err := statement.Exec(changes.Text, changes.Done, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
num, err := rows.RowsAffected()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if num < 1 {
|
|
return nil, errors.New("no rows affected")
|
|
}
|
|
|
|
return db.GetTodo(&model.Todo{ID: todoId})
|
|
}
|
|
|
|
func (db CustomDB) DeleteUser(userId string) (*string, error) {
|
|
statement, err := db.connection.Prepare("DELETE FROM User WHERE userId = ?")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rows, err := statement.Exec(userId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
num, err := rows.RowsAffected()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if num < 1 {
|
|
return nil, errors.New("no rows affected")
|
|
}
|
|
|
|
return &userId, nil
|
|
}
|
|
|
|
func (db CustomDB) DeleteTodo(todoId string) (*string, error) {
|
|
statement, err := db.connection.Prepare("DELETE FROM Todo WHERE todoId = ?")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rows, err := statement.Exec(todoId)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
num, err := rows.RowsAffected()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if num < 1 {
|
|
return nil, errors.New("no rows affected")
|
|
}
|
|
|
|
return &todoId, nil
|
|
}
|