Skip to content

Commit 266c204

Browse files
authored
Merge pull request #8 from pattern-tech/feat/ai-sdk-provider
AI SDK provider
2 parents a4a2369 + 0ba7d5b commit 266c204

16 files changed

Lines changed: 791 additions & 88 deletions

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ POSTGRES_URL=****
1919
NEXT_PUBLIC_PROJECT_ID=
2020
NEXTAUTH_SECRET=
2121

22-
SIWE_VERIFICATION_API=
22+
PATTERN_CORE_ENDPOINT=

app/(auth)/adapter.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { Ok, Err, Result } from 'ts-results-es';
2+
3+
import { extractErrorMessageOrDefault } from '@/lib/utils';
4+
5+
import {
6+
ApiCreateProjectResponse,
7+
ApiCreateWorkspaceResponse,
8+
ApiListAllProjectsResponse,
9+
ApiListAllWorkspacesResponse,
10+
} from './types';
11+
12+
const patternCoreEndpoint = process.env.PATTERN_CORE_ENDPOINT;
13+
if (!patternCoreEndpoint) {
14+
throw new Error('PATTERN_CORE_ENDPOINT is not set');
15+
}
16+
17+
/**
18+
* Get all workspaces
19+
* @param accessToken
20+
* @returns result containing all workspaces of current user
21+
*/
22+
export const getAllWorkspaces = async (
23+
accessToken: string,
24+
): Promise<Result<ApiListAllWorkspacesResponse, string>> => {
25+
try {
26+
const allWorkspacesResponse = await fetch(
27+
`${patternCoreEndpoint}/workspace`,
28+
{
29+
headers: {
30+
Authorization: `Bearer ${accessToken}`,
31+
'Content-Type': 'application/json',
32+
},
33+
},
34+
);
35+
36+
if (allWorkspacesResponse.ok) {
37+
const allWorkspaces: ApiListAllWorkspacesResponse = (
38+
await allWorkspacesResponse.json()
39+
).data;
40+
41+
return Ok(allWorkspaces);
42+
}
43+
return Err(
44+
`Fetching workspaces failed with error code ${allWorkspacesResponse.status}`,
45+
);
46+
} catch (error) {
47+
return Err(extractErrorMessageOrDefault(error));
48+
}
49+
};
50+
51+
/**
52+
* Create a workspace
53+
* @param accessToken
54+
* @returns result containing the created workspace
55+
*/
56+
export const createWorkspace = async (
57+
accessToken: string,
58+
): Promise<Result<ApiCreateWorkspaceResponse, string>> => {
59+
try {
60+
const response = await fetch(`${patternCoreEndpoint}/workspace`, {
61+
method: 'POST',
62+
headers: {
63+
Authorization: `Bearer ${accessToken}`,
64+
'Content-Type': 'application/json',
65+
},
66+
body: JSON.stringify({
67+
name: 'Default Workspace',
68+
}),
69+
});
70+
71+
if (response.ok) {
72+
const workspace: ApiCreateWorkspaceResponse = (await response.json())
73+
.data;
74+
75+
return Ok(workspace);
76+
}
77+
return Err(`Creating workspace failed with error code ${response.status}`);
78+
} catch (error) {
79+
return Err(extractErrorMessageOrDefault(error));
80+
}
81+
};
82+
83+
/**
84+
* Get all projects
85+
* @param accessToken
86+
* @returns result containing all projects of current user
87+
*/
88+
export const getAllProjects = async (
89+
accessToken: string,
90+
): Promise<Result<ApiListAllProjectsResponse, string>> => {
91+
try {
92+
const allProjectsResponse = await fetch(`${patternCoreEndpoint}/project`, {
93+
headers: {
94+
Authorization: `Bearer ${accessToken}`,
95+
'Content-Type': 'application/json',
96+
},
97+
});
98+
if (allProjectsResponse.ok) {
99+
const allProjects: ApiListAllProjectsResponse = (
100+
await allProjectsResponse.json()
101+
).data;
102+
103+
return Ok(allProjects);
104+
}
105+
return Err(
106+
`Fetching projects failed with error code ${allProjectsResponse.status}`,
107+
);
108+
} catch (error) {
109+
return Err(extractErrorMessageOrDefault(error));
110+
}
111+
};
112+
113+
/**
114+
* Create a project in a workspace
115+
* @param accessToken
116+
* @param workspaceId
117+
* @returns result containing the created project
118+
*/
119+
export const createProjectInWorkspace = async (
120+
accessToken: string,
121+
workspaceId: string,
122+
): Promise<Result<ApiCreateProjectResponse, string>> => {
123+
try {
124+
const response = await fetch(`${patternCoreEndpoint}/project`, {
125+
method: 'POST',
126+
headers: {
127+
Authorization: `Bearer ${accessToken}`,
128+
'Content-Type': 'application/json',
129+
},
130+
body: JSON.stringify({
131+
name: 'Default Project',
132+
workspace_id: workspaceId,
133+
}),
134+
});
135+
136+
if (response.ok) {
137+
const project: ApiCreateProjectResponse = (await response.json()).data;
138+
139+
return Ok(project);
140+
}
141+
return Err(`Creating project failed with error code ${response.status}`);
142+
} catch (error) {
143+
return Err(extractErrorMessageOrDefault(error));
144+
}
145+
};

