🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
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.1
to
1.0.2
+70
controllers/pricingPackageController.js
import pricingPackageService from '../services/pricingPackageService.js';
class PricingPackageController {
async createPricingPackage(req, res) {
try {
const result = await pricingPackageService.create(req.body);
res.status(201).json(result);
} catch (err) {
res.status(400).json({ error: err.message });
}
}
async getPricingPackageById(req, res) {
try {
const result = await pricingPackageService.getById(req.params.id);
res.json(result);
} catch (err) {
res.status(404).json({ error: err.message });
}
}
async getAllPricingPackages(req, res) {
try {
const result = await pricingPackageService.getAll();
res.json(result);
} catch (err) {
res.status(500).json({ error: err.message });
}
}
async updatePricingPackage(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 PricingPackage ID provided. Must be a number.' });
}
const result = await pricingPackageService.update(resourceId, req.body);
res.json(result);
} catch (err) {
res.status(400).json({ error: err.message });
}
}
async deletePricingPackage(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 PricingPackage ID provided. Must be a number.' });
}
await pricingPackageService.delete(resourceId);
res.status(204).send();
} catch (err) {
if (err.message.includes("Record to delete does not exist")) {
res.status(404).json({ error: "PricingPackage not found." });
} else {
res.status(400).json({ error: err.message });
}
}
}
}
export default new PricingPackageController();
// Add to your schema.prisma:
/*
model Feature {
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 FeatureModel {
/**
* Creates a new feature in the database.
* @param {object} data - The data for the new feature.
* @returns {Promise<object>} The created feature object.
*/
static async create(data) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.feature.create({ data });
}
/**
* Finds a feature by its unique ID.
* @param {string} id - The ID of the feature to find.
* @returns {Promise<object|null>} The found feature object, or null if not found.
*/
static async findById(id) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.feature.findUnique({ where: { id } });
}
/// getNonCrudFeatureNames
static async getNonCrudFeatureNames() {
const prisma = await getPrismaClient();
const result = await prisma.feature.findMany({
where: { non_crud_feature_set_name: { not: null } }, // exclude nulls
select: { id: true, non_crud_feature_set_name: true },
});
console.log("✅ Prisma returned:", result);
return result;
}
/**
* Retrieves all features from the database.
* @returns {Promise<Array<object>>} An array of all feature objects.
*/
static async findAll() {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.feature.findMany();
}
/**
* Updates an existing feature by its ID.
* @param {string} id - The ID of the feature to update.
* @param {object} data - The data to update the feature with.
* @returns {Promise<object>} The updated feature object.
*/
static async update(id, data) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.feature.update({
where: { id },
data,
});
}
/**
* Deletes a feature by its ID.
* @param {string} id - The ID of the feature to delete.
* @returns {Promise<object>} The deleted feature object.
*/
static async delete(id) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.feature.delete({ where: { id } });
}
/**
* Finds features 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 feature objects for the given pagination.
*/
static async findWithPagination(skip = 0, take = 10) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.feature.findMany({
skip,
take,
orderBy: { createdAt: 'desc' }, // Assuming 'createdAt' field exists for ordering
});
}
}
// Add to your schema.prisma:
/*
model Metering {
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 MeteringModel {
static async create(data) {
console.log("3. inside Metering logUsage");
const prisma = await getPrismaClient();
return prisma.metering.create({
data
});
}
static async findById(id) {
const prisma = await getPrismaClient();
return prisma.metering.findUnique({
where: { id },
include: {
organization: true,
feature: true
}
});
}
static async countMonthlyUsage(organizationId, featureName) {
const prisma = await getPrismaClient();
// Ensure organizationId is a string
const orgId = parseInt(organizationId);
// Get the start and end of the current month (UTC-safe)
const now = new Date();
const startOfMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
const endOfMonth = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 1));
// Count how many records fall within this month
const count = await prisma.metering.count({
where: {
organizationId: orgId,
featureName,
createdAt: {
gte: startOfMonth,
lt: endOfMonth,
},
},
});
return count;
}
static async countYearlyUsage(organizationId, featureName) {
const prisma = await getPrismaClient();
// Ensure organizationId is a string
const orgId = parseInt(organizationId);
// Get the start and end of the current year (UTC-safe)
const now = new Date();
const startOfYear = new Date(Date.UTC(now.getUTCFullYear(), 0, 1)); // Jan 1
const endOfYear = new Date(Date.UTC(now.getUTCFullYear() + 1, 0, 1)); // Jan 1 next year
// Count how many records fall within this year
const count = await prisma.metering.count({
where: {
organizationId: orgId,
featureName,
createdAt: {
gte: startOfYear,
lt: endOfYear,
},
},
});
return count;
}
static async findByOrganizationAndFeature(organizationId, featureId) {
const prisma = await getPrismaClient();
return prisma.metering.findUnique({
where: {
organizationId_featureId: {
organizationId,
featureId
}
},
include: {
organization: true,
feature: true
}
});
}
static async findAll() {
const prisma = await getPrismaClient();
return prisma.metering.findMany({
include: {
organization: true,
feature: true
}
});
}
static async update(id, data) {
const prisma = await getPrismaClient();
return prisma.metering.update({
where: { id },
data,
include: {
organization: true,
feature: true
}
});
}
static async delete(id) {
const prisma = await getPrismaClient();
return prisma.metering.delete({ where: { id } });
}
// Pylon-specific methods
static async getOrCreate(organizationId, featureId) {
const prisma = await getPrismaClient();
const existing = await prisma.metering.findUnique({
where: {
organizationId_featureId: { organizationId, featureId }
}
});
if (existing) {
// Check if period needs reset
if (existing.periodEnd && existing.periodEnd < new Date()) {
const feature = await prisma.feature.findUnique({
where: { id: featureId }
});
const { periodStart, periodEnd } = this.calculatePeriod(feature?.timeframe);
return prisma.metering.update({
where: { id: existing.id },
data: {
currentValue: 0,
periodStart,
periodEnd
}
});
}
return existing;
}
// Create new metering record
const feature = await prisma.feature.findUnique({
where: { id: featureId }
});
const { periodStart, periodEnd } = this.calculatePeriod(feature?.timeframe);
return prisma.metering.create({
data: {
organizationId,
featureId,
currentValue: 0,
periodStart,
periodEnd
}
});
}
static async incrementUsage(organizationId, featureId, amount = 1) {
const prisma = await getPrismaClient();
const metering = await this.getOrCreate(organizationId, featureId);
return prisma.metering.update({
where: { id: metering.id },
data: { currentValue: { increment: amount } }
});
}
static calculatePeriod(timeframe) {
const now = new Date();
let periodEnd;
switch (timeframe) {
case 'MONTHLY':
periodEnd = new Date(now.getFullYear(), now.getMonth() + 1, 1);
break;
case 'YEARLY':
periodEnd = new Date(now.getFullYear() + 1, 0, 1);
break;
default:
periodEnd = new Date(2100, 0, 1); // Forever
}
return { periodStart: now, periodEnd };
}
}
// Add to your schema.prisma:
/*
model Model {
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 ModelModel {
/**
* Creates a new model in the database.
* @param {object} data - The data for the new model.
* @returns {Promise<object>} The created model object.
*/
static async create(data) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.model.create({ data });
}
/**
* Finds a model by its unique ID.
* @param {string} id - The ID of the model to find.
* @returns {Promise<object|null>} The found model object, or null if not found.
*/
static async findById(id) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.model.findUnique({ where: { id } });
}
/**
* Retrieves all models from the database.
* @returns {Promise<Array<object>>} An array of all model objects.
*/
static async findAll() {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.model.findMany();
}
/**
* Updates an existing model by its ID.
* @param {string} id - The ID of the model to update.
* @param {object} data - The data to update the model with.
* @returns {Promise<object>} The updated model object.
*/
static async update(id, data) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.model.update({
where: { id },
data,
});
}
/**
* Deletes a model by its ID.
* @param {string} id - The ID of the model to delete.
* @returns {Promise<object>} The deleted model object.
*/
static async delete(id) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.model.delete({ where: { id } });
}
/**
* Finds models 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 model objects for the given pagination.
*/
static async findWithPagination(skip = 0, take = 10) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.model.findMany({
skip,
take,
orderBy: { createdAt: 'desc' }, // Assuming 'createdAt' field exists for ordering
});
}
}
// Add to your schema.prisma:
/*
model Organization {
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 OrganizationModel {
static async create(data) {
const prisma = await getPrismaClient();
return prisma.organization.create({ data });
}
static async findById(id) {
const prisma = await getPrismaClient();
return prisma.organization.findUnique({
where: { id },
include: {
pricingPackage: {
include: {
features: {
include: {
feature: true
}
}
}
}
}
});
}
static async findAll() {
const prisma = await getPrismaClient();
return prisma.organization.findMany({
include: {
pricingPackage: true
}
});
}
/**
* Simulates a payment by setting the paid period for a full year.
* @param {string | number} id - The ID of the organization to update.
* @returns {Promise<object>} The updated organization record.
*/
static async simulatePayment (id) {
// Get the current time for paidPeriodStart
const paidPeriodStart = new Date();
// Calculate the paidPeriodEnd (one year from now)
const paidPeriodEnd = new Date();
paidPeriodEnd.setFullYear(paidPeriodEnd.getFullYear() + 1);
const prisma = await getPrismaClient();
// Update the organization record
return prisma.organization.update({
where: { id },
data: {
paidPeriodStart: paidPeriodStart,
paidPeriodEnd: paidPeriodEnd,
},
});
}
static async update(id, data) {
const prisma = await getPrismaClient();
return prisma.organization.update({
where: { id },
data,
});
}
static async delete(id) {
const prisma = await getPrismaClient();
return prisma.organization.delete({ where: { id } });
}
}
// Add to your schema.prisma:
/*
model PricingPackage {
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 PricingPackageModel {
/**
* Creates a new pricingPackage in the database.
* @param {object} data - The data for the new pricingPackage.
* @returns {Promise<object>} The created pricingPackage object.
*/
static async create(data) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pricingPackage.create({ data });
}
/**
* Finds a pricingPackage by its unique ID.
* @param {string} id - The ID of the pricingPackage to find.
* @returns {Promise<object|null>} The found pricingPackage object, or null if not found.
*/
static async findById(id) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pricingPackage.findUnique({ where: { id } });
}
/**
* Retrieves all pricingPackages from the database.
* @returns {Promise<Array<object>>} An array of all pricingPackage objects.
*/
/*
static async findAll() {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pricingPackage.findMany({include: {features: true } });
} */
static async findAll() {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pricingPackage.findMany({
// Include all PricingPackage fields by default (no 'select' needed here)
include: {
features: {
include: {
feature: true, // true includes ALL fields of the Feature model
},
},
},
});
}
/**
* Updates an existing pricingPackage by its ID.
* @param {string} id - The ID of the pricingPackage to update.
* @param {object} data - The data to update the pricingPackage with.
* @returns {Promise<object>} The updated pricingPackage object.
*/
static async update(id, data) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pricingPackage.update({
where: { id },
data,
});
}
/**
* Deletes a pricingPackage by its ID.
* @param {string} id - The ID of the pricingPackage to delete.
* @returns {Promise<object>} The deleted pricingPackage object.
*/
static async delete(id) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pricingPackage.delete({ where: { id } });
}
/**
* Finds pricingPackages 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 pricingPackage objects for the given pagination.
*/
static async findWithPagination(skip = 0, take = 10) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pricingPackage.findMany({
skip,
take,
orderBy: { createdAt: 'desc' }, // Assuming 'createdAt' field exists for ordering
});
}
}
// Add to your schema.prisma:
/*
model PricingPackageFeature {
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 PricingPackageFeatureModel {
static async create(data) {
const prisma = await getPrismaClient();
return prisma.pricingPackageFeature.create({
data,
include: {
pricingPackage: true,
feature: true
}
});
}
static async findById(id) {
const prisma = await getPrismaClient();
return prisma.pricingPackageFeature.findUnique({
where: { id },
include: {
pricingPackage: true,
feature: true
}
});
}
static async findByPackageAndFeature(pricingPackageId, featureId) {
const prisma = await getPrismaClient();
return prisma.pricingPackageFeature.findUnique({
where: {
pricingPackageId_featureId: {
pricingPackageId,
featureId
}
},
include: {
pricingPackage: true,
feature: true
}
});
}
static async findAll() {
const prisma = await getPrismaClient();
return prisma.pricingPackageFeature.findMany({
include: {
pricingPackage: true,
feature: true
}
});
}
static async update(id, data) {
const prisma = await getPrismaClient();
return prisma.pricingPackageFeature.update({
where: { id },
data,
include: {
pricingPackage: true,
feature: true
}
});
}
static async delete(id) {
const prisma = await getPrismaClient();
return prisma.pricingPackageFeature.delete({
where: { id }
});
}
}
// Add to your schema.prisma:
/*
model Pylon {
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 PylonModel {
/**
* Creates a new pylon in the database.
* @param {object} data - The data for the new pylon.
* @returns {Promise<object>} The created pylon object.
*/
static async create(data) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pylon.create({ data });
}
/**
* Finds a pylon by its unique ID.
* @param {string} id - The ID of the pylon to find.
* @returns {Promise<object|null>} The found pylon object, or null if not found.
*/
static async findById(id) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pylon.findUnique({ where: { id } });
}
/**
* Retrieves all pylons from the database.
* @returns {Promise<Array<object>>} An array of all pylon objects.
*/
static async findAll() {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pylon.findMany();
}
/**
* Updates an existing pylon by its ID.
* @param {string} id - The ID of the pylon to update.
* @param {object} data - The data to update the pylon with.
* @returns {Promise<object>} The updated pylon object.
*/
static async update(id, data) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pylon.update({
where: { id },
data,
});
}
/**
* Deletes a pylon by its ID.
* @param {string} id - The ID of the pylon to delete.
* @returns {Promise<object>} The deleted pylon object.
*/
static async delete(id) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pylon.delete({ where: { id } });
}
/**
* Finds pylons 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 pylon objects for the given pagination.
*/
static async findWithPagination(skip = 0, take = 10) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pylon.findMany({
skip,
take,
orderBy: { createdAt: 'desc' }, // Assuming 'createdAt' field exists for ordering
});
}
}
import express from 'express';
import pricingPackageController from '../controllers/pricingPackageController.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 /pricingPackages/:id
// For example:
// router.get('/pricingPackages/stats', validateApiKey, pricingPackageController.getPricingPackageStats);
// =========================================================================
// 🟢 PUBLIC - No authentication
// For example:
// router.get('/pricingPackages/public/:id', pricingPackageController.getPublicPricingPackageById);
// router.get('/pricingPackages/latest', pricingPackageController.getLatestPricingPackages);
// 🟡 AUTHENTICATED - Logged-in users only
router.get('/pricingPackage/pricingPackages', pricingPackageController.getAllPricingPackages);
router.get('/pricingPackage/pricingPackages/:id', authenticateToken, pricingPackageController.getPricingPackageById);
router.post('/pricingPackage/pricingPackages', authenticateToken, pricingPackageController.createPricingPackage);
router.put('/pricingPackage/pricingPackages/:id', authenticateToken, pricingPackageController.updatePricingPackage);
router.delete('/pricingPackage/pricingPackages/:id', authenticateToken, pricingPackageController.deletePricingPackage); // 🆕 DELETE route added
// 🟠 AUTHORIZED - Specific user roles
// For example, this route requires an access level of 2
// router.patch('/pricingPackages/:id', authenticateToken, authorize(2), pricingPackageController.patchPricingPackage);
// router.delete('/pricingPackages/:id/admin', authenticateToken, authorize(3), pricingPackageController.adminDeletePricingPackage);
// 🔴 FULLY_PROTECTED - API key + user authentication
// For example:
// router.post('/pricingPackages/bulk', validateApiKey, authenticateToken, pricingPackageController.bulkCreatePricingPackages);
// 🚨 FULLY_AUTHORIZED - API key + user authentication + specific role
// For example:
// router.post('/pricingPackages/system', validateApiKey, authenticateToken, authorize(3), pricingPackageController.systemCreatePricingPackage);
export default router;
import PricingPackageModel from '../models/mysql/PricingPackage.js';
class PricingPackageService {
async create(data) {
const {
name,
priceMonthly,
priceYearly,
isStandard,
pricingTableLabel,
yearlyPriceDiscountPercentage
} = data;
const finalData = {
name: name,
priceMonthly: priceMonthly ? parseInt(priceMonthly) : null,
priceYearly: priceYearly ? parseInt(priceYearly) : null,
isStandard: isStandard === '1' || isStandard === 1 || isStandard === true,
pricingTableLabel: pricingTableLabel || null,
yearlyPriceDiscountPercentage: yearlyPriceDiscountPercentage
? parseFloat(yearlyPriceDiscountPercentage)
: null
};
return await PricingPackageModel.create(finalData);
}
async getById(id) {
return await PricingPackageModel.findById(id);
}
async getAll() {
return await PricingPackageModel.findAll();
}
async update(id, data) {
return await PricingPackageModel.update(id, data);
}
async delete(id) {
return await PricingPackageModel.delete(id);
}
}
export default new PricingPackageService();
import PylonModel from '../models/mysql/Pylon.js';
import UserModel from '../../../../models/mysql/User.js';
import MeteringModel from '../models/mysql/Metering.js';
class PylonService {
constructor() {
// You can initialize properties here if needed
}
// FIXED: Proper middleware factory function
featureGuard(model, action) {
return async (req, res, next) => {
try {
//console.log("THERE?",req.userId);
let isAllowed = false;
//let hasCredit = false;
let notAlloweMessage;
let noCreditMessage;
const routeFeature = `${model.toLowerCase()}_${action}`; // e.g., Form + create → form_create
// Retrieve user data
const userData = await UserModel.getPylonUserById(req.userId);
//console.log('- userData:', JSON.stringify(userData,null,2));
const features = Object.fromEntries(
userData.organization.pricingPackage.features.map(f => [
f.feature.name,
{
limitValue: f.limitValue,
status: f.status,
count: f.feature.count,
timeframe: f.feature.timeframe
}
])
);
//console.log("Feature Map:", features);
if (!userData) {
console.log('User not found:', req.userId);
return res.status(404).json({ message: 'User not found' });
}
// Extract user settings
const userSettings = userData.userSettings;
// console.log('- User Settings:', userSettings);
//console.log('- routeFeature:', routeFeature);
if(userSettings.includes(routeFeature)) {
isAllowed = true;
// console.log("routeFeature",isAllowed);
} else {
notAlloweMessage = 'You have no access to this feature';
}
// Check feature access or quota
const hasCredit = await this.checkCredit(userData, routeFeature, features);
//console.log("hasCredit",hasCredit);
if(!hasCredit.status) {
noCreditMessage = `${features[routeFeature]?.timeframe} Quota exceeded. You need to upgrade your plan.`;
}
if(isAllowed && hasCredit.status) {
//console.log('Feature access granted');
// prepare data need to metering
// metering happens at controller level after succesful record creation
req.userData = {
id: userData.id,
organizationId: userData.organization?.id,
featureName: routeFeature,
action: action,
};
req.pylon = true; // so that in your controller
next();
} else {
console.log(`${features[routeFeature]?.timeframe} Quota exceeded for user:`, req.userId);
return res.status(403).json({
message: `${notAlloweMessage || ''} ${noCreditMessage}`.trim(),
});
}
} catch (error) {
console.error('Error in featureGuard:', error);
return res.status(500).json({
message: 'Internal server error in feature guard'
});
}
};
}
// Helper method for feature access logic
async checkCredit(userData, routeFeature, features) {
const timeframe = features[routeFeature]?.timeframe;
const limitValue = features[routeFeature]?.limitValue;
const status = features[routeFeature]?.status;
const count = features[routeFeature]?.count;
/// STATUS IS false we return false regardless of count metering or not
if (!status) {
return {
status: false,
message: 'This feature is disabled'
};
}
// if this is a count based feature
if (count) {
const usageCount = timeframe === "MONTHLY"
? await MeteringModel.countMonthlyUsage(userData.organizationId, routeFeature)
: await MeteringModel.countYearlyUsage(userData.organizationId, routeFeature);
const credit = limitValue - usageCount;
if (credit <= 0) {
return {
status: false,
message: `Quota exhausted for this ${timeframe.toLowerCase()}`
};
} else {
// return true if credit is still available
return {
status: true,
remaining: credit
};
}
}
// For features that are not count-based
return {
status: true
};
}
async logUsage(data) {
const usageData = {
organizationId: parseInt(data.organizationId,10),
featureName: data.featureName,
action: data.action,
metadata: { userId: data.id }
};
return await MeteringModel.create(usageData);
}
async create(data) {
return await PylonModel.create(data);
}
async getById(id) {
return await PylonModel.findById(id);
}
async getAll() {
return await PylonModel.findAll();
}
async update(id, data) {
return await PylonModel.update(id, data);
}
async delete(id) {
return await PylonModel.delete(id);
}
}
export default new PylonService();
+121
-10
import featureService from '../services/featureService.js';
class FeatureController {
// featureController.js
async createFeature(req, res) {
try {
const result = await featureService.create(req.body);
const payloadData = req.body?.data ?? req.body ?? {};
const {
name,
unit,
model_action,
description,
meterType,
timeframe,
feature_type,
feature_set_name_options,
feature_set_name,
non_crud_name,
} = payloadData;
// 1. Validation Check for required feature_type
if (!feature_type || (feature_type !== 'Crud' && feature_type !== 'non_crud')) {
throw new Error("Feature type is required and must be 'Crud' or 'non_crud'.");
}
let finalName;
let finalFeatureSet = null; // Default to null for non_crud_feature_set_name
const isCrud = feature_type === 'Crud';
// 2. Conditional Logic for Naming and Feature Set
if (isCrud) {
// --- CRUD Feature Logic (Guaranteed to be set) ---
if (!name || !model_action) {
throw new Error("Missing Model Name or Action for a CRUD feature.");
}
// Set the final required 'name' argument for Prisma
finalName = `${name}_${model_action}`;
// finalFeatureSet remains null, which is correct for CRUD.
} else {
// --- NON-CRUD Feature Logic ---
if (!non_crud_name) {
throw new Error("Missing Non-CRUD Feature Name.");
}
// Set the final required 'name' argument for Prisma
finalName = non_crud_name;
// Set the non_crud_feature_set_name
if (feature_set_name_options === 'add_new') {
finalFeatureSet = feature_set_name;
} else {
finalFeatureSet = feature_set_name_options;
}
}
// 3. Transform Metering Fields to Booleans
const supportsCount = meterType?.includes("COUNT") ?? null;
const supportsOnOff = meterType?.includes("ON_OFF") ?? null;
// 4. BUILD FINAL DATA: finalName MUST be present here.
const finalData = {
// --- CRITICAL FIX: finalName must be defined and passed ---
name: finalName, // This is now guaranteed to be set
// Standard Fields
unit: unit,
description: description,
timeframe: timeframe,
// Metering Fields
count: supportsCount,
on_off: supportsOnOff,
// Non-CRUD Specific Fields (Set for non-CRUD, null for CRUD)
non_crud: !isCrud ? 1 : null,
non_crud_feature_set_name: finalFeatureSet, // Null for CRUD, set for Non-CRUD
};
// 5. Call the service
const result = await featureService.create(finalData);
res.status(201).json(result);
} catch (err) {
res.status(400).json({ error: err.message });
console.error("Error creating feature:", err);
// Check if the error is the Prisma validation error
if (err.name === 'PrismaClientValidationError') {
res.status(400).json({
error: "Database validation failed. Required field is missing.",
detail: err.message
});
} else {
res.status(400).json({
error: "Failed to process feature creation request.",
detail: err.message
});
}
}
}
} // ← THIS CLOSING BRACE WAS MISSING
async getFeatureById(req, res) {
async getNonCrudFeatureNames(req, res) {
try {
const result = await featureService.getById(req.params.id);
const result = await featureService.getNonCrudFeatureNames();
res.json(result);

@@ -22,5 +113,5 @@ } catch (err) {

async getFeatureByName(req, res) {
async getFeatureById(req, res) {
try {
const result = await featureService.getByName(req.params.name);
const result = await featureService.getById(req.params.id);
res.json(result);

@@ -43,3 +134,11 @@ } catch (err) {

try {
const result = await featureService.update(req.params.id, req.body);
// 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 Feature ID provided. Must be a number.' });
}
const result = await featureService.update(resourceId, req.body);
res.json(result);

@@ -53,6 +152,18 @@ } catch (err) {

try {
await featureService.delete(req.params.id);
// 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 Feature ID provided. Must be a number.' });
}
await featureService.delete(resourceId);
res.status(204).send();
} catch (err) {
res.status(400).json({ error: err.message });
if (err.message.includes("Record to delete does not exist")) {
res.status(404).json({ error: "Feature not found." });
} else {
res.status(400).json({ error: err.message });
}
}

@@ -59,0 +170,0 @@ }

@@ -13,2 +13,5 @@ import meteringService from '../services/meteringService.js';

async getMeteringById(req, res) {

@@ -15,0 +18,0 @@ try {

@@ -6,2 +6,3 @@ import organizationService from '../services/organizationService.js';

try {
console.log(req.body);
const result = await organizationService.create(req.body);

@@ -8,0 +9,0 @@ res.status(201).json(result);

@@ -6,3 +6,18 @@ import pricingPackageFeatureService from '../services/pricingPackageFeatureService.js';

try {
const result = await pricingPackageFeatureService.create(req.body);
const { seats,pricingPackageId, featureId, limitValue, status } = req.body;
const finalData = {
pricingPackageId:pricingPackageId,
featureId: featureId,
limitValue: parseInt(limitValue,10),
status: parseInt(status) > 0 ? true : false
}
if (seats) {
finalData.seats = parseInt(seats,10);
}
const result = await pricingPackageFeatureService.create(finalData);
res.status(201).json(result);

@@ -9,0 +24,0 @@ } catch (err) {

+1
-1

@@ -14,3 +14,3 @@ // Add to your schema.prisma:

// Import the function that returns the Prisma client promise
import getPrismaClient from '../../lib/prisma.js';
import getPrismaClient from '../../../../../lib/prisma.js';

@@ -17,0 +17,0 @@ export default class FeatureModel {

@@ -14,3 +14,3 @@ // Add to your schema.prisma:

// Import the function that returns the Prisma client promise
import getPrismaClient from '../../lib/prisma.js';
import getPrismaClient from '../../../../../lib/prisma.js';

@@ -17,0 +17,0 @@ export default class MeteringModel {

@@ -14,3 +14,3 @@ // Add to your schema.prisma:

// Import the function that returns the Prisma client promise
import getPrismaClient from '../../lib/prisma.js';
import getPrismaClient from '../../../../../lib/prisma.js';

@@ -17,0 +17,0 @@ export default class ModelModel {

@@ -14,3 +14,3 @@ // Add to your schema.prisma:

// Import the function that returns the Prisma client promise
import getPrismaClient from '../../lib/prisma.js';
import getPrismaClient from '../../../../../lib/prisma.js';

@@ -17,0 +17,0 @@

@@ -14,3 +14,3 @@ // Add to your schema.prisma:

// Import the function that returns the Prisma client promise
import getPrismaClient from '../../lib/prisma.js';
import getPrismaClient from '../../../../../lib/prisma.js';

@@ -17,0 +17,0 @@ export default class PricingPackageFeatureModel {

{
"name": "@semantq/pylon",
"version": "1.0.1",
"version": "1.0.2",
"description": "Feature Guard Package For SaaS Applications",

@@ -5,0 +5,0 @@ "keywords": [

import express from 'express';
import pricingPackageFeatureController from '../controllers/pricingPackageFeatureController.js';
import featureController from '../controllers/featureController.js';
// Import authentication and authorization middleware
import { validateApiKey } from '../../../../middleware/validateApiKey.js';
import { authenticateToken } from '@semantq/auth/lib/middleware/authMiddleware.js';

@@ -8,9 +10,43 @@ import { authorize } from '@semantq/auth/lib/middleware/authorize.js';

// 🟢 ADMIN ONLY - Pricing package feature management
router.post('/pricing-package-features', authenticateToken, authorize(3), pricingPackageFeatureController.createPricingPackageFeature);
router.get('/pricing-package-features', authenticateToken, pricingPackageFeatureController.getAllPricingPackageFeatures);
router.get('/pricing-package-features/:id', authenticateToken, pricingPackageFeatureController.getPricingPackageFeatureById);
router.put('/pricing-package-features/:id', authenticateToken, authorize(3), pricingPackageFeatureController.updatePricingPackageFeature);
router.delete('/pricing-package-features/:id', authenticateToken, authorize(3), pricingPackageFeatureController.deletePricingPackageFeature);
const model = 'Feature';
// =========================================================================
// 🔵 API_KEY - Service-to-service communication - DIRECT MIDDLEWARE
// THIS MUST COME FIRST to ensure it is not blocked by /features/:id
// For example:
// router.get('/features/stats', validateApiKey, featureController.getFeatureStats);
// =========================================================================
// 🟢 PUBLIC - No authentication
// For example:
// router.get('/features/public/:id', featureController.getPublicFeatureById);
// router.get('/features/latest', featureController.getLatestFeatures);
// 🟡 AUTHENTICATED - Logged-in users only
// /@semantq/pylon/feature/features/noncrud
router.get('/feature/features', authenticateToken, featureController.getAllFeatures);
router.get('/feature/features/:id', authenticateToken, featureController.getFeatureById);
// get non crud feature set names
router.get('/feature/features/noncrud/names', authenticateToken, featureController.getNonCrudFeatureNames);
router.post('/feature/features', authenticateToken, featureController.createFeature);
router.put('/feature/features/:id', authenticateToken, featureController.updateFeature);
router.delete('/feature/features/:id', authenticateToken, featureController.deleteFeature); // 🆕 DELETE route added
// 🟠 AUTHORIZED - Specific user roles
// For example, this route requires an access level of 2
// router.patch('/features/:id', authenticateToken, authorize(2), featureController.patchFeature);
// router.delete('/features/:id/admin', authenticateToken, authorize(3), featureController.adminDeleteFeature);
// 🔴 FULLY_PROTECTED - API key + user authentication
// For example:
// router.post('/features/bulk', validateApiKey, authenticateToken, featureController.bulkCreateFeatures);
// 🚨 FULLY_AUTHORIZED - API key + user authentication + specific role
// For example:
// router.post('/features/system', validateApiKey, authenticateToken, authorize(3), featureController.systemCreateFeature);
export default router;
import express from 'express';
import meteringController from '../controllers/meteringController.js';
// Import authentication and authorization middleware
import { validateApiKey } from '../middleware/validateApiKey.js';
import { validateApiKey } from '../../../../middleware/validateApiKey.js';
import { authenticateToken } from '@semantq/auth/lib/middleware/authMiddleware.js';

@@ -24,2 +24,6 @@ import { authorize } from '@semantq/auth/lib/middleware/authorize.js';

router.get('/meterings', authenticateToken, meteringController.getAllMeterings);
//
//router.get('/meterings/monthly/usage/:organizationId/:featureName', authenticateToken, meteringController.getMonthlyUsage);
router.get('/meterings/:id', authenticateToken, meteringController.getMeteringById);

@@ -26,0 +30,0 @@ router.post('/meterings', authenticateToken, meteringController.createMetering);

import express from 'express';
import modelController from '../controllers/modelController.js';
// Import authentication and authorization middleware
import { validateApiKey } from '../middleware/validateApiKey.js';
import { validateApiKey } from '../../../../middleware/validateApiKey.js';
import { authenticateToken } from '@semantq/auth/lib/middleware/authMiddleware.js';

@@ -23,7 +23,7 @@ import { authorize } from '@semantq/auth/lib/middleware/authorize.js';

// 🟡 AUTHENTICATED - Logged-in users only
router.get('/models', modelController.getAllModels);
router.get('/models/:id', authenticateToken, modelController.getModelById);
router.post('/models', authenticateToken, modelController.createModel);
router.put('/models/:id', authenticateToken, modelController.updateModel);
router.delete('/models/:id', authenticateToken, modelController.deleteModel); // 🆕 DELETE route added
router.get('/model/models', modelController.getAllModels);
router.get('/model/models/:id', authenticateToken, modelController.getModelById);
router.post('/model/models', authenticateToken, modelController.createModel);
router.put('/model/models/:id', authenticateToken, modelController.updateModel);
router.delete('/model/models/:id', authenticateToken, modelController.deleteModel); // 🆕 DELETE route added

@@ -30,0 +30,0 @@ // 🟠 AUTHORIZED - Specific user roles

import express from 'express';
import organizationController from '../controllers/organizationController.js';
// Import authentication and authorization middleware
import { validateApiKey } from '../middleware/validateApiKey.js';
import { validateApiKey } from '../../../../middleware/validateApiKey.js';
import { authenticateToken } from '@semantq/auth/lib/middleware/authMiddleware.js';

@@ -23,7 +23,7 @@ import { authorize } from '@semantq/auth/lib/middleware/authorize.js';

// 🟡 AUTHENTICATED - Logged-in users only
router.get('/organizations', authenticateToken, organizationController.getAllOrganizations);
router.get('/organizations/:id', authenticateToken, organizationController.getOrganizationById);
router.post('/organizations', authenticateToken, organizationController.createOrganization);
router.put('/organizations/:id', authenticateToken, organizationController.updateOrganization);
router.delete('/organizations/:id', authenticateToken, organizationController.deleteOrganization); // 🆕 DELETE route added
router.get('/organization/organizations', authenticateToken, organizationController.getAllOrganizations);
router.get('/organization/organizations/:id', authenticateToken, organizationController.getOrganizationById);
router.post('/organization/organizations', authenticateToken, organizationController.createOrganization);
router.put('/organization/organizations/:id', authenticateToken, organizationController.updateOrganization);
router.delete('/organization/organizations/:id', authenticateToken, organizationController.deleteOrganization); // 🆕 DELETE route added

@@ -30,0 +30,0 @@ // 🟠 AUTHORIZED - Specific user roles

import express from 'express';
import pricingPackageFeatureController from '../controllers/pricingPackageFeatureController.js';
// Import authentication and authorization middleware
import { validateApiKey } from '../middleware/validateApiKey.js';
import { validateApiKey } from '../../../../middleware/validateApiKey.js';
import { authenticateToken } from '@semantq/auth/lib/middleware/authMiddleware.js';

@@ -23,7 +23,7 @@ import { authorize } from '@semantq/auth/lib/middleware/authorize.js';

// 🟡 AUTHENTICATED - Logged-in users only
router.get('/pricingPackageFeatures', authenticateToken, pricingPackageFeatureController.getAllPricingPackageFeatures);
router.get('/pricingPackageFeatures/:id', authenticateToken, pricingPackageFeatureController.getPricingPackageFeatureById);
router.post('/pricingPackageFeatures', authenticateToken, pricingPackageFeatureController.createPricingPackageFeature);
router.put('/pricingPackageFeatures/:id', authenticateToken, pricingPackageFeatureController.updatePricingPackageFeature);
router.delete('/pricingPackageFeatures/:id', authenticateToken, pricingPackageFeatureController.deletePricingPackageFeature); // 🆕 DELETE route added
router.get('/pricingPackageFeature/pricingPackageFeatures', authenticateToken, pricingPackageFeatureController.getAllPricingPackageFeatures);
router.get('/pricingPackageFeature/pricingPackageFeatures/:id', authenticateToken, pricingPackageFeatureController.getPricingPackageFeatureById);
router.post('/pricingPackageFeature/pricingPackageFeatures', authenticateToken, pricingPackageFeatureController.createPricingPackageFeature);
router.put('/pricingPackageFeature/pricingPackageFeatures/:id', authenticateToken, pricingPackageFeatureController.updatePricingPackageFeature);
router.delete('/pricingPackageFeature/pricingPackageFeatures/:id', authenticateToken, pricingPackageFeatureController.deletePricingPackageFeature); // 🆕 DELETE route added

@@ -30,0 +30,0 @@ // 🟠 AUTHORIZED - Specific user roles

import express from 'express';
import pylonController from '../controllers/pylonController.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';
import { validateApiKey } from '../../../../middleware/validateApiKey.js';
import { authenticateToken } from '../../../@semantq/auth/lib/middleware/authMiddleware.js';
import { authorize } from '../../../@semantq/auth/lib/middleware/authorize.js';

@@ -8,0 +8,0 @@ const router = express.Router();

@@ -1,2 +0,2 @@

import FeatureModel from '../models/supabase/Feature.js';
import FeatureModel from '../models/mysql/Feature.js';

@@ -9,11 +9,9 @@ class FeatureService {

async getById(id) {
const feature = await FeatureModel.findById(id);
if (!feature) throw new Error('Feature not found');
return feature;
return await FeatureModel.findById(id);
}
async getByName(name) {
const feature = await FeatureModel.findByName(name);
if (!feature) throw new Error('Feature not found');
return feature;
// getNonCrudFeatureNames
async getNonCrudFeatureNames() {
return await FeatureModel.getNonCrudFeatureNames();
}

@@ -26,3 +24,2 @@

async update(id, data) {
await this.getById(id); // Verify exists
return await FeatureModel.update(id, data);

@@ -32,3 +29,2 @@ }

async delete(id) {
await this.getById(id); // Verify exists
return await FeatureModel.delete(id);

@@ -35,0 +31,0 @@ }

@@ -1,4 +0,5 @@

import MeteringModel from '../models/supabase/Metering.js';
import MeteringModel from '../models/mysql/Metering.js';
class MeteringService {
async create(data) {

@@ -34,2 +35,3 @@ return await MeteringModel.create(data);

/*
// Pylon-specific methods

@@ -43,4 +45,6 @@ async getUsage(organizationId, featureId) {

}
*/
}
export default new MeteringService();

@@ -1,3 +0,3 @@

import ModelModel from '../models/supabase/Model.js';
import ModelsManifest from '../lib/models_manifest.js';
import ModelModel from '../models/mysql/Model.js';
import ModelsManifest from '../../../../lib/models_manifest.js';

@@ -4,0 +4,0 @@ class ModelService {

@@ -1,6 +0,47 @@

import OrganizationModel from '../models/supabase/Organization.js';
import OrganizationModel from '../models/mysql/Organization.js';
import PricingPackageModel from '../models/mysql/PricingPackage.js';
//
import UserModel from '../../../../models/mysql/User.js';
class OrganizationService {
async create(data) {
return await OrganizationModel.create(data);
const { name, pricingPackageId, ownerId } = data;
// Get pricing package details
const pricingPackage = await PricingPackageModel.findById(pricingPackageId);
if (!pricingPackage) {
throw new Error('Pricing package not found');
}
const now = new Date();
let finalData = { ...data };
// Check if it's a free package (both prices are 0 or less than 1)
const isFreePackage =
(!pricingPackage.priceMonthly || pricingPackage.priceMonthly < 1) &&
(!pricingPackage.priceYearly || pricingPackage.priceYearly < 1);
if (isFreePackage) {
// For free packages, activate immediately with 1-year period
finalData.paidPeriodStart = now;
finalData.paidPeriodEnd = new Date(now.getFullYear() + 1, now.getMonth(), now.getDate());
console.log('Creating free package with automatic activation');
} else {
// For paid packages, set trial period (30 days)
finalData.trialStartedAt = now;
finalData.trialEndsAt = new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000); // 30 days
console.log('Creating paid package with trial period');
}
// Create the organization
const organization = await OrganizationModel.create(finalData);
const userUpdate = await UserModel.updateOrgId(organization.ownerId, organization.id);
//console.log("organization data",organization);
return organization;
}

@@ -7,0 +48,0 @@

@@ -1,2 +0,2 @@

import PricingPackageFeatureModel from '../models/supabase/PricingPackageFeature.js';
import PricingPackageFeatureModel from '../models/mysql/PricingPackageFeature.js';

@@ -3,0 +3,0 @@ class PricingPackageFeatureService {

//models/supabase/Pylon.js
// Add to your schema.prisma:
/*
model Pylon {
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 PylonModel {
/**
* Creates a new pylon in the database.
* @param {object} data - The data for the new pylon.
* @returns {Promise<object>} The created pylon object.
*/
static async create(data) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pylon.create({ data });
}
/**
* Finds a pylon by its unique ID.
* @param {string} id - The ID of the pylon to find.
* @returns {Promise<object|null>} The found pylon object, or null if not found.
*/
static async findById(id) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pylon.findUnique({ where: { id } });
}
/**
* Retrieves all pylons from the database.
* @returns {Promise<Array<object>>} An array of all pylon objects.
*/
static async findAll() {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pylon.findMany();
}
/**
* Updates an existing pylon by its ID.
* @param {string} id - The ID of the pylon to update.
* @param {object} data - The data to update the pylon with.
* @returns {Promise<object>} The updated pylon object.
*/
static async update(id, data) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pylon.update({
where: { id },
data,
});
}
/**
* Deletes a pylon by its ID.
* @param {string} id - The ID of the pylon to delete.
* @returns {Promise<object>} The deleted pylon object.
*/
static async delete(id) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pylon.delete({ where: { id } });
}
/**
* Finds pylons 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 pylon objects for the given pagination.
*/
static async findWithPagination(skip = 0, take = 10) {
const prisma = await getPrismaClient(); // Get the initialized Prisma client
return prisma.pylon.findMany({
skip,
take,
orderBy: { createdAt: 'desc' }, // Assuming 'createdAt' field exists for ordering
});
}
}