Files
speckle-server/packages/server/modules/core/services/streams.js
T
2022-03-23 14:29:58 +02:00

272 lines
7.8 KiB
JavaScript

'use strict'
const crs = require('crypto-random-string')
const appRoot = require('app-root-path')
const knex = require(`${appRoot}/db/knex`)
const Streams = () => knex('streams')
const Acl = () => knex('stream_acl')
const debug = require('debug')
const { createBranch } = require('./branches')
module.exports = {
async createStream({ name, description, isPublic, ownerId }) {
let stream = {
id: crs({ length: 10 }),
name: name || generateStreamName(),
description: description || '',
isPublic: isPublic !== false,
updatedAt: knex.fn.now()
}
// Create the stream & set up permissions
let [{ id: streamId }] = await Streams().returning('id').insert(stream)
await Acl().insert({ userId: ownerId, resourceId: streamId, role: 'stream:owner' })
// Create a default main branch
await createBranch({
name: 'main',
description: 'default branch',
streamId: streamId,
authorId: ownerId
})
return streamId
},
async getStream({ streamId, userId }) {
let stream = await Streams().where({ id: streamId }).select('*').first()
if (!userId) return stream
let acl = await Acl().where({ resourceId: streamId, userId: userId }).select('role').first()
if (acl) stream.role = acl.role
return stream
},
async updateStream({ streamId, name, description, isPublic }) {
let [{ id }] = await Streams()
.returning('id')
.where({ id: streamId })
.update({ name, description, isPublic, updatedAt: knex.fn.now() })
return id
},
async grantPermissionsStream({ streamId, userId, role }) {
// upserts the existing role (sets a new one!)
// TODO: check if we're removing the last owner (ie, does the stream still have an owner after this operation)?
let query =
Acl().insert({ userId: userId, resourceId: streamId, role: role }).toString() +
' on conflict on constraint stream_acl_pkey do update set role=excluded.role'
await knex.raw(query)
// update stream updated at
await Streams().where({ id: streamId }).update({ updatedAt: knex.fn.now() })
return true
},
async revokePermissionsStream({ streamId, userId }) {
let streamAclEntriesCount = Acl().count({ resourceId: streamId })
// TODO: check if streamAclEntriesCount === 1 then throw big boo-boo (can't delete last ownership link)
if (streamAclEntriesCount === 1)
throw new Error('Stream has only one ownership link left - cannot revoke permissions.')
// TODO: below behaviour not correct. Flow:
// Count owners
// If owner count > 1, then proceed to delete, otherwise throw an error (can't delete last owner - delete stream)
let aclEntry = await Acl().where({ resourceId: streamId, userId: userId }).select('*').first()
if (aclEntry.role === 'stream:owner') {
let ownersCount = Acl().count({ resourceId: streamId, role: 'stream:owner' })
if (ownersCount === 1) throw new Error('Could not revoke permissions for user')
else {
await Acl().where({ resourceId: streamId, userId: userId }).del()
return true
}
}
let delCount = await Acl().where({ resourceId: streamId, userId: userId }).del()
if (delCount === 0) throw new Error('Could not revoke permissions for user')
// update stream updated at
await Streams().where({ id: streamId }).update({ updatedAt: knex.fn.now() })
return true
},
async deleteStream({ streamId }) {
debug('speckle:db')('Deleting stream ' + streamId)
// Delete stream commits (not automatically cascaded)
await knex.raw(
`
DELETE FROM commits WHERE id IN (
SELECT sc."commitId" FROM streams s
INNER JOIN stream_commits sc ON s.id = sc."streamId"
WHERE s.id = ?
)
`,
[streamId]
)
return await Streams().where({ id: streamId }).del()
},
async getUserStreams({ userId, limit, cursor, publicOnly, searchQuery }) {
limit = limit || 25
publicOnly = publicOnly !== false //defaults to true if not provided
let query = Acl()
.columns([
{ id: 'streams.id' },
'name',
'description',
'isPublic',
'createdAt',
'updatedAt',
'role'
])
.select()
.join('streams', 'stream_acl.resourceId', 'streams.id')
.where('stream_acl.userId', userId)
if (cursor) query.andWhere('streams.updatedAt', '<', cursor)
if (publicOnly) query.andWhere('streams.isPublic', true)
if (searchQuery)
query.andWhere(function () {
this.where('name', 'ILIKE', `%${searchQuery}%`)
.orWhere('description', 'ILIKE', `%${searchQuery}%`)
.orWhere('id', 'ILIKE', `%${searchQuery}%`) //potentially useless?
})
query.orderBy('streams.updatedAt', 'desc').limit(limit)
let rows = await query
return {
streams: rows,
cursor: rows.length > 0 ? rows[rows.length - 1].updatedAt.toISOString() : null
}
},
async getStreams({ offset, limit, orderBy, visibility, searchQuery }) {
let query = knex
.column('streams.*', knex.raw('coalesce(sum(pg_column_size(objects.data)),0) as size'))
.select()
.from('streams')
.leftJoin('objects', 'streams.id', 'objects.streamId')
.groupBy('streams.id')
let countQuery = Streams()
if (searchQuery) {
const whereFunc = function () {
this.where('streams.name', 'ILIKE', `%${searchQuery}%`).orWhere(
'streams.description',
'ILIKE',
`%${searchQuery}%`
)
}
query.where(whereFunc)
countQuery.where(whereFunc)
}
if (visibility && visibility !== 'all') {
if (!['private', 'public'].includes(visibility))
throw new Error('Stream visibility should be either private, public or all')
let isPublic = visibility === 'public'
const publicFunc = function () {
this.where({ isPublic })
}
query.andWhere(publicFunc)
countQuery.andWhere(publicFunc)
}
let [res] = await countQuery.count()
let count = parseInt(res.count)
if (!count) return { streams: [], totalCount: 0 }
orderBy = orderBy || 'updatedAt,desc'
let [columnName, order] = orderBy.split(',')
let rows = await query.orderBy(`${columnName}`, order).offset(offset).limit(limit)
return { streams: rows, totalCount: count }
},
async getUserStreamsCount({ userId, publicOnly, searchQuery }) {
publicOnly = publicOnly !== false //defaults to true if not provided
let query = Acl()
.count()
.join('streams', 'stream_acl.resourceId', 'streams.id')
.where({ userId: userId })
if (publicOnly) query.andWhere('streams.isPublic', true)
if (searchQuery)
query.andWhere(function () {
this.where('name', 'ILIKE', `%${searchQuery}%`)
.orWhere('description', 'ILIKE', `%${searchQuery}%`)
.orWhere('id', 'ILIKE', `%${searchQuery}%`) //potentially useless?
})
let [res] = await query
return parseInt(res.count)
},
async getStreamUsers({ streamId }) {
let query = Acl()
.columns({ role: 'stream_acl.role' }, 'id', 'name', 'company', 'avatar')
.select()
.where({ resourceId: streamId })
.rightJoin('users', { 'users.id': 'stream_acl.userId' })
.select('stream_acl.role', 'name', 'id', 'company', 'avatar')
.orderBy('stream_acl.role')
return await query
}
}
const adjectives = [
'Tall',
'Curved',
'Stacked',
'Purple',
'Pink',
'Rectangular',
'Circular',
'Oval',
'Shiny',
'Speckled',
'Blue',
'Stretched',
'Round',
'Spherical',
'Majestic',
'Symmetrical'
]
const nouns = [
'Building',
'House',
'Treehouse',
'Tower',
'Tunnel',
'Bridge',
'Pyramid',
'Structure',
'Edifice',
'Palace',
'Castle',
'Villa'
]
const generateStreamName = () => {
return `${adjectives[Math.floor(Math.random() * adjectives.length)]} ${
nouns[Math.floor(Math.random() * nouns.length)]
}`
}