summaryrefslogblamecommitdiffstats
path: root/tjp.go
blob: 500b80c5d3acbf157dbfa3cdba7e209dd459c78f (plain) (tree)



















                                              






                                   















                                                                                                










                                                                                                                                      






                                                                                                                                                         
                                                   









                                                                                                             
                                                   

                                                                                                            




                              
                                                       

                                                                                                                         




                              


















                                                                                                   




















                                                                                                                                                                                                                                                                                                                                               

                                                              

                          


                                  




                                            
                                                                                                                                                                                          

                                                        
                                                                                                                                                                   
                                            


                                                                                                                                                                   


                                                                                                                                                                   
         
 



                              
                           
 


                                                                            

                            
                                                                          

                                      





                                                                                                                   
                                              
                                                              
                                                       
                                                            
                 

                                                                                         


         




                                                                     
                                                                                       


                                                             

 

                                                                                                                  



                                                   



                                             
                                                        



























                                                  


                      






                                                                   
                      



                                                                                      

                                                                               
                                                                                                  

                                                                                   
                                                                           
                                                                                      
                                                                 
 
                       
                                                            


                                                                                                                               

                                                                                                                    


                                                                                                                                     








                                                                   




                                                                                                          










                                                                                                          



                                                                                                     




                                                                                           




                                                                                           

                                              
                 
                          

                                          









                                                                                   

                                                         



                                               



                                    
 
package main

import (
	"database/sql"
	"flag"
	"fmt"
	"log"
	"os"
	"time"

	_ "github.com/lib/pq"
)

// Database connection details
const (
	host   = "postgres.banded-neon.ts.net"
	user   = "postgresql"
	dbname = "journal"
)

const (
	JOURNAL_TYPE_MOD = iota + 1
	JOURNAL_TYPE_PERSONAL
	JOURNAL_TYPE_JOBBY
	JOURNAL_TYPE_SLEEP
)

var db *sql.DB

func init() {
	var err error
	// Connection string
	psqlInfo := fmt.Sprintf("host=%s user=%s dbname=%s sslmode=disable", host, user, dbname)
	db, err = sql.Open("postgres", psqlInfo)
	if err != nil {
		log.Fatal(err)
	}

	if err = db.Ping(); err != nil {
		log.Fatal(err)
	}
}

// addMODMeeting adds a MOD meeting to the database
func addMODMeeting(name string, date string, subject string) {
	var meetingId int
	query := fmt.Sprintf("INSERT INTO meetings (name, date, subject) VALUES ('%s', '%s', '%s') RETURNING id", name, date, subject)
	err := db.QueryRow(query).Scan(&meetingId)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("MOD meeting added with ID: %d\n", meetingId)
}

func addMODMeetingJournalEntry(entry string, meetingID int) {
	_, err := db.Exec(fmt.Sprintf("INSERT INTO journal_entries (entry, type, meeting_id) VALUES ('%s', %d, %d)", entry, JOURNAL_TYPE_MOD, meetingID))
	if err != nil {
		log.Fatal(err)
	}
}

// addMODContact adds a MOD contact to the database
func addMODContact(firstName, lastName string) {
	var contactId int
	query := `INSERT INTO contacts (first_name, last_name, contact_type) VALUES ($1, $2, 3) RETURNING id`
	err := db.QueryRow(query, firstName, lastName).Scan(&contactId)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("MOD contact added with ID: %d\n", contactId)
}

// addSleepEntry adds a sleep entry to the database
func addSleepEntry(entry string, journalType int) {
	_, err := db.Exec("INSERT INTO journal_entries (entry, type) VALUES ('%s', %d)", entry, journalType)
	if err != nil {
		log.Fatal(err)
	}
}

// addJournalEntry adds a journal entry to the database
func addJournalEntry(entry string, journalType int) {
	_, err := db.Exec(fmt.Sprintf("INSERT INTO journal_entries (entry, type) VALUES ('%s', %d)", entry, journalType))
	if err != nil {
		log.Fatal(err)
	}
}

// List meetings
func listMeetings() {
	rows, err := db.Query("SELECT id, name, date, subject FROM meetings ORDER BY date ASC")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
	fmt.Println("Meetings:")
	for rows.Next() {
		var id int
		var date time.Time
		var name, subject string
		if err := rows.Scan(&id, &name, &date, &subject); err != nil {
			log.Fatal(err)
		}
		fmt.Printf("ID: %d\t %s\t %s (%s)\n", id, date.Format("2006-01-02"), name, subject)
	}
}

