Skip to content

Commit fb7f5e3

Browse files
committed
feat: azure cosmosdb example
1 parent 766f9ef commit fb7f5e3

27 files changed

+378
-32
lines changed

.github/workflows/deploy-container-to-azure-web-app.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,23 @@ env:
1616
AZURE_APPSERVICE_APP_NAME: ${{ vars.AZURE_APPSERVICE_APP_NAME }}
1717
AZURE_APPSERVICE_SLOT_NAME: ${{ vars.AZURE_APPSERVICE_SLOT_NAME }}
1818
NEXT_PUBLIC_AZURE_B2C_AUTHORITY: ${{ vars.NEXT_PUBLIC_AZURE_B2C_AUTHORITY }}
19+
NEXT_PUBLIC_AZURE_B2C_CLIENT_ID: ${{ vars.NEXT_PUBLIC_AZURE_B2C_CLIENT_ID }}
1920
NEXT_PUBLIC_AZURE_B2C_FLOW_EDIT_PROFILE: ${{ vars.NEXT_PUBLIC_AZURE_B2C_FLOW_EDIT_PROFILE }}
2021
NEXT_PUBLIC_AZURE_B2C_FLOW_FORGOT_PASSWORD: ${{ vars.NEXT_PUBLIC_AZURE_B2C_FLOW_FORGOT_PASSWORD }}
2122
NEXT_PUBLIC_AZURE_B2C_FLOW_SIGN_UP_SIGN_IN: ${{ vars.NEXT_PUBLIC_AZURE_B2C_FLOW_SIGN_UP_SIGN_IN }}
22-
NEXT_PUBLIC_AZURE_B2C_CLIENT_ID: ${{ vars.NEXT_PUBLIC_AZURE_B2C_CLIENT_ID }}
2323
NEXT_PUBLIC_AZURE_B2C_REDIRECT_URI: ${{ vars.NEXT_PUBLIC_AZURE_B2C_REDIRECT_URI }}
2424
NEXT_PUBLIC_AZURE_B2C_TENANT_ID: ${{ vars.NEXT_PUBLIC_AZURE_B2C_TENANT_ID }}
2525

2626
# Secrets - values won't be shown in the logs
2727
AZURE_APPSERVICE_PUBLISHPROFILE: ${{ secrets.AZURE_APPSERVICE_PUBLISHPROFILE }}
28+
AZURE_COSMOSDB_CONTAINER_ID: ${{ secrets.AZURE_COSMOSDB_CONTAINER_ID }}
29+
AZURE_COSMOSDB_DATABASE_ID: ${{ secrets.AZURE_COSMOSDB_DATABASE_ID }}
30+
AZURE_COSMOSDB_ENDPOINT: ${{ secrets.AZURE_COSMOSDB_ENDPOINT }}
31+
AZURE_COSMOSDB_KEY: ${{ secrets.AZURE_COSMOSDB_KEY }}
2832
DOCKERHUB_REPO: ${{ secrets.DOCKERHUB_REPO }}
2933
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
3034
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
3135

32-
3336
jobs:
3437
check_commit:
3538
runs-on: ubuntu-latest
@@ -86,6 +89,10 @@ jobs:
8689
tags: index.docker.io/${{ env.DOCKERHUB_USERNAME }}/${{ env.DOCKERHUB_REPO}}:${{ github.sha }}
8790
context: .
8891
build-args: |
92+
AZURE_COSMOSDB_CONTAINER_ID: ${{ env.AZURE_COSMOSDB_CONTAINER_ID }}
93+
AZURE_COSMOSDB_DATABASE_ID: ${{ env.AZURE_COSMOSDB_DATABASE_ID }}
94+
AZURE_COSMOSDB_ENDPOINT: ${{ env.AZURE_COSMOSDB_ENDPOINT }}
95+
AZURE_COSMOSDB_KEY: ${{ env.AZURE_COSMOSDB_KEY }}
8996
NEXT_PUBLIC_AZURE_B2C_AUTHORITY: ${{ env.NEXT_PUBLIC_AZURE_B2C_AUTHORITY }}
9097
NEXT_PUBLIC_AZURE_B2C_CLIENT_ID: ${{ env.NEXT_PUBLIC_AZURE_B2C_CLIENT_ID }}
9198
NEXT_PUBLIC_AZURE_B2C_FLOW_EDIT_PROFILE: ${{ env.NEXT_PUBLIC_AZURE_B2C_FLOW_EDIT_PROFILE }}

Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ ARG NEXT_PUBLIC_AZURE_B2C_FLOW_FORGOT_PASSWORD
1616
ARG NEXT_PUBLIC_AZURE_B2C_FLOW_SIGN_UP_SIGN_IN
1717
ARG NEXT_PUBLIC_AZURE_B2C_REDIRECT_URI
1818
ARG NEXT_PUBLIC_AZURE_B2C_TENANT_ID
19+
ARG AZURE_COSMOSDB_ENDPOINT
20+
ARG AZURE_COSMOSDB_KEY
21+
ARG AZURE_COSMOSDB_DATABASE_ID
22+
ARG AZURE_COSMOSDB_CONTAINER_ID
1923

