2015-11-13 17:04:42 +08:00
package db
2016-02-01 22:34:19 +08:00
/ *
Stores the database functions related to tasks like
GetTaskByID ( id int )
GetTasks ( status string )
DeleteAll ( )
* /
2015-11-13 17:04:42 +08:00
import (
"database/sql"
2016-01-31 22:22:00 +08:00
"log"
"strings"
"time"
2016-02-01 21:45:04 +08:00
_ "github.com/mattn/go-sqlite3" //we want to use sqlite natively
md "github.com/shurcooL/github_flavored_markdown"
"github.com/thewhitetulip/Tasks/types"
2015-11-13 17:04:42 +08:00
)
2016-01-31 21:48:19 +08:00
var database Database
2016-05-14 15:26:24 +08:00
var taskStatus map [ string ] int
2015-11-13 17:04:42 +08:00
var err error
2016-01-31 21:48:19 +08:00
//Database encapsulates database
type Database struct {
db * sql . DB
}
2016-05-14 15:26:24 +08:00
//Begins a transaction
2016-01-31 21:48:19 +08:00
func ( db Database ) begin ( ) ( tx * sql . Tx ) {
tx , err := db . db . Begin ( )
if err != nil {
log . Println ( err )
2016-02-01 21:45:04 +08:00
return nil
2016-01-31 21:48:19 +08:00
}
return tx
}
func ( db Database ) prepare ( q string ) ( stmt * sql . Stmt ) {
stmt , err := db . db . Prepare ( q )
if err != nil {
log . Println ( err )
2016-02-01 21:45:04 +08:00
return nil
2016-01-31 21:48:19 +08:00
}
return stmt
}
func ( db Database ) query ( q string , args ... interface { } ) ( rows * sql . Rows ) {
2016-01-31 22:00:31 +08:00
rows , err := db . db . Query ( q , args ... )
2016-01-31 21:48:19 +08:00
if err != nil {
log . Println ( err )
2016-02-01 21:45:04 +08:00
return nil
2016-01-31 21:48:19 +08:00
}
return rows
}
2015-11-13 17:04:42 +08:00
func init ( ) {
2016-01-31 21:48:19 +08:00
database . db , err = sql . Open ( "sqlite3" , "./tasks.db" )
2016-05-14 18:37:20 +08:00
taskStatus = map [ string ] int { "COMPLETE" : 1 , "PENDING" : 2 , "DELETED" : 3 }
2015-11-13 17:04:42 +08:00
if err != nil {
2016-02-01 21:45:04 +08:00
log . Fatal ( err )
2015-11-13 17:04:42 +08:00
}
}
2015-11-21 14:39:15 +08:00
//Close function closes this database connection
2015-11-13 17:04:42 +08:00
func Close ( ) {
2016-01-31 21:48:19 +08:00
database . db . Close ( )
2015-11-13 17:04:42 +08:00
}
2015-11-21 14:39:15 +08:00
//GetTasks retrieves all the tasks depending on the
//status pending or trashed or completed
2016-05-14 15:26:24 +08:00
func GetTasks ( username , status , category string ) ( types . Context , error ) {
log . Println ( "getting tasks for " , status )
2016-02-15 01:46:22 +08:00
var tasks [ ] types . Task
var task types . Task
2015-11-14 18:10:21 +08:00
var TaskCreated time . Time
2016-02-15 01:46:22 +08:00
var context types . Context
2016-05-14 15:26:24 +08:00
var getTaskSQL string
2016-02-03 01:40:44 +08:00
var rows * sql . Rows
2015-11-21 21:20:51 +08:00
2016-05-14 15:26:24 +08:00
comments , err := GetComments ( username )
2016-02-17 00:53:03 +08:00
if err != nil {
return context , err
}
2016-02-15 01:46:22 +08:00
2016-05-22 17:40:40 +08:00
basicSQL := "select t.id, title, content, created_date, priority, case when c.name is null then 'NA' else c.name end from task t, status s, user u left outer join category c on c.id=t.cat_id where u.username=? and s.id=t.task_status_id and u.id=t.user_id"
2016-05-14 15:26:24 +08:00
if category == "" {
switch status {
case "pending" :
2016-05-14 18:37:20 +08:00
getTaskSQL = basicSQL + " and s.status='PENDING'"
2016-05-14 15:26:24 +08:00
case "deleted" :
getTaskSQL = basicSQL + " and s.status='DELETED' "
case "completed" :
getTaskSQL = basicSQL + " and s.status='COMPLETE'"
}
2015-11-13 17:04:42 +08:00
2016-05-22 17:40:40 +08:00
getTaskSQL += " order by t.created_date asc"
rows = database . query ( getTaskSQL , username , username )
2016-05-14 15:26:24 +08:00
} else {
2016-02-03 01:40:44 +08:00
status = category
2016-05-16 23:27:09 +08:00
//This is a special case for showing tasks with null categories, we do a union query
if category == "UNCATEGORIZED" {
getTaskSQL = "select t.id, title, content, created_date, priority, 'UNCATEGORIZED' from task t, status s, user u where u.username=? and s.id=t.task_status_id and u.id=t.user_id and t.cat_id=0 and s.status='PENDING' order by priority desc, created_date asc, finish_date asc"
rows , err = database . db . Query ( getTaskSQL , username )
} else {
getTaskSQL = basicSQL + " and name = ? and s.status='PENDING' order by priority desc, created_date asc, finish_date asc"
rows , err = database . db . Query ( getTaskSQL , username , category )
}
2016-02-29 10:05:38 +08:00
2016-02-03 01:40:44 +08:00
if err != nil {
2016-05-14 15:26:24 +08:00
log . Println ( "tasks.go: something went wrong while getting query fetch tasks by category" )
2016-02-03 01:40:44 +08:00
}
}
2016-05-14 15:26:24 +08:00
2015-11-13 17:04:42 +08:00
defer rows . Close ( )
2015-11-13 21:11:30 +08:00
for rows . Next ( ) {
2016-02-15 01:46:22 +08:00
task = types . Task { }
2016-02-29 10:05:38 +08:00
err = rows . Scan ( & task . Id , & task . Title , & task . Content , & TaskCreated , & task . Priority , & task . Category )
2016-02-15 01:46:22 +08:00
task . Content = string ( md . Markdown ( [ ] byte ( task . Content ) ) )
2016-01-31 20:30:10 +08:00
// TaskContent = strings.Replace(TaskContent, "\n", "<br>", -1)
2015-11-13 21:11:30 +08:00
if err != nil {
2016-01-31 20:16:44 +08:00
log . Println ( err )
2015-11-13 17:04:42 +08:00
}
2016-02-15 01:46:22 +08:00
if comments [ task . Id ] != nil {
task . Comments = comments [ task . Id ]
}
2015-11-14 18:10:21 +08:00
TaskCreated = TaskCreated . Local ( )
2016-08-07 12:32:47 +08:00
// if task.Priority != "1" { // if priority is not 1 then calculate, else why bother?
// CurrentTime := time.Now().Local()
// diff := CurrentTime.Sub(TaskCreated).Hours()
// if diff > 168 {
// task.IsOverdue = true // If one week then overdue by default
// }
// }
2016-03-01 02:47:14 +08:00
task . Created = TaskCreated . Format ( "Jan 2 2006" )
2016-02-15 01:46:22 +08:00
tasks = append ( tasks , task )
2015-11-13 17:04:42 +08:00
}
2016-02-15 01:46:22 +08:00
context = types . Context { Tasks : tasks , Navigation : status }
2016-02-01 21:45:04 +08:00
return context , nil
2015-11-13 17:04:42 +08:00
}
2015-11-22 11:51:29 +08:00
//GetTaskByID function gets the tasks from the ID passed to the function, used to populate EditTask
2016-05-14 15:26:24 +08:00
func GetTaskByID ( username string , id int ) ( types . Context , error ) {
2015-11-22 10:59:39 +08:00
var tasks [ ] types . Task
2015-11-13 17:04:42 +08:00
var task types . Task
2016-01-23 21:47:34 +08:00
2016-05-16 23:27:09 +08:00
getTaskSQL := "select t.id, t.title, t.content, t.priority, 'UNCATEGORIZED' from task t join user u where t.user_id=u.id and t.cat_id=0 union select t.id, t.title, t.content, t.priority, c.name from task t join user u left outer join category c where c.id = t.cat_id and t.user_id=u.id and t.id=? and u.username=?;"
2016-01-31 20:30:10 +08:00
2016-05-14 15:26:24 +08:00
rows := database . query ( getTaskSQL , id , username )
2015-11-13 17:04:42 +08:00
defer rows . Close ( )
2015-11-13 21:11:30 +08:00
if rows . Next ( ) {
2016-02-05 02:36:00 +08:00
err := rows . Scan ( & task . Id , & task . Title , & task . Content , & task . Priority , & task . Category )
2015-11-13 21:11:30 +08:00
if err != nil {
2016-01-31 20:16:44 +08:00
log . Println ( err )
2016-01-31 20:30:10 +08:00
//send email to respective people
2015-11-13 17:04:42 +08:00
}
}
2015-11-22 10:59:39 +08:00
tasks = append ( tasks , task )
context := types . Context { Tasks : tasks , Navigation : "edit" }
2016-02-01 21:45:04 +08:00
return context , nil
2015-11-13 17:04:42 +08:00
}
2015-11-21 14:39:15 +08:00
//TrashTask is used to delete the task
2016-05-14 15:26:24 +08:00
func TrashTask ( username string , id int ) error {
err := taskQuery ( "update task set task_status_id=?,last_modified_at=datetime() where user_id=(select id from user where username=?) and id=?" , taskStatus [ "DELETED" ] , username , id )
2015-11-14 18:56:53 +08:00
return err
}
2015-11-21 14:39:15 +08:00
//CompleteTask is used to mark tasks as complete
2016-05-14 15:26:24 +08:00
func CompleteTask ( username string , id int ) error {
err := taskQuery ( "update task set task_status_id=?, finish_date=datetime(),last_modified_at=datetime() where id=? and user_id=(select id from user where username=?) " , taskStatus [ "COMPLETE" ] , id , username )
2015-11-13 17:04:42 +08:00
return err
}
2015-11-21 14:39:15 +08:00
//DeleteAll is used to empty the trash
2016-05-14 15:26:24 +08:00
func DeleteAll ( username string ) error {
err := taskQuery ( "delete from task where task_status_id=? where user_id=(select id from user where username=?)" , taskStatus [ "DELETED" ] , username )
2015-11-13 17:04:42 +08:00
return err
}
2015-11-21 14:39:15 +08:00
//RestoreTask is used to restore tasks from the Trash
2016-05-14 15:26:24 +08:00
func RestoreTask ( username string , id int ) error {
2016-05-14 18:37:20 +08:00
err := taskQuery ( "update task set task_status_id=?,last_modified_at=datetime(),finish_date=null where id=? and user_id=(select id from user where username=?)" , taskStatus [ "PENDING" ] , id , username )
2016-01-31 20:30:10 +08:00
return err
}
2016-01-31 21:48:19 +08:00
//RestoreTaskFromComplete is used to restore tasks from the Trash
2016-05-14 15:26:24 +08:00
func RestoreTaskFromComplete ( username string , id int ) error {
2016-05-14 18:37:20 +08:00
err := taskQuery ( "update task set finish_date=null,last_modified_at=datetime(), task_status_id=? where id=? and user_id=(select id from user where username=?)" , taskStatus [ "PENDING" ] , id , username )
2016-01-09 13:03:35 +08:00
return err
}
2015-11-21 14:39:15 +08:00
//DeleteTask is used to delete the task from the database
2016-05-14 15:26:24 +08:00
func DeleteTask ( username string , id int ) error {
err := taskQuery ( "delete from task where id = ? and user_id=(select id from user where username=?)" , id , username )
2015-11-13 17:04:42 +08:00
return err
}
2015-11-21 14:39:15 +08:00
//AddTask is used to add the task in the database
2016-07-30 23:21:16 +08:00
func AddTask ( title , content , category string , taskPriority int , username , dueDate string ) error {
2016-05-14 15:26:24 +08:00
log . Println ( "AddTask: started function" )
2016-02-03 01:40:44 +08:00
var err error
2016-07-30 23:21:16 +08:00
timeDueDate , err := time . Parse ( "31/12/2016" , dueDate )
if err != nil {
log . Fatal ( err )
}
2016-05-14 15:26:24 +08:00
userID , err := GetUserID ( username )
2016-05-22 17:40:40 +08:00
if err != nil && ( title != "" || content != "" ) {
2016-05-14 15:26:24 +08:00
return err
}
2016-02-03 01:40:44 +08:00
if category == "" {
2016-07-30 23:21:16 +08:00
err = taskQuery ( "insert into task(title, content, priority, task_status_id, created_date, last_modified_at, user_id,due_date) values(?,?,?,?,datetime(), datetime(),?)" , title , content , taskPriority , taskStatus [ "PENDING" ] , userID , timeDueDate )
2016-02-03 01:40:44 +08:00
} else {
2016-05-14 15:26:24 +08:00
categoryID := GetCategoryByName ( username , category )
2016-07-30 23:21:16 +08:00
err = taskQuery ( "insert into task(title, content, priority, created_date, last_modified_at, cat_id, task_status_id, user_id,due_date) values(?,?,?,datetime(), datetime(), ?,?,?)" , title , content , taskPriority , categoryID , taskStatus [ "PENDING" ] , userID , timeDueDate )
2016-02-03 01:40:44 +08:00
}
2015-11-13 17:04:42 +08:00
return err
}
2016-05-14 15:26:24 +08:00
//GetCategoryIDByName will return the category ID for the category, used in the edit task
2016-02-06 02:09:09 +08:00
//function where we need to be able to update the categoryID of the task
2016-05-14 15:26:24 +08:00
func GetCategoryIDByName ( username string , category string ) int {
2016-02-06 02:09:09 +08:00
var categoryID int
2016-05-14 15:26:24 +08:00
getTaskSQL := "select c.id from category c , user u where u.id = c.user_id and name=? and u.username=?"
2016-02-06 02:09:09 +08:00
2016-05-14 15:26:24 +08:00
rows := database . query ( getTaskSQL , category , username )
2016-02-06 02:09:09 +08:00
defer rows . Close ( )
if rows . Next ( ) {
err := rows . Scan ( & categoryID )
if err != nil {
log . Println ( err )
//send email to respective people
}
}
return categoryID
}
2015-11-21 14:39:15 +08:00
//UpdateTask is used to update the tasks in the database
2016-05-14 15:26:24 +08:00
func UpdateTask ( id int , title , content , category string , priority int , username string ) error {
categoryID := GetCategoryIDByName ( username , category )
userID , err := GetUserID ( username )
if err != nil {
return err
}
err = taskQuery ( "update task set title=?, content=?, cat_id=?, priority = ? where id=? and user_id=?" , title , content , categoryID , priority , id , userID )
2016-01-31 21:56:14 +08:00
return err
}
2016-01-31 22:22:00 +08:00
//taskQuery encapsulates running multiple queries which don't do much things
2016-01-31 21:56:14 +08:00
func taskQuery ( sql string , args ... interface { } ) error {
2016-05-14 17:34:42 +08:00
log . Print ( "inside task query" )
2016-01-31 22:10:15 +08:00
SQL := database . prepare ( sql )
2016-01-31 21:48:19 +08:00
tx := database . begin ( )
2016-01-31 22:00:31 +08:00
_ , err = tx . Stmt ( SQL ) . Exec ( args ... )
2015-11-13 21:11:30 +08:00
if err != nil {
2016-05-14 15:26:24 +08:00
log . Println ( "taskQuery: " , err )
2015-11-13 17:04:42 +08:00
tx . Rollback ( )
} else {
2016-05-14 17:34:42 +08:00
err = tx . Commit ( )
if err != nil {
log . Println ( err )
return err
}
log . Println ( "Commit successful" )
2015-11-13 17:04:42 +08:00
}
return err
}
2015-11-21 14:39:15 +08:00
//SearchTask is used to return the search results depending on the query
2016-05-14 15:26:24 +08:00
func SearchTask ( username , query string ) ( types . Context , error ) {
2016-02-29 10:05:38 +08:00
var tasks [ ] types . Task
var task types . Task
2015-11-21 19:03:34 +08:00
var TaskCreated time . Time
2015-11-21 22:12:20 +08:00
var context types . Context
2015-11-13 17:04:42 +08:00
2016-05-14 15:26:24 +08:00
comments , err := GetComments ( username )
2016-02-29 10:05:38 +08:00
if err != nil {
log . Println ( "SearchTask: something went wrong in finding comments" )
}
2016-05-14 15:26:24 +08:00
userID , err := GetUserID ( username )
if err != nil {
return context , err
}
stmt := "select t.id, title, content, created_date, priority, c.name from task t, category c where t.user_id=? and c.id = t.cat_id and (title like '%" + query + "%' or content like '%" + query + "%') order by created_date desc"
2016-02-29 10:05:38 +08:00
2016-05-14 15:26:24 +08:00
rows := database . query ( stmt , userID , query , query )
2016-05-14 17:34:42 +08:00
defer rows . Close ( )
2015-11-13 21:11:30 +08:00
for rows . Next ( ) {
2016-02-29 10:05:38 +08:00
err := rows . Scan ( & task . Id , & task . Title , & task . Content , & TaskCreated , & task . Priority , & task . Category )
2015-11-13 21:11:30 +08:00
if err != nil {
2016-01-31 20:16:44 +08:00
log . Println ( err )
2015-11-13 17:04:42 +08:00
}
2016-02-29 10:05:38 +08:00
if comments [ task . Id ] != nil {
task . Comments = comments [ task . Id ]
}
task . Title = strings . Replace ( task . Title , query , "<span class='highlight'>" + query + "</span>" , - 1 )
task . Content = strings . Replace ( task . Content , query , "<span class='highlight'>" + query + "</span>" , - 1 )
task . Content = string ( md . Markdown ( [ ] byte ( task . Content ) ) )
TaskCreated = TaskCreated . Local ( )
2016-03-01 02:26:41 +08:00
CurrentTime := time . Now ( ) . Local ( )
week := TaskCreated . AddDate ( 0 , 0 , 7 )
if ( week . String ( ) < CurrentTime . String ( ) ) && ( task . Priority != "1" ) {
task . IsOverdue = true // If one week then overdue by default
}
2016-03-01 02:47:14 +08:00
task . Created = TaskCreated . Format ( "Jan 2 2006" )
2016-02-29 10:05:38 +08:00
tasks = append ( tasks , task )
2015-11-13 17:04:42 +08:00
}
2016-02-29 10:05:38 +08:00
context = types . Context { Tasks : tasks , Search : query , Navigation : "search" }
2016-05-14 15:26:24 +08:00
return context , nil
2015-11-13 17:04:42 +08:00
}
2016-02-15 01:46:22 +08:00
//GetComments is used to get comments, all of them.
//We do not want 100 different pages to show tasks, we want to use as few pages as possible
//so we are going to populate everything on the damn home pages
2016-05-14 15:26:24 +08:00
func GetComments ( username string ) ( map [ int ] [ ] types . Comment , error ) {
2016-02-15 01:46:22 +08:00
commentMap := make ( map [ int ] [ ] types . Comment )
2016-02-17 00:53:03 +08:00
var taskID int
var comment types . Comment
var created time . Time
2016-02-15 01:46:22 +08:00
2016-05-14 15:26:24 +08:00
userID , err := GetUserID ( username )
if err != nil {
return commentMap , err
}
2016-05-15 20:30:19 +08:00
stmt := "select c.id, c.taskID, c.content, c.created, u.username from comments c, task t, user u where t.id=c.taskID and c.user_id=t.user_id and t.user_id=u.id and u.id=?"
2016-05-14 15:26:24 +08:00
rows := database . query ( stmt , userID )
2016-02-15 01:46:22 +08:00
2016-05-14 17:34:42 +08:00
defer rows . Close ( )
2016-02-15 01:46:22 +08:00
for rows . Next ( ) {
2016-05-15 20:30:19 +08:00
err := rows . Scan ( & comment . ID , & taskID , & comment . Content , & created , & comment . Username )
2016-06-01 12:18:57 +08:00
comment . Content = string ( md . Markdown ( [ ] byte ( comment . Content ) ) )
2016-02-15 01:46:22 +08:00
if err != nil {
2016-02-17 00:53:03 +08:00
return commentMap , err
2016-02-15 01:46:22 +08:00
}
2016-02-17 00:53:03 +08:00
// comment.Content = string(md.Markdown([]byte(comment.Content))) ## have to fix the <p> issue markdown support
created = created . Local ( )
2016-03-01 02:47:14 +08:00
comment . Created = created . Format ( "Jan 2 2006 15:04:05" )
2016-02-17 00:53:03 +08:00
commentMap [ taskID ] = append ( commentMap [ taskID ] , comment )
2016-02-15 01:46:22 +08:00
}
2016-02-17 00:53:03 +08:00
return commentMap , nil
2016-02-15 01:46:22 +08:00
}
//AddComments will be used to add comments in the database
2016-05-14 15:26:24 +08:00
func AddComments ( username string , id int , comment string ) error {
userID , err := GetUserID ( username )
if err != nil {
return err
}
stmt := "insert into comments(taskID, content, created, user_id) values (?,?,datetime(),?)"
err = taskQuery ( stmt , id , comment , userID )
2016-02-15 01:46:22 +08:00
if err != nil {
return err
}
log . Println ( "added comment to task ID " , id )
return nil
}