├── docs ├── PersonService.md ├── SystemService.md ├── DashboardService.md ├── Accounts.md ├── ChatService.md ├── MessagingService.md └── ConfigJSON.md ├── ddl ├── list │ ├── list.inc │ └── list_sqlite.inc ├── photo │ ├── photo.inc │ └── photo_sqlite.inc ├── role │ ├── role.inc │ └── role_sqlite.inc ├── token │ ├── token.inc │ └── token_sqlite.inc ├── person │ ├── person.inc │ └── person_sqlite.inc ├── api_key │ ├── api_key.inc │ └── api_key_sqlite.inc ├── contact │ ├── contact.inc │ └── contact_sqlite.inc ├── ip_allow │ ├── ip_allow.inc │ └── ip_allow_sqlite.inc ├── ip_block │ ├── ip_block.inc │ └── ip_block_sqlite.inc ├── messaging │ ├── messaging.inc │ └── messaging_sqlite.inc ├── login_fail │ ├── login_fail.inc │ └── login_fail_sqlite.inc ├── person_role │ ├── person_role.inc │ └── person_role_sqlite.inc ├── login_history │ ├── login_history.inc │ └── login_history_sqlite.inc ├── action_history │ ├── action_history.inc │ └── action_history_sqlite.inc ├── chatai_history │ ├── chatai_history.inc │ └── chatai_history_sqlite.inc ├── imageai_history │ ├── imageai_history.inc │ └── imageai_history_sqlite.inc └── endpoint_history │ ├── endpoint_history.inc │ └── endpoint_history_sqlite.inc ├── sql ├── person │ ├── role │ │ ├── role.inc │ │ └── role_sqlite.inc │ ├── photo │ │ ├── photo.inc │ │ └── photo_sqlite.inc │ ├── contact │ │ ├── contact.inc │ │ └── contact_sqlite.inc │ ├── profile │ │ ├── profile.inc │ │ └── profile_sqlite.inc │ ├── directory │ │ ├── directory.inc │ │ └── directory_sqlite.inc │ ├── login_count │ │ ├── login_count.inc │ │ └── login_count_sqlite.inc │ └── login_recent │ │ ├── login_recent.inc │ │ └── login_recent_sqlite.inc ├── ai │ ├── chatai │ │ ├── chatai_usage.inc │ │ ├── log_chatai.inc │ │ ├── chatai_recent.inc │ │ ├── chatai_recent_sqlite.inc │ │ ├── chatai_usage_sqlite.inc │ │ └── log_chatai_sqlite.inc │ └── imageai │ │ ├── log_imageai.inc │ │ ├── imageai_usage.inc │ │ ├── imageai_recent.inc │ │ ├── imageai_retrieve.inc │ │ ├── imageai_retrieve_sqlite.inc │ │ ├── imageai_recent_sqlite.inc │ │ ├── log_imageai_sqlite.inc │ │ └── imageai_usage_sqlite.inc ├── system │ ├── list_by_id │ │ ├── list_by_id.inc │ │ └── list_by_id_sqlite.inc │ ├── token_check │ │ ├── token_check.inc │ │ └── token_check_sqlite.inc │ ├── token_insert │ │ ├── token_insert.inc │ │ └── token_insert_sqlite.inc │ ├── token_revoke │ │ ├── token_revoke.inc │ │ └── token_revoke_sqlite.inc │ ├── api_key_check │ │ ├── api_key_check.inc │ │ └── api_key_check_sqlite.inc │ ├── contact_email │ │ ├── contact_email.inc │ │ └── contact_email_sqlite.inc │ ├── login_cleanup │ │ ├── login_cleanup.inc │ │ └── login_cleanup_sqlite.inc │ ├── contact_search │ │ ├── contact_search.inc │ │ └── contact_search_sqlite.inc │ ├── ip_allow_check │ │ ├── ip_allow_check.inc │ │ └── ip_allow_check_sqlite.inc │ ├── ip_block_check │ │ ├── ip_block_check.inc │ │ └── ip_block_check_sqlite.inc │ ├── ip_block_insert │ │ ├── ip_block_insert.inc │ │ └── ip_block_insert_sqlite.inc │ ├── login_fail_check │ │ ├── login_fail_check.inc │ │ └── login_fail_check_sqlite.inc │ ├── login_fail_insert │ │ ├── login_fail_insert.inc │ │ └── login_fail_insert_sqlite.inc │ ├── person_role_check │ │ ├── person_role_check.inc │ │ └── person_role_check_sqlite.inc │ ├── login_history_insert │ │ ├── login_history_insert.inc │ │ └── login_history_insert_sqlite.inc │ ├── action_history_insert │ │ ├── action_history_insert.inc │ │ └── action_history_insert_sqlite.inc │ ├── person_password_check │ │ ├── person_password_check.inc │ │ └── person_password_check_sqlite.inc │ └── endpoint_history_insert │ │ ├── endpoint_history_insert.inc │ │ └── endpoint_history_insert_sqlite.inc └── messaging │ └── messaging │ ├── log_message.inc │ └── log_message_sqlite.inc ├── .gitattributes ├── TDD.png ├── tdd.ico ├── .github ├── FUNDING.yml └── workflows │ └── main.yml ├── Project1.res ├── RandomDLL.dll ├── XDataServer.res ├── Project1_Icon1.ico ├── units ├── Unit3.dfm ├── DashboardService.pas ├── Unit1.dfm ├── PersonService.pas ├── Unit1.pas ├── MessagingService.pas ├── Unit2.dfm ├── ChatService.pas ├── SystemService.pas ├── DashboardServiceImplementation.pas ├── Unit3.pas └── PersonServiceImplementation.pas ├── XDataServer.dpr ├── LICENSE ├── .gitignore └── README.md /docs/PersonService.md: -------------------------------------------------------------------------------- 1 | # PersonService 2 | -------------------------------------------------------------------------------- /docs/SystemService.md: -------------------------------------------------------------------------------- 1 | # SystemService 2 | -------------------------------------------------------------------------------- /docs/DashboardService.md: -------------------------------------------------------------------------------- 1 | # DashboardService 2 | -------------------------------------------------------------------------------- /ddl/list/list.inc: -------------------------------------------------------------------------------- 1 | {$Include ddl\list\list_sqlite.inc} 2 | -------------------------------------------------------------------------------- /ddl/photo/photo.inc: -------------------------------------------------------------------------------- 1 | {$Include ddl\photo\photo_sqlite.inc} 2 | -------------------------------------------------------------------------------- /ddl/role/role.inc: -------------------------------------------------------------------------------- 1 | {$Include ddl\role\role_sqlite.inc} 2 | 3 | -------------------------------------------------------------------------------- /ddl/token/token.inc: -------------------------------------------------------------------------------- 1 | {$Include ddl\token\token_sqlite.inc} 2 | -------------------------------------------------------------------------------- /ddl/person/person.inc: -------------------------------------------------------------------------------- 1 | {$Include ddl\person\person_sqlite.inc} 2 | -------------------------------------------------------------------------------- /ddl/api_key/api_key.inc: -------------------------------------------------------------------------------- 1 | {$Include ddl\api_key\api_key_sqlite.inc} 2 | -------------------------------------------------------------------------------- /ddl/contact/contact.inc: -------------------------------------------------------------------------------- 1 | {$Include ddl\contact\contact_sqlite.inc} 2 | -------------------------------------------------------------------------------- /ddl/ip_allow/ip_allow.inc: -------------------------------------------------------------------------------- 1 | {$Include ddl\ip_allow\ip_allow_sqlite.inc} 2 | -------------------------------------------------------------------------------- /ddl/ip_block/ip_block.inc: -------------------------------------------------------------------------------- 1 | {$Include ddl\ip_block\ip_block_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/person/role/role.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\person\role\role_sqlite.inc} 2 | -------------------------------------------------------------------------------- /ddl/messaging/messaging.inc: -------------------------------------------------------------------------------- 1 | {$Include ddl\messaging\messaging_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/person/photo/photo.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\person\photo\photo_sqlite.inc} 2 | -------------------------------------------------------------------------------- /ddl/login_fail/login_fail.inc: -------------------------------------------------------------------------------- 1 | {$Include ddl\login_fail\login_fail_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/ai/chatai/chatai_usage.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\ai\chatai\chatai_usage_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/ai/chatai/log_chatai.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\ai\chatai\log_chatai_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/ai/imageai/log_imageai.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\ai\imageai\log_imageai_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/person/contact/contact.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\person\contact\contact_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/person/profile/profile.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\person\profile\profile_sqlite.inc} 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /TDD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/500Foods/TMS-XData-TemplateDemoData/HEAD/TDD.png -------------------------------------------------------------------------------- /ddl/person_role/person_role.inc: -------------------------------------------------------------------------------- 1 | {$Include ddl\person_role\person_role_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/ai/chatai/chatai_recent.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\ai\chatai\chatai_recent_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/ai/imageai/imageai_usage.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\ai\imageai\imageai_usage_sqlite.inc} 2 | -------------------------------------------------------------------------------- /tdd.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/500Foods/TMS-XData-TemplateDemoData/HEAD/tdd.ico -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [500Foods] 2 | custom: [www.buymeacoffee.com/andrewsimard500] 3 | -------------------------------------------------------------------------------- /ddl/login_history/login_history.inc: -------------------------------------------------------------------------------- 1 | {$Include ddl\login_history\login_history_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/ai/imageai/imageai_recent.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\ai\imageai\imageai_recent_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/ai/imageai/imageai_retrieve.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\ai\imageai\imageai_retrieve_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/person/directory/directory.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\person\directory\directory_sqlite.inc} 2 | -------------------------------------------------------------------------------- /Project1.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/500Foods/TMS-XData-TemplateDemoData/HEAD/Project1.res -------------------------------------------------------------------------------- /RandomDLL.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/500Foods/TMS-XData-TemplateDemoData/HEAD/RandomDLL.dll -------------------------------------------------------------------------------- /ddl/action_history/action_history.inc: -------------------------------------------------------------------------------- 1 | {$Include ddl\action_history\action_history_sqlite.inc} 2 | -------------------------------------------------------------------------------- /ddl/chatai_history/chatai_history.inc: -------------------------------------------------------------------------------- 1 | {$Include ddl\chatai_history\chatai_history_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/list_by_id/list_by_id.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\list_by_id\list_by_id_sqlite.inc} 2 | -------------------------------------------------------------------------------- /XDataServer.res: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/500Foods/TMS-XData-TemplateDemoData/HEAD/XDataServer.res -------------------------------------------------------------------------------- /ddl/imageai_history/imageai_history.inc: -------------------------------------------------------------------------------- 1 | {$Include ddl\imageai_history\imageai_history_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/messaging/messaging/log_message.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\messaging\messaging\log_message_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/person/login_count/login_count.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\person\login_count\login_count_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/person/login_recent/login_recent.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\person\login_recent\login_recent_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/token_check/token_check.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\token_check\token_check_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/token_insert/token_insert.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\token_insert\token_insert_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/token_revoke/token_revoke.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\token_revoke\token_revoke_sqlite.inc} 2 | -------------------------------------------------------------------------------- /Project1_Icon1.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/500Foods/TMS-XData-TemplateDemoData/HEAD/Project1_Icon1.ico -------------------------------------------------------------------------------- /ddl/endpoint_history/endpoint_history.inc: -------------------------------------------------------------------------------- 1 | {$include ddl\endpoint_history\endpoint_history_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/api_key_check/api_key_check.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\api_key_check\api_key_check_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/contact_email/contact_email.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\contact_email\contact_email_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/login_cleanup/login_cleanup.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\login_cleanup\login_cleanup_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/contact_search/contact_search.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\contact_search\contact_search_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/ip_allow_check/ip_allow_check.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\ip_allow_check\ip_allow_check_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/ip_block_check/ip_block_check.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\ip_block_check\ip_block_check_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/ip_block_insert/ip_block_insert.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\ip_block_insert\ip_block_insert_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/login_fail_check/login_fail_check.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\login_fail_check\login_fail_check_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/login_fail_insert/login_fail_insert.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\login_fail_insert\login_fail_insert_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/person_role_check/person_role_check.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\person_role_check\person_role_check_sqlite.inc} 2 | -------------------------------------------------------------------------------- /units/Unit3.dfm: -------------------------------------------------------------------------------- 1 | object DBSupport: TDBSupport 2 | OldCreateOrder = False 3 | Height = 343 4 | Width = 533 5 | end 6 | -------------------------------------------------------------------------------- /sql/system/login_history_insert/login_history_insert.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\login_history_insert\login_history_insert_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/action_history_insert/action_history_insert.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\action_history_insert\action_history_insert_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/person_password_check/person_password_check.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\person_password_check\person_password_check_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/endpoint_history_insert/endpoint_history_insert.inc: -------------------------------------------------------------------------------- 1 | {$Include sql\system\endpoint_history_insert\endpoint_history_insert_sqlite.inc} 2 | -------------------------------------------------------------------------------- /sql/system/token_revoke/token_revoke_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] token_revoke 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('delete from '+ 11 | ' token '+ 12 | 'where '+ 13 | ' token_hash = :TOKENHASH '+ 14 | ';' 15 | ); 16 | 17 | end; 18 | end; -------------------------------------------------------------------------------- /sql/person/login_count/login_count_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] login_count 2 | 3 | if (MainForm.DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('SELECT '+ 11 | ' count(*) logins '+ 12 | 'FROM '+ 13 | ' login_history '+ 14 | 'WHERE '+ 15 | ' person_id = :PERSONID ' 16 | ); 17 | 18 | end; 19 | end; 20 | -------------------------------------------------------------------------------- /sql/ai/imageai/imageai_retrieve_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] imageai_retrieve 2 | 3 | if (MainForm.DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('SELECT '+ 11 | ' cast(generated_image as blob) generated_image '+ 12 | 'FROM '+ 13 | ' imageai_history '+ 14 | 'WHERE '+ 15 | ' chat_id = :CHATID;' 16 | ); 17 | 18 | end; 19 | end; 20 | 21 | -------------------------------------------------------------------------------- /sql/system/contact_search/contact_search_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] contact_search 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('select '+ 11 | ' distinct person_id '+ 12 | 'from '+ 13 | ' contact '+ 14 | 'where '+ 15 | ' (lower(value) = :LOGINID) '+ 16 | ' and (login_use = 1);' 17 | ); 18 | 19 | end; 20 | end; -------------------------------------------------------------------------------- /sql/person/photo/photo_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] photo 2 | 3 | if (MainForm.DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('SELECT '+ 11 | ' cast(photo_datauri as blob) photo_datauri '+ 12 | 'FROM '+ 13 | ' photo '+ 14 | 'WHERE '+ 15 | ' person_id = :PERSONID '+ 16 | ' and photo_type = 1; ' 17 | ); 18 | 19 | end; 20 | end; 21 | -------------------------------------------------------------------------------- /sql/system/list_by_id/list_by_id_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] list_by_id 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | SQL.Clear; 9 | SQL.Add('select '+ 10 | ' list_id, lookup_id, last_modified, last_modifier, preference, value, attributes '+ 11 | 'from '+ 12 | ' list '+ 13 | 'where '+ 14 | ' list_id = :LISTID '+ 15 | ';' 16 | ); 17 | 18 | end; 19 | end; -------------------------------------------------------------------------------- /sql/system/login_fail_insert/login_fail_insert_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] login_fail_insert 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('insert into '+ 11 | ' login_fail '+ 12 | ' (login_id, ip_address, attempted) '+ 13 | 'values( '+ 14 | ' :LOGINID, '+ 15 | ' :IPADDRESS, '+ 16 | ' Datetime("now") '+ 17 | ');' 18 | ); 19 | 20 | end; 21 | end; 22 | -------------------------------------------------------------------------------- /sql/system/contact_email/contact_email_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] contact_email 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('select '+ 11 | ' value '+ 12 | 'from '+ 13 | ' contact '+ 14 | 'where '+ 15 | ' (person_id = :PERSONID) '+ 16 | ' and (list_contact = 1) '+ 17 | 'order by '+ 18 | ' preference;' 19 | ); 20 | 21 | end; 22 | end; 23 | -------------------------------------------------------------------------------- /sql/system/ip_allow_check/ip_allow_check_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] ip_allow_check 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('select '+ 11 | ' ip_address '+ 12 | 'from '+ 13 | ' ip_allow '+ 14 | 'where '+ 15 | ' ip_address = :IPADDRESS '+ 16 | ' and (valid_after < Datetime("now")) '+ 17 | ' and (valid_until > Datetime("now"));' 18 | ); 19 | 20 | end; 21 | end; -------------------------------------------------------------------------------- /sql/system/ip_block_check/ip_block_check_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] ip_block_check 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('select '+ 11 | ' valid_until '+ 12 | 'from '+ 13 | ' ip_block '+ 14 | 'where '+ 15 | ' ip_address = :IPADDRESS '+ 16 | ' and (valid_after < Datetime("now")) '+ 17 | ' and (valid_until > Datetime("now"));' 18 | ); 19 | 20 | end; 21 | end; 22 | -------------------------------------------------------------------------------- /sql/system/login_fail_check/login_fail_check_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] login_fail_check 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('select '+ 11 | ' count(*) attempts '+ 12 | 'from '+ 13 | ' login_fail '+ 14 | 'where '+ 15 | ' (login_id = :LOGINID) '+ 16 | ' and (ip_address = :IPADDRESS) '+ 17 | ' and (attempted > Datetime("now", "-10 minutes"));' 18 | ); 19 | 20 | end; 21 | end; 22 | 23 | -------------------------------------------------------------------------------- /sql/system/api_key_check/api_key_check_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] api_key_check 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('select '+ 11 | ' application, Datetime(valid_until, "localtime") valid_until '+ 12 | 'from '+ 13 | ' api_key '+ 14 | 'where '+ 15 | ' lower(api_key) = :APIKEY '+ 16 | ' and (valid_after < Datetime("now")) '+ 17 | ' and (valid_until > Datetime("now"));' 18 | ); 19 | 20 | end; 21 | end; 22 | -------------------------------------------------------------------------------- /sql/system/person_password_check/person_password_check_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] person_password_check 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('select '+ 11 | ' first_name, '+ 12 | ' middle_name, '+ 13 | ' last_name, '+ 14 | ' account_name '+ 15 | 'from '+ 16 | ' person '+ 17 | 'where '+ 18 | ' (person_id = :PERSONID) '+ 19 | ' and (password_hash = :PASSWORDHASH);' 20 | ); 21 | 22 | end; 23 | end; -------------------------------------------------------------------------------- /sql/system/token_check/token_check_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] token_check 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('select '+ 11 | ' token_hash, valid_until '+ 12 | 'from '+ 13 | ' token '+ 14 | 'where '+ 15 | ' (token_hash = :TOKENHASH) '+ 16 | ' and (ip_address = :IPADDRESS) '+ 17 | ' and (Datetime("now") > valid_after) '+ 18 | ' and (Datetime("now") < valid_until) '+ 19 | ';' 20 | ); 21 | 22 | end; 23 | end; -------------------------------------------------------------------------------- /sql/system/person_role_check/person_role_check_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] role_check 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('select '+ 11 | ' role_id, valid_until '+ 12 | 'from '+ 13 | ' person_role '+ 14 | 'where '+ 15 | ' person_id = :PERSONID '+ 16 | ' and (valid_after < Datetime("now")) '+ 17 | ' and (valid_until > Datetime("now")) '+ 18 | 'order by '+ 19 | ' role_id;' 20 | ); 21 | 22 | end; 23 | end; 24 | -------------------------------------------------------------------------------- /sql/ai/chatai/chatai_recent_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] chatai_recent 2 | 3 | if (MainForm.DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('SELECT '+ 11 | ' chat_id, '+ 12 | ' conversation, '+ 13 | ' context, '+ 14 | ' response, '+ 15 | ' last_modified '+ 16 | 'FROM '+ 17 | ' chatai_history '+ 18 | 'ORDER BY '+ 19 | ' last_modified DESC '+ 20 | 'LIMIT '+ 21 | ' 100;' 22 | ); 23 | 24 | end; 25 | end; 26 | 27 | 28 | -------------------------------------------------------------------------------- /sql/system/login_history_insert/login_history_insert_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] login_history_insert 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('insert into '+ 11 | ' login_history '+ 12 | ' (logged_in, person_id, ip_address, application, version ) '+ 13 | 'values( '+ 14 | ' :LOGGEDIN, '+ 15 | ' :PERSONID, '+ 16 | ' :IPADDRESS, '+ 17 | ' :APPLICATION, '+ 18 | ' :VERSION '+ 19 | ');' 20 | ); 21 | 22 | end; 23 | end; 24 | -------------------------------------------------------------------------------- /sql/ai/imageai/imageai_recent_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] imageai_recent 2 | 3 | if (MainForm.DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('SELECT '+ 11 | ' chat_id, '+ 12 | ' prompt, '+ 13 | ' "" generated_image, '+ 14 | ' last_modified '+ 15 | 'FROM '+ 16 | ' imageai_history '+ 17 | 'ORDER BY '+ 18 | ' last_modified DESC '+ 19 | 'LIMIT '+ 20 | ' 100;' 21 | ); 22 | 23 | end; 24 | end; 25 | 26 | 27 | -------------------------------------------------------------------------------- /sql/person/login_recent/login_recent_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] login_recent 2 | 3 | if (MainForm.DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('SELECT '+ 11 | ' logged_in, '+ 12 | ' ip_address, '+ 13 | ' application, '+ 14 | ' version '+ 15 | 'FROM '+ 16 | ' login_history '+ 17 | 'WHERE '+ 18 | ' (person_id = :PERSONID) '+ 19 | ' and (logged_in >= datetime("now" , "-7 days")) '+ 20 | 'ORDER BY '+ 21 | ' logged_in DESC; ' 22 | ); 23 | 24 | end; 25 | end; 26 | -------------------------------------------------------------------------------- /sql/system/token_insert/token_insert_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] token_insert 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('insert into '+ 11 | ' token '+ 12 | ' (token_hash, valid_after, valid_until, person_id, application, version, ip_address) '+ 13 | 'values( '+ 14 | ' :TOKENHASH, '+ 15 | ' :VALIDAFTER, '+ 16 | ' :VALIDUNTIL, '+ 17 | ' :PERSONID, '+ 18 | ' :APPLICATION, '+ 19 | ' :VERSION, '+ 20 | ' :IPADDRESS '+ 21 | ');' 22 | ); 23 | 24 | end; 25 | end; -------------------------------------------------------------------------------- /units/DashboardService.pas: -------------------------------------------------------------------------------- 1 | unit DashboardService; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | XData.Security.Attributes, 8 | XData.Service.Common; 9 | 10 | type 11 | [ServiceContract] 12 | IDashboardService = interface(IInvokable) 13 | ['{925353F2-1ADD-4239-90F3-37BC41F48332}'] 14 | 15 | /// 16 | /// Return JSON for setting up an Administrator dashboard. 17 | /// 18 | /// 19 | /// JWT is used to determine Administrator identity. 20 | /// 21 | [HttpGet] [Authorize] function AdministratorDashboard: TStream; 22 | end; 23 | 24 | implementation 25 | 26 | initialization 27 | RegisterServiceType(TypeInfo(IDashboardService)); 28 | 29 | end. 30 | -------------------------------------------------------------------------------- /sql/ai/imageai/log_imageai_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] log_imageai 2 | 3 | if (MainForm.DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('insert into '+ 11 | ' imageai_history '+ 12 | ' (chat_id, last_modified, last_modifier, model, model_actual, cost_total, prompt, generated_image ) '+ 13 | 'values( '+ 14 | ' :CHATID, '+ 15 | ' :LASTMODIFIED, '+ 16 | ' :LASTMODIFIER, '+ 17 | ' :MODEL, '+ 18 | ' :MODELACTUAL, '+ 19 | ' :COSTTOTAL, '+ 20 | ' :PROMPT, '+ 21 | ' :GENERATEDIMAGE '+ 22 | ');' 23 | ); 24 | 25 | end; 26 | end; 27 | 28 | -------------------------------------------------------------------------------- /sql/system/action_history_insert/action_history_insert_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] action_history_insert 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('insert into '+ 11 | ' action_history '+ 12 | ' (person_id, ip_address, application, version, session_id, session_start, session_recorded, actions) '+ 13 | 'values( '+ 14 | ' :PERSONID, '+ 15 | ' :IPADDRESS, '+ 16 | ' :APPLICATION, '+ 17 | ' :VERSION, '+ 18 | ' :SESSIONID, '+ 19 | ' :SESSIONSTART, '+ 20 | ' :SESSIONRECORDED, '+ 21 | ' :ACTIONS '+ 22 | ');' 23 | ); 24 | 25 | end; 26 | end; 27 | -------------------------------------------------------------------------------- /sql/person/profile/profile_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] profile 2 | 3 | if (MainForm.DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('SELECT '+ 11 | ' p.last_name, '+ 12 | ' p.first_name, '+ 13 | ' p.middle_name, '+ 14 | ' p.birthdate, '+ 15 | ' p.account_name, '+ 16 | ' p.last_modified, '+ 17 | ' p.last_modifier, '+ 18 | ' m.account_name last_modifier_account '+ 19 | 'FROM '+ 20 | ' person p '+ 21 | ' LEFT OUTER JOIN '+ 22 | ' person m '+ 23 | ' on p.last_modifier = m.person_id '+ 24 | 'WHERE '+ 25 | ' p.person_id = :PERSONID; ' 26 | ); 27 | 28 | end; 29 | end; 30 | -------------------------------------------------------------------------------- /sql/system/endpoint_history_insert/endpoint_history_insert_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] endpoint_history_insert 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('insert into '+ 11 | ' endpoint_history '+ 12 | ' (person_id, endpoint, accessed, execution_ms, ip_address, application, version, database_name, database_engine, details) '+ 13 | 'values( '+ 14 | ' :PERSONID, '+ 15 | ' :ENDPOINT, '+ 16 | ' :ACCESSED, '+ 17 | ' :EXECUTIONMS, '+ 18 | ' :IPADDRESS, '+ 19 | ' :APPLICATION, '+ 20 | ' :VERSION, '+ 21 | ' :DATABASENAME, '+ 22 | ' :DATABASEENGINE, '+ 23 | ' :DETAILS '+ 24 | ');' 25 | ); 26 | 27 | end; 28 | end; 29 | -------------------------------------------------------------------------------- /sql/system/ip_block_insert/ip_block_insert_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] ip_block_insert 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('delete from '+ 11 | ' ip_block '+ 12 | 'where '+ 13 | ' (ip_address = :IPADDRESS) '+ 14 | ' or (valid_until < Datetime("now")); ' 15 | ); 16 | SQL.Add('insert into '+ 17 | ' ip_block '+ 18 | ' (ip_address, last_modified, last_modifier, valid_after, valid_until, justification) '+ 19 | 'values( '+ 20 | ' :IPADDRESS, '+ 21 | ' Datetime("now"), '+ 22 | ' 1, '+ 23 | ' Datetime("now"), '+ 24 | ' Datetime("now", "+10 minutes"), '+ 25 | ' :REASON); ' 26 | ); 27 | 28 | end; 29 | end; 30 | 31 | -------------------------------------------------------------------------------- /sql/person/directory/directory_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] directory 2 | 3 | if (MainForm.DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('SELECT '+ 11 | ' last_name as "Last Name", '+ 12 | ' first_name as "First Name", '+ 13 | ' middle_name as "Middle Name", '+ 14 | ' list.value as "Type", '+ 15 | ' contact.value as "Value" '+ 16 | 'FROM '+ 17 | ' person '+ 18 | ' LEFT OUTER JOIN contact '+ 19 | ' ON person.person_id = contact.person_id '+ 20 | ' LEFT OUTER JOIN list '+ 21 | ' ON list.list_id = 1 '+ 22 | ' AND contact.list_contact = list.lookup_id '+ 23 | 'ORDER BY '+ 24 | ' last_name, '+ 25 | ' contact.preference ' 26 | ); 27 | 28 | end; 29 | end; 30 | -------------------------------------------------------------------------------- /sql/person/role/role_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] role 2 | 3 | if (MainForm.DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('SELECT '+ 11 | ' pr.role_id, '+ 12 | ' pr.valid_after, '+ 13 | ' pr.valid_until, '+ 14 | ' pr.last_modified, '+ 15 | ' pr.last_modifier, '+ 16 | ' m.account_name last_modifier_account, '+ 17 | ' r.name, '+ 18 | ' r.icon '+ 19 | 'FROM '+ 20 | ' person_role pr '+ 21 | ' LEFT OUTER JOIN '+ 22 | ' person m '+ 23 | ' on pr.last_modifier = m.person_id '+ 24 | ' LEFT OUTER JOIN '+ 25 | ' role r '+ 26 | ' on pr.role_id = r.role_id '+ 27 | 'WHERE '+ 28 | ' pr.person_id = :PERSONID '+ 29 | 'ORDER BY '+ 30 | ' pr.valid_after; ' 31 | ); 32 | 33 | end; 34 | end; 35 | -------------------------------------------------------------------------------- /docs/Accounts.md: -------------------------------------------------------------------------------- 1 | # Accounts 2 | In order to login, the client application will need to use the PersonService to authenticate and retrieve the necessary authorization for a given account - essentially a JWT. 3 | 4 | Briefly, this involves comparing a username/password with data contained in the ***person*** table. Sample data has been provided for this purpose, and is defined in the [DDL](https://github.com/500Foods/TMS-XData-TemplateDemoData/blob/main/ddl/person/person_sqlite.inc) for that table. 5 | 6 | In addition, the person needs to have the "login role" assigned, where the login role is defined as role_id = 0. This is managed using the ***person_role*** table. 7 | 8 | Default Administrator Credentials: 9 | ``` 10 | Username: SYSINSTALLER 11 | Password: TMSWEBCore 12 | ``` 13 | Naturally, this sample content should be replaced with actual data before deploying this project anywhere publicly visible. 14 | 15 | Note that the password is stored as an SHA-256 digest of the password, with the "XData-Password:" prefix added to the password before it is hashed. 16 | -------------------------------------------------------------------------------- /sql/person/contact/contact_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] contact 2 | 3 | if (MainForm.DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('SELECT '+ 11 | ' c.contact_id, '+ 12 | ' c.last_modified, '+ 13 | ' c.last_modifier, '+ 14 | ' c.list_contact, '+ 15 | ' c.value, '+ 16 | ' c.preference, '+ 17 | ' c.login_use, '+ 18 | ' m.account_name last_modifier_account, '+ 19 | ' l.value contact_type, '+ 20 | ' l.attributes contact_attributes '+ 21 | 'FROM '+ 22 | ' contact c '+ 23 | ' LEFT OUTER JOIN '+ 24 | ' person m '+ 25 | ' on c.last_modifier = m.person_id '+ 26 | ' LEFT OUTER JOIN '+ 27 | ' list l '+ 28 | ' on l.list_id = 1 '+ 29 | ' and c.list_contact = l.lookup_id '+ 30 | 'WHERE '+ 31 | ' c.person_id = :PERSONID '+ 32 | 'ORDER BY '+ 33 | ' c.preference; ' 34 | ); 35 | 36 | end; 37 | end; 38 | -------------------------------------------------------------------------------- /docs/ChatService.md: -------------------------------------------------------------------------------- 1 | # Chat Service 2 | The Chat Service endpoints provide access to either the OpenAI ChatGPT service, or to GPT4All, depending on how the installation is configured. These enpdoints facilitate back-and-forth chat conversations, logging chats, viewing past chats, and other chat-related statistics to be used in the main Chat Dashboard within the TMS WEB Core Template Demo project. 3 | 4 | ### Configuration 5 | In order for these endpoints to work, a number of configuration parameters must be passed to the XData application. This is typically done by creating a suitably popupalted JSON file, and then using the CONFIG command-line parameter to tell the XData application where it is. 6 | 7 | ### OpenAI 8 | Here's what a configuration file might look like when using the OpenAI ChatGPT API. 9 | ``` 10 | { 11 | "Name": "ChatGPT 3.5", 12 | "Default": true, 13 | "Model": "gpt-3.5-turbo", 14 | "Organization": "your-org-identifier-here", 15 | "API Key": "your-api-key-here", 16 | "Endpoint": "https://api.openai.com/v1/chat/completions", 17 | "Limit": 4096, 18 | "Cost Prompt": 0.000002, 19 | "Cost Completion": 0.000002 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /sql/ai/chatai/chatai_usage_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] chatai_usage 2 | 3 | if (MainForm.DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('select '+ 11 | ' "7 days" period, '+ 12 | ' count(*) requests, '+ 13 | ' coalesce(sum(cost_total),0) cost '+ 14 | ' from '+ 15 | ' chatai_history '+ 16 | ' WHERE '+ 17 | ' last_modified BETWEEN datetime("now", "-7 days") AND datetime("now") '+ 18 | 19 | 'UNION '+ 20 | 21 | 'select '+ 22 | ' "30 days" period, '+ 23 | ' count(*) requests, '+ 24 | ' coalesce(sum(cost_total),0) cost '+ 25 | ' from '+ 26 | ' chatai_history '+ 27 | ' WHERE '+ 28 | ' last_modified BETWEEN datetime("now", "-30 days") AND datetime("now") '+ 29 | 30 | 'UNION '+ 31 | 32 | 'select '+ 33 | ' "all" period, '+ 34 | ' count(*) requests, '+ 35 | ' coalesce(sum(cost_total),0) cost '+ 36 | ' from '+ 37 | ' chatai_history;' 38 | ); 39 | 40 | end; 41 | end; 42 | 43 | 44 | -------------------------------------------------------------------------------- /sql/ai/imageai/imageai_usage_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] imageai_usage 2 | 3 | if (MainForm.DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('select '+ 11 | ' "7 days" period, '+ 12 | ' count(*) requests, '+ 13 | ' coalesce(sum(cost_total),0) cost '+ 14 | ' from '+ 15 | ' imageai_history '+ 16 | ' WHERE '+ 17 | ' last_modified BETWEEN datetime("now", "-7 days") AND datetime("now") '+ 18 | 19 | 'UNION '+ 20 | 21 | 'select '+ 22 | ' "30 days" period, '+ 23 | ' count(*) requests, '+ 24 | ' coalesce(sum(cost_total),0) cost '+ 25 | ' from '+ 26 | ' imageai_history '+ 27 | ' WHERE '+ 28 | ' last_modified BETWEEN datetime("now", "-30 days") AND datetime("now") '+ 29 | 30 | 'UNION '+ 31 | 32 | 'select '+ 33 | ' "all" period, '+ 34 | ' count(*) requests, '+ 35 | ' coalesce(sum(cost_total),0) cost '+ 36 | ' from '+ 37 | ' imageai_history;' 38 | ); 39 | 40 | end; 41 | end; 42 | 43 | 44 | -------------------------------------------------------------------------------- /sql/ai/chatai/log_chatai_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] log_chatai 2 | 3 | if (MainForm.DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('delete from '+ 11 | ' chatai_history '+ 12 | 'where '+ 13 | ' chat_id = :CHATID;'+ 14 | ' '+ 15 | 'replace into '+ 16 | ' chatai_history '+ 17 | ' (chat_id, last_modified, last_modifier, model, model_actual, cost_prompt, cost_completion, cost_total, '+ 18 | ' token_prompt, token_completion, token_total, conversation, context, response, reason ) '+ 19 | 'values( '+ 20 | ' :CHATID, '+ 21 | ' :LASTMODIFIED, '+ 22 | ' :LASTMODIFIER, '+ 23 | ' :MODEL, '+ 24 | ' :MODELACTUAL, '+ 25 | ' :COSTPROMPT, '+ 26 | ' :COSTCOMPLETION, '+ 27 | ' :COSTTOTAL, '+ 28 | ' :TOKENPROMPT, '+ 29 | ' :TOKENCOMPLETION, '+ 30 | ' :TOKENTOTAL, '+ 31 | ' :CONVERSATION, '+ 32 | ' :CONTEXT, '+ 33 | ' :RESPONSE, '+ 34 | ' :REASON '+ 35 | ');' 36 | ); 37 | 38 | end; 39 | end; 40 | 41 | 42 | -------------------------------------------------------------------------------- /XDataServer.dpr: -------------------------------------------------------------------------------- 1 | program XDataServer; 2 | 3 | uses 4 | Vcl.Forms, 5 | Unit1 in 'units\Unit1.pas' {ServerContainer: TDataModule}, 6 | Unit2 in 'units\Unit2.pas' {MainForm}, 7 | Unit3 in 'units\Unit3.pas' {DBSupport: TDataModule}, 8 | SystemService in 'units\SystemService.pas', 9 | SystemServiceImplementation in 'units\SystemServiceImplementation.pas', 10 | TZDB in 'TZDB.pas', 11 | PersonService in 'units\PersonService.pas', 12 | PersonServiceImplementation in 'units\PersonServiceImplementation.pas', 13 | DashboardService in 'units\DashboardService.pas', 14 | DashboardServiceImplementation in 'units\DashboardServiceImplementation.pas', 15 | ChatService in 'units\ChatService.pas', 16 | ChatServiceImplementation in 'units\ChatServiceImplementation.pas', 17 | MessagingService in 'units\MessagingService.pas', 18 | MessagingServiceImplementation in 'units\MessagingServiceImplementation.pas'; 19 | 20 | {$R *.res} 21 | 22 | begin 23 | Application.Initialize; 24 | Application.MainFormOnTaskbar := True; 25 | Application.Title := 'TMS XData Template Demo Data'; 26 | Application.CreateForm(TServerContainer, ServerContainer); 27 | Application.CreateForm(TMainForm, MainForm); 28 | Application.CreateForm(TDBSupport, DBSupport); 29 | Application.Run; 30 | end. 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /sql/system/login_cleanup/login_cleanup_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] login_cleanup 2 | 3 | if (DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | 11 | // We don't need to keep these around any longer than necessary 12 | 13 | SQL.Add('delete from '+ 14 | ' login_fail '+ 15 | 'where '+ 16 | ' attempted < Datetime("now", "-24 hours");' 17 | ); 18 | 19 | SQL.Add('delete from '+ 20 | ' login_fail '+ 21 | 'where '+ 22 | ' (ip_address = :IPADDRESS) '+ 23 | ' and (login_id = :LOGINID); ' 24 | ); 25 | 26 | SQL.Add('delete from '+ 27 | ' login_history '+ 28 | 'where '+ 29 | ' logged_in < Datetime("now", "-90 days");' 30 | ); 31 | 32 | SQL.Add('delete from '+ 33 | ' ip_block '+ 34 | 'where '+ 35 | ' valid_until < Datetime("now", "-7 days");' 36 | ); 37 | 38 | SQL.Add('delete from '+ 39 | ' ip_allow '+ 40 | 'where '+ 41 | ' valid_until < Datetime("now", "-7 days");' 42 | ); 43 | 44 | SQL.Add('delete from '+ 45 | ' token '+ 46 | 'where '+ 47 | ' valid_until < Datetime("now", "-24 hours");' 48 | ); 49 | end; 50 | end; 51 | -------------------------------------------------------------------------------- /units/Unit1.dfm: -------------------------------------------------------------------------------- 1 | object ServerContainer: TServerContainer 2 | OldCreateOrder = False 3 | OnCreate = DataModuleCreate 4 | Height = 210 5 | Width = 431 6 | object SparkleHttpSysDispatcher: TSparkleHttpSysDispatcher 7 | Left = 72 8 | Top = 16 9 | end 10 | object XDataServer: TXDataServer 11 | BaseUrl = 'http://+:12345/tms/xdata' 12 | Dispatcher = SparkleHttpSysDispatcher 13 | Pool = XDataConnectionPool 14 | EntitySetPermissions = <> 15 | SwaggerOptions.Enabled = True 16 | SwaggerOptions.AuthMode = Jwt 17 | SwaggerUIOptions.Enabled = True 18 | SwaggerUIOptions.ShowFilter = True 19 | SwaggerUIOptions.TryItOutEnabled = True 20 | RedocOptions.Enabled = True 21 | Left = 216 22 | Top = 16 23 | object XDataServerJWT: TSparkleJwtMiddleware 24 | Secret = 25 | 'ThisIsAReallyLongSecretThatReallyDoesNeedToBeLongAsItIsUsedAsACr' + 26 | 'iticalPartOfOurXDataSecurityModel' 27 | end 28 | object XDataServerCompress: TSparkleCompressMiddleware 29 | end 30 | object XDataServerCORS: TSparkleCorsMiddleware 31 | Origin = '*' 32 | end 33 | end 34 | object XDataConnectionPool: TXDataConnectionPool 35 | Connection = AureliusConnection 36 | Left = 216 37 | Top = 72 38 | end 39 | object AureliusConnection: TAureliusConnection 40 | Left = 216 41 | Top = 128 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /ddl/login_fail/login_fail_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [table] login_fail 2 | // 3 | // We don't really have any reason to create sample data here. 4 | 5 | TableName := 'login_fail'; 6 | 7 | if (DatabaseEngine = 'sqlite') then 8 | begin 9 | 10 | with Query1 do 11 | begin 12 | 13 | // Check if the table exists 14 | SQL.Clear; 15 | SQL.Add('select count(*) records from '+TableName+';'); 16 | try 17 | Open; 18 | LogEvent('...'+TableName+' ('+IntToStr(FieldByName('records').AsInteger)+' records)'); 19 | 20 | except on E:Exception do 21 | begin 22 | LogEvent('...'+TableName+' (CREATE)'); 23 | SQL.Clear; 24 | SQL.Add('create table if not exists '+TableName+' ( '+ 25 | ' login_id text NOT NULL, '+ 26 | ' ip_address text NOT NULL, '+ 27 | ' attempted integer NOT NULL, '+ 28 | ' CONSTRAINT constraint_name PRIMARY KEY (login_id,ip_address,attempted) '+ 29 | ');' 30 | ); 31 | 32 | ExecSQL; 33 | 34 | // Try it again 35 | SQL.Clear; 36 | SQL.Add('select count(*) records from '+TableName+';'); 37 | Open; 38 | end; 39 | end; 40 | 41 | // Populate empty table with sample data 42 | // if (FieldByName('records').AsInteger = 0) then 43 | // begin 44 | // LogEvent('...'+TableName+' (POPULATE)'); 45 | // SQL.Clear; 46 | // end; 47 | 48 | end; 49 | end; 50 | 51 | -------------------------------------------------------------------------------- /units/PersonService.pas: -------------------------------------------------------------------------------- 1 | unit PersonService; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | XData.Security.Attributes, 8 | XData.Service.Common; 9 | 10 | type 11 | [ServiceContract] 12 | IPersonService = interface(IInvokable) 13 | ['{B3F998DC-587F-442D-8101-97819329D6C9}'] 14 | 15 | /// 16 | /// Return directory dataset. 17 | /// 18 | /// 19 | /// All person records are returned, along with their contact information. 20 | /// 21 | /// 22 | /// Valid dataset formats for this request include the following. 23 | /// - CSV - Comma Separated Values Format, includes header row 24 | /// - PLAIN - No delimiters or header row 25 | /// - FIREDAC: FireDAC JSON Format 26 | /// - BINARY: FireDAC Binary Format 27 | /// - XML: FireDAC XML Format 28 | /// - JSON: Simple JSON Format 29 | /// 30 | [Authorize] [HttpGet] function Directory(Format: String): TStream; 31 | 32 | /// 33 | /// Return user profile information. 34 | /// 35 | /// 36 | /// The profile information is returned as JSONbased on the person that is 37 | /// logged in - the JWT is used to determine the person_id. 38 | /// 39 | [Authorize] [HttpGet] function Profile: TStream; 40 | 41 | end; 42 | 43 | implementation 44 | 45 | initialization 46 | RegisterServiceType(TypeInfo(IPersonService)); 47 | 48 | end. 49 | -------------------------------------------------------------------------------- /docs/MessagingService.md: -------------------------------------------------------------------------------- 1 | # Messaging Service 2 | The Messaging Service endpoints provide access to send and recevie SMS messages, and potentially other messages (MMS and WhatsApp potentially), using third-party REST APIs. Currently, Twilio has been implemented, but others may soon follow. 3 | 4 | ### Configuration 5 | In order for these endpoints to work, a number of configuration parameters must be passed to the XData application. This is typically done by creating a suitably popupalted JSON file, and then using the CONFIG command-line parameter to tell the XData application where it is. 6 | 7 | ### Twilio 8 | Here's what a configuration file might look like when using the OpenAI ChatGPT API. 9 | ``` 10 | "Messaging Services":{ 11 | "Twilio": { 12 | "Service Name":"Twilio Messaging", 13 | "Account": "AC5346342f844fc5b7dd41412ed8eXXXXX", 14 | "Auth Token": "df21f3f76799992f8b52164021bXXXXX", 15 | "Send URL":"https://api.twilio.com/2010-04-01/Accounts/AC5346342f844fc5b7dd41412ed8eXXXXX/Messages.json", 16 | "Messaging Services":[ 17 | {"500 Dashboards Notify":"MGec8ddf1c3187241921577f73f5XXXXX"}, 18 | {"500 Dashboards Support":"MGec8ddf1c3187241921577f73fYYYYY"}, 19 | {"500 Dashboards Billing":"MGec8ddf1c3187241921577f73f5ZZZZZ"} 20 | ] 21 | } 22 | } 23 | ``` 24 | 25 | ### Additional Information 26 | The details of this implementation first appeared as the subject of a blog post on the TMS Software website, which can be found [here](https://www.tmssoftware.com/site/blog.asp?post=1159)https://www.tmssoftware.com/site/blog.asp?post=1159. 27 | -------------------------------------------------------------------------------- /ddl/login_history/login_history_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [table] login_history 2 | // 3 | // We don't really have any reason to create sample data here. 4 | 5 | TableName := 'login_history'; 6 | 7 | if (DatabaseEngine = 'sqlite') then 8 | begin 9 | 10 | with Query1 do 11 | begin 12 | 13 | // Check if the table exists 14 | SQL.Clear; 15 | SQL.Add('select count(*) records from '+TableName+';'); 16 | try 17 | Open; 18 | LogEvent('...'+TableName+' ('+IntToStr(FieldByName('records').AsInteger)+' records)'); 19 | 20 | except on E:Exception do 21 | begin 22 | LogEvent('...'+TableName+' (CREATE)'); 23 | SQL.Clear; 24 | SQL.Add('create table if not exists '+TableName+' ( '+ 25 | ' logged_in text NOT NULL, '+ 26 | ' person_id integer NOT NULL, '+ 27 | ' ip_address text NOT NULL, '+ 28 | ' application text NOT NULL, '+ 29 | ' version text NOT NULL, '+ 30 | ' CONSTRAINT constraint_name PRIMARY KEY (logged_in, person_id, ip_address) '+ 31 | ');' 32 | ); 33 | ExecSQL; 34 | 35 | // Try it again 36 | SQL.Clear; 37 | SQL.Add('select count(*) records from '+TableName+';'); 38 | Open; 39 | end; 40 | end; 41 | 42 | // Populate empty table with sample data 43 | // if (FieldByName('records').AsInteger = 0) then 44 | // begin 45 | // LogEvent('...'+TableName+' (POPULATE)'); 46 | // SQL.Clear; 47 | // end; 48 | 49 | end; 50 | end; 51 | 52 | -------------------------------------------------------------------------------- /ddl/token/token_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [table] token 2 | // 3 | // We don't really have any reason to create sample data here. 4 | 5 | TableName := 'token'; 6 | 7 | if (DatabaseEngine = 'sqlite') then 8 | begin 9 | 10 | with Query1 do 11 | begin 12 | 13 | // Check if the table exists 14 | SQL.Clear; 15 | SQL.Add('select count(*) records from '+TableName+';'); 16 | try 17 | Open; 18 | LogEvent('...'+TableName+' ('+IntToStr(FieldByName('records').AsInteger)+' records)'); 19 | 20 | except on E:Exception do 21 | begin 22 | LogEvent('...'+TableName+' (CREATE)'); 23 | SQL.Clear; 24 | SQL.Add('create table if not exists '+TableName+' ( '+ 25 | ' token_hash text NOT NULL, '+ 26 | ' valid_after text NOT NULL, '+ 27 | ' valid_until text NOT NULL, '+ 28 | ' person_id integer NOT NULL, '+ 29 | ' application text NOT NULL, '+ 30 | ' version text NOT NULL, '+ 31 | ' ip_address text NOT NULL, '+ 32 | ' CONSTRAINT constraint_name PRIMARY KEY (token_hash) '+ 33 | ');' 34 | ); 35 | ExecSQL; 36 | 37 | // Try it again 38 | SQL.Clear; 39 | SQL.Add('select count(*) records from '+TableName+';'); 40 | Open; 41 | end; 42 | end; 43 | 44 | // Populate empty table with sample data 45 | // if (FieldByName('records').AsInteger = 0) then 46 | // begin 47 | // LogEvent('...'+TableName+' (POPULATE)'); 48 | // SQL.Clear; 49 | // end; 50 | 51 | end; 52 | end; 53 | 54 | -------------------------------------------------------------------------------- /ddl/action_history/action_history_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [table] action_history 2 | // 3 | // We don't really have any reason to create sample data here. 4 | 5 | TableName := 'action_history'; 6 | 7 | if (DatabaseEngine = 'sqlite') then 8 | begin 9 | 10 | with Query1 do 11 | begin 12 | 13 | // Check if the table exists 14 | SQL.Clear; 15 | SQL.Add('select count(*) records from '+TableName+';'); 16 | try 17 | Open; 18 | LogEvent('...'+TableName+' ('+IntToStr(FieldByName('records').AsInteger)+' records)'); 19 | 20 | except on E:Exception do 21 | begin 22 | LogEvent('...'+TableName+' (CREATE)'); 23 | SQL.Clear; 24 | SQL.Add('create table if not exists '+TableName+' ( '+ 25 | ' person_id integer NOT NULL, '+ 26 | ' ip_address text NOT NULL, '+ 27 | ' application text NOT NULL, '+ 28 | ' version text NOT NULL, '+ 29 | ' session_id text NOT NULL, '+ 30 | ' session_start text NOT NULL, '+ 31 | ' session_recorded text NOT NULL, '+ 32 | ' actions text NOT NULL '+ 33 | ');' 34 | ); 35 | ExecSQL; 36 | 37 | // Try it again 38 | SQL.Clear; 39 | SQL.Add('select count(*) records from '+TableName+';'); 40 | Open; 41 | end; 42 | end; 43 | 44 | // Populate empty table with sample data 45 | // if (FieldByName('records').AsInteger = 0) then 46 | // begin 47 | // LogEvent('...'+TableName+' (POPULATE)'); 48 | // SQL.Clear; 49 | // end; 50 | 51 | end; 52 | end; 53 | 54 | -------------------------------------------------------------------------------- /ddl/imageai_history/imageai_history_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [table] imageai_history 2 | // 3 | // No sample data needed. 4 | 5 | TableName := 'imageai_history'; 6 | 7 | if (DatabaseEngine = 'sqlite') then 8 | begin 9 | 10 | with Query1 do 11 | begin 12 | 13 | // Check if the table exists 14 | SQL.Clear; 15 | SQL.Add('select count(*) records from '+TableName+';'); 16 | try 17 | Open; 18 | LogEvent('...'+TableName+' ('+IntToStr(FieldByName('records').AsInteger)+' records)'); 19 | 20 | except on E:Exception do 21 | begin 22 | LogEvent('...'+TableName+' (CREATE)'); 23 | SQL.Clear; 24 | SQL.Add('create table if not exists '+TableName+' ( '+ 25 | ' chat_id text NOT NULL, '+ 26 | ' last_modified text NOT NULL, '+ 27 | ' last_modifier integer NOT NULL, '+ 28 | ' model text NOT NULL, '+ 29 | ' model_actual text , '+ 30 | ' cost_total real NOT NULL, '+ 31 | ' prompt text NOT NULL, '+ 32 | ' generated_image text NOT NULL, '+ 33 | ' CONSTRAINT constraint_name PRIMARY KEY (chat_id) '+ 34 | ');' 35 | ); 36 | ExecSQL; 37 | 38 | // Try it again 39 | SQL.Clear; 40 | SQL.Add('select count(*) records from '+TableName+';'); 41 | Open; 42 | end; 43 | end; 44 | 45 | // Populate empty table with sample data 46 | // if (FieldByName('records').AsInteger = 0) then 47 | // begin 48 | // LogEvent('...'+TableName+' (POPULATE)'); 49 | // SQL.Clear; 50 | // end; 51 | end; 52 | end; 53 | 54 | 55 | -------------------------------------------------------------------------------- /ddl/ip_block/ip_block_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [table] ip_block 2 | 3 | TableName := 'ip_block'; 4 | 5 | if (DatabaseEngine = 'sqlite') then 6 | begin 7 | 8 | with Query1 do 9 | begin 10 | 11 | // Check if the table exists 12 | SQL.Clear; 13 | SQL.Add('select count(*) records from '+TableName+';'); 14 | try 15 | Open; 16 | LogEvent('...'+TableName+' ('+IntToStr(FieldByName('records').AsInteger)+' records)'); 17 | 18 | except on E:Exception do 19 | begin 20 | LogEvent('...'+TableName+' (CREATE)'); 21 | SQL.Clear; 22 | SQL.Add('create table if not exists '+TableName+' ( '+ 23 | ' ip_address text NOT NULL, '+ 24 | ' last_modified text NOT NULL, '+ 25 | ' last_modifier integer NOT NULL, '+ 26 | ' valid_after text NOT NULL, '+ 27 | ' valid_until text NOT NULL, '+ 28 | ' justification text NOT NULL, '+ 29 | ' CONSTRAINT constraint_name PRIMARY KEY (ip_address) '+ 30 | ');' 31 | ); 32 | ExecSQL; 33 | 34 | // Try it again 35 | SQL.Clear; 36 | SQL.Add('select count(*) records from '+TableName+';'); 37 | Open; 38 | end; 39 | end; 40 | 41 | // Populate empty table with sample data 42 | if (FieldByName('records').AsInteger = 0) then 43 | begin 44 | LogEvent('...'+TableName+' (POPULATE)'); 45 | SQL.Clear; 46 | 47 | // Just as an example 48 | SQL.Add('insert into '+TableName+' values( "0.0.0.0", Datetime("now"), 0, Datetime("now"), Datetime("now", "+100 years"), "Not a valid IP address - Permanent Ban" );'); 49 | 50 | ExecSQL; 51 | end; 52 | end; 53 | end; 54 | 55 | -------------------------------------------------------------------------------- /sql/messaging/messaging/log_message_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [query] log_messaging 2 | 3 | if (MainForm.DatabaseEngine = 'sqlite') then 4 | begin 5 | 6 | with Query1 do 7 | begin 8 | 9 | SQL.Clear; 10 | SQL.Add('insert into '+ 11 | ' messaging '+ 12 | ' (service, created_at, MessageStatus, ErrorMessage, ErrorCode, Direction, RawDlrDoneDate, SmsStatus, SmsSid, SmsMessageSid, AccountSid, MessageSid, MessagingServiceSid, Body, '+ 13 | ' ToNum, ToCountry, ToState, ToCity, ToZip,'+ 14 | ' FromNum, FromCountry, FromState, FromCity, FromZip, '+ 15 | ' NumSegments, NumMedia, Price, PriceUnit, AddOns, Uri, Resource, ApiVersion )'+ 16 | 'values( '+ 17 | ' :service, '+ 18 | ' Datetime("now"), '+ 19 | ' :MessageStatus, '+ 20 | ' :ErrorMessage, '+ 21 | ' :ErrorCode, '+ 22 | ' :Direction, '+ 23 | ' :RawDlrDoneDate, '+ 24 | ' :SmsStatus, '+ 25 | ' :SmsSid, '+ 26 | ' :SmsMessageSid, '+ 27 | ' :AccountSid, '+ 28 | ' :MessageSid, '+ 29 | ' :MessagingServiceSid, '+ 30 | ' :Body, '+ 31 | ' :ToNum, '+ 32 | ' :ToCountry, '+ 33 | ' :ToState, '+ 34 | ' :ToCity, '+ 35 | ' :ToZip, '+ 36 | ' :FromNum, '+ 37 | ' :FromCountry, '+ 38 | ' :FromState, '+ 39 | ' :FromCity, '+ 40 | ' :FromZip, '+ 41 | ' :NumSegments, '+ 42 | ' :NumMedia, '+ 43 | ' :Price, '+ 44 | ' :PriceUnits, '+ 45 | ' :AddOns, '+ 46 | ' :Uri, '+ 47 | ' :Resource, '+ 48 | ' :ApiVersion '+ 49 | ');' 50 | ); 51 | 52 | end; 53 | end; 54 | 55 | 56 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Count Lines of Code 2 | 3 | # Controls when the action will run. Triggers the workflow on push or pull request 4 | # events but only for the main branch 5 | on: 6 | push: 7 | branches: [ main ] 8 | pull_request: 9 | branches: [ main ] 10 | 11 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 12 | jobs: 13 | # This workflow contains a single job called "build" 14 | cloc: 15 | # The type of runner that the job will run on 16 | runs-on: ubuntu-latest 17 | 18 | permissions: 19 | # Give the default GITHUB_TOKEN write permission to commit and push the 20 | # added or changed files to the repository. 21 | contents: write 22 | 23 | # Steps represent a sequence of tasks that will be executed as part of the job 24 | steps: 25 | 26 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 27 | - uses: actions/checkout@v3 28 | 29 | # Runs djdefi/cloc-action 30 | - name: Count Lines of Code (cloc) 31 | uses: djdefi/cloc-action@main 32 | with: 33 | options: --report-file=cloc.txt 34 | 35 | # Copy the new cloc.txt contents into the README.md file at the appropriate spot 36 | - run: csplit README.md /\<\!--CLOC/ {1} 37 | - run: cp xx00 README.md 38 | - run: echo "" >> README.md 39 | - run: echo "\`\`\`" >> README.md 40 | - run: echo "NOW=$(date +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_ENV 41 | - run: echo "Last Updated at ${{ env.NOW }}" >> README.md 42 | - run: tail -n +2 cloc.txt >> README.md 43 | - run: echo "\`\`\`" >> README.md 44 | - run: cat xx02 >> README.md 45 | 46 | # Save the output back to the repository 47 | - uses: stefanzweifel/git-auto-commit-action@v4 48 | with: 49 | skip_dirty_check: true 50 | branch: main 51 | file_pattern: 'README.md' 52 | -------------------------------------------------------------------------------- /units/Unit1.pas: -------------------------------------------------------------------------------- 1 | unit Unit1; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, System.Classes, Sparkle.HttpServer.Module, 7 | Sparkle.HttpServer.Context, Sparkle.Comp.Server, 8 | Sparkle.Comp.HttpSysDispatcher, Aurelius.Drivers.Interfaces, 9 | Aurelius.Comp.Connection, XData.Comp.ConnectionPool, XData.Server.Module, 10 | XData.Comp.Server, Sparkle.Comp.CorsMiddleware, 11 | Sparkle.Comp.CompressMiddleware, Sparkle.Comp.JwtMiddleware, XData.Aurelius.ModelBuilder, 12 | FireDAC.Stan.Intf, FireDAC.Stan.Option, FireDAC.Stan.Error, 13 | FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def, FireDAC.Stan.Pool, 14 | FireDAC.Stan.Async, FireDAC.Phys, FireDAC.VCLUI.Wait, FireDAC.Stan.Param, 15 | FireDAC.DatS, FireDAC.DApt.Intf, FireDAC.DApt, FireDAC.Stan.ExprFuncs, 16 | FireDAC.Phys.SQLiteDef, FireDAC.Phys.SQLite, Data.DB, 17 | FireDAC.Comp.DataSet, FireDAC.Comp.Client; 18 | 19 | type 20 | TServerContainer = class(TDataModule) 21 | SparkleHttpSysDispatcher: TSparkleHttpSysDispatcher; 22 | XDataServer: TXDataServer; 23 | XDataConnectionPool: TXDataConnectionPool; 24 | AureliusConnection: TAureliusConnection; 25 | XDataServerJWT: TSparkleJwtMiddleware; 26 | XDataServerCompress: TSparkleCompressMiddleware; 27 | XDataServerCORS: TSparkleCorsMiddleware; 28 | procedure DataModuleCreate(Sender: TObject); 29 | end; 30 | 31 | var 32 | ServerContainer: TServerContainer; 33 | 34 | implementation 35 | 36 | {%CLASSGROUP 'Vcl.Controls.TControl'} 37 | 38 | {$R *.dfm} 39 | 40 | procedure TServerContainer.DataModuleCreate(Sender: TObject); 41 | begin 42 | // Setup Swagger Header 43 | TXDataModelBuilder.LoadXMLDoc(XDataServer.Model); 44 | XDataServer.Model.Title := 'XData Template Demo API'; 45 | XDataServer.Model.Version := '1.0'; 46 | XDataServer.Model.Description := 47 | '### Overview'#13#10 + 48 | 'This is the REST API for interacting with the XData Template Demo.'; 49 | end; 50 | 51 | end. 52 | -------------------------------------------------------------------------------- /ddl/endpoint_history/endpoint_history_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [table] endpoint_history 2 | // 3 | // We don't really have any reason to create sample data here. 4 | 5 | TableName := 'endpoint_history'; 6 | 7 | if (DatabaseEngine = 'sqlite') then 8 | begin 9 | 10 | with Query1 do 11 | begin 12 | 13 | // Check if the table exists 14 | SQL.Clear; 15 | SQL.Add('select count(*) records from '+TableName+';'); 16 | try 17 | Open; 18 | LogEvent('...'+TableName+' ('+IntToStr(FieldByName('records').AsInteger)+' records)'); 19 | 20 | except on E:Exception do 21 | begin 22 | LogEvent('...'+TableName+' (CREATE)'); 23 | SQL.Clear; 24 | SQL.Add('create table if not exists '+TableName+' ( '+ 25 | ' person_id integer NOT NULL, '+ 26 | ' endpoint text NOT NULL, '+ 27 | ' accessed text NOT NULL, '+ 28 | ' execution_ms integer NOT NULL, '+ 29 | ' ip_address text NOT NULL, '+ 30 | ' application text NOT NULL, '+ 31 | ' version text NOT NULL, '+ 32 | ' database_name text NOT NULL, '+ 33 | ' database_engine text NOT NULL, '+ 34 | ' details text '+ 35 | ');' 36 | ); 37 | 38 | ExecSQL; 39 | 40 | // Try it again 41 | SQL.Clear; 42 | SQL.Add('select count(*) records from '+TableName+';'); 43 | Open; 44 | end; 45 | end; 46 | 47 | // Populate empty table with sample data 48 | // if (FieldByName('records').AsInteger = 0) then 49 | // begin 50 | // LogEvent('...'+TableName+' (POPULATE)'); 51 | // SQL.Clear; 52 | // end; 53 | 54 | end; 55 | end; 56 | 57 | -------------------------------------------------------------------------------- /ddl/ip_allow/ip_allow_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [table] ip_allow 2 | 3 | TableName := 'ip_allow'; 4 | 5 | if (DatabaseEngine = 'sqlite') then 6 | begin 7 | 8 | with Query1 do 9 | begin 10 | 11 | // Check if the table exists 12 | SQL.Clear; 13 | SQL.Add('select count(*) records from '+TableName+';'); 14 | try 15 | Open; 16 | LogEvent('...'+TableName+' ('+IntToStr(FieldByName('records').AsInteger)+' records)'); 17 | 18 | except on E:Exception do 19 | begin 20 | LogEvent('...'+TableName+' (CREATE)'); 21 | SQL.Clear; 22 | SQL.Add('create table if not exists '+TableName+' ( '+ 23 | ' ip_address text NOT NULL, '+ 24 | ' last_modified text NOT NULL, '+ 25 | ' last_modifier integer NOT NULL, '+ 26 | ' valid_after text NOT NULL, '+ 27 | ' valid_until text NOT NULL, '+ 28 | ' justification text NOT NULL, '+ 29 | ' CONSTRAINT constraint_name PRIMARY KEY (ip_address) '+ 30 | ');' 31 | ); 32 | ExecSQL; 33 | 34 | // Try it again 35 | SQL.Clear; 36 | SQL.Add('select count(*) records from '+TableName+';'); 37 | Open; 38 | end; 39 | end; 40 | 41 | // Populate empty table with sample data 42 | if (FieldByName('records').AsInteger = 0) then 43 | begin 44 | LogEvent('...'+TableName+' (POPULATE)'); 45 | SQL.Clear; 46 | 47 | // Local IPs 48 | SQL.Add('insert into '+TableName+' values( "127.0.0.1", Datetime("now"), 0, Datetime("now"), Datetime("now","+100 years"), "Local development IP address - Always Allow" );'); 49 | SQL.Add('insert into '+TableName+' values( "::1", Datetime("now"), 0, Datetime("now"),Datetime("now","+100 years"), "Local development IP address - Always Allow" );'); 50 | 51 | ExecSQL; 52 | end; 53 | end; 54 | end; 55 | 56 | -------------------------------------------------------------------------------- /ddl/role/role_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [table] role 2 | 3 | TableName := 'role'; 4 | 5 | if (DatabaseEngine = 'sqlite') then 6 | begin 7 | 8 | with Query1 do 9 | begin 10 | 11 | // Check if the table exists 12 | SQL.Clear; 13 | SQL.Add('select count(*) records from '+TableName+';'); 14 | try 15 | Open; 16 | LogEvent('...'+TableName+' ('+IntToStr(FieldByName('records').AsInteger)+' records)'); 17 | 18 | except on E:Exception do 19 | begin 20 | LogEvent('...'+TableName+' (CREATE)'); 21 | SQL.Clear; 22 | SQL.Add('create table if not exists '+TableName+' ( '+ 23 | ' role_id integer NOT NULL, '+ 24 | ' last_modified text NOT NULL, '+ 25 | ' last_modifier integer NOT NULL, '+ 26 | ' name text NOT NULL, '+ 27 | ' icon text NOT NULL, '+ 28 | ' CONSTRAINT constraint_name PRIMARY KEY (role_id), '+ 29 | ' UNIQUE(name) '+ 30 | ');' 31 | ); 32 | ExecSQL; 33 | 34 | // Try it again 35 | SQL.Clear; 36 | SQL.Add('select count(*) records from '+TableName+';'); 37 | Open; 38 | end; 39 | end; 40 | 41 | // Populate empty table with sample data 42 | if (FieldByName('records').AsInteger = 0) then 43 | begin 44 | LogEvent('...'+TableName+' (POPULATE)'); 45 | SQL.Clear; 46 | 47 | // Default roles 48 | SQL.Add('insert into '+TableName+' values( 0, Datetime("now"), 0, "Login", ":Login" );'); 49 | SQL.Add('insert into '+TableName+' values( 1, Datetime("now"), 0, "Administrator", ":Administrator" );'); 50 | SQL.Add('insert into '+TableName+' values( 2, Datetime("now"), 0, "People", ":People" );'); 51 | SQL.Add('insert into '+TableName+' values( 3, Datetime("now"), 0, "Labels", ":Labels" );'); 52 | 53 | ExecSQL; 54 | end; 55 | end; 56 | end; 57 | -------------------------------------------------------------------------------- /ddl/api_key/api_key_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [table] api_key 2 | 3 | TableName := 'api_key'; 4 | 5 | if (DatabaseEngine = 'sqlite') then 6 | begin 7 | 8 | with Query1 do 9 | begin 10 | 11 | // Check if the table exists 12 | SQL.Clear; 13 | SQL.Add('select count(*) records from '+TableName+';'); 14 | try 15 | Open; 16 | LogEvent('...'+TableName+' ('+IntToStr(FieldByName('records').AsInteger)+' records)'); 17 | 18 | except on E:Exception do 19 | begin 20 | LogEvent('...'+TableName+' (CREATE)'); 21 | SQL.Clear; 22 | SQL.Add('create table if not exists '+TableName+' ( '+ 23 | ' api_key text NOT NULL, '+ 24 | ' last_modified text NOT NULL, '+ 25 | ' last_modifier integer NOT NULL, '+ 26 | ' valid_after text NOT NULL, '+ 27 | ' valid_until text NOT NULL, '+ 28 | ' application text NOT NULL, '+ 29 | ' CONSTRAINT constraint_name PRIMARY KEY (api_key) '+ 30 | ');' 31 | ); 32 | ExecSQL; 33 | 34 | // Try it again 35 | SQL.Clear; 36 | SQL.Add('select count(*) records from '+TableName+';'); 37 | Open; 38 | end; 39 | end; 40 | 41 | // Populate empty table with sample data 42 | if (FieldByName('records').AsInteger = 0) then 43 | begin 44 | LogEvent('...'+TableName+' (POPULATE)'); 45 | SQL.Clear; 46 | 47 | // Default API Keys for testing - set to expire after three months 48 | SQL.Add('insert into '+TableName+' values( "testing", Datetime("now"), 0, Datetime("now"), Datetime("now", "+3 months"), "Testing XData Template Demo Data" );'); 49 | SQL.Add('insert into '+TableName+' values( "{39A411D0-FB62-4F95-8F34-E63839D6E98E}", Datetime("now"), 0, Datetime("now"), Datetime("now", "+3 months"), "TMS WEB Core Template Demo" );'); 50 | 51 | ExecSQL; 52 | end; 53 | end; 54 | end; 55 | 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Uncomment these types if you want even more clean repository. But be careful. 2 | # It can make harm to an existing project source. Read explanations below. 3 | # 4 | # Resource files are binaries containing manifest, project icon and version info. 5 | # They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files. 6 | #*.res 7 | # 8 | # Type library file (binary). In old Delphi versions it should be stored. 9 | # Since Delphi 2009 it is produced from .ridl file and can safely be ignored. 10 | #*.tlb 11 | # 12 | # Diagram Portfolio file. Used by the diagram editor up to Delphi 7. 13 | # Uncomment this if you are not using diagrams or use newer Delphi version. 14 | #*.ddp 15 | # 16 | # Visual LiveBindings file. Added in Delphi XE2. 17 | # Uncomment this if you are not using LiveBindings Designer. 18 | #*.vlb 19 | # 20 | # Deployment Manager configuration file for your project. Added in Delphi XE2. 21 | # Uncomment this if it is not mobile development and you do not use remote debug feature. 22 | #*.deployproj 23 | # 24 | # C++ object files produced when C/C++ Output file generation is configured. 25 | # Uncomment this if you are not using external objects (zlib library for example). 26 | #*.obj 27 | # 28 | 29 | # Delphi compiler-generated binaries (safe to delete) 30 | *.exe 31 | # *.dll - actually, we ant this one 32 | *.bpl 33 | *.bpi 34 | *.dcp 35 | *.so 36 | *.apk 37 | *.drc 38 | *.map 39 | *.dres 40 | *.rsm 41 | *.tds 42 | *.dcu 43 | *.lib 44 | *.a 45 | *.o 46 | *.ocx 47 | 48 | # Delphi autogenerated files (duplicated info) 49 | *.cfg 50 | *.hpp 51 | *Resource.rc 52 | 53 | # Delphi local files (user-specific info) 54 | *.local 55 | *.identcache 56 | *.projdata 57 | *.tvsconfig 58 | *.dsk 59 | 60 | # Delphi history and backups 61 | __history/ 62 | __recovery/ 63 | *.~* 64 | 65 | # Castalia statistics file (since XE7 Castalia is distributed with Delphi) 66 | *.stat 67 | 68 | # Boss dependency manager vendor folder https://github.com/HashLoad/boss 69 | modules/ 70 | 71 | # Not needed for TMS WEB Core projects on GitHub 72 | Win32/ 73 | Win64/ 74 | TMSWeb/ 75 | -------------------------------------------------------------------------------- /ddl/person_role/person_role_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [table] person_role 2 | 3 | TableName := 'person_role'; 4 | 5 | if (DatabaseEngine = 'sqlite') then 6 | begin 7 | 8 | with Query1 do 9 | begin 10 | 11 | // Check if the table exists 12 | SQL.Clear; 13 | SQL.Add('select count(*) records from '+TableName+';'); 14 | try 15 | Open; 16 | LogEvent('...'+TableName+' ('+IntToStr(FieldByName('records').AsInteger)+' records)'); 17 | 18 | except on E:Exception do 19 | begin 20 | LogEvent('...'+TableName+' (CREATE)'); 21 | SQL.Clear; 22 | SQL.Add('create table if not exists '+TableName+' ( '+ 23 | ' person_id integer NOT NULL, '+ 24 | ' role_id integer NOT NULL, '+ 25 | ' valid_after text NOT NULL, '+ 26 | ' valid_until text NOT NULL, '+ 27 | ' last_modified text NOT NULL, '+ 28 | ' last_modifier integer NOT NULL, '+ 29 | ' CONSTRAINT constraint_name PRIMARY KEY (person_id, role_id, valid_after) '+ 30 | ');' 31 | ); 32 | ExecSQL; 33 | 34 | // Try it again 35 | SQL.Clear; 36 | SQL.Add('select count(*) records from '+TableName+';'); 37 | Open; 38 | end; 39 | end; 40 | 41 | // Populate empty table with sample data 42 | if (FieldByName('records').AsInteger = 0) then 43 | begin 44 | LogEvent('...'+TableName+' (POPULATE)'); 45 | SQL.Clear; 46 | 47 | // Grant System Installer the role of Login(0), Administrator(1), People(2), and Labels(3) 48 | SQL.Add('insert into '+TableName+' values( 0, 0, Datetime("now"), Datetime("now", "+100 years"), Datetime("now"), 0);'); 49 | SQL.Add('insert into '+TableName+' values( 0, 1, Datetime("now"), Datetime("now", "+100 years"), Datetime("now"), 0);'); 50 | SQL.Add('insert into '+TableName+' values( 0, 2, Datetime("now"), Datetime("now", "+100 years"), Datetime("now"), 0);'); 51 | SQL.Add('insert into '+TableName+' values( 0, 3, Datetime("now"), Datetime("now", "+100 years"), Datetime("now"), 0);'); 52 | 53 | ExecSQL; 54 | end; 55 | end; 56 | end; 57 | 58 | -------------------------------------------------------------------------------- /ddl/chatai_history/chatai_history_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [table] chatai_history 2 | // 3 | // No sample data 4 | 5 | TableName := 'chatai_history'; 6 | 7 | if (DatabaseEngine = 'sqlite') then 8 | begin 9 | 10 | with Query1 do 11 | begin 12 | 13 | // Check if the table exists 14 | SQL.Clear; 15 | SQL.Add('select count(*) records from '+TableName+';'); 16 | try 17 | Open; 18 | LogEvent('...'+TableName+' ('+IntToStr(FieldByName('records').AsInteger)+' records)'); 19 | 20 | except on E:Exception do 21 | begin 22 | LogEvent('...'+TableName+' (CREATE)'); 23 | SQL.Clear; 24 | SQL.Add('create table if not exists '+TableName+' ( '+ 25 | ' chat_id text NOT NULL, '+ 26 | ' last_modified text NOT NULL, '+ 27 | ' last_modifier integer NOT NULL, '+ 28 | ' model text NOT NULL, '+ 29 | ' model_actual text , '+ 30 | ' cost_prompt real NOT NULL, '+ 31 | ' cost_completion real NOT NULL, '+ 32 | ' cost_total real NOT NULL, '+ 33 | ' token_prompt integer NOT NULL, '+ 34 | ' token_completion integer NOT NULL, '+ 35 | ' token_total integer NOT NULL, '+ 36 | ' conversation text NOT NULL, '+ 37 | ' context text , '+ 38 | ' response text NOT NULL, '+ 39 | ' reason text NOT NULL, '+ 40 | ' CONSTRAINT constraint_name PRIMARY KEY (chat_id) '+ 41 | ');' 42 | ); 43 | ExecSQL; 44 | 45 | // Try it again 46 | SQL.Clear; 47 | SQL.Add('select count(*) records from '+TableName+';'); 48 | Open; 49 | end; 50 | end; 51 | 52 | // Populate empty table with sample data 53 | // if (FieldByName('records').AsInteger = 0) then 54 | // begin 55 | // LogEvent('...'+TableName+' (POPULATE)'); 56 | // SQL.Clear; 57 | // end; 58 | end; 59 | end; 60 | 61 | 62 | -------------------------------------------------------------------------------- /units/MessagingService.pas: -------------------------------------------------------------------------------- 1 | unit MessagingService; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | XData.Security.Attributes, 8 | XData.Service.Common; 9 | 10 | type 11 | [ServiceContract] 12 | IMessagingService = interface(IInvokable) 13 | ['{51CE7921-7949-40C2-ACB9-257998E2F054}'] 14 | 15 | 16 | /// 17 | /// Twilio Webhook Callback 18 | /// 19 | /// 20 | /// After sending a message, Twilio issues a webhook callback to here 21 | /// with the details of the transaction. Incoming messages arrive here 22 | /// as well. 23 | /// 24 | /// 25 | /// This is the body of the Twilio message which may contain a number 26 | /// of URL-encoded parameters. 27 | /// 28 | [HttpPost] function Callback(Incoming: TStream):TStream; 29 | 30 | 31 | /// 32 | /// Handle Webhook Fallback - Same as Callback, reallly 33 | /// 34 | /// 35 | /// After sending a message, Twilio issues a webhook callback to here 36 | /// with the details of the transaction if the callback doesn't get a 37 | /// suitably formatted response 38 | /// 39 | /// 40 | /// This is the body of the Twilio message which may contain a number 41 | /// of URL-encoded parameters. 42 | /// 43 | [HttpPost] function Fallback(Incoming: TStream):TStream; 44 | 45 | /// 46 | /// Send an SMS via Twilio Programmable Messaging System 47 | /// 48 | /// 49 | /// This is used to send SMS messages from a central source. 50 | /// 51 | /// 52 | /// The Twilio Messaging Service identifier, which in turn is linked to one or more phone numbers. 53 | /// 54 | /// 55 | /// The SMS number to send to (typically a 10-digit mobile phone number). 56 | /// 57 | /// 58 | /// The body of the message. 59 | /// 60 | [HttpPost] function SendAMessage(MessageService: String; Destination: String; AMessage: String):TStream; 61 | 62 | end; 63 | 64 | implementation 65 | 66 | initialization 67 | RegisterServiceType(TypeInfo(IMessagingService)); 68 | 69 | end. 70 | -------------------------------------------------------------------------------- /units/Unit2.dfm: -------------------------------------------------------------------------------- 1 | object MainForm: TMainForm 2 | Left = 0 3 | Top = 0 4 | Caption = 'ServerName: Not Set' 5 | ClientHeight = 502 6 | ClientWidth = 743 7 | Color = clBtnFace 8 | Font.Charset = DEFAULT_CHARSET 9 | Font.Color = clWindowText 10 | Font.Height = -11 11 | Font.Name = 'Tahoma' 12 | Font.Style = [] 13 | OldCreateOrder = False 14 | OnCreate = FormCreate 15 | OnShow = FormShow 16 | DesignSize = ( 17 | 743 18 | 502) 19 | PixelsPerInch = 96 20 | TextHeight = 13 21 | object mmInfo: TMemo 22 | Left = 8 23 | Top = 39 24 | Width = 727 25 | Height = 454 26 | Anchors = [akLeft, akTop, akRight, akBottom] 27 | DoubleBuffered = True 28 | ParentDoubleBuffered = False 29 | ReadOnly = True 30 | ScrollBars = ssVertical 31 | TabOrder = 0 32 | end 33 | object btStart: TButton 34 | Left = 8 35 | Top = 8 36 | Width = 75 37 | Height = 25 38 | Caption = 'Start' 39 | TabOrder = 1 40 | OnClick = btStartClick 41 | end 42 | object btStop: TButton 43 | Left = 90 44 | Top = 8 45 | Width = 75 46 | Height = 25 47 | Caption = 'Stop' 48 | TabOrder = 2 49 | OnClick = btStopClick 50 | end 51 | object btSwagger: TButton 52 | Left = 171 53 | Top = 8 54 | Width = 75 55 | Height = 25 56 | Caption = 'Swagger' 57 | Enabled = False 58 | TabOrder = 3 59 | OnClick = btSwaggerClick 60 | end 61 | object btRedoc: TButton 62 | Left = 252 63 | Top = 8 64 | Width = 75 65 | Height = 25 66 | Caption = 'Redoc' 67 | Enabled = False 68 | TabOrder = 4 69 | OnClick = btRedocClick 70 | end 71 | object btEMail: TButton 72 | Left = 333 73 | Top = 8 74 | Width = 75 75 | Height = 25 76 | Caption = 'E-Mail' 77 | Enabled = False 78 | TabOrder = 5 79 | OnClick = btEMailClick 80 | end 81 | object DBConn: TFDConnection 82 | FormatOptions.AssignedValues = [fvMaxStringSize] 83 | Left = 520 84 | Top = 48 85 | end 86 | object FDPhysSQLiteDriverLink1: TFDPhysSQLiteDriverLink 87 | Left = 520 88 | Top = 160 89 | end 90 | object Query1: TFDQuery 91 | Connection = DBConn 92 | Left = 520 93 | Top = 104 94 | end 95 | object tmrStart: TTimer 96 | Enabled = False 97 | OnTimer = tmrStartTimer 98 | Left = 440 99 | Top = 240 100 | end 101 | object tmrInit: TTimer 102 | Enabled = False 103 | OnTimer = tmrInitTimer 104 | Left = 352 105 | Top = 240 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /ddl/messaging/messaging_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [table] messaging 2 | 3 | TableName := 'messaging'; 4 | 5 | if (DatabaseEngine = 'sqlite') then 6 | begin 7 | 8 | with Query1 do 9 | begin 10 | 11 | // Check if the table exists 12 | SQL.Clear; 13 | SQL.Add('select count(*) records from '+TableName+';'); 14 | try 15 | Open; 16 | LogEvent('...'+TableName+' ('+IntToStr(FieldByName('records').AsInteger)+' records)'); 17 | 18 | except on E:Exception do 19 | begin 20 | LogEvent('...'+TableName+' (CREATE)'); 21 | SQL.Clear; 22 | SQL.Add('create table if not exists '+TableName+' ( '+ 23 | ' service text NOT NULL, '+ 24 | ' created_at text, '+ 25 | ' MessageStatus text, '+ 26 | ' ErrorMessage text, '+ 27 | ' ErrorCode text, '+ 28 | ' Direction text, '+ 29 | ' RawDlrDoneDate text, '+ 30 | ' SmsStatus text, '+ 31 | ' SmsSid text, '+ 32 | ' SmsMessageSid text, '+ 33 | ' AccountSid text, '+ 34 | ' MessageSid text, '+ 35 | ' MessagingServiceSid text, '+ 36 | ' Body text, '+ 37 | ' ToNum text, '+ 38 | ' ToCountry text, '+ 39 | ' ToState text, '+ 40 | ' ToCity text, '+ 41 | ' ToZip text, '+ 42 | ' FromNum text, '+ 43 | ' FromCountry text, '+ 44 | ' FromState text, '+ 45 | ' FromCity text, '+ 46 | ' FromZip text, '+ 47 | ' NumSegments text, '+ 48 | ' NumMedia text, '+ 49 | ' Price text, '+ 50 | ' PriceUnit text, '+ 51 | ' AddOns text, '+ 52 | ' Uri text, '+ 53 | ' Resource text, '+ 54 | ' ApiVersion text '+ 55 | ');' 56 | ); 57 | ExecSQL; 58 | 59 | // Try it again 60 | SQL.Clear; 61 | SQL.Add('select count(*) records from '+TableName+';'); 62 | Open; 63 | end; 64 | end; 65 | end; 66 | end; 67 | 68 | -------------------------------------------------------------------------------- /units/ChatService.pas: -------------------------------------------------------------------------------- 1 | unit ChatService; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | XData.Security.Attributes, 8 | XData.Service.Common; 9 | 10 | type 11 | [ServiceContract] 12 | IChatService = interface(IInvokable) 13 | ['{F95E651C-DA7A-4997-8630-314587B1857F}'] 14 | 15 | /// Chat 16 | /// 17 | /// Submit a conversation to OpenAI, along with context, get a response. 18 | /// 19 | /// 20 | /// There are limits to the overall conversation, typically 4096 tokens, which includes the current conversation, the context, and the response. 21 | /// 22 | /// 23 | /// The name of the model to use for this chat. The model name should be selected from the list provided by GetChatInformation, which is the user-viewable name, not the internal chat model name. 24 | /// 25 | /// 26 | /// The next part of the conversation. 27 | /// 28 | /// 29 | /// The prior portions of the conversation that we would like to include to give the model the opportunity to include it when coming up with a reponse. This is what gives our chat the illusion of knowing about prior responses. It doesn't, really, so we have to supply them each time. 30 | /// 31 | /// 32 | /// An integer that indicates how many choices to return. For normal chat conversations, this is typically going to be "1". For images, this might be any value from 1 to 10. 33 | /// 34 | /// 35 | /// A unique identifier for the chat. This is used to store the chat, and subsequent updates to the chat, in a server database, and helps ensure that we just end up with a single copy of the conversation when we're done. 36 | /// 37 | [Authorize] [HttpPost] function Chat([XDefault('ChatGPT 3.5')] Model: String; Conversation: String; [XDefault('None')] Context: String; [XDefault(1)] Choices: Integer; [XDefault('Swagger Testing')] ChatID: String):TStream; 38 | 39 | /// GetChatInformation 40 | /// 41 | /// Returns JSON that describes various aspects of the chat subsystem, like what models are available, and other statistical information. 42 | /// 43 | /// 44 | /// No parameters here as the block of JSON is the same regardless. 45 | /// 46 | [Authorize] [HttpGet] function GetChatInformation:TStream; 47 | 48 | /// GetChatInformation 49 | /// 50 | /// Returns JSON that describes various aspects of the chat subsystem, like what models are available, and other statistical information. 51 | /// 52 | /// 53 | /// No parameters here as the block of JSON is the same regardless. 54 | /// 55 | [HttpGet] function GetChatImage(F: String):TStream; 56 | 57 | end; 58 | 59 | implementation 60 | 61 | initialization 62 | RegisterServiceType(TypeInfo(IChatService)); 63 | 64 | end. 65 | -------------------------------------------------------------------------------- /ddl/contact/contact_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [table] contact 2 | // 3 | // list_contact refers to the list table, containing arbitrary lists of things. 4 | // In this case, the list includes things like 'email', 'telephone','fax', 'home', etc. 5 | // preference is where the sort order is kept. 6 | 7 | TableName := 'contact'; 8 | 9 | if (DatabaseEngine = 'sqlite') then 10 | begin 11 | 12 | with Query1 do 13 | begin 14 | 15 | // Check if the table exists 16 | SQL.Clear; 17 | SQL.Add('select count(*) records from '+TableName+';'); 18 | try 19 | Open; 20 | LogEvent('...'+TableName+' ('+IntToStr(FieldByName('records').AsInteger)+' records)'); 21 | 22 | except on E:Exception do 23 | begin 24 | LogEvent('...'+TableName+' (CREATE)'); 25 | SQL.Clear; 26 | SQL.Add('create table if not exists '+TableName+' ( '+ 27 | ' person_id integer NOT NULL, '+ 28 | ' contact_id integer NOT NULL, '+ 29 | ' last_modified text NOT NULL, '+ 30 | ' last_modifier integer NOT NULL, '+ 31 | ' list_contact integer NOT NULL, '+ 32 | ' value text NOT NULL, '+ 33 | ' preference integer NOT NULL, '+ 34 | ' login_use integer NOT NULL, '+ 35 | ' CONSTRAINT constraint_name PRIMARY KEY (person_id,contact_id) '+ 36 | ');' 37 | ); 38 | ExecSQL; 39 | 40 | // Try it again 41 | SQL.Clear; 42 | SQL.Add('select count(*) records from '+TableName+';'); 43 | Open; 44 | end; 45 | end; 46 | 47 | // Populate empty table with sample data 48 | if (FieldByName('records').AsInteger = 0) then 49 | begin 50 | LogEvent('...'+TableName+' (POPULATE)'); 51 | SQL.Clear; 52 | 53 | // SYSINSTALLER 54 | SQL.Add('insert into '+TableName+' values( 0, 0, Datetime("now"), 0, 0, "SYSINSTALLER", 0, 1 );'); 55 | 56 | // Add E-Mail (with login ability) for all users 57 | SQL.Add('insert into '+TableName+' '+ 58 | ' select '+ 59 | ' person_id, '+ 60 | ' 1, '+ 61 | ' Datetime("now"), '+ 62 | ' 0, '+ 63 | ' 1, '+ 64 | ' lower(account_name) || "@company.com", '+ 65 | ' 0, '+ 66 | ' 1 '+ 67 | ' from person;' 68 | ); 69 | 70 | // Add Telephone (Work) (without login ability) for all users 71 | SQL.Add('insert into '+TableName+' '+ 72 | ' select '+ 73 | ' person_id, '+ 74 | ' 2, '+ 75 | ' Datetime("now"), '+ 76 | ' 0, '+ 77 | ' 3, '+ 78 | ' "(" || cast(abs(random() % 899)+100 as TEXT) || ") " || cast(abs(random() % 899)+100 as TEXT) || "-" || cast(abs(random() % 8999)+1000 as TEXT), '+ 79 | ' 1, '+ 80 | ' 0 '+ 81 | ' from person;' 82 | ); 83 | 84 | // Add Telephone (Mobile) (without login ability) for all users 85 | SQL.Add('insert into '+TableName+' '+ 86 | ' select '+ 87 | ' person_id, '+ 88 | ' 3, '+ 89 | ' Datetime("now"), '+ 90 | ' 0, '+ 91 | ' 4, '+ 92 | ' "(" || cast(abs(random() % 899)+100 as TEXT) || ") " || cast(abs(random() % 899)+100 as TEXT) || "-" || cast(abs(random() % 8999)+1000 as TEXT), '+ 93 | ' 2, '+ 94 | ' 0 '+ 95 | ' from person;' 96 | ); 97 | 98 | // Add Telephone (Home) (without login ability) for all users 99 | SQL.Add('insert into '+TableName+' '+ 100 | ' select '+ 101 | ' person_id, '+ 102 | ' 4, '+ 103 | ' Datetime("now"), '+ 104 | ' 0, '+ 105 | ' 5, '+ 106 | ' "(" || cast(abs(random() % 899)+100 as TEXT) || ") " || cast(abs(random() % 899)+100 as TEXT) || "-" || cast(abs(random() % 8999)+1000 as TEXT), '+ 107 | ' 3, '+ 108 | ' 0 '+ 109 | ' from person;' 110 | ); 111 | 112 | ExecSQL; 113 | end; 114 | end; 115 | end; 116 | 117 | -------------------------------------------------------------------------------- /ddl/list/list_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [table] list 2 | 3 | TableName := 'list'; 4 | 5 | if (DatabaseEngine = 'sqlite') then 6 | begin 7 | 8 | with Query1 do 9 | begin 10 | 11 | // Check if the table exists 12 | SQL.Clear; 13 | SQL.Add('select count(*) records from '+TableName+';'); 14 | try 15 | Open; 16 | LogEvent('...'+TableName+' ('+IntToStr(FieldByName('records').AsInteger)+' records)'); 17 | 18 | except on E:Exception do 19 | begin 20 | LogEvent('...'+TableName+' (CREATE)'); 21 | SQL.Clear; 22 | SQL.Add('create table if not exists '+TableName+' ( '+ 23 | ' list_id integer NOT NULL, '+ 24 | ' lookup_id integer NOT NULL, '+ 25 | ' last_modified text NOT NULL, '+ 26 | ' last_modifier integer NOT NULL, '+ 27 | ' preference integer NOT NULL, '+ 28 | ' value text NOT NULL, '+ 29 | ' attributes text , '+ 30 | ' CONSTRAINT constraint_name PRIMARY KEY (list_id,lookup_id) '+ 31 | ');' 32 | ); 33 | ExecSQL; 34 | 35 | // Try it again 36 | SQL.Clear; 37 | SQL.Add('select count(*) records from '+TableName+';'); 38 | Open; 39 | end; 40 | end; 41 | 42 | // Populate empty table with sample data 43 | if (FieldByName('records').AsInteger = 0) then 44 | begin 45 | LogEvent('...'+TableName+' (POPULATE)'); 46 | SQL.Clear; 47 | 48 | // list_id = 0 contains the names of the lists 49 | SQL.Add('insert into '+TableName+' values( 0, 0, Datetime("now"), 0, 0, "Lists", "");'); 50 | SQL.Add('insert into '+TableName+' values( 0, 1, Datetime("now"), 0, 1, "Contact Types", "");'); 51 | SQL.Add('insert into '+TableName+' values( 0, 2, Datetime("now"), 0, 2, "Organization Info", "");'); 52 | SQL.Add('insert into '+TableName+' values( 0, 3, Datetime("now"), 0, 2, "Photo Types", "");'); 53 | SQL.Add('insert into '+TableName+' values( 0, 4, Datetime("now"), 0, 2, "Custom Icons", "");'); 54 | 55 | 56 | // Contact Types 57 | SQL.Add('insert into '+TableName+' values( 1, 0, Datetime("now"), 0, 0, "Login Name", ":Login");'); 58 | SQL.Add('insert into '+TableName+' values( 1, 1, Datetime("now"), 0, 1, "E-Mail Address (Work)", ":EMail");'); 59 | SQL.Add('insert into '+TableName+' values( 1, 2, Datetime("now"), 0, 2, "E-Mail Address (Home)", ":EMailHome");'); 60 | SQL.Add('insert into '+TableName+' values( 1, 3, Datetime("now"), 0, 3, "Telephone (Work)", ":Telephone");'); 61 | SQL.Add('insert into '+TableName+' values( 1, 4, Datetime("now"), 0, 4, "Telephone (Mobile)", ":TelephoneMobile");'); 62 | SQL.Add('insert into '+TableName+' values( 1, 5, Datetime("now"), 0, 5, "Telephone (Home)", ":TelephoneHome");'); 63 | SQL.Add('insert into '+TableName+' values( 1, 6, Datetime("now"), 0, 6, "Fax Machine (Work)", ":Fax");'); 64 | SQL.Add('insert into '+TableName+' values( 1, 7, Datetime("now"), 0, 7, "Fax Machine (Home)", ":FaxHome");'); 65 | SQL.Add('insert into '+TableName+' values( 1, 8, Datetime("now"), 0, 8, "Facebook", ":Facebook");'); 66 | SQL.Add('insert into '+TableName+' values( 1, 9, Datetime("now"), 0, 9, "Twitter", ":Twitter");'); 67 | SQL.Add('insert into '+TableName+' values( 1, 10, Datetime("now"), 0, 10, "Instagram", ":Instagram");'); 68 | SQL.Add('insert into '+TableName+' values( 1, 11, Datetime("now"), 0, 11, "Address (Work)", ":Address");'); 69 | SQL.Add('insert into '+TableName+' values( 1, 12, Datetime("now"), 0, 12, "Address (Home)", ":AddressWork");'); 70 | 71 | // Organization Info 72 | SQL.Add('insert into '+TableName+' values( 2, 0, Datetime("now"), 0, 0, "TMS WEB Core/XData Template", "Organization Name");'); 73 | SQL.Add('insert into '+TableName+' values( 2, 1, Datetime("now"), 0, 1, "Template", "Organization Short Name");'); 74 | SQL.Add('insert into '+TableName+' values( 2, 2, Datetime("now"), 0, 2, "TMS WEB Core and More", "Organization Slogan");'); 75 | SQL.Add('insert into '+TableName+' values( 2, 3, Datetime("now"), 0, 3, "Copyright (c) 2023", "Copyright Notice");'); 76 | 77 | // Photo Types 78 | SQL.Add('insert into '+TableName+' values( 3, 0, Datetime("now"), 0, 0, "Photo (Large)", ":User");'); 79 | SQL.Add('insert into '+TableName+' values( 3, 1, Datetime("now"), 1, 0, "Photo (Thumbnail)", ":User");'); 80 | 81 | ExecSQL; 82 | end; 83 | end; 84 | end; 85 | 86 | -------------------------------------------------------------------------------- /units/SystemService.pas: -------------------------------------------------------------------------------- 1 | unit SystemService; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | XData.Service.Common; 8 | 9 | type 10 | [ServiceContract] 11 | ISystemService = interface(IInvokable) 12 | ['{F9CC965F-B6F1-4D38-A50A-E271705E9FCB}'] 13 | 14 | /// 15 | /// XData Application Information 16 | /// 17 | /// 18 | /// Returns JSON that includes information about the currently running application. 19 | /// 20 | /// 21 | /// The TimeZone of the connecting client. This is used in determining what 22 | /// adjustments to make when displaying dates and times on reports, or where 23 | /// similar data needs to be converted to a local context. This uses IANA 24 | /// TimeZone names. If an invalid TimeZone is provided, the JSON object for 25 | /// Current Time (Client) will indicate as much. Here are some examples. 26 | /// - Pacific Standard Time 27 | /// - America/New_York 28 | /// - Europe/Paris 29 | /// - EET 30 | /// - UTC 31 | /// - GMT 32 | /// - EST 33 | /// - PST8PDT 34 | /// 35 | [HttpGet] function Info(TZ: String):TStream; 36 | 37 | /// 38 | /// Login to XData Server 39 | /// 40 | /// 41 | /// If login is successful, a JWT will be returned. 42 | /// 43 | /// 44 | /// Login_ID can be any of the contact entries that have been marked as login_ok, 45 | /// which would typically be just the email address but could also include phone 46 | /// numbers or other values. 47 | /// 48 | /// 49 | /// Password corresponding to the username. 50 | /// 51 | /// 52 | /// An application-level API key that has been provided for your use. 53 | /// 54 | /// 55 | /// The TimeZone of the connecting client. This is used in determining what 56 | /// adjustments to make when displaying dates and times on reports, or where 57 | /// similar data needs to be converted to a local context. This uses IANA 58 | /// TimeZone names. If an invalid TimeZone is provided, the JSON object for 59 | /// Current Time (Client) will indicate as much. Here are some examples. 60 | /// - Pacific Standard Time 61 | /// - America/New_York 62 | /// - Europe/Paris 63 | /// - EET 64 | /// - UTC 65 | /// - GMT 66 | /// - EST 67 | /// - PST8PDT 68 | /// 69 | [HttpGet] function Login(Login_ID: String; Password: String; API_Key: String; TZ: String):TStream; 70 | 71 | /// 72 | /// Logout - revoke the JWT. 73 | /// 74 | /// 75 | /// The JWT issued by the Login endpoint is good for a set period of time. This will revoke 76 | /// the JWT, making it invalid immediately rather than when it expires after a period of time. 77 | /// 78 | /// 79 | /// Session identifier unique to the user - just an encoded Unix timestamp. 80 | /// 81 | /// 82 | /// Client action log. Just a text log. 83 | /// 84 | [HttpPost] function Logout(ActionSession: String; ActionLog: String):TStream; 85 | 86 | /// 87 | /// Renew a previously issued JWT. 88 | /// 89 | /// 90 | /// The JWT issued by the Login endpoint is good for a set period of time. 91 | /// This endpoint will re-issue a new JWT with the same claims for another period of time. 92 | /// 93 | /// 94 | /// Session identifier unique to the user - just an encoded Unix timestamp. 95 | /// 96 | /// 97 | /// Client action log. Just a text log. 98 | /// 99 | [HttpPost] function Renew(ActionSession: String; ActionLog: String):TStream; 100 | 101 | /// 102 | /// List of Icon Sets that are available for search and retrieval 103 | /// 104 | /// 105 | /// Returns a JSON array that includes the following. 106 | /// - Name of Icon Set 107 | /// - License Information 108 | /// - Count of Icons included in Set 109 | /// - Default Icon Width for Set 110 | /// - Default Icon Height for Set 111 | /// 112 | /// The order of the array should be used to identify which sets are to be included or excluded when a search is requested. 113 | /// 114 | [HttpGet] function AvailableIconSets:TStream; 115 | 116 | /// 117 | /// Performs a search for icons, returing whatever icons were found as a JSON array. 118 | /// 119 | /// 120 | /// The returned array is a JSON list of icons, including the SVG parts needed to build the icon. 121 | /// 122 | /// 123 | /// Up to three terms will be used in conducting the search. Any more that are passed in will be ignored. 124 | /// 125 | /// 126 | /// A comma-separated list of Icon Sets to search, where the number indicates the position in the array from AvailableIconSets. A value of 'all' is also accepted, as this is likely the default search choice much of the time. 127 | /// 128 | /// 129 | /// Indicates how many icons are to be returned. If conducting searches while someone is typing, this should be a much smaller number than if a more deliberate search is being performed. 130 | /// 131 | [HttpGet] function SearchIconSets(SearchTerms: String; SearchSets:String; Results:Integer):TStream; 132 | 133 | [HttpGet] function SearchFontAwesome(Query: String):TStream; 134 | 135 | end; 136 | 137 | implementation 138 | 139 | initialization 140 | RegisterServiceType(TypeInfo(ISystemService)); 141 | 142 | end. 143 | -------------------------------------------------------------------------------- /units/DashboardServiceImplementation.pas: -------------------------------------------------------------------------------- 1 | unit DashboardServiceImplementation; 2 | 3 | interface 4 | 5 | uses 6 | System.Classes, 7 | System.SysUtils, 8 | System.JSON, 9 | System.DateUtils, 10 | 11 | Sparkle.HttpSys.Server, 12 | Sparkle.Security, 13 | 14 | XData.Server.Module, 15 | XData.Service.Common, 16 | XData.Sys.Exceptions, 17 | 18 | Bcl.Jose.Core.JWT, 19 | Bcl.Jose.Core.Builder, 20 | 21 | FireDAC.Stan.Intf, 22 | FireDAC.Stan.Option, 23 | FireDAC.Stan.Param, 24 | FireDAC.Comp.Client, 25 | FireDAC.Comp.BatchMove, 26 | FireDAC.Comp.BatchMove.Dataset, 27 | FireDAC.Comp.BatchMove.JSON, 28 | 29 | DashboardService; 30 | 31 | type 32 | [ServiceImplementation] 33 | TDashboardService = class(TInterfacedObject, IDashboardService) 34 | private 35 | function AdministratorDashboard: TStream; 36 | end; 37 | 38 | implementation 39 | 40 | uses Unit2, Unit3, TZDB; 41 | 42 | function TDashboardService.AdministratorDashboard: TStream; 43 | var 44 | DBConn: TFDConnection; 45 | Query1: TFDQuery; 46 | DatabaseName: String; 47 | DatabaseEngine: String; 48 | ElapsedTime: TDateTime; 49 | User: IUserIdentity; 50 | JWT: String; 51 | ResultJSON: TJSONObject; 52 | ResultArray: TJSONArray; 53 | Roles: TStringList; 54 | GotRole: Boolean; 55 | i: Integer; 56 | begin 57 | // Returning JSON, so flag it as such 58 | TXDataOperationContext.Current.Response.Headers.SetValue('content-type', 'application/json'); 59 | 60 | // Time this event 61 | ElapsedTime := Now; 62 | 63 | // Get data from the JWT 64 | User := TXDataOperationContext.Current.Request.User; 65 | JWT := TXDataOperationContext.Current.Request.Headers.Get('Authorization'); 66 | if (User = nil) then raise EXDataHttpUnauthorized.Create('Missing authentication'); 67 | 68 | // Setup DB connection and query 69 | try 70 | DatabaseName := User.Claims.Find('dbn').AsString; 71 | DatabaseEngine := User.Claims.Find('dbe').AsString; 72 | DBSupport.ConnectQuery(DBConn, Query1, DatabaseName, DatabaseEngine); 73 | except on E: Exception do 74 | begin 75 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 76 | raise EXDataHttpUnauthorized.Create('Internal Error: CQ'); 77 | end; 78 | end; 79 | 80 | // Check if we've got a valid JWT (one that has not been revoked) 81 | try 82 | {$Include sql\system\token_check\token_check.inc} 83 | Query1.ParamByName('TOKENHASH').AsString := DBSupport.HashThis(JWT); 84 | Query1.ParamByName('IPADDRESS').AsString := TXDataOperationContext.Current.Request.RemoteIP; 85 | Query1.Open; 86 | except on E: Exception do 87 | begin 88 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 89 | raise EXDataHttpUnauthorized.Create('Internal Error: JC'); 90 | end; 91 | end; 92 | if Query1.RecordCount <> 1 then raise EXDataHttpUnauthorized.Create('JWT was not validated'); 93 | 94 | // Check if user has the Administrator role 95 | Roles := TStringList.Create; 96 | Roles.CommaText := User.Claims.Find('rol').AsString; 97 | i := 0; 98 | GotRole := False; 99 | while i < Roles.Count do 100 | begin 101 | if Roles[i] = '1' then GotRole := True; 102 | i := i + 1; 103 | end; 104 | Roles.Free; 105 | if not(GotRole) then raise EXDataHttpUnauthorized.Create('Missing Administrator Role'); 106 | 107 | // Create object to be returned 108 | ResultJSON := TJSONObject.Create; 109 | 110 | // Get All Organization Info 111 | try 112 | {$Include sql\system\list_by_id\list_by_id.inc} 113 | Query1.ParamByName('LISTID').AsInteger := 2; 114 | Query1.Open; 115 | except on E: Exception do 116 | begin 117 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 118 | raise EXDataHttpUnauthorized.Create('Internal Error: List By ID'); 119 | end; 120 | end; 121 | ResultArray := TJSONObject.ParseJSONValue(DBSupport.QueryToJSON(Query1)) as TJSONArray; 122 | ResultJSON.AddPair('Organization', ResultArray); 123 | 124 | // Add photo 125 | try 126 | {$Include sql\person\photo\photo.inc} 127 | Query1.ParamByName('PERSONID').AsInteger := User.Claims.Find('usr').AsInteger; 128 | Query1.Open; 129 | except on E: Exception do 130 | begin 131 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 132 | raise EXDataHttpUnauthorized.Create('Internal Error: Photo'); 133 | end; 134 | end; 135 | ResultJSON.AddPair('Photo', String(Query1.FieldByName('photo_datauri').AsString)); 136 | 137 | // Not sure if there is another version of this that is more direct? 138 | Result := TStringStream.Create(ResultJSON.ToString); 139 | 140 | // Cleanup 141 | ResultJSON.Free; 142 | 143 | // Keep track of endpoint history 144 | try 145 | {$Include sql\system\endpoint_history_insert\endpoint_history_insert.inc} 146 | Query1.ParamByName('PERSONID').AsInteger := User.Claims.Find('usr').AsInteger; 147 | Query1.ParamByName('ENDPOINT').AsString := 'DashboardService.AdministratorDashboard'; 148 | Query1.ParamByName('ACCESSED').AsDateTime := TTimeZone.local.ToUniversalTime(ElapsedTime); 149 | Query1.ParamByName('IPADDRESS').AsString := TXDataOperationContext.Current.Request.RemoteIP; 150 | Query1.ParamByName('APPLICATION').AsString := User.Claims.Find('app').AsString; 151 | Query1.ParamByName('VERSION').AsString := MainForm.AppVersion; 152 | Query1.ParamByName('DATABASENAME').AsString := DatabaseName; 153 | Query1.ParamByName('DATABASEENGINE').AsString := DatabaseEngine; 154 | Query1.ParamByName('EXECUTIONMS').AsInteger := MillisecondsBetween(Now,ElapsedTime); 155 | Query1.ParamByName('DETAILS').AsString := '['+User.Claims.Find('anm').AsString+']'; 156 | Query1.ExecSQL; 157 | except on E: Exception do 158 | begin 159 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 160 | raise EXDataHttpUnauthorized.Create('Internal Error: EHI'); 161 | end; 162 | end; 163 | 164 | // All Done 165 | try 166 | DBSupport.DisconnectQuery(DBConn, Query1); 167 | except on E: Exception do 168 | begin 169 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 170 | raise EXDataHttpUnauthorized.Create('Internal Error: DQ'); 171 | end; 172 | end; 173 | end; 174 | 175 | 176 | 177 | initialization 178 | RegisterServiceType(TDashboardService); 179 | 180 | end. 181 | -------------------------------------------------------------------------------- /docs/ConfigJSON.md: -------------------------------------------------------------------------------- 1 | ## Config.JSON 2 | 3 | When the XData server first starts, it checks to see if a configuration JSON file is available. XData first checks the application directory (wherever the application is launched from) for a .json file with the same name as the application (eg: project1.json). This location can be overriden by passsing a CONFIG parameter (eg: CONFIG=c:\data\config.json) to the XData application. If neither are available, defaults will provided for all values. As a result, the configuration file is entirely optional. This is the expected configuration during intial development and testing. However, once the project is deployed, a configuration JSON file will most likely be needed. This is how the BaseURL property is set, if it isn't otherwise altered in code. 4 | 5 | As this project unfolds, it is likely that additional elements will be added here. 6 | 7 | ### BaseURL 8 | This is used to set the BaseURL for the XData server. For example, in a production environment, this might be something like "https://+9999/tms/xdata" while in a development environment it might be "http://+2001/tms/xdata". Production servers, particularly those that are public-facing, should be configured with an HTTPS protocol, with the necessary SSL certificates. Note that regardless of the environment, the TMS HTTP Config Tool, or equivalent, should be used to reserve the port number on the system that XData is running on. This includes adding an SSL certificate if the HTTPS protocol is used. 9 | 10 | ### ServerName 11 | Used to set the caption at the top of the XData window. Defaults to "TMS XData Template: Demo Data". Might be used to set different names if multiple instances of XData are being used, where each configuration file can supply a unique name. 12 | 13 | ### Cache Folder 14 | Endpoints that return images, specifically the ChatService/GetChatImage endpoint, will generate a cache of any images requested from the database. This may include both thumbnails as well as the original image stored in the database. By default, a cache folder will be created in the same folder as the XData application when it first starts. This behaviour can be overriden by setting a "Cache Folder" entry in the configuration. Eg: c:/data/cache. Note carefully that the folders are specified using a forward slash. 15 | 16 | ### Chat Interface (ChatGPT) 17 | For the chat features of this project to work properly, appropriate API keys need to be provided in most cases (eg: OpenAI's ChatGPT offerings). The configuration JSON file is used to provide these keys, along with several other chat-related parameters, to the XData application. As there may very well be several chat interfaces provided, a JSON array is used in this case. An example is provided below. Please refer to the [ChatService documentation](https://github.com/500Foods/TMS-XData-TemplateDemoData/blob/main/docs/ChatService.md) for more detailed information. 18 | 19 | ### Messaging Interface 20 | For the messaging features (SMS) of this project to work properly, appropriate API keys need to be provided in most cases (eg: Twilio). The configuration JSON file is used to provide these keys, along with several other messaging-related parameters, to the XData application. As there may very well be several messaging services provided for a given messaging provider, a JSON array is used in this case. An example is provided below. Please refer to the [MessagingService documentation](https://github.com/500Foods/TMS-XData-TemplateDemoData/blob/main/docs/MessagingService.md) for more detailed information. 21 | 22 | ### Mail Services 23 | In order for notification e-mails to be sent, an SMTP mail server needs to be specified along with an account used to log in to that server for submitting e-mails. In addition, a separate e-mail address/name is used for addressing the e-mails. 24 | 25 | ### Example 26 | 27 | Here is an example JSON configuration file. 28 | 29 | ``` 30 | { 31 | "BaseURL": "http://+:12345/tms/xdata", 32 | "ServerName": "TMS XData Template: Demo Data", 33 | "Cache Folder": "C:/Data/Cache", 34 | "Chat Interface": [ 35 | { 36 | "Name": "ChatGPT 3.5", 37 | "Default": true, 38 | "Model": "gpt-3.5-turbo", 39 | "Organization": "your-org-identifier-here", 40 | "API Key": "your-api-key-here", 41 | "Endpoint": "https://api.openai.com/v1/chat/completions", 42 | "Limit": 4096, 43 | "Cost Prompt": 0.000002, 44 | "Cost Completion": 0.000002 45 | }, 46 | { 47 | "Name": "ChatGPT 4 8K", 48 | "Default": false, 49 | "Model": "gpt-4-8k", 50 | "Organization": "your-org-identifier-here", 51 | "API Key": "your-api-key-here", 52 | "Endpoint": "https://api.openai.com/v1/chat/completions", 53 | "Limit": 8192, 54 | "Cost Prompt": 0.00003, 55 | "Cost Completion": 0.00006 56 | }, 57 | { 58 | "Name": "ChatGPT 4 32K", 59 | "Default": false, 60 | "Model": "gpt-4-32k", 61 | "Organization": "your-org-identifier-here", 62 | "API Key": "your-api-key-here", 63 | "Endpoint": "https://api.openai.com/v1/chat/completions", 64 | "Limit": 32768, 65 | "Cost Prompt": 0.00006, 66 | "Cost Completion": 0.00012 67 | }, 68 | { 69 | "Name": "Image 256", 70 | "Default": false, 71 | "Model": "dall-e 256", 72 | "Organization": "your-org-identifier-here", 73 | "API Key": "your-api-key-here", 74 | "Endpoint": "https://api.openai.com/v1/images/generations", 75 | "Limit": 1000, 76 | "Cost": 0.016 77 | }, 78 | { 79 | "Name": "Image 512", 80 | "Default": false, 81 | "Model": "dall-e 512", 82 | "Organization": "your-org-identifier-here", 83 | "API Key": "your-api-key-here", 84 | "Endpoint": "https://api.openai.com/v1/images/generations", 85 | "Limit": 1000, 86 | "Cost": 0.018 87 | }, 88 | { 89 | "Name": "Image 1024", 90 | "Default": false, 91 | "Model": "dall-e 1024", 92 | "Organization": "your-org-identifier-here", 93 | "API Key": "your-api-key-here", 94 | "Endpoint": "https://api.openai.com/v1/images/generations", 95 | "Limit": 1000, 96 | "Cost": 0.02 97 | } 98 | ], 99 | "Mail Services":{ 100 | "SMTP Host":"mail.example.com", 101 | "SMTP Port":587, 102 | "SMTP User":"username.domain", 103 | "SMTP Pass":"kJkh3oaDfwk7A8A", 104 | "SMTP From":"concierge@example.com", 105 | "SMTP Name":"Example Concierge" 106 | }, 107 | "Messaging Services":{ 108 | "Twilio": { 109 | "Service Name":"Twilio Messaging", 110 | "Account": "AC5346342f844fc5b7dd41412ed8eXXXXX", 111 | "Auth Token": "df21f3f76799992f8b52164021bXXXXX", 112 | "Send URL":"https://api.twilio.com/2010-04-01/Accounts/AC5346342f844fc5b7dd41412ed8eXXXXX/Messages.json", 113 | "Messaging Services":[ 114 | {"500 Dashboards Notify":"MGec8ddf1c3187241921577f73f5XXXXX"}, 115 | {"500 Dashboards Support":"MGec8ddf1c3187241921577f73fYYYYY"}, 116 | {"500 Dashboards Billing":"MGec8ddf1c3187241921577f73f5ZZZZZ"} 117 | ] 118 | } 119 | } 120 | } 121 | ``` 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TMS XData Template: Demo Data 2 | This repository contains a [TMS XData](https://www.tmssoftware.com/site/xdata.asp) project - a REST API server - that serves up a variety of endpoints and sample data. This may be useful for those first learning to use TMS XData in their own projects. Or as a template for starting a new TMS XData project, with many core features, like Swagger and a Login endpoint, already in place. This project originated as part of a series of blog posts about using TMS XData and [TMS WEB Core](https://www.tmssoftware.com/site/tmswebcore.asp) with different kinds of templates, the first of which can be found [here](https://www.tmssoftware.com/site/blog.asp?post=1068). 3 | 4 | A second repository, [TMS WEB Core Template Demo](https://github.com/500Foods/TMS-WEB-Core-TemplateDemo), contains the implementation of an example web client application that works specifically with this REST API. It was created using a pre-release build of the [AdminLTE 4](https://github.com/ColorlibHQ/AdminLTE/tree/v4-dev) admin template, which works with Bootstrap 5. 5 | 6 | ## Getting Started 7 | In order to get the most of this repository, you'll need [Delphi](https://www.embarcadero.com/products/delphi) version 10 or later, as well as a current version of [TMS XData](https://www.tmssoftware.com/site/xdata.asp). Note that these are licensed commercial products, but trial versions of both are available on their respective websites. Please review [The Unlicense](https://github.com/500Foods/TMS-XData-TemplateDemoData/blob/main/LICENSE) that applies to this repository. 8 | 9 | The contents of this repository consist of a fully funcitional TMS XData project. It can be compiled and run as-is without further modification, which will generate a Win64 REST API server application. Several endpoints, sample data, a SQLite database, and Swagger are already configured. 10 | 11 | ![image](https://user-images.githubusercontent.com/41052272/222645643-2827211b-6750-45d5-ad8e-db758ed194e6.png) 12 | *XData Server Application Running* 13 | 14 | Not much to look at, honestly, but that's just the server application. All it is primarily used for is starting and stopping the REST API server, and as it starts automatically, there's usually not much need to use it directly, hence the lack of much of a UI. The recommended way to test its functions is through the use of its Swagger interface, which is already configured in this project. 15 | 16 | ![image](https://user-images.githubusercontent.com/41052272/222646739-118e88fd-e47d-4bbf-b17a-90af3499b1da.png) 17 | *Testing with Swagger Interface* 18 | 19 | ## Services 20 | Most of the services are collections of endpoints that are intended to support a particular Dashboard. Many of the endpoints are used as a front-end to one or more SQL queries against one or more underlying databases. As there is no ORM used here, the only access to these databases is through these endpoints. This means there are likely to be endpoints needed to cover any CRUD operations that a client may want to issue. Many endpoints have parameters that allow more than one of these kinds of operations from the same endpoint. 21 | 22 | Some services are also more complex, interfacing to other systems, as is the case with the Chat Service, or require additional configuration to be fully operational, which also happens to be the case with the Chat Service. Please refer to the documentation for each individual service you plan on using for additional information. 23 | 24 | * [Chat Service](https://github.com/500Foods/TMS-XData-TemplateDemoData/blob/main/docs/ChatService.md) 25 | * [Dashboard Service](https://github.com/500Foods/TMS-XData-TemplateDemoData/blob/main/docs/DashboardService.md) 26 | * [Messaging Service](https://github.com/500Foods/TMS-XData-TemplateDemoData/blob/main/docs/MessagingService.md) 27 | * [Person Service](https://github.com/500Foods/TMS-XData-TemplateDemoData/blob/main/docs/PersonService.md) 28 | * [System Service](https://github.com/500Foods/TMS-XData-TemplateDemoData/blob/main/docs/SystemService.md) 29 | 30 | ## Documentation 31 | While the code is intended to be straightforward, and the blog is intended to be the primary introduction to this project, other documentation will be added as issues arise. 32 | - [Config.JSON](https://github.com/500Foods/TMS-XData-TemplateDemoData/blob/main/docs/ConfigJSON.md) 33 | - [Accounts](https://github.com/500Foods/TMS-XData-TemplateDemoData/blob/main/docs/Accounts.md) 34 | 35 | ## Key Dependencies 36 | As with any modern application, other libraries/dependencies have been used in this project. 37 | - [TMS XData](https://www.tmssoftware.com/site/tmswebcore.asp) - This is a TMS XData project, after all 38 | - [TMS Cryptography Pack](https://www.tmssoftware.com/site/tmscrypto.asp) - Supples the SHA-256 hash function 39 | - [TZDB](https://github.com/pavkam/tzdb) - Comprehensive IANA TZ library for Delphi 40 | 41 | ## Usage Note: RandomDLL.DLL 42 | This DLL needs to be included in the same folder as the project executable. It is needed by the SHA-256 hash function that is used in several places, that, in turn, comes from the [TMS Cryptography Pack](https://www.tmssoftware.com/site/tmscrypto.asp). A post-build event has been added to the project to do this automatically. This assumes that a Win64 project is being built. Please adjust accordingly. 43 | 44 | ## Contributions 45 | Initially, this example uses SQLite as its database, as well as a collection of include files for all of the SQL operations that have been implemented so far. Over time, this will be expanded to include support for more databases and more queries. If there's a database you'd like to see included in the template, by all means please post an Issue or, if you're able, make a Pull Request and we'll see that it gets added. 46 | 47 | ## Repository Information 48 | [![Count Lines of Code](https://github.com/500Foods/TMS-XData-TemplateDemoData/actions/workflows/main.yml/badge.svg)](https://github.com/500Foods/TMS-XData-TemplateDemoData/actions/workflows/main.yml) 49 | 50 | ``` 51 | Last Updated at 2023-12-24 05:18:19 UTC 52 | ------------------------------------------------------------------------------- 53 | Language files blank comment code 54 | ------------------------------------------------------------------------------- 55 | Pascal 15 1524 2766 12948 56 | Markdown 8 36 2 218 57 | Delphi Form 3 0 0 155 58 | YAML 2 8 12 33 59 | Text 1 0 0 1 60 | ------------------------------------------------------------------------------- 61 | SUM: 29 1568 2780 13355 62 | ------------------------------------------------------------------------------- 63 | ``` 64 | 65 | 66 | ## Sponsor / Donate / Support 67 | If you find this work interesting, helpful, or valuable, or that it has saved you time, money, or both, please consider directly supporting these efforts financially via [GitHub Sponsors](https://github.com/sponsors/500Foods) or donating via [Buy Me a Pizza](https://www.buymeacoffee.com/andrewsimard500). Also, check out these other [GitHub Repositories](https://github.com/500Foods?tab=repositories&q=&sort=stargazers) that may interest you. 68 | 69 | ## More TMS WEB Core and TMS XData Content 70 | If you're interested in other TMS WEB Core and TMS XData content, follow along on 𝕏 at [@WebCoreAndMore](https://x.com/WebCoreAndMore), join our 𝕏 [Web Core and More Community](https://twitter.com/i/communities/1683267402384183296), or check out the [TMS Software Blog](https://www.tmssoftware.com/site/blog.asp). 71 | -------------------------------------------------------------------------------- /units/Unit3.pas: -------------------------------------------------------------------------------- 1 | unit Unit3; 2 | 3 | interface 4 | 5 | uses 6 | System.SysUtils, 7 | System.Classes, 8 | System.NetEncoding, 9 | System.Math, 10 | System.DateUtils, 11 | 12 | XData.Server.Module, 13 | XData.Service.Common, 14 | XData.Sys.Exceptions, 15 | 16 | HashObj, 17 | MiscObj, 18 | 19 | FireDAC.Stan.Intf, 20 | FireDAC.Stan.Option, 21 | FireDAC.Stan.Param, 22 | FireDAC.Comp.Client, 23 | FireDAC.Stan.StorageBin, 24 | FireDAC.Stan.StorageJSON, 25 | FireDAC.Stan.StorageXML, 26 | FireDAC.Comp.BatchMove, 27 | FireDAC.Comp.BatchMove.Dataset, 28 | FireDAC.Comp.BatchMove.JSON, 29 | 30 | Data.DB, 31 | 32 | ActiveX; // For Co/UnInitailze when using XML StreamFormat; 33 | 34 | type 35 | TDBSupport = class(TDataModule) 36 | private 37 | { Private declarations } 38 | public 39 | { Public declarations } 40 | procedure ConnectQuery(var conn: TFDConnection; var qry: TFDQuery; DatabaseName: String; DatabaseEngine: String); 41 | procedure DisconnectQuery(var conn: TFDConnection; var qry: TFDQuery); 42 | function HashThis(InputText: String):String; 43 | procedure Export(Format: String; QueryResult: TFDQuery; var OutputStream: TStream); 44 | function QueryToJSON(QueryResult: TFDQuery): String; 45 | function DecodeSession(s: String):TDateTime; 46 | 47 | end; 48 | 49 | var 50 | DBSupport: TDBSupport; 51 | 52 | implementation 53 | 54 | {%CLASSGROUP 'Vcl.Controls.TControl'} 55 | 56 | {$R *.dfm} 57 | 58 | uses Unit1, Unit2; 59 | 60 | function TDBSupport.HashThis(InputText: String):String; 61 | var 62 | SHA2: TSHA2Hash; 63 | begin 64 | SHA2 := TSHA2Hash.Create; 65 | SHA2.HashSizeBits:= 256; 66 | SHA2.OutputFormat:= hexa; 67 | SHA2.Unicode:= noUni; 68 | Result := LowerCase(SHA2.Hash(InputText)); 69 | SHA2.Free; 70 | end; 71 | 72 | function TDBSupport.QueryToJSON(QueryResult: TFDQuery): String; 73 | var 74 | bm: TFDBatchMove; 75 | bw: TFDBatchMoveJSONWriter; 76 | br: TFDBatchMoveDataSetReader; 77 | os: TMemoryStream; 78 | begin 79 | os := TMemoryStream.Create; 80 | bm := TFDBatchMove.Create(nil); 81 | bw := TFDBatchMoveJSONWriter.Create(nil); 82 | br := TFDBatchMoveDataSetReader.Create(nil); 83 | try 84 | br.Dataset := QueryResult; 85 | bw.Stream := os; 86 | bm.Reader := br; 87 | bm.Writer := bw; 88 | bm.Execute; 89 | SetString(Result, PAnsiChar(os.Memory), os.Size ); 90 | finally 91 | br.Free; 92 | bw.Free; 93 | bm.Free; 94 | os.Free; 95 | end; 96 | end; 97 | 98 | procedure TDBSupport.Export(Format: String; QueryResult: TFDQuery; var OutputStream: TStream); 99 | var 100 | ContentFormat: String; 101 | ContentType: String; 102 | 103 | L: TStringList; 104 | S: String; 105 | 106 | bm: TFDBatchMove; 107 | bw: TFDBatchMoveJSONWriter; 108 | br: TFDBatchMoveDataSetReader; 109 | 110 | ms: TMemoryStream; 111 | 112 | i: Integer; 113 | begin 114 | ContentFormat := Uppercase(Trim(Format)); 115 | ContentType := 'text/plain'; 116 | 117 | 118 | if (ContentFormat = 'FIREDAC') then 119 | begin 120 | ContentType := 'application/json'; 121 | OutputStream := TMemoryStream.Create; 122 | QueryResult.SaveToStream(OutputStream, sfJSON); 123 | end 124 | 125 | else if (ContentFormat = 'XML') then 126 | begin 127 | ContentType := 'application/xml'; 128 | OutputStream := TMemoryStream.Create; 129 | CoInitialize(nil); 130 | try 131 | QueryResult.SaveToStream(OutputStream, sfXML); 132 | finally 133 | CoUninitialize; 134 | end; 135 | end 136 | 137 | else if (ContentFormat = 'BINARY') then 138 | begin 139 | ContentType := 'application/json'; 140 | ms := TMemoryStream.Create; 141 | try 142 | QueryResult.SaveToStream(ms,sfBinary); 143 | ms.Position := 0; 144 | OutputStream := TMemoryStream.Create; 145 | TNetEncoding.Base64.Encode(ms, OutputStream); 146 | finally 147 | ms.Free; 148 | end; 149 | end 150 | 151 | else if (ContentFormat = 'PLAIN') then 152 | begin 153 | ContentType := 'text/plain'; 154 | L := TStringList.Create; 155 | S := ''; 156 | try 157 | QueryResult.First; 158 | while not QueryResult.Eof do 159 | begin 160 | S := ''; 161 | for i := 0 to QueryResult.FieldCount - 1 do 162 | begin 163 | if (S > '') then S := S + ''; 164 | S := S + '' + QueryResult.Fields[i].AsString + ''; 165 | end; 166 | L.Add(S); 167 | QueryResult.Next; 168 | end; 169 | finally 170 | OutputStream := TMemoryStream.Create; 171 | L.SaveToStream(OutputStream); 172 | L.Free; 173 | end; 174 | end 175 | 176 | else if (ContentFormat = 'CSV') then 177 | begin 178 | ContentType := 'text/csv'; 179 | L := TStringList.Create; 180 | S := ''; 181 | for i := 0 to QueryResult.FieldCount - 1 do 182 | begin 183 | if (S > '') then S := S + ','; 184 | S := S + '"' +QueryResult.FieldDefs.Items[I].Name + '"'; 185 | end; 186 | L.Add(S); 187 | try 188 | QueryResult.First; 189 | while not (QueryResult.EOF) do 190 | begin 191 | S := ''; 192 | for i := 0 to QueryResult.FieldCount - 1 do 193 | begin 194 | if (S > '') then S := S + ','; 195 | S := S + '"' + QueryResult.Fields[I].AsString + '"'; 196 | end; 197 | L.Add(S); 198 | QueryResult.Next; 199 | end; 200 | finally 201 | OutputStream := TMemoryStream.Create; 202 | L.SaveToStream(OutputStream); 203 | L.Free; 204 | end; 205 | end 206 | 207 | else // if ContentFormat = 'JSON' then 208 | begin 209 | ContentType := 'application/json'; 210 | OutputStream := TMemoryStream.Create; 211 | bm := TFDBatchMove.Create(nil); 212 | bw := TFDBatchMoveJSONWriter.Create(nil); 213 | br := TFDBatchMoveDataSetReader.Create(nil); 214 | try 215 | br.Dataset := QueryResult; 216 | bw.Stream := OutputStream; 217 | bm.Reader := br; 218 | bm.Writer := bw; 219 | bm.Execute; 220 | finally 221 | br.Free; 222 | bw.Free; 223 | bm.Free; 224 | end; 225 | end; 226 | 227 | TXDataOperationContext.Current.Response.Headers.SetValue('content-type', ContentType); 228 | 229 | end; 230 | 231 | procedure TDBSupport.ConnectQuery(var conn: TFDConnection; var qry: TFDQuery; DatabaseName: String; DatabaseEngine: String); 232 | begin 233 | try 234 | // Establish a new connection for each endpoint invocation (not ideal!) 235 | if DatabaseEngine = 'sqlite' then 236 | begin 237 | conn := TFDConnection.Create(nil); 238 | conn.Params.Clear; 239 | conn.Params.DriverID := 'SQLite'; 240 | conn.Params.Database := DatabaseName; 241 | conn.Params.Add('DateTimeFormat=String'); 242 | conn.Params.Add('Synchronous=Full'); 243 | conn.Params.Add('LockingMode=Normal'); 244 | conn.Params.Add('SharedCache=False'); 245 | conn.Params.Add('UpdateOptions.LockWait=True'); 246 | conn.Params.Add('BusyTimeout=10000'); 247 | conn.Params.Add('SQLiteAdvanced=page_size=4096'); 248 | // Extras 249 | conn.FormatOptions.StrsEmpty2Null := True; 250 | with conn.FormatOptions do 251 | begin 252 | StrsEmpty2Null := true; 253 | OwnMapRules := True; 254 | with MapRules.Add do begin 255 | SourceDataType := dtWideMemo; 256 | TargetDataType := dtWideString; 257 | end; 258 | end; 259 | end; 260 | 261 | conn.Open; 262 | 263 | // Create a query to do our work 264 | qry := TFDQuery.Create(nil); 265 | qry.Connection := conn; 266 | 267 | except on E: Exception do 268 | begin 269 | // If the above fails, not a good thing, but at least try and make a note as to why 270 | Mainform.mmInfo.Lines.Add('[ '+E.ClassName+' ] '+E.Message); 271 | end; 272 | end; 273 | end; 274 | 275 | function TDBSupport.DecodeSession(s: String): TDateTime; 276 | var 277 | i: Double; 278 | ex: INteger; 279 | 280 | const 281 | c: TArray = ['B','b','C','c','D','d','F','f','G','g','H','h','J','j','K','k','L','M','m','N','n','P','p','Q','q','R','r','S','s','T','t','V','W','w','X','x','Z','z','0','1','2','3','4','5','6','7','8','9']; 282 | 283 | function findc(s: String): Integer; 284 | var 285 | j: integer; 286 | begin 287 | Result := -1; 288 | j := 0; 289 | while j < length(c) do 290 | begin 291 | if c[j] = s then result := j; 292 | j := j + 1; 293 | end; 294 | end; 295 | 296 | begin 297 | // https://github.com/marko-36/base29-shortener 298 | 299 | // This decodes a custom Base-48 encoded string back into an integer. 300 | // This is used to pass the action log session id which is just the 301 | // app start time in UTC as a unix datetime format. Why? So we 302 | // get a nice short session id that we can use without being as 303 | // burdensome as something like a GUID. 304 | 305 | // JavaScript: 306 | // i = 0; 307 | // for (var ex=0; ex 1 then 87 | begin 88 | DBSupport.DisconnectQuery(DBConn, Query1); 89 | raise EXDataHttpUnauthorized.Create('JWT was not validated'); 90 | end; 91 | 92 | // Get the data that we want to return as a Query result set 93 | try 94 | {$Include sql\person\directory\directory.inc} 95 | Query1.Open; 96 | except on E: Exception do 97 | begin 98 | DBSupport.DisconnectQuery(DBConn, Query1); 99 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 100 | raise EXDataHttpUnauthorized.Create('Internal Error: Directory'); 101 | end; 102 | end; 103 | 104 | // Assuming Result is an uninitialized TStream 105 | DBSupport.Export(Format, Query1, Result); 106 | 107 | // Keep track of endpoint history 108 | try 109 | {$Include sql\system\endpoint_history_insert\endpoint_history_insert.inc} 110 | Query1.ParamByName('PERSONID').AsInteger := User.Claims.Find('usr').AsInteger; 111 | Query1.ParamByName('ENDPOINT').AsString := 'SystemService.Login'; 112 | Query1.ParamByName('ACCESSED').AsDateTime := TTimeZone.local.ToUniversalTime(ElapsedTime); 113 | Query1.ParamByName('IPADDRESS').AsString := TXDataOperationContext.Current.Request.RemoteIP; 114 | Query1.ParamByName('APPLICATION').AsString := User.Claims.Find('app').AsString; 115 | Query1.ParamByName('VERSION').AsString := MainForm.AppVersion; 116 | Query1.ParamByName('DATABASENAME').AsString := DatabaseName; 117 | Query1.ParamByName('DATABASEENGINE').AsString := DatabaseEngine; 118 | Query1.ParamByName('EXECUTIONMS').AsInteger := MillisecondsBetween(Now,ElapsedTime); 119 | Query1.ParamByName('DETAILS').AsString := '['+User.Claims.Find('anm').AsString+'] ['+Format+']'; 120 | Query1.ExecSQL; 121 | except on E: Exception do 122 | begin 123 | DBSupport.DisconnectQuery(DBConn, Query1); 124 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 125 | raise EXDataHttpUnauthorized.Create('Internal Error: EHI'); 126 | end; 127 | end; 128 | 129 | 130 | // All Done 131 | try 132 | DBSupport.DisconnectQuery(DBConn, Query1); 133 | except on E: Exception do 134 | begin 135 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 136 | raise EXDataHttpUnauthorized.Create('Internal Error: DQ'); 137 | end; 138 | end; 139 | end; 140 | 141 | 142 | function TPersonService.Profile: TStream; 143 | var 144 | DBConn: TFDConnection; 145 | Query1: TFDQuery; 146 | DatabaseName: String; 147 | DatabaseEngine: String; 148 | ElapsedTime: TDateTime; 149 | User: IUserIdentity; 150 | JWT: String; 151 | ResultJSON: TJSONObject; 152 | ResultArray: TJSONArray; 153 | begin 154 | // Returning JSON, so flag it as such 155 | TXDataOperationContext.Current.Response.Headers.SetValue('content-type', 'application/json'); 156 | 157 | // Time this event 158 | ElapsedTime := Now; 159 | 160 | // Get data from the JWT 161 | User := TXDataOperationContext.Current.Request.User; 162 | JWT := TXDataOperationContext.Current.Request.Headers.Get('Authorization'); 163 | if (User = nil) then raise EXDataHttpUnauthorized.Create('Missing authentication'); 164 | 165 | // Setup DB connection and query 166 | try 167 | DatabaseName := User.Claims.Find('dbn').AsString; 168 | DatabaseEngine := User.Claims.Find('dbe').AsString; 169 | DBSupport.ConnectQuery(DBConn, Query1, DatabaseName, DatabaseEngine); 170 | except on E: Exception do 171 | begin 172 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 173 | raise EXDataHttpUnauthorized.Create('Internal Error: CQ'); 174 | end; 175 | end; 176 | 177 | // Check if we've got a valid JWT (one that has not been revoked) 178 | try 179 | {$Include sql\system\token_check\token_check.inc} 180 | Query1.ParamByName('TOKENHASH').AsString := DBSupport.HashThis(JWT); 181 | Query1.ParamByName('IPADDRESS').AsString := TXDataOperationContext.Current.Request.RemoteIP; 182 | Query1.Open; 183 | except on E: Exception do 184 | begin 185 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 186 | raise EXDataHttpUnauthorized.Create('Internal Error: JC'); 187 | end; 188 | end; 189 | if Query1.RecordCount <> 1 then 190 | begin 191 | DBSupport.DisconnectQuery(DBConn, Query1); 192 | raise EXDataHttpUnauthorized.Create('JWT was not validated'); 193 | end; 194 | 195 | 196 | // Create object to be returned 197 | ResultJSON := TJSONObject.Create; 198 | 199 | // Get Profile information 200 | try 201 | {$Include sql\person\profile\profile.inc} 202 | Query1.ParamByName('PERSONID').AsInteger := User.Claims.Find('usr').AsInteger; 203 | Query1.Open; 204 | except on E: Exception do 205 | begin 206 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 207 | raise EXDataHttpUnauthorized.Create('Internal Error: Profile'); 208 | end; 209 | end; 210 | ResultArray := TJSONObject.ParseJSONValue(DBSupport.QueryToJSON(Query1)) as TJSONArray; 211 | ResultJSON.AddPair('Profile', ResultArray); 212 | 213 | // Get Contact information 214 | try 215 | {$Include sql\person\contact\contact.inc} 216 | Query1.ParamByName('PERSONID').AsInteger := User.Claims.Find('usr').AsInteger; 217 | Query1.Open; 218 | except on E: Exception do 219 | begin 220 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 221 | raise EXDataHttpUnauthorized.Create('Internal Error: Contact'); 222 | end; 223 | end; 224 | ResultArray := TJSONObject.ParseJSONValue(DBSupport.QueryToJSON(Query1)) as TJSONArray; 225 | ResultJSON.AddPair('Contact', ResultArray); 226 | 227 | // Get Role information 228 | try 229 | {$Include sql\person\role\role.inc} 230 | Query1.ParamByName('PERSONID').AsInteger := User.Claims.Find('usr').AsInteger; 231 | Query1.Open; 232 | except on E: Exception do 233 | begin 234 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 235 | raise EXDataHttpUnauthorized.Create('Internal Error: Role'); 236 | end; 237 | end; 238 | ResultArray := TJSONObject.ParseJSONValue(DBSupport.QueryToJSON(Query1)) as TJSONArray; 239 | ResultJSON.AddPair('Role', ResultArray); 240 | 241 | // Get Login Count information 242 | try 243 | {$Include sql\person\login_count\login_count.inc} 244 | Query1.ParamByName('PERSONID').AsInteger := User.Claims.Find('usr').AsInteger; 245 | Query1.Open; 246 | except on E: Exception do 247 | begin 248 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 249 | raise EXDataHttpUnauthorized.Create('Internal Error: LoginCount'); 250 | end; 251 | end; 252 | ResultArray := TJSONObject.ParseJSONValue(DBSupport.QueryToJSON(Query1)) as TJSONArray; 253 | ResultJSON.AddPair('Logins', ResultArray); 254 | 255 | // Get Recent Login information 256 | try 257 | {$Include sql\person\login_recent\login_recent.inc} 258 | Query1.ParamByName('PERSONID').AsInteger := User.Claims.Find('usr').AsInteger; 259 | Query1.Open; 260 | except on E: Exception do 261 | begin 262 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 263 | raise EXDataHttpUnauthorized.Create('Internal Error: LoginRecent'); 264 | end; 265 | end; 266 | ResultArray := TJSONObject.ParseJSONValue(DBSupport.QueryToJSON(Query1)) as TJSONArray; 267 | ResultJSON.AddPair('RecentLogins', ResultArray); 268 | 269 | // Add photo 270 | try 271 | {$Include sql\person\photo\photo.inc} 272 | Query1.ParamByName('PERSONID').AsInteger := User.Claims.Find('usr').AsInteger; 273 | Query1.Open; 274 | except on E: Exception do 275 | begin 276 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 277 | raise EXDataHttpUnauthorized.Create('Internal Error: Photo'); 278 | end; 279 | end; 280 | ResultJSON.AddPair('Photo', String(Query1.FieldByName('photo_datauri').AsString)); 281 | 282 | // Not sure if there is another version of this that is more direct? 283 | Result := TStringStream.Create(ResultJSON.ToString); 284 | 285 | // Cleanup 286 | ResultJSON.Free; 287 | 288 | // Keep track of endpoint history 289 | try 290 | {$Include sql\system\endpoint_history_insert\endpoint_history_insert.inc} 291 | Query1.ParamByName('PERSONID').AsInteger := User.Claims.Find('usr').AsInteger; 292 | Query1.ParamByName('ENDPOINT').AsString := 'PersonService.Profile'; 293 | Query1.ParamByName('ACCESSED').AsDateTime := TTimeZone.local.ToUniversalTime(ElapsedTime); 294 | Query1.ParamByName('IPADDRESS').AsString := TXDataOperationContext.Current.Request.RemoteIP; 295 | Query1.ParamByName('APPLICATION').AsString := User.Claims.Find('app').AsString; 296 | Query1.ParamByName('VERSION').AsString := MainForm.AppVersion; 297 | Query1.ParamByName('DATABASENAME').AsString := DatabaseName; 298 | Query1.ParamByName('DATABASEENGINE').AsString := DatabaseEngine; 299 | Query1.ParamByName('EXECUTIONMS').AsInteger := MillisecondsBetween(Now,ElapsedTime); 300 | Query1.ParamByName('DETAILS').AsString := '['+User.Claims.Find('anm').AsString+']'; 301 | Query1.ExecSQL; 302 | except on E: Exception do 303 | begin 304 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 305 | raise EXDataHttpUnauthorized.Create('Internal Error: EHI'); 306 | end; 307 | end; 308 | 309 | // All Done 310 | try 311 | DBSupport.DisconnectQuery(DBConn, Query1); 312 | except on E: Exception do 313 | begin 314 | MainForm.mmInfo.Lines.Add('['+E.Classname+'] '+E.Message); 315 | raise EXDataHttpUnauthorized.Create('Internal Error: DQ'); 316 | end; 317 | end; 318 | end; 319 | 320 | initialization 321 | RegisterServiceType(TPersonService); 322 | 323 | end. 324 | -------------------------------------------------------------------------------- /ddl/photo/photo_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [table] photo 2 | // 3 | // Names were randomly generated - any resemblance to actual people, living or dead, etc. etc. 4 | // The first names were drawn from the following link 5 | // https://www.babycenter.com/baby-names/most-popular/top-baby-names 6 | // The last names were drawn from the following link 7 | // https://selectsurnames.com/top-300-surnames/ 8 | 9 | TableName := 'photo'; 10 | 11 | if (DatabaseEngine = 'sqlite') then 12 | begin 13 | 14 | with Query1 do 15 | begin 16 | 17 | // Check if the table exists 18 | SQL.Clear; 19 | SQL.Add('select count(*) records from '+TableName+';'); 20 | try 21 | Open; 22 | LogEvent('...'+TableName+' ('+IntToStr(FieldByName('records').AsInteger)+' records)'); 23 | 24 | except on E:Exception do 25 | begin 26 | LogEvent('...'+TableName+' (CREATE)'); 27 | SQL.Clear; 28 | SQL.Add('create table if not exists '+TableName+' ( '+ 29 | ' person_id integer NOT NULL, '+ 30 | ' photo_id integer NOT NULL, '+ 31 | ' last_modified text NOT NULL, '+ 32 | ' last_modifier integer NOT NULL, '+ 33 | ' photo_type integer NOT NULL, '+ 34 | ' photo_datauri text NOT NULL, '+ 35 | ' CONSTRAINT constraint_name PRIMARY KEY (person_id, photo_id) '+ 36 | ');' 37 | ); 38 | ExecSQL; 39 | 40 | // Try it again 41 | SQL.Clear; 42 | SQL.Add('select count(*) records from '+TableName+';'); 43 | Open; 44 | end; 45 | end; 46 | 47 | // Populate empty table with sample data 48 | if (FieldByName('records').AsInteger = 0) then 49 | begin 50 | LogEvent('...'+TableName+' (POPULATE)'); 51 | SQL.Clear; 52 | 53 | // Random photos generated via https://boredhumans.com/faces.php 54 | SQL.Clear; 55 | ImageFile.Text := ''; 56 | SQL.Add('insert into '+TableName+' values(0, 1, Datetime("now"), 0, 1, :PHOTO );'); 57 | ImageFile.LoadFromFile('ddl/photo/data/1.txt'); 58 | ParamByName('PHOTO').AsString := ImageFile.Text; 59 | ExecSQL; 60 | 61 | // SQL.Clear; 62 | // ImageFile.Text := ''; 63 | // SQL.Add('insert into '+TableName+' values(0010010001, 1, Datetime("now"), 0, 1, :PHOTO );'); 64 | // ImageFile.LoadFromFile('ddl/photo/data/2.txt'); 65 | // ParamByName('PHOTO').AsString := ImageFile.Text; 66 | // ExecSQL; 67 | // 68 | // SQL.Clear; 69 | // ImageFile.Text := ''; 70 | // SQL.Add('insert into '+TableName+' values(0010020002, 1, Datetime("now"), 0, 1, :PHOTO );'); 71 | // ImageFile.LoadFromFile('ddl/photo/data/3.txt'); 72 | // ParamByName('PHOTO').AsString := ImageFile.Text; 73 | // ExecSQL; 74 | 75 | // SQL.Add('insert into person values(0010030003, 1, Datetime("now"), 0, 1, "Emma", "C", "Williams", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EWILLIAMS", "NO PASSWORD SET" );'); 76 | // SQL.Add('insert into person values(0010040004, 1, Datetime("now"), 0, 1, "Noah", "D", "Brown", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "NBROWN", "NO PASSWORD SET" );'); 77 | // SQL.Add('insert into person values(0010050005, 1, Datetime("now"), 0, 1, "Amelia", "E", "Johnson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "AJOHNSON", "NO PASSWORD SET" );'); 78 | // SQL.Add('insert into person values(0010060006, 1, Datetime("now"), 0, 1, "Oliver", "F", "Taylor", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "OTAYLOR", "NO PASSWORD SET" );'); 79 | // SQL.Add('insert into person values(0010070007, 1, Datetime("now"), 0, 1, "Ava", "G", "Davis", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ADAVIS", "NO PASSWORD SET" );'); 80 | // SQL.Add('insert into person values(0010080008, 1, Datetime("now"), 0, 1, "Elijah", "H", "Miller", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EMILLER", "NO PASSWORD SET" );'); 81 | // SQL.Add('insert into person values(0010090009, 1, Datetime("now"), 0, 1, "Sophia", "I", "Wilson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "SWILSON", "NO PASSWORD SET" );'); 82 | // SQL.Add('insert into person values(0010000010, 1, Datetime("now"), 0, 1, "Mateo", "J", "Thompson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "MTHOMPSON", "NO PASSWORD SET" );'); 83 | // SQL.Add('insert into person values(0010010011, 1, Datetime("now"), 0, 1, "Isabella", "K", "Thomas", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ITHOMAS", "NO PASSWORD SET" );'); 84 | // SQL.Add('insert into person values(0010020012, 1, Datetime("now"), 0, 1, "Lucas", "L", "Anderson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LANDERSON", "NO PASSWORD SET" );'); 85 | // SQL.Add('insert into person values(0010030012, 1, Datetime("now"), 0, 1, "Luna", "M", "White", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LWHITE", "NO PASSWORD SET" );'); 86 | // SQL.Add('insert into person values(0020040013, 1, Datetime("now"), 0, 1, "Levi", "N", "Martin", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LMARTIN", "NO PASSWORD SET" );'); 87 | // SQL.Add('insert into person values(0020050014, 1, Datetime("now"), 0, 1, "Mia", "O", "Moore", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "MMOORE", "NO PASSWORD SET" );'); 88 | // SQL.Add('insert into person values(0020060015, 1, Datetime("now"), 0, 1, "Asher", "P", "Jackson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "AJACKSON", "NO PASSWORD SET" );'); 89 | // SQL.Add('insert into person values(0020070016, 1, Datetime("now"), 0, 1, "Charlotte", "Q", "Clark", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "CCLARK", "NO PASSWORD SET" );'); 90 | // SQL.Add('insert into person values(0020080017, 1, Datetime("now"), 0, 1, "James", "R", "Walker", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "JWALKER", "NO PASSWORD SET" );'); 91 | // SQL.Add('insert into person values(0020090018, 1, Datetime("now"), 0, 1, "Evelyn", "S", "Evans", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EEVANS", "NO PASSWORD SET" );'); 92 | // SQL.Add('insert into person values(0020000019, 1, Datetime("now"), 0, 1, "Leo", "T", "Lee", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LLEE", "NO PASSWORD SET" );'); 93 | // SQL.Add('insert into person values(0020010020, 1, Datetime("now"), 0, 1, "Harper", "U", "Lewis", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "HLEWIS", "NO PASSWORD SET" );'); 94 | // SQL.Add('insert into person values(0030020021, 1, Datetime("now"), 0, 1, "Grayson", "V", "King", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "GKING", "NO PASSWORD SET" );'); 95 | // SQL.Add('insert into person values(0030030022, 1, Datetime("now"), 0, 1, "Scarlett", "W", "Harris", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "SHARRIS", "NO PASSWORD SET" );'); 96 | // SQL.Add('insert into person values(0030040023, 1, Datetime("now"), 0, 1, "Ezra", "X", "Roberts", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EROBERTS", "NO PASSWORD SET" );'); 97 | // SQL.Add('insert into person values(0030050024, 1, Datetime("now"), 0, 1, "Nova", "Y", "Robinson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "NROBINSON", "NO PASSWORD SET" );'); 98 | // SQL.Add('insert into person values(0030060025, 1, Datetime("now"), 0, 1, "Luca", "Z", "Wright", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LWRIGHT", "NO PASSWORD SET" );'); 99 | // SQL.Add('insert into person values(0030070026, 1, Datetime("now"), 0, 1, "Aurora", "A", "Young", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "AYOUNG", "NO PASSWORD SET" );'); 100 | // SQL.Add('insert into person values(0030080027, 1, Datetime("now"), 0, 1, "Ethan", "B", "Scott", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ESCOTT", "NO PASSWORD SET" );'); 101 | // SQL.Add('insert into person values(0030090028, 1, Datetime("now"), 0, 1, "Ella", "C", "Reed", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EREED", "NO PASSWORD SET" );'); 102 | // SQL.Add('insert into person values(0040000029, 1, Datetime("now"), 0, 1, "Aiden", "D", "Murphy", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "AMURPHY", "NO PASSWORD SET" );'); 103 | // SQL.Add('insert into person values(0040010031, 1, Datetime("now"), 0, 1, "Mila", "E", "Hill", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "MHILL", "NO PASSWORD SET" );'); 104 | // SQL.Add('insert into person values(0040020032, 1, Datetime("now"), 0, 1, "Wyatt", "F", "Wood", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "WWOOD", "NO PASSWORD SET" );'); 105 | // SQL.Add('insert into person values(0050030033, 1, Datetime("now"), 0, 1, "Aria", "G", "Hall", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "AHALL", "NO PASSWORD SET" );'); 106 | // SQL.Add('insert into person values(0050040034, 1, Datetime("now"), 0, 1, "Sebastian", "H", "Green", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "SGREEN", "NO PASSWORD SET" );'); 107 | // SQL.Add('insert into person values(0050050035, 1, Datetime("now"), 0, 1, "Ellie", "I", "Allen", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EALLEN", "NO PASSWORD SET" );'); 108 | // SQL.Add('insert into person values(0070060036, 1, Datetime("now"), 0, 1, "Benjamin", "J", "Kelly", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "BKELLY", "NO PASSWORD SET" );'); 109 | // SQL.Add('insert into person values(0070070037, 1, Datetime("now"), 0, 1, "Gianna", "K", "Campbell", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "GCAMPBELL", "NO PASSWORD SET" );'); 110 | // SQL.Add('insert into person values(0070080038, 1, Datetime("now"), 0, 1, "Mason", "L", "Edwards", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "MEDWARDS", "NO PASSWORD SET" );'); 111 | // SQL.Add('insert into person values(0080090039, 1, Datetime("now"), 0, 1, "Sofia", "M", "Adams", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "SADAMS", "NO PASSWORD SET" );'); 112 | // SQL.Add('insert into person values(0080000040, 1, Datetime("now"), 0, 1, "Henry", "N", "Baker", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "HBAKER", "NO PASSWORD SET" );'); 113 | // SQL.Add('insert into person values(0080010041, 1, Datetime("now"), 0, 1, "Violet", "O", "Watson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "VWATSON", "NO PASSWORD SET" );'); 114 | // SQL.Add('insert into person values(0080010042, 1, Datetime("now"), 0, 1, "Hudson", "P", "Mitchell", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "HMITCHELL", "NO PASSWORD SET" );'); 115 | // SQL.Add('insert into person values(0090010043, 1, Datetime("now"), 0, 1, "Layla", "Q", "Phillips", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LPHILLIPS", "NO PASSWORD SET" );'); 116 | // SQL.Add('insert into person values(0090010044, 1, Datetime("now"), 0, 1, "Jack", "R", "Cooper", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "JCOOPER", "NO PASSWORD SET" );'); 117 | // SQL.Add('insert into person values(0090020045, 1, Datetime("now"), 0, 1, "Willow", "S", "Turner", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "WTURNER", "NO PASSWORD SET" );'); 118 | // SQL.Add('insert into person values(0090020046, 1, Datetime("now"), 0, 1, "Jackson", "T", "Morris", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "JMORRIS", "NO PASSWORD SET" );'); 119 | // SQL.Add('insert into person values(0090030047, 1, Datetime("now"), 0, 1, "Lily", "U", "Carter", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LCARTER", "NO PASSWORD SET" );'); 120 | // SQL.Add('insert into person values(0090040048, 1, Datetime("now"), 0, 1, "Owen", "V", "Morgan", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "OMORGAN", "NO PASSWORD SET" );'); 121 | // SQL.Add('insert into person values(0090040049, 1, Datetime("now"), 0, 1, "Hazel", "W", "Hughes", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "HHUGHES", "NO PASSWORD SET" );'); 122 | // SQL.Add('insert into person values(0090050051, 1, Datetime("now"), 0, 1, "Daniel", "X", "Cook", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "DCOOK", "NO PASSWORD SET" );'); 123 | // SQL.Add('insert into person values(0090060052, 1, Datetime("now"), 0, 1, "Camila", "Y", "Ward", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "CWARD", "NO PASSWORD SET" );'); 124 | // SQL.Add('insert into person values(0090060053, 1, Datetime("now"), 0, 1, "Alexander", "Z", "Collins", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ACOLLINS", "NO PASSWORD SET" );'); 125 | // SQL.Add('insert into person values(0090070054, 1, Datetime("now"), 0, 1, "Avery", "A", "James", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "AJAMES", "NO PASSWORD SET" );'); 126 | // SQL.Add('insert into person values(0100080055, 1, Datetime("now"), 0, 1, "Maverick", "B", "Parker", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "MPARKER", "NO PASSWORD SET" );'); 127 | // SQL.Add('insert into person values(0100080056, 1, Datetime("now"), 0, 1, "Chloe", "C", "Bell", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "CBELL", "NO PASSWORD SET" );'); 128 | // SQL.Add('insert into person values(0100080057, 1, Datetime("now"), 0, 1, "Kai", "D", "Nelson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "KNELSON", "NO PASSWORD SET" );'); 129 | // SQL.Add('insert into person values(0110080058, 1, Datetime("now"), 0, 1, "Elena", "E", "Stewart", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ESTEWART", "NO PASSWORD SET" );'); 130 | // SQL.Add('insert into person values(0110090059, 1, Datetime("now"), 0, 1, "Gabriel", "F", "Bailey", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "GBAILEY", "NO PASSWORD SET" );'); 131 | // SQL.Add('insert into person values(0110090061, 1, Datetime("now"), 0, 1, "Paisley", "G", "Stevens", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "PSTEVENS", "NO PASSWORD SET" );'); 132 | // SQL.Add('insert into person values(0110090062, 1, Datetime("now"), 0, 1, "Carter", "H", "Cox", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "CCOX", "NO PASSWORD SET" );'); 133 | // SQL.Add('insert into person values(0110000063, 1, Datetime("now"), 0, 1, "Eliana", "I", "Bennett", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EBENNETT", "NO PASSWORD SET" );'); 134 | // SQL.Add('insert into person values(0110010064, 1, Datetime("now"), 0, 1, "William", "J", "Murray", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "WMURRAY", "NO PASSWORD SET" );'); 135 | // SQL.Add('insert into person values(0110010065, 1, Datetime("now"), 0, 1, "Penelope", "K", "Rogers", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "PROGERS", "NO PASSWORD SET" );'); 136 | // SQL.Add('insert into person values(0110010066, 1, Datetime("now"), 0, 1, "Logan", "L", "Gray", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LGRAY", "NO PASSWORD SET" );'); 137 | // SQL.Add('insert into person values(0110010067, 1, Datetime("now"), 0, 1, "Eleanor", "M", "Price", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EPRICE", "NO PASSWORD SET" );'); 138 | // SQL.Add('insert into person values(0110010068, 1, Datetime("now"), 0, 1, "Michael", "N", "Ryan", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "MRYAN", "NO PASSWORD SET" );'); 139 | // SQL.Add('insert into person values(0110010069, 1, Datetime("now"), 0, 1, "Ivy", "O", "McDonald", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "IMCDONALD", "NO PASSWORD SET" );'); 140 | // SQL.Add('insert into person values(0110010070, 1, Datetime("now"), 0, 1, "Samuel", "P", "Russell", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "SRUSSELL", "NO PASSWORD SET" );'); 141 | // SQL.Add('insert into person values(0110010071, 1, Datetime("now"), 0, 1, "Elizabeth", "Q", "Richardson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ERICHARDSON", "NO PASSWORD SET" );'); 142 | // SQL.Add('insert into person values(0110010072, 1, Datetime("now"), 0, 1, "Muhammad", "R", "Harrison", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "MHARRISON", "NO PASSWORD SET" );'); 143 | // SQL.Add('insert into person values(0110010073, 1, Datetime("now"), 0, 1, "Riley", "S", "Sanders", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "RSANDERS", "NO PASSWORD SET" );'); 144 | // SQL.Add('insert into person values(0110110074, 1, Datetime("now"), 0, 1, "Waylon", "T", "Walsh", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "WWALSH", "NO PASSWORD SET" );'); 145 | // SQL.Add('insert into person values(0110110075, 1, Datetime("now"), 0, 1, "Isla", "U", "O''Connor", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "IOCONNOR", "NO PASSWORD SET" );'); 146 | // SQL.Add('insert into person values(0110210076, 1, Datetime("now"), 0, 1, "Ezekiel", "V", "Simpson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ESIMPSON", "NO PASSWORD SET" );'); 147 | // SQL.Add('insert into person values(0110210077, 1, Datetime("now"), 0, 1, "Abigail", "W", "Marshall", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "AMARSHALL", "NO PASSWORD SET" );'); 148 | // SQL.Add('insert into person values(0110310078, 1, Datetime("now"), 0, 1, "Jayden", "X", "Ross", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "JROSS", "NO PASSWORD SET" );'); 149 | // SQL.Add('insert into person values(0110310079, 1, Datetime("now"), 0, 1, "Nora", "Y", "Perry", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "NPERRY", "NO PASSWORD SET" );'); 150 | // SQL.Add('insert into person values(0110410080, 1, Datetime("now"), 0, 1, "Luke", "Z", "O''Brien", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LOBRIEN", "NO PASSWORD SET" );'); 151 | // SQL.Add('insert into person values(0110510081, 1, Datetime("now"), 0, 1, "Stella", "A", "Kennedy", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "SKENNEDY", "NO PASSWORD SET" );'); 152 | // SQL.Add('insert into person values(0110010082, 1, Datetime("now"), 0, 1, "Lincoln", "B", "Graham", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LGRAHAM", "NO PASSWORD SET" );'); 153 | // SQL.Add('insert into person values(0110610083, 1, Datetime("now"), 0, 1, "Grace", "C", "Foster", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "GFOSTER", "NO PASSWORD SET" );'); 154 | // SQL.Add('insert into person values(0110610084, 1, Datetime("now"), 0, 1, "Theo", "D", "Shaw", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "TSHAW", "NO PASSWORD SET" );'); 155 | // SQL.Add('insert into person values(0120610085, 1, Datetime("now"), 0, 1, "Zoey", "E", "Ellis", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ZELLIS", "NO PASSWORD SET" );'); 156 | // SQL.Add('insert into person values(0012710086, 1, Datetime("now"), 0, 1, "Jacob", "F", "Griffiths", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "JGRIFFITHS", "NO PASSWORD SET" );'); 157 | // SQL.Add('insert into person values(0120810087, 1, Datetime("now"), 0, 1, "Emily", "G", "Fisher", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EFISHER", "NO PASSWORD SET" );'); 158 | // SQL.Add('insert into person values(0120810088, 1, Datetime("now"), 0, 1, "Josiah", "H", "Butler", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "JBUTLER", "NO PASSWORD SET" );'); 159 | // SQL.Add('insert into person values(0120910089, 1, Datetime("now"), 0, 1, "Emilia", "I", "Reynolds", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EREYNOLDS", "NO PASSWORD SET" );'); 160 | // SQL.Add('insert into person values(0120910090, 1, Datetime("now"), 0, 1, "David", "J", "Fox", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "DFOX", "NO PASSWORD SET" );'); 161 | // SQL.Add('insert into person values(0120910091, 1, Datetime("now"), 0, 1, "Leilani", "K", "Robertson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LROBERTSON", "NO PASSWORD SET" );'); 162 | // SQL.Add('insert into person values(0120910092, 1, Datetime("now"), 0, 1, "Jaxon", "L", "Barnes", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "JBARNES", "NO PASSWORD SET" );'); 163 | // SQL.Add('insert into person values(0120910093, 1, Datetime("now"), 0, 1, "Everly", "M", "Chapman", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ECHAPMAN", "NO PASSWORD SET" );'); 164 | // SQL.Add('insert into person values(0120910094, 1, Datetime("now"), 0, 1, "Elias", "N", "Powell", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EPOWELL", "NO PASSWORD SET" );'); 165 | // SQL.Add('insert into person values(0120910095, 1, Datetime("now"), 0, 1, "Kinsley", "O", "Fraser", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "KFRASER", "NO PASSWORD SET" );'); 166 | // SQL.Add('insert into person values(0120910096, 1, Datetime("now"), 0, 1, "Julian", "P", "Mason", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "JMASON", "NO PASSWORD SET" );'); 167 | // SQL.Add('insert into person values(0120910097, 1, Datetime("now"), 0, 1, "Athena", "Q", "Henderson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "AHENDERSON", "NO PASSWORD SET" );'); 168 | // SQL.Add('insert into person values(0120910098, 1, Datetime("now"), 0, 1, "Theodore", "R", "Hamilton", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "THAMILTON", "NO PASSWORD SET" );'); 169 | // SQL.Add('insert into person values(0120910099, 1, Datetime("now"), 0, 1, "Delilah", "S", "Peterson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "DPETERSON", "NO PASSWORD SET" );'); 170 | // SQL.Add('insert into person values(0120910100, 1, Datetime("now"), 0, 1, "Isaiah", "T", "Howard", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "IHOWARD", "NO PASSWORD SET" );'); 171 | // SQL.Add('insert into person values(0120910101, 1, Datetime("now"), 0, 1, "Naomi", "U", "O''Sullivan", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "NOSULLIVAN", "NO PASSWORD SET" );'); 172 | // SQL.Add('insert into person values(0120910102, 1, Datetime("now"), 0, 1, "Matthew", "V", "Brooks", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "MBROOKS", "NO PASSWORD SET" );'); 173 | 174 | end; 175 | end; 176 | end; 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | -------------------------------------------------------------------------------- /ddl/person/person_sqlite.inc: -------------------------------------------------------------------------------- 1 | // [table] person 2 | // 3 | // Names were randomly generated - any resemblance to actual people, living or dead, etc. etc. 4 | // The first names were drawn from the following link 5 | // https://www.babycenter.com/baby-names/most-popular/top-baby-names 6 | // The last names were drawn from the following link 7 | // https://selectsurnames.com/top-300-surnames/ 8 | 9 | TableName := 'person'; 10 | 11 | if (DatabaseEngine = 'sqlite') then 12 | begin 13 | 14 | with Query1 do 15 | begin 16 | 17 | // Check if the table exists 18 | SQL.Clear; 19 | SQL.Add('select count(*) records from '+TableName+';'); 20 | try 21 | Open; 22 | LogEvent('...'+TableName+' ('+IntToStr(FieldByName('records').AsInteger)+' records)'); 23 | 24 | except on E:Exception do 25 | begin 26 | LogEvent('...'+TableName+' (CREATE)'); 27 | SQL.Clear; 28 | SQL.Add('create table if not exists '+TableName+' ( '+ 29 | ' person_id integer NOT NULL, '+ 30 | ' last_modified text NOT NULL, '+ 31 | ' last_modifier integer NOT NULL, '+ 32 | ' first_name text NOT NULL, '+ 33 | ' middle_name text , '+ 34 | ' last_name text NOT NULL, '+ 35 | ' birthdate text , '+ 36 | ' account_name text NOT NULL, '+ 37 | ' password_hash text NOT NULL, '+ 38 | ' CONSTRAINT constraint_name PRIMARY KEY (person_id), '+ 39 | ' UNIQUE(account_name)'+ 40 | ');' 41 | ); 42 | ExecSQL; 43 | 44 | // Try it again 45 | SQL.Clear; 46 | SQL.Add('select count(*) records from '+TableName+';'); 47 | Open; 48 | end; 49 | end; 50 | 51 | // Populate empty table with sample data 52 | if (FieldByName('records').AsInteger = 0) then 53 | begin 54 | LogEvent('...'+TableName+' (POPULATE)'); 55 | SQL.Clear; 56 | 57 | // Default password for SYSINSTALLER is "TMSWEBCore" - Passwords are SHA256 Hashes with a prefix of XData-Password: 58 | SQL.Add('insert into '+TableName+' values(0000000000, Datetime("now"), 0, "Original", "System", "Installer", "1971-05-25", "SYSINSTALLER", "f40154051817192179edb9dabaec056fdb0eacd977fb443b2fe69232cff23fd4" );'); // TMSWEBCore 59 | 60 | // Default password for SYSADMIN is not set - Deliberately. This account is just used when logging system-level functions, and can't be used to login. 61 | SQL.Add('insert into '+TableName+' values(0000000001, Datetime("now"), 0, "Server", "", "Administration", "1977-01-17", "SYSADMIN", "NO PASSWORD SET" );'); 62 | 63 | // Random data 64 | SQL.Add('insert into '+TableName+' values(0010010001, Datetime("now"), 0, "Olivia", "A", "Smith", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "OSMITH", "b2cc479bdcabf5bb5964321e9cdd6808c100289e3dcd68fb911d5145423ba706" );'); // hellothere 65 | SQL.Add('insert into '+TableName+' values(0010020002, Datetime("now"), 0, "Liam", "B", "Jones", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LJONES", "NO PASSWORD SET" );'); 66 | SQL.Add('insert into '+TableName+' values(0010030003, Datetime("now"), 0, "Emma", "C", "Williams", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EWILLIAMS", "NO PASSWORD SET" );'); 67 | SQL.Add('insert into '+TableName+' values(0010040004, Datetime("now"), 0, "Noah", "D", "Brown", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "NBROWN", "NO PASSWORD SET" );'); 68 | SQL.Add('insert into '+TableName+' values(0010050005, Datetime("now"), 0, "Amelia", "E", "Johnson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "AJOHNSON", "NO PASSWORD SET" );'); 69 | SQL.Add('insert into '+TableName+' values(0010060006, Datetime("now"), 0, "Oliver", "F", "Taylor", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "OTAYLOR", "NO PASSWORD SET" );'); 70 | SQL.Add('insert into '+TableName+' values(0010070007, Datetime("now"), 0, "Ava", "G", "Davis", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ADAVIS", "NO PASSWORD SET" );'); 71 | SQL.Add('insert into '+TableName+' values(0010080008, Datetime("now"), 0, "Elijah", "H", "Miller", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EMILLER", "NO PASSWORD SET" );'); 72 | SQL.Add('insert into '+TableName+' values(0010090009, Datetime("now"), 0, "Sophia", "I", "Wilson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "SWILSON", "NO PASSWORD SET" );'); 73 | SQL.Add('insert into '+TableName+' values(0010000010, Datetime("now"), 0, "Mateo", "J", "Thompson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "MTHOMPSON", "NO PASSWORD SET" );'); 74 | SQL.Add('insert into '+TableName+' values(0010010011, Datetime("now"), 0, "Isabella", "K", "Thomas", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ITHOMAS", "NO PASSWORD SET" );'); 75 | SQL.Add('insert into '+TableName+' values(0010020012, Datetime("now"), 0, "Lucas", "L", "Anderson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LANDERSON", "NO PASSWORD SET" );'); 76 | SQL.Add('insert into '+TableName+' values(0010030012, Datetime("now"), 0, "Luna", "M", "White", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LWHITE", "NO PASSWORD SET" );'); 77 | SQL.Add('insert into '+TableName+' values(0020040013, Datetime("now"), 0, "Levi", "N", "Martin", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LMARTIN", "NO PASSWORD SET" );'); 78 | SQL.Add('insert into '+TableName+' values(0020050014, Datetime("now"), 0, "Mia", "O", "Moore", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "MMOORE", "NO PASSWORD SET" );'); 79 | SQL.Add('insert into '+TableName+' values(0020060015, Datetime("now"), 0, "Asher", "P", "Jackson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "AJACKSON", "NO PASSWORD SET" );'); 80 | SQL.Add('insert into '+TableName+' values(0020070016, Datetime("now"), 0, "Charlotte", "Q", "Clark", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "CCLARK", "NO PASSWORD SET" );'); 81 | SQL.Add('insert into '+TableName+' values(0020080017, Datetime("now"), 0, "James", "R", "Walker", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "JWALKER", "NO PASSWORD SET" );'); 82 | SQL.Add('insert into '+TableName+' values(0020090018, Datetime("now"), 0, "Evelyn", "S", "Evans", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EEVANS", "NO PASSWORD SET" );'); 83 | SQL.Add('insert into '+TableName+' values(0020000019, Datetime("now"), 0, "Leo", "T", "Lee", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LLEE", "NO PASSWORD SET" );'); 84 | SQL.Add('insert into '+TableName+' values(0020010020, Datetime("now"), 0, "Harper", "U", "Lewis", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "HLEWIS", "NO PASSWORD SET" );'); 85 | SQL.Add('insert into '+TableName+' values(0030020021, Datetime("now"), 0, "Grayson", "V", "King", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "GKING", "NO PASSWORD SET" );'); 86 | SQL.Add('insert into '+TableName+' values(0030030022, Datetime("now"), 0, "Scarlett", "W", "Harris", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "SHARRIS", "NO PASSWORD SET" );'); 87 | SQL.Add('insert into '+TableName+' values(0030040023, Datetime("now"), 0, "Ezra", "X", "Roberts", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EROBERTS", "NO PASSWORD SET" );'); 88 | SQL.Add('insert into '+TableName+' values(0030050024, Datetime("now"), 0, "Nova", "Y", "Robinson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "NROBINSON", "NO PASSWORD SET" );'); 89 | SQL.Add('insert into '+TableName+' values(0030060025, Datetime("now"), 0, "Luca", "Z", "Wright", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LWRIGHT", "NO PASSWORD SET" );'); 90 | SQL.Add('insert into '+TableName+' values(0030070026, Datetime("now"), 0, "Aurora", "A", "Young", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "AYOUNG", "NO PASSWORD SET" );'); 91 | SQL.Add('insert into '+TableName+' values(0030080027, Datetime("now"), 0, "Ethan", "B", "Scott", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ESCOTT", "NO PASSWORD SET" );'); 92 | SQL.Add('insert into '+TableName+' values(0030090028, Datetime("now"), 0, "Ella", "C", "Reed", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EREED", "NO PASSWORD SET" );'); 93 | SQL.Add('insert into '+TableName+' values(0040000029, Datetime("now"), 0, "Aiden", "D", "Murphy", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "AMURPHY", "NO PASSWORD SET" );'); 94 | SQL.Add('insert into '+TableName+' values(0040010031, Datetime("now"), 0, "Mila", "E", "Hill", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "MHILL", "NO PASSWORD SET" );'); 95 | SQL.Add('insert into '+TableName+' values(0040020032, Datetime("now"), 0, "Wyatt", "F", "Wood", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "WWOOD", "NO PASSWORD SET" );'); 96 | SQL.Add('insert into '+TableName+' values(0050030033, Datetime("now"), 0, "Aria", "G", "Hall", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "AHALL", "NO PASSWORD SET" );'); 97 | SQL.Add('insert into '+TableName+' values(0050040034, Datetime("now"), 0, "Sebastian", "H", "Green", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "SGREEN", "NO PASSWORD SET" );'); 98 | SQL.Add('insert into '+TableName+' values(0050050035, Datetime("now"), 0, "Ellie", "I", "Allen", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EALLEN", "NO PASSWORD SET" );'); 99 | SQL.Add('insert into '+TableName+' values(0070060036, Datetime("now"), 0, "Benjamin", "J", "Kelly", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "BKELLY", "NO PASSWORD SET" );'); 100 | SQL.Add('insert into '+TableName+' values(0070070037, Datetime("now"), 0, "Gianna", "K", "Campbell", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "GCAMPBELL", "NO PASSWORD SET" );'); 101 | SQL.Add('insert into '+TableName+' values(0070080038, Datetime("now"), 0, "Mason", "L", "Edwards", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "MEDWARDS", "NO PASSWORD SET" );'); 102 | SQL.Add('insert into '+TableName+' values(0080090039, Datetime("now"), 0, "Sofia", "M", "Adams", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "SADAMS", "NO PASSWORD SET" );'); 103 | SQL.Add('insert into '+TableName+' values(0080000040, Datetime("now"), 0, "Henry", "N", "Baker", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "HBAKER", "NO PASSWORD SET" );'); 104 | SQL.Add('insert into '+TableName+' values(0080010041, Datetime("now"), 0, "Violet", "O", "Watson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "VWATSON", "NO PASSWORD SET" );'); 105 | SQL.Add('insert into '+TableName+' values(0080010042, Datetime("now"), 0, "Hudson", "P", "Mitchell", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "HMITCHELL", "NO PASSWORD SET" );'); 106 | SQL.Add('insert into '+TableName+' values(0090010043, Datetime("now"), 0, "Layla", "Q", "Phillips", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LPHILLIPS", "NO PASSWORD SET" );'); 107 | SQL.Add('insert into '+TableName+' values(0090010044, Datetime("now"), 0, "Jack", "R", "Cooper", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "JCOOPER", "NO PASSWORD SET" );'); 108 | SQL.Add('insert into '+TableName+' values(0090020045, Datetime("now"), 0, "Willow", "S", "Turner", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "WTURNER", "NO PASSWORD SET" );'); 109 | SQL.Add('insert into '+TableName+' values(0090020046, Datetime("now"), 0, "Jackson", "T", "Morris", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "JMORRIS", "NO PASSWORD SET" );'); 110 | SQL.Add('insert into '+TableName+' values(0090030047, Datetime("now"), 0, "Lily", "U", "Carter", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LCARTER", "NO PASSWORD SET" );'); 111 | SQL.Add('insert into '+TableName+' values(0090040048, Datetime("now"), 0, "Owen", "V", "Morgan", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "OMORGAN", "NO PASSWORD SET" );'); 112 | SQL.Add('insert into '+TableName+' values(0090040049, Datetime("now"), 0, "Hazel", "W", "Hughes", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "HHUGHES", "NO PASSWORD SET" );'); 113 | SQL.Add('insert into '+TableName+' values(0090050051, Datetime("now"), 0, "Daniel", "X", "Cook", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "DCOOK", "NO PASSWORD SET" );'); 114 | SQL.Add('insert into '+TableName+' values(0090060052, Datetime("now"), 0, "Camila", "Y", "Ward", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "CWARD", "NO PASSWORD SET" );'); 115 | SQL.Add('insert into '+TableName+' values(0090060053, Datetime("now"), 0, "Alexander", "Z", "Collins", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ACOLLINS", "NO PASSWORD SET" );'); 116 | SQL.Add('insert into '+TableName+' values(0090070054, Datetime("now"), 0, "Avery", "A", "James", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "AJAMES", "NO PASSWORD SET" );'); 117 | SQL.Add('insert into '+TableName+' values(0100080055, Datetime("now"), 0, "Maverick", "B", "Parker", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "MPARKER", "NO PASSWORD SET" );'); 118 | SQL.Add('insert into '+TableName+' values(0100080056, Datetime("now"), 0, "Chloe", "C", "Bell", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "CBELL", "NO PASSWORD SET" );'); 119 | SQL.Add('insert into '+TableName+' values(0100080057, Datetime("now"), 0, "Kai", "D", "Nelson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "KNELSON", "NO PASSWORD SET" );'); 120 | SQL.Add('insert into '+TableName+' values(0110080058, Datetime("now"), 0, "Elena", "E", "Stewart", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ESTEWART", "NO PASSWORD SET" );'); 121 | SQL.Add('insert into '+TableName+' values(0110090059, Datetime("now"), 0, "Gabriel", "F", "Bailey", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "GBAILEY", "NO PASSWORD SET" );'); 122 | SQL.Add('insert into '+TableName+' values(0110090061, Datetime("now"), 0, "Paisley", "G", "Stevens", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "PSTEVENS", "NO PASSWORD SET" );'); 123 | SQL.Add('insert into '+TableName+' values(0110090062, Datetime("now"), 0, "Carter", "H", "Cox", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "CCOX", "NO PASSWORD SET" );'); 124 | SQL.Add('insert into '+TableName+' values(0110000063, Datetime("now"), 0, "Eliana", "I", "Bennett", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EBENNETT", "NO PASSWORD SET" );'); 125 | SQL.Add('insert into '+TableName+' values(0110010064, Datetime("now"), 0, "William", "J", "Murray", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "WMURRAY", "NO PASSWORD SET" );'); 126 | SQL.Add('insert into '+TableName+' values(0110010065, Datetime("now"), 0, "Penelope", "K", "Rogers", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "PROGERS", "NO PASSWORD SET" );'); 127 | SQL.Add('insert into '+TableName+' values(0110010066, Datetime("now"), 0, "Logan", "L", "Gray", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LGRAY", "NO PASSWORD SET" );'); 128 | SQL.Add('insert into '+TableName+' values(0110010067, Datetime("now"), 0, "Eleanor", "M", "Price", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EPRICE", "NO PASSWORD SET" );'); 129 | SQL.Add('insert into '+TableName+' values(0110010068, Datetime("now"), 0, "Michael", "N", "Ryan", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "MRYAN", "NO PASSWORD SET" );'); 130 | SQL.Add('insert into '+TableName+' values(0110010069, Datetime("now"), 0, "Ivy", "O", "McDonald", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "IMCDONALD", "NO PASSWORD SET" );'); 131 | SQL.Add('insert into '+TableName+' values(0110010070, Datetime("now"), 0, "Samuel", "P", "Russell", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "SRUSSELL", "NO PASSWORD SET" );'); 132 | SQL.Add('insert into '+TableName+' values(0110010071, Datetime("now"), 0, "Elizabeth", "Q", "Richardson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ERICHARDSON", "NO PASSWORD SET" );'); 133 | SQL.Add('insert into '+TableName+' values(0110010072, Datetime("now"), 0, "Muhammad", "R", "Harrison", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "MHARRISON", "NO PASSWORD SET" );'); 134 | SQL.Add('insert into '+TableName+' values(0110010073, Datetime("now"), 0, "Riley", "S", "Sanders", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "RSANDERS", "NO PASSWORD SET" );'); 135 | SQL.Add('insert into '+TableName+' values(0110110074, Datetime("now"), 0, "Waylon", "T", "Walsh", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "WWALSH", "NO PASSWORD SET" );'); 136 | SQL.Add('insert into '+TableName+' values(0110110075, Datetime("now"), 0, "Isla", "U", "O''Connor", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "IOCONNOR", "NO PASSWORD SET" );'); 137 | SQL.Add('insert into '+TableName+' values(0110210076, Datetime("now"), 0, "Ezekiel", "V", "Simpson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ESIMPSON", "NO PASSWORD SET" );'); 138 | SQL.Add('insert into '+TableName+' values(0110210077, Datetime("now"), 0, "Abigail", "W", "Marshall", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "AMARSHALL", "NO PASSWORD SET" );'); 139 | SQL.Add('insert into '+TableName+' values(0110310078, Datetime("now"), 0, "Jayden", "X", "Ross", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "JROSS", "NO PASSWORD SET" );'); 140 | SQL.Add('insert into '+TableName+' values(0110310079, Datetime("now"), 0, "Nora", "Y", "Perry", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "NPERRY", "NO PASSWORD SET" );'); 141 | SQL.Add('insert into '+TableName+' values(0110410080, Datetime("now"), 0, "Luke", "Z", "O''Brien", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LOBRIEN", "NO PASSWORD SET" );'); 142 | SQL.Add('insert into '+TableName+' values(0110510081, Datetime("now"), 0, "Stella", "A", "Kennedy", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "SKENNEDY", "NO PASSWORD SET" );'); 143 | SQL.Add('insert into '+TableName+' values(0110010082, Datetime("now"), 0, "Lincoln", "B", "Graham", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LGRAHAM", "NO PASSWORD SET" );'); 144 | SQL.Add('insert into '+TableName+' values(0110610083, Datetime("now"), 0, "Grace", "C", "Foster", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "GFOSTER", "NO PASSWORD SET" );'); 145 | SQL.Add('insert into '+TableName+' values(0110610084, Datetime("now"), 0, "Theo", "D", "Shaw", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "TSHAW", "NO PASSWORD SET" );'); 146 | SQL.Add('insert into '+TableName+' values(0120610085, Datetime("now"), 0, "Zoey", "E", "Ellis", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ZELLIS", "NO PASSWORD SET" );'); 147 | SQL.Add('insert into '+TableName+' values(0012710086, Datetime("now"), 0, "Jacob", "F", "Griffiths", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "JGRIFFITHS", "NO PASSWORD SET" );'); 148 | SQL.Add('insert into '+TableName+' values(0120810087, Datetime("now"), 0, "Emily", "G", "Fisher", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EFISHER", "NO PASSWORD SET" );'); 149 | SQL.Add('insert into '+TableName+' values(0120810088, Datetime("now"), 0, "Josiah", "H", "Butler", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "JBUTLER", "NO PASSWORD SET" );'); 150 | SQL.Add('insert into '+TableName+' values(0120910089, Datetime("now"), 0, "Emilia", "I", "Reynolds", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EREYNOLDS", "NO PASSWORD SET" );'); 151 | SQL.Add('insert into '+TableName+' values(0120910090, Datetime("now"), 0, "David", "J", "Fox", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "DFOX", "NO PASSWORD SET" );'); 152 | SQL.Add('insert into '+TableName+' values(0120910091, Datetime("now"), 0, "Leilani", "K", "Robertson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "LROBERTSON", "NO PASSWORD SET" );'); 153 | SQL.Add('insert into '+TableName+' values(0120910092, Datetime("now"), 0, "Jaxon", "L", "Barnes", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "JBARNES", "NO PASSWORD SET" );'); 154 | SQL.Add('insert into '+TableName+' values(0120910093, Datetime("now"), 0, "Everly", "M", "Chapman", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "ECHAPMAN", "NO PASSWORD SET" );'); 155 | SQL.Add('insert into '+TableName+' values(0120910094, Datetime("now"), 0, "Elias", "N", "Powell", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "EPOWELL", "NO PASSWORD SET" );'); 156 | SQL.Add('insert into '+TableName+' values(0120910095, Datetime("now"), 0, "Kinsley", "O", "Fraser", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "KFRASER", "NO PASSWORD SET" );'); 157 | SQL.Add('insert into '+TableName+' values(0120910096, Datetime("now"), 0, "Julian", "P", "Mason", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "JMASON", "NO PASSWORD SET" );'); 158 | SQL.Add('insert into '+TableName+' values(0120910097, Datetime("now"), 0, "Athena", "Q", "Henderson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "AHENDERSON", "NO PASSWORD SET" );'); 159 | SQL.Add('insert into '+TableName+' values(0120910098, Datetime("now"), 0, "Theodore", "R", "Hamilton", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "THAMILTON", "NO PASSWORD SET" );'); 160 | SQL.Add('insert into '+TableName+' values(0120910099, Datetime("now"), 0, "Delilah", "S", "Peterson", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "DPETERSON", "NO PASSWORD SET" );'); 161 | SQL.Add('insert into '+TableName+' values(0120910100, Datetime("now"), 0, "Isaiah", "T", "Howard", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "IHOWARD", "NO PASSWORD SET" );'); 162 | SQL.Add('insert into '+TableName+' values(0120910101, Datetime("now"), 0, "Naomi", "U", "O''Sullivan", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "NOSULLIVAN", "NO PASSWORD SET" );'); 163 | SQL.Add('insert into '+TableName+' values(0120910102, Datetime("now"), 0, "Matthew", "V", "Brooks", "'+FormatDateTime('yyyy-mm-dd',Now-(20*365)-Random(50 * 365))+'", "MBROOKS", "NO PASSWORD SET" );'); 164 | 165 | ExecSQL; 166 | end; 167 | end; 168 | end; 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | --------------------------------------------------------------------------------