├── 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 | 
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 | 
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 | [](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 |
--------------------------------------------------------------------------------