Skip to content

Commit 993cfed

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
Merge origin/staging into feat/oracle-netsuite-integration
2 parents 4d0a523 + cb28090 commit 993cfed

247 files changed

Lines changed: 11501 additions & 4212 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agiloft-spec/SPEC.md

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# Agiloft REST API — authoritative spec (transcribed from live help.agiloft.com)
2+
3+
## GLOBAL / API Security
4+
- Every REST call should contain credentials as `login={login}&password={password}`.
5+
- POST-body credentials supported ONLY for: /ewws/EWRead, /ewws/EWSelect, /ewws/EWCreate,
6+
/ewws/EWUpdate, /ewws/EWDelete. ("avoid passing the login or password ... by using POST
7+
instead of GET to pass the parameters in the request body")
8+
- JWT: EWLogin returns a token; "The token can then be used in an Authorization request header,
9+
prefixed by the authentication scheme, instead of including the login and password parameters
10+
in following requests." Default scheme Bearer, expiry 15 min (token_expires_in, max 60).
11+
- Statefulness: pattern is "login, do multiple calls, logout". EWLogout terminates the session
12+
associated with the token passed in the Authorization header.
13+
- DELAYS: every REST call has a delay after completion, default 1 second, global var WSDelay.
14+
- Group must be REST-enabled (Setup > System > Manage Web Services > Groups allowed for REST),
15+
else 403.
16+
17+
### General error codes (selected)
18+
- 400 "There is no permissions to access this resource"
19+
- 400 "One has to specify $login and $password parameters or authentication token." <-- BOTH auth methods provided
20+
- 400 "Token is expired"
21+
- 400 "One has to specify $login, $password parameters or use $genhotlink/$genproject pair ..." <-- no auth
22+
- 401 "Wrong Authorization data" <-- invalid authorization scheme
23+
- 401 "Token is blocked" / "User is blocked"
24+
- 403 invalid login attempt / "Authentication failed." / "Invalid login/password combination ..."
25+
- 500 "No active session found for current token"
26+
- 400 "Unable to identify KB with name" / "Cannot find specified knowledgebase: <KBName>"
27+
- 400 "One has to specify $table, $KB, $lang parameters or use $genhotlink/$genproject pair ..."
28+
- 400 "Wrong combination of access token and KB name. No access to data in KB"
29+
- 400 "One has to specify id value."
30+
- 400 "Project <projectId> has not been found" / "Table <tableId> has not been found"
31+
- 400 "No value for 'field' parameter specified."
32+
- 403 "not allowed, please check logs" (IP blacklist)
33+
- 405 "HTTP method <methodName> is not supported by this URL"
34+
35+
## URL CONVENTIONS
36+
- KB names and table names are CASE SENSITIVE (use Logical Table Name).
37+
- REST style: `/ewws/REST/{kbName}/{table}[/{id}]?$login={login}&password={password}&lang={lang}&...`
38+
(omit /{id} for Create)
39+
- GET/POST style: `/ewws/{operation}?$KB={kbName}&$table={table}&$login={login}&password={password}&lang={lang}&...`
40+
"The parameters of the POST request can be inserted into the body of the request to conceal the user credentials."
41+
- Return values: JavaScript eval() form, all names prefixed `EWREST_`. Empty fields returned as nulls.
42+
- JSON decorator: append `/.json` -> `{"success":true,"message":"","result":{...}}`
43+
optional `err_code_resp=1` for real status codes instead of always 200.
44+
- Async decorator: `/ewws/async/EWCreate?...` or `/ewws/EWCreate/.async?...` (EWCreate, EWUpdate, EWDelete)
45+
- Redirect decorator: `/ewws/redirect/...` with $exiturl and $errorurl
46+
- Decorators chain left to right.
47+
48+
## OPERATIONS TABLE (endpoint / methods / returns)
49+
| Create | GET/POST | /ewws/EWCreate | ID of new record |
50+
| Read | GET/POST | /ewws/EWRead | encoded record info |
51+
| Update | GET/POST | /ewws/EWUpdate | encoded record info after update |
52+
| Delete | GET/POST/DELETE | /ewws/EWDelete | nothing |
53+
| Select | GET/POST | /ewws/EWSelect | list of record ids + length |
54+
| Login | GET/POST | /ewws/EWLogin | session token, expiration, auth scheme |
55+
| Logout | GET/POST | /ewws/EWLogout | nothing |
56+
| Search | GET/POST | /ewws/EWSearch | saved search + ad hoc |
57+
| Attach | PUT | /ewws/EWAttach | total files attached |
58+
| RemoveAttached | GET/POST | /ewws/EWRemoveAttachment | nothing |
59+
| RetrieveAttached | GET/POST | /ewws/EWRetrieve | attachment |
60+
| Lock | GET/PUT/DELETE | /ewws/EWLock | lock status |
61+
| AttachInfo | GET/POST | /ewws/EWAttachInfo | attachment info |
62+
| Hotlink | POST | /ewws/EWHotlinks | hotlink |
63+
| Table | GET/POST | /ewws/EWTable | all tables and fields |
64+
| Async Status | GET/POST | /ewws/EWAsyncStatus | execution status |
65+
| GetChoiceLineId | GET | /ewws/GetChoiceLineID | internal id for a choice value |
66+
| Action Button | POST | /ewws/EWActionButton | runs an action button |
67+
| Saved Search | GET/POST | /ewws/EWSavedSearch | saved search details |
68+
69+
NOTE: the operations table lists `/ewws/GetChoiceLineID` and `/ewws/EWActionButton`, but the
70+
detail pages use `/ewws/EWGetChoiceLineId` and `/ewws/async/EWActionButton`. Detail pages carry
71+
working curl examples; the table does not.
72+
73+
## EWLogin
74+
POST /ewws/EWLogin, Content-Type: plain/text.
75+
Params (CAN BE FILLED TO REQUEST BODY): $KB, $login, $password, $lang (default en).
76+
Response JSON: access_token, refresh_token, expiration_time_unit, expires_in, authentication_scheme
77+
(default "Bearer " — NOTE TRAILING SPACE in examples).
78+
Example: POST https://server/ewws/EWLogin?$login=user&$password=passwd&$KB=Demo&$lang=en
79+
Refresh: POST /ewws/EWLogin with Authorization header + body refresh_token=...
80+
Logout: POST or GET /ewws/EWLogout with Authorization header; params $KB, $lang.
81+
Errors: 400 no refresh_token / wrong refresh_token; 401 Refresh Token is expired;
82+
403 "User <userName> lacks permission log in"
83+
84+
## EWCreate
85+
GET/POST /ewws/EWCreate. Content-Type application/x-www-form-urlencoded.
86+
Params in URL/body: $KB, $table, $login, $password, $lang + field values.
87+
Returns: EWREST_id='353';
88+
Async-compatible.
89+
Errors: 400 "Wrong format/value pointed to <columnName>"; linked-field errors.
90+
91+
## EWRead
92+
GET/POST /ewws/EWRead. Params: $KB,$table,$login,$password,$lang,id
93+
Alternative to id: $searchSQL=ext_id='a0B2c345' (must match exactly one record).
94+
Returns EWREST_<field>='<value>'; lines including EWREST_id.
95+
Errors: 400 "no data found for id range(s)"
96+
97+
## EWUpdate
98+
GET/POST /ewws/EWUpdate. Params: ... id=358 + field values.
99+
Alternative to id: $searchSQL.
100+
$operationHints=NOLOCK forces update on a locked record (URL or POST body).
101+
Returns full updated record as EWREST_ lines.
102+
Errors: 400 "One has to specify id or searchSQL value."; constraint violations.
103+
104+
## EWDelete
105+
GET/POST/DELETE /ewws/EWDelete. Params: ... id=358 & deleteRule=...
106+
deleteRule values: ERROR_IF_DEPENDANTS, APPLY_DELETE_WHERE_POSSIBLE,
107+
DELETE_WHERE_POSSIBLE_OTHERWISE_UNLINK, APPLY_UNLINK,
108+
UNLINK_WHERE_POSSIBLE_OTHERWISE_DELETE, REPLACE_WITH_ANOTHER (needs `subs`).
109+
Returns nothing on success; error message on failure.
110+
Errors: 400 "One has to specify deleteRule, id and substitute values.";
111+
409 "Operation cannot be done. Record has <n> dependants" etc.
112+
113+
## EWSelect
114+
GET/POST /ewws/EWSelect. Params: ... where=<sql where clause>
115+
Queries in the URL must use %N equivalent operators.
116+
SQL uses dbname column names. Choice values via GetChoiceLineId.
117+
Limit via DB syntax e.g. "limit 0,200". No sort control (use EWSearch).
118+
Returns: EWREST_id_length = '3'; EWREST_id_0 = '150'; ...
119+
Empty: EWREST_id_length = '0';
120+
Errors: 400 "Error <error> parsing the query <query>"; 500 "Error executing query, please consult logs"
121+
122+
## EWSearch
123+
GET/POST /ewws/EWSearch. Content-Type x-www-form-urlencoded.
124+
Params: $KB,$table,$login,$password,$lang, search=<saved search label>, query=<ad hoc>,
125+
field=<repeated>, page, limit
126+
Operators: = %3D | != %21%3D | ~= %7E%3D (contains) | && %26%26 | || %7C%7C | < <= > >=
127+
Surround each search value in single quotes; if a field label contains spaces, quote the label too.
128+
Empty fields designated with null.
129+
Returns: EWREST_length = '4'; then EWREST_<field>_<i>='value';
130+
Empty: EWREST_id_length = '0';
131+
Pagination: page starts 0. limit 0 = ALL records on page 0.
132+
"The REST interface creates a new session and performs an explicit logout for each call.
133+
As such, though pagination is available, the query will always be rebuilt and rerun."
134+
ALREST EQUIVALENT (documented in this page):
135+
curl --location 'http://localhost:8080/ewws/alrest/CLM Template/case/search?lang=en' \
136+
--data '{ "field": ["id","summary"], "query": "summary=test" }'
137+
Errors: 400 "No search <savedSearch> for table <tableName>"; 400 "No column <columnName> in table <tableName>"
138+
139+
## EWAttach
140+
PUT /ewws/EWAttach. Content-Type multipart/form-data (file in body).
141+
Params: $KB,$table,$login,$password,id,field,fileName
142+
Returns: EWREST_someField.length='1'; (key is `<fieldName>.length`)
143+
Errors: 400 "No value for 'fileName' parameter specified."; forbidden extension etc.
144+
145+
## EWAttachInfo
146+
GET/POST /ewws/EWAttachInfo. Example uses /.json
147+
URL: /ewws/EWAttachInfo/.json?$KB=..&$table=..&$lang=en&field=attached_file&$login=..&$password=..&id=1
148+
Returns: {"success":true,"message":"","result":[{"fileName":"..","size":22126,"filePosition":0}]}
149+
150+
## EWRemoveAttachment
151+
GET/POST /ewws/EWRemoveAttachment. Params: $KB,$table,$login,$password,id,field,filePosition
152+
Returns: the number of attached files remaining in the field.
153+
154+
## EWRetrieve
155+
GET/POST /ewws/EWRetrieve. Params: $KB,$table,$login,$password,id,field,filePosition
156+
Returns: file content in body. Content-Type = the type used when attaching.
157+
158+
## EWLock
159+
GET (status) / PUT (lock) / DELETE (unlock) /ewws/EWLock
160+
Params: $KB,$table,$lang,id (+ $login/$password OR OAuth/JWT). `force` (any value) on DELETE only.
161+
Success JSON: {"id":18,"table_id":2788,"locked_by":"admin","lock_status":"LOCKED","lock_expires_in_minutes":25}
162+
Unlock: {"id":18,"table_id":2788,"lock_status":"NO_LOCK"}
163+
lock_status values: NO_LOCK | LOCKED
164+
Failure JSON: {error, error_description}; codes BAD_REQUEST/UNAUTHORIZED/FORBIDDEN/CONFLICT/SERVER_ERROR
165+
166+
## EWGetChoiceLineId
167+
GET. Detail page URL: /ewws/EWGetChoiceLineId?$KB=..&$login=..&$password=..&$table=case&$lang=en&field=priority&value=High
168+
Returns: EWREST_choiceLineId = '1';
169+
No match -> HTTP 400.
170+
Errors: 400 "No choice line found for value <fieldName>"; 500 unexpected
171+
172+
## EWActionButton
173+
POST ONLY. URL: /ewws/async/EWActionButton?$KB=..&$login=..&$password=..&$lang=en&$table=case&name=ab_field&id=82
174+
Returns: EWREST_id='82'; EWREST_EWCALLBACK_ID='10100_1';
175+
Compatible with EWAsyncStatus.
176+
Errors: 400 "Wrong value for 'sequence' parameter"; 400 "No information for requested column <fieldName>"
177+
178+
## EWAsyncStatus
179+
GET or POST /ewws/EWAsyncStatus. Params: $KB,$login,$password,$lang,$table,callback_id
180+
Returns response CODE only (empty body): 200 completed, 201 queued, 202 in progress,
181+
501 failed, 523 no info for callback id.
182+
183+
## EWSavedSearch
184+
GET/POST /ewws/EWSavedSearch/.json (JSON is the ONLY output; /.json is MANDATORY)
185+
URL must include the logical table name. For POST the table param may be in the body.
186+
MUST be used with EWLogin or OAuth 2.0 authorization. Never asynchronous.
187+
Example: https://localhost:8080/ewws/EWSavedSearch/.json?$table=contract
188+
Returns: {"success":true,"message":"","result":[{"label":"...","name":"...","id":265185,"description":""}]}
189+
190+
## EWTable
191+
GET/POST /ewws/EWTable/.json. Requests x-www-form-urlencoded; returns application/json.
192+
MUST be used with EWLogin or OAuth 2.0 authorization. Never asynchronous.
193+
Params: $KB; optional `table` (plain, NOT $table) = logical name e.g. table=contacts;
194+
includelinkedinfo=true; skipColumnsInfo=true
195+
Example: https://localhost:8080/ewws/EWTable/.json?$KB=Demo&includelinkedinfo=true
196+
Returns: {"success":true,"message":"","result":{"tables":[{"label":"WMI Sample","logicalName":"wmi_sample",
197+
"fields":[{"columnLabel":"ID","columnName":"id","columnType":"BIGINT","columnTypeDomain":"swautoincrementfield"},
198+
{"columnLabel":"Updated By","columnName":"_1794_full_name","columnType":"VARCHAR",
199+
"columnTypeDomain":"swshorttextfield","isLinked":true,
200+
"linkedInfo":[{"linkedTable":"contacts","linkedColumn":"full_name","linkedDao":"_dao3_link0"}],
201+
"textFieldType":"text/plain"}]}]}}
202+
Returns for fields: name, label, type, and required flag. Linked fields return only source table info.
203+
Action buttons, related tables, embedded search results, embedded communications NOT supported.
204+
205+
## EWUpsert
206+
POST /ewws/EWUpsert. Content-Type application/x-www-form-urlencoded.
207+
Authentication: Required ($login and $password).
208+
Async support: Yes (EWAsyncStatus) via $async.
209+
System params: $KB*, $table*, $login*, $password*, $match*, $lang, $async
210+
Remaining params are record fields.
211+
Matching: no match -> create; one match -> update; multiple -> error.
212+
Example body: $KB=Demo / $table=contacts.employees / $login=admin / $password=qwerty /
213+
$lang=en / $match=_login / _login=jdoe / first_name=John / ...
214+
Returns: EWREST_id='353';
215+
Status: 200 updated, 201 created, 202 accepted(async), 400, 401, 403, 404,
216+
409 Conflict (multiple matching records), 500
217+
218+
## EWNLPSearch
219+
Content-Types: application/json, application/x-www-form-urlencoded
220+
Params: $KB*, $login*, $password*, $lang, field[]* , nlp_query*, page, limit
221+
Returns same format as REST-Search.
222+
Example return: {"success":true,"message":"","result":[{"company_name":"...","id":31,...}]}
223+
224+
## DATA ENCODING
225+
- Choice fields: text value as in GUI (`&country=USA`). For EWSelect ad hoc queries use
226+
GetChoiceLineId IDs instead.
227+
- Multi-choice: repeated key/value pairs.
228+
- Elapsed time: "days:hours:minutes:seconds" e.g. "0:1:35:15"
229+
- Linked fields: Query By Example with ':' qualifier (`&company_name=:Agiloft` or
230+
`&company_name=Company:Agiloft`). ':' and '?' in values escaped with backslash.
231+
SQL sub-select form uses '?' qualifier.
232+
- File/image fields: POST with enctype multipart/form-data; form field name = file field name;
233+
`fieldName$overwrite` to replace rather than add.

.github/workflows/ci.yml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ jobs:
7272
# Detect shell-code changes on dev/staging pushes. Web-only changes never
7373
# need a desktop build (installed shells load the web app live); changes to
7474
# the Electron app or the bridge packages trigger a per-env prerelease build
75-
# (dev → alpha channel, staging → beta) that the env's update feed
75+
# (dev → dev stream, staging → staging) that the env's update feed
7676
# (/api/desktop/update) starts offering automatically.
7777
detect-desktop-changes:
7878
name: Detect Desktop Changes
@@ -707,10 +707,10 @@ jobs:
707707
secrets: inherit
708708

709709
# Per-env desktop prereleases: a dev/staging push that touches shell code
710-
# publishes a channel-tagged GitHub prerelease (vX.Y.Z-alpha.N from dev,
711-
# vX.Y.Z-beta.N from staging). Each environment's /api/desktop/update feed
712-
# offers only its channel, so dev-pointed shells pick up alpha builds,
713-
# staging-pointed shells beta builds, and prod-pointed shells stable
710+
# publishes an environment-tagged GitHub prerelease (vX.Y.Z-dev.N from dev,
711+
# vX.Y.Z-staging.N from staging). Each environment's /api/desktop/update feed
712+
# offers only its stream, so dev-pointed shells pick up dev builds,
713+
# staging-pointed shells staging builds, and prod-pointed shells stable
714714
# releases — independently. Unlike stable releases, prereleases build even
715715
# before the Apple signing secrets exist — unsigned, so the update pipeline
716716
# is testable end to end; installed shells detect the missing Developer ID
@@ -738,7 +738,7 @@ jobs:
738738
GH_REPO: ${{ github.repository }}
739739
SIGNED: ${{ needs.check-desktop-signing.outputs.configured }}
740740
run: |
741-
if [ "$GITHUB_REF" = "refs/heads/dev" ]; then CHANNEL=alpha; APP_NAME="Sim Dev"; else CHANNEL=beta; APP_NAME="Sim Staging"; fi
741+
if [ "$GITHUB_REF" = "refs/heads/dev" ]; then CHANNEL=dev; APP_NAME="Sim Dev"; else CHANNEL=staging; APP_NAME="Sim Staging"; fi
742742
# Prerelease core = next patch after the latest stable release, so
743743
# channel builds always outrank the stable they are built on top of
744744
# and are always superseded by the next stable. The run-attempt
@@ -825,9 +825,9 @@ jobs:
825825
steps:
826826
- name: Delete stale prereleases
827827
run: |
828-
if [ "$GITHUB_REF" = "refs/heads/dev" ]; then CHANNEL=alpha; else CHANNEL=beta; fi
828+
if [ "$GITHUB_REF" = "refs/heads/dev" ]; then CHANNELS='(dev|alpha)'; else CHANNELS='(staging|beta)'; fi
829829
gh release list --limit 100 --json tagName,isPrerelease,isDraft,createdAt \
830-
--jq "[.[] | select(.isPrerelease and (.isDraft | not) and (.tagName | test(\"-${CHANNEL}\\\\.\")))] | sort_by(.createdAt) | reverse | .[5:] | .[].tagName" |
830+
--jq "[.[] | select(.isPrerelease and (.isDraft | not) and (.tagName | test(\"-${CHANNELS}\\\\.\")))] | sort_by(.createdAt) | reverse | .[5:] | .[].tagName" |
831831
while read -r TAG; do
832832
[ -n "$TAG" ] || continue
833833
echo "Deleting stale prerelease $TAG"
@@ -836,11 +836,11 @@ jobs:
836836
837837
- name: Delete leftover draft prereleases
838838
run: |
839-
if [ "$GITHUB_REF" = "refs/heads/dev" ]; then CHANNEL=alpha; else CHANNEL=beta; fi
839+
if [ "$GITHUB_REF" = "refs/heads/dev" ]; then CHANNELS='(dev|alpha)'; else CHANNELS='(staging|beta)'; fi
840840
# Drafts have no tag ref, so delete by release id via the API
841841
# (gh release delete resolves by tag, which is ambiguous for drafts).
842842
gh api "repos/${GH_REPO}/releases?per_page=100" \
843-
--jq ".[] | select(.draft and (.tag_name | test(\"-${CHANNEL}\\\\.\"))) | .id" |
843+
--jq ".[] | select(.draft and (.tag_name | test(\"-${CHANNELS}\\\\.\"))) | .id" |
844844
while read -r ID; do
845845
[ -n "$ID" ] || continue
846846
echo "Deleting leftover draft release $ID"

.github/workflows/desktop-release.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,9 @@ jobs:
9191
exit 1
9292
fi
9393
94-
# Prerelease versions carry their environment in the tag: -alpha.N is a
95-
# dev build, -beta.N a staging build. The channel decides the app's
94+
# Prerelease versions carry their environment in the tag: -dev.N is a
95+
# dev build, -staging.N a staging build. Legacy -alpha/-beta tags remain
96+
# accepted while already-published builds age out. The channel decides the app's
9697
# identity (name/bundle id — a separate app per environment, installable
9798
# side by side) and the default origin baked into the bundle, which in
9899
# turn selects the update feed the installed app polls.
@@ -102,9 +103,9 @@ jobs:
102103
VERSION: ${{ inputs.version }}
103104
run: |
104105
case "$VERSION" in
105-
*-alpha.*)
106+
*-dev.*|*-alpha.*)
106107
NAME='Sim Dev'; APP_ID=ai.sim.desktop.dev; ORIGIN=https://www.dev.sim.ai ;;
107-
*-beta.*)
108+
*-staging.*|*-beta.*)
108109
NAME='Sim Staging'; APP_ID=ai.sim.desktop.staging; ORIGIN=https://www.staging.sim.ai ;;
109110
*)
110111
NAME='Sim'; APP_ID=ai.sim.desktop; ORIGIN='' ;;

