Skip to content

Commit

Permalink
Do API calls in desktop frontend via ts-rest (CapSoftware#201)
Browse files Browse the repository at this point in the history
* Do API calls in desktop frontend via ts-rest

* node v20

* typescript
  • Loading branch information
Brendonovich authored Dec 5, 2024
1 parent 6fdad53 commit 65e8054
Show file tree
Hide file tree
Showing 15 changed files with 259 additions and 203 deletions.
2 changes: 1 addition & 1 deletion .github/actions/setup-js/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ runs:
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20
cache: pnpm

- name: Install frontend dependencies
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@cap/ui": "workspace:*",
"@cap/ui-solid": "workspace:*",
"@cap/utils": "workspace:*",
"@cap/web-api-contract": "workspace:*",
"@corvu/tooltip": "^0.2.1",
"@kobalte/core": "^0.13.7",
"@solid-primitives/bounds": "^0.0.122",
Expand Down Expand Up @@ -41,6 +42,7 @@
"@tauri-apps/plugin-shell": ">=2.0.0-rc.0",
"@tauri-apps/plugin-store": "2.1.0",
"@tauri-apps/plugin-updater": "2.0.0-rc.0",
"@ts-rest/core": "^3.51.0",
"@types/react-tooltip": "^4.2.4",
"cva": "npm:class-variance-authority@^0.7.0",
"effect": "^3.7.2",
Expand Down
22 changes: 15 additions & 7 deletions apps/desktop/src/routes/(window-chrome)/(main).tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,17 @@ import {
const getAuth = cache(async () => {
const value = await authStore.get();
const local = import.meta.env.VITE_LOCAL_MODE === "true";
if (!value && !local) return redirect("/signin");
const res = await fetch(`${clientEnv.VITE_SERVER_URL}/api/desktop/plan`, {
headers: { authorization: `Bearer ${value?.token}` },

if (!value) {
if (local) return;
return redirect("/signin");
}

const res = await apiClient.getUserPlan({
headers: await protectedHeaders(),
});
if (res.status !== 200 && !local) return redirect("/signin");

return value;
}, "getAuth");

Expand Down Expand Up @@ -258,6 +264,7 @@ import * as updater from "@tauri-apps/plugin-updater";
import { makePersisted } from "@solid-primitives/storage";
import titlebarState, { setTitlebar } from "~/utils/titlebar-state";
import { type as ostype } from "@tauri-apps/plugin-os";
import { apiClient, protectedHeaders } from "~/utils/web-api";

let hasChecked = false;
function createUpdateCheck() {
Expand Down Expand Up @@ -767,10 +774,11 @@ function ChangelogButton() {
if (!version) {
return { hasUpdate: false };
}
const response = await fetch(
`${clientEnv.VITE_SERVER_URL}/api/changelog/status?version=${version}`
);
return await response.json();
const response = await apiClient.getChangelogStatus({
query: { version },
});
if (response.status === 200) return response.body;
return null;
}
);

Expand Down
120 changes: 35 additions & 85 deletions apps/desktop/src/routes/(window-chrome)/settings/apps/s3-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createSignal, onMount } from "solid-js";
import { authStore } from "~/store";
import { clientEnv } from "~/utils/env";
import { commands } from "~/utils/tauri";
import { apiClient as apiClient, protectedHeaders } from "~/utils/web-api";

interface S3Config {
provider: string;
Expand Down Expand Up @@ -56,51 +57,16 @@ export default function S3ConfigPage() {
window.close();
};

const makeAuthenticatedRequest = async (
url: string,
options: RequestInit
) => {
const auth = await authStore.get();
if (!auth) {
await handleAuthError();
return null;
}

const response = await fetch(
`${clientEnv.VITE_SERVER_URL}${url}?origin=${window.location.origin}`,
{
...options,
credentials: "include",
headers: {
...options.headers,
Authorization: `Bearer ${auth.token}`,
},
}
);

if (!response.ok) {
throw new Error(
`Failed to ${options.method?.toLowerCase() || "fetch"} S3 configuration`
);
}

return response;
};

onMount(async () => {
try {
const response = await makeAuthenticatedRequest(
"/api/desktop/s3/config/get",
{
method: "GET",
}
);
const response = await apiClient.getS3Config({
headers: await protectedHeaders(),
});

if (!response) return;
if (response.status !== 200) throw new Error("Failed to fetch S3 config");

const data = await response.json();
if (data.config) {
const config = data.config as S3Config;
if (response.body.config) {
const config = response.body.config;
if (!config.accessKeyId) return;

setProvider(config.provider || DEFAULT_CONFIG.provider);
Expand All @@ -123,25 +89,19 @@ export default function S3ConfigPage() {
const handleSave = async () => {
setSaving(true);
try {
const response = await makeAuthenticatedRequest(
"/api/desktop/s3/config",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
provider: provider(),
accessKeyId: accessKeyId(),
secretAccessKey: secretAccessKey(),
endpoint: endpoint(),
bucketName: bucketName(),
region: region(),
}),
}
);
const response = await apiClient.setS3Config({
body: {
provider: provider(),
accessKeyId: accessKeyId(),
secretAccessKey: secretAccessKey(),
endpoint: endpoint(),
bucketName: bucketName(),
region: region(),
},
headers: await protectedHeaders(),
});

if (response) {
if (response.status === 200) {
setHasConfig(true);
await commands.globalMessageDialog(
"S3 configuration saved successfully"
Expand All @@ -160,14 +120,11 @@ export default function S3ConfigPage() {
const handleDelete = async () => {
setDeleting(true);
try {
const response = await makeAuthenticatedRequest(
"/api/desktop/s3/config/delete",
{
method: "DELETE",
}
);
const response = await apiClient.deleteS3Config({
headers: await protectedHeaders(),
});

if (response) {
if (response.status === 200) {
resetForm();
await commands.globalMessageDialog(
"S3 configuration deleted successfully"
Expand All @@ -189,28 +146,21 @@ export default function S3ConfigPage() {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5500); // 5.5s timeout (slightly longer than backend)

const response = await makeAuthenticatedRequest(
"/api/desktop/s3/config/test",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
provider: provider(),
accessKeyId: accessKeyId(),
secretAccessKey: secretAccessKey(),
endpoint: endpoint(),
bucketName: bucketName(),
region: region(),
}),
signal: controller.signal,
}
);
const response = await apiClient.testS3Config({
body: {
provider: provider(),
accessKeyId: accessKeyId(),
secretAccessKey: secretAccessKey(),
endpoint: endpoint(),
bucketName: bucketName(),
region: region(),
},
headers: await protectedHeaders(),
});

clearTimeout(timeoutId);

if (response) {
if (response.status === 200) {
await commands.globalMessageDialog(
"S3 configuration test successful! Connection is working."
);
Expand Down
27 changes: 11 additions & 16 deletions apps/desktop/src/routes/(window-chrome)/settings/changelog.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
import { For, Show, createResource, ErrorBoundary, Suspense } from "solid-js";
import { clientEnv } from "~/utils/env";
import { For, Show, ErrorBoundary, Suspense } from "solid-js";
import { SolidMarkdown } from "solid-markdown";
import { createQuery } from "@tanstack/solid-query";
import { AbsoluteInsetLoader } from "~/components/Loader";
import { cx } from "cva";

interface ChangelogEntry {
title: string;
app: string;
version: string;
publishedAt: string;
content: string;
}
import { AbsoluteInsetLoader } from "~/components/Loader";
import { apiClient } from "~/utils/web-api";

export default function Page() {
const changelog = createQuery(() => ({
queryKey: ["changelog"],
queryFn: async () => {
const response = await fetch(
`${clientEnv.VITE_SERVER_URL}/api/changelog?origin=${window.location.origin}`
);
if (!response.ok) throw new Error("Failed to fetch changelog");
const response = await apiClient.getChangelogPosts({
query: { origin: window.location.origin },
});

return (await response.json()) as Array<ChangelogEntry>;
if (response.status !== 200) throw new Error("Failed to fetch changelog");
return response.body;
},
}));

Expand All @@ -40,7 +33,9 @@ export default function Page() {
>
<ErrorBoundary
fallback={(e) => (
<div class="text-[--text-primary] font-medium">{e.toString()}</div>
<div class="text-[--text-primary] font-medium">
{e.toString()}
</div>
)}
>
<ul class="space-y-8">
Expand Down
Loading

0 comments on commit 65e8054

Please sign in to comment.