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)
}
}