::requires 'ooSQLite.cls' ::requires "rxunixsys" LIBRARY ::requires 'ncurses.cls' ::requires 'app/utils.rex' ::CLASS AddressBookDB PUBLIC ::method init expose db /* Create database directory if it doesn't exist */ if SysFileExists(.dbPath) == .false then DO SAY "Initializing new address book" db = .ooSQLiteConnection~new(.dbPath) self~createTables END Else Do Say "Initializing existing address book" db = .ooSQLiteConnection~new(.dbPath,.ooSQLite~OPEN_READWRITE) End return ::METHOD getFileName expose db return db~fileName() ::METHOD closeDb expose db db~Close() RETURN ::METHOD createTables expose db /* Contacts table */ db~exec("CREATE TABLE IF NOT EXISTS contacts ("||, "id INTEGER PRIMARY KEY AUTOINCREMENT,"||, "first_name TEXT,"||, "last_name TEXT,"||, "created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,"||, "updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP"||, ")") /* Phone numbers table */ db~exec("CREATE TABLE IF NOT EXISTS phone_numbers ("||, "id INTEGER PRIMARY KEY AUTOINCREMENT,"||, "contact_id INTEGER,"||, "type TEXT,"||, -- home, work, mobile, etc. "number TEXT,"||, "FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE"||, ")") /* Email addresses table */ db~exec("CREATE TABLE IF NOT EXISTS email_addresses ("||, "id INTEGER PRIMARY KEY AUTOINCREMENT,"||, "contact_id INTEGER,"||, "type TEXT,"||, -- home, work, etc. "email TEXT,"||, "FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE"||, ")") /* Physical addresses table */ db~exec("CREATE TABLE IF NOT EXISTS addresses ("||, "id INTEGER PRIMARY KEY AUTOINCREMENT,"||, "contact_id INTEGER,"||, "type TEXT,"||, -- home, work, etc. "street TEXT,"||, "city TEXT,"||, "state TEXT,"||, "postal_code TEXT,"||, "country TEXT,"||, "FOREIGN KEY (contact_id) REFERENCES contacts(id) ON DELETE CASCADE"||, ")") return /* Contact CRUD Operations */ ::METHOD addContact expose db use arg contactDict /* Use a Rexx 'directory' to pass multiple values around */ sql = "INSERT INTO contacts (first_name, last_name) VALUES ('"contactDict~firstName"', '"contactDict~lastName"')" rc = db~exec(sql) if rc \= 0 then do say "Error adding contact:" db~errMsg() return -1 end contactId = db~lastInsertRowId() /* the row id of 'contacts' table is the master id */ self~addPhoneNumber(contactId, contactDict["PHONE_TYPE"], contactDict["PHONE_NUMBER"]) self~addEmailAddress(contactId, contactDict["EMAIL_TYPE"], contactDict["EMAIL_ADDRESS"]) self~addRealAddress(contactId, contactDict~addressType, contactDict~street, contactDict~city, contactDict~state, contactDict~postalCode, contactDict~country) return contactId ::METHOD getContact expose db use arg contactId returnedContent = .Directory~new() sql1 = "SELECT * FROM contacts WHERE id = "contactId contacts = db~exec(sql1,.true,.ooSQLite~OO_ARRAY_OF_DIRECTORIES) /* The result will be rendered as an array of Rexx 'directories' * which are basically analogous to python dictionaries. */ if contacts = .nil then DO Say 'NO CONTACTS FOUND' return END if contacts~size < 1 then do Say 'NO CONTACT FOUND' return END return contacts[1] /* Rexx is 1-indexed */ /*** returnedContent~firstName = contact["FIRST_NAME"] returnedContent~lastName = contact["LAST_NAME"] sql2 = "SELECT * FROM email_addresses WHERE contact_id = "contactId emails = db~exec(sql2,.true,.ooSQLite~OO_ARRAY_OF_DIRECTORIES) email_array = .Array~new() do row over emails email_array.append(row["EMAIL"]) END returnedContent~emailAddresses = email_array sql3 = "SELECT * FROM phone_numbers WHERE contact_id = "contactId phones = db~exec(sql3,.true,.ooSQLite~OO_ARRAY_OF_DIRECTORIES) phone_dir = .Directory~new() phone_array = .Array~new() do phone over phones phone_dir~put(phone["NUMBER"], phone["TYPE"]) phone_array.append(phone_dir) END returnedContent~phoneNumbers = phone_array sql4 = "SELECT * FROM addresses WHERE contact_id = "contactId addresses = db~exec(sql3,.true,.ooSQLite~~OO_ARRAY_OF_DIRECTORIES) address_dir = .Dictionary~new() address_array = .Array~new() do address over addresses address_dir~type = address["TYPE"] address_dir~street = address["STREET"] address_dir~city = address["CITY"] address_dir~state = address["STATE"] address_dir~postal_code = address["POSTAL_CODE"] address_dir~country = address["COUNTRY"] address_array.append(address_dir) END returnedContent~addresses = address_array return returnedContent ***/ ::METHOD getAllContacts expose db sql = "SELECT * FROM contacts ORDER BY last_name, first_name" contacts = db~exec(sql, .true, .ooSQLite~OO_ARRAY_OF_DIRECTORIES) return contacts ::METHOD searchContacts expose db use arg searchTerm contactsList = .Array~new() sql = "SELECT id FROM contacts WHERE first_name LIKE '%"searchTerm"%' OR last_name LIKE '%"searchTerm"%' ORDER BY last_name, first_name" contactIds = db~exec(sql, .true, .ooSQLite~OO_ARRAY_OF_DIRECTORIES) do contactDir over contactIds contactId = contactDir["ID"] contact = self~getContact(contactId) if contact \= .nil then do contactsList~append(contact) end end return contactsList ::METHOD updateContact expose db use arg contactId, firstName, lastName sql = "UPDATE contacts SET first_name = '"firstName"', last_name = '"lastName"' WHERE id = "contactId rc = db~exec(sql) if rc \= 0 then do say "Error updating contact:" db~errMsg() return -1 end return rc /** self~removeContactPhones(contactId) self~removeContactEmails(contactId) self~removeContactAddresses(contactId) /* Then add new entries */ self~addPhoneNumber(contactId, contactDict["PHONE_TYPE"], contactDict["PHONE_NUMBER"]) self~addEmailAddress(contactId, contactDict["EMAIL_TYPE"], contactDict["EMAIL_ADDRESS"]) self~addAddress(contactId, contactDict~addressType, contactDict~street, contactDict~city, contactDict~state, contactDict~postalCode, contactDict~country) return 0 **/ ::METHOD deleteContact expose db use arg contactId /* Delete all related records first (assuming foreign key constraints) */ self~removeContactPhones(contactId) self~removeContactEmails(contactId) self~removeContactAddresses(contactId) /* Delete the contact record */ sql = "DELETE FROM contacts WHERE id = "contactId rc = db~exec(sql) if rc \= 0 then do say "Error deleting contact:" db~errMsg() return -1 end return 0 ::METHOD getPhoneNumbers expose db use arg contactId phones = .Array~new sql = "SELECT id, type, number FROM phone_numbers WHERE contact_id = "contactId phones = db~exec(sql,.true, .ooSQLite~OO_ARRAY_OF_DIRECTORIES) return phones ::METHOD addPhoneNumber expose db use arg contactId, phoneType, phoneNumber if phoneType = .nil | phoneNumber = .nil then return 0 sql = "INSERT INTO phone_numbers (contact_id, type, number) VALUES ("contactId", '"phoneType"', '"phoneNumber"')" rc = db~exec(sql) if rc \= 0 then DO Say "Unable to insert phone number for contact id "contactId return rc END phone_id = db~lastInsertRowId() return phone_id ::METHOD updatePhoneNumber expose db use arg phoneId, phoneType, phoneNumber sql = "UPDATE phone_numbers SET type = '"phoneType"', number = '"phoneNumber"' WHERE id = "phoneId rc = db~exec(sql) if rc \= 0 then DO Say "Unable to update phone number or type for phone id "contactId return rc END return rc ::METHOD deletePhoneNumber expose db use arg phoneId stmt = db~prepare("DELETE FROM phone_numbers WHERE id = ?") stmt~bind(1, phoneId) stmt~step return db~changes() > 0 /* Email address operations */ ::METHOD addEmailAddress expose db use arg contactId, emailType, emailAddress if emailType = .nil | emailAddress = .nil then return 0 sql = "INSERT INTO email_addresses (contact_id, type, email) VALUES ("contactId", '"emailType"', '"emailAddress"')" rc = db~exec(sql) if rc \= 0 then do say "Error adding email address:" db~errMsg() return -1 end email_id = db~lastInsertRowId() return email_id ::METHOD getEmailAddresses expose db use arg contactId sql = "SELECT id, type, email FROM email_addresses WHERE contact_id = "contactId emails = db~exec(sql, .true, .ooSQLite~OO_ARRAY_OF_DIRECTORIES) return emails ::METHOD updateEmailAddress expose db use arg emailId, emailType, emailAddress sql = "UPDATE email_addresses SET type = '"emailType"', email = '"emailaddress"' WHERE id = "emailId rc = db~exec(sql) if rc \= 0 then do say "Error adding email address:" db~errMsg() return -1 end return rc ::METHOD deleteEmailAddress expose db use arg emailId stmt = db~prepare("DELETE FROM email_addresses WHERE id = ?") stmt~bind(1, emailId) stmt~step return db~changes() > 0 /* Physical address operations */ ::METHOD addRealAddress expose db use arg contactId, addressType, street, city, state, postalCode, country if addressType = .nil | street = .nil then return 0 sql = "INSERT INTO addresses (contact_id, type, street, city, state, postal_code, country)", "VALUES ("contactId", '"addressType"', '"street"', '"city"', '"state"', '"postalCode"', '"country"')" rc = db~exec(sql) if rc \= 0 then do say "Error adding address:" db~errMsg() return -1 end addr_id = db~lastInsertRowId() return addr_id ::METHOD getRealAddresses expose db use arg contactId sql = "SELECT id, type, street, city, state, postal_code, country" || , " FROM addresses WHERE contact_id = " contactId addresses = db~exec(sql, .true, .ooSQLite~OO_ARRAY_OF_DIRECTORIES) return addresses ::METHOD updateRealAddress expose db use arg addressId, addressType, street, city, state, postalCode, country sql = "UPDATE addresses SET type = '"addressType"', street = '"street"', city = '"city"'," || , " state = '"state"', postal_code = '"postalCode"', country = '"country"' WHERE id = "addressId rc = db~exec(sql) if rc \= 0 then do say "Error updating address:" db~errMsg() return -1 end return rc ::METHOD deleteAddress expose db use arg addressId sql="DELETE FROM addresses WHERE id = "addressId rc = db~exec(sql) if rc \= 0 then do say "Error removing address:" db~errMsg() return -1 end return 0 ::METHOD removeContactPhones expose db use arg contactId sql = "DELETE FROM phone_numbers WHERE contact_id = "contactId rc = db~exec(sql) if rc \= 0 then do say "Error removing phone numbers:" db~errMsg() return -1 end return 0 ::METHOD removeContactEmails expose db use arg contactId sql = "DELETE FROM email_addresses WHERE contact_id = "contactId rc = db~exec(sql) if rc \= 0 then do say "Error removing email addresses:" db~errMsg() return -1 end return 0 ::METHOD removeContactAddresses expose db use arg contactId sql = "DELETE FROM addresses WHERE contact_id = "contactId rc = db~exec(sql) if rc \= 0 then do say "Error removing addresses:" db~errMsg() return -1 end return 0