@nauticana/sail 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +741 -0
- package/package.json +58 -0
- package/src/component/abstract/base_async.ts +49 -0
- package/src/component/abstract/base_form.ts +11 -0
- package/src/component/abstract/base_table.ts +320 -0
- package/src/component/abstract/base_view.ts +93 -0
- package/src/component/billing/checkout_button.html +10 -0
- package/src/component/billing/checkout_button.ts +57 -0
- package/src/component/billing/payment_methods.html +27 -0
- package/src/component/billing/payment_methods.ts +36 -0
- package/src/component/billing/plan_selector.html +24 -0
- package/src/component/billing/plan_selector.ts +29 -0
- package/src/component/form/form_field.html +54 -0
- package/src/component/form/form_field.ts +156 -0
- package/src/component/form/form_record.html +14 -0
- package/src/component/form/form_record.ts +56 -0
- package/src/component/form/form_table.html +19 -0
- package/src/component/form/form_table.ts +58 -0
- package/src/component/login/account_deletion.html +42 -0
- package/src/component/login/account_deletion.ts +79 -0
- package/src/component/login/chpass_component.html +58 -0
- package/src/component/login/chpass_component.ts +96 -0
- package/src/component/login/confirm_chpass_component.html +47 -0
- package/src/component/login/confirm_chpass_component.ts +70 -0
- package/src/component/login/confirm_register_component.html +37 -0
- package/src/component/login/confirm_register_component.ts +71 -0
- package/src/component/login/consent_gate.html +30 -0
- package/src/component/login/consent_gate.ts +109 -0
- package/src/component/login/login_component.html +43 -0
- package/src/component/login/login_component.ts +73 -0
- package/src/component/login/otp_input.html +38 -0
- package/src/component/login/otp_input.ts +104 -0
- package/src/component/login/register_component.html +132 -0
- package/src/component/login/register_component.ts +184 -0
- package/src/component/login/social_login.html +13 -0
- package/src/component/login/social_login.ts +120 -0
- package/src/component/navigation/navigation.html +49 -0
- package/src/component/navigation/navigation.ts +69 -0
- package/src/component/security/trusted_devices.html +76 -0
- package/src/component/security/trusted_devices.ts +92 -0
- package/src/component/security/twofactor_setup.html +75 -0
- package/src/component/security/twofactor_setup.ts +106 -0
- package/src/component/security/twofactor_verify.html +45 -0
- package/src/component/security/twofactor_verify.ts +90 -0
- package/src/component/table/table_detail.html +53 -0
- package/src/component/table/table_detail.ts +133 -0
- package/src/component/table/table_edit.html +58 -0
- package/src/component/table/table_edit.ts +163 -0
- package/src/component/table/table_list.html +41 -0
- package/src/component/table/table_list.ts +100 -0
- package/src/component/table/table_lookup.html +35 -0
- package/src/component/table/table_lookup.ts +88 -0
- package/src/component/table/table_search.html +17 -0
- package/src/component/table/table_search.ts +80 -0
- package/src/config.ts +67 -0
- package/src/index.ts +41 -0
- package/src/model/appdata.ts +266 -0
- package/src/model/auth.ts +134 -0
- package/src/model/common.ts +115 -0
- package/src/model/decorator.ts +74 -0
- package/src/service/api-response.interceptor.ts +26 -0
- package/src/service/auth.interceptor.ts +11 -0
- package/src/service/auth.service.ts +576 -0
- package/src/service/billing.service.ts +65 -0
- package/src/service/label.service.ts +18 -0
- package/src/service/rest_service.ts +94 -0
- package/src/service/rest_url.ts +42 -0
- package/src/service/script_loader.ts +31 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { HttpClient, HttpErrorResponse, HttpParams } from "@angular/common/http";
|
|
2
|
+
import { inject, Injectable } from "@angular/core";
|
|
3
|
+
import { catchError, map, Observable, throwError } from "rxjs";
|
|
4
|
+
import { PaginatedList } from "../model/appdata";
|
|
5
|
+
import { RestURL } from "./rest_url";
|
|
6
|
+
|
|
7
|
+
@Injectable({providedIn: 'root'})
|
|
8
|
+
export class BackendService {
|
|
9
|
+
protected readonly prefix = RestURL.httpHost + RestURL.api_prefix;
|
|
10
|
+
protected readonly http = inject(HttpClient);
|
|
11
|
+
|
|
12
|
+
protected handleError(error: HttpErrorResponse) {
|
|
13
|
+
if (error.status === 0) {
|
|
14
|
+
console.error('an error occurred', error.error);
|
|
15
|
+
} else {
|
|
16
|
+
console.error(`backend return error code: ${error.status} error body: ${error.error}`);
|
|
17
|
+
}
|
|
18
|
+
return throwError(() => 'error occurred, see console log and try again');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Build a fully-qualified URL under the configured API base. Use for
|
|
23
|
+
* endpoints that don't fit the metadata-driven `/api/{name}/{op}`
|
|
24
|
+
* shape — e.g. domain-specific actions like `rides/start`. Strips any
|
|
25
|
+
* leading slash on `path` so callers can pass either form.
|
|
26
|
+
*
|
|
27
|
+
* For metadata-driven CRUD use list/get/post/delete instead — they
|
|
28
|
+
* already build the URL.
|
|
29
|
+
*/
|
|
30
|
+
protected apiUrl(path: string): string {
|
|
31
|
+
const trimmed = path.startsWith('/') ? path.slice(1) : path;
|
|
32
|
+
return this.prefix + trimmed;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Generic typed HTTP request for endpoints that don't fit the
|
|
37
|
+
* list/get/post/delete pattern. Subclasses use this for
|
|
38
|
+
* domain-specific actions:
|
|
39
|
+
*
|
|
40
|
+
* class RideService extends BackendService {
|
|
41
|
+
* startRide(req: StartRideReq) {
|
|
42
|
+
* return this.request<RideResponse>('POST', 'rides/start', req);
|
|
43
|
+
* }
|
|
44
|
+
* }
|
|
45
|
+
*
|
|
46
|
+
* Errors are routed through handleError so callers see the same
|
|
47
|
+
* "see console log and try again" envelope as CRUD methods.
|
|
48
|
+
*/
|
|
49
|
+
protected request<T>(method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', path: string, body?: unknown): Observable<T> {
|
|
50
|
+
return this.http.request<T>(method, this.apiUrl(path), { body }).pipe(catchError(this.handleError));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Build query params from a flat string-string map; skips undefined/null. */
|
|
54
|
+
private toParams(filter?: {[key: string]: string}): HttpParams {
|
|
55
|
+
let params = new HttpParams();
|
|
56
|
+
if (!filter) return params;
|
|
57
|
+
for (const key in filter) {
|
|
58
|
+
if (Object.prototype.hasOwnProperty.call(filter, key)) {
|
|
59
|
+
params = params.set(key, filter[key]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return params;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* keel list endpoints return `{items, limit, offset, total}`. This method
|
|
67
|
+
* unwraps to the array — most callers only need the records. Use
|
|
68
|
+
* `listPaginated()` to access the total + offset. Pass `_limit` / `_offset`
|
|
69
|
+
* in `filter` to control paging (defaults: 100 / 0).
|
|
70
|
+
*/
|
|
71
|
+
list<T>(apiName: string, filter?: {[key: string]: string}) : Observable<T[]> {
|
|
72
|
+
return this.listPaginated<T>(apiName, filter).pipe(map((page) => page.items));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Paginated list — returns `{items, limit, offset, total}`. */
|
|
76
|
+
listPaginated<T>(apiName: string, filter?: {[key: string]: string}) : Observable<PaginatedList<T>> {
|
|
77
|
+
return this.http.get<PaginatedList<T>>(
|
|
78
|
+
this.apiUrl(apiName + '/list'),
|
|
79
|
+
{params: this.toParams(filter)},
|
|
80
|
+
).pipe(catchError(this.handleError));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get<T>(apiName: string, filter?: {[key: string]: string}) : Observable<T> {
|
|
84
|
+
return this.http.get<T>(this.apiUrl(apiName + '/get'), {params: this.toParams(filter)}).pipe(catchError(this.handleError));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
post<T>(apiName: string, items: T | T[]) : Observable<{message: string}> {
|
|
88
|
+
return this.http.post<{message: string}>(this.apiUrl(apiName + '/post'), items).pipe(catchError(this.handleError));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
delete(apiName: string, filter?: {[key: string]: string}): Observable<{message: string}> {
|
|
92
|
+
return this.http.delete<{message: string}>(this.apiUrl(apiName + '/delete'), {params: this.toParams(filter)}).pipe(catchError(this.handleError));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export const RestURL = {
|
|
2
|
+
httpHost: '',
|
|
3
|
+
api_prefix: '/api/',
|
|
4
|
+
appdataURL: '/api/config/appdata',
|
|
5
|
+
loginURL: '/public/login/local',
|
|
6
|
+
loginGoogleURL: '/public/login/google',
|
|
7
|
+
registerURL: '/public/register',
|
|
8
|
+
chpassURL: '/public/login/chpass',
|
|
9
|
+
confirmRegisterURL: '/public/confirm/register',
|
|
10
|
+
plansURL: '/public/plans',
|
|
11
|
+
confirmChpassURL: '/public/confirm/password',
|
|
12
|
+
twoFactorSetupURL: '/api/user/2fa/setup',
|
|
13
|
+
twoFactorVerifyURL: '/api/user/2fa/verify', // authenticated — confirms setup
|
|
14
|
+
twoFactorDisableURL: '/api/user/2fa/disable',
|
|
15
|
+
twoFactorLoginVerifyURL: '/public/2fa/verify', // public — login-time TOTP verify
|
|
16
|
+
twoFactorBackupVerifyURL: '/public/2fa/backup-verify', // public — login-time backup verify
|
|
17
|
+
trustedDeviceListURL: '/api/user/trusted-device/list',
|
|
18
|
+
trustedDeviceRegisterURL: '/api/user/trusted-device/register',
|
|
19
|
+
trustedDeviceRevokeURL: '/api/user/trusted-device/revoke',
|
|
20
|
+
checkoutURL: '/api/billing/checkout',
|
|
21
|
+
subscriptionURL: '/api/billing/subscription',
|
|
22
|
+
cancelSubURL: '/api/billing/subscription/cancel',
|
|
23
|
+
invoicesURL: '/api/billing/invoices',
|
|
24
|
+
paymentMethodsURL: '/api/billing/payment-methods',
|
|
25
|
+
// OTP / social / push / account-lifecycle endpoints
|
|
26
|
+
otpSendURL: '/public/otp/send',
|
|
27
|
+
otpVerifyURL: '/public/otp/verify',
|
|
28
|
+
otpResendURL: '/public/otp/resend',
|
|
29
|
+
loginSocialURL: '/public/login/social',
|
|
30
|
+
logoutEverywhereURL: '/api/user/logout-everywhere',
|
|
31
|
+
deleteAccountURL: '/api/user/account',
|
|
32
|
+
pushRegisterURL: '/api/push/register',
|
|
33
|
+
pushRevokeURL: '/api/push/revoke',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** Call this at app startup to set the backend host URL */
|
|
37
|
+
export function configureRestUrls(httpHost: string, overrides?: Partial<typeof RestURL>) {
|
|
38
|
+
RestURL.httpHost = httpHost;
|
|
39
|
+
if (overrides) {
|
|
40
|
+
Object.assign(RestURL, overrides);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dynamic external <script> loader. Used by SocialLoginComponent to bring
|
|
3
|
+
* in Google Identity Services / Apple JS on demand instead of bundling them.
|
|
4
|
+
* Caches by URL — subsequent calls for the same URL resolve immediately.
|
|
5
|
+
*/
|
|
6
|
+
const cache = new Map<string, Promise<void>>();
|
|
7
|
+
|
|
8
|
+
export function loadScript(src: string): Promise<void> {
|
|
9
|
+
const existing = cache.get(src);
|
|
10
|
+
if (existing) return existing;
|
|
11
|
+
|
|
12
|
+
const p = new Promise<void>((resolve, reject) => {
|
|
13
|
+
if (typeof document === 'undefined') {
|
|
14
|
+
reject(new Error('loadScript: no document (SSR?)'));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const prior = document.querySelector<HTMLScriptElement>(`script[src="${src}"]`);
|
|
18
|
+
if (prior) { resolve(); return; }
|
|
19
|
+
|
|
20
|
+
const el = document.createElement('script');
|
|
21
|
+
el.src = src;
|
|
22
|
+
el.async = true;
|
|
23
|
+
el.defer = true;
|
|
24
|
+
el.onload = () => resolve();
|
|
25
|
+
el.onerror = () => reject(new Error(`Failed to load ${src}`));
|
|
26
|
+
document.head.appendChild(el);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
cache.set(src, p);
|
|
30
|
+
return p;
|
|
31
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ES2022",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ES2022", "dom"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"experimentalDecorators": true,
|
|
11
|
+
"emitDecoratorMetadata": true,
|
|
12
|
+
"noEmit": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*.ts"],
|
|
15
|
+
"exclude": ["node_modules"]
|
|
16
|
+
}
|