2023-10-10 22:26:07 +02:00
/ *
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
2023-10-27 13:08:39 +02:00
db . connection , err = sql . Open ( "sqlite3" , "file:" + path + "?_foreign_keys=1" )
2023-10-10 22:26:07 +02:00
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 )
}
}
2023-10-27 13:08:39 +02:00
_ , err := db . connection . Exec ( "PRAGMA user_version = " + fmt . Sprintf ( "%d" , db . schema ) )
2023-10-10 22:26:07 +02:00
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
}