@semantq/pylon
Advanced tools
| import roleService from '../services/roleService.js'; | ||
| class RoleController { | ||
| async createRole(req, res) { | ||
| try { | ||
| const result = await roleService.create(req.body); | ||
| res.status(201).json(result); | ||
| } catch (err) { | ||
| res.status(400).json({ error: err.message }); | ||
| } | ||
| } | ||
| async getRoleById(req, res) { | ||
| try { | ||
| const result = await roleService.getById(req.params.id); | ||
| res.json(result); | ||
| } catch (err) { | ||
| res.status(404).json({ error: err.message }); | ||
| } | ||
| } | ||
| async getAllRoles(req, res) { | ||
| try { | ||
| const result = await roleService.getAll(); | ||
| res.json(result); | ||
| } catch (err) { | ||
| res.status(500).json({ error: err.message }); | ||
| } | ||
| } | ||
| // roleController.js | ||
| async getOrganizationRoles(req, res) { | ||
| try { | ||
| // 1. EXTRACT THE PARAMETER | ||
| const organizationId = parseInt(req.params.organizationId); // Assuming organizationId is an integer | ||
| // 2. PASS IT TO THE SERVICE | ||
| const result = await roleService.getOrganizationRoles(organizationId); | ||
| res.json(result); | ||
| } catch (err) { | ||
| // Handle non-numeric organizationId or other errors | ||
| console.error("Error fetching organization roles:", err); | ||
| res.status(500).json({ error: err.message || 'Internal server error' }); | ||
| } | ||
| } | ||
| async updateRole(req, res) { | ||
| try { | ||
| // Parse ID from string to integer (for sqlite/mysql; mongo uses string IDs) | ||
| const resourceId = parseInt(req.params.id, 10); | ||
| // Validate parsed ID | ||
| if (isNaN(resourceId)) { | ||
| return res.status(400).json({ error: 'Invalid Role ID provided. Must be a number.' }); | ||
| } | ||
| const result = await roleService.update(resourceId, req.body); | ||
| res.json(result); | ||
| } catch (err) { | ||
| res.status(400).json({ error: err.message }); | ||
| } | ||
| } | ||
| async deleteRole(req, res) { | ||
| try { | ||
| // Parse ID from string to integer (for sqlite/mysql; mongo uses string IDs) | ||
| const resourceId = parseInt(req.params.id, 10); | ||
| // Validate parsed ID | ||
| if (isNaN(resourceId)) { | ||
| return res.status(400).json({ error: 'Invalid Role ID provided. Must be a number.' }); | ||
| } | ||
| await roleService.delete(resourceId); | ||
| res.status(204).send(); | ||
| } catch (err) { | ||
| if (err.message.includes("Record to delete does not exist")) { | ||
| res.status(404).json({ error: "Role not found." }); | ||
| } else { | ||
| res.status(400).json({ error: err.message }); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| export default new RoleController(); |
| import userService from '../services/userService.js'; | ||
| class UserController { | ||
| async createUser(req, res) { | ||
| try { | ||
| const result = await userService.create(req.body); | ||
| res.status(201).json(result); | ||
| } catch (err) { | ||
| res.status(400).json({ error: err.message }); | ||
| } | ||
| } | ||
| async getUserPlan(req, res) { | ||
| try { | ||
| const result = await userService.getUserPlan(req.params.id); | ||
| res.json(result); | ||
| } catch (err) { | ||
| res.status(404).json({ error: err.message }); | ||
| } | ||
| } | ||
| // getOrganizationUsers | ||
| async getOrganizationUsers(req, res) { | ||
| try { | ||
| //console.log("REQ",req.params.organizationId); | ||
| const result = await userService.getOrganizationUsers(req.params.organizationId); | ||
| res.json(result); | ||
| } catch (err) { | ||
| res.status(404).json({ error: err.message }); | ||
| } | ||
| } | ||
| async getUserById(req, res) { | ||
| try { | ||
| const result = await userService.getById(req.params.id); | ||
| res.json(result); | ||
| } catch (err) { | ||
| res.status(404).json({ error: err.message }); | ||
| } | ||
| } | ||
| async getAllUsers(req, res) { | ||
| try { | ||
| const result = await userService.getAll(); | ||
| res.json(result); | ||
| } catch (err) { | ||
| res.status(500).json({ error: err.message }); | ||
| } | ||
| } | ||
| async updateUser(req, res) { | ||
| try { | ||
| // Parse ID from string to integer (for sqlite/mysql; mongo uses string IDs) | ||
| const resourceId = parseInt(req.params.id, 10); | ||
| // Validate parsed ID | ||
| if (isNaN(resourceId)) { | ||
| return res.status(400).json({ error: 'Invalid User ID provided. Must be a number.' }); | ||
| } | ||
| const result = await userService.update(resourceId, req.body); | ||
| res.json(result); | ||
| } catch (err) { | ||
| res.status(400).json({ error: err.message }); | ||
| } | ||
| } | ||
| async deleteUser(req, res) { | ||
| try { | ||
| // Parse ID from string to integer (for sqlite/mysql; mongo uses string IDs) | ||
| const resourceId = parseInt(req.params.id, 10); | ||
| // Validate parsed ID | ||
| if (isNaN(resourceId)) { | ||
| return res.status(400).json({ error: 'Invalid User ID provided. Must be a number.' }); | ||
| } | ||
| await userService.delete(resourceId); | ||
| res.status(204).send(); | ||
| } catch (err) { | ||
| if (err.message.includes("Record to delete does not exist")) { | ||
| res.status(404).json({ error: "User not found." }); | ||
| } else { | ||
| res.status(400).json({ error: err.message }); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| export default new UserController(); |
| // Add to your schema.prisma: | ||
| /* | ||
| model Role { | ||
| id String @id @default(uuid()) | ||
| name String | ||
| createdAt DateTime @default(now()) | ||
| updatedAt DateTime @updatedAt | ||
| // Add additional fields as needed | ||
| } | ||
| */ | ||
| // Import the function that returns the Prisma client promise | ||
| import getPrismaClient from '../../../../../lib/prisma.js'; | ||
| export default class RoleModel { | ||
| /** | ||
| * Creates a new role in the database. | ||
| * @param {object} data - The data for the new role. | ||
| * @returns {Promise<object>} The created role object. | ||
| */ | ||
| static async create(data) { | ||
| const prisma = await getPrismaClient(); // Get the initialized Prisma client | ||
| return prisma.role.create({ data }); | ||
| } | ||
| /** | ||
| * Finds a role by its unique ID. | ||
| * @param {string} id - The ID of the role to find. | ||
| * @returns {Promise<object|null>} The found role object, or null if not found. | ||
| */ | ||
| static async findById(id) { | ||
| const prisma = await getPrismaClient(); // Get the initialized Prisma client | ||
| return prisma.role.findUnique({ where: { id } }); | ||
| } | ||
| /** | ||
| * Retrieves all roles from the database. | ||
| * @returns {Promise<Array<object>>} An array of all role objects. | ||
| */ | ||
| static async findAll() { | ||
| const prisma = await getPrismaClient(); // Get the initialized Prisma client | ||
| return prisma.role.findMany(); | ||
| } | ||
| // ACCEPT THE PARAMETER | ||
| static async getOrganizationRoles(organizationId) { | ||
| const prisma = await getPrismaClient(); | ||
| // USE THE PARAMETER FOR FILTERING | ||
| return prisma.role.findMany({ | ||
| where: { | ||
| organizationId: organizationId, // Filters roles by the provided organizationId | ||
| isSystemRole: false, // Optional: You might want to exclude system roles | ||
| }, | ||
| }); | ||
| } | ||
| // getOrganizationRoles | ||
| /** | ||
| * Updates an existing role by its ID. | ||
| * @param {string} id - The ID of the role to update. | ||
| * @param {object} data - The data to update the role with. | ||
| * @returns {Promise<object>} The updated role object. | ||
| */ | ||
| static async update(id, data) { | ||
| const prisma = await getPrismaClient(); // Get the initialized Prisma client | ||
| return prisma.role.update({ | ||
| where: { id }, | ||
| data, | ||
| }); | ||
| } | ||
| /** | ||
| * Deletes a role by its ID. | ||
| * @param {string} id - The ID of the role to delete. | ||
| * @returns {Promise<object>} The deleted role object. | ||
| */ | ||
| static async delete(id) { | ||
| const prisma = await getPrismaClient(); // Get the initialized Prisma client | ||
| return prisma.role.delete({ where: { id } }); | ||
| } | ||
| /** | ||
| * Finds roles with pagination. | ||
| * @param {number} [skip=0] - The number of records to skip. | ||
| * @param {number} [take=10] - The number of records to take. | ||
| * @returns {Promise<Array<object>>} An array of role objects for the given pagination. | ||
| */ | ||
| static async findWithPagination(skip = 0, take = 10) { | ||
| const prisma = await getPrismaClient(); // Get the initialized Prisma client | ||
| return prisma.role.findMany({ | ||
| skip, | ||
| take, | ||
| orderBy: { createdAt: 'desc' }, // Assuming 'createdAt' field exists for ordering | ||
| }); | ||
| } | ||
| } |
| // Add to your schema.prisma: | ||
| /* | ||
| model User { | ||
| id String @id @default(uuid()) | ||
| name String | ||
| createdAt DateTime @default(now()) | ||
| updatedAt DateTime @updatedAt | ||
| // Add additional fields as needed | ||
| } | ||
| */ | ||
| // Import the function that returns the Prisma client promise | ||
| import getPrismaClient from '../../../../../lib/prisma.js'; | ||
| export default class UserModel { | ||
| /** | ||
| * Creates a new user in the database. | ||
| * @param {object} data - The data for the new user. | ||
| * @returns {Promise<object>} The created user object. | ||
| */ | ||
| static async create(data) { | ||
| const prisma = await getPrismaClient(); // Get the initialized Prisma client | ||
| return prisma.user.create({ data }); | ||
| } | ||
| /** | ||
| * Updates a user's organization ID (orgId) field. | ||
| * @param {number | string} userId - The ID of the user to update. | ||
| * @param {number | string} organizationId - The ID of the organization to link. | ||
| * @returns {Promise<object>} The updated user object. | ||
| */ | ||
| static async updateOrgId(userId, organizationId) { | ||
| const prisma = await getPrismaClient(); // Get the initialized Prisma client | ||
| // This assumes your User model has a field named 'organizationId' or similar | ||
| // and that 'id' in the where clause refers to the User's ID. | ||
| return prisma.user.update({ | ||
| where: { id: userId }, | ||
| data: { | ||
| organizationId: organizationId // CRITICAL: Provide the field name and data | ||
| }, | ||
| }); | ||
| } | ||
| /** | ||
| * Finds a user by its unique ID. | ||
| * @param {string} id - The ID of the user to find. | ||
| * @returns {Promise<object|null>} The found user object, or null if not found. | ||
| */ | ||
| static async findById(id) { | ||
| const prisma = await getPrismaClient(); // Get the initialized Prisma client | ||
| return prisma.user.findUnique({ where: { id } }); | ||
| } | ||
| /* | ||
| static async findByUuid(uuid) { | ||
| const prisma = await getPrismaClient(); // Get the initialized Prisma client | ||
| return prisma.user.findFirst({ where: { uuid } }); | ||
| } | ||
| */ | ||
| static async getPylonUserById(id) { | ||
| const prisma = await getPrismaClient(); | ||
| return prisma.user.findUnique({ | ||
| where: { id }, | ||
| include: { | ||
| organization: { | ||
| include: { | ||
| pricingPackage: { | ||
| include: { | ||
| features: { | ||
| include: { | ||
| feature: true | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * Retrieves all users from the database. | ||
| * @returns {Promise<Array<object>>} An array of all user objects. | ||
| */ | ||
| static async findAll() { | ||
| const prisma = await getPrismaClient(); // Get the initialized Prisma client | ||
| return prisma.user.findMany(); | ||
| } | ||
| static async getOrganizationUsers(organizationId) { | ||
| const prisma = await getPrismaClient(); // Get the initialized Prisma client | ||
| return prisma.user.findMany({ | ||
| where: { | ||
| organizationId: organizationId | ||
| } | ||
| }); | ||
| } | ||
| // | ||
| /** | ||
| * Updates an existing user by its ID. | ||
| * @param {string} id - The ID of the user to update. | ||
| * @param {object} data - The data to update the user with. | ||
| * @returns {Promise<object>} The updated user object. | ||
| */ | ||
| static async update(id, data) { | ||
| const prisma = await getPrismaClient(); // Get the initialized Prisma client | ||
| return prisma.user.update({ | ||
| where: { id }, | ||
| data, | ||
| }); | ||
| } | ||
| /** | ||
| * Deletes a user by its ID. | ||
| * @param {string} id - The ID of the user to delete. | ||
| * @returns {Promise<object>} The deleted user object. | ||
| */ | ||
| static async delete(id) { | ||
| const prisma = await getPrismaClient(); // Get the initialized Prisma client | ||
| return prisma.user.delete({ where: { id } }); | ||
| } | ||
| /** | ||
| * Finds users with pagination. | ||
| * @param {number} [skip=0] - The number of records to skip. | ||
| * @param {number} [take=10] - The number of records to take. | ||
| * @returns {Promise<Array<object>>} An array of user objects for the given pagination. | ||
| */ | ||
| static async findWithPagination(skip = 0, take = 10) { | ||
| const prisma = await getPrismaClient(); // Get the initialized Prisma client | ||
| return prisma.user.findMany({ | ||
| skip, | ||
| take, | ||
| orderBy: { createdAt: 'desc' }, // Assuming 'createdAt' field exists for ordering | ||
| }); | ||
| } | ||
| } |
| import express from 'express'; | ||
| import roleController from '../controllers/roleController.js'; | ||
| // Import authentication and authorization middleware | ||
| import { validateApiKey } from '../../../../middleware/validateApiKey.js'; | ||
| import { authenticateToken } from '@semantq/auth/lib/middleware/authMiddleware.js'; | ||
| import { authorize } from '@semantq/auth/lib/middleware/authorize.js'; | ||
| const router = express.Router(); | ||
| // ========================================================================= | ||
| // 🔵 API_KEY - Service-to-service communication - DIRECT MIDDLEWARE | ||
| // THIS MUST COME FIRST to ensure it is not blocked by /roles/:id | ||
| // For example: | ||
| // router.get('/roles/stats', validateApiKey, roleController.getRoleStats); | ||
| // ========================================================================= | ||
| // 🟢 PUBLIC - No authentication | ||
| // For example: | ||
| // router.get('/roles/public/:id', roleController.getPublicRoleById); | ||
| // router.get('/roles/latest', roleController.getLatestRoles); | ||
| // 🟡 AUTHENTICATED - Logged-in users only | ||
| router.get('/role/roles/account/:organizationId', authenticateToken, roleController.getOrganizationRoles); | ||
| router.get('/role/roles/:id', authenticateToken, roleController.getRoleById); | ||
| router.post('/role/roles', authenticateToken, roleController.createRole); | ||
| router.put('/role/roles/:id', authenticateToken, roleController.updateRole); | ||
| router.delete('/role/roles/:id', authenticateToken, roleController.deleteRole); | ||
| // 🟠 AUTHORIZED - Specific user roles | ||
| // For example, this route requires an access level of 2 | ||
| // router.patch('/roles/:id', authenticateToken, authorize(2), roleController.patchRole); | ||
| // router.delete('/roles/:id/admin', authenticateToken, authorize(3), roleController.adminDeleteRole); | ||
| // 🔴 FULLY_PROTECTED - API key + user authentication | ||
| // For example: | ||
| // router.post('/roles/bulk', validateApiKey, authenticateToken, roleController.bulkCreateRoles); | ||
| // 🚨 FULLY_AUTHORIZED - API key + user authentication + specific role | ||
| // For example: | ||
| // router.post('/roles/system', validateApiKey, authenticateToken, authorize(3), roleController.systemCreateRole); | ||
| export default router; |
| import express from 'express'; | ||
| import userController from '../controllers/userController.js'; | ||
| // Import authentication and authorization middleware | ||
| import { validateApiKey } from '../../../../middleware/validateApiKey.js'; | ||
| import { authenticateToken } from '@semantq/auth/lib/middleware/authMiddleware.js'; | ||
| import { authorize } from '@semantq/auth/lib/middleware/authorize.js'; | ||
| const router = express.Router(); | ||
| // ========================================================================= | ||
| // 🔵 API_KEY - Service-to-service communication - DIRECT MIDDLEWARE | ||
| // THIS MUST COME FIRST to ensure it is not blocked by /users/:id | ||
| // For example: | ||
| // router.get('/users/stats', validateApiKey, userController.getUserStats); | ||
| // ========================================================================= | ||
| // 🟢 PUBLIC - No authentication | ||
| // For example: | ||
| // router.get('/users/public/:id', userController.getPublicUserById); | ||
| // router.get('/users/latest', userController.getLatestUsers); | ||
| // 🟡 AUTHENTICATED - Logged-in users only | ||
| router.get('/user/users', authenticateToken, userController.getAllUsers); | ||
| router.get('/user/users/plan/:id', authenticateToken, userController.getUserPlan); | ||
| router.get('/user/users/account/:organizationId', authenticateToken, userController.getOrganizationUsers); | ||
| //user/users/plan/${user.id} | ||
| router.get('/user/users/:id', authenticateToken, userController.getUserById); | ||
| router.post('/user/users', authenticateToken, userController.createUser); | ||
| router.put('/user/users/:id', authenticateToken, userController.updateUser); | ||
| router.delete('/user/users/:id', authenticateToken, userController.deleteUser); // 🆕 DELETE route added | ||
| // 🟠 AUTHORIZED - Specific user roles | ||
| // For example, this route requires an access level of 2 | ||
| // router.patch('/users/:id', authenticateToken, authorize(2), userController.patchUser); | ||
| // router.delete('/users/:id/admin', authenticateToken, authorize(3), userController.adminDeleteUser); | ||
| // 🔴 FULLY_PROTECTED - API key + user authentication | ||
| // For example: | ||
| // router.post('/users/bulk', validateApiKey, authenticateToken, userController.bulkCreateUsers); | ||
| // 🚨 FULLY_AUTHORIZED - API key + user authentication + specific role | ||
| // For example: | ||
| // router.post('/users/system', validateApiKey, authenticateToken, authorize(3), userController.systemCreateUser); | ||
| export default router; |
| import RoleModel from '../models/mysql/Role.js'; | ||
| class RoleService { | ||
| async create(data) { | ||
| return await RoleModel.create(data); | ||
| } | ||
| async getById(id) { | ||
| return await RoleModel.findById(id); | ||
| } | ||
| async getAll() { | ||
| return await RoleModel.findAll(); | ||
| } | ||
| async getOrganizationRoles(organizationId) { | ||
| // PASS IT TO THE MODEL | ||
| return await RoleModel.getOrganizationRoles(organizationId); | ||
| } | ||
| async update(id, data) { | ||
| return await RoleModel.update(id, data); | ||
| } | ||
| async delete(id) { | ||
| return await RoleModel.delete(id); | ||
| } | ||
| } | ||
| export default new RoleService(); |
| import UserModel from '../models/mysql/User.js'; | ||
| class UserService { | ||
| async create(data) { | ||
| return await UserModel.create(data); | ||
| } | ||
| async getById(id) { | ||
| return await UserModel.findById(id); | ||
| } | ||
| async getUserPlan(id) { | ||
| return await UserModel.getPylonUserById(parseInt(id,10)); | ||
| } | ||
| async getOrganizationUsers(organizationId) { | ||
| return await UserModel.getOrganizationUsers(parseInt(organizationId,10)); | ||
| } | ||
| // getOrganizationUsers | ||
| async getAll() { | ||
| return await UserModel.findAll(); | ||
| } | ||
| async update(id, data) { | ||
| return await UserModel.update(id, data); | ||
| } | ||
| async delete(id) { | ||
| return await UserModel.delete(id); | ||
| } | ||
| } | ||
| export default new UserService(); |
@@ -48,4 +48,2 @@ import featureService from '../services/featureService.js'; | ||
| // Set the final required 'name' argument for Prisma | ||
| finalName = non_crud_name; | ||
@@ -58,2 +56,5 @@ // Set the non_crud_feature_set_name | ||
| } | ||
| // Set the final required 'name' argument for Prisma | ||
| finalName = `${finalFeatureSet}_${non_crud_name}`.toLowerCase(); | ||
| } | ||
@@ -60,0 +61,0 @@ |
+1
-1
| { | ||
| "name": "@semantq/pylon", | ||
| "version": "1.0.2", | ||
| "version": "1.0.3", | ||
| "description": "Feature Guard Package For SaaS Applications", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
+243
-1
@@ -143,2 +143,244 @@ # pylon | ||
| This gives SuperAdmin complete control over the feature economy while keeping the runtime simple for developers. | ||
| This gives SuperAdmin complete control over the feature economy while keeping the runtime simple for developers. | ||
| # Pylon Commands Reference | ||
| ## Pylon Route Management | ||
| ### Create Pylon Route | ||
| Creates a role-based route with Pylon dashboard layout structure. | ||
| **Syntax:** | ||
| ```bash | ||
| semantq make:route <routeName> <role> --pylon | ||
| ``` | ||
| **Examples:** | ||
| ```bash | ||
| # Create a plan route for project-manager role | ||
| semantq make:route plan project-manager --pylon | ||
| # Create a user-add route for admin role (short flag) | ||
| semantq make:route user-add admin -p | ||
| # Create with server handlers | ||
| semantq make:route master-plan project-manager --pylon -A | ||
| ``` | ||
| **What it creates:** | ||
| - `src/routes/<role>/<routeName>/@page.smq` - Main route page with component imports | ||
| - `src/routes/<role>/<routeName>/@layout.smq` - Dashboard layout with CSS imports | ||
| **Key Features:** | ||
| - Automatically converts route names to PascalCase (e.g., `user-add` → `UserAdd`) | ||
| - Sets up dashboard container with Sidebar, Header, and Footer imports | ||
| - Includes dashboard CSS and required external stylesheets | ||
| - Enables authentication by default in config | ||
| ### Remove Route | ||
| Removes an existing route directory and all its contents. | ||
| **Syntax:** | ||
| ```bash | ||
| semantq remove:route <routeName> | ||
| ``` | ||
| **Options:** | ||
| - `-y, --yes`: Skip confirmation prompt | ||
| **Example:** | ||
| ```bash | ||
| # Remove a route with confirmation | ||
| semantq remove:route contact | ||
| # Remove without confirmation | ||
| semantq remove:route about -y | ||
| ``` | ||
| **Notes:** | ||
| - Works for both regular routes and Pylon routes | ||
| - Shows all files that will be removed before deletion | ||
| - Requires confirmation unless `-y` flag is used | ||
| --- | ||
| ## Pylon Component Management | ||
| ### Create Pylon Component | ||
| Creates a Pylon-enabled component with feature guarding and permission-based UI. | ||
| **Syntax:** | ||
| ```bash | ||
| semantq make:component <componentName> --pylon | ||
| ``` | ||
| **Examples:** | ||
| ```bash | ||
| # Create a basic Pylon component | ||
| semantq make:component Plan --pylon | ||
| # Create nested Pylon component | ||
| semantq make:component admin/User --pylon | ||
| ``` | ||
| **What it creates:** | ||
| - `src/components/pylon/<componentName>.smq` (or nested path) | ||
| - Includes complete permission system with `can()`, `canAny()`, `canAll()` functions | ||
| - Formique configuration for CRUD operations | ||
| - AnyGrid integration with permission-controlled features | ||
| - Loading states and error handling | ||
| - Accordion-based UI for create/view operations | ||
| **Key Features:** | ||
| - Automatic permission mapping from user settings | ||
| - State management with `$state` and `$effect` | ||
| - Integrated Formique forms with validation | ||
| - AnyGrid data tables with export permissions | ||
| - Role-based access control | ||
| - Automatic data refresh on record creation | ||
| ### Remove Component | ||
| Removes a component file from the project. | ||
| **Syntax:** | ||
| ```bash | ||
| semantq remove:component <componentName> | ||
| ``` | ||
| **Options:** | ||
| - `-p, --pylon`: Remove from Pylon components directory | ||
| - `-y, --yes`: Skip confirmation prompt | ||
| **Examples:** | ||
| ```bash | ||
| # Remove regular component | ||
| semantq remove:component Button | ||
| # Remove Pylon component | ||
| semantq remove:component Plan --pylon | ||
| # Remove without confirmation | ||
| semantq remove:component User -p -y | ||
| ``` | ||
| **Notes:** | ||
| - Defaults to regular components directory | ||
| - Use `--pylon` flag for Pylon components | ||
| - Shows alternative location suggestions if not found | ||
| --- | ||
| ## Pylon Resource Management | ||
| ### Create Pylon Resource | ||
| Generates a complete backend resource with Pylon feature guarding. | ||
| **Syntax:** | ||
| ```bash | ||
| semantq make:resource <resourceName> --pylon | ||
| ``` | ||
| **Examples:** | ||
| ```bash | ||
| # Create Pylon resource for User model | ||
| semantq make:resource User --pylon | ||
| # Create regular resource (non-Pylon) | ||
| semantq make:resource Product | ||
| ``` | ||
| **What it creates:** | ||
| - **Model**: Database model with Pylon permission fields | ||
| - **Controller**: CRUD operations with permission checks | ||
| - **Service**: Business logic layer | ||
| - **Routes**: API endpoints with middleware | ||
| **Key Features:** | ||
| - Database adapter-aware (MySQL, MongoDB, SQLite, Supabase) | ||
| - Automatic Pylon permission integration in controllers | ||
| - Feature flag system for SaaS capabilities | ||
| - Role-based access middleware | ||
| - Consistent naming conventions | ||
| ### Remove Resource | ||
| Removes all backend resource files for a given resource. | ||
| **Syntax:** | ||
| ```bash | ||
| semantq remove:resource <resourceName> | ||
| ``` | ||
| **Options:** | ||
| - `-y, --yes`: Skip confirmation prompt | ||
| **Example:** | ||
| ```bash | ||
| # Remove User resource with confirmation | ||
| semantq remove:resource User | ||
| # Remove without confirmation | ||
| semantq remove:resource Product -y | ||
| ``` | ||
| **What it removes:** | ||
| - Model files across all database adapters | ||
| - Controller file | ||
| - Service file | ||
| - Route file | ||
| **Notes:** | ||
| - Only removes files that exist | ||
| - Shows list of files before deletion | ||
| - Requires server directory (`semantqQL`) to exist | ||
| --- | ||
| ## Common Workflow Example | ||
| ### Complete Pylon Feature Creation | ||
| ```bash | ||
| # 1. Create the backend resource | ||
| semantq make:resource Invoice --pylon | ||
| # 2. Create the Pylon component | ||
| semantq make:component Invoice --pylon | ||
| # 3. Create the route for admin role | ||
| semantq make:route invoice admin --pylon | ||
| ``` | ||
| This creates: | ||
| - Backend: `Invoice` model, controller, service, routes | ||
| - Frontend: `Invoice.smq` Pylon component with permission UI | ||
| - Route: `/admin/invoice` dashboard route | ||
| ### Cleanup Example | ||
| ```bash | ||
| # Remove everything | ||
| semantq remove:resource Invoice -y | ||
| semantq remove:component Invoice --pylon -y | ||
| semantq remove:route invoice -y | ||
| ``` | ||
| ## Notes & Best Practices | ||
| 1. **Naming Conventions:** | ||
| - Routes: lowercase, hyphenated (e.g., `user-add`) | ||
| - Components: PascalCase (e.g., `UserAdd`) | ||
| - Resources: Singular, PascalCase (e.g., `User`) | ||
| 2. **Directory Structure:** | ||
| - Pylon components: `src/components/pylon/` | ||
| - Pylon routes: `src/routes/<role>/` | ||
| - Resources: `semantqQL/` (models, controllers, services, routes) | ||
| 3. **Permission System:** | ||
| - Uses `user.userSettings` Set for permission checks | ||
| - Supports CRUD operations (`create`, `read`, `update`, `delete`) | ||
| - Includes DataGrid features (`datagrid_csvexport`, `datagrid_excelexport`) | ||
| 4. **File Generation:** | ||
| - All commands check for existing files first | ||
| - Provide helpful error messages for conflicts | ||
| - Include clear "next steps" after creation |
| import OrganizationModel from '../models/mysql/Organization.js'; | ||
| import PricingPackageModel from '../models/mysql/PricingPackage.js'; | ||
| // | ||
| import UserModel from '../../../../models/mysql/User.js'; | ||
| import UserModel from '../models/mysql/User.js'; | ||
@@ -6,0 +5,0 @@ |
| import PylonModel from '../models/mysql/Pylon.js'; | ||
| import UserModel from '../../../../models/mysql/User.js'; | ||
| import UserModel from '../models/mysql/User.js'; | ||
| import MeteringModel from '../models/mysql/Metering.js'; | ||
@@ -24,3 +24,3 @@ | ||
| const routeFeature = `${model.toLowerCase()}_${action}`; // e.g., Form + create → form_create | ||
| //console.log("routeFeature",routeFeature); | ||
| // Retrieve user data | ||
@@ -56,3 +56,3 @@ const userData = await UserModel.getPylonUserById(req.userId); | ||
| isAllowed = true; | ||
| // console.log("routeFeature",isAllowed); | ||
| //console.log("routeFeature",isAllowed); | ||
| } else { | ||
@@ -66,3 +66,3 @@ notAlloweMessage = 'You have no access to this feature'; | ||
| if(!hasCredit.status) { | ||
| noCreditMessage = `${features[routeFeature]?.timeframe} Quota exceeded. You need to upgrade your plan.`; | ||
| noCreditMessage = `${features[routeFeature]?.timeframe.charAt(0).toUpperCase()}${features[routeFeature]?.timeframe.slice(1).toLowerCase()} Quota exceeded. You need to upgrade your plan.`; | ||
| } | ||
@@ -120,2 +120,15 @@ | ||
| // If limitValue is -1, the feature is unlimited, so return true immediately. | ||
| if (count && limitValue === -1) { | ||
| return { | ||
| status: true, | ||
| message: 'Unlimited access' // Optional message for clarity | ||
| }; | ||
| } | ||
| // if this is a count based feature | ||
@@ -129,2 +142,7 @@ if (count) { | ||
| //console.log("limitValue",limitValue); | ||
| //console.log("usageCount",limitValue); | ||
| //console.log("credit",credit); | ||
| if (credit <= 0) { | ||
@@ -150,4 +168,2 @@ return { | ||
| async logUsage(data) { | ||
@@ -154,0 +170,0 @@ const usageData = { |
114110
27.56%46
21.05%2601
23.27%385
169.23%