correct tests, the way god intended
This commit is contained in:
		
							parent
							
								
									1571221293
								
							
						
					
					
						commit
						6dbd58faf4
					
				
							
								
								
									
										317
									
								
								addbook.rex
									
									
									
									
									
								
							
							
						
						
									
										317
									
								
								addbook.rex
									
									
									
									
									
								
							@ -18,7 +18,8 @@ app~run()
 | 
				
			|||||||
Do forever
 | 
					Do forever
 | 
				
			||||||
  if .environment['STOPNOW'] = 1 then do
 | 
					  if .environment['STOPNOW'] = 1 then do
 | 
				
			||||||
    app~cleanup()  /* Clean up before exiting */
 | 
					    app~cleanup()  /* Clean up before exiting */
 | 
				
			||||||
    address system 'clear'
 | 
					    call SysCls
 | 
				
			||||||
 | 
					    Say .environment["REXX_PATH"]
 | 
				
			||||||
    Say "Exiting Address Book."
 | 
					    Say "Exiting Address Book."
 | 
				
			||||||
    exit 0
 | 
					    exit 0
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
@ -27,18 +28,6 @@ end
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Exit
 | 
					Exit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
::routine SysWait
 | 
					 | 
				
			||||||
  use arg seconds
 | 
					 | 
				
			||||||
  address system 'sleep' seconds
 | 
					 | 
				
			||||||
  return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
::ROUTINE setEnv
 | 
					 | 
				
			||||||
    .local~home = SysGetpwnam("gmgauthier", "d")
 | 
					 | 
				
			||||||
    .local~projectRoot = .home||"/Projects/rexx-address-book"
 | 
					 | 
				
			||||||
    .local~dbPath = .projectRoot||"/db/contacts.sqlite"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
::CLASS AddressBookApp PUBLIC
 | 
					::CLASS AddressBookApp PUBLIC
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ::METHOD Init
 | 
					    ::METHOD Init
 | 
				
			||||||
@ -64,300 +53,20 @@ Exit
 | 
				
			|||||||
      db~closeDb()
 | 
					      db~closeDb()
 | 
				
			||||||
      return
 | 
					      return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					::ROUTINE setEnv
 | 
				
			||||||
 | 
					    .environment~home = SysGetpwnam("gmgauthier", "d")
 | 
				
			||||||
 | 
					    .environment~projectRoot = .home||"/Projects/rexx-address-book"
 | 
				
			||||||
 | 
					    .environment~pkgPath = .projectRoot||"/app"
 | 
				
			||||||
 | 
					    .environment~dbPath = .projectRoot||"/db"
 | 
				
			||||||
 | 
					    .environment["REXX_PATH"] = .projectRoot||";"||.pkgPath||";"||.dbPath||";"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
::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
 | 
					 | 
				
			||||||
          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
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* UI handling class */
 | 
					 | 
				
			||||||