func listEntriesForMeeting(meetingID int) {
	// this should be a join which includes the meeting name
	rows, err := db.Query(fmt.Sprintf("select journal_entries.id, journal_entries.date_added, journal_entries.entry, meetings.name from journal_entries inner join meetings on journal_entries.meeting_id = meetings.id where journal_entries.meeting_id = %d order by journal_entries.date_added asc, journal_entries.id asc", meetingID))

	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
	fmt.Println(fmt.Sprintf("Entries for meeting ID: %d", meetingID))
	for rows.Next() {
		var id int
		var date time.Time
		var entry string
		var meetingName string
		if err := rows.Scan(&id, &date, &entry, &meetingName); err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%d\t %s\t %s\t %s\n", id, meetingName, date.Format("2006-01-02 - 15:04:05"), entry)
	}
}

// List journal entries, dependent on journalType
func listjournalEntries(orientation string, journalType int) {
	var rows *sql.Rows

	var id sql.NullInt64
	var dateAdded sql.NullTime
	var entry sql.NullString
	var header string
	var err error

	// if MOD journalType
	if journalType == JOURNAL_TYPE_MOD {
		rows, err = db.Query(fmt.Sprintf("SELECT id, date_added, entry FROM journal_entries WHERE type = %d AND meeting_id IS NULL ORDER BY date_added ASC, id ASC", journalType))
		header = "MOD entries:"
	} else if journalType == JOURNAL_TYPE_PERSONAL {
		rows, err = db.Query(fmt.Sprintf("SELECT id, date_added, entry FROM journal_entries WHERE type = %d ORDER BY date_added ASC, id ASC", journalType))
		header = "Personal entries:"
	} else if journalType == JOURNAL_TYPE_JOBBY {
		rows, err = db.Query(fmt.Sprintf("SELECT id, date_added, entry FROM journal_entries WHERE type = %d ORDER BY date_added ASC, id ASC", journalType))
		header = "Jobblylog entries:"
	} else if journalType == JOURNAL_TYPE_SLEEP {
		rows, err = db.Query(fmt.Sprintf("SELECT id, date_added, entry FROM journal_entries WHERE type = %d ORDER BY date_added ASC, id ASC", journalType))
		header = "Sleep entries:"
	}

	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
	fmt.Println(header)

	var previousDateAdded sql.NullTime   // Track the previous date only
	previousDateAdded.Time = time.Time{} // Initialize to zero value

	// Scan for each row
	for rows.Next() {
		if err := rows.Scan(&id, &dateAdded, &entry); err != nil {
			log.Fatal(err)
		}

		// Check if previousDateAdded is valid to avoid printing dash before first entry
		if previousDateAdded.Valid && dateAdded.Valid && !sameDay(previousDateAdded.Time, dateAdded.Time) {
			fmt.Println("----") // Print a dashed line between different day entries
		}

		if orientation == "vertical" {
			formatVertically(id, dateAdded, entry)
		} else if orientation == "horizontal" {
			formatHorizontally(dateAdded, entry)
		}

		previousDateAdded = dateAdded // Update previousDateAdded to current date
	}
}

// Function to determine if two times are on the same day
func sameDay(t1, t2 time.Time) bool {
	return t1.Year() == t2.Year() && t1.YearDay() == t2.YearDay()
}

func formatVertically(id sql.NullInt64, dateAdded sql.NullTime, entry sql.NullString) {
	fmt.Printf("ID: %s\n", formatInt64(id))
	fmt.Printf("Date Added: %s\n", formatTime(dateAdded))
	fmt.Printf("Entry: %s\n", formatString(entry))
}

func formatHorizontally(dateAdded sql.NullTime, entry sql.NullString) {
	fmt.Printf("%s %s | %s\n", formatShortTime(dateAdded), getWeekdayFromDate(dateAdded), formatString(entry))
}

func getWeekdayFromDate(date sql.NullTime) string {
	return date.Time.Format("Monday")
}

func formatShortTime(t sql.NullTime) string {
	if t.Valid {
		return t.Time.Format("2006-01-02 15:04")
	}
	return "NULL"
}

// Helper functions to handle sql.Null* types
func formatInt64(v sql.NullInt64) string {
	if v.Valid {
		return fmt.Sprintf("%d", v.Int64)
	}
	return "NULL"
}

func formatTime(v sql.NullTime) string {
	if v.Valid {
		return v.Time.Format(time.RFC3339)
	}
	return "NULL"
}

func formatString(v sql.NullString) string {
	if v.Valid {
		return v.String
	}
	return "NULL"
}

