GraphQL Schema Designer
Run ID: 69bca62277c0421c0bf49a962026-03-29Development
PantheraHive BOS
BOS Dashboard

GraphQL Schema Design for "Test Project Name"

This document outlines a comprehensive GraphQL schema, including types, queries, mutations, subscriptions, resolver logic, and integration examples, tailored for the "Test Project Name" project with JWT authentication and real-time capabilities. Given the generic nature of the "entities" input, we've designed a robust schema for a common application scenario involving Users, Posts, and Comments to demonstrate a wide range of GraphQL features.


1. Project Overview

Project Name: Test Project Name

Goal: To provide a robust and flexible API for data interaction, leveraging GraphQL's declarative nature, strong typing, and real-time capabilities.

Authentication Strategy: JWT (JSON Web Tokens)

Real-time Capabilities: Enabled via Subscriptions

2. GraphQL Schema Definition Language (SDL)

This section defines the core structure of your GraphQL API using Schema Definition Language (SDL).

text • 9,496 chars
## 3. Resolver Structure and Logic

Resolvers are functions that tell GraphQL how to fetch the data for a particular field. They are organized by type and field.

### A. Scalar Resolvers

*   **`DateTime`**:
    *   **Serialize**: Convert `Date` objects (from database) to ISO 8601 strings for API output.
    *   **ParseValue**: Convert ISO 8601 strings (from variables) to `Date` objects for internal use.
    *   **ParseLiteral**: Convert AST `StringValue` (from inline arguments) to `Date` objects.

### B. Type Resolvers

#### `Query` Resolvers

*   **`hello`**: Returns a simple string (e.g., "Hello from Test Project Name!").
*   **`users(first, after, last, before)`**:
    *   **Authentication Check**: Verify JWT and `ADMIN` role using the `@auth` directive.
    *   **Logic**: Fetch users from the database, apply pagination logic (cursor-based), and return `UserConnection`.
    *   **Example**: `return await database.getUsersPaginated({ first, after });`
*   **`user(id)`**:
    *   **Authentication Check**: Verify JWT using the `@auth` directive.
    *   **Logic**: Fetch a single user by `id` from the database.
    *   **Example**: `return await database.getUserById(id);`
*   **`posts(status, authorId, tagId, first, after, last, before)`**:
    *   **Logic**: Fetch posts from the database, apply filters (status, author, tag), and pagination.
    *   **Example**: `return await database.getPostsPaginated({ status, authorId, tagId, first, after });`
*   **`post(id)`**:
    *   **Logic**: Fetch a single post by `id`.
    *   **Example**: `return await database.getPostById(id);`
*   **`comments(postId, authorId, first, after, last, before)`**:
    *   **Logic**: Fetch comments, apply filters (post, author), and pagination.
    *   **Example**: `return await database.getCommentsPaginated({ postId, authorId, first, after });`
*   **`comment(id)`**:
    *   **Logic**: Fetch a single comment by `id`.
    *   **Example**: `return await database.getCommentById(id);`
*   **`tags(first, after, last, before)`**:
    *   **Logic**: Fetch tags from the database with pagination.
    *   **Example**: `return await database.getTagsPaginated({ first, after });`
*   **`tag(id)`**:
    *   **Logic**: Fetch a single tag by `id`.
    *   **Example**: `return await database.getTagById(id);`

#### `Mutation` Resolvers

*   **`createUser(input)`**:
    *   **Authentication Check**: Verify JWT and `ADMIN` role.
    *   **Logic**: Hash `input.password`, create a new user in the database, and return the new `User` object.
    *   **Example**: `const hashedPassword = await hash(input.password); return await database.createUser({ ...input, password: hashedPassword });`
*   **`updateUser(id, input)`**:
    *   **Authentication Check**: Verify JWT and `ADMIN` role. Implement additional logic to allow a user to update *their own* profile.
    *   **Logic**: Update user details in the database. If `input.password` is provided, hash it. Return the updated `User`.
    *   **Example**: `return await database.updateUser(id, input);`
*   **`deleteUser(id)`**:
    *   **Authentication Check**: Verify JWT and `ADMIN` role.
    *   **Logic**: Delete the user from the database. Return `true` on success.
    *   **Example**: `await database.deleteUser(id); return true;`
*   **`createPost(input)`**:
    *   **Authentication Check**: Verify JWT and `ADMIN` or `EDITOR` role. Get `authorId` from context.
    *   **Logic**: Create a new post in the database, link tags if `tagIds` are provided. Publish `postCreated` subscription event.
    *   **Example**: `const newPost = await database.createPost({ ...input, authorId: context.user.id }); pubsub.publish('POST_CREATED', { postCreated: newPost }); return newPost;`
*   **`updatePost(id, input)`**:
    *   **Authentication Check**: Verify JWT and `ADMIN` or `EDITOR` role. Implement additional logic to allow a user to update *their own* posts.
    *   **Logic**: Update post details in the database, update linked tags. Return the updated `Post`.
    *   **Example**: `return await database.updatePost(id, input);`
*   **`deletePost(id)`**:
    *   **Authentication Check**: Verify JWT and `ADMIN` or `EDITOR` role. Implement additional logic to allow a user to delete *their own* posts.
    *   **Logic**: Delete the post from the database. Return `true` on success.
    *   **Example**: `await database.deletePost(id); return true;`
*   **`createComment(input)`**:
    *   **Authentication Check**: Verify JWT. Get `authorId` from context.
    *   **Logic**: Create a new comment in the database. Publish `commentAdded` subscription event.
    *   **Example**: `const newComment = await database.createComment({ ...input, authorId: context.user.id }); pubsub.publish('COMMENT_ADDED', { commentAdded: newComment, postId: input.postId }); return newComment;`
*   **`updateComment(id, input)`**:
    *   **Authentication Check**: Verify JWT. Implement additional logic to allow a user to update *their own* comments.
    *   **Logic**: Update comment content. Return the updated `Comment`.
    *   **Example**: `return await database.updateComment(id, input);`
*   **`deleteComment(id)`**:
    *   **Authentication Check**: Verify JWT. Implement additional logic to allow a user to delete *their own* comments.
    *   **Logic**: Delete the comment. Return `true`.
    *   **Example**: `await database.deleteComment(id); return true;`
*   **`createTag(input)`**:
    *   **Authentication Check**: Verify JWT and `ADMIN` role.
    *   **Logic**: Create a new tag.
    *   **Example**: `return await database.createTag(input);`
*   **`updateTag(id, input)`**:
    *   **Authentication Check**: Verify JWT and `ADMIN` role.
    *   **Logic**: Update an existing tag.
    *   **Example**: `return await database.updateTag(id, input);`
*   **`deleteTag(id)`**:
    *   **Authentication Check**: Verify JWT and `ADMIN` role.
    *   **Logic**: Delete a tag.
    *   **Example**: `await database.deleteTag(id); return true;`

#### `Subscription` Resolvers

Subscriptions typically involve two parts: `subscribe` and `resolve`.

*   **`postCreated`**:
    *   **Authentication Check**: Verify JWT.
    *   **`subscribe`**: Listens for `POST_CREATED` events from the PubSub system.
    *   **`resolve`**: Returns the `Post` object received from the event.
    *   **Example**: `pubsub.asyncIterator('POST_CREATED')`
*   **`commentAdded(postId)`**:
    *   **Authentication Check**: Verify JWT.
    *   **`subscribe`**: Listens for `COMMENT_ADDED` events. Includes a filter to only send events for the specified `postId`.
    *   **`resolve`**: Returns the `Comment` object received from the event.
    *   **Example**: `withFilter(() => pubsub.asyncIterator('COMMENT_ADDED'), (payload, variables) => payload.postId === variables.postId)`
