🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@semantq/pylon

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@semantq/pylon - npm Package Compare versions

Comparing version
1.0.2
to
1.0.3
+92
controllers/roleController.js
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();
+3
-2

@@ -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 = {