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) } // 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 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() { // Define flags addMODFlag := flag.Bool("addMOD", false, "Add a MOD contact with first name and last name") firstName := flag.String("firstName", "", "First name of the contact") lastName := flag.String("lastName", "", "Last name of the contact") dateOfEntry := flag.String("date", "", "Date of entry") // orientationFlag is used to set the orientation of the journal entries (vertical or horizontal) orientationFlag := flag.String("orientation", "horizontal", "Orientation of the journal entries (vertical or horizontal)") addMODMeetingFlag := flag.Bool("addmeeting", false, "Add a MOD meeting") addMODEntryFlag := flag.Bool("addmod", false, "Add a MOD entry") addJobbylogEntryFlag := flag.Bool("addjobby", false, "Add a Jobbylog entry") addSleepEntryFlag := flag.Bool("addsleep", false, "Add a sleep entry") listPersonalEntriesFlag := flag.Bool("listpersonal", false, "List all personal entries") listMODEntriesFlag := flag.Bool("listmod", false, "List all MOD entries, not including those associated with a meeting") listJobbylogEntriesFlag := flag.Bool("listjobby", false, "List all Jobblylog entries") listSleepEntriesFlag := flag.Bool("listsleep", false, "List all sleep entries") // Parse flags flag.Parse() // TODO: Break these up into subcommands: // - add // - list // - update // - delete // ref: https://www.golinuxcloud.com/golang-flags-examples/ // add commands addCmd := flag.NewFlagSet("add", flag.ExitOnError) // add flags addPersonalEntryFlag := addCmd.Bool("personal", false, "Add a Personal entry") entry := addCmd.String("entry", "", "Content of the journal entry") // list commands listCmd := flag.NewFlagSet("list", flag.ExitOnError) 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:]) // create personal entry if *addPersonalEntryFlag { if *entry == "" { log.Fatal("Personal entry content must be provided - use the -entry flag") } addJournalEntry(*entry, JOURNAL_TYPE_PERSONAL) } // create MOD entry if *addMODEntryFlag { if *entry == "" { log.Fatal("MOD entry content must be provided - use the -entry flag") } addJournalEntry(*entry, JOURNAL_TYPE_MOD) } case "list": listCmd.Parse(os.Args[2:]) default: flag.PrintDefaults() os.Exit(1) } // Call functions based on flags if *addMODFlag { if *firstName == "" || *lastName == "" { log.Fatal("Both firstName and lastName must be provided for adding a MOD contact") } addMODContact(*firstName, *lastName) } else if *addMODMeetingFlag { if *entry == "" { log.Fatal("Meeting name must be provided with -entry flag") } if *dateOfEntry == "" { log.Fatal("Meeting date must be provided with -date flag") } addMODMeeting(*entry, *dateOfEntry, "Meeting") } else if *addSleepEntryFlag { if *entry == "" { log.Fatal("Sleep entry content must be provided") } addSleepEntry(*entry, JOURNAL_TYPE_SLEEP) } else if *addPersonalEntryFlag { if *entry == "" { log.Fatal("Personal entry content must be provided - use the -entry flag") } addJournalEntry(*entry, JOURNAL_TYPE_PERSONAL) } else if *addMODEntryFlag { if *entry == "" { log.Fatal("MOD entry content must be provided - use the -entry flag") } } else if *addJobbylogEntryFlag { if *entry == "" { log.Fatal("Jobblylog entry content must be provided - use the -entry flag") } addJournalEntry(*entry, JOURNAL_TYPE_JOBBY) } else 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 { flag.Usage() // Print usage if no valid flags are provided os.Exit(1) } }