apps/desktop/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ Raw local file bytes are never exposed through the preload bridge and cannot be
168168
## Auto-update, channels, rollout, rollback
169169

170170
- `electron-updater` reads the GitHub Releases feed (`publish` is pinned to `simstudioai/sim`); deltas via `.zip.blockmap`. Install is prompt-based (Restart Now / Later; Later installs on quit) — never forced mid-session.
171-
- Channels: stable builds (`X.Y.Z`) follow `latest`; `-beta.N` builds follow `beta` (never attach a beta `latest-mac.yml` to a stable tag).
171+
- Streams: production follows stable `X.Y.Z` releases, dev follows `-dev.N`, and staging follows `-staging.N`. The feed still recognizes legacy `-alpha.N`/`-beta.N` releases during migration.
172172
- Staged rollout: after publishing, edit `stagingPercentage: 10` into the release's `latest-mac.yml`, then raise as crash metrics stay clean.
173173
- Rollback: a pulled release must be superseded by a **higher** version — users on the broken build will not reinstall an equal one. (A blocked-versions kill-switch was removed as unwired dead code; reintroduce it in `updater.ts` if a remote config source ever exists to feed it.)
174174
- Ship the DMG and tell users to install to `/Applications` — App Translocation breaks Squirrel.Mac updates from quarantined paths.

0 commit comments

Comments
 (0)