*   **`userUpdated(userId)`**:
    *   **Authentication Check**: Verify JWT and `ADMIN` role.
    *   **`subscribe`**: Listens for `USER_UPDATED` events. Filters for a specific `userId`.
    *   **`resolve`**: Returns the `User` object received from the event.
    *   **Example**: `withFilter(() => pubsub.asyncIterator('USER_UPDATED'), (payload, variables) => payload.userId === variables.userId)`

#### Field Resolvers (for nested objects)

For fields within `User`, `Post`, `Comment`, `Tag` that are not simple scalars, dedicated resolvers might be needed if the data is not directly available from the parent object.

*   **`User.posts`**:
    *   **Logic**: Given a `User` object, fetch their associated posts from the database with pagination.
    *   **Example**: `(parent, { first, after }) => database.getPostsByAuthorId(parent.id, { first, after })`
*   **`Post.author`**:
    *   **Logic**: Given a `Post` object, fetch the `User` object for its `authorId`. (Often optimized with DataLoader).
    *   **Example**: `(parent) => database.getUserById(parent.authorId)`
*   **`Post.comments`**:
    *   **Logic**: Given a `Post` object, fetch its associated comments with pagination.
    *   **Example**: `(parent, { first, after }) => database.getCommentsByPostId(parent.id, { first, after })`
*   **`Comment.author`**:
    *   **Logic**: Given a `Comment` object, fetch the `User` object for its `authorId`.
    *   **Example**: `(parent) => database.getUserById(parent.authorId)`
*   **`Comment.post`**:
    *   **Logic**: Given a `Comment` object, fetch the `Post` object for its `postId`.
    *   **Example**: `(parent) => database.getPostById(parent.postId)`
*   **`Tag.posts`**:
    *   **Logic**: Given a `Tag` object, fetch posts associated with this tag with pagination.
    *   **Example**: `(parent, { first, after }) => database.getPostsByTagId(parent.id, { first, after })`

## 4. Authentication & Authorization Strategy (JWT)

### A. JWT Integration

1.  **Client-side**: The client (e.g., web app, mobile app) obtains a JWT upon successful login/registration. This token is then sent with every subsequent GraphQL request in the `Authorization` header, typically as `Bearer <token>`.
2.  **Server-side (Context)**:
    *   The GraphQL server (e.g., Apollo Server) intercepts incoming requests.
    *   It extracts the JWT from the `Authorization` header.
    *   It verifies the token's signature, expiry, and issuer.
    *   If valid, the decoded payload (containing `userId`, `role`, etc.) is injected into the GraphQL `context` object, making it accessible to all resolvers.
    *   If invalid or missing, the `context.user` will be `null` or an error will be thrown.

    
Sandboxed live preview

javascript

// app.js (Simplified example)

const { ApolloServer } = require('@apollo/server');

const { expressMiddleware } = require('@apollo/server/express4');

const { ApolloServerPluginDrainHttpServer } = require('@apollo/server/plugin/drainHttpServer');

const { makeExecutableSchema } = require('@graphql-tools/schema');

const { WebSocketServer } = require('ws');

const { useServer } = require('graphql-ws/lib/use/ws');

const express = require('express');

const http = require('http');

const cors = require('cors');

const bodyParser = require('body-parser');

const jwt = require('jsonwebtoken');

const { PubSub } = require('graphql-subscriptions'); // For in-memory PubSub, use RedisPubSub for production

// Load environment variables

require('dotenv').config();

// Your schema definition (from SDL above)

const typeDefs = `

scalar DateTime

enum UserRole { ADMIN EDITOR VIEWER }

enum PostStatus { DRAFT PUBLISHED ARCHIVED }

interface Node { id: ID! }

type User implements Node { ... }

type Post implements Node { ... }

type Comment implements Node { ... }

type Tag implements Node { ... }

type PageInfo { ... }

type UserEdge { ... }

type UserConnection { ... }

type PostEdge { ... }

type PostConnection { ... }

type CommentEdge { ... }

type CommentConnection { ... }

input CreateUserInput { ... }

input UpdateUserInput { ... }

input CreatePostInput { ... }

input UpdatePostInput { ... }

input CreateCommentInput { ... }

input UpdateCommentInput { ... }

input CreateTagInput { ... }

type Query { ... }

type Mutation { ... }

type Subscription { ... }

directive @auth(requires: [UserRole!] = [ADMIN, EDITOR, VIEWER]) on FIELD_DEFINITION | OBJECT

`;

// Your resolvers (simplified)

const resolvers = {

DateTime: { / scalar implementation / },

Query: {

hello: () => 'Hello from Test Project Name!',

users: (parent, args, context) => {

if (!context.user || context.user.role !== 'ADMIN') {

throw new Error('Unauthorized');

}

// Implement pagination logic and fetch from DB

return {

pageInfo: { hasNextPage: false, hasPreviousPage: false },

edges: [],

totalCount: 0

};

},

// ... other query resolvers

},

Mutation: {

createUser: (parent, { input }, context) => {

if (!context.user || context.user.role !== 'ADMIN') {

throw new Error('Unauthorized');

}

// Hash password, save to DB, return user

return { id: '1', username: input.username, email: input.email, role: input.role, createdAt: new Date(), updatedAt: new Date() };

},

createPost: (parent, { input }, context) => {

if (!context.user) throw new Error('Unauthorized');

// Save to DB

const newPost = { id: 'p1', author: context.user, ...input, createdAt: new Date(), updatedAt: new Date() };

context.pubsub.publish('POST_CREATED', { postCreated: newPost });

return newPost;

},

createComment: (parent, { input }, context) => {

if (!context.user) throw new Error('Unauthorized');

const newComment = { id: 'c1', author: context.user, post: { id: input.postId }, content: input.content, createdAt: new Date(), updatedAt: new Date() };

context.pubsub.publish('COMMENT_ADDED', { commentAdded: newComment, postId: input.postId });

return newComment;

},

// ... other mutation resolvers

},

Subscription: {

postCreated: {

subscribe: (parent, args, context) => context.pubsub.asyncIterator('POST_CREATED'),

resolve: (payload) => payload.postCreated,

},

commentAdded: {

subscribe: (parent, { postId }, context) =>

context.pubsub.asyncIterator('COMMENT_ADDED', (payload) => payload.postId === postId),

resolve: (payload) => payload.commentAdded,

},

},

// ... Field resolvers for nested types (e.g., User.posts, Post.author)

};

// Create a PubSub instance

const pubsub = new PubSub();

// Create the executable schema

const schema = makeExecutableSchema({ typeDefs, resolvers });

// Apply the @auth directive logic

const authDirectiveTransformer = (schema) => {

return mapSchema(schema, {

[MapperKind.OBJECT_FIELD]: (fieldConfig) => {

const authDirective = getDirective(schema, fieldConfig, 'auth')?.[0];

if (authDirective) {

const { resolve = defaultFieldResolver } = fieldConfig;

fieldConfig.resolve = async function (source, args, context, info) {

const { requires } = authDirective;

if (!context.user) {

throw new Error('Authentication required.');

}

if (requires && requires.length > 0 && !requires.includes(context.user.role)) {

throw new Error(Unauthorized: Requires role ${requires.join(' or ')});

}

return resolve(source, args, context, info);

};

return fieldConfig;

}

},

});

};

const authenticatedSchema = authDirectiveTransformer(schema);

