@geode/opengeodeweb-front 10.38.0-rc.3 → 10.38.0-rc.4

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.
@@ -1,6 +1,5 @@
1
1
  <script setup lang="ts">
2
2
  import Loading from "@ogw_front/components/Loading.vue";
3
- import Recaptcha from "@ogw_front/components/Recaptcha.vue";
4
3
  import { Status } from "@ogw_front/utils/status";
5
4
  import { appMode } from "@ogw_shared/app_mode";
6
5
  import { getCloudUrlParam } from "@ogw_front/utils/cloud";
@@ -8,22 +7,28 @@ import { useInfraStore } from "@ogw_front/stores/infra";
8
7
 
9
8
  interface Props {
10
9
  appName: string;
11
- email?: string;
10
+ // Firebase ID token for the cloud launch; required in cloud mode.
11
+ authToken?: string;
12
12
  isUserAuthenticated?: boolean;
13
13
  logo?: string;
14
14
  }
15
15
 
16
- const { appName, email = undefined, isUserAuthenticated = false, logo = "" } = defineProps<Props>();
16
+ const {
17
+ appName,
18
+ authToken = undefined,
19
+ isUserAuthenticated = false,
20
+ logo = "",
21
+ } = defineProps<Props>();
17
22
 
18
23
  const infraStore = useInfraStore();
19
24
  // In cloud mode, wait for the user to authenticate and load the app, unless a `?cloud_url=` link
20
25
  // Points at an already running service: that one is connected straight away, without authentication.
21
26
  if (infraStore.app_mode !== appMode.CLOUD || getCloudUrlParam() !== undefined) {
22
- infraStore.create_backend(email);
27
+ infraStore.create_backend(authToken);
23
28
  }
24
29
 
25
30
  function cloudCreateBackend(): Promise<void> {
26
- return infraStore.create_backend(email);
31
+ return infraStore.create_backend(authToken);
27
32
  }
28
33
  </script>
29
34
 
package/app/stores/api.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { RequestHandlers } from "@ogw_shared/utils/types";
1
+ import type { ParamsOf, RequestHandlers, ResponseOf } from "@ogw_shared/utils/types";
2
2
  import { api_fetch } from "@ogw_internal/utils/api_fetch";
3
3
  import { consola } from "consola";
4
4
 
@@ -8,7 +8,7 @@ interface ApiSchema {
8
8
  [key: string]: unknown;
9
9
  }
10
10
 