2024
ENV NEXT_PUBLIC_AZURE_B2C_AUTHORITY=$NEXT_PUBLIC_AZURE_B2C_AUTHORITY
2125
ENV NEXT_PUBLIC_AZURE_B2C_CLIENT_ID=$NEXT_PUBLIC_AZURE_B2C_CLIENT_ID
@@ -24,5 +28,9 @@ ENV NEXT_PUBLIC_AZURE_B2C_FLOW_FORGOT_PASSWORD=$NEXT_PUBLIC_AZURE_B2C_FLOW_FORGO
2428
ENV NEXT_PUBLIC_AZURE_B2C_FLOW_SIGN_UP_SIGN_IN=$NEXT_PUBLIC_AZURE_B2C_FLOW_SIGN_UP_SIGN_IN
2529
ENV NEXT_PUBLIC_AZURE_B2C_REDIRECT_URI=$NEXT_PUBLIC_AZURE_B2C_REDIRECT_URI
2630
ENV NEXT_PUBLIC_AZURE_B2C_TENANT_ID=$NEXT_PUBLIC_AZURE_B2C_TENANT_ID
31+
ENV AZURE_COSMOSDB_ENDPOINT=$AZURE_COSMOSDB_ENDPOINT
32+
ENV AZURE_COSMOSDB_KEY=$AZURE_COSMOSDB_KEY
33+
ENV AZURE_COSMOSDB_DATABASE_ID=$AZURE_COSMOSDB_DATABASE_ID
34+
ENV AZURE_COSMOSDB_CONTAINER_ID=$AZURE_COSMOSDB_CONTAINER_ID
2735

2836
CMD ["npx", "next", "start"]

apps/msal-react-demo/.env.template

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
# https://nextjs.org/docs/app/building-your-application/configuring/environment-variables#bundling-environment-variables-for-the-browser
2+
AZURE_COSMOSDB_CONTAINER_ID=
3+
AZURE_COSMOSDB_DATABASE_ID=
4+
AZURE_COSMOSDB_ENDPOINT=
5+
AZURE_COSMOSDB_KEY=
26
NEXT_PUBLIC_AZURE_B2C_AUTHORITY=
37
NEXT_PUBLIC_AZURE_B2C_CLIENT_ID=
48
NEXT_PUBLIC_AZURE_B2C_FLOW_EDIT_PROFILE=
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Template for creating GitHub Actions secrets
22
AZURE_APPSERVICE_PUBLISHPROFILE=
3+
AZURE_COSMOSDB_CONTAINER_ID=
4+
AZURE_COSMOSDB_DATABASE_ID=
5+
AZURE_COSMOSDB_ENDPOINT=
6+
AZURE_COSMOSDB_KEY=
37
DOCKERHUB_REPO=
48
DOCKERHUB_TOKEN=
59
DOCKERHUB_USERNAME=
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/* eslint-disable @typescript-eslint/no-explicit-any */
2+
import { client, database, container } from "@my-workspace/data-access-azure-cosmos";
3+
import { verifyAzureB2CToken } from "@my-workspace/util-verify-token";
4+
5+
/**
6+
* Retrieves resources from Azure Cosmos DB based on the user's ID.
7+
* @param request - The request object.
8+
* @returns A response object containing the retrieved resources.
9+
*/
10+
export const GET = async function (request: Request) {
11+
try {
12+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
13+
const user:any = await verifyAzureB2CToken(request, "ReadUser");
14+
const { resources } = await container.items
15+
.query({
16+
query: "SELECT TOP 3 * from c WHERE c.userId = @userId ORDER BY c.timestamp DESC",
17+
parameters: [{ name: "@userId", value: user.oid }],
18+
})
19+
.fetchAll();
20+
21+
return new Response(JSON.stringify(resources));
22+
} catch (error) {
23+
return new Response(
24+
(error as Error).message || "Error during token verification",
25+
{ status: 401 }
26+
);
27+
}
28+
};
29+
30+
/**
31+
* Handles the POST request to create an item in Azure Cosmos DB.
32+
* @param request - The request object containing the HTTP request details.
33+
* @returns A Response object with the created item or an error message.
34+
*/
35+
export const POST = async function (request: Request) {
36+
try {
37+
const user: any = await verifyAzureB2CToken(request, "WriteUser");
38+
const data = await request.json();
39+
40+
const item = {
41+
userId: user.oid,
42+
...data,
43+
};
44+
45+
await container.items.create(item);
46+
47+
return new Response(JSON.stringify(item), { status: 201 });
48+
} catch (error) {
49+
return new Response(
50+
(error as Error).message || "Error during token verification",
51+
{ status: 401 }
52+
);
53+
}
54+
}