app/(auth)/auth.config.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
import type { SIWESession } from '@reown/appkit-siwe';
1+
import { type SIWESession } from '@reown/appkit-siwe';
22
import type { NextAuthConfig } from 'next-auth';
33

4+
import { fetchSessionPrerequisites } from './service';
5+
46
declare module 'next-auth' {
57
interface Session extends SIWESession {
68
address: string;
79
chainId: number;
810
accessToken: string;
11+
workspaceId: string;
12+
projectId: string;
913
}
1014
interface User {
1115
accessToken: string;
@@ -43,7 +47,7 @@ export const authConfig = {
4347
}
4448
return token;
4549
},
46-
session({ session, token }) {
50+
async session({ session, token }) {
4751
if (!token.sub) {
4852
return session;
4953
}
@@ -59,6 +63,20 @@ export const authConfig = {
5963
session.address = address;
6064
session.chainId = Number.parseInt(chainId, 10);
6165
session.accessToken = wrappedJWT;
66+
67+
const sessionPrerequisitesResult =
68+
await fetchSessionPrerequisites(wrappedJWT);
69+
70+
if (sessionPrerequisitesResult.isErr()) {
71+
throw new Error(
72+
'Cannot fetch session prerequisites (default workspace and project)',
73+
{ cause: sessionPrerequisitesResult.error },
74+
);
75+
}
76+
77+
const [workspaceId, projectId] = sessionPrerequisitesResult.value;
78+
session.workspaceId = workspaceId;
79+
session.projectId = projectId;
6280
}
6381

6482
return session;

app/(auth)/auth.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ if (!projectId) {
2323
throw new Error('NEXT_PUBLIC_PROJECT_ID is not set');
2424
}
2525

26-
const siweVerificationApi = process.env.SIWE_VERIFICATION_API;
27-
if (!siweVerificationApi) {
28-
throw new Error('SIWE_VERIFICATION_API is not set');
26+
const patternCoreEndpoint = process.env.PATTERN_CORE_ENDPOINT;
27+
if (!patternCoreEndpoint) {
28+
throw new Error('PATTERN_CORE_ENDPOINT is not set');
2929
}
30+
const siweVerificationApi = `${patternCoreEndpoint}/auth/verify`;
3031

3132
const providers = [
3233
credentialsProvider({

app/(auth)/service.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { Err, Ok, Result } from 'ts-results-es';
2+
3+
import {
4+
createProjectInWorkspace,
5+
createWorkspace,
6+
getAllProjects,
7+
getAllWorkspaces,
8+
} from './adapter';
9+
10+
const patternCoreEndpoint = process.env.PATTERN_CORE_ENDPOINT;
11+
if (!patternCoreEndpoint) {
12+
throw new Error('PATTERN_CORE_ENDPOINT is not set');
13+
}
14+
15+
/**
16+
* Checks if the default workspace and project exist, if not, creates them
17+
* @param accessToken
18+
* @returns result containing default workspace and project ids
19+
*/
20+
export const fetchSessionPrerequisites = async (
21+
accessToken: string,
22+
): Promise<Result<[string, string], string>> => {
23+
const allWorkspacesResult = await getAllWorkspaces(accessToken);
24+
if (allWorkspacesResult.isErr()) {
25+
return Err(allWorkspacesResult.error);
26+
}
27+
28+
const allWorkspaces = allWorkspacesResult.value;
29+
let workspace = allWorkspaces[0];
30+
31+
if (!workspace) {
32+
const createWorkspaceResult = await createWorkspace(accessToken);
33+
if (createWorkspaceResult.isErr()) {
34+
return Err(createWorkspaceResult.error);
35+
}
36+
37+
workspace = createWorkspaceResult.value;
38+
}
39+
40+
const allProjectsResult = await getAllProjects(accessToken);
41+
if (allProjectsResult.isErr()) {
42+
return Err(allProjectsResult.error);
43+
}
44+
45+
const allProjects = allProjectsResult.value;
46+
let project = allProjects.find(
47+
(project) => project.workspace_id === workspace.id,
48+
);
49+
if (!project) {
50+
const createProjectResult = await createProjectInWorkspace(
51+
accessToken,
52+
workspace.id,
53+
);
54+
if (createProjectResult.isErr()) {
55+
return Err(createProjectResult.error);
56+
}
57+
58+
project = createProjectResult.value;
59+
}
60+
61+
return Ok([workspace.id, project.id]);
62+
};

app/(auth)/types.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
export interface Workspace {
2+
id: string;
3+
name: string;
4+
}
5+
6+
export interface Project {
7+
id: string;
8+
name: string;
9+
workspace_id: string;
10+
}
11+
12+
export type ApiListAllWorkspacesResponse = Workspace[];
13+
export type ApiCreateWorkspaceResponse = Workspace;
14+
15+
export type ApiListAllProjectsResponse = Project[];
16+
export type ApiCreateProjectResponse = Project;

0 commit comments

Comments
 (0)