::class AddressBookUI public
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ::method init
 | 
					 | 
				
			||||||
      expose win mainMenu
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ::method initialize
 | 
					 | 
				
			||||||
      expose win mainMenu
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      win = self~DrawMainPanel(0, .true) /* default colour, draw border box */
 | 
					 | 
				
			||||||
      self~setupMainMenu(win)
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ::METHOD DrawMainPanel
 | 
					 | 
				
			||||||
        use arg clrset, with_box
 | 
					 | 
				
			||||||
        win = .window~new()                    /* The first invocation must be with no arguments */
 | 
					 | 
				
			||||||
        win~curs_set(0)                        /* Hide the cursor by default                     */
 | 
					 | 
				
			||||||
        win~keypad(1)                          /* Enable function keys and arrow keys            */
 | 
					 | 
				
			||||||
        win~raw
 | 
					 | 
				
			||||||
        win~noecho
 | 
					 | 
				
			||||||
        win~clear
 | 
					 | 
				
			||||||
        win~cbreak
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        self~SetPanelColor(win, clrset)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if with_box = .true then DO
 | 
					 | 
				
			||||||
                win~box(0, 0) /* Draw box with default ACS characters */
 | 
					 | 
				
			||||||
                win~refresh
 | 
					 | 
				
			||||||
        END
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return win
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ::METHOD DrawSubPanel
 | 
					 | 
				
			||||||
        use arg height, width, starty, startx, clrset, title, with_box
 | 
					 | 
				
			||||||
        new_win = .window~new(height, width, starty, startx)
 | 
					 | 
				
			||||||
        new_win~curs_set(0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /* Set the panel color */
 | 
					 | 
				
			||||||
        self~SetPanelColor(new_win, clrset)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /* Display title */
 | 
					 | 
				
			||||||
        new_win~attron(new_win~A_BOLD)
 | 
					 | 
				
			||||||
        new_win~mvaddstr(starty-(starty-2), (width - title~length) % 2, title)
 | 
					 | 
				
			||||||
        new_win~attroff(new_win~A_BOLD)
 | 
					 | 
				
			||||||
        new_win~refresh
 | 
					 | 
				
			||||||
        if with_box = .true then DO
 | 
					 | 
				
			||||||
            new_win~box(0,0)
 | 
					 | 
				
			||||||
            new_win~refresh
 | 
					 | 
				
			||||||
        END
 | 
					 | 
				
			||||||
        return new_win
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ::METHOD SetPanelColor
 | 
					 | 
				
			||||||
        use arg panel, clrset
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        panel~start_color()
 | 
					 | 
				
			||||||
        SELECT
 | 
					 | 
				
			||||||
            /* white text on blue background */
 | 
					 | 
				
			||||||
            when clrset = 1 then panel~init_pair(2, panel~COLOR_WHITE, panel~COLOR_BLUE)
 | 
					 | 
				
			||||||
            /* white text on grey background */
 | 
					 | 
				
			||||||
            when clrset = 2 then panel~init_pair(3, panel~COLOR_WHITE, panel~COLOR_GREEN)
 | 
					 | 
				
			||||||
            /* yellow text on red background */
 | 
					 | 
				
			||||||
            when clrset = 3 then panel~init_pair(4, panel~COLOR_YELLOW, panel~COLOR_RED)
 | 
					 | 
				
			||||||
            /* black text on white background */
 | 
					 | 
				
			||||||
            when clrset = 4 then panel~init_pair(5, panel~COLOR_BLACK, panel~COLOR_WHITE)
 | 
					 | 
				
			||||||
            /* black text on green background */
 | 
					 | 
				
			||||||
            when clrset = 5 then panel~init_pair(6, panel~COLOR_BLACK, panel~COLOR_GREEN)
 | 
					 | 
				
			||||||
            /* black text on cyan background */
 | 
					 | 
				
			||||||
            when clrset = 6 then panel~init_pair(7, panel~COLOR_BLACK, panel~COLOR_CYAN)
 | 
					 | 
				
			||||||
            /* Default to white text on black background */
 | 
					 | 
				
			||||||
            OTHERWISE DO
 | 
					 | 
				
			||||||
                clrset=0
 | 
					 | 
				
			||||||
                panel~init_pair(1, panel~COLOR_WHITE, panel~COLOR_BLACK)
 | 
					 | 
				
			||||||
            END
 | 
					 | 
				
			||||||
        END
 | 
					 | 
				
			||||||
        panel~bkgd(panel~color_pair(clrset+1))
 | 
					 | 
				
			||||||
        panel~refresh
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ::method setupMainMenu
 | 
					 | 
				
			||||||
      expose mainMenu win menuwin menuItems menu_keys
 | 
					 | 
				
			||||||
      use arg win
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      max_y = win~lines
 | 
					 | 
				
			||||||
      max_x = win~cols
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      menu_height = 21
 | 
					 | 
				
			||||||
      menu_width = 40
 | 
					 | 
				
			||||||
      start_y = (max_y - menu_height) % 2
 | 
					 | 
				
			||||||
      start_x = (max_x - menu_width) % 2
 | 
					 | 
				
			||||||
      clrset=1
 | 
					 | 
				
			||||||
      title = "Rexx Address Book"
 | 
					 | 
				
			||||||
      with_box = .true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      menuwin = self~DrawSubPanel(menu_height, menu_width, start_y, start_x, clrset, title, with_box)
 | 
					 | 
				
			||||||
      menuItems = .array~of("[A]dd Contact", "[R]emove Contact", "[E]dit Contact", "[S]earch", "[L]ist All", "E[X]it")
 | 
					 | 
				
			||||||
      menu_keys = .array~of("a", "r", "e", "s", "l", "x")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      /* Display menu items */
 | 
					 | 
				
			||||||
      selected = 1
 | 
					 | 
				
			||||||
      self~DrawMenu(menuwin, menuItems, selected, win)
 | 
					 | 
				
			||||||
      /* menuwin~mvaddstr(menu_height - 2, 3, "Type 'X' to exit") */
 | 
					 | 
				
			||||||
      menuwin~refresh
 | 
					 | 
				
			||||||
      RETURN
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ::method DrawMenu
 | 
					 | 
				
			||||||
      expose win menuItems selected mainwin
 | 
					 | 
				
			||||||
      use arg win, items, selected, mainwin
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      do i = 1 to items~items
 | 
					 | 
				
			||||||
        if i = selected then do
 | 
					 | 
				
			||||||
          win~attron(mainwin~COLOR_PAIR(2))
 | 
					 | 
				
			||||||
          win~attron(mainwin~A_BOLD)
 | 
					 | 
				
			||||||
          win~mvaddstr(i+3, 2, items[i])
 | 
					 | 
				
			||||||
          win~attroff(mainwin~COLOR_PAIR(2))
 | 
					 | 
				
			||||||
          win~attroff(mainwin~A_BOLD)
 | 
					 | 
				
			||||||
        end
 | 
					 | 
				
			||||||
        else do
 | 
					 | 
				
			||||||
          win~mvaddstr(i+3, 2, items[i])
 | 
					 | 
				
			||||||
        end
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
      win~refresh
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /** MAIN LOOP **/
 | 
					 | 
				
			||||||
    ::method mainLoop
 | 
					 | 
				
			||||||
      expose win mainMenu menuwin
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      running = .true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      do while running
 | 
					 | 
				
			||||||
        menuwin~refresh
 | 
					 | 
				
			||||||
        key = win~getch
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        menu_keys = .array~of("a", "r", "e", "s", "l", "x")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        select
 | 
					 | 
				
			||||||
          when key = win~KEY_UP then do
 | 
					 | 
				
			||||||
            if selected > 1 then selected = selected - 1
 | 
					 | 
				
			||||||
            call DrawMenu menuwin, menuItems, selected, win
 | 
					 | 
				
			||||||
          end
 | 
					 | 
				
			||||||
          when key = win~KEY_DOWN then do
 | 
					 | 
				
			||||||
            if selected < menuItems~items then selected = selected + 1
 | 
					 | 
				
			||||||
            call DrawMenu menuwin, menuItems, selected, win
 | 
					 | 
				
			||||||
          end
 | 
					 | 
				
			||||||
          when key = D2C(10) | key = D2C(13) then do /* Enter key - numeric codes only */
 | 
					 | 
				
			||||||
            menuwin~refresh
 | 
					 | 
				
			||||||
            call ProcessSelection menu_keys[selected], home
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
          end
 | 
					 | 
				
			||||||
          when key = D2C(88) | key = D2C(120) | key = "x" | key = C2D("x") then do
 | 
					 | 
				
			||||||
            menuwin~endwin
 | 
					 | 
				
			||||||
            .environment['STOPNOW'] = 1
 | 
					 | 
				
			||||||
            RETURN
 | 
					 | 
				
			||||||
          end
 | 
					 | 
				
			||||||
          otherwise do
 | 
					 | 
				
			||||||
            if datatype(key) = 'CHAR' then do
 | 
					 | 
				
			||||||
              key = lower(key)
 | 
					 | 
				
			||||||
              pos = self~findInArray(menu_keys, key)
 | 
					 | 
				
			||||||
              if pos > 0 then do
 | 
					 | 
				
			||||||
                menuwin~mvaddstr(19 - 1, 18, "Letter selection ["||key||"]")
 | 
					 | 
				
			||||||
                menuwin~refresh
 | 
					 | 
				
			||||||
                call SysWait 0.5
 | 
					 | 
				
			||||||
                self~ProcessSelection(menuwin, key)
 | 
					 | 
				
			||||||
                return
 | 
					 | 
				
			||||||
              end
 | 
					 | 
				
			||||||
            end
 | 
					 | 
				
			||||||
          end
 | 
					 | 
				
			||||||
        end
 | 
					 | 
				
			||||||
      end /* do while running */
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /* Process selection */
 | 
					 | 
				
			||||||
    ::METHOD  ProcessSelection
 | 
					 | 
				
			||||||
      use arg menuwin, key_char
 | 
					 | 
				
			||||||
      select
 | 
					 | 
				
			||||||
        when key_char = 'a' then do
 | 
					 | 
				
			||||||
            menuwin~mvaddstr(19 - 3, 5, "I would launch the ADD panel     ");menuwin~refresh;
 | 
					 | 
				
			||||||
            menuwin~refresh
 | 
					 | 
				
			||||||
            call SysWait 1
 | 
					 | 
				
			||||||
            END
 | 
					 | 
				
			||||||
        when key_char = 'r' then do
 | 
					 | 
				
			||||||
            menuwin~mvaddstr(19 - 3, 5, "I would launch the REMOVE panel  ");menuwin~refresh;
 | 
					 | 
				
			||||||
            menuwin~refresh
 | 
					 | 
				
			||||||
            call SysWait 1
 | 
					 | 
				
			||||||
            END
 | 
					 | 
				
			||||||
        when key_char = 'e' then do
 | 
					 | 
				
			||||||
            menuwin~mvaddstr(19 - 3, 5, "I would launch the EDIT panel    ");menuwin~refresh;
 | 
					 | 
				
			||||||
            menuwin~refresh
 | 
					 | 
				
			||||||
            call SysWait 1
 | 
					 | 
				
			||||||
            END
 | 
					 | 
				
			||||||
        when key_char = 's' then do
 | 
					 | 
				
			||||||
            menuwin~mvaddstr(19 - 3, 5, "I would launch the SEARCH panel  ");menuwin~refresh;
 | 
					 | 
				
			||||||
            menuwin~refresh
 | 
					 | 
				
			||||||
            call SysWait 1
 | 
					 | 
				
			||||||
            END
 | 
					 | 
				
			||||||
        when key_char = 'l' then do
 | 
					 | 
				
			||||||
            menuwin~mvaddstr(19 - 3, 5, "I would launch the LIST panel    ");menuwin~refresh;
 | 
					 | 
				
			||||||
            menuwin~refresh
 | 
					 | 
				
			||||||
            call SysWait 1
 | 
					 | 
				
			||||||
            END
 | 
					 | 
				
			||||||
        otherwise nop
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ::METHOD findInArray
 | 
					 | 
				
			||||||
      use arg array, item
 | 
					 | 
				
			||||||
      do i = 1 to array~items
 | 
					 | 
				
			||||||
        if array[i] = item then return i
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
      return 0  /* Not found */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ::method cleanup
 | 
					 | 
				
			||||||
      expose win
 | 
					 | 
				
			||||||
      /* Clean up ncurses */
 | 
					 | 
				
			||||||
      win~endwin
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* Handle program termination */
 | 
					 | 
				
			||||||
::ROUTINE ProgramHalt
 | 
					 | 
				
			||||||
  signal off halt
 | 
					 | 
				
			||||||
  Say "PROGRAM TERMINATED AT THE KEYBOARD"
 | 
					 | 
				
			||||||
  exit 0
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** External Libraries **/
 | 
					/** External Libraries **/
 | 
				
			||||||
 | 
					::requires 'app/appdb.cls'
 | 
				
			||||||
 | 
					::requires 'app/appui.cls'
 | 
				
			||||||
 | 
					::requires 'app/utils.rex'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
::requires 'ooSQLite.cls'
 | 
					::requires 'ooSQLite.cls'
 | 
				
			||||||
::requires "rxunixsys" LIBRARY
 | 
					::requires "rxunixsys" LIBRARY
 | 
				
			||||||
::requires 'ncurses.cls'
 | 
					::requires 'ncurses.cls'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										411
									
								
								app/appdb.cls
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										411
									
								
								app/appdb.cls
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,411 @@
 | 
				
			|||||||
 | 
					::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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										220
									
								
								app/appui.cls
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										220
									
								
								app/appui.cls
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,220 @@
 | 
				
			|||||||
 | 
					::requires 'ooSQLite.cls'
 | 
				
			||||||
 | 
					::requires "rxunixsys" LIBRARY
 | 
				
			||||||
 | 
					::requires 'ncurses.cls'
 | 
				
			||||||
 | 
					::requires 'app/utils.rex'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					::class AddressBookUI public
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::method init
 | 
				
			||||||
 | 
					      expose win mainMenu
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::method initialize
 | 
				
			||||||
 | 
					      expose win mainMenu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      win = self~DrawMainPanel(0, .true) /* default colour, draw border box */
 | 
				
			||||||
 | 
					      self~setupMainMenu(win)
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::METHOD DrawMainPanel
 | 
				
			||||||
 | 
					        use arg clrset, with_box
 | 
				
			||||||
 | 
					        win = .window~new()                    /* The first invocation must be with no arguments */
 | 
				
			||||||
 | 
					        win~curs_set(0)                        /* Hide the cursor by default                     */
 | 
				
			||||||
 | 
					        win~keypad(1)                          /* Enable function keys and arrow keys            */
 | 
				
			||||||
 | 
					        win~raw
 | 
				
			||||||
 | 
					        win~noecho
 | 
				
			||||||
 | 
					        win~clear
 | 
				
			||||||
 | 
					        win~cbreak
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self~SetPanelColor(win, clrset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if with_box = .true then DO
 | 
				
			||||||
 | 
					                win~box(0, 0) /* Draw box with default ACS characters */
 | 
				
			||||||
 | 
					                win~refresh
 | 
				
			||||||
 | 
					        END
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return win
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::METHOD DrawSubPanel
 | 
				
			||||||
 | 
					        use arg height, width, starty, startx, clrset, title, with_box
 | 
				
			||||||
 | 
					        new_win = .window~new(height, width, starty, startx)
 | 
				
			||||||
 | 
					        new_win~curs_set(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* Set the panel color */
 | 
				
			||||||
 | 
					        self~SetPanelColor(new_win, clrset)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* Display title */
 | 
				
			||||||
 | 
					        new_win~attron(new_win~A_BOLD)
 | 
				
			||||||
 | 
					        new_win~mvaddstr(starty-(starty-2), (width - title~length) % 2, title)
 | 
				
			||||||
 | 
					        new_win~attroff(new_win~A_BOLD)
 | 
				
			||||||
 | 
					        new_win~refresh
 | 
				
			||||||
 | 
					        if with_box = .true then DO
 | 
				
			||||||
 | 
					            new_win~box(0,0)
 | 
				
			||||||
 | 
					            new_win~refresh
 | 
				
			||||||
 | 
					        END
 | 
				
			||||||
 | 
					        return new_win
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::METHOD SetPanelColor
 | 
				
			||||||
 | 
					        use arg panel, clrset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        panel~start_color()
 | 
				
			||||||
 | 
					        SELECT
 | 
				
			||||||
 | 
					            /* white text on blue background */
 | 
				
			||||||
 | 
					            when clrset = 1 then panel~init_pair(2, panel~COLOR_WHITE, panel~COLOR_BLUE)
 | 
				
			||||||
 | 
					            /* white text on grey background */
 | 
				
			||||||
 | 
					            when clrset = 2 then panel~init_pair(3, panel~COLOR_WHITE, panel~COLOR_GREEN)
 | 
				
			||||||
 | 
					            /* yellow text on red background */
 | 
				
			||||||
 | 
					            when clrset = 3 then panel~init_pair(4, panel~COLOR_YELLOW, panel~COLOR_RED)
 | 
				
			||||||
 | 
					            /* black text on white background */
 | 
				
			||||||
 | 
					            when clrset = 4 then panel~init_pair(5, panel~COLOR_BLACK, panel~COLOR_WHITE)
 | 
				
			||||||
 | 
					            /* black text on green background */
 | 
				
			||||||
 | 
					            when clrset = 5 then panel~init_pair(6, panel~COLOR_BLACK, panel~COLOR_GREEN)
 | 
				
			||||||
 | 
					            /* black text on cyan background */
 | 
				
			||||||
 | 
					            when clrset = 6 then panel~init_pair(7, panel~COLOR_BLACK, panel~COLOR_CYAN)
 | 
				
			||||||
 | 
					            /* Default to white text on black background */
 | 
				
			||||||
 | 
					            OTHERWISE DO
 | 
				
			||||||
 | 
					                clrset=0
 | 
				
			||||||
 | 
					                panel~init_pair(1, panel~COLOR_WHITE, panel~COLOR_BLACK)
 | 
				
			||||||
 | 
					            END
 | 
				
			||||||
 | 
					        END
 | 
				
			||||||
 | 
					        panel~bkgd(panel~color_pair(clrset+1))
 | 
				
			||||||
 | 
					        panel~refresh
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::method setupMainMenu
 | 
				
			||||||
 | 
					      expose mainMenu win menuwin menuItems menu_keys
 | 
				
			||||||
 | 
					      use arg win
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      max_y = win~lines
 | 
				
			||||||
 | 
					      max_x = win~cols
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      menu_height = 21
 | 
				
			||||||
 | 
					      menu_width = 40
 | 
				
			||||||
 | 
					      start_y = (max_y - menu_height) % 2
 | 
				
			||||||
 | 
					      start_x = (max_x - menu_width) % 2
 | 
				
			||||||
 | 
					      clrset=1
 | 
				
			||||||
 | 
					      title = "Rexx Address Book"
 | 
				
			||||||
 | 
					      with_box = .true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      menuwin = self~DrawSubPanel(menu_height, menu_width, start_y, start_x, clrset, title, with_box)
 | 
				
			||||||
 | 
					      menuItems = .array~of("[A]dd Contact", "[R]emove Contact", "[E]dit Contact", "[S]earch", "[L]ist All", "E[X]it")
 | 
				
			||||||
 | 
					      menu_keys = .array~of("a", "r", "e", "s", "l", "x")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      /* Display menu items */
 | 
				
			||||||
 | 
					      selected = 1
 | 
				
			||||||
 | 
					      self~DrawMenu(menuwin, menuItems, selected, win)
 | 
				
			||||||
 | 
					      /* menuwin~mvaddstr(menu_height - 2, 3, "Type 'X' to exit") */
 | 
				
			||||||
 | 
					      menuwin~refresh
 | 
				
			||||||
 | 
					      RETURN
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::method DrawMenu
 | 
				
			||||||
 | 
					      expose win menuItems selected mainwin
 | 
				
			||||||
 | 
					      use arg win, items, selected, mainwin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      do i = 1 to items~items
 | 
				
			||||||
 | 
					        if i = selected then do
 | 
				
			||||||
 | 
					          win~attron(mainwin~COLOR_PAIR(2))
 | 
				
			||||||
 | 
					          win~attron(mainwin~A_BOLD)
 | 
				
			||||||
 | 
					          win~mvaddstr(i+3, 2, items[i])
 | 
				
			||||||
 | 
					          win~attroff(mainwin~COLOR_PAIR(2))
 | 
				
			||||||
 | 
					          win~attroff(mainwin~A_BOLD)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        else do
 | 
				
			||||||
 | 
					          win~mvaddstr(i+3, 2, items[i])
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      win~refresh
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** MAIN LOOP **/
 | 
				
			||||||
 | 
					    ::method mainLoop
 | 
				
			||||||
 | 
					      expose win mainMenu menuwin
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      running = .true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      do while running
 | 
				
			||||||
 | 
					        menuwin~refresh
 | 
				
			||||||
 | 
					        key = win~getch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        menu_keys = .array~of("a", "r", "e", "s", "l", "x")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        select
 | 
				
			||||||
 | 
					          when key = win~KEY_UP then do
 | 
				
			||||||
 | 
					            if selected > 1 then selected = selected - 1
 | 
				
			||||||
 | 
					            call DrawMenu menuwin, menuItems, selected, win
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          when key = win~KEY_DOWN then do
 | 
				
			||||||
 | 
					            if selected < menuItems~items then selected = selected + 1
 | 
				
			||||||
 | 
					            call DrawMenu menuwin, menuItems, selected, win
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          when key = D2C(10) | key = D2C(13) then do /* Enter key - numeric codes only */
 | 
				
			||||||
 | 
					            menuwin~refresh
 | 
				
			||||||
 | 
					            call ProcessSelection menu_keys[selected], home
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          when key = D2C(88) | key = D2C(120) | key = "x" | key = C2D("x") then do
 | 
				
			||||||
 | 
					            menuwin~endwin
 | 
				
			||||||
 | 
					            .environment['STOPNOW'] = 1
 | 
				
			||||||
 | 
					            RETURN
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					          otherwise do
 | 
				
			||||||
 | 
					            if datatype(key) = 'CHAR' then do
 | 
				
			||||||
 | 
					              key = lower(key)
 | 
				
			||||||
 | 
					              pos = self~findInArray(menu_keys, key)
 | 
				
			||||||
 | 
					              if pos > 0 then do
 | 
				
			||||||
 | 
					                menuwin~mvaddstr(19 - 1, 18, "Letter selection ["||key||"]")
 | 
				
			||||||
 | 
					                menuwin~refresh
 | 
				
			||||||
 | 
					                call SysWait 0.5
 | 
				
			||||||
 | 
					                self~ProcessSelection(menuwin, key)
 | 
				
			||||||
 | 
					                return
 | 
				
			||||||
 | 
					              end
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end /* do while running */
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /* Process selection */
 | 
				
			||||||
 | 
					    ::METHOD  ProcessSelection
 | 
				
			||||||
 | 
					      use arg menuwin, key_char
 | 
				
			||||||
 | 
					      select
 | 
				
			||||||
 | 
					        when key_char = 'a' then do
 | 
				
			||||||
 | 
					            menuwin~mvaddstr(19 - 3, 5, "I would launch the ADD panel     ");menuwin~refresh;
 | 
				
			||||||
 | 
					            menuwin~refresh
 | 
				
			||||||
 | 
					            call SysWait 1
 | 
				
			||||||
 | 
					            END
 | 
				
			||||||
 | 
					        when key_char = 'r' then do
 | 
				
			||||||
 | 
					            menuwin~mvaddstr(19 - 3, 5, "I would launch the REMOVE panel  ");menuwin~refresh;
 | 
				
			||||||
 | 
					            menuwin~refresh
 | 
				
			||||||
 | 
					            call SysWait 1
 | 
				
			||||||
 | 
					            END
 | 
				
			||||||
 | 
					        when key_char = 'e' then do
 | 
				
			||||||
 | 
					            menuwin~mvaddstr(19 - 3, 5, "I would launch the EDIT panel    ");menuwin~refresh;
 | 
				
			||||||
 | 
					            menuwin~refresh
 | 
				
			||||||
 | 
					            call SysWait 1
 | 
				
			||||||
 | 
					            END
 | 
				
			||||||
 | 
					        when key_char = 's' then do
 | 
				
			||||||
 | 
					            menuwin~mvaddstr(19 - 3, 5, "I would launch the SEARCH panel  ");menuwin~refresh;
 | 
				
			||||||
 | 
					            menuwin~refresh
 | 
				
			||||||
 | 
					            call SysWait 1
 | 
				
			||||||
 | 
					            END
 | 
				
			||||||
 | 
					        when key_char = 'l' then do
 | 
				
			||||||
 | 
					            menuwin~mvaddstr(19 - 3, 5, "I would launch the LIST panel    ");menuwin~refresh;
 | 
				
			||||||
 | 
					            menuwin~refresh
 | 
				
			||||||
 | 
					            call SysWait 1
 | 
				
			||||||
 | 
					            END
 | 
				
			||||||
 | 
					        otherwise nop
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::METHOD findInArray
 | 
				
			||||||
 | 
					      use arg array, item
 | 
				
			||||||
 | 
					      do i = 1 to array~items
 | 
				
			||||||
 | 
					        if array[i] = item then return i
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					      return 0  /* Not found */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::method cleanup
 | 
				
			||||||
 | 
					      expose win
 | 
				
			||||||
 | 
					      /* Clean up ncurses */
 | 
				
			||||||
 | 
					      win~endwin
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
							
								
								
									
										11
									
								
								app/utils.rex
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								app/utils.rex
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					::ROUTINE SysWait PUBLIC
 | 
				
			||||||
 | 
					  use arg seconds
 | 
				
			||||||
 | 
					  address system 'sleep' seconds
 | 
				
			||||||
 | 
					  return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Handle program termination */
 | 
				
			||||||
 | 
					::ROUTINE ProgramHalt
 | 
				
			||||||
 | 
					  signal off halt
 | 
				
			||||||
 | 
					  Say "PROGRAM TERMINATED AT THE KEYBOARD"
 | 
				
			||||||
 | 
					  exit 0
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								db/test_contacts.sqlite
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								db/test_contacts.sqlite
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										326
									
								
								tests/test_appdb.rexx
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										326
									
								
								tests/test_appdb.rexx
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,326 @@
 | 
				
			|||||||
 | 
					#!/usr/bin/env rexx
 | 
				
			||||||
 | 
					/***************************************************************************
 | 
				
			||||||
 | 
					 * Rexx Address Book - Database Test Script                                *
 | 
				
			||||||
 | 
					 *                                                                         *
 | 
				
			||||||
 | 
					 * This script tests the database functionality without the ncurses UI.    *
 | 
				
			||||||
 | 
					 * It steps through creating, reading, updating, and deleting contacts     *
 | 
				
			||||||
 | 
					 * along with their associated data.                                       *
 | 
				
			||||||
 | 
					 ***************************************************************************/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Setup environment */
 | 
				
			||||||
 | 
					.local~home = SysGetpwnam("gmgauthier", "d")
 | 
				
			||||||
 | 
					.local~projectRoot = .home||"/Projects/rexx-address-book"
 | 
				
			||||||
 | 
					.local~appPkg = .projectRoot||"/app"
 | 
				
			||||||
 | 
					.local~dbPath = .projectRoot||"/db/test_contacts.sqlite"  /* Use a test database */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tests = .TestSuite~new()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DO
 | 
				
			||||||
 | 
					    tests~TestCreateContact
 | 
				
			||||||
 | 
					    tests~TestGetContact
 | 
				
			||||||
 | 
					    tests~TestGetAllContacts
 | 
				
			||||||
 | 
					    tests~TestAddPhoneNumber
 | 
				
			||||||
 | 
					    tests~TestAddEmailAddress
 | 
				
			||||||
 | 
					    tests~TestAddRealAddress
 | 
				
			||||||
 | 
					    tests~TestFullDetailRetrieval
 | 
				
			||||||
 | 
					    tests~TestUpdateContact
 | 
				
			||||||
 | 
					END
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					tests~tearDown
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exit 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					::requires 'app/appdb.cls'
 | 
				
			||||||
 | 
					::requires 'ooSQLite.cls'
 | 
				
			||||||
 | 
					::requires "rxunixsys" LIBRARY
 | 
				
			||||||
 | 
					::requires 'ncurses.cls'
 | 
				
			||||||
 | 
					::requires 'app/utils.rex'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					::CLASS TestSuite
 | 
				
			||||||
 | 
					    ::METHOD init
 | 
				
			||||||
 | 
					        self~setUp()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::METHOD setUp
 | 
				
			||||||
 | 
					        expose db contactDict
 | 
				
			||||||
 | 
					        if SysFileExists(.dbPath) then do
 | 
				
			||||||
 | 
					            call SysFileDelete .dbPath
 | 
				
			||||||
 | 
					            say "Deleted existing test database."
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        say "Initializing test database at:" .dbPath
 | 
				
			||||||
 | 
					        db = .AddressBookDB~new()
 | 
				
			||||||
 | 
					        self~setupFixtureData()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::METHOD tearDown
 | 
				
			||||||
 | 
					        expose db
 | 
				
			||||||
 | 
					        db~closeDb()
 | 
				
			||||||
 | 
					        say "Test completed!"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::METHOD setupFixtureData
 | 
				
			||||||
 | 
					        expose db contactDict addressDict emailArray phoneArray
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        emailArray = .Array~new()
 | 
				
			||||||
 | 
					        emailDict1 = .Directory~new()
 | 
				
			||||||
 | 
					        emailDict1~Type = "HOME"
 | 
				
			||||||
 | 
					        emailDict1~Number = "john.doe@home.com"
 | 
				
			||||||
 | 
					        emailArray~append(emailDict1)
 | 
				
			||||||
 | 
					        emailDict2 = .Directory~new()
 | 
				
			||||||
 | 
					        emailDict2~Type = "WORK"
 | 
				
			||||||
 | 
					        emailDict2~Number = "john.doe@work.com"
 | 
				
			||||||
 | 
					        emailArray~append(emailDict2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        phoneArray = .Array~new()
 | 
				
			||||||
 | 
					        phoneDict1 = .Directory~new()
 | 
				
			||||||
 | 
					        phoneDict1~Type = "HOME"
 | 
				
			||||||
 | 
					        phoneDict1~Number = "123-456-7890"
 | 
				
			||||||
 | 
					        phoneArray~append(phoneDict1)
 | 
				
			||||||
 | 
					        phoneDict2 = .Directory~new()
 | 
				
			||||||
 | 
					        phoneDict2~Type = "WORK"
 | 
				
			||||||
 | 
					        phoneDict2~Number = "987-654-3210"
 | 
				
			||||||
 | 
					        phoneArray~append(phoneDict2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        contactDict = .Directory~new()
 | 
				
			||||||
 | 
					        contactDict~firstName = "John"
 | 
				
			||||||
 | 
					        contactDict~lastName = "Doe"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::METHOD TestCreateContact
 | 
				
			||||||
 | 
					        expose db contactDict
 | 
				
			||||||
 | 
					        /* Test contact creation */
 | 
				
			||||||
 | 
					        say ""
 | 
				
			||||||
 | 
					        say "=== TESTING CONTACT CREATION ==="
 | 
				
			||||||
 | 
					        say "Creating contact:" contactDict~firstName contactDict~lastName
 | 
				
			||||||
 | 
					        contactId = db~addContact(contactDict)
 | 
				
			||||||
 | 
					        say "Contact created with ID:" contactId
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::METHOD TestGetContact
 | 
				
			||||||
 | 
					        expose db contactDict
 | 
				
			||||||
 | 
					        /* Test retrieving contact */
 | 
				
			||||||
 | 
					        say ""
 | 
				
			||||||
 | 
					        say "=== TESTING CONTACT RETRIEVAL ==="
 | 
				
			||||||
 | 
					        contact = db~getContact(1)
 | 
				
			||||||
 | 
					        if contact \= .nil then do
 | 
				
			||||||
 | 
					            say "Contact retrieved successfully:"
 | 
				
			||||||
 | 
					            say "  ID:" contact['ID']
 | 
				
			||||||
 | 
					            say "  Name:" contact['FIRST_NAME'] contact['LAST_NAME']
 | 
				
			||||||
 | 
					            say "  Created at:" contact['CREATED_AT']
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        else do
 | 
				
			||||||
 | 
					            say "ERROR: Failed to retrieve contact!"
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::METHOD TestGetAllContacts
 | 
				
			||||||
 | 
					        expose db contactDict
 | 
				
			||||||
 | 
					        /* Get all contacts */
 | 
				
			||||||
 | 
					        say ""
 | 
				
			||||||
 | 
					        say "=== TESTING GET ALL CONTACTS ==="
 | 
				
			||||||
 | 
					        /* add some extras */
 | 
				
			||||||
 | 
					        db~addContact(contactDict)
 | 
				
			||||||
 | 
					        db~addContact(contactDict)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        allContacts = db~getAllContacts()
 | 
				
			||||||
 | 
					        say "Total contacts in database:" allContacts~items()
 | 
				
			||||||
 | 
					        do contact over allContacts
 | 
				
			||||||
 | 
					            say "  " contact['FIRST_NAME'] contact['LAST_NAME'] "(ID:" contact['ID']")"
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::METHOD TestAddPhoneNumber
 | 
				
			||||||
 | 
					        expose db contactDict
 | 
				
			||||||
 | 
					        /* Test adding phone numbers */
 | 
				
			||||||
 | 
					        say ""
 | 
				
			||||||
 | 
					        say "=== TESTING PHONE NUMBER ADDITION ==="
 | 
				
			||||||
 | 
					        Say "Adding phone numbers for contact id: 1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        say "Adding home phone number..."
 | 
				
			||||||
 | 
					        homePhoneId = db~addPhoneNumber(1, "Home", "555-123-4567")
 | 
				
			||||||
 | 
					        say "Added phone with ID:" homePhoneId
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        say "Adding mobile phone number..."
 | 
				
			||||||
 | 
					        mobilePhoneId = db~addPhoneNumber(1, "Mobile", "555-987-6543")
 | 
				
			||||||
 | 
					        say "Added phone with ID:" mobilePhoneId
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::METHOD TestAddEmailAddress
 | 
				
			||||||
 | 
					        expose db contactDict
 | 
				
			||||||
 | 
					        /* Test adding email addresses */
 | 
				
			||||||
 | 
					        say ""
 | 
				
			||||||
 | 
					        say "=== TESTING EMAIL ADDITION ==="
 | 
				
			||||||
 | 
					        Say "Adding email addresses for contact id: 1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        say "Adding personal email..."
 | 
				
			||||||
 | 
					        personalEmailId = db~addEmailAddress(1, "Personal", "john.doe@personal.example")
 | 
				
			||||||
 | 
					        say "Added email with ID:" personalEmailId
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        say "Adding work email..."
 | 
				
			||||||
 | 
					        workEmailId = db~addEmailAddress(1, "Work", "john.doe@work.example")
 | 
				
			||||||
 | 
					        say "Added email with ID:" workEmailId
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::METHOD TestAddRealAddress
 | 
				
			||||||
 | 
					        expose db contactDict
 | 
				
			||||||
 | 
					        /* Test adding physical address */
 | 
				
			||||||
 | 
					        say ""
 | 
				
			||||||
 | 
					        say "=== TESTING ADDRESS ADDITION ==="
 | 
				
			||||||
 | 
					        Say "Adding real addresses for contact id: 1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        say "Adding home address..."
 | 
				
			||||||
 | 
					        homeAddressId = db~addRealAddress(1, "Home", "123 Main St", "Anytown", "NY", "12345", "USA")
 | 
				
			||||||
 | 
					        say "Added address with ID:" homeAddressId
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::METHOD TestFullDetailRetrieval
 | 
				
			||||||
 | 
					        expose db ContactDict
 | 
				
			||||||
 | 
					        /* Read the complete contact details */
 | 
				
			||||||
 | 
					        say ""
 | 
				
			||||||
 | 
					        say "=== TESTING COMPLETE CONTACT RETRIEVAL ==="
 | 
				
			||||||
 | 
					        contact = db~getContact(1)
 | 
				
			||||||
 | 
					        if contact \= .nil then do
 | 
				
			||||||
 | 
					            say "Contact retrieved successfully:"
 | 
				
			||||||
 | 
					            say "  ID:" contact['ID']
 | 
				
			||||||
 | 
					            say "  Name:" contact['FIRST_NAME'] contact['LAST_NAME']
 | 
				
			||||||
 | 
					            say "  Created at:" contact['CREATED_AT']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            phones = db~getPhoneNumbers(1)
 | 
				
			||||||
 | 
					            say "Phone numbers:"
 | 
				
			||||||
 | 
					            do phone over phones
 | 
				
			||||||
 | 
					                say "  " phone['TYPE']":" phone['NUMBER'] "(ID:" phone['ID']")"
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            emails = db~getEmailAddresses(1)
 | 
				
			||||||
 | 
					            say "Email addresses:"
 | 
				
			||||||
 | 
					            do email over emails
 | 
				
			||||||
 | 
					                say "  " email['TYPE']":" email['EMAIL'] "(ID:" email['ID']")"
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            addresses = db~getRealAddresses(1)
 | 
				
			||||||
 | 
					            say "Addresses:"
 | 
				
			||||||
 | 
					            do address over addresses
 | 
				
			||||||
 | 
					                say "  " address['TYPE']" (ID: "address['ID']"):"
 | 
				
			||||||
 | 
					                say "    Street:" address['STREET']
 | 
				
			||||||
 | 
					                say "    City:" address['CITY']
 | 
				
			||||||
 | 
					                say "    State:" address['STATE']
 | 
				
			||||||
 | 
					                say "    Postal code:" address['POSTAL_CODE']
 | 
				
			||||||
 | 
					                say "    Country:" address['COUNTRY']
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        else do
 | 
				
			||||||
 | 
					            say "ERROR: Failed to retrieve complete contact details!"
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::METHOD TestUpdateContact
 | 
				
			||||||
 | 
					        expose db ContactDict
 | 
				
			||||||
 | 
					        /* Test updating contact information */
 | 
				
			||||||
 | 
					        say ""
 | 
				
			||||||
 | 
					        say "=== TESTING CONTACT UPDATE ==="
 | 
				
			||||||
 | 
					        say "Updating contact name..."
 | 
				
			||||||
 | 
					        updatedFirstName = "Jonathan"
 | 
				
			||||||
 | 
					        updatedLastName = "Dorian"
 | 
				
			||||||
 | 
					        result = db~updateContact(1, updatedFirstName, updatedLastName)
 | 
				
			||||||
 | 
					        say "Update successful:" result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        say "Updating home phone number..."
 | 
				
			||||||
 | 
					        result = db~updatePhoneNumber(1, "Home", "555-111-2222")
 | 
				
			||||||
 | 
					        say "Update successful:" result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        say "Updating work email..."
 | 
				
			||||||
 | 
					        result = db~updateEmailAddress(2, "Work", "jonathan.dorian@hospital.example")
 | 
				
			||||||
 | 
					        say "Update successful:" result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        say "Updating home address..."
 | 
				
			||||||
 | 
					        result = db~updateRealAddress(1, "Home", "456 Oak Ave", "Springfield", "IL", "54321", "USA")
 | 
				
			||||||
 | 
					        say "Update successful:" result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* Read the updated contact details */
 | 
				
			||||||
 | 
					        say ""
 | 
				
			||||||
 | 
					        say "=== TESTING UPDATED CONTACT RETRIEVAL ==="
 | 
				
			||||||
 | 
					        contact = db~getContact(1)
 | 
				
			||||||
 | 
					        if contact \= .nil then do
 | 
				
			||||||
 | 
					            say "Updated contact:"
 | 
				
			||||||
 | 
					            say "  ID:" contact['ID']
 | 
				
			||||||
 | 
					            say "  Name:" contact['FIRST_NAME'] contact['LAST_NAME']
 | 
				
			||||||
 | 
					            say "  Created at:" contact['CREATED_AT']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            say "Updated phone numbers:"
 | 
				
			||||||
 | 
					            phones = db~getPhoneNumbers(1)
 | 
				
			||||||
 | 
					            do phone over phones
 | 
				
			||||||
 | 
					                say "  " phone['TYPE']":" phone['NUMBER'] "(ID:" phone['ID']")"
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            emails = db~getEmailAddresses(1)
 | 
				
			||||||
 | 
					            say "Email addresses:"
 | 
				
			||||||
 | 
					            do email over emails
 | 
				
			||||||
 | 
					                say "  " email['TYPE']":" email['EMAIL'] "(ID:" email['ID']")"
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            addresses = db~getRealAddresses(1)
 | 
				
			||||||
 | 
					            say "Addresses:"
 | 
				
			||||||
 | 
					            do address over addresses
 | 
				
			||||||
 | 
					                say "  " address['TYPE']" (ID: "address['ID']"):"
 | 
				
			||||||
 | 
					                say "    Street:" address['STREET']
 | 
				
			||||||
 | 
					                say "    City:" address['CITY']
 | 
				
			||||||
 | 
					                say "    State:" address['STATE']
 | 
				
			||||||
 | 
					                say "    Postal code:" address['POSTAL_CODE']
 | 
				
			||||||
 | 
					                say "    Country:" address['COUNTRY']
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					        else do
 | 
				
			||||||
 | 
					            say "ERROR: Failed to retrieve updated contact details!"
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::METHOD TestDeletion
 | 
				
			||||||
 | 
					        say ""
 | 
				
			||||||
 | 
					        say "=== TESTING DELETION ==="
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* Test deleting an email */
 | 
				
			||||||
 | 
					        say "Deleting personal email..."
 | 
				
			||||||
 | 
					        result = db~deleteEmailAddress(personalEmailId)
 | 
				
			||||||
 | 
					        say "Deletion successful:" result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* Get contact to verify deletion */
 | 
				
			||||||
 | 
					        contact = db~getContact(contactId)
 | 
				
			||||||
 | 
					        say "Email addresses after deletion:"
 | 
				
			||||||
 | 
					        do email over contact['emails']
 | 
				
			||||||
 | 
					            say "  " email['type']":" email['email'] "(ID:" email['id']")"
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ::METHOD OtherStuff
 | 
				
			||||||
 | 
					        /* Test deleting the entire contact */
 | 
				
			||||||
 | 
					        say "Deleting entire contact..."
 | 
				
			||||||
 | 
					        result = db~deleteContact(contactId)
 | 
				
			||||||
 | 
					        say "Deletion successful:" result
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* Try to retrieve the deleted contact */
 | 
				
			||||||
 | 
					        contact = db~getContact(contactId)
 | 
				
			||||||
 | 
					        if contact = .nil then
 | 
				
			||||||
 | 
					            say "Contact successfully deleted - could not retrieve contact with ID" contactId
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					            say "ERROR: Contact was not properly deleted!"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* Test searching functionality */
 | 
				
			||||||
 | 
					        say ""
 | 
				
			||||||
 | 
					        say "=== TESTING SEARCH FUNCTIONALITY ==="
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* Add multiple contacts for search testing */
 | 
				
			||||||
 | 
					        say "Adding test contacts for search..."
 | 
				
			||||||
 | 
					        db~addContact("Jane", "Smith")
 | 
				
			||||||
 | 
					        db~addContact("John", "Johnson")
 | 
				
			||||||
 | 
					        db~addContact("Bob", "Smith")
 | 
				
			||||||
 | 
					        db~addContact("Sarah", "Williams")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        /* Search for contacts */
 | 
				
			||||||
 | 
					        say "Searching for 'Smith'..."
 | 
				
			||||||
 | 
					        results = db~searchContacts("Smith")
 | 
				
			||||||
 | 
					        say "Found" results~items() "contacts:"
 | 
				
			||||||
 | 
					        do contact over results
 | 
				
			||||||
 | 
					            say "  " contact['firstName'] contact['lastName'] "(ID:" contact['id']")"
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        say "Searching for 'John'..."
 | 
				
			||||||
 | 
					        results = db~searchContacts("John")
 | 
				
			||||||
 | 
					        say "Found" results~items() "contacts:"
 | 
				
			||||||
 | 
					        do contact over results
 | 
				
			||||||
 | 
					            say "  " contact['firstName'] contact['lastName'] "(ID:" contact['id']")"
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user