11
- type ApiCallbacks = RequestHandlers & {
11
+ type ApiCallbacks<Response = unknown> = RequestHandlers<Response> & {
12
12
  skip_feedback_error?: boolean;
13
13
  };
14
14
 
@@ -26,21 +26,21 @@ export const useAPIStore = defineStore("api", () => {
26
26
  request_counter.value -= 1;
27
27
  }
28
28
 
29
- // `TResult` is asserted, not verified: the response is only checked against `schema` at runtime
30
- async function request<TResult = unknown>(
29
+ // The response type comes from the schema's generated `response` type (see the Cloud API typed schemas), asserted at the API boundary below, not verified
30
+ async function request<Schema extends ApiSchema>(
31
31
  {
32
32
  schema,
33
- params = {},
33
+ params,
34
34
  headers = {},
35
- }: { schema: ApiSchema; params?: Record<string, unknown>; headers?: Record<string, string> },
36
- callbacks: ApiCallbacks = {},
37
- ): Promise<TResult> {
35
+ }: { schema: Schema; params?: ParamsOf<Schema>; headers?: Record<string, string> },
36
+ callbacks: ApiCallbacks<ResponseOf<Schema>> = {},
37
+ ): Promise<ResponseOf<Schema>> {
38
38
  consola.info("[API] Request:", schema.$id);
39
39
  const start = Date.now();
40
40
 
41
41
  const result = await api_fetch(
42
42
  { $id: schema.$id, base_url: base_url.value, start_request, stop_request },
43
- { schema, params, headers },
43
+ { schema, params: params ?? {}, headers },
44
44
  {
45
45
  ...callbacks,
46
46
  response_function: async (response: unknown) => {
@@ -52,13 +52,14 @@ export const useAPIStore = defineStore("api", () => {
52
52
  "s",
53
53
  );
54
54
  if (callbacks.response_function) {
55
- await callbacks.response_function(response);
55
+ // oxlint-disable-next-line no-unsafe-type-assertion -- trusted API boundary; see comment above.
56
+ await callbacks.response_function(response as ResponseOf<Schema>);
56
57
  }
57
58
  },
58
59
  },
59
60
  );
60
61
  // oxlint-disable-next-line no-unsafe-type-assertion -- trusted API boundary; see comment above.
61
- return result as TResult;
62
+ return result as ResponseOf<Schema>;
62
63
  }
63
64
  return {
64
65
  base_url,
@@ -1,9 +1,8 @@
1
1
  import { clearCloudUrlParam, getCloudUrlParam } from "@ogw_front/utils/cloud";
2
- import type { RunCloudResponse } from "@geode/cloud-api/types";
3
2
  import { Status } from "@ogw_front/utils/status";
4
3
  import { api_fetch } from "@ogw_internal/utils/api_fetch";
5
4
  import back_schemas from "@geode/opengeodeweb-back/opengeodeweb_back_typed_schemas.js";
6
- import cloud_api_schemas from "@geode/cloud-api/cloud_api_schemas.json";
5
+ import cloud_api_schemas from "@geode/cloud-api/cloud_api_typed_schemas.js";
7
6
  import { setAppBaseUrl } from "@ogw_shared/scripts";
8
7
  import { useAPIStore } from "@ogw_front/stores/api";
9
8
  import { useFeedbackStore } from "@ogw_front/stores/feedback";
@@ -17,10 +16,13 @@ export const useCloudStore = defineStore("cloud", {
17
16
  actions: {
18
17
  // Reuses the running service given by `?cloud_url=` when present, otherwise launches a new one.
19
18
  // The param is single use: it is cleared whatever the outcome, so a retry launches a new service.
20
- async start(email: string) {
19
+ async start(authToken?: string) {
21
20
  const existing_host = getCloudUrlParam();
22
21
  if (existing_host === undefined) {
23
- await this.launch(email);
22
+ if (authToken === undefined) {
23
+ throw new Error("Launching a cloud service requires an authenticated user");
24
+ }
25
+ await this.launch(authToken);
24
26
  return;
25
27
  }
26
28
  try {
@@ -29,29 +31,23 @@ export const useCloudStore = defineStore("cloud", {
29
31
  clearCloudUrlParam();
30
32
  }
31
33
  },
32
- async launch(email: string) {
34
+ // `authToken` is the user's Firebase ID token, checked by the Cloud API
35
+ async launch(authToken: string) {
33
36
  this.status = Status.CONNECTING;
34
37
  const { PROJECT, BRANCH } = useRuntimeConfig().public;
35
- const params = { email, project: PROJECT, branch: BRANCH };
38
+ const params = { project: PROJECT, branch: BRANCH };
39
+ const headers = { Authorization: `Bearer ${authToken}` };
36
40
  const feedbackStore = useFeedbackStore();
37
41
  const APIStore = useAPIStore();
38
- const result = await APIStore.request<RunCloudResponse>(
39
- { schema: run_cloud_schema, params },
42
+ const result = await APIStore.request(
43
+ { schema: run_cloud_schema, params, headers },
40
44
  {
41
45
  request_error_function: () => {
42
46
  feedbackStore.$patch({ server_error: true });
43
47
  this.status = Status.NOT_CONNECTED;
44
48
  },
45
- response_function: async (response: unknown) => {
46
- if (
47
- typeof response !== "object" ||
48
- response === null ||
49
- !("url" in response) ||
50
- typeof response.url !== "string"
51
- ) {
52
- return;
53
- }
54
- await this.on_connected(response.url);
49
+ response_function: async ({ url }) => {
50
+ await this.on_connected(url);
55
51
  },
56
52
  response_error_function: () => {
57
53
  feedbackStore.$patch({ server_error: true });
@@ -44,7 +44,7 @@ export const useInfraStore = defineStore("infra", {
44
44
  (microservice: Microservice) => microservice.$id !== microserviceId,
45
45
  );
46
46
  },
47
- async create_backend(email?: string) {
47
+ async create_backend(authToken?: string) {
48
48
  if (this.status === Status.CREATED) {
49
49
  return undefined;
50
50
  }
@@ -56,7 +56,7 @@ export const useInfraStore = defineStore("infra", {
56
56
  if (this.app_mode === appMode.CLOUD) {
57
57
  const cloudStore = useCloudStore();
58
58
  try {
59
- await cloudStore.start(email ?? "");
59
+ await cloudStore.start(authToken);
60
60
  } catch (error) {
61
61
  // Back to the "Load the app" button so the user is not stuck on the loading screen.
62
62
  this.status = Status.NOT_CREATED;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geode/opengeodeweb-front",
3
- "version": "10.38.0-rc.3",
3
+ "version": "10.38.0-rc.4",
4
4
  "description": "OpenSource Vue/Nuxt/Pinia/Vuetify framework for web applications",
5
5
  "homepage": "https://github.com/Geode-solutions/OpenGeodeWeb-Front",
6
6
  "bugs": {
@@ -1,89 +0,0 @@
1
- <script setup lang="ts">
2
- import { useInfraStore } from "@ogw_front/stores/infra";
3
-
4
- interface Props {
5
- buttonLabel?: string;
6
- buttonColor?: string;
7
- color?: string;
8
- }
9
-
10
- const {
11
- buttonLabel = "Load the app",
12
- buttonColor = "white",
13
- color = undefined,
14
- } = defineProps<Props>();
15
-
16
- const name = ref("");
17
- const email = ref("");
18
- const launch = ref(false);
19
- const valid = ref(false);
20
- const emailRules = [
21
- (value: string): boolean | string => {
22
- if (value) {
23
- return true;
24
- }
25
- return "E-mail is required.";
26
- },
27
- (value: string): boolean | string => {
28
- if (/.+@.+\..+/u.test(value)) {
29
- return true;
30
- }
31
- return "E-mail must be valid.";
32
- },
33
- ];
34
-
35
- function submit(): Promise<void> {
36
- const infraStore = useInfraStore();
37
- return infraStore.create_backend(email.value);
38
- }
39
- </script>
40
-
41
- <template>
42
- <VRow align="center" justify="center" style="display: none">
43
- <VCol cols="4">
44
- <VForm v-model="valid">
45
- <VContainer>
46
- <VRow>
47
- <VCol>
48
- <VTextField v-model="name" label="Name" required />
49
- </VCol>
50
- </VRow>
51
- <VRow>
52
- <VCol>
53
- <VTextField v-model="email" :rules="emailRules" label="E-mail" required />
54
- </VCol>
55
- </VRow>
56
- <VRow>
57
- <VCol>
58
- <VCheckbox label="Load the app" v-model="launch" />
59
- </VCol>
60
- </VRow>
61
- </VContainer>
62
- </VForm>
63
- </VCol>
64
- </VRow>
65
- <VRow align="center" justify="center">
66
- <VCol cols="auto" class="d-flex justify-center align-center">
67
- <VBtn class="load-btn" :text="buttonLabel" :color="color || buttonColor" @click="submit" />
68
- </VCol>
69
- </VRow>
70
- </template>
71
-
72
- <style scoped>
73
- .load-btn {
74
- padding: 0 40px !important;
75
- height: 50px !important;
76
- border-radius: 8px;
77
- text-transform: none !important;
78
- font-weight: 600;
79
- letter-spacing: 0.5px;
80
- transition:
81
- transform 0.2s ease,
82
- box-shadow 0.2s ease !important;
83
- }
84
-
85
- .load-btn:hover {
86
- transform: translateY(-2px);
87
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
88
- }
89
- </style>