Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add Owner to the database #514

Merged
merged 18 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
### Bug Fixes
* **core**: Fix Arbimon API path to separated the path between staging as `legacy-api` and production as `api`
* **core**: Fix logging request error on endpoint `streams/:id/detections` receiving array of objects
* **core**: Add `Create Owner to role and role permission table` migration files
* **core**: Add `include_roles` and `permissions` params to `project/:id/users` endpoint
* **core**: Fix test cases after above changes

## 1.3.4 (2023-12-xx)
### Bug Fixes
Expand Down
17 changes: 3 additions & 14 deletions common/testing/sequelize.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const differentUserLastname = 'Shady'
const roleAdmin = 1
const roleMember = 2
const roleGuest = 3
const roleOwner = 4
const seedValues = {
primaryUserId,
primaryUserGuid,
Expand Down Expand Up @@ -91,30 +92,18 @@ const seedValues = {
differentUserLastname,
roleAdmin,
roleMember,
roleGuest
roleGuest,
roleOwner
}

async function seed (models) {
await models.ClassificationType.destroy({ where: {} })
await models.ClassificationSource.destroy({ where: {} })
await models.RolePermission.destroy({ where: {} })
await models.Role.destroy({ where: {} })
await models.User.destroy({ where: {} })
await models.User.findOrCreate({ where: { id: primaryUserId, guid: primaryUserGuid, username: 'jb', firstname: primaryUserFirstname, lastname: primaryUserLastname, email: primaryUserEmail } })
await models.User.findOrCreate({ where: { id: otherUserId, guid: otherUserGuid, username: 'em', firstname: otherUserFirstname, lastname: otherUserLastname, email: otherUserEmail } })
await models.User.findOrCreate({ where: { id: anotherUserId, guid: anotherUserGuid, username: 'st', firstname: anotherUserFirstname, lastname: anotherUserLastname, email: anotherUserEmail } })
await models.User.findOrCreate({ where: { id: differentUserId, guid: differentUserGuid, username: 'sl', firstname: differentUserFirstname, lastname: differentUserLastname, email: differentUserEmail } })
await models.Role.findOrCreate({ where: { id: roleAdmin, name: 'Admin' } })
await models.Role.findOrCreate({ where: { id: roleMember, name: 'Member' } })
await models.Role.findOrCreate({ where: { id: roleGuest, name: 'Guest' } })
await models.RolePermission.findOrCreate({ where: { role_id: roleAdmin, permission: 'C' } })
await models.RolePermission.findOrCreate({ where: { role_id: roleAdmin, permission: 'R' } })
await models.RolePermission.findOrCreate({ where: { role_id: roleAdmin, permission: 'U' } })
await models.RolePermission.findOrCreate({ where: { role_id: roleAdmin, permission: 'D' } })
await models.RolePermission.findOrCreate({ where: { role_id: roleMember, permission: 'C' } })
await models.RolePermission.findOrCreate({ where: { role_id: roleMember, permission: 'R' } })
await models.RolePermission.findOrCreate({ where: { role_id: roleMember, permission: 'U' } })
await models.RolePermission.findOrCreate({ where: { role_id: roleGuest, permission: 'R' } })
await models.ClassificationSource.findOrCreate({ where: { id: 1, value: 'unknown' } })
await models.ClassificationType.findOrCreate({ where: { id: 1, value: 'unknown' } })
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict'
const { ADMIN, MEMBER, GUEST, OWNER } = require('../../roles/dao')
const roleIds = [ADMIN, MEMBER, GUEST, OWNER] // [1, 2, 3, 4]
const roleList = ['Admin', 'Member', 'Guest', 'Owner']
const permissionList = [`${ADMIN},C`, `${ADMIN},R`, `${ADMIN},U`, `${ADMIN},D`, `${MEMBER},C`, `${MEMBER},R`, `${MEMBER},U`, `${GUEST},R`, `${OWNER},C`, `${OWNER},R`, `${OWNER},U`, `${OWNER},D`]
const insertedRoles = [] // [1, 2 ,3 ,4]
const insertedRolePermissions = [] // [{ roleId:1, permission:'C'}, { roleId:1, permission:'R'}]
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction(async transaction => {
// Find and Create roles (1, Admin), (2, Member), (3, Guest), (4, Owner)
for (const id of roleIds) {
const [[roleData]] = await queryInterface.sequelize.query(`select (id) from roles where id = ${id}`, { transaction })
if (!roleData) {
const [[roleInsertData]] = await queryInterface.sequelize.query(`insert into roles (id, name, description) values (${id}, '${roleList[id - 1]}', '') returning id;`, { transaction })
insertedRoles.push(roleInsertData.id)
}
}
// Find and Create role permissions (1, C), (1, R), (1, U), (1, D), (2, C), (2, R), (2, U), (3, R), (4, C), (4, R), (4, U), (4, D)
for (const permission of permissionList) {
const roleId = permission.split(',')[0]
const permissionValue = permission.split(',')[1]
const [[permissionData]] = await queryInterface.sequelize.query(`select (role_id) from role_permissions where role_id = ${roleId} and permission = '${permissionValue}'`, { transaction })
if (!permissionData) {
await queryInterface.sequelize.query(`insert into role_permissions (role_id, permission) values (${roleId}, '${permissionValue}')`, { transaction })
insertedRolePermissions.push({
roleId: roleId,
permission: permissionValue
})
}
}
})
},
down: (queryInterface) => {
return queryInterface.sequelize.transaction(async transaction => {
for (const rolePerm of insertedRolePermissions) {
const id = rolePerm.roleId
const permission = rolePerm.permission
await queryInterface.sequelize.query(`delete from role_permissions where role_id = ${id}' and permission = '${permission}'`, { transaction })
}
for (const id of insertedRoles) {
await queryInterface.sequelize.query(`delete from roles where id = ${id}`, { transaction })
}
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict'
const { OWNER } = require('../../roles/dao')
const insertedUserAndProject = [] // [{ userId: 1, projectId: "aaaaaaaaaaa" }]
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction(async transaction => {
const [projectsCreatedBy] = await queryInterface.sequelize.query('select id, created_by_id from projects', { transaction })
for (const projectAndOwner of projectsCreatedBy) {
await queryInterface.sequelize.query(`delete from user_project_roles where user_id = ${projectAndOwner.created_by_id} and project_id = '${projectAndOwner.id}'`, { transaction })
await queryInterface.sequelize.query(`insert into user_project_roles (user_id, project_id, role_id, created_at, updated_at) values (${projectAndOwner.created_by_id}, '${projectAndOwner.id}', ${OWNER}, now(), now())`, { transaction })
insertedUserAndProject.push({
userId: projectAndOwner.created_by_id,
projectId: projectAndOwner.id
})
}
})
},
down: (queryInterface) => {
return queryInterface.sequelize.transaction(async transaction => {
for (const userAndProject of insertedUserAndProject) {
await queryInterface.sequelize.query(`delete from user_project_roles where user_id = ${userAndProject.userId} and project_id = '${userAndProject.projectId}'`, { transaction })
}
})
}
}
3 changes: 0 additions & 3 deletions core/_cli/seeds/03-roles.sql

This file was deleted.

9 changes: 0 additions & 9 deletions core/_cli/seeds/04-role-permissions.sql

This file was deleted.

2 changes: 2 additions & 0 deletions core/classifier-jobs/create.int.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ async function seedTestData () {
await await models.Classifier.bulkCreate(CLASSIFIERS)
await await models.Project.bulkCreate(PROJECTS)
await await models.Stream.bulkCreate(STREAMS)
await models.UserProjectRole.bulkCreate(PROJECTS.map(project => { return { project_id: project.id, user_id: project.createdById, role_id: seedValues.roleOwner } }))
await models.UserStreamRole.bulkCreate(STREAMS.map(stream => { return { stream_id: stream.id, user_id: stream.createdById, role_id: seedValues.roleOwner } }))
await models.UserProjectRole.create({ user_id: seedValues.primaryUserId, project_id: PROJECT_1.id, role_id: seedValues.roleMember })
}

Expand Down
1 change: 1 addition & 0 deletions core/detections/list.int.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ afterAll(async () => {

async function commonSetup () {
const project = await models.Project.create({ id: 'foo', name: 'my project', createdById: seedValues.primaryUserId })
await models.UserProjectRole.create({ user_id: project.createdById, project_id: project.id, role_id: seedValues.roleOwner })
const stream = { id: 'abc', name: 'my stream', createdById: seedValues.primaryUserId, projectId: project.id }
await models.Stream.create(stream)
const classification = { id: 6, value: 'chainsaw', title: 'Chainsaw', typeId: 1, sourceId: 1 }
Expand Down
2 changes: 2 additions & 0 deletions core/detections/review.int.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ afterAll(async () => {
async function commonSetup () {
const project = await models.Project.create({ id: 'ppp111', name: 'My Project 122', createdById: seedValues.primaryUserId })
const stream = await models.Stream.create({ id: 'abc', name: 'my stream', createdById: seedValues.primaryUserId, projectId: project.id })
await models.UserProjectRole.create({ project_id: project.id, user_id: seedValues.primaryUserId, role_id: seedValues.roleOwner })
await models.UserStreamRole.create({ stream_id: stream.id, user_id: seedValues.primaryUserId, role_id: seedValues.roleOwner })
const classification = await models.Classification.create({ value: 'chainsaw', title: 'Chainsaw', typeId: 1, source_id: 1 })
const classification2 = await models.Classification.create({ value: 'gunshot', title: 'Gunshot', typeId: 1, source_id: 1 })
const classification3 = await models.Classification.create({ value: 'vehicle', title: 'Vehicle', typeId: 1, source_id: 1 })
Expand Down
2 changes: 2 additions & 0 deletions core/detections/stream.int.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ afterAll(async () => {

async function commonSetup () {
const project = await models.Project.create({ id: 'foo', name: 'my project', createdById: seedValues.primaryUserId })
await models.UserProjectRole.create({ user_id: project.createdById, project_id: project.id, role_id: seedValues.roleOwner })
const stream = await models.Stream.create({ id: 'abc', name: 'my stream', createdById: seedValues.primaryUserId, projectId: project.id })
await models.UserStreamRole.create({ stream_id: stream.id, user_id: stream.createdById, role_id: seedValues.roleOwner })
const classification = await models.Classification.create({ value: 'chainsaw', title: 'Chainsaw', typeId: 1, source_id: 1 })
const classifier = await models.Classifier.create({ externalId: 'cccddd', name: 'chainsaw model', version: 1, createdById: seedValues.otherUserId, modelRunner: 'tf2', modelUrl: 's3://something' })
const classifierOutput = { classifierId: classifier.id, classificationId: classification.id, outputClassName: 'chnsw', ignoreThreshold: 0.1 }
Expand Down
2 changes: 2 additions & 0 deletions core/internal/assets/streams.int.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ async function commonSetup () {
await models.AudioCodec.create({ id: 1, value: 'wav' })
await models.FileExtension.create({ id: 1, value: '.wav' })
const stream = await models.Stream.create({ id: 'abc123', name: 'Magpies Nest', latitude: 14.1, longitude: 141.1, createdById: seedValues.primaryUserId })
await models.UserStreamRole.create({ stream_id: stream.id, user_id: stream.createdById, role_id: seedValues.roleOwner })
const sourceFile = await models.StreamSourceFile.create({ stream_id: stream.id, filename: '20210726_101000.wav', duration: 600, sample_count: 1, sample_rate: 12000, channels_count: 1, bit_rate: 1, audio_codec_id: 1, audio_file_format_id: 1 })
const segments = await Promise.all([
{ stream_id: stream.id, start: '2021-07-26T10:10:00.000Z', end: '2021-07-26T10:10:59.999Z', stream_source_file_id: sourceFile.id, sample_count: 1, file_extension_id: 1, availability: 1 },
Expand All @@ -51,6 +52,7 @@ describe('GET /internal/assets/streams/:attributes', () => {
test('segment not found', async () => {
const stream = { id: 'j123s', createdById: seedValues.primaryUserId, name: 'Jaguar Station', latitude: 10.1, longitude: 101.1, altitude: 200 }
await models.Stream.create(stream)
await models.UserStreamRole.create({ stream_id: stream.id, user_id: stream.createdById, role_id: seedValues.roleOwner })

const response = await request(app).get(`/streams/${stream.id}_t20191227T134400000Z.20191227T134420000Z_fwav.wav`)

Expand Down
2 changes: 2 additions & 0 deletions core/internal/ingest/get.int.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ async function commonSetup () {
const fileExtension = { id: 1, value: '.wav' }
await models.FileExtension.create(fileExtension)
const project = await models.Project.create({ id: 'foo', name: 'my project', createdById: seedValues.primaryUserId })
await models.UserProjectRole.create({ user_id: project.createdById, project_id: project.id, role_id: seedValues.roleOwner })
const stream = await models.Stream.create({ id: 'j123k', name: 'Jaguar Station', latitude: 10.1, longitude: 101.1, createdById: seedValues.primaryUserId, projectId: project.id })
await models.UserStreamRole.create({ stream_id: stream.id, user_id: stream.createdById, role_id: seedValues.roleOwner })
return { audioFileFormat, audioCodec, fileExtension, project, stream }
}

Expand Down
1 change: 1 addition & 0 deletions core/internal/prediction/detections.int.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ afterAll(async () => {
async function commonSetup () {
const stream = { id: 'abced', name: 'my stream', createdById: seedValues.primaryUserId }
await models.Stream.findOrCreate({ where: stream })
await models.UserStreamRole.create({ stream_id: stream.id, user_id: stream.createdById, role_id: seedValues.roleOwner })
const classification = (await models.Classification.findOrCreate({ where: { value: 'chainsaw', title: 'Chainsaw', typeId: 1, sourceId: 1 } }))[0]
const classifier = (await models.Classifier.findOrCreate({ where: { externalId: 'cccddd', name: 'chainsaw model', version: 1, createdById: seedValues.otherUserId, modelRunner: 'tf2', modelUrl: 's3://something' } }))[0]
const classifierOutput = { classifierId: classifier.id, classificationId: classification.id, outputClassName: 'chnsw', ignoreThreshold: 0.1 }
Expand Down
1 change: 1 addition & 0 deletions core/internal/prediction/stream.int.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ afterAll(async () => {

async function commonSetup () {
const project = (await models.Project.findOrCreate({ where: { id: 'foo', name: 'my project', createdById: seedValues.primaryUserId } }))[0]
await models.UserProjectRole.create({ user_id: project.createdById, project_id: project.id, role_id: seedValues.roleOwner })
const stream1 = (await models.Stream.findOrCreate({ where: { id: 'j123k', name: 'Jaguar Station', latitude: 10.1, longitude: 101.1, createdById: seedValues.primaryUserId, projectId: project.id } }))[0]
const stream2 = (await models.Stream.findOrCreate({ where: { id: 'opqw1', name: 'Jaguar Station 2', latitude: 10.1, longitude: 101.1, createdById: seedValues.primaryUserId, projectId: project.id } }))[0]
const audioFileFormat = { id: 1, value: 'wav' }
Expand Down
1 change: 1 addition & 0 deletions core/organizations/get.int.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('GET /organizations/:id', () => {
test('result', async () => {
const org = { id: 'r0F1c2X3', name: 'RFCx', createdById: seedValues.primaryUserId }
await models.Organization.create(org)
await models.UserOrganizationRole.create({ user_id: org.createdById, organization_id: org.id, role_id: seedValues.roleOwner })

const response = await request(app).get(`/${org.id}`)

Expand Down
1 change: 1 addition & 0 deletions core/organizations/remove.int.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('DELETE /organizations/:id', () => {
test('result', async () => {
const org = { id: 'r0F1c2X3', name: 'RFCx', createdById: seedValues.primaryUserId }
await models.Organization.create(org)
await models.UserOrganizationRole.create({ user_id: org.createdById, organization_id: org.id, role_id: seedValues.roleOwner })

const response = await request(app).delete(`/${org.id}`)

Expand Down
1 change: 1 addition & 0 deletions core/organizations/update.int.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('PATCH /organizations/:id', () => {
test('result', async () => {
const org = { id: 'r0F1c2X3', name: 'RFCx', createdById: seedValues.primaryUserId }
await models.Organization.create(org)
await models.UserOrganizationRole.create({ user_id: org.createdById, organization_id: org.id, role_id: seedValues.roleOwner })
const requestBody = { name: 'Rainforest Connection' }

const response = await request(app).patch(`/${org.id}`).send(requestBody)
Expand Down
53 changes: 51 additions & 2 deletions core/projects/bl/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const dao = require('../dao')
const { hasPermission, PROJECT, UPDATE } = require('../../roles/dao')
const { hasPermission, addRole, ORGANIZATION, PROJECT, UPDATE, CREATE, OWNER } = require('../../roles/dao')
const { sequelize } = require('../../_models')
const { ForbiddenError } = require('../../../common/error-handling/errors')
const arbimonService = require('../../_services/arbimon')
const { randomId } = require('../../../common/crypto/random')

/**
* Update project
Expand Down Expand Up @@ -34,6 +36,53 @@ async function update (id, project, options = {}) {
return result
}

/**
* Create project
* @param {Project} project
* @param {string} project.id
* @param {string} project.name
* @param {string} project.description
* @param {boolean} project.is_public
* @param {boolean} project.organization_id
* @param {integer} project.external_id
* @param {*} options
* @param {number} options.creatableById Create only if project is creatable by the given user id
* @param {string} options.requestSource Whether the request was sent from the Arbimon or not
* @param {string} options.idToken user jwt token
* @throws ForbiddenError when `creatableBy` user does not have create permission on the project
*/
async function create (params, options = {}) {
if (params.organizationId && options.creatableById) {
const allowed = await hasPermission(CREATE, options.creatableById, params.organizationId, ORGANIZATION)
if (!allowed) {
throw new ForbiddenError('You do not have permission to create project in this organization.')
}
}

const project = {
...params,
createdById: options.creatableById,
id: randomId()
}

return sequelize.transaction(async (transaction) => {
options.transaction = transaction
const result = await dao.create(project, options)
await addRole(result.createdById, OWNER, result.id, PROJECT, options)

if (arbimonService.isEnabled && options.requestSource !== 'arbimon') {
try {
const arbimonProject = await arbimonService.createProject(project, options.idToken)
project.externalId = arbimonProject.project_id
} catch (error) {
console.error(`Error creating project in Arbimon (project: ${project.id})`)
}
}
return result
})
}

module.exports = {
update
update,
create
}
15 changes: 14 additions & 1 deletion core/projects/create.int.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const request = require('supertest')
const routes = require('.')
const models = require('../_models')
const { expressApp, truncateNonBase } = require('../../common/testing/sequelize')
const { expressApp, truncateNonBase, seedValues } = require('../../common/testing/sequelize')

const app = expressApp()

Expand Down Expand Up @@ -39,4 +39,17 @@ describe('POST /projects', () => {
expect(response.statusCode).toBe(400)
expect(console.warn).toHaveBeenCalled()
})

test('UserProjectRoles row of Owner is created', async () => {
const requestBody = {
name: 'Test Project'
}

const response = await request(app).post('/').send(requestBody)
const id = response.header.location.replace('/projects/', '')
const userProjectRole = await models.UserProjectRole.findOne({ where: { project_id: id, user_id: seedValues.primaryUserId } })

expect(response.statusCode).toBe(201)
expect(userProjectRole.role_id).toBe(4)
})
})
Loading
Loading