apps/msal-react-demo/src/app/api/azure-cosmos/todo_route.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.

apps/msal-react-demo/src/app/page.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
UnauthenticatedTemplate,
88
} from '@azure/msal-react';
99
import { AccountSummary } from '@my-workspace/ui';
10+
import { ButtonUserDataGet, ButtonUserDataPost } from '@my-workspace/feature-azure-cosmosdb';
1011

1112
const ButtonLogin = dynamic(() => import('@my-workspace/feature-msal-react').then(mod => mod.ButtonLogin), { ssr: false });
1213
const ButtonLogout = dynamic(() => import('@my-workspace/feature-msal-react').then(mod => mod.ButtonLogout), { ssr: false });
@@ -31,7 +32,11 @@ export default function Page() {
3132
</UnauthenticatedTemplate>
3233
</div>
3334
</div>
34-
<AccountSummary />
35+
<AuthenticatedTemplate>
36+
<AccountSummary />
37+
<ButtonUserDataGet />
38+
<ButtonUserDataPost />
39+
</AuthenticatedTemplate>
3540
</div>
3641
</div>
3742
);

libs/data-access/data-access-azure-cosmos/src/lib/data-access-azure-cosmos.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { CosmosClient } from "@azure/cosmos";
22

3-
const endpoint = process.env["AZURE_COSMOS_ENDPOINT"] || "";
4-
const key = process.env["AZURE_COSMOS_KEY"] || "";
5-
const databaseId = process.env["AZURE_COSMOS_DATABASE_ID"] || "";
6-
const containerId = process.env["AZURE_COSMOS_CONTAINER_ID"] || "";
3+
const endpoint = process.env["AZURE_COSMOSDB_ENDPOINT"] || "";
4+
const key = process.env["AZURE_COSMOSDB_KEY"] || "";
5+
const databaseId = process.env["AZURE_COSMOSDB_DATABASE_ID"] || "";
6+
const containerId = process.env["AZURE_COSMOSDB_CONTAINER_ID"] || "";
77

88
const client = new CosmosClient({ endpoint: endpoint, key: key });
99
const database = client.database(databaseId);

libs/data-access/data-access-msal-config/src/lib/data-access-msal-config.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ export const b2cPolicies = {
2828
};
2929

3030
export const loginConfig = {
31-
scopes: [`https://${process.env['NEXT_PUBLIC_AZURE_B2C_AUTHORITY']}.onmicrosoft.com/${process.env['NEXT_PUBLIC_AZURE_B2C_CLIENT_ID']}/ReadUser`]
31+
scopes: [
32+
`https://${process.env['NEXT_PUBLIC_AZURE_B2C_AUTHORITY']}.onmicrosoft.com/${process.env['NEXT_PUBLIC_AZURE_B2C_CLIENT_ID']}/ReadUser`,
33+
`https://${process.env['NEXT_PUBLIC_AZURE_B2C_AUTHORITY']}.onmicrosoft.com/${process.env['NEXT_PUBLIC_AZURE_B2C_CLIENT_ID']}/WriteUser`
34+
]
3235
};
3336

