@weirdfingers/boards 0.1.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.
- package/dist/index.d.mts +508 -0
- package/dist/index.d.ts +508 -0
- package/dist/index.js +1150 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1096 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +49 -0
- package/src/auth/context.tsx +104 -0
- package/src/auth/hooks/useAuth.ts +6 -0
- package/src/auth/providers/__tests__/none.test.ts +187 -0
- package/src/auth/providers/base.ts +62 -0
- package/src/auth/providers/none.ts +157 -0
- package/src/auth/types.ts +67 -0
- package/src/config/ApiConfigContext.tsx +47 -0
- package/src/graphql/client.ts +130 -0
- package/src/graphql/operations.ts +293 -0
- package/src/hooks/useBoard.ts +323 -0
- package/src/hooks/useBoards.ts +138 -0
- package/src/hooks/useGeneration.ts +429 -0
- package/src/hooks/useGenerators.ts +44 -0
- package/src/index.test.ts +7 -0
- package/src/index.ts +25 -0
- package/src/providers/BoardsProvider.tsx +68 -0
- package/src/test-setup.ts +29 -0
- package/tsconfig.json +37 -0
- package/tsup.config.ts +11 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* No-auth provider for local development without authentication.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { BaseAuthProvider } from './base';
|
|
6
|
+
import { AuthState, User, AuthProviderConfig } from '../types';
|
|
7
|
+
|
|
8
|
+
interface NoAuthConfig extends AuthProviderConfig {
|
|
9
|
+
/**
|
|
10
|
+
* Default user ID for development.
|
|
11
|
+
* Defaults to 'dev-user'.
|
|
12
|
+
*/
|
|
13
|
+
defaultUserId?: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Default user email for development.
|
|
17
|
+
* Defaults to 'dev@example.com'.
|
|
18
|
+
*/
|
|
19
|
+
defaultEmail?: string;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Default display name for development.
|
|
23
|
+
* Defaults to 'Development User'.
|
|
24
|
+
*/
|
|
25
|
+
defaultDisplayName?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export class NoAuthProvider extends BaseAuthProvider {
|
|
29
|
+
protected config: NoAuthConfig;
|
|
30
|
+
private listeners: ((state: AuthState) => void)[] = [];
|
|
31
|
+
private currentState: AuthState;
|
|
32
|
+
private defaultUser: User;
|
|
33
|
+
|
|
34
|
+
constructor(config: NoAuthConfig = {}) {
|
|
35
|
+
super(config);
|
|
36
|
+
|
|
37
|
+
// Production safety check
|
|
38
|
+
const nodeEnv = typeof process !== 'undefined' ? process.env?.NODE_ENV : '';
|
|
39
|
+
const isDevelopment = nodeEnv === 'development' || nodeEnv === '' || nodeEnv === 'test';
|
|
40
|
+
|
|
41
|
+
if (!isDevelopment) {
|
|
42
|
+
const error = new Error(
|
|
43
|
+
'NoAuthProvider cannot be used in production environments. ' +
|
|
44
|
+
'Please configure a proper authentication provider (JWT, Supabase, Clerk, etc.)'
|
|
45
|
+
);
|
|
46
|
+
console.error('🚨 SECURITY ERROR:', error.message);
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.config = {
|
|
51
|
+
defaultUserId: 'dev-user',
|
|
52
|
+
defaultEmail: 'dev@example.com',
|
|
53
|
+
defaultDisplayName: 'Development User',
|
|
54
|
+
...config,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
this.defaultUser = {
|
|
58
|
+
id: this.config.defaultUserId!,
|
|
59
|
+
email: this.config.defaultEmail!,
|
|
60
|
+
name: this.config.defaultDisplayName,
|
|
61
|
+
avatar: undefined,
|
|
62
|
+
metadata: { provider: 'none' },
|
|
63
|
+
credits: {
|
|
64
|
+
balance: 1000,
|
|
65
|
+
reserved: 0,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
this.currentState = {
|
|
70
|
+
user: this.defaultUser,
|
|
71
|
+
status: 'authenticated', // Always authenticated in no-auth mode
|
|
72
|
+
signIn: this.signIn.bind(this),
|
|
73
|
+
signOut: this.signOut.bind(this),
|
|
74
|
+
getToken: this.getToken.bind(this),
|
|
75
|
+
refreshToken: this.refreshToken.bind(this),
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Use structured warning instead of console.warn
|
|
79
|
+
if (console.warn) {
|
|
80
|
+
console.warn(
|
|
81
|
+
'🚨 [AUTH] NoAuthProvider is active - authentication is disabled!',
|
|
82
|
+
{
|
|
83
|
+
message: 'This should ONLY be used in development environments',
|
|
84
|
+
environment: nodeEnv || 'unknown',
|
|
85
|
+
provider: 'none',
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async initialize(): Promise<void> {
|
|
92
|
+
// No initialization needed - always authenticated
|
|
93
|
+
this.updateState({ user: this.defaultUser, status: 'authenticated' });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async getAuthState(): Promise<AuthState> {
|
|
97
|
+
return this.currentState;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async signIn(): Promise<void> {
|
|
101
|
+
// No-op in no-auth mode - already signed in
|
|
102
|
+
if (console.info) {
|
|
103
|
+
console.info('[AUTH] SignIn called in no-auth mode - no action taken', {
|
|
104
|
+
provider: 'none',
|
|
105
|
+
action: 'signIn',
|
|
106
|
+
status: 'ignored'
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async signOut(): Promise<void> {
|
|
112
|
+
// No-op in no-auth mode - can't sign out
|
|
113
|
+
if (console.info) {
|
|
114
|
+
console.info('[AUTH] SignOut called in no-auth mode - no action taken', {
|
|
115
|
+
provider: 'none',
|
|
116
|
+
action: 'signOut',
|
|
117
|
+
status: 'ignored'
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async getToken(): Promise<string | null> {
|
|
123
|
+
// Return a fake development token
|
|
124
|
+
return 'dev-token|no-auth-mode|always-valid';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async refreshToken(): Promise<string | null> {
|
|
128
|
+
// Return the same fake token since it doesn't expire
|
|
129
|
+
return 'dev-token|no-auth-mode|always-valid';
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async getUser(): Promise<User | null> {
|
|
133
|
+
return this.defaultUser;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
onAuthStateChange(callback: (state: AuthState) => void): () => void {
|
|
137
|
+
// Call immediately with current state
|
|
138
|
+
callback(this.currentState);
|
|
139
|
+
|
|
140
|
+
this.listeners.push(callback);
|
|
141
|
+
return () => {
|
|
142
|
+
const index = this.listeners.indexOf(callback);
|
|
143
|
+
if (index > -1) {
|
|
144
|
+
this.listeners.splice(index, 1);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async destroy(): Promise<void> {
|
|
150
|
+
this.listeners = [];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private updateState(updates: Partial<AuthState>): void {
|
|
154
|
+
this.currentState = { ...this.currentState, ...updates };
|
|
155
|
+
this.listeners.forEach(listener => listener(this.currentState));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core authentication types and interfaces.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface User {
|
|
6
|
+
id: string;
|
|
7
|
+
email: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
avatar?: string;
|
|
10
|
+
metadata: Record<string, unknown>;
|
|
11
|
+
credits: {
|
|
12
|
+
balance: number;
|
|
13
|
+
reserved: number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AuthProvider {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
type: 'oauth' | 'email' | 'magic-link' | 'custom';
|
|
21
|
+
config: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SignInOptions {
|
|
25
|
+
provider?: string;
|
|
26
|
+
redirectTo?: string;
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface AuthState {
|
|
31
|
+
user: User | null;
|
|
32
|
+
status: 'loading' | 'authenticated' | 'unauthenticated' | 'error';
|
|
33
|
+
signIn: (provider?: AuthProvider, options?: SignInOptions) => Promise<void>;
|
|
34
|
+
signOut: () => Promise<void>;
|
|
35
|
+
getToken: () => Promise<string | null>;
|
|
36
|
+
refreshToken: () => Promise<string | null>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface AuthProviderConfig {
|
|
40
|
+
/**
|
|
41
|
+
* Tenant identifier for multi-tenant applications.
|
|
42
|
+
* If not provided, defaults to single-tenant mode.
|
|
43
|
+
*/
|
|
44
|
+
tenantId?: string;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Additional configuration specific to the auth provider.
|
|
48
|
+
*/
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface AuthContextValue extends AuthState {
|
|
53
|
+
/**
|
|
54
|
+
* Whether the auth system is initializing.
|
|
55
|
+
*/
|
|
56
|
+
isInitializing: boolean;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Any error that occurred during authentication.
|
|
60
|
+
*/
|
|
61
|
+
error: Error | null;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Clear any authentication errors.
|
|
65
|
+
*/
|
|
66
|
+
clearError: () => void;
|
|
67
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API configuration context for providing backend URLs to hooks.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createContext, useContext, ReactNode } from "react";
|
|
6
|
+
|
|
7
|
+
export interface ApiConfig {
|
|
8
|
+
/**
|
|
9
|
+
* Base URL for the backend API (e.g., "http://localhost:8088")
|
|
10
|
+
* Used for REST endpoints like SSE streams
|
|
11
|
+
*/
|
|
12
|
+
apiUrl: string;
|
|
13
|
+
/**
|
|
14
|
+
* GraphQL endpoint URL (e.g., "http://localhost:8088/graphql")
|
|
15
|
+
*/
|
|
16
|
+
graphqlUrl: string;
|
|
17
|
+
/**
|
|
18
|
+
* WebSocket URL for GraphQL subscriptions
|
|
19
|
+
*/
|
|
20
|
+
subscriptionUrl?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const ApiConfigContext = createContext<ApiConfig | null>(null);
|
|
24
|
+
|
|
25
|
+
interface ApiConfigProviderProps {
|
|
26
|
+
children: ReactNode;
|
|
27
|
+
config: ApiConfig;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function ApiConfigProvider({
|
|
31
|
+
children,
|
|
32
|
+
config,
|
|
33
|
+
}: ApiConfigProviderProps) {
|
|
34
|
+
return (
|
|
35
|
+
<ApiConfigContext.Provider value={config}>
|
|
36
|
+
{children}
|
|
37
|
+
</ApiConfigContext.Provider>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function useApiConfig(): ApiConfig {
|
|
42
|
+
const context = useContext(ApiConfigContext);
|
|
43
|
+
if (!context) {
|
|
44
|
+
throw new Error("useApiConfig must be used within ApiConfigProvider");
|
|
45
|
+
}
|
|
46
|
+
return context;
|
|
47
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL client configuration with authentication.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
createClient,
|
|
7
|
+
fetchExchange,
|
|
8
|
+
cacheExchange,
|
|
9
|
+
subscriptionExchange,
|
|
10
|
+
makeOperation,
|
|
11
|
+
} from "urql";
|
|
12
|
+
import { authExchange } from "@urql/exchange-auth";
|
|
13
|
+
import { createClient as createWSClient } from "graphql-ws";
|
|
14
|
+
|
|
15
|
+
interface AuthState {
|
|
16
|
+
getToken(): Promise<string | null>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface ClientConfig {
|
|
20
|
+
url: string;
|
|
21
|
+
subscriptionUrl?: string;
|
|
22
|
+
auth: AuthState;
|
|
23
|
+
tenantId?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function createGraphQLClient({
|
|
27
|
+
url,
|
|
28
|
+
subscriptionUrl,
|
|
29
|
+
auth,
|
|
30
|
+
tenantId,
|
|
31
|
+
}: ClientConfig) {
|
|
32
|
+
const wsClient = subscriptionUrl
|
|
33
|
+
? createWSClient({
|
|
34
|
+
url: subscriptionUrl,
|
|
35
|
+
connectionParams: async () => {
|
|
36
|
+
const token = await auth.getToken();
|
|
37
|
+
const headers: Record<string, string> = {};
|
|
38
|
+
|
|
39
|
+
if (token) {
|
|
40
|
+
headers.Authorization = `Bearer ${token}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (tenantId) {
|
|
44
|
+
headers["X-Tenant"] = tenantId;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return headers;
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
: null;
|
|
51
|
+
|
|
52
|
+
return createClient({
|
|
53
|
+
url,
|
|
54
|
+
exchanges: [
|
|
55
|
+
cacheExchange,
|
|
56
|
+
authExchange(async () => {
|
|
57
|
+
// Initialize auth state by fetching token
|
|
58
|
+
let token = await auth.getToken();
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
addAuthToOperation: (operation) => {
|
|
62
|
+
// Build headers
|
|
63
|
+
const headers: Record<string, string> = {};
|
|
64
|
+
|
|
65
|
+
if (token) {
|
|
66
|
+
headers.Authorization = `Bearer ${token}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (tenantId) {
|
|
70
|
+
headers["X-Tenant"] = tenantId;
|
|
71
|
+
}
|
|
72
|
+
const fetchOptions =
|
|
73
|
+
typeof operation.context.fetchOptions === "function"
|
|
74
|
+
? operation.context.fetchOptions()
|
|
75
|
+
: operation.context.fetchOptions || {};
|
|
76
|
+
|
|
77
|
+
// Add headers to operation context
|
|
78
|
+
return makeOperation(operation.kind, operation, {
|
|
79
|
+
...operation.context,
|
|
80
|
+
fetchOptions: {
|
|
81
|
+
...operation.context.fetchOptions,
|
|
82
|
+
headers: {
|
|
83
|
+
...fetchOptions.headers,
|
|
84
|
+
...headers,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
didAuthError: (error) => {
|
|
91
|
+
// Check if error is auth-related
|
|
92
|
+
return error.graphQLErrors.some(
|
|
93
|
+
(e) =>
|
|
94
|
+
e.extensions?.code === "UNAUTHENTICATED" ||
|
|
95
|
+
e.extensions?.code === "UNAUTHORIZED"
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
willAuthError: () => {
|
|
100
|
+
// We don't preemptively block requests
|
|
101
|
+
return false;
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
refreshAuth: async () => {
|
|
105
|
+
// Re-fetch token on auth error and update the closure variable
|
|
106
|
+
token = await auth.getToken();
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}),
|
|
110
|
+
fetchExchange,
|
|
111
|
+
...(wsClient
|
|
112
|
+
? [
|
|
113
|
+
subscriptionExchange({
|
|
114
|
+
forwardSubscription: (operation) => ({
|
|
115
|
+
subscribe: (sink) => ({
|
|
116
|
+
unsubscribe: wsClient.subscribe(
|
|
117
|
+
{
|
|
118
|
+
query: operation.query || "",
|
|
119
|
+
variables: operation.variables,
|
|
120
|
+
},
|
|
121
|
+
sink
|
|
122
|
+
),
|
|
123
|
+
}),
|
|
124
|
+
}),
|
|
125
|
+
}),
|
|
126
|
+
]
|
|
127
|
+
: []),
|
|
128
|
+
],
|
|
129
|
+
});
|
|
130
|
+
}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL queries and mutations for the Boards API.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { gql } from "urql";
|
|
6
|
+
|
|
7
|
+
// Fragments for reusable query parts
|
|
8
|
+
export const USER_FRAGMENT = gql`
|
|
9
|
+
fragment UserFragment on User {
|
|
10
|
+
id
|
|
11
|
+
email
|
|
12
|
+
displayName
|
|
13
|
+
avatarUrl
|
|
14
|
+
createdAt
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
export const BOARD_FRAGMENT = gql`
|
|
19
|
+
fragment BoardFragment on Board {
|
|
20
|
+
id
|
|
21
|
+
tenantId
|
|
22
|
+
ownerId
|
|
23
|
+
title
|
|
24
|
+
description
|
|
25
|
+
isPublic
|
|
26
|
+
settings
|
|
27
|
+
metadata
|
|
28
|
+
createdAt
|
|
29
|
+
updatedAt
|
|
30
|
+
generationCount
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
export const GENERATION_FRAGMENT = gql`
|
|
35
|
+
fragment GenerationFragment on Generation {
|
|
36
|
+
id
|
|
37
|
+
boardId
|
|
38
|
+
userId
|
|
39
|
+
generatorName
|
|
40
|
+
artifactType
|
|
41
|
+
status
|
|
42
|
+
progress
|
|
43
|
+
storageUrl
|
|
44
|
+
thumbnailUrl
|
|
45
|
+
inputParams
|
|
46
|
+
outputMetadata
|
|
47
|
+
errorMessage
|
|
48
|
+
createdAt
|
|
49
|
+
updatedAt
|
|
50
|
+
completedAt
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
// Auth queries
|
|
55
|
+
export const GET_CURRENT_USER = gql`
|
|
56
|
+
${USER_FRAGMENT}
|
|
57
|
+
query GetCurrentUser {
|
|
58
|
+
me {
|
|
59
|
+
...UserFragment
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
// Board queries
|
|
65
|
+
export const GET_BOARDS = gql`
|
|
66
|
+
${BOARD_FRAGMENT}
|
|
67
|
+
${USER_FRAGMENT}
|
|
68
|
+
query GetBoards($limit: Int, $offset: Int) {
|
|
69
|
+
myBoards(limit: $limit, offset: $offset) {
|
|
70
|
+
...BoardFragment
|
|
71
|
+
owner {
|
|
72
|
+
...UserFragment
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
`;
|
|
77
|
+
|
|
78
|
+
export const GET_BOARD = gql`
|
|
79
|
+
${BOARD_FRAGMENT}
|
|
80
|
+
${USER_FRAGMENT}
|
|
81
|
+
${GENERATION_FRAGMENT}
|
|
82
|
+
query GetBoard($id: UUID!) {
|
|
83
|
+
board(id: $id) {
|
|
84
|
+
...BoardFragment
|
|
85
|
+
owner {
|
|
86
|
+
...UserFragment
|
|
87
|
+
}
|
|
88
|
+
members {
|
|
89
|
+
id
|
|
90
|
+
boardId
|
|
91
|
+
userId
|
|
92
|
+
role
|
|
93
|
+
invitedBy
|
|
94
|
+
joinedAt
|
|
95
|
+
user {
|
|
96
|
+
...UserFragment
|
|
97
|
+
}
|
|
98
|
+
inviter {
|
|
99
|
+
...UserFragment
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
generations(limit: 10) {
|
|
103
|
+
...GenerationFragment
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
`;
|
|
108
|
+
|
|
109
|
+
// Generator queries
|
|
110
|
+
export const GET_GENERATORS = gql`
|
|
111
|
+
query GetGenerators($artifactType: String) {
|
|
112
|
+
generators(artifactType: $artifactType) {
|
|
113
|
+
name
|
|
114
|
+
description
|
|
115
|
+
artifactType
|
|
116
|
+
inputSchema
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
`;
|
|
120
|
+
|
|
121
|
+
// Generation queries
|
|
122
|
+
export const GET_GENERATIONS = gql`
|
|
123
|
+
${GENERATION_FRAGMENT}
|
|
124
|
+
query GetGenerations($boardId: UUID, $limit: Int, $offset: Int) {
|
|
125
|
+
generations(boardId: $boardId, limit: $limit, offset: $offset) {
|
|
126
|
+
...GenerationFragment
|
|
127
|
+
board {
|
|
128
|
+
id
|
|
129
|
+
title
|
|
130
|
+
}
|
|
131
|
+
user {
|
|
132
|
+
...UserFragment
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
`;
|
|
137
|
+
|
|
138
|
+
export const GET_GENERATION = gql`
|
|
139
|
+
${GENERATION_FRAGMENT}
|
|
140
|
+
query GetGeneration($id: UUID!) {
|
|
141
|
+
generation(id: $id) {
|
|
142
|
+
...GenerationFragment
|
|
143
|
+
board {
|
|
144
|
+
...BoardFragment
|
|
145
|
+
}
|
|
146
|
+
user {
|
|
147
|
+
...UserFragment
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
`;
|
|
152
|
+
|
|
153
|
+
// Board mutations
|
|
154
|
+
export const CREATE_BOARD = gql`
|
|
155
|
+
${BOARD_FRAGMENT}
|
|
156
|
+
${USER_FRAGMENT}
|
|
157
|
+
mutation CreateBoard($input: CreateBoardInput!) {
|
|
158
|
+
createBoard(input: $input) {
|
|
159
|
+
...BoardFragment
|
|
160
|
+
owner {
|
|
161
|
+
...UserFragment
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
`;
|
|
166
|
+
|
|
167
|
+
export const UPDATE_BOARD = gql`
|
|
168
|
+
${BOARD_FRAGMENT}
|
|
169
|
+
mutation UpdateBoard($id: UUID!, $input: UpdateBoardInput!) {
|
|
170
|
+
updateBoard(id: $id, input: $input) {
|
|
171
|
+
...BoardFragment
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
`;
|
|
175
|
+
|
|
176
|
+
export const DELETE_BOARD = gql`
|
|
177
|
+
mutation DeleteBoard($id: UUID!) {
|
|
178
|
+
deleteBoard(id: $id) {
|
|
179
|
+
success
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
`;
|
|
183
|
+
|
|
184
|
+
// Board member mutations
|
|
185
|
+
export const ADD_BOARD_MEMBER = gql`
|
|
186
|
+
mutation AddBoardMember($boardId: UUID!, $email: String!, $role: BoardRole!) {
|
|
187
|
+
addBoardMember(boardId: $boardId, email: $email, role: $role) {
|
|
188
|
+
id
|
|
189
|
+
boardId
|
|
190
|
+
userId
|
|
191
|
+
role
|
|
192
|
+
invitedBy
|
|
193
|
+
joinedAt
|
|
194
|
+
user {
|
|
195
|
+
...UserFragment
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
`;
|
|
200
|
+
|
|
201
|
+
export const UPDATE_BOARD_MEMBER_ROLE = gql`
|
|
202
|
+
mutation UpdateBoardMemberRole($id: UUID!, $role: BoardRole!) {
|
|
203
|
+
updateBoardMemberRole(id: $id, role: $role) {
|
|
204
|
+
id
|
|
205
|
+
role
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
`;
|
|
209
|
+
|
|
210
|
+
export const REMOVE_BOARD_MEMBER = gql`
|
|
211
|
+
mutation RemoveBoardMember($id: UUID!) {
|
|
212
|
+
removeBoardMember(id: $id) {
|
|
213
|
+
success
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
`;
|
|
217
|
+
|
|
218
|
+
// Generation mutations
|
|
219
|
+
export const CREATE_GENERATION = gql`
|
|
220
|
+
${GENERATION_FRAGMENT}
|
|
221
|
+
mutation CreateGeneration($input: CreateGenerationInput!) {
|
|
222
|
+
createGeneration(input: $input) {
|
|
223
|
+
...GenerationFragment
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
`;
|
|
227
|
+
|
|
228
|
+
export const CANCEL_GENERATION = gql`
|
|
229
|
+
mutation CancelGeneration($id: UUID!) {
|
|
230
|
+
cancelGeneration(id: $id) {
|
|
231
|
+
id
|
|
232
|
+
status
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
`;
|
|
236
|
+
|
|
237
|
+
export const RETRY_GENERATION = gql`
|
|
238
|
+
${GENERATION_FRAGMENT}
|
|
239
|
+
mutation RetryGeneration($id: UUID!) {
|
|
240
|
+
retryGeneration(id: $id) {
|
|
241
|
+
...GenerationFragment
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
`;
|
|
245
|
+
|
|
246
|
+
// Input types (these should match your backend GraphQL schema)
|
|
247
|
+
export interface CreateBoardInput {
|
|
248
|
+
title: string;
|
|
249
|
+
description?: string;
|
|
250
|
+
isPublic?: boolean;
|
|
251
|
+
settings?: Record<string, unknown>;
|
|
252
|
+
metadata?: Record<string, unknown>;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export interface UpdateBoardInput {
|
|
256
|
+
title?: string;
|
|
257
|
+
description?: string;
|
|
258
|
+
isPublic?: boolean;
|
|
259
|
+
settings?: Record<string, unknown>;
|
|
260
|
+
metadata?: Record<string, unknown>;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export interface CreateGenerationInput {
|
|
264
|
+
boardId: string;
|
|
265
|
+
generatorName: string;
|
|
266
|
+
artifactType: ArtifactType; // Allow string for flexibility with new types
|
|
267
|
+
inputParams: Record<string, unknown>;
|
|
268
|
+
metadata?: Record<string, unknown>;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Enums (should match backend)
|
|
272
|
+
export enum BoardRole {
|
|
273
|
+
VIEWER = "VIEWER",
|
|
274
|
+
EDITOR = "EDITOR",
|
|
275
|
+
ADMIN = "ADMIN",
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export enum GenerationStatus {
|
|
279
|
+
PENDING = "PENDING",
|
|
280
|
+
RUNNING = "RUNNING",
|
|
281
|
+
COMPLETED = "COMPLETED",
|
|
282
|
+
FAILED = "FAILED",
|
|
283
|
+
CANCELLED = "CANCELLED",
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export enum ArtifactType {
|
|
287
|
+
IMAGE = "image",
|
|
288
|
+
VIDEO = "video",
|
|
289
|
+
AUDIO = "audio",
|
|
290
|
+
TEXT = "text",
|
|
291
|
+
LORA = "lora",
|
|
292
|
+
MODEL = "model",
|
|
293
|
+
}
|