Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Api for Plugin using smaple_data in api #3294

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -1436,6 +1436,9 @@ type Mutation {
"""Mutation field to update an organization membership."""
updateOrganizationMembership(input: MutationUpdateOrganizationMembershipInput!): Organization

"""Toggle plugin installation status for an organization"""
updatePluginStatus(orgId: String!, pluginId: String!): Plugin

"""Mutation field to update a post."""
updatePost(input: MutationUpdatePostInput!): Post

Expand Down Expand Up @@ -2753,6 +2756,23 @@ A field whose value conforms to the standard E.164 format as specified in: https
"""
scalar PhoneNumber

type Plugin {
"""Unique identifier for the plugin"""
id: String

"""Creator of the plugin"""
pluginCreatedBy: String

"""Description of the plugin"""
pluginDesc: String

"""Name of the plugin"""
pluginName: String

"""List of organization IDs where the plugin is not installed"""
uninstalledOrgs: [String!]
}

type Post {
"""Array of attachments."""
attachments: [PostAttachment!]
Expand Down Expand Up @@ -2896,11 +2916,15 @@ type Query {
"""Query field to read a fund campaign pledge."""
fundCampaignPledge(input: QueryFundCampaignPledgeInput!): FundCampaignPledge

"""Fetch all available plugins"""
getPlugins: [Plugin!]

"""Query field to read an organization."""
organization(input: QueryOrganizationInput!): Organization

"""Query to fetch all organizations. Returns up to 20 organizations."""
organizations: [Organization!]
plugin(input: QueryPluginInput!): Plugin

"""Query field to read a post."""
post(input: QueryPostInput!): Post
Expand Down Expand Up @@ -3007,6 +3031,14 @@ input QueryOrganizationInput {
id: String!
}

input QueryPluginInput {
"""ID of the plugin to query"""
id: String

"""Name of the plugin to query"""
pluginName: String!
}

""""""
input QueryPostInput {
"""Global id of the post."""
Expand Down
32 changes: 32 additions & 0 deletions src/graphql/inputs/QueryPluginInput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// src/graphql/inputs/QueryPluginInput.ts
import { z } from "zod";
import { builder } from "~/src/graphql/builder";

// Define the validation schema
export const queryPluginInputSchema = z.object({
id: z.string().uuid().optional(), // ID as an optional UUID
pluginName: z
.string()
.min(1, { message: "Plugin name must be at least 1 character long" })
.max(100, { message: "Plugin name must be at most 100 characters long" })
.regex(/^[a-zA-Z0-9-_]+$/, {
message:
"Plugin name can only contain alphanumeric characters, hyphens, and underscores",
}),
});

// Define the GraphQL input type
export const QueryPluginInput = builder
.inputRef<z.infer<typeof queryPluginInputSchema>>("QueryPluginInput")
.implement({
fields: (t) => ({
id: t.string({
description: "ID of the plugin to query",
required: false, // Optional field
}),
pluginName: t.string({
description: "Name of the plugin to query",
required: true,
}),
}),
});
46 changes: 46 additions & 0 deletions src/graphql/types/Plugins/Plugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { z } from "zod";
// src/graphql/types/Plugin/Plugin.ts
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Fix incorrect file path in the comment.

The comment references src/graphql/types/Plugin/Plugin.ts (singular) but the actual path uses the plural form Plugins.

-// src/graphql/types/Plugin/Plugin.ts
+// src/graphql/types/Plugins/Plugins.ts
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// src/graphql/types/Plugin/Plugin.ts
// src/graphql/types/Plugins/Plugins.ts


import { builder } from "~/src/graphql/builder";

// Define the Plugin type
type Plugin = {
id: string;
pluginName: string;
pluginCreatedBy: string;
pluginDesc: string;
uninstalledOrgs: string[];
};

// Define validation schema
export const pluginSchema = z.object({
id: z.string().uuid(),
pluginName: z.string().min(1),
pluginCreatedBy: z.string().min(1),
pluginDesc: z.string().min(1),
uninstalledOrgs: z.array(z.string()),
});

// Create Plugin object reference
export const PluginRef = builder.objectRef<Plugin>("Plugin");

// Implement Plugin type
PluginRef.implement({
fields: (t) => ({
id: t.exposeString("id", {
description: "Unique identifier for the plugin",
}),
pluginName: t.exposeString("pluginName", {
description: "Name of the plugin",
}),
pluginCreatedBy: t.exposeString("pluginCreatedBy", {
description: "Creator of the plugin",
}),
pluginDesc: t.exposeString("pluginDesc", {
description: "Description of the plugin",
}),
uninstalledOrgs: t.exposeStringList("uninstalledOrgs", {
description: "List of organization IDs where the plugin is not installed",
}),
}),
});
30 changes: 30 additions & 0 deletions src/graphql/types/Plugins/pluginData.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
[
{
"id": "c1f8a0b3-9d4e-4a7b-b6f5-1a2d3c4e5f6g",
"pluginName": "Posts",
"pluginCreatedBy": "Talawa Team",
"pluginDesc": "Allow users to create, comment and like and share the content in the form of Pictures and Videos.",
"uninstalledOrgs": ["ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5e"]
},
{
"id": "d2e9b1a4-7c5f-4d3b-8e6f-2g3h4i5j6k7l",
"pluginName": "Events",
"pluginCreatedBy": "Talawa Team",
"pluginDesc": "Allow users to register and attend for new events with an inbuilt calendar to keep track of daily events.",
"uninstalledOrgs": ["ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5e"]
},
{
"id": "e3f0c2d5-8g6h-4i7j-9k8l-3m4n5o6p7q8r",
"pluginName": "Donation",
"pluginCreatedBy": "Talawa Team",
"pluginDesc": "Enables members of the organization to do one-time or recurring donations to an organization.",
"uninstalledOrgs": ["ab1c2d3e-4f5b-6a7c-8d9e-0f1a2b3c4d5e"]
},
{
"id": "f4g1h3i6-9j8k-4l7m-0n9o-5p6q7r8s9t0u",
"pluginName": "Chats",
"pluginCreatedBy": "Talawa Team",
"pluginDesc": "User can share messages with other users in a chat user interface.",
"uninstalledOrgs": []
}
]
1 change: 1 addition & 0 deletions src/graphql/types/Query/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import "./tag";
import "./tagFolder";
import "./user";
import "./venue";
import "./plugin";
import "./allUsers";

import "./organizations";
181 changes: 181 additions & 0 deletions src/graphql/types/Query/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { builder } from "~/src/graphql/builder";
import { QueryPluginInput } from "~/src/graphql/inputs/QueryPluginInput";
import { TalawaGraphQLError } from "~/src/utilities/TalawaGraphQLError";
import { PluginRef } from "../Plugins/Plugins";
import pluginsData from "../Plugins/pluginData.json" assert { type: "json" };
Comment on lines +1 to +5
Copy link

@coderabbitai coderabbitai bot Feb 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Imports look fine, but please address formatting issues.

All imports appear valid and necessary. However, according to the pipeline failures, the codebase's formatting checks are failing on this file. Please ensure you run the project's formatter to resolve any style inconsistencies.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you format it and i do change in my code

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's retrieve the full content of the file
cat src/graphql/types/Query/plugin.ts

# Check if there are specific formatting tools/commands used in the project
ls -la package.json
cat package.json | grep -E "format|lint" | grep -v "ignore"

Length of output: 4501


🏁 Script executed:

#!/bin/bash
# Check for common formatter/linter config files
ls -la .eslintrc* .prettierrc* .editorconfig* tsconfig.json

# Check package.json scripts to find format commands
jq '.scripts' package.json

# Try to find formatting-related dependencies
jq '.devDependencies' package.json | grep -E 'eslint|prettier|format'

Length of output: 2107


🏁 Script executed:

#!/bin/bash
# Check for biome configuration file
ls -la biome.json

# If biome configuration exists, display it
if [ -f biome.json ]; then
  cat biome.json
fi

# Try to run the project's formatter command on the file
pnpm fix_code_quality src/graphql/types/Query/plugin.ts || npm run fix_code_quality src/graphql/types/Query/plugin.ts

Length of output: 432


@MayankJha014 I ran the project's formatter command (pnpm fix_code_quality) on your file, but it reported "No fixes applied", which suggests the file is already correctly formatted according to the project's standards.

import { builder } from "~/src/graphql/builder";
import { QueryPluginInput } from "~/src/graphql/inputs/QueryPluginInput";
import { TalawaGraphQLError } from "~/src/utilities/TalawaGraphQLError";
import { PluginRef } from "../Plugins/Plugins";
import pluginsData from "../Plugins/pluginData.json" assert { type: "json" };

builder.queryField("getPlugins", (t) =>
	t.field({
		type: [PluginRef],
		description: "Fetch all available plugins",
		resolve: async (_parent, _args, ctx) => {
			try {
				// Check authentication
				if (!ctx.currentClient.isAuthenticated) {
					throw new TalawaGraphQLError({
						extensions: {
							code: "unauthenticated",
						},
					});
				}

				return pluginsData;
			} catch (error) {
				if (error instanceof TalawaGraphQLError) {
					throw error;
				}
				throw new TalawaGraphQLError({
					extensions: {
						code: "unexpected",
					},
				});
			}
		},
	}),
);

// Create a cache based on plugin ID
const pluginCacheById = new Map(
	pluginsData.map((plugin) => [plugin.id, plugin]),
);

// Create a cache based on plugin name (case insensitive)
const pluginCacheByName = new Map(
	pluginsData.map((plugin) => [plugin.pluginName.toLowerCase(), plugin]),
);

builder.queryField("plugin", (t) =>
	t.field({
		type: PluginRef,
		args: {
			input: t.arg({
				type: QueryPluginInput,
				required: true,
			}),
		},
		resolve: async (_parent, { input }, ctx) => {
			try {
				// Check authentication
				if (!ctx.currentClient.isAuthenticated) {
					throw new TalawaGraphQLError({
						extensions: {
							code: "unauthenticated",
						},
					});
				}

				// Validate input (either ID or pluginName must be provided)
				if (!input.pluginName?.trim() && !input.id?.trim()) {
					throw new TalawaGraphQLError({
						extensions: {
							code: "invalid_arguments",
							issues: [
								{
									argumentPath: ["input"],
									message: "Either plugin ID or plugin name must be provided",
								},
							],
						},
					});
				}
				let plugin: (typeof pluginsData)[0] | undefined;

				// Lookup by ID first if provided, otherwise by plugin name
				if (input.id) {
					plugin = pluginCacheById.get(input.id);
				} else if (input.pluginName) {
					plugin = pluginCacheByName.get(input.pluginName.toLowerCase());
				}

				if (!plugin) {
					throw new TalawaGraphQLError({
						extensions: {
							code: "arguments_associated_resources_not_found",
							issues: [
								{
									argumentPath: ["input"],
								},
							],
						},
					});
				}

				return plugin;
			} catch (error) {
				if (error instanceof TalawaGraphQLError) {
					throw error;
				}
				throw new TalawaGraphQLError({
					extensions: {
						code: "unexpected",
					},
				});
			}
		},
	}),
);

builder.mutationField("updatePluginStatus", (t) =>
	t.field({
		type: PluginRef,
		description: "Toggle plugin installation status for an organization",
		args: {
			pluginId: t.arg.string({ required: true }),
			orgId: t.arg.string({ required: true }),
		},
		resolve: async (_parent, { pluginId, orgId }, ctx) => {
			try {
				// Check authentication
				if (!ctx.currentClient.isAuthenticated) {
					throw new TalawaGraphQLError({
						extensions: {
							code: "unauthenticated",
						},
					});
				}

				// Find plugin
				const plugin = pluginCacheById.get(pluginId);
				if (!plugin) {
					throw new TalawaGraphQLError({
						extensions: {
							code: "arguments_associated_resources_not_found",
							issues: [
								{
									argumentPath: ["pluginId"],
								},
							],
						},
					});
				}

				// Check if the org is already in uninstalledOrgs
				const isUninstalled = plugin.uninstalledOrgs.includes(orgId);

				if (isUninstalled) {
					// Remove from uninstalledOrgs (installing the plugin)
					plugin.uninstalledOrgs = plugin.uninstalledOrgs.filter(
						(org) => org !== orgId,
					);
				} else {
					// Add to uninstalledOrgs (uninstalling the plugin)
					plugin.uninstalledOrgs.push(orgId);
				}

				// Update cache
				pluginCacheById.set(pluginId, plugin);

				return plugin;
			} catch (error) {
				if (error instanceof TalawaGraphQLError) {
					throw error;
				}
				throw new TalawaGraphQLError({
					extensions: {
						code: "unexpected",
					},
				});
			}
		},
	}),
);

The code looks well-structured with consistent indentation and proper spacing. If you're still experiencing pipeline failures, it might be related to:

  1. Line ending issues (CRLF vs LF)
  2. Different formatting rules in CI vs local
  3. Other files that might need formatting

You can try running the full project formatter with pnpm fix_code_quality without specifying files to fix all formatting issues across the codebase.


builder.queryField("getPlugins", (t) =>
t.field({
type: [PluginRef],
description: "Fetch all available plugins",
resolve: async (_parent, _args, ctx) => {
try {

Check warning on line 12 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L12

Added line #L12 was not covered by tests
// Check authentication
if (!ctx.currentClient.isAuthenticated) {
throw new TalawaGraphQLError({
extensions: {
code: "unauthenticated",
},
});
}

Check warning on line 20 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L14-L20

Added lines #L14 - L20 were not covered by tests

return pluginsData;
} catch (error) {
if (error instanceof TalawaGraphQLError) {
throw error;
}
throw new TalawaGraphQLError({
extensions: {
code: "unexpected",
},
});
}
},

Check warning on line 33 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L22-L33

Added lines #L22 - L33 were not covered by tests
}),
);
Comment on lines +7 to +35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Implement pagination for the getPlugins query.

Currently, the query returns all plugins without pagination, which could be problematic as the number of plugins grows. Consider implementing pagination for better scalability.

 builder.queryField("getPlugins", (t) =>
 	t.field({
 		type: [PluginRef],
 		description: "Fetch all available plugins",
+		args: {
+			skip: t.arg.int({
+				description: "Number of records to skip",
+				required: false,
+				defaultValue: 0
+			}),
+			limit: t.arg.int({
+				description: "Number of records to fetch",
+				required: false,
+				defaultValue: 10
+			})
+		},
 		resolve: async (_parent, _args, ctx) => {
 			try {
 				// Check authentication
 				if (!ctx.currentClient.isAuthenticated) {
 					throw new TalawaGraphQLError({
 						extensions: {
 							code: "unauthenticated",
 						},
 					});
 				}
 
-				return pluginsData;
+				const { skip = 0, limit = 10 } = _args;
+				return pluginsData.slice(skip, skip + limit);
 			} catch (error) {
 				if (error instanceof TalawaGraphQLError) {
 					throw error;
 				}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
builder.queryField("getPlugins", (t) =>
t.field({
type: [PluginRef],
description: "Fetch all available plugins",
resolve: async (_parent, _args, ctx) => {
try {
// Check authentication
if (!ctx.currentClient.isAuthenticated) {
throw new TalawaGraphQLError({
extensions: {
code: "unauthenticated",
},
});
}
return pluginsData;
} catch (error) {
if (error instanceof TalawaGraphQLError) {
throw error;
}
throw new TalawaGraphQLError({
extensions: {
code: "unexpected",
},
});
}
},
}),
);
builder.queryField("getPlugins", (t) =>
t.field({
type: [PluginRef],
description: "Fetch all available plugins",
args: {
skip: t.arg.int({
description: "Number of records to skip",
required: false,
defaultValue: 0
}),
limit: t.arg.int({
description: "Number of records to fetch",
required: false,
defaultValue: 10
})
},
resolve: async (_parent, _args, ctx) => {
try {
// Check authentication
if (!ctx.currentClient.isAuthenticated) {
throw new TalawaGraphQLError({
extensions: {
code: "unauthenticated",
},
});
}
const { skip = 0, limit = 10 } = _args;
return pluginsData.slice(skip, skip + limit);
} catch (error) {
if (error instanceof TalawaGraphQLError) {
throw error;
}
throw new TalawaGraphQLError({
extensions: {
code: "unexpected",
},
});
}
},
}),
);


// Create a cache based on plugin ID
const pluginCacheById = new Map(
pluginsData.map((plugin) => [plugin.id, plugin]),
);

// Create a cache based on plugin name (case insensitive)
const pluginCacheByName = new Map(
pluginsData.map((plugin) => [plugin.pluginName.toLowerCase(), plugin]),
);

builder.queryField("plugin", (t) =>
t.field({
type: PluginRef,
args: {
input: t.arg({
type: QueryPluginInput,
required: true,
}),
},
resolve: async (_parent, { input }, ctx) => {
try {

Check warning on line 57 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L57

Added line #L57 was not covered by tests
// Check authentication
if (!ctx.currentClient.isAuthenticated) {
throw new TalawaGraphQLError({
extensions: {
code: "unauthenticated",
},
});
}

Check warning on line 65 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L59-L65

Added lines #L59 - L65 were not covered by tests

// Validate input (either ID or pluginName must be provided)
if (!input.pluginName?.trim() && !input.id?.trim()) {
throw new TalawaGraphQLError({
extensions: {
code: "invalid_arguments",
issues: [
{
argumentPath: ["input"],
message: "Either plugin ID or plugin name must be provided",
},
],
},
});
}
let plugin: (typeof pluginsData)[0] | undefined;

Check warning on line 81 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L68-L81

Added lines #L68 - L81 were not covered by tests

// Lookup by ID first if provided, otherwise by plugin name
if (input.id) {
plugin = pluginCacheById.get(input.id);
} else if (input.pluginName) {
plugin = pluginCacheByName.get(input.pluginName.toLowerCase());
}

Check warning on line 88 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L84-L88

Added lines #L84 - L88 were not covered by tests

if (!plugin) {
throw new TalawaGraphQLError({
extensions: {
code: "arguments_associated_resources_not_found",
issues: [
{
argumentPath: ["input"],
},
],
},
});
}

Check warning on line 101 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L90-L101

Added lines #L90 - L101 were not covered by tests

return plugin;
} catch (error) {
if (error instanceof TalawaGraphQLError) {
throw error;
}
throw new TalawaGraphQLError({
extensions: {
code: "unexpected",
},
});
}
},

Check warning on line 114 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L103-L114

Added lines #L103 - L114 were not covered by tests
}),
);

builder.mutationField("updatePluginStatus", (t) =>
t.field({
type: PluginRef,
description: "Toggle plugin installation status for an organization",
args: {
pluginId: t.arg.string({ required: true }),
orgId: t.arg.string({ required: true }),
},
resolve: async (_parent, { pluginId, orgId }, ctx) => {
try {

Check warning on line 127 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L127

Added line #L127 was not covered by tests
// Check authentication
if (!ctx.currentClient.isAuthenticated) {
throw new TalawaGraphQLError({
extensions: {
code: "unauthenticated",
},
});
}

Check warning on line 135 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L129-L135

Added lines #L129 - L135 were not covered by tests

Comment on lines +126 to +136
Copy link

@coderabbitai coderabbitai bot Feb 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add authorization checks for the updatePluginStatus mutation.

Currently, the mutation only checks if the user is authenticated but doesn't verify if they have permission to update the plugin status for a specific organization. Consider adding role-based authorization checks.

 resolve: async (_parent, { pluginId, orgId }, ctx) => {
 	try {
 		// Check authentication
 		if (!ctx.currentClient.isAuthenticated) {
 			throw new TalawaGraphQLError({
 				extensions: {
 					code: "unauthenticated",
 				},
 			});
 		}
+
+		// Check authorization - verify the user has admin rights for the organization
+		const isAdmin = await ctx.dataSource.userRoleForOrganization(
+			ctx.currentClient.userId, 
+			orgId
+		) === 'ADMIN';
+		
+		if (!isAdmin) {
+			throw new TalawaGraphQLError({
+				extensions: {
+					code: "unauthorized",
+					message: "Only organization administrators can update plugin installation status"
+				},
+			});
+		}

 		// Find plugin
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
resolve: async (_parent, { pluginId, orgId }, ctx) => {
try {
// Check authentication
if (!ctx.currentClient.isAuthenticated) {
throw new TalawaGraphQLError({
extensions: {
code: "unauthenticated",
},
});
}
resolve: async (_parent, { pluginId, orgId }, ctx) => {
try {
// Check authentication
if (!ctx.currentClient.isAuthenticated) {
throw new TalawaGraphQLError({
extensions: {
code: "unauthenticated",
},
});
}
// Check authorization - verify the user has admin rights for the organization
const isAdmin = await ctx.dataSource.userRoleForOrganization(
ctx.currentClient.userId,
orgId
) === 'ADMIN';
if (!isAdmin) {
throw new TalawaGraphQLError({
extensions: {
code: "unauthorized",
message: "Only organization administrators can update plugin installation status"
},
});
}
// Find plugin

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in ctx dataSource is not available

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Let's first look at how the context object is used in the PR
rg "ctx\." src/graphql/types/Query/plugin.ts -A 2 -B 2

# Look for authorization patterns in mutation resolvers
rg "authorization|isAuthorized|hasPermission|isAdmin|userRole" src/graphql/types/ -A 5 -B 5

# Look at imports in the plugin.ts file to understand what's available
head -20 src/graphql/types/Query/plugin.ts

# Check how roles and permissions are typically checked in the codebase
rg "userRoleForOrganization|getUserRole|role.*organization" src/ -A 3 -B 3

Length of output: 9359


🏁 Script executed:

#!/bin/bash
# Let's look at the GraphQL context definition to understand its structure
rg -g "*.ts" "GraphQLContext|context" src/graphql --include="*.d.ts" --include="context.ts" -A 10 -B 10

# Look for examples of organization role checks in mutation resolvers
rg -g "*.ts" "organization.*role|isAdmin" src/graphql/types/Mutation -A 5 -B 5

# Let's check if there are examples of fetching organization memberships
rg -g "*.ts" "organizationMemberships" src/graphql/types/ -A 5 -B 5

# Look at how plugins and organization permissions are handled elsewhere
rg -g "*.ts" "plugin.*organization|organization.*plugin" src/ -A 5 -B 5

Length of output: 75586


@MayankJha014 You're right, the dataSource property isn't available in the context. Let me provide a correct implementation that follows the codebase's patterns for authorization checks:

// Check authorization - verify the user has admin rights for the organization
const currentUserId = ctx.currentClient.user.id;
const currentUser = await ctx.drizzleClient.query.usersTable.findFirst({
  with: {
    organizationMembershipsWhereMember: {
      columns: {
        role: true,
      },
      where: (fields, operators) =>
        operators.eq(fields.organizationId, orgId),
    },
  },
  where: (fields, operators) => operators.eq(fields.id, currentUserId),
});

const isGlobalAdmin = currentUser.role === "administrator";
const currentUserOrganizationMembership = 
  currentUser.organizationMembershipsWhereMember[0];
const isOrgAdmin = currentUserOrganizationMembership?.role === "administrator";

if (!isGlobalAdmin && !isOrgAdmin) {
  throw new TalawaGraphQLError({
    extensions: {
      code: "unauthorized_action",
      message: "Only organization administrators can update plugin installation status"
    },
  });
}

// Find plugin

This follows the authentication pattern used throughout the codebase, checking if the user is either a global admin or an admin of the specific organization.

// Find plugin
const plugin = pluginCacheById.get(pluginId);
if (!plugin) {
throw new TalawaGraphQLError({
extensions: {
code: "arguments_associated_resources_not_found",
issues: [
{
argumentPath: ["pluginId"],
},
],
},
});
}

Check warning on line 150 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L138-L150

Added lines #L138 - L150 were not covered by tests

// Check if the org is already in uninstalledOrgs
const isUninstalled = plugin.uninstalledOrgs.includes(orgId);

Check warning on line 153 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L153

Added line #L153 was not covered by tests

if (isUninstalled) {

Check warning on line 155 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L155

Added line #L155 was not covered by tests
// Remove from uninstalledOrgs (installing the plugin)
plugin.uninstalledOrgs = plugin.uninstalledOrgs.filter(
(org) => org !== orgId,
);
} else {

Check warning on line 160 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L157-L160

Added lines #L157 - L160 were not covered by tests
// Add to uninstalledOrgs (uninstalling the plugin)
plugin.uninstalledOrgs.push(orgId);
}

Check warning on line 163 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L162-L163

Added lines #L162 - L163 were not covered by tests

// Update cache
pluginCacheById.set(pluginId, plugin);

Check warning on line 166 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L166

Added line #L166 was not covered by tests

return plugin;
} catch (error) {
if (error instanceof TalawaGraphQLError) {
throw error;
}
throw new TalawaGraphQLError({
extensions: {
code: "unexpected",
},
});
}
},

Check warning on line 179 in src/graphql/types/Query/plugin.ts

View check run for this annotation

Codecov / codecov/patch

src/graphql/types/Query/plugin.ts#L168-L179

Added lines #L168 - L179 were not covered by tests
}),
);
Comment on lines +118 to +181
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Add persistence mechanism for plugin changes.

The current implementation modifies in-memory data with the updatePluginStatus mutation. These changes will be lost on server restart since they're not persisted to a database or file system.

Consider implementing a persistence mechanism to save plugin installation status changes:

  1. Use an existing database connection if available in the project
  2. Implement file-based persistence to update the JSON file
  3. Add a comment indicating this is demo-only functionality if persistence is out of scope

🏁 Script executed:

#!/bin/bash
# Check for database or persistence implementations in the codebase
echo "Searching for database connection configurations..."
rg -l "mongoose|prisma|typeorm|sequelize|knex" --type ts

echo -e "\nChecking if sample data is meant to be persisted or just for demo..."
grep -r "sampleData" --include="*.ts" --include="*.js" .

Length of output: 654


Ensure persistent storage for plugin status changes

The mutation implemented in src/graphql/types/Query/plugin.ts (lines 118-181) currently updates the plugin's installation status solely in-memory. Our repository search did not reveal any database connection configurations (e.g., mongoose, prisma, typeorm, sequelize, knex) that could be used for persistence, aside from sample data handling in ./scripts/dbManagement/helpers.ts. This means that any changes made via the updatePluginStatus mutation will be lost upon server restart.

To address this, please consider one of the following actions:

  • Integrate a persistence layer: Utilize an existing database connection or add a new one to store these changes persistently.
  • Implement file-based persistence: Update a JSON file or similar storage mechanism to retain the plugin status.
  • Clarify demo-only behavior: If persistence is intentionally out of scope, add a comment in the code to explicitly state that the changes are temporary.

Loading
Loading