YetAnotherToDoList/database/main.go

353 lines
8.4 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", "file:"+path+"?_foreign_keys=1")
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 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
}