3437
// MSAL Configurationcc
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"presets": [
3+
[
4+
"@nx/react/babel",
5+
{
6+
"runtime": "automatic",
7+
"useBuiltIns": "usage"
8+
}
9+
]
10+
],
11+
"plugins": []
12+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"extends": ["plugin:@nx/react", "../../../.eslintrc.json"],
3+
"ignorePatterns": ["!**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7+
"rules": {}
8+
},
9+
{
10+
"files": ["*.ts", "*.tsx"],
11+
"rules": {}
12+
},
13+
{
14+
"files": ["*.js", "*.jsx"],
15+
"rules": {}
16+
}
17+
]
18+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# feature-azure-cosmosdb
2+
3+
This library was generated with [Nx](https://nx.dev).
4+
5+
## Running unit tests
6+
7+
Run `nx test feature-azure-cosmosdb` to execute the unit tests via [Jest](https://jestjs.io).
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "feature-azure-cosmosdb",
3+
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
4+
"sourceRoot": "libs/feature/feature-azure-cosmosdb/src",
5+
"projectType": "library",
6+
"tags": [],
7+
"// targets": "to see all targets run: nx show project feature-azure-cosmosdb --web",
8+
"targets": {}
9+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './lib/feature-azure-cosmosdb';
2+
export * from './lib/user-data-get/user-data-get';
3+
export * from './lib/user-data-post/user-data-post';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Replace this with your own classes
3+
*
4+
* e.g.
5+
* .container {
6+
* }
7+
*/
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { render } from '@testing-library/react';
2+
3+
import FeatureAzureCosmosdb from './feature-azure-cosmosdb';
4+
5+
describe('FeatureAzureCosmosdb', () => {
6+
it('should render successfully', () => {
7+
const { baseElement } = render(<FeatureAzureCosmosdb />);
8+
expect(baseElement).toBeTruthy();
9+
});
10+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import styles from './feature-azure-cosmosdb.module.css';
2+
3+
/* eslint-disable-next-line */
4+
export interface FeatureAzureCosmosdbProps {}
5+
6+
export function FeatureAzureCosmosdb(props: FeatureAzureCosmosdbProps) {
7+
return (
8+
<div className={styles['container']}>
9+
<h1>Welcome to FeatureAzureCosmosdb!</h1>
10+
</div>
11+
);
12+
}
13+
14+
export default FeatureAzureCosmosdb;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Replace this with your own classes
3+
*
4+
* e.g.
5+
* .container {
6+
* }
7+
*/
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { render } from '@testing-library/react';
2+
3+
import UserDataGet from './user-data-get';
4+
5+
describe('UserDataGet', () => {
6+
it('should render successfully', () => {
7+
const { baseElement } = render(<UserDataGet />);
8+
expect(baseElement).toBeTruthy();
9+
});
10+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { useState } from 'react';
2+
import { useMsal } from "@azure/msal-react";
3+
import { PublicClientApplication } from '@azure/msal-browser';
4+
import { getToken } from '@my-workspace/data-access-auth';
5+
6+
/* eslint-disable-next-line */
7+
export interface ButtonUserDataGetProps {}
8+
9+
export function ButtonUserDataGet(props: ButtonUserDataGetProps) {
10+
const { inProgress, instance, accounts } = useMsal();
11+
const [data, setData] = useState(null);
12+
13+
const handleClick = async () => {
14+
if (inProgress === 'none' && accounts.length > 0) {
15+
const token = await getToken(instance as PublicClientApplication, accounts[0]);
16+
if (token) {
17+
try {
18+
const res = await fetch('/api/azure-cosmos', {
19+
headers: {
20+
Authorization: `Bearer ${token}`
21+
}
22+
});
23+
const data = await res.json();
24+
setData(data); // set the state
25+
} catch (error) {
26+
console.error('Error:', error);
27+
}
28+
}
29+
}
30+
};
31+
32+
return (
33+
<div className="p-4 bg-gray-100 rounded-md">
34+
<h1 className="text-2xl font-bold mb-4">Welcome to ButtonUserDataGet!</h1>
35+
<button
36+
onClick={handleClick}
37+
className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-700"
38+
>
39+
Get Data
40+
</button>
41+
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
42+
</div>
43+
);
44+
}
45+
46+
export default ButtonUserDataGet;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Replace this with your own classes
3+
*
4+
* e.g.
5+
* .container {
6+
* }
7+
*/
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { render } from '@testing-library/react';
2+
3+
import UserDataPost from './user-data-post';
4+
5+
describe('UserDataPost', () => {
6+
it('should render successfully', () => {
7+
const { baseElement } = render(<UserDataPost />);
8+
expect(baseElement).toBeTruthy();
9+
});
10+
});

0 commit comments

Comments
 (0)