async function startApolloServer() {

const app = express();

const httpServer = http.createServer(app);

// Set up WebSocket server for subscriptions

const wsServer = new WebSocketServer({

server: httpServer,

path: '/graphql',

});

const serverCleanup = useServer(

{

schema: authenticatedSchema,

context: async (ctx) => {

// This context is for subscriptions. It's built once per connection.

const token = ctx.connectionParams?.authToken; // Or however you pass it

try {

const user = token ? jwt.verify(token, process.env.JWT_SECRET) : null;

return { user, pubsub };

} catch (error) {

return { user: null, pubsub };

}

},

},

wsServer

);

const server = new ApolloServer({

schema: authenticatedSchema,

plugins: [

ApolloServerPluginDrainHttpServer({ httpServer }),

{

async serverWillStart() {

return {

async drainServer() {

await serverCleanup.dispose

Step 2: projectmanager

GraphQL Schema Designer: Comprehensive Output

Project Name: Test Project Name

Description: A comprehensive GraphQL schema designed to demonstrate types, queries, mutations, subscriptions, resolvers, and integration examples for a modern application.

Authentication Strategy: JWT

Real-time Capabilities: Enabled (Subscriptions)


1. Project Overview

This document outlines a complete GraphQL schema for the "Test Project Name," focusing on user-generated content, specifically posts and comments. The schema is designed to be robust, scalable, and secure, incorporating JWT-based authentication and real-time updates via subscriptions. The primary entities modeled are User, Post, Comment, and Tag.

Key Features:

  • Structured Data: Clearly defined types for entities and their relationships.
  • Comprehensive Operations: Queries for data retrieval, Mutations for data manipulation, and Subscriptions for real-time updates.
  • Security: JWT integration for authentication and authorization.
  • Real-time: Support for instant data propagation using GraphQL Subscriptions.
  • Extensibility: Designed with best practices to allow for future expansion.

2. GraphQL Schema Definition Language (SDL)

This section presents the full GraphQL Schema Definition Language (SDL), including types, input types, enums, queries, mutations, subscriptions, and directives.


# --- Directives ---
"""
@auth directive for role-based access control.
Requires a list of roles; if the user's token contains any of these roles, access is granted.
If no roles are specified, it only checks for authentication (presence of a valid token).
"""
directive @auth(roles: [UserRole!] = []) on FIELD_DEFINITION | OBJECT

# --- Enums ---
"""
Represents the different roles a user can have within the system.
"""
enum UserRole {
  ADMIN
  EDITOR
  AUTHOR
  VIEWER
}

"""
Represents the possible statuses of a post.
"""
enum PostStatus {
  DRAFT
  PUBLISHED
  ARCHIVED
}

# --- Scalar Types ---
# Custom scalar for Date/Time representation.
# It's recommended to use a standard library like 'graphql-scalars' for implementation.
scalar DateTime

# --- Types ---

"""
Represents a user in the system.
"""
type User @auth(roles: [ADMIN, EDITOR, AUTHOR, VIEWER]) {
  id: ID!
  username: String! @auth(roles: [ADMIN, EDITOR, AUTHOR])
  email: String! @auth(roles: [ADMIN]) # Email is more sensitive, only visible to ADMIN
  roles: [UserRole!]! @auth(roles: [ADMIN, EDITOR])
  posts(limit: Int = 10, offset: Int = 0): [Post!]!
  comments(limit: Int = 10, offset: Int = 0): [Comment!]!
  createdAt: DateTime!
  updatedAt: DateTime!
}

"""
Represents a post created by a user.
"""
type Post @auth(roles: [ADMIN, EDITOR, AUTHOR, VIEWER]) {
  id: ID!
  title: String!
  content: String!
  author: User!
  status: PostStatus!
  tags: [Tag!]!
  comments(limit: Int = 10, offset: Int = 0): [Comment!]!
  createdAt: DateTime!
  updatedAt: DateTime!
}

"""
Represents a comment on a post.
"""
type Comment @auth(roles: [ADMIN, EDITOR, AUTHOR, VIEWER]) {
  id: ID!
  content: String!
  author: User!
  post: Post!
  createdAt: DateTime!
  updatedAt: DateTime!
}

"""
Represents a tag associated with a post.
"""
type Tag @auth(roles: [ADMIN, EDITOR, AUTHOR, VIEWER]) {
  id: ID!
  name: String!
  posts(limit: Int = 10, offset: Int = 0): [Post!]!
  createdAt: DateTime!
  updatedAt: DateTime!
}

# --- Input Types ---

"""
Input for creating a new user.
"""
input CreateUserInput {
  username: String!
  email: String!
  password: String! # Passwords should be hashed server-side
  roles: [UserRole!] = [VIEWER] # Default role is VIEWER
}

"""
Input for updating an existing user.
"""
input UpdateUserInput {
  username: String
  email: String
  roles: [UserRole!]
}

"""
Input for creating a new post.
"""
input CreatePostInput {
  title: String!
  content: String!
  status: PostStatus = DRAFT # Default status is DRAFT
  tagIds: [ID!]
}

"""
Input for updating an existing post.
"""
input UpdatePostInput {
  title: String
  content: String
  status: PostStatus
  tagIds: [ID!]
}

"""
Input for creating a new comment.
"""
input CreateCommentInput {
  postId: ID!
  content: String!
}

"""
Input for updating an existing comment.
"""
input UpdateCommentInput {
  content: String
}

"""
Input for creating a new tag.
"""
input CreateTagInput {
  name: String!
}

"""
Input for updating an existing tag.
"""
input UpdateTagInput {
  name: String
}

# --- Root Query Type ---
"""
The root query type for retrieving data.
"""
type Query {
  # User queries
  me: User @auth # Get current authenticated user
  user(id: ID!): User @auth
  users(limit: Int = 10, offset: Int = 0): [User!]! @auth(roles: [ADMIN, EDITOR])

  # Post queries
  post(id: ID!): Post @auth
  posts(
    status: PostStatus
    authorId: ID
    tagId: ID
    search: String
    limit: Int = 10
    offset: Int = 0
  ): [Post!]! @auth

  # Comment queries
  comment(id: ID!): Comment @auth
  comments(
    postId: ID
    authorId: ID
    limit: Int = 10
    offset: Int = 0
  ): [Comment!]! @auth

  # Tag queries
  tag(id: ID!): Tag @auth
  tags(search: String, limit: Int = 10, offset: Int = 0): [Tag!]! @auth
}

# --- Root Mutation Type ---
"""
The root mutation type for modifying data.
"""
type Mutation {
  # User mutations
  register(input: CreateUserInput!): User!
  login(usernameOrEmail: String!, password: String!): String! # Returns JWT token
  updateUser(id: ID!, input: UpdateUserInput!): User! @auth(roles: [ADMIN])
  deleteUser(id: ID!): Boolean! @auth(roles: [ADMIN])

  # Post mutations
  createPost(input: CreatePostInput!): Post! @auth(roles: [ADMIN, EDITOR, AUTHOR])
  updatePost(id: ID!, input: UpdatePostInput!): Post! @auth(roles: [ADMIN, EDITOR, AUTHOR])
  deletePost(id: ID!): Boolean! @auth(roles: [ADMIN, EDITOR, AUTHOR])

  # Comment mutations
  createComment(input: CreateCommentInput!): Comment! @auth(roles: [ADMIN, EDITOR, AUTHOR, VIEWER])
  updateComment(id: ID!, input: UpdateCommentInput!): Comment! @auth(roles: [ADMIN, EDITOR, AUTHOR, VIEWER])
  deleteComment(id: ID!): Boolean! @auth(roles: [ADMIN, EDITOR, AUTHOR, VIEWER])

  # Tag mutations
  createTag(input: CreateTagInput!): Tag! @auth(roles: [ADMIN, EDITOR])
  updateTag(id: ID!, input: UpdateTagInput!): Tag! @auth(roles: [ADMIN, EDITOR])
  deleteTag(id: ID!): Boolean! @auth(roles: [ADMIN, EDITOR])
}

# --- Root Subscription Type ---
"""
The root subscription type for real-time data updates.
"""
type Subscription {
  postCreated: Post! @auth
  postUpdated(id: ID): Post! @auth # Can subscribe to all updates or specific post updates
  postDeleted: ID! @auth
  commentAdded(postId: ID!): Comment! @auth # Subscribe to comments for a specific post
  userJoined: User! @auth(roles: [ADMIN, EDITOR]) # Notifies when a new user registers
}

3. Resolver Map (Conceptual)

Resolvers are functions that tell GraphQL how to fetch the data for a particular field. This section outlines the conceptual resolver structure.


// Example Resolver Map Structure (Conceptual)

const resolvers = {
  DateTime: 'scalar', // Use a custom scalar implementation (e.g., from graphql-scalars)

  User: {
    // Field-level resolvers for User type
    // These are called when a User object is resolved and these specific fields are requested
    posts: (parent, { limit, offset }, context) => {
      // parent is the User object; context contains auth info, data sources
      // Fetch posts where authorId matches parent.id
      return context.dataSources.postAPI.getPostsByAuthorId(parent.id, limit, offset);
    },
    comments: (parent, { limit, offset }, context) => {
      // Fetch comments where authorId matches parent.id
      return context.dataSources.commentAPI.getCommentsByAuthorId(parent.id, limit, offset);
    },
    // The @auth directive will be handled by a schema directive transformer,
    // which wraps the resolver with authorization logic.
  },

  Post: {
    author: (parent, args, context) => {
      // parent is the Post object; context contains auth info, data sources
      // Fetch the author user based on parent.authorId (assuming Post stores authorId)
      return context.dataSources.userAPI.getUserById(parent.authorId);
    },
    tags: (parent, args, context) => {
      // Fetch tags associated with this post
      return context.dataSources.tagAPI.getTagsByPostId(parent.id);
    },
    comments: (parent, { limit, offset }, context) => {
      // Fetch comments for this post
      return context.dataSources.commentAPI.getCommentsByPostId(parent.id, limit, offset);
    },
  },

  Comment: {
    author: (parent, args, context) => {
      return context.dataSources.userAPI.getUserById(parent.authorId);
    },
    post: (parent, args, context) => {
      return context.dataSources.postAPI.getPostById(parent.postId);
    },
  },

  Tag: {
    posts: (parent, { limit, offset }, context) => {
      return context.dataSources.postAPI.getPostsByTagId(parent.id, limit, offset);
    },
  },

  Query: {
    me: (parent, args, context) => {
      // context.user will be populated by the auth middleware/directive
      if (!context.user) throw new AuthenticationError('Not authenticated');
      return context.dataSources.userAPI.getUserById(context.user.id);
    },
    user: (parent, { id }, context) => context.dataSources.userAPI.getUserById(id),
    users: (parent, { limit, offset }, context) => context.dataSources.userAPI.getUsers(limit, offset),
    post: (parent, { id }, context) => context.dataSources.postAPI.getPostById(id),
    posts: (parent, args, context) => context.dataSources.postAPI.getPosts(args),
    comment: (parent, { id }, context) => context.dataSources.commentAPI.getCommentById(id),
    comments: (parent, args, context) => context.dataSources.commentAPI.getComments(args),
    tag: (parent, { id }, context) => context.dataSources.tagAPI.getTagById(id),
    tags: (parent, args, context) => context.dataSources.tagAPI.getTags(args),
  },

  Mutation: {
    register: async (parent, { input }, context) => {
      // Hash password
      const hashedPassword = await hashPassword(input.password);
      const newUser = await context.dataSources.userAPI.createUser({ ...input, password: hashedPassword });
      // Publish userJoined subscription
      context.pubsub.publish('USER_JOINED', { userJoined: newUser });
      return newUser;
    },
    login: async (parent, { usernameOrEmail, password }, context) => {
      const user = await context.dataSources.userAPI.findUserByUsernameOrEmail(usernameOrEmail);
      if (!user || !(await verifyPassword(password, user.password))) {
        throw new AuthenticationError('Invalid credentials');
      }
      return generateJwtToken(user); // Returns the JWT token
    },
    updateUser: (parent, { id, input }, context) => context.dataSources.userAPI.updateUser(id, input),
    deleteUser: (parent, { id }, context) => context.dataSources.userAPI.deleteUser(id),

    createPost: async (parent, { input }, context) => {
      const newPost = await context.dataSources.postAPI.createPost({ ...input, authorId: context.user.id });
      // Publish postCreated subscription
      context.pubsub.publish('POST_CREATED', { postCreated: newPost });
      return newPost;
    },
    updatePost: async (parent, { id, input }, context) => {
      const updatedPost = await context.dataSources.postAPI.updatePost(id, input);
      // Publish postUpdated subscription
      context.pubsub.publish('POST_UPDATED', { postUpdated: updatedPost });
      return updatedPost;
    },
    deletePost: async (parent, { id }, context) => {
      const deleted = await context.dataSources.postAPI.deletePost(id);
      if (deleted) {
        context.pubsub.publish('POST_DELETED', { postDeleted: id });
      }
      return deleted;
    },

    createComment: async (parent, { input }, context) => {
      const newComment = await context.dataSources.commentAPI.createComment({ ...input, authorId: context.user.id });
      // Publish commentAdded subscription
      context.pubsub.publish('COMMENT_ADDED', { commentAdded: newComment, postId: input.postId });
      return newComment;
    },
    updateComment: (parent, { id, input }, context) => context.dataSources.commentAPI.updateComment(id, input),
    deleteComment: (parent, { id }, context) => context.dataSources.commentAPI.deleteComment(id),

    createTag: (parent, { input }, context) => context.dataSources.tagAPI.createTag(input),
    updateTag: (parent, { id, input }, context) => context.dataSources.tagAPI.updateTag(id, input),
    deleteTag: (parent, { id }, context) => context.dataSources.tagAPI.deleteTag(id),
  },

  Subscription: {
    postCreated: {
      subscribe: (parent, args, context) => context.pubsub.asyncIterator(['POST_CREATED']),
    },
    postUpdated: {
      subscribe: (parent, { id }, context) => {
        // If an ID is provided, filter for updates to that specific post
        // Otherwise, subscribe to all post updates
        return context.pubsub.asyncIterator(['POST_UPDATED']); // Logic to filter by ID would be in a resolver or trigger
      },
      resolve: (payload, args) => {
        if (args.id && payload.postUpdated.id !== args.id) {
          return null; // Don't send update if it's not the requested ID
        }
        return payload.postUpdated;
      }
    },
    postDeleted: {
      subscribe: (parent, args, context) => context.pubsub.asyncIterator(['POST_DELETED']),
    },
    commentAdded: {
      subscribe: (parent, { postId }, context) => {
        // Only publish for comments on the specified post
        return context.pubsub.asyncIterator(['COMMENT_ADDED']);
      },
      resolve: (payload, { postId }) => {
        if (payload.postId === postId) {
          return payload.commentAdded;
        }
        return null; // Filter out comments for other posts
      },
    },
    userJoined: {
      subscribe: (parent, args, context) => context.pubsub.asyncIterator(['USER_JOINED']),
    },
  },
};

4. Authentication and Authorization Strategy (JWT)

The strategy employs JSON Web Tokens (JWT) for authentication and a custom @auth directive for authorization.

Authentication Flow:

  1. Login/Register: A user sends credentials to the login or register mutation.
  2. Token Generation: Upon successful authentication, the server generates a JWT containing user id, username, and roles. This token is signed with a secret key.
  3. Client Storage: The client stores this JWT (e.g., in localStorage or HttpOnly cookie).
  4. Subsequent Requests: For every subsequent GraphQL request, the client includes the JWT in the Authorization header (e.g., Bearer <token>).
  5. Server Validation: The GraphQL server middleware intercepts the request, validates the JWT, and extracts the user information (id, username, roles). This information is then attached to the context object for resolver access.

Authorization with @auth Directive:

  • Schema-level: The @auth directive is applied to Type definitions and Field definitions.

* On Type: If User is marked with @auth, it implies that any field of User will first check if the user is authenticated. Specific fields can override this.

* On Field: email: String! @auth(roles: [ADMIN]) specifies that only users with the ADMIN role can access the email field of a User object.

Default Behavior: If @auth is used without roles (e.g., me: User @auth), it simply checks if any* valid JWT is present (i.e., the user is authenticated).

  • Implementation:

1. Schema Directive Transformer: A custom schema transformer (e.g., using @graphql-tools/schema's mapSchema and get Directive Extensions) modifies the schema at startup.

2. Wrapper Function: For each field with the @auth directive, the transformer wraps the field's resolver with an authorization function.

3. Authorization Logic: This wrapper function accesses context.user (populated by the JWT middleware) and compares context.user.roles with the roles specified in the directive. If the user doesn't have the required role(s) or is not authenticated, it throws an AuthenticationError or ForbiddenError.

Example Context Structure:


// In your Apollo Server setup, after JWT validation:
const context = ({ req, connection }) => {
  // For HTTP requests
  if (req) {
    const token = req.headers.authorization || '';
    const user = validateJwt(token); // Returns { id, username, roles } or null
    return { user, pubsub, dataSources };
  }
  // For WebSocket subscriptions
  if (connection) {
    const user = connection.context.user; // User already validated during connectionParams
    return { user, pubsub, dataSources };
  }
};

5. Real-time Capabilities (Subscriptions)

Real-time capabilities are implemented using GraphQL Subscriptions, powered by a publish-subscribe (pub/sub) mechanism.

Mechanism:

  1. Pub/Sub Engine: A pub/sub engine (e.g., graphql-subscriptions with RedisPubSub for production, or InMemoryPubSub for development) is integrated into the GraphQL server.
  2. Subscription Resolvers: Each subscription field (postCreated, commentAdded, etc.) has a subscribe function. This function returns an AsyncIterator from the pub/sub engine, listening to specific topics (e.g., POST_CREATED, COMMENT_ADDED).
  3. Publishing Events: When a relevant mutation occurs (e.g., createPost, createComment), the mutation resolver publishes an event to the pub/sub engine on the corresponding topic.
  4. Client Connection: Clients establish a WebSocket connection to the GraphQL server, subscribing to desired topics.
  5. Real-time Updates: When an event is published, the pub/sub engine pushes the data through the WebSocket connection to all subscribed clients.
  6. Filtering: Subscriptions like commentAdded(postId: ID!) demonstrate filtering, where the resolve function on the subscription checks if the published event matches the client's subscription criteria (e.g., postId).

Example Flow (New Post):

  1. Client A (Web) Subscribes:

    subscription {
      postCreated {
        id
        title
        author { username }
      }
    }
  1. Client B (Mobile) Mutates:

    mutation {
      createPost(input: { title: "New Post", content: "..." }) {
        id
      }
    }
  1. Server Action:

* createPost mutation resolver executes.

* New post is saved to the database.

* context.pubsub.publish('POST_CREATED', { postCreated: newPost }); is called.

  1. Client A Receives Update: Client A's WebSocket receives the newPost data in real-time.

6. Integration Examples

6.1 Client-side (React with Apollo Client)


// src/App.js (or a component)
import React from 'react';
import { ApolloClient, InMemoryCache, ApolloProvider, HttpLink, split } from '@apollo/client';
import { WebSocketLink } from '@apollo/client/link/ws';
import { getMainDefinition } from '@apollo/client/utilities';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { useQuery, useMutation, useSubscription, gql } from '@apollo/client';

// Example JWT retrieval (e.g., from localStorage)
const getToken = () => localStorage.getItem('jwt_token');

// 1. HTTP Link for Queries and Mutations
const httpLink = new HttpLink({
  uri: 'http://localhost:4000/graphql',
  headers: {
    authorization: getToken() ? `Bearer ${getToken()}` : '',
  },
});

// 2. WebSocket Link for Subscriptions
const wsLink = new WebSocketLink({
  uri: `ws://localhost:4000/graphql`,
  options: {
    reconnect: true,
    connectionParams: () => ({
      authToken: getToken(), // Send token for WebSocket authentication
    }),
  },
});

// 3. Split Link for routing operations
const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  httpLink,
);

// 4. Apollo Client Setup
const client = new ApolloClient({
  link: splitLink,
  cache: new InMemoryCache(),
});

// --- Example Component: PostsList ---
const GET_POSTS = gql`
  query GetPosts {
    posts {
      id
      title
      content
      author {
        username
      }
      status
      createdAt
    }
  }
`;

const POST_CREATED_SUBSCRIPTION = gql`
  subscription OnPostCreated {
    postCreated {
      id
      title
      author {
        username
      }
      createdAt
    }
  }
`;

function PostsList() {
  const { loading, error, data } = useQuery(GET_POSTS);
  const { data: subscriptionData, loading: subscriptionLoading } = useSubscription(
    POST_CREATED_SUBSCRIPTION
  );

  React.useEffect(() => {
    if (subscriptionData) {
      alert(`New Post: ${subscriptionData.postCreated.title} by ${subscriptionData.postCreated.author.username}`);
      // You would typically update your local cache here
      // client.cache.updateQuery({ query: GET_POSTS }, (data) => { ... });
    }
  }, [subscriptionData]);

  if (loading) return <p>Loading posts...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <h2>Posts</h2>
      {data.posts.map((post) => (
        <div key={post.id} style={{ border: '1px solid #ccc', margin: '10px', padding: '10px' }}>
          <h3>{post.title}</h3>
          <p>{post.content.substring(0, 100)}...</p>
          <p>By: {post.author.username} ({new Date(post.createdAt).toLocaleDateString()})</p>
        </div>
      ))}
      {subscriptionLoading ? <p>Listening for new posts...</p> : null}
    </div>
  );
}

// --- Example Component: CreatePostForm ---
const CREATE_POST_MUTATION = gql`
  mutation CreatePost($input: CreatePostInput!) {
    createPost(input: $input) {
      id
      title
      author {
        username
      }
    }
  }
`;

function CreatePostForm() {
  const [title, setTitle] = React.useState('');
  const [content, setContent] = React.useState('');
  const [createPost, { loading, error }] = useMutation(CREATE_POST_MUTATION, {
    refetchQueries: [{ query: GET_POSTS }], // Refresh posts list after creating
    onCompleted: () => {
      setTitle('');
      setContent('');
      alert('Post created!');
    }
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    createPost({ variables: { input: { title, content } } });
  };

  return (
    <form onSubmit={handleSubmit} style={{ margin: '20px', padding: '15px', border: '1px dashed #aaa' }}>
      <h3>Create New Post</h3>
      <input
        type="text"
        placeholder="Title"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        required
        style={{ display: 'block', marginBottom: '10px', width: '300px', padding: '8px' }}
      />
      <textarea
        placeholder="Content"
        value={content}
        onChange={(e) => setContent(e.target.value)}
        required
        rows="5"
        style={{ display: 'block', marginBottom: '10px', width: '300px', padding: '8px' }}
      />
      <button type="submit" disabled={loading}>
        {loading ? 'Creating...' : 'Create Post'}
      </button>
      {error && <p style={{ color: 'red' }}>Error: {error.message}</p>}
    </form>
  );
}

// --- Main App Component ---
function App() {
  // Example login function
  const handleLogin = async () => {
    // In a real app, this would be a mutation call
    // For demonstration, simulate a token
    const dummyToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjEyMyIsInVzZXJuYW1lIjoidGVzdHVzZXIiLCJyb2xlcyI6WyJBVVRIT1IiLCJWSUVXRVIiXSwiaWF0IjoxNTE2MjM5MDIyfQ.some_dummy_signature";
    localStorage.setItem('jwt_token', dummyToken);
    window.location.reload(); // Reload to re-initialize Apollo Client with new token
  };

  const handleLogout = () => {
    localStorage.removeItem('jwt_token');
    window.location.reload();
  };

  return (
    <ApolloProvider client={client}>
      <Router>
        <div style={{ fontFamily: 'Arial, sans-serif', padding: '20px' }}>
          <h1>Test Project Name - Blog</h1>
          {!getToken() ? (
            <button onClick={handleLogin}>Log In (as Author)</button>
          ) : (
            <button onClick={handleLogout}>Log Out</button>
          )}
          <Switch>
            <Route path="/" exact component={PostsList} />
            <Route path="/create-post" component={CreatePostForm} />
            {/* Add more routes for other features */}
          </Switch>
        </div>
      </Router>
    </ApolloProvider>
  );
}

export default App;

6.2 Server-side (Node.js with Apollo Server)


// src/index.js (Apollo Server Setup)
const { ApolloServer, gql } = require('apollo-server');
const { PubSub } = require('graphql-subscriptions');
const { makeExecutableSchema } = require('@graphql-tools/schema');
const { applyDirectiveTransformers } = require('./directives/authDirective'); // Custom directive logic
const jwt = require('jsonwebtoken');
require('dotenv').config(); // For process.env.JWT_SECRET

// Mock Data Sources (replace with actual database/API calls)
const users = [
  { id: '1', username: 'admin', email: 'admin@example.com', password: 'hashed_admin_password', roles: ['ADMIN', 'EDITOR', 'AUTHOR', 'VIEWER'], createdAt: new Date(), updatedAt: new Date() },
  { id: '2', username: 'testuser', email: 'test@example.com', password: 'hashed_user_password', roles: ['AUTHOR', 'VIEWER'], createdAt: new Date(), updatedAt: new Date() },
];
let posts = [
  { id: '101', title: 'First Post', content: 'Content of the first post.', authorId: '2', status: 'PUBLISHED', tagIds: ['t1'], createdAt: new Date(), updatedAt: new Date() },
  { id: '102', title: 'Second Post', content: 'Content of the second post.', authorId: '1', status: 'DRAFT', tagIds: ['t2'], createdAt: new Date(), updatedAt: new Date() },
];
let comments = [
  { id: 'c1', content: 'Great post!', authorId: '1', postId: '101', createdAt: new Date(), updatedAt: new Date() },
];
let tags = [
  { id: 't1', name: 'GraphQL', createdAt: new Date(), updatedAt: new Date() },
  { id: 't2', name: 'Apollo', createdAt: new Date(), updatedAt: new Date() },
];

const pubsub = new PubSub();

// Mock Data Source APIs
const dataSources = {
  userAPI: {
    getUserById: (id) => users.find(u => u.id === id),
    findUserByUsernameOrEmail: (identifier) => users.find(u => u.username === identifier || u.email === identifier),
    getUsers: (limit, offset) => users.slice(offset, offset + limit),
    createUser: async (input) => {
      const newUser = { id: String(users.length + 1), ...input, createdAt: new Date(), updatedAt: new Date() };
      users.push(newUser);
      return newUser;
    },
    updateUser: (id, input) => {
      const userIndex = users.findIndex(u => u.id === id);
      if (userIndex === -1) return null;
      users[userIndex] = { ...users[userIndex], ...input, updatedAt: new Date() };
      return users[userIndex];
    },
    deleteUser: (id) => {
      const initialLength = users.length;
      users = users.filter(u => u.id !== id);
      return users.length < initialLength;
    }
  },
  postAPI: {
    getPostById: (id) => posts.find(p => p.id === id),
    getPosts: ({ status, authorId, tagId, search, limit, offset
graphql_schema_designer.txt
Download source file
Copy all content
Full output as text
Download ZIP
IDE-ready project ZIP
Copy share link
Permanent URL for this run
Get Embed Code
Embed this result on any website
Print / Save PDF
Use browser print dialog
\n\n\n"); var hasSrcMain=Object.keys(extracted).some(function(k){return k.indexOf("src/main")>=0;}); if(!hasSrcMain) zip.file(folder+"src/main."+ext,"import React from 'react'\nimport ReactDOM from 'react-dom/client'\nimport App from './App'\nimport './index.css'\n\nReactDOM.createRoot(document.getElementById('root')!).render(\n \n \n \n)\n"); var hasSrcApp=Object.keys(extracted).some(function(k){return k==="src/App."+ext||k==="App."+ext;}); if(!hasSrcApp) zip.file(folder+"src/App."+ext,"import React from 'react'\nimport './App.css'\n\nfunction App(){\n return(\n
\n
\n

"+slugTitle(pn)+"

\n

Built with PantheraHive BOS

\n
\n
\n )\n}\nexport default App\n"); zip.file(folder+"src/index.css","*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:system-ui,-apple-system,sans-serif;background:#f0f2f5;color:#1a1a2e}\n.app{min-height:100vh;display:flex;flex-direction:column}\n.app-header{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:40px}\nh1{font-size:2.5rem;font-weight:700}\n"); zip.file(folder+"src/App.css",""); zip.file(folder+"src/components/.gitkeep",""); zip.file(folder+"src/pages/.gitkeep",""); zip.file(folder+"src/hooks/.gitkeep",""); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nnpm run dev\n\`\`\`\n\n## Build\n\`\`\`bash\nnpm run build\n\`\`\`\n\n## Open in IDE\nOpen the project folder in VS Code or WebStorm.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n"); } /* --- Vue (Vite + Composition API + TypeScript) --- */ function buildVue(zip,folder,app,code,panelTxt){ var pn=pkgName(app); var C=cc(pn); var extracted=extractCode(panelTxt); zip.file(folder+"package.json",'{\n "name": "'+pn+'",\n "version": "0.0.0",\n "type": "module",\n "scripts": {\n "dev": "vite",\n "build": "vue-tsc -b && vite build",\n "preview": "vite preview"\n },\n "dependencies": {\n "vue": "^3.5.13",\n "vue-router": "^4.4.5",\n "pinia": "^2.3.0",\n "axios": "^1.7.9"\n },\n "devDependencies": {\n "@vitejs/plugin-vue": "^5.2.1",\n "typescript": "~5.7.3",\n "vite": "^6.0.5",\n "vue-tsc": "^2.2.0"\n }\n}\n'); zip.file(folder+"vite.config.ts","import { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport { resolve } from 'path'\n\nexport default defineConfig({\n plugins: [vue()],\n resolve: { alias: { '@': resolve(__dirname,'src') } }\n})\n"); zip.file(folder+"tsconfig.json",'{"files":[],"references":[{"path":"./tsconfig.app.json"},{"path":"./tsconfig.node.json"}]}\n'); zip.file(folder+"tsconfig.app.json",'{\n "compilerOptions":{\n "target":"ES2020","useDefineForClassFields":true,"module":"ESNext","lib":["ES2020","DOM","DOM.Iterable"],\n "skipLibCheck":true,"moduleResolution":"bundler","allowImportingTsExtensions":true,\n "isolatedModules":true,"moduleDetection":"force","noEmit":true,"jsxImportSource":"vue",\n "strict":true,"paths":{"@/*":["./src/*"]}\n },\n "include":["src/**/*.ts","src/**/*.d.ts","src/**/*.tsx","src/**/*.vue"]\n}\n'); zip.file(folder+"env.d.ts","/// \n"); zip.file(folder+"index.html","\n\n\n \n \n "+slugTitle(pn)+"\n\n\n
\n \n\n\n"); var hasMain=Object.keys(extracted).some(function(k){return k==="src/main.ts"||k==="main.ts";}); if(!hasMain) zip.file(folder+"src/main.ts","import { createApp } from 'vue'\nimport { createPinia } from 'pinia'\nimport App from './App.vue'\nimport './assets/main.css'\n\nconst app = createApp(App)\napp.use(createPinia())\napp.mount('#app')\n"); var hasApp=Object.keys(extracted).some(function(k){return k.indexOf("App.vue")>=0;}); if(!hasApp) zip.file(folder+"src/App.vue","\n\n\n\n\n"); zip.file(folder+"src/assets/main.css","*{margin:0;padding:0;box-sizing:border-box}body{font-family:system-ui,sans-serif;background:#fff;color:#213547}\n"); zip.file(folder+"src/components/.gitkeep",""); zip.file(folder+"src/views/.gitkeep",""); zip.file(folder+"src/stores/.gitkeep",""); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nnpm run dev\n\`\`\`\n\n## Build\n\`\`\`bash\nnpm run build\n\`\`\`\n\nOpen in VS Code or WebStorm.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n"); } /* --- Angular (v19 standalone) --- */ function buildAngular(zip,folder,app,code,panelTxt){ var pn=pkgName(app); var C=cc(pn); var sel=pn.replace(/_/g,"-"); var extracted=extractCode(panelTxt); zip.file(folder+"package.json",'{\n "name": "'+pn+'",\n "version": "0.0.0",\n "scripts": {\n "ng": "ng",\n "start": "ng serve",\n "build": "ng build",\n "test": "ng test"\n },\n "dependencies": {\n "@angular/animations": "^19.0.0",\n "@angular/common": "^19.0.0",\n "@angular/compiler": "^19.0.0",\n "@angular/core": "^19.0.0",\n "@angular/forms": "^19.0.0",\n "@angular/platform-browser": "^19.0.0",\n "@angular/platform-browser-dynamic": "^19.0.0",\n "@angular/router": "^19.0.0",\n "rxjs": "~7.8.0",\n "tslib": "^2.3.0",\n "zone.js": "~0.15.0"\n },\n "devDependencies": {\n "@angular-devkit/build-angular": "^19.0.0",\n "@angular/cli": "^19.0.0",\n "@angular/compiler-cli": "^19.0.0",\n "typescript": "~5.6.0"\n }\n}\n'); zip.file(folder+"angular.json",'{\n "$schema": "./node_modules/@angular/cli/lib/config/schema.json",\n "version": 1,\n "newProjectRoot": "projects",\n "projects": {\n "'+pn+'": {\n "projectType": "application",\n "root": "",\n "sourceRoot": "src",\n "prefix": "app",\n "architect": {\n "build": {\n "builder": "@angular-devkit/build-angular:application",\n "options": {\n "outputPath": "dist/'+pn+'",\n "index": "src/index.html",\n "browser": "src/main.ts",\n "tsConfig": "tsconfig.app.json",\n "styles": ["src/styles.css"],\n "scripts": []\n }\n },\n "serve": {"builder":"@angular-devkit/build-angular:dev-server","configurations":{"production":{"buildTarget":"'+pn+':build:production"},"development":{"buildTarget":"'+pn+':build:development"}},"defaultConfiguration":"development"}\n }\n }\n }\n}\n'); zip.file(folder+"tsconfig.json",'{\n "compileOnSave": false,\n "compilerOptions": {"baseUrl":"./","outDir":"./dist/out-tsc","forceConsistentCasingInFileNames":true,"strict":true,"noImplicitOverride":true,"noPropertyAccessFromIndexSignature":true,"noImplicitReturns":true,"noFallthroughCasesInSwitch":true,"paths":{"@/*":["src/*"]},"skipLibCheck":true,"esModuleInterop":true,"sourceMap":true,"declaration":false,"experimentalDecorators":true,"moduleResolution":"bundler","importHelpers":true,"target":"ES2022","module":"ES2022","useDefineForClassFields":false,"lib":["ES2022","dom"]},\n "references":[{"path":"./tsconfig.app.json"}]\n}\n'); zip.file(folder+"tsconfig.app.json",'{\n "extends":"./tsconfig.json",\n "compilerOptions":{"outDir":"./dist/out-tsc","types":[]},\n "files":["src/main.ts"],\n "include":["src/**/*.d.ts"]\n}\n'); zip.file(folder+"src/index.html","\n\n\n \n "+slugTitle(pn)+"\n \n \n \n\n\n \n\n\n"); zip.file(folder+"src/main.ts","import { bootstrapApplication } from '@angular/platform-browser';\nimport { appConfig } from './app/app.config';\nimport { AppComponent } from './app/app.component';\n\nbootstrapApplication(AppComponent, appConfig)\n .catch(err => console.error(err));\n"); zip.file(folder+"src/styles.css","* { margin: 0; padding: 0; box-sizing: border-box; }\nbody { font-family: system-ui, -apple-system, sans-serif; background: #f9fafb; color: #111827; }\n"); var hasComp=Object.keys(extracted).some(function(k){return k.indexOf("app.component")>=0;}); if(!hasComp){ zip.file(folder+"src/app/app.component.ts","import { Component } from '@angular/core';\nimport { RouterOutlet } from '@angular/router';\n\n@Component({\n selector: 'app-root',\n standalone: true,\n imports: [RouterOutlet],\n templateUrl: './app.component.html',\n styleUrl: './app.component.css'\n})\nexport class AppComponent {\n title = '"+pn+"';\n}\n"); zip.file(folder+"src/app/app.component.html","
\n
\n

"+slugTitle(pn)+"

\n

Built with PantheraHive BOS

\n
\n \n
\n"); zip.file(folder+"src/app/app.component.css",".app-header{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:60vh;gap:16px}h1{font-size:2.5rem;font-weight:700;color:#6366f1}\n"); } zip.file(folder+"src/app/app.config.ts","import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';\nimport { provideRouter } from '@angular/router';\nimport { routes } from './app.routes';\n\nexport const appConfig: ApplicationConfig = {\n providers: [\n provideZoneChangeDetection({ eventCoalescing: true }),\n provideRouter(routes)\n ]\n};\n"); zip.file(folder+"src/app/app.routes.ts","import { Routes } from '@angular/router';\n\nexport const routes: Routes = [];\n"); Object.keys(extracted).forEach(function(p){ var fp=p.startsWith("src/")?p:"src/"+p; zip.file(folder+fp,extracted[p]); }); zip.file(folder+"README.md","# "+slugTitle(pn)+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\nng serve\n# or: npm start\n\`\`\`\n\n## Build\n\`\`\`bash\nng build\n\`\`\`\n\nOpen in VS Code with Angular Language Service extension.\n"); zip.file(folder+".gitignore","node_modules/\ndist/\n.env\n.DS_Store\n*.local\n.angular/\n"); } /* --- Python --- */ function buildPython(zip,folder,app,code){ var title=slugTitle(app); var pn=pkgName(app); var src=code.replace(/^\`\`\`[\w]*\n?/m,"").replace(/\n?\`\`\`$/m,"").trim(); var reqMap={"numpy":"numpy","pandas":"pandas","sklearn":"scikit-learn","tensorflow":"tensorflow","torch":"torch","flask":"flask","fastapi":"fastapi","uvicorn":"uvicorn","requests":"requests","sqlalchemy":"sqlalchemy","pydantic":"pydantic","dotenv":"python-dotenv","PIL":"Pillow","cv2":"opencv-python","matplotlib":"matplotlib","seaborn":"seaborn","scipy":"scipy"}; var reqs=[]; Object.keys(reqMap).forEach(function(k){if(src.indexOf("import "+k)>=0||src.indexOf("from "+k)>=0)reqs.push(reqMap[k]);}); var reqsTxt=reqs.length?reqs.join("\n"):"# add dependencies here\n"; zip.file(folder+"main.py",src||"# "+title+"\n# Generated by PantheraHive BOS\n\nprint(title+\" loaded\")\n"); zip.file(folder+"requirements.txt",reqsTxt); zip.file(folder+".env.example","# Environment variables\n"); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\npython3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\n\`\`\`\n\n## Run\n\`\`\`bash\npython main.py\n\`\`\`\n"); zip.file(folder+".gitignore",".venv/\n__pycache__/\n*.pyc\n.env\n.DS_Store\n"); } /* --- Node.js --- */ function buildNode(zip,folder,app,code){ var title=slugTitle(app); var pn=pkgName(app); var src=code.replace(/^\`\`\`[\w]*\n?/m,"").replace(/\n?\`\`\`$/m,"").trim(); var depMap={"mongoose":"^8.0.0","dotenv":"^16.4.5","axios":"^1.7.9","cors":"^2.8.5","bcryptjs":"^2.4.3","jsonwebtoken":"^9.0.2","socket.io":"^4.7.4","uuid":"^9.0.1","zod":"^3.22.4","express":"^4.18.2"}; var deps={}; Object.keys(depMap).forEach(function(k){if(src.indexOf(k)>=0)deps[k]=depMap[k];}); if(!deps["express"])deps["express"]="^4.18.2"; var pkgJson=JSON.stringify({"name":pn,"version":"1.0.0","main":"src/index.js","scripts":{"start":"node src/index.js","dev":"nodemon src/index.js"},"dependencies":deps,"devDependencies":{"nodemon":"^3.0.3"}},null,2)+"\n"; zip.file(folder+"package.json",pkgJson); var fallback="const express=require(\"express\");\nconst app=express();\napp.use(express.json());\n\napp.get(\"/\",(req,res)=>{\n res.json({message:\""+title+" API\"});\n});\n\nconst PORT=process.env.PORT||3000;\napp.listen(PORT,()=>console.log(\"Server on port \"+PORT));\n"; zip.file(folder+"src/index.js",src||fallback); zip.file(folder+".env.example","PORT=3000\n"); zip.file(folder+".gitignore","node_modules/\n.env\n.DS_Store\n"); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Setup\n\`\`\`bash\nnpm install\n\`\`\`\n\n## Run\n\`\`\`bash\nnpm run dev\n\`\`\`\n"); } /* --- Vanilla HTML --- */ function buildVanillaHtml(zip,folder,app,code){ var title=slugTitle(app); var isFullDoc=code.trim().toLowerCase().indexOf("=0||code.trim().toLowerCase().indexOf("=0; var indexHtml=isFullDoc?code:"\n\n\n\n\n"+title+"\n\n\n\n"+code+"\n\n\n\n"; zip.file(folder+"index.html",indexHtml); zip.file(folder+"style.css","/* "+title+" — styles */\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:system-ui,-apple-system,sans-serif;background:#fff;color:#1a1a2e}\n"); zip.file(folder+"script.js","/* "+title+" — scripts */\n"); zip.file(folder+"assets/.gitkeep",""); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\n## Open\nDouble-click \`index.html\` in your browser.\n\nOr serve locally:\n\`\`\`bash\nnpx serve .\n# or\npython3 -m http.server 3000\n\`\`\`\n"); zip.file(folder+".gitignore",".DS_Store\nnode_modules/\n.env\n"); } /* ===== MAIN ===== */ var sc=document.createElement("script"); sc.src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"; sc.onerror=function(){ if(lbl)lbl.textContent="Download ZIP"; alert("JSZip load failed — check connection."); }; sc.onload=function(){ var zip=new JSZip(); var base=(_phFname||"output").replace(/\.[^.]+$/,""); var app=base.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"")||"my_app"; var folder=app+"/"; var vc=document.getElementById("panel-content"); var panelTxt=vc?(vc.innerText||vc.textContent||""):""; var lang=detectLang(_phCode,panelTxt); if(_phIsHtml){ buildVanillaHtml(zip,folder,app,_phCode); } else if(lang==="flutter"){ buildFlutter(zip,folder,app,_phCode,panelTxt); } else if(lang==="react-native"){ buildReactNative(zip,folder,app,_phCode,panelTxt); } else if(lang==="swift"){ buildSwift(zip,folder,app,_phCode,panelTxt); } else if(lang==="kotlin"){ buildKotlin(zip,folder,app,_phCode,panelTxt); } else if(lang==="react"){ buildReact(zip,folder,app,_phCode,panelTxt); } else if(lang==="vue"){ buildVue(zip,folder,app,_phCode,panelTxt); } else if(lang==="angular"){ buildAngular(zip,folder,app,_phCode,panelTxt); } else if(lang==="python"){ buildPython(zip,folder,app,_phCode); } else if(lang==="node"){ buildNode(zip,folder,app,_phCode); } else { /* Document/content workflow */ var title=app.replace(/_/g," "); var md=_phAll||_phCode||panelTxt||"No content"; zip.file(folder+app+".md",md); var h=""+title+""; h+="

"+title+"

"; var hc=md.replace(/&/g,"&").replace(//g,">"); hc=hc.replace(/^### (.+)$/gm,"

$1

"); hc=hc.replace(/^## (.+)$/gm,"

$1

"); hc=hc.replace(/^# (.+)$/gm,"

$1

"); hc=hc.replace(/\*\*(.+?)\*\*/g,"$1"); hc=hc.replace(/\n{2,}/g,"

"); h+="

"+hc+"

Generated by PantheraHive BOS
"; zip.file(folder+app+".html",h); zip.file(folder+"README.md","# "+title+"\n\nGenerated by PantheraHive BOS.\n\nFiles:\n- "+app+".md (Markdown)\n- "+app+".html (styled HTML)\n"); } zip.generateAsync({type:"blob"}).then(function(blob){ var a=document.createElement("a"); a.href=URL.createObjectURL(blob); a.download=app+".zip"; a.click(); URL.revokeObjectURL(a.href); if(lbl)lbl.textContent="Download ZIP"; }); }; document.head.appendChild(sc); } function phShare(){navigator.clipboard.writeText(window.location.href).then(function(){var el=document.getElementById("ph-share-lbl");if(el){el.textContent="Link copied!";setTimeout(function(){el.textContent="Copy share link";},2500);}});}function phEmbed(){var runId=window.location.pathname.split("/").pop().replace(".html","");var embedUrl="https://pantherahive.com/embed/"+runId;var code='';navigator.clipboard.writeText(code).then(function(){var el=document.getElementById("ph-embed-lbl");if(el){el.textContent="Embed code copied!";setTimeout(function(){el.textContent="Get Embed Code";},2500);}});}