Skip to content

Commit

Permalink
Merge pull request #17 from Ferlab-Ste-Justine/refresh-token
Browse files Browse the repository at this point in the history
refresh tokens
  • Loading branch information
oliviercperrier authored Feb 5, 2025
2 parents bb26b2b + 5577602 commit 4534924
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 30 deletions.
4 changes: 3 additions & 1 deletion frontend/apps/variant/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@
},
"dependencies": {
"components": "file:../../components",
"utils": "file:../../utils",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"swr": "^2.3.1"
"swr": "^2.3.1",
"axios": "1.7.9"
},
"devDependencies": {
"@eslint/js": "^9.15.0",
Expand Down
31 changes: 18 additions & 13 deletions frontend/apps/variant/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@ import {
defaultSettings,
} from "./include_variant_table";
import { IVariantEntity } from "@/variant_type";
import useSWR, { Fetcher } from "swr";
import useSWR from "swr";
import { axiosClient } from "@/utils/axios";

export interface AppProps {}
type OccurrenceInput = {
seqId: string;
listBody: ListBody;
};

const fetcher: Fetcher<Occurrence[], string> = (url: string) =>
fetch(url, {
method: "POST",
body: JSON.stringify({
const fetcher = (input: OccurrenceInput) =>
axiosClient.post("/occurrences", input).then((res) => res.data);

function App() {
const { data } = useSWR<Occurrence[], any, OccurrenceInput>(
{
seqId: "5011",
listBody: {
selected_fields: ["hgvsg", "variant_class"],
Expand All @@ -39,13 +45,12 @@ const fetcher: Fetcher<Occurrence[], string> = (url: string) =>
value: [4],
},
},
}),
}).then((res) => res.json());

function App({}: AppProps) {
const { data, error, isLoading } = useSWR("/api/occurrences", fetcher, {
revalidateOnFocus: false,
});
},
fetcher,
{
revalidateOnFocus: false,
}
);
const occurrences = data || [];

return (
Expand Down
1 change: 1 addition & 0 deletions frontend/apps/variant/tsconfig.app.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"noUncheckedSideEffectImports": true,
"paths": {
"@/api/*": ["../../api/*"],
"@/utils/*": ["../../utils/*"],
"@/components/*": ["../../components/*"],
"@/base/ui/*": ["../../components/base/ui/*"],
"@/lib/*": ["../../components/lib/*"],
Expand Down
14 changes: 14 additions & 0 deletions frontend/docs/project-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Instead of working by portals, now we work by applications and we have to make s
## Project Structure

### Directory Organization

```
frontend/
├── apps/ # Full applications for a domain (e.g., Variant, Prescription)
Expand All @@ -28,36 +29,43 @@ frontend/
├── themes/ #
│ ├── themesX/ # Theme-specific assets
│ | └── assets/ #
│ utils/ # Shared utils
└── types/ # TypeScript types/interfaces
```

---

### 1. **Apps**

The `Apps` directory contains full applications designed to serve specific business domains.
Each application is a complete unit, including pages and modal dialogs specific to its purpose.

It does not contain navigation, site layout. Only the core features of the application.

#### Examples:

- **Variant**: Application related to variant exploration.
- **Prescription**: Handles prescription-related functionalities.
- **Community**: Manages community-specific features.

#### Key Characteristics:

- Combines pages and modals for cohesive user flows for a specific domain.
- Leverages components from the `Components` directory for reusable building blocks.

---

### 2. **Components**

The `Components` directory holds all basic to advanced UI components required to build applications. These components are categorized into generic and custom components.

#### Types of Components:

- **Generic Components**: Built using [shadcn](https://shadcn.dev), leveraging pre-made, highly customizable components.
- **Custom Components**: Created with React and TailwindCSS for unique design and behavior tailored to application needs.

#### Key Features:

- Encourages reusability across multiple applications.
- Provides a shared library of consistent UI elements.

Expand All @@ -66,26 +74,32 @@ The `Components` directory holds all basic to advanced UI components required to
---

### 3. **Portals**

The `Portals` directory contains the layout and setup for portals, including the base structure for generating different portals (e.g., "Radiant").

#### Functionality:

- Uses `react-router` for navigation and routing.
- Capable of generating multiple portals based on different themes and environment configurations during the build process.

---

### 4. **Themes**

The `Themes` directory houses theme-related assets such as images and TailwindCSS configurations. These themes are used to style the portals and applications, ensuring a consistent look and feel across the project.

#### Contents:

- **Images**: Logos, backgrounds, and other visual assets.
- **CSS**: TailwindCSS configurations for styling components and layouts.

---

### 5. **Storybook**

The `Storybook` directory contains a setup for testing and demonstrating components. It allows developers to preview individual components or groups of components in isolation.

#### Capabilities:

- Select and test components with different themes applied.
- Serves as a living documentation for the component library.
14 changes: 12 additions & 2 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,8 @@
"devDependencies": {
"@ferlab/eslint-config": "^2.0.0",
"eslint-plugin-prettier": "^5.2.3"
},
"dependencies": {
"axios": "^1.7.9"
}
}
22 changes: 16 additions & 6 deletions frontend/portals/radiant/app/api/occurrences.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getOccurrencesApi } from "~/utils/api.server";
import type { Route } from "./+types/occurrences";
import { AxiosError, HttpStatusCode } from "axios";

export function action({ request }: Route.ActionArgs) {
switch (request.method) {
Expand All @@ -25,11 +26,20 @@ const fetchOccurences = async (request: Request) => {
},
});
} catch (error: any) {
return new Response(JSON.stringify([]), {
status: 200,
headers: {
"Content-Type": "application/json",
},
});
if (error instanceof AxiosError) {
return new Response(JSON.stringify(error.response?.data), {
status: error.response?.status || HttpStatusCode.InternalServerError,
headers: {
"Content-Type": "application/json",
},
});
} else {
return new Response(JSON.stringify(error), {
status: HttpStatusCode.InternalServerError,
headers: {
"Content-Type": "application/json",
},
});
}
}
};
19 changes: 12 additions & 7 deletions frontend/portals/radiant/app/api/refresh-token.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { HttpStatusCode } from "axios";
import type { Route } from "./+types/occurrences";
import { refreshAccessToken } from "~/utils/auth.server";

export async function action({ request }: Route.LoaderArgs) {
const results = await refreshAccessToken(request);
if (request.method === "POST") {
const results = await refreshAccessToken(request);

return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: {
"Set-Cookie": results.cookie,
},
});
return new Response(JSON.stringify({ success: true }), {
status: HttpStatusCode.Ok,
headers: {
"Set-Cookie": results.cookie,
},
});
}