func main() {

	// Parse flags
	flag.Parse()

	// TODO: Break these up into subcommands:
	// - add
	// - list
	// - update
	// - delete
	// ref: https://www.golinuxcloud.com/golang-flags-examples/

	// add command
	addCmd := flag.NewFlagSet("add", flag.ExitOnError)

	// add flags
	addPersonalEntryFlag := addCmd.Bool("personal", false, "Add a Personal entry")
	addMODEntryFlag := addCmd.Bool("mod", false, "Add a MOD entry")
	addMODMeetingFlag := addCmd.Bool("meeting", false, "Add a MOD meeting")
	addMODMeetingNoteFlag := addCmd.Bool("meeting-note", false, "Add a note to a MOD meeting")
	addJobbylogEntryFlag := addCmd.Bool("jobby", false, "Add a Jobbylog entry")
	addSleepEntryFlag := addCmd.Bool("sleep", false, "Add a sleep entry")
	entry := addCmd.String("entry", "", "Content of the journal entry")
	meetingID := addCmd.Int("meeting-id", 1, "ID of the meeting to add a note to")
	dateOfEntry := addCmd.String("date", "", "Date of entry")

	// list command
	listCmd := flag.NewFlagSet("list", flag.ExitOnError)
	listPersonalEntriesFlag := listCmd.Bool("personal", false, "List all personal entries")
	listMODEntriesFlag := listCmd.Bool("mod", false, "List all MOD entries, not including those associated with a meeting")
	listMODMeetingsFlag := listCmd.Bool("meetings", false, "List all MOD meetings")
	meetingID = listCmd.Int("meeting-id", 1, "ID of the meeting to add a note to")
	listEntriesForMODMeetingFlag := listCmd.Bool("meeting-entries", false, "List all entries for a MOD meeting")
	listJobbylogEntriesFlag := listCmd.Bool("jobby", false, "List all Jobblylog entries")
	listSleepEntriesFlag := listCmd.Bool("sleep", false, "List all sleep entries")
	orientationFlag := listCmd.String("orientation", "horizontal", "Orientation of the journal entries (vertical or horizontal)")

	if len(os.Args) < 2 {
		fmt.Println("expected 'add' or 'list' subcommands")
		os.Exit(1)
	}

	switch os.Args[1] {
	case "add":
		addCmd.Parse(os.Args[2:])
		if *addPersonalEntryFlag {
			if *entry == "" {
				log.Fatal("Personal entry content must be provided - use the -entry flag")
			}
			addJournalEntry(*entry, JOURNAL_TYPE_PERSONAL)
		} else if *addSleepEntryFlag {
			if *entry == "" {
				log.Fatal("Sleep entry content must be provided - use the -entry flag")
			}
			addJournalEntry(*entry, JOURNAL_TYPE_SLEEP)
		} else if *addJobbylogEntryFlag {
			if *entry == "" {
				log.Fatal("Jobbylog entry content must be provided - use the -entry flag")
			}
			addJournalEntry(*entry, JOURNAL_TYPE_JOBBY)
		} else if *addMODEntryFlag {
			if *entry == "" {
				log.Fatal("MOD entry content must be provided - use the -entry flag")
			}
			addJournalEntry(*entry, JOURNAL_TYPE_MOD)
		} else if *addMODMeetingFlag {
			if *entry == "" {
				log.Fatal("Meeting name must be provided with -entry flag")
			}
			addMODMeeting(*entry, *dateOfEntry, "Default meeting")
		} else if *addMODMeetingNoteFlag {
			if *entry == "" {
				log.Fatal("Meeting note must be provided with -entry flag")
			}
			addMODMeetingJournalEntry(*entry, *meetingID)
		} else {
			addCmd.PrintDefaults()
		}
		os.Exit(1)
	case "list":
		listCmd.Parse(os.Args[2:])
		if *listPersonalEntriesFlag {
			listjournalEntries(*orientationFlag, JOURNAL_TYPE_PERSONAL)
		} else if *listMODEntriesFlag {
			listjournalEntries(*orientationFlag, JOURNAL_TYPE_MOD)
		} else if *listJobbylogEntriesFlag {
			listjournalEntries(*orientationFlag, JOURNAL_TYPE_JOBBY)
		} else if *listSleepEntriesFlag {
			listjournalEntries(*orientationFlag, JOURNAL_TYPE_SLEEP)
		} else if *listMODMeetingsFlag {
			listMeetings()
		} else if *listEntriesForMODMeetingFlag {
			listEntriesForMeeting(*meetingID)
		} else {
			listCmd.PrintDefaults()
		}
		os.Exit(1)
	default:
		flag.PrintDefaults()
		os.Exit(1)
	}
}