I have 2 models, entity and acl,
acl rules can be included/excluded and target is reference to entity ( or special case 'public' )
ACL:
const { DataTypes, Model } = require('sequelize');
class ACL extends Model {}
module.exports = (sequelize) => {
const attributes = {
id: {
type: DataTypes.INTEGER(11),
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
object_unique_id: {
type: DataTypes.STRING(36),
allowNull: false,
},
principal_unique_id: {
type: DataTypes.STRING(36),
allowNull: false,
},
rule: {
type: DataTypes.INTEGER(11),
allowNull: false,
defaultValue: 1,
comment: '1=allow, 0=deny',
},
};
const options = {
modelName: 'acl',
tableName: 'acl',
sequelize, // We need to pass the connection instance
};
return ACL.init(attributes, options);
};
ENTITY:
const { DataTypes, Model } = require('sequelize');
const { Op } = require('sequelize');
class Entity extends Model {}
module.exports = (sequelize, ACL) => {
Entity.init(
{
id: {
type: DataTypes.INTEGER(11).UNSIGNED,
allowNull: false,
primaryKey: true,
autoIncrement: true,
},
unique_id: {
type: DataTypes.STRING(36),
allowNull: false,
},
name: {
type: DataTypes.STRING(50),
allowNull: false,
},
},
{
indexes: [
{
name: 'unique_id',
unique: true,
fields: ['unique_id'],
},
],
tableName: 'entity',
sequelize,
modelName: 'entity',
scopes: {
byPrincipalUID(principalUID) {
const includedCondition = [
{ principal_unique_id: 'public' },
];
if (principalUID) {
includedCondition.push({
principal_unique_id: principalUID,
});
}
return {
include: [
{
model: ACL,
where: {
[Op.and]: [
{ rule: 1 },
{ [Op.or]: includedCondition },
],
},
},
],
};
},
public: {
include: [
{
model: ACL,
where: {
[Op.and]: [
{ rule: 1 },
{ principal_unique_id: 'public' },
],
},
},
],
},
},
}
);
Entity.hasMany(ACL, {
sourceKey: 'unique_id',
foreignKey: 'object_unique_id',
});
return Entity;
};
test cases:
describe('AclService', () => {
it('should works with scopes', async () => {
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const ACL = require('../db/models/acl.model')(sequelize);
const Entity = require('../db/models/entity.model')(sequelize, ACL);
await ACL.sync();
await Entity.sync();
await Entity.create({ name: 'test 1', unique_id: 's1' });
await Entity.create({ name: 'test 2', unique_id: 's2' });
await Entity.create({ name: 'test 3', unique_id: 's3' });
await ACL.create({
object_unique_id: 's1',
rule: 1,
principal_unique_id: 'public',
});
await ACL.create({
object_unique_id: 's2',
rule: 1,
principal_unique_id: 'c1',
});
await ACL.create({
object_unique_id: 's3',
rule: 1,
principal_unique_id: 'p1',
});
const all = await Entity.findAll();
expect(all.length).toBe(3);
const publicSubjects = await Entity.scope('public').findAll();
expect(publicSubjects.length).toBe(1);
const companySubjects = await Entity.scope({
method: ['byPrincipalUID', 'c1'],
}).findAll();
expect(companySubjects.length).toBe(2);
const accountSubjects = await Entity.scope({
method: ['byPrincipalUID', 'p1'],
}).findAll();
expect(accountSubjects.length).toBe(2);
});
it('should works with scopes and deny rule', async () => {
const { Sequelize } = require('sequelize');
const sequelize = new Sequelize('sqlite::memory:');
const ACL = require('../db/models/acl.model')(sequelize);
const Entity = require('../db/models/entity.model')(sequelize, ACL);
await ACL.sync();
await Entity.sync();
await Entity.create({ name: 'test 1', unique_id: 's1' });
await Entity.create({ name: 'test 2', unique_id: 's2' });
await ACL.create({
object_unique_id: 's1',
rule: 1,
principal_unique_id: 'public',
});
await ACL.create({
object_unique_id: 's2',
rule: 1,
principal_unique_id: 'public',
});
await ACL.create({
object_unique_id: 's2',
rule: 0,
principal_unique_id: 'c1',
});
const all = await Entity.findAll();
expect(all.length).toBe(2);
const publicSubjects = await Entity.scope('public').findAll();
expect(publicSubjects.length).toBe(2);
//s2 is denied for c1
const companySubjects = await Entity.scope({
method: ['byPrincipalUID', 'c1'],
}).findAll();
expect(companySubjects.length).toBe(1);
});
});
so with 'Grant' permission all works as expected.
But have no good ideas how to exclude 'Deny' records.
So in second test I have 2 'public' entities, but second one have "deny" rule for c1
I have only one idea, somehow use raw subquery something like
NOT EXISTS(SELECT id FROM acl
WHERE acl.object_unique_id = ${targetTable}.unique_id
AND (acl.rule = 0 AND (
.....
)
)
but not sure how to make it using sequelize
Related
I have got a few sql tables as
export default (sequelize, Sequelize) => {
return sequelize.define('teacher', {
tagline: {
type: Sequelize.TEXT,
},
modeOfPayment: {
type: Sequelize.STRING,
},
modeOfSession: {
type: Sequelize.STRING,
},
preferredTimeZones: {
type: Sequelize.STRING,
},
titleForSessions: {
type: Sequelize.STRING,
},
availableForWork: {
type: Sequelize.BOOLEAN,
},
});
};
export default (sequelize, Sequelize) => {
return sequelize.define('skill', {
name: {
type: Sequelize.STRING,
unique: true,
allowNull: false,
},
});
};
export default (sequelize, Sequelize) => {
return sequelize.define('category', {
name: {
type: Sequelize.STRING,
unique: true,
allowNull: false,
},
});
};
Here is the model relation between them
Teacher.belongsToMany(Skill, {
through: 'skill_teacher',
});
Skill.belongsToMany(Teacher, {
through: 'skill_teacher',
});
Category.hasMany(Skill);
Skill.belongsTo(Category);
Earlier I needed to query count of teachers in each skill, here's my controller for that
// #desc Get skill count for each skill
// #route GET /api/skills/count
// #access Public
const getSkillCount = asyncHandler(async (req, res) => {
try {
const skills = await Skill.findAll({
attributes: [
'id',
'name',
[sequelize.fn('count', sequelize.col('teachers.id')), 'teacherCount'],
],
include: [{ attributes: [], model: Teacher }],
group: ['skill.id'],
});
res.json(skills);
} catch (err) {
console.log(err.message);
res.status(500);
throw new Error(err.message);
}
});
Now, the skills are grouped in categories. So I wanna query a list of skills having their own teacher count grouped in their own categories, which category also having a skillCount column. I tried this but it is not giving my desired results
// #desc Get category and their skills counts
// #route GET /api/categories/skills/count
// #access Public
const getCategorySkillCounts = asyncHandler(async (req, res) => {
try {
const categories = await Category.findAll({
attributes: [
'id',
'name',
[sequelize.fn('count', sequelize.col('skills.id')), 'skillCount'],
],
include: {
model: Skill,
include: [{ model: Teacher }],
attributes: [
'id',
'name',
[sequelize.fn('count', sequelize.col('teachers.id')), 'teacherCount'],
],
group: ['skill.id'],
},
group: ['category.id'],
});
res.json(categories);
} catch (err) {
console.log(err.message);
res.status(500);
throw new Error(err.message);
}
});
I have a postAsset action in my vuex store like so
async postAsset({dispatch}, asset) {
const f = await dispatch('srcToFile', asset);
asset[0].files.fileList = f;
const fileData = asset[0].files.fileList;
const detailData = asset[0].detail;
const fData = new FormData();
fData.append('Name', asset[0].name);
Object.keys(detailData).forEach((key) => {
fData.append(`Detail.${key}`, detailData[key]);
});
for (var i = 0; i < fileData.length; i++) {
fData.append('Files', fileData[i]);
}
await axios({
method: 'post',
url: 'https://localhost:5001/api/Assets',
data: fData,
headers: {
'Content-Type': undefined
}
})
.then(function(response) {
console.warn(response);
})
.catch(function(response) {
console.warn(response);
});
}
It is successfully posting to my api backend and to the database.
The issue that I am running into is that after I make the first post it posts the previous data and the new data I do not know why it is doing this. I did add await to the axios call but that just slowed it down it is still posting two times after the first and im sure if i keep posting it will continue to post the previous ones into the db again and again. Im at a loss as to what is going on so reaching out for some assistance to see if I can get this resolved.
examples of what it looks like in the db
does anyone have any advice for me so I can get this fixed? I should only be getting one item posted at a time that is the desired result. I have gone through my inputs and put in .prevent to stop them from clicking twice but I don't think it is that .. this is like it is saving the data and reposting it all at once each time I add a new record .
UPDATE:
the code that calls the action
populateAssets ({ dispatch }, asset) {
return new Promise((resolve) => {
assets.forEach((asset) => {
commit('createAsset', asset);
);
dispatch('postAsset', asset);
resolve(true);
});
},
the populate assets populates a list with a completed asset.
and asset is coming from the srcToFile method
that converts the files to a blob that I can post with
async srcToFile(context, asset) {
const files = asset[0].files.fileList;
let pmsArray = [];
for (let f = 0; f < files.length; f++) {
var data = files[f].data;
let name = files[f].name;
let mimeType = files[f].type;
await fetch(data)
.then(function(res) {
const r = res.arrayBuffer();
console.warn('resource ', r);
return r;
})
.then(function(buf) {
console.warn('buffer: ', [buf]);
let file = new File([buf], name, { type: mimeType });
pmsArray.push(file);
});
}
console.warn(pmsArray);
return pmsArray;
},
asset is an array from my add asset component
structure of asset
name: '',
detail: {
category: '',
manufacturer: '',
model: '',
serialNumber: '',
purchasePlace: '',
quantity: 1,
acquiredDate: '',
purchasePrice: '',
currentValue: '',
condition: '',
assetLocation: '',
retiredDate: '',
description: ''
},
files: {
fileList: []
}
hope this helps out some
the whole store file
import Vue from 'vue'
import Vuex from 'vuex'
import { states } from '../components/enums/enums'
import { getField, updateField } from 'vuex-map-fields'
import axios from 'axios'
Vue.use(Vuex);
const inventory = {
namespaced: true,
strict: true,
state: {
assets: {
items: []
},
categories: [],
manufacturers: [],
assetLocations: [],
conditions: ['New', 'Fair', 'Good', 'Poor']
},
getters: {
assetItems: state => state.assets.items,
getAssetById: (state) => (id) => {
return state.assets.items.find(i => i.id === id);
},
conditions: (state) => state.conditions,
categories: (state) => state.categories,
manufacturers: (state) => state.manufacturers,
assetLocations: (state) => state.assetLocations
},
mutations: {
createAsset (state, assets) {
state.assets.items.push(assets);
},
createCategories (state, category) {
state.categories.push(category);
},
createManufacturers (state, manufacturer) {
state.manufacturers.push(manufacturer);
},
createLocations (state, locations) {
state.assetLocations.push(locations);
}
},
actions: {
addToCategories ({ commit }, categories) {
commit('createCategories', categories);
},
addToManufacturers ({ commit }, manufacturers) {
commit('createManufacturers', manufacturers);
},
addToLocations ({ commit }, locations) {
commit('createLocations', locations);
},
populateAssets ({ dispatch }, asset) {
//return new Promise((resolve) => {
// assets.forEach((asset) => {
// commit('createAsset', asset);
// });
dispatch('postAsset', asset);
// resolve(true);
//});
},
addAsset ({ dispatch, /*getters*/ }, newAsset) {
//let assetCount = getters.assetItems.length;
//newAsset.id = assetCount === 0
// ? 1
// : assetCount++;
dispatch('populateAssets', [newAsset]);
},
async srcToFile(context, asset) {
const files = asset[0].files.fileList;
let pmsArray = [];
for (let f = 0; f < files.length; f++) {
var data = files[f].data;
let name = files[f].name;
let mimeType = files[f].type;
await fetch(data)
.then(function(res) {
const r = res.arrayBuffer();
console.warn('resource ', r);
return r;
})
.then(function(buf) {
console.warn('buffer: ', [buf]);
let file = new File([buf], name, { type: mimeType });
pmsArray.push(file);
});
}
console.warn(pmsArray);
return pmsArray;
},
async postAsset({ dispatch }, asset) {
const f = await dispatch('srcToFile', asset);
asset[0].files.fileList = f;
const fileData = asset[0].files.fileList;
const detailData = asset[0].detail;
const fData = new FormData();
fData.append('Name', asset[0].name);
Object.keys(detailData).forEach((key) => {
fData.append(`Detail.${key}`, detailData[key]);
});
for (var i = 0; i < fileData.length; i++) {
fData.append('Files', fileData[i]);
}
await axios({
method: 'post',
url: 'https://localhost:5001/api/Assets',
data: fData,
headers: { 'Content-Type': undefined }
})
.then(function(response) {
console.warn(response);
})
.catch(function(response) {
console.warn(response);
});
}
}
};
const maintenance = {
state: {
backup: []
},
strict: true,
getters: {},
mutations: {},
actions: {}
};
const assetProcessing = {
namespaced: true,
state: {
currentAsset: {
id: 0,
name: '',
detail: {
category: '',
manufacturer: '',
model: '',
serialNumber: '',
purchasePlace: '',
quantity: 1,
acquiredDate: '',
purchasePrice: '',
currentValue: '',
condition: '',
assetLocation: '',
retiredDate: '',
description: ''
},
files: {
fileList: []
}
},
filePosition: -1,
selectedItem: -1,
state: states.view,
isNewAsset: false
},
getters: {
getField,
getOpenAsset (state) {
return state.currentAsset
},
getSelectedAsset: (state, getters, rootState, rootGetters) => (id) => {
if (state.isNewAsset) return state.currentAsset
Object.assign(state.currentAsset, JSON.parse(JSON.stringify(rootGetters['inventory/getAssetById'](!id ? 0 : id))));
return state.currentAsset
},
appState: (state) => state.state,
getCurrentPosition (state) {
return state.filePosition
},
selectedAssetId: (state) => state.selectedItem
},
mutations: {
updateField,
setAsset (state, asset) {
Object.assign(state.currentAsset, asset)
},
setFiles (state, files) {
Object.assign(state.currentAsset.files, files)
},
newAsset (state) {
Object.assign(state.isNewAsset, true)
Object.assign(state.currentAsset, {
id: 0,
name: '',
detail: {
category: '',
manufacturer: '',
model: '',
serialNumber: '',
purchasePlace: '',
quantity: 1,
acquiredDate: '',
purchasePrice: '',
currentValue: '',
condition: '',
assetLocation: '',
retiredDate: '',
description: ''
},
files: {
fileList: []
}
})
},
updateSelectedItem (state, id) {
Vue.set(state, 'selectedItem', id);
},
updateState (state, newState) {
Vue.set(state, 'state', newState);
}
},
actions: {}
};
export const store = new Vuex.Store({
modules: {
inventory: inventory,
maintenance: maintenance,
assetProcessing
}
})
add asset is called when the user clicks the save button on the form
addAsset () {
this.$store.dispatch('inventory/addAsset', this.newAsset) <--- this calls add asset
this.$store.commit('assetProcessing/updateState', states.view);<-- this closes the window
},
So after much debugging we found that the eventbus was firing multiple times causing the excessive posting we added
beforeDestroy() {
eventBus.$off('passAssetToBeSaved');
eventBus.$off('updateAddActionBar');
},
to the AssetAdd.vue component and it eliminated the excessive posting of the asset.
I want to thank #phil for helping me out in this.
import Realm from 'realm';
class Cities extends Realm.Object {}
class Users extends Realm.Object {}
Cities.schema = {
name: 'Cities',
properties: {
'name': {
type: 'string'
},
'pincode': {
type: 'int'
}
}
};
Users.schema = {
name: 'Users',
primaryKey: 'id',
properties: {
'id': 'string',
'name': {
type: 'string'
},
'city': {
type: 'list',
objectType: 'Cities'
}
}
};
const schemaList = [Users, Cities];
const realmInstance = new Realm({schema: schemaList});
export default realmInstance;
// pushing a cityObj (that is already present in 'Cities') for a existing User:
onPress={() => this.addCity({name: 'Delhi', pincode: 110004})}
addCity = (cityObj) => {
realm.write(() => {
let user = realm.create('Users', {
'id': 'someUniqueID'
}, true);
user.city.push(cityObj);
});
let cities = realm.objects('Cities');
console.log('cities.length', cities.length);
}
though, trying to update a record in 'Users', The write transaction is writing a new record in Cities table as well creating duplicates. Why so?
Adding to a list will in general create a new object. But you can add a primary key to Cities, create/update the object first and finally push it to the list. Something like:
const Realm = require('realm');
const CitiesSchema = {
name: 'Cities',
primaryKey: 'pincode',
properties: {
'name': {
type: 'string'
},
'pincode': {
type: 'int'
}
}
};
const UsersSchema = {
name: 'Users',
primaryKey: 'id',
properties: {
'id': 'string',
'name': {
type: 'string'
},
'city': {
type: 'list',
objectType: 'Cities'
}
}
};
const schemaList = [UsersSchema, CitiesSchema];
const realm = new Realm({schema: schemaList});
addCity = (cityObj) => {
realm.write(() => {
let city = realm.create('Cities', cityObj, true);
let user = realm.create('Users', {
id: 'someUniqueID',
name: 'Foo Bar'
}, true);
user.city.push(city);
});
let cities = realm.objects('Cities');
console.log('cities.length', cities.length);
}
addCity({name: 'Delhi', pincode: 110004});
addCity({name: 'Delhi', pincode: 110004});
I have 2 models User and Item with many to many relation, here is the definitions:
User = sequelize.define('User', {
name: {type: Sequelize.STRING}
})
Item = sequelize.define('Item', {
name: {
type: Sequelize.STRING,
allowNull: false
}
}
User.belongsToMany(models.Item, {
as: 'items',
through: 'UserItem'
})
Item.belongsToMany(models.User, {
as: 'owners',
through: 'UserItem'
})
And my request is :
Item.findAll({
include: [{
model: User,
through: {
where: {id: 2}
}
}]
}).then(items => {
log.debug(items)
}).catch(err => {
log.error(err)
})
Then I have : Error: User is not associated to Item!
I also try this :
Item.findAll({
where: {'owners.id': 2},
include: Item.assocations.owners
}).then(items => {
debug(items)
}).catch(err => {
log.error(err)
})
But now I have Error: SQLITE_ERROR: no such column: Item.owners.id
Any ideas ?
Here is my working solution :
'use strict'
const Sequelize = require('Sequelize')
const sequelize = new Sequelize({
host: 'localhost',
dialect: 'sqlite',
// SQLite only
storage: 'database.sqlite'
})
const User = sequelize.define('user', {
name: {
type: Sequelize.STRING
}
}, {
freezeTableName: true // Model tableName will be the same as the model name
})
const Item = sequelize.define('item', {
name: {
type: Sequelize.STRING
}
}, {
freezeTableName: true // Model tableName will be the same as the model name
})
const UserItem = sequelize.define('useritem', {})
User.belongsToMany(Item, {
as: 'items',
through: 'useritem'
})
Item.belongsToMany(User, {
as: 'owners',
through: 'useritem'
})
Promise.all([
User.sync({force: true}),
Item.sync({force: true}),
UserItem.sync({force: true})])
.then(() => {
return Promise.all([
User.create({name: 'test'}),
User.create({name: 'admin'}),
Item.create({name: 'item1'}),
Item.create({name: 'item2'})])
.then(results => {
return results[2].addOwner(results[0])
}).then(() => {
return Item.findAll({
include: {model: User, as: 'owners', where:{
'id': 1
}}
}).then(items => {
console.log(items)
})
})
}).catch(err => console.log(err))
as need to be on include clause and where should be added on include too
I am trying to associate my User model with my Organization model, but I'm running into an error that says, Error: user is not associated to organization! despite the fact that I am following the process to associate the User to my Organization. Is it possible that the type of association method that I am using is causing the problem?
User Model (user.js):
var bcrypt = require('bcrypt-nodejs');
module.exports = function(sequelize, DataTypes) {
var User = sequelize.define('user', {
user_id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
firstName: {
type: DataTypes.STRING,
field: 'first_name'
},
lastName: {
type: DataTypes.STRING,
field: 'last_name'
},
email: {
type: DataTypes.STRING,
isEmail: true,
unique: true
},
password: DataTypes.STRING,
organizationId: {
type: DataTypes.INTEGER,
field: 'organization_id',
allowNull: true
}
}, {
freezeTableName: true,
classMethods: {
generateHash: function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
},
associate: function(db) {
User.belongsTo(db.Organization, {foreignKey: 'organizationId'});
},
},
instanceMethods: {
validPassword: function(password) {
return bcrypt.compareSync(password, this.password);
},
},
});
return User;
}
Organization model (organization.js):
module.exports = function(sequelize, DataTypes) {
var Organization = sequelize.define('organization', {
organizationId: {
type: DataTypes.INTEGER,
field: 'organization_id',
autoIncrement: true,
primaryKey: true
},
organizationName: {
type: DataTypes.STRING,
field: 'organization_name'
},
admin: DataTypes.STRING,
members: DataTypes.STRING
},{
freezeTableName: true
});
return Organization;
}
index for tables to connect (db-index.js):
var Sequelize = require('sequelize');
var path = require('path');
var config = require(path.resolve(__dirname, '..', '..','./config/config.js'));
var sequelize = new Sequelize(config.database, config.username, config.password, {
host:'localhost',
port:'3306',
dialect: 'mysql'
});
sequelize.authenticate().then(function(err) {
if (!!err) {
console.log('Unable to connect to the database:', err)
} else {
console.log('Connection has been established successfully.')
}
});
var db = {}
db.Organization = sequelize.import(__dirname + "/organization");
db.User = sequelize.import(__dirname + "/user");
db.Records = sequelize.import(__dirname + "/records");
db.User.associate(db);
db.Records.associate(db);
db.sequelize = sequelize;
db.Sequelize = Sequelize;
sequelize.sync();
module.exports = db;
Are of a route call that triggers this error:
appRoutes.route('/sign-up/organization')
.get(function(req, res){
models.User.find({
where: {
user_id: req.user.email
}, attributes: [ 'user_id', 'email'
]
}).then(function(user){
res.render('pages/sign-up-organization.hbs',{
user: req.user
});
})
})
.post(function(req, res, user){
models.Organization.create({
organizationName: req.body.organizationName,
admin: req.body.admin,
user: {
organizationId: req.body.organizationId
}
}, { include: [models.User] }).then(function(){
console.log(user.user_id);
res.redirect('/app');
}).catch(function(error){
res.send(error);
console.log('Error at Post');
})
});
You need to set up the reverse of the association, Organization hasMany Users