return new Response(null, { status: HttpStatusCode.MethodNotAllowed });
}
5 changes: 4 additions & 1 deletion frontend/portals/radiant/app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,8 @@ export default [
layout("./layout/protected-layout.tsx", [index("./routes/home.tsx")]),
route("auth/callback", "./routes/auth/callback.ts"),
route("auth/logout", "./routes/auth/logout.ts"),
...prefix("api", [route("occurrences", "./api/occurrences.ts")]),
...prefix("api", [
route("occurrences", "./api/occurrences.ts"),
route("refresh-token", "./api/refresh-token.ts"),
]),
] satisfies RouteConfig;
44 changes: 44 additions & 0 deletions frontend/utils/axios.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import axios, { HttpStatusCode } from "axios";

export const axiosClient = axios.create({
baseURL: "/api",
headers: {
"Content-Type": "application/json",
},
});

let refreshTokenPromise: Promise<any> | null = null;

axiosClient.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;

if (refreshTokenPromise) {
await refreshTokenPromise;

return axiosClient(originalRequest);
}

if (
error.response.status === HttpStatusCode.Unauthorized &&
!originalRequest._retry
) {
originalRequest._retry = true;

try {
refreshTokenPromise = axios.post("/api/refresh-token").finally(() => {
refreshTokenPromise = null;
});

await refreshTokenPromise;

return axiosClient(originalRequest);
} catch (refreshError) {
window.location.href = "/auth/logout";
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);

0 comments on commit 4534924

Please sign in to comment.