mirror of
https://github.com/sasjs/server.git
synced 2025-12-11 19:44:35 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecd8ed9032 | ||
|
|
a8d89ff1d6 | ||
|
|
8702a4e8fd | ||
|
|
5f06132ece | ||
|
|
56c80b0979 | ||
|
|
c19a20c1d4 | ||
|
|
f8eaadae7b | ||
| 90e0973a7f | |||
| 869a13fc69 | |||
| 6b0b94ad38 |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,3 +1,17 @@
|
|||||||
|
## [0.6.1](https://github.com/sasjs/server/compare/v0.6.0...v0.6.1) (2022-06-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* home page wording. Using fix to force previous change through.. ([8702a4e](https://github.com/sasjs/server/commit/8702a4e8fd1bbfaf4f426b75e8b85a87ede0e0b0))
|
||||||
|
|
||||||
|
# [0.6.0](https://github.com/sasjs/server/compare/v0.5.0...v0.6.0) (2022-06-16)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* get group by group name ([6b0b94a](https://github.com/sasjs/server/commit/6b0b94ad38215ae58e62279a4f73ac3ed2d9d0e8))
|
||||||
|
|
||||||
# [0.5.0](https://github.com/sasjs/server/compare/v0.4.2...v0.5.0) (2022-06-16)
|
# [0.5.0](https://github.com/sasjs/server/compare/v0.4.2...v0.5.0) (2022-06-16)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -425,6 +425,27 @@ components:
|
|||||||
- description
|
- description
|
||||||
type: object
|
type: object
|
||||||
additionalProperties: false
|
additionalProperties: false
|
||||||
|
_LeanDocument__LeanDocument_T__:
|
||||||
|
properties: {}
|
||||||
|
type: object
|
||||||
|
Pick__LeanDocument_T_.Exclude_keyof_LeanDocument_T_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested__:
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
description: 'The string version of this documents _id.'
|
||||||
|
_id:
|
||||||
|
$ref: '#/components/schemas/_LeanDocument__LeanDocument_T__'
|
||||||
|
description: 'This documents _id.'
|
||||||
|
__v:
|
||||||
|
description: 'This documents __v.'
|
||||||
|
type: object
|
||||||
|
description: 'From T, pick a set of properties whose keys are in the union K'
|
||||||
|
Omit__LeanDocument_this_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested_:
|
||||||
|
$ref: '#/components/schemas/Pick__LeanDocument_T_.Exclude_keyof_LeanDocument_T_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested__'
|
||||||
|
description: 'Construct a type with the properties of T except for those in type K.'
|
||||||
|
LeanDocument_this_:
|
||||||
|
$ref: '#/components/schemas/Omit__LeanDocument_this_.Exclude_keyofDocument._id-or-id-or-__v_-or-%24isSingleNested_'
|
||||||
|
IGroup:
|
||||||
|
$ref: '#/components/schemas/LeanDocument_this_'
|
||||||
InfoResponse:
|
InfoResponse:
|
||||||
properties:
|
properties:
|
||||||
mode:
|
mode:
|
||||||
@@ -1216,6 +1237,30 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/GroupPayload'
|
$ref: '#/components/schemas/GroupPayload'
|
||||||
|
'/SASjsApi/group/by/groupname/{name}':
|
||||||
|
get:
|
||||||
|
operationId: GetGroupByGroupName
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GroupDetailsResponse'
|
||||||
|
summary: 'Get list of members of a group (userName). All users can request this.'
|
||||||
|
tags:
|
||||||
|
- Group
|
||||||
|
security:
|
||||||
|
-
|
||||||
|
bearerAuth: []
|
||||||
|
parameters:
|
||||||
|
-
|
||||||
|
description: 'The group''s name'
|
||||||
|
in: path
|
||||||
|
name: name
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
'/SASjsApi/group/{groupId}':
|
'/SASjsApi/group/{groupId}':
|
||||||
get:
|
get:
|
||||||
operationId: GetGroup
|
operationId: GetGroup
|
||||||
@@ -1245,8 +1290,14 @@ paths:
|
|||||||
delete:
|
delete:
|
||||||
operationId: DeleteGroup
|
operationId: DeleteGroup
|
||||||
responses:
|
responses:
|
||||||
'204':
|
'200':
|
||||||
description: 'No content'
|
description: Ok
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- {$ref: '#/components/schemas/IGroup'}
|
||||||
|
- {properties: {_id: {}}, required: [_id], type: object}
|
||||||
summary: 'Delete a group. Admin task only.'
|
summary: 'Delete a group. Admin task only.'
|
||||||
tags:
|
tags:
|
||||||
- Group
|
- Group
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ interface GroupDetailsResponse {
|
|||||||
users: UserResponse[]
|
users: UserResponse[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetGroupBy {
|
||||||
|
groupId?: number
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
|
||||||
@Security('bearerAuth')
|
@Security('bearerAuth')
|
||||||
@Route('SASjsApi/group')
|
@Route('SASjsApi/group')
|
||||||
@Tags('Group')
|
@Tags('Group')
|
||||||
@@ -66,6 +71,18 @@ export class GroupController {
|
|||||||
return createGroup(body)
|
return createGroup(body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Get list of members of a group (userName). All users can request this.
|
||||||
|
* @param name The group's name
|
||||||
|
* @example dcgroup
|
||||||
|
*/
|
||||||
|
@Get('by/groupname/{name}')
|
||||||
|
public async getGroupByGroupName(
|
||||||
|
@Path() name: string
|
||||||
|
): Promise<GroupDetailsResponse> {
|
||||||
|
return getGroup({ name })
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Get list of members of a group (userName). All users can request this.
|
* @summary Get list of members of a group (userName). All users can request this.
|
||||||
* @param groupId The group's identifier
|
* @param groupId The group's identifier
|
||||||
@@ -75,7 +92,7 @@ export class GroupController {
|
|||||||
public async getGroup(
|
public async getGroup(
|
||||||
@Path() groupId: number
|
@Path() groupId: number
|
||||||
): Promise<GroupDetailsResponse> {
|
): Promise<GroupDetailsResponse> {
|
||||||
return getGroup(groupId)
|
return getGroup({ groupId })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -129,9 +146,13 @@ export class GroupController {
|
|||||||
*/
|
*/
|
||||||
@Delete('{groupId}')
|
@Delete('{groupId}')
|
||||||
public async deleteGroup(@Path() groupId: number) {
|
public async deleteGroup(@Path() groupId: number) {
|
||||||
const { deletedCount } = await Group.deleteOne({ groupId })
|
const group = await Group.findOne({ groupId })
|
||||||
if (deletedCount) return
|
if (group) return await group.remove()
|
||||||
throw new Error('No Group deleted!')
|
throw {
|
||||||
|
code: 404,
|
||||||
|
status: 'Not Found',
|
||||||
|
message: 'Group not found.'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +166,15 @@ const createGroup = async ({
|
|||||||
description,
|
description,
|
||||||
isActive
|
isActive
|
||||||
}: GroupPayload): Promise<GroupDetailsResponse> => {
|
}: GroupPayload): Promise<GroupDetailsResponse> => {
|
||||||
|
// Checking if user is already in the database
|
||||||
|
const groupnameExist = await Group.findOne({ name })
|
||||||
|
if (groupnameExist)
|
||||||
|
throw {
|
||||||
|
code: 409,
|
||||||
|
status: 'Conflict',
|
||||||
|
message: 'Group name already exists.'
|
||||||
|
}
|
||||||
|
|
||||||
const group = new Group({
|
const group = new Group({
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
@@ -162,15 +192,20 @@ const createGroup = async ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getGroup = async (groupId: number): Promise<GroupDetailsResponse> => {
|
const getGroup = async (findBy: GetGroupBy): Promise<GroupDetailsResponse> => {
|
||||||
const group = (await Group.findOne(
|
const group = (await Group.findOne(
|
||||||
{ groupId },
|
findBy,
|
||||||
'groupId name description isActive users -_id'
|
'groupId name description isActive users -_id'
|
||||||
).populate(
|
).populate(
|
||||||
'users',
|
'users',
|
||||||
'id username displayName -_id'
|
'id username displayName -_id'
|
||||||
)) as unknown as GroupDetailsResponse
|
)) as unknown as GroupDetailsResponse
|
||||||
if (!group) throw new Error('Group not found.')
|
if (!group)
|
||||||
|
throw {
|
||||||
|
code: 404,
|
||||||
|
status: 'Not Found',
|
||||||
|
message: 'Group not found.'
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
groupId: group.groupId,
|
groupId: group.groupId,
|
||||||
@@ -199,16 +234,31 @@ const updateUsersListInGroup = async (
|
|||||||
action: 'addUser' | 'removeUser'
|
action: 'addUser' | 'removeUser'
|
||||||
): Promise<GroupDetailsResponse> => {
|
): Promise<GroupDetailsResponse> => {
|
||||||
const group = await Group.findOne({ groupId })
|
const group = await Group.findOne({ groupId })
|
||||||
if (!group) throw new Error('Group not found.')
|
if (!group)
|
||||||
|
throw {
|
||||||
|
code: 404,
|
||||||
|
status: 'Not Found',
|
||||||
|
message: 'Group not found.'
|
||||||
|
}
|
||||||
|
|
||||||
const user = await User.findOne({ id: userId })
|
const user = await User.findOne({ id: userId })
|
||||||
if (!user) throw new Error('User not found.')
|
if (!user)
|
||||||
|
throw {
|
||||||
|
code: 404,
|
||||||
|
status: 'Not Found',
|
||||||
|
message: 'User not found.'
|
||||||
|
}
|
||||||
|
|
||||||
const updatedGroup = (action === 'addUser'
|
const updatedGroup = (action === 'addUser'
|
||||||
? await group.addUser(user._id)
|
? await group.addUser(user._id)
|
||||||
: await group.removeUser(user._id)) as unknown as GroupDetailsResponse
|
: await group.removeUser(user._id)) as unknown as GroupDetailsResponse
|
||||||
|
|
||||||
if (!updatedGroup) throw new Error('Unable to update group')
|
if (!updatedGroup)
|
||||||
|
throw {
|
||||||
|
code: 400,
|
||||||
|
status: 'Bad Request',
|
||||||
|
message: 'Unable to update group.'
|
||||||
|
}
|
||||||
|
|
||||||
if (action === 'addUser') user.addGroup(group._id)
|
if (action === 'addUser') user.addGroup(group._id)
|
||||||
else user.removeGroup(group._id)
|
else user.removeGroup(group._id)
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ ${autoExecContent}`
|
|||||||
// however we also need a promise so that we can update the
|
// however we also need a promise so that we can update the
|
||||||
// session array to say that it has (eventually) finished.
|
// session array to say that it has (eventually) finished.
|
||||||
|
|
||||||
|
// Additional windows specific options to avoid the desktop popups.
|
||||||
|
|
||||||
execFilePromise(process.sasLoc, [
|
execFilePromise(process.sasLoc, [
|
||||||
'-SYSIN',
|
'-SYSIN',
|
||||||
codePath,
|
codePath,
|
||||||
@@ -95,7 +97,9 @@ ${autoExecContent}`
|
|||||||
autoExecPath,
|
autoExecPath,
|
||||||
'-ENCODING',
|
'-ENCODING',
|
||||||
'UTF-8',
|
'UTF-8',
|
||||||
process.platform === 'win32' ? '-nosplash' : ''
|
process.platform === 'win32' ? '-nosplash' : '',
|
||||||
|
process.platform === 'win32' ? '-icon' : '',
|
||||||
|
process.platform === 'win32' ? '-nologo' : ''
|
||||||
])
|
])
|
||||||
.then(() => {
|
.then(() => {
|
||||||
session.completed = true
|
session.completed = true
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import mongoose, { Schema, model, Document, Model } from 'mongoose'
|
import mongoose, { Schema, model, Document, Model } from 'mongoose'
|
||||||
|
import User from './User'
|
||||||
const AutoIncrement = require('mongoose-sequence')(mongoose)
|
const AutoIncrement = require('mongoose-sequence')(mongoose)
|
||||||
|
|
||||||
export interface GroupPayload {
|
export interface GroupPayload {
|
||||||
@@ -34,7 +35,8 @@ interface IGroupModel extends Model<IGroup> {}
|
|||||||
const groupSchema = new Schema<IGroupDocument>({
|
const groupSchema = new Schema<IGroupDocument>({
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true,
|
||||||
|
unique: true
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -46,6 +48,7 @@ const groupSchema = new Schema<IGroupDocument>({
|
|||||||
},
|
},
|
||||||
users: [{ type: Schema.Types.ObjectId, ref: 'User' }]
|
users: [{ type: Schema.Types.ObjectId, ref: 'User' }]
|
||||||
})
|
})
|
||||||
|
|
||||||
groupSchema.plugin(AutoIncrement, { inc_field: 'groupId' })
|
groupSchema.plugin(AutoIncrement, { inc_field: 'groupId' })
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
@@ -55,6 +58,17 @@ groupSchema.post('save', function (group: IGroup, next: Function) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// pre remove hook to remove all references of group from users
|
||||||
|
groupSchema.pre('remove', async function () {
|
||||||
|
const userIds = this.users
|
||||||
|
await Promise.all(
|
||||||
|
userIds.map(async (userId) => {
|
||||||
|
const user = await User.findById(userId)
|
||||||
|
user?.removeGroup(this._id)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
// Instance Methods
|
// Instance Methods
|
||||||
groupSchema.method(
|
groupSchema.method(
|
||||||
'addUser',
|
'addUser',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import { GroupController } from '../../controllers/'
|
import { GroupController } from '../../controllers/'
|
||||||
import { authenticateAccessToken, verifyAdmin } from '../../middlewares'
|
import { authenticateAccessToken, verifyAdmin } from '../../middlewares'
|
||||||
import { registerGroupValidation } from '../../utils'
|
import { getGroupValidation, registerGroupValidation } from '../../utils'
|
||||||
|
|
||||||
const groupRouter = express.Router()
|
const groupRouter = express.Router()
|
||||||
|
|
||||||
@@ -18,7 +18,11 @@ groupRouter.post(
|
|||||||
const response = await controller.createGroup(body)
|
const response = await controller.createGroup(body)
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(403).send(err.toString())
|
const statusCode = err.code
|
||||||
|
|
||||||
|
delete err.code
|
||||||
|
|
||||||
|
res.status(statusCode).send(err.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -29,7 +33,11 @@ groupRouter.get('/', authenticateAccessToken, async (req, res) => {
|
|||||||
const response = await controller.getAllGroups()
|
const response = await controller.getAllGroups()
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(403).send(err.toString())
|
const statusCode = err.code
|
||||||
|
|
||||||
|
delete err.code
|
||||||
|
|
||||||
|
res.status(statusCode).send(err.message)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -41,10 +49,37 @@ groupRouter.get('/:groupId', authenticateAccessToken, async (req, res) => {
|
|||||||
const response = await controller.getGroup(parseInt(groupId))
|
const response = await controller.getGroup(parseInt(groupId))
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(403).send(err.toString())
|
const statusCode = err.code
|
||||||
|
|
||||||
|
delete err.code
|
||||||
|
|
||||||
|
res.status(statusCode).send(err.message)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
groupRouter.get(
|
||||||
|
'/by/groupname/:name',
|
||||||
|
authenticateAccessToken,
|
||||||
|
async (req, res) => {
|
||||||
|
const { error, value: params } = getGroupValidation(req.params)
|
||||||
|
if (error) return res.status(400).send(error.details[0].message)
|
||||||
|
|
||||||
|
const { name } = params
|
||||||
|
|
||||||
|
const controller = new GroupController()
|
||||||
|
try {
|
||||||
|
const response = await controller.getGroupByGroupName(name)
|
||||||
|
res.send(response)
|
||||||
|
} catch (err: any) {
|
||||||
|
const statusCode = err.code
|
||||||
|
|
||||||
|
delete err.code
|
||||||
|
|
||||||
|
res.status(statusCode).send(err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
groupRouter.post(
|
groupRouter.post(
|
||||||
'/:groupId/:userId',
|
'/:groupId/:userId',
|
||||||
authenticateAccessToken,
|
authenticateAccessToken,
|
||||||
@@ -60,7 +95,11 @@ groupRouter.post(
|
|||||||
)
|
)
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(403).send(err.toString())
|
const statusCode = err.code
|
||||||
|
|
||||||
|
delete err.code
|
||||||
|
|
||||||
|
res.status(statusCode).send(err.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -80,7 +119,11 @@ groupRouter.delete(
|
|||||||
)
|
)
|
||||||
res.send(response)
|
res.send(response)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(403).send(err.toString())
|
const statusCode = err.code
|
||||||
|
|
||||||
|
delete err.code
|
||||||
|
|
||||||
|
res.status(statusCode).send(err.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -97,7 +140,11 @@ groupRouter.delete(
|
|||||||
await controller.deleteGroup(parseInt(groupId))
|
await controller.deleteGroup(parseInt(groupId))
|
||||||
res.status(200).send('Group Deleted!')
|
res.status(200).send('Group Deleted!')
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
res.status(403).send(err.toString())
|
const statusCode = err.code
|
||||||
|
|
||||||
|
delete err.code
|
||||||
|
|
||||||
|
res.status(statusCode).send(err.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const user = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const group = {
|
const group = {
|
||||||
name: 'DCGroup1',
|
name: 'dcgroup1',
|
||||||
description: 'DC group for testing purposes.'
|
description: 'DC group for testing purposes.'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +70,32 @@ describe('group', () => {
|
|||||||
expect(res.body.users).toEqual([])
|
expect(res.body.users).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should respond with Conflict when group already exists with same name', async () => {
|
||||||
|
await groupController.createGroup(group)
|
||||||
|
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/SASjsApi/group')
|
||||||
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
|
.send(group)
|
||||||
|
.expect(409)
|
||||||
|
|
||||||
|
expect(res.text).toEqual('Group name already exists.')
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Bad Request when group name does not match the group name schema', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.post('/SASjsApi/group')
|
||||||
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
|
.send({ ...group, name: 'Wrong Group Name' })
|
||||||
|
.expect(400)
|
||||||
|
|
||||||
|
expect(res.text).toEqual(
|
||||||
|
'"name" must only contain alpha-numeric characters'
|
||||||
|
)
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
it('should respond with Unauthorized if access token is not present', async () => {
|
it('should respond with Unauthorized if access token is not present', async () => {
|
||||||
const res = await request(app).post('/SASjsApi/group').send().expect(401)
|
const res = await request(app).post('/SASjsApi/group').send().expect(401)
|
||||||
|
|
||||||
@@ -125,14 +151,51 @@ describe('group', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Forbidden if groupId is incorrect', async () => {
|
it(`should delete group's reference from users' groups array`, async () => {
|
||||||
|
const dbGroup = await groupController.createGroup(group)
|
||||||
|
const dbUser1 = await userController.createUser({
|
||||||
|
...user,
|
||||||
|
username: 'deletegroup1'
|
||||||
|
})
|
||||||
|
const dbUser2 = await userController.createUser({
|
||||||
|
...user,
|
||||||
|
username: 'deletegroup2'
|
||||||
|
})
|
||||||
|
|
||||||
|
await groupController.addUserToGroup(dbGroup.groupId, dbUser1.id)
|
||||||
|
await groupController.addUserToGroup(dbGroup.groupId, dbUser2.id)
|
||||||
|
|
||||||
|
await request(app)
|
||||||
|
.delete(`/SASjsApi/group/${dbGroup.groupId}`)
|
||||||
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
|
.send()
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
const res1 = await request(app)
|
||||||
|
.get(`/SASjsApi/user/${dbUser1.id}`)
|
||||||
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
|
.send()
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(res1.body.groups).toEqual([])
|
||||||
|
|
||||||
|
const res2 = await request(app)
|
||||||
|
.get(`/SASjsApi/user/${dbUser2.id}`)
|
||||||
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
|
.send()
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(res2.body.groups).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Not Found if groupId is incorrect', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/group/1234`)
|
.delete(`/SASjsApi/group/1234`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(403)
|
.expect(404)
|
||||||
|
|
||||||
expect(res.text).toEqual('Error: No Group deleted!')
|
expect(res.text).toEqual('Group not found.')
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -216,16 +279,76 @@ describe('group', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Forbidden if groupId is incorrect', async () => {
|
it('should respond with Not Found if groupId is incorrect', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.get('/SASjsApi/group/1234')
|
.get('/SASjsApi/group/1234')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(403)
|
.expect(404)
|
||||||
|
|
||||||
expect(res.text).toEqual('Error: Group not found.')
|
expect(res.text).toEqual('Group not found.')
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('by group name', () => {
|
||||||
|
it('should respond with group', async () => {
|
||||||
|
const { name } = await groupController.createGroup(group)
|
||||||
|
|
||||||
|
const res = await request(app)
|
||||||
|
.get(`/SASjsApi/group/by/groupname/${name}`)
|
||||||
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
|
.send()
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(res.body.groupId).toBeTruthy()
|
||||||
|
expect(res.body.name).toEqual(group.name)
|
||||||
|
expect(res.body.description).toEqual(group.description)
|
||||||
|
expect(res.body.isActive).toEqual(true)
|
||||||
|
expect(res.body.users).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with group when access token is not of an admin account', async () => {
|
||||||
|
const accessToken = await generateSaveTokenAndCreateUser({
|
||||||
|
...user,
|
||||||
|
username: 'getbyname' + user.username
|
||||||
|
})
|
||||||
|
|
||||||
|
const { name } = await groupController.createGroup(group)
|
||||||
|
|
||||||
|
const res = await request(app)
|
||||||
|
.get(`/SASjsApi/group/by/groupname/${name}`)
|
||||||
|
.auth(accessToken, { type: 'bearer' })
|
||||||
|
.send()
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(res.body.groupId).toBeTruthy()
|
||||||
|
expect(res.body.name).toEqual(group.name)
|
||||||
|
expect(res.body.description).toEqual(group.description)
|
||||||
|
expect(res.body.isActive).toEqual(true)
|
||||||
|
expect(res.body.users).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Unauthorized if access token is not present', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.get('/SASjsApi/group/by/groupname/dcgroup')
|
||||||
|
.send()
|
||||||
|
.expect(401)
|
||||||
|
|
||||||
|
expect(res.text).toEqual('Unauthorized')
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should respond with Not Found if groupname is incorrect', async () => {
|
||||||
|
const res = await request(app)
|
||||||
|
.get('/SASjsApi/group/by/groupname/randomCharacters')
|
||||||
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
|
.send()
|
||||||
|
.expect(404)
|
||||||
|
|
||||||
|
expect(res.text).toEqual('Group not found.')
|
||||||
|
expect(res.body).toEqual({})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('getAll', () => {
|
describe('getAll', () => {
|
||||||
@@ -245,8 +368,8 @@ describe('group', () => {
|
|||||||
expect(res.body).toEqual([
|
expect(res.body).toEqual([
|
||||||
{
|
{
|
||||||
groupId: expect.anything(),
|
groupId: expect.anything(),
|
||||||
name: 'DCGroup1',
|
name: group.name,
|
||||||
description: 'DC group for testing purposes.'
|
description: group.description
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
@@ -267,8 +390,8 @@ describe('group', () => {
|
|||||||
expect(res.body).toEqual([
|
expect(res.body).toEqual([
|
||||||
{
|
{
|
||||||
groupId: expect.anything(),
|
groupId: expect.anything(),
|
||||||
name: 'DCGroup1',
|
name: group.name,
|
||||||
description: 'DC group for testing purposes.'
|
description: group.description
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
@@ -309,6 +432,34 @@ describe('group', () => {
|
|||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it(`should add group to user's groups array`, async () => {
|
||||||
|
const dbGroup = await groupController.createGroup(group)
|
||||||
|
const dbUser = await userController.createUser({
|
||||||
|
...user,
|
||||||
|
username: 'addUserToGroup'
|
||||||
|
})
|
||||||
|
|
||||||
|
await request(app)
|
||||||
|
.post(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
|
||||||
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
|
.send()
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
const res = await request(app)
|
||||||
|
.get(`/SASjsApi/user/${dbUser.id}`)
|
||||||
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
|
.send()
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(res.body.groups).toEqual([
|
||||||
|
{
|
||||||
|
groupId: expect.anything(),
|
||||||
|
name: group.name,
|
||||||
|
description: group.description
|
||||||
|
}
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it('should respond with group without duplicating user', async () => {
|
it('should respond with group without duplicating user', async () => {
|
||||||
const dbGroup = await groupController.createGroup(group)
|
const dbGroup = await groupController.createGroup(group)
|
||||||
const dbUser = await userController.createUser({
|
const dbUser = await userController.createUser({
|
||||||
@@ -362,26 +513,26 @@ describe('group', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Forbidden if groupId is incorrect', async () => {
|
it('should respond with Not Found if groupId is incorrect', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post('/SASjsApi/group/123/123')
|
.post('/SASjsApi/group/123/123')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(403)
|
.expect(404)
|
||||||
|
|
||||||
expect(res.text).toEqual('Error: Group not found.')
|
expect(res.text).toEqual('Group not found.')
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Forbidden if userId is incorrect', async () => {
|
it('should respond with Not Found if userId is incorrect', async () => {
|
||||||
const dbGroup = await groupController.createGroup(group)
|
const dbGroup = await groupController.createGroup(group)
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post(`/SASjsApi/group/${dbGroup.groupId}/123`)
|
.post(`/SASjsApi/group/${dbGroup.groupId}/123`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(403)
|
.expect(404)
|
||||||
|
|
||||||
expect(res.text).toEqual('Error: User not found.')
|
expect(res.text).toEqual('User not found.')
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -412,6 +563,29 @@ describe('group', () => {
|
|||||||
expect(res.body.users).toEqual([])
|
expect(res.body.users).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it(`should remove group from user's groups array`, async () => {
|
||||||
|
const dbGroup = await groupController.createGroup(group)
|
||||||
|
const dbUser = await userController.createUser({
|
||||||
|
...user,
|
||||||
|
username: 'removeGroupFromUser'
|
||||||
|
})
|
||||||
|
await groupController.addUserToGroup(dbGroup.groupId, dbUser.id)
|
||||||
|
|
||||||
|
await request(app)
|
||||||
|
.delete(`/SASjsApi/group/${dbGroup.groupId}/${dbUser.id}`)
|
||||||
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
|
.send()
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
const res = await request(app)
|
||||||
|
.get(`/SASjsApi/user/${dbUser.id}`)
|
||||||
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
|
.send()
|
||||||
|
.expect(200)
|
||||||
|
|
||||||
|
expect(res.body.groups).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
it('should respond with Unauthorized if access token is not present', async () => {
|
it('should respond with Unauthorized if access token is not present', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete('/SASjsApi/group/123/123')
|
.delete('/SASjsApi/group/123/123')
|
||||||
@@ -438,26 +612,26 @@ describe('group', () => {
|
|||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Forbidden if groupId is incorrect', async () => {
|
it('should respond with Not Found if groupId is incorrect', async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete('/SASjsApi/group/123/123')
|
.delete('/SASjsApi/group/123/123')
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(403)
|
.expect(404)
|
||||||
|
|
||||||
expect(res.text).toEqual('Error: Group not found.')
|
expect(res.text).toEqual('Group not found.')
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should respond with Forbidden if userId is incorrect', async () => {
|
it('should respond with Not Found if userId is incorrect', async () => {
|
||||||
const dbGroup = await groupController.createGroup(group)
|
const dbGroup = await groupController.createGroup(group)
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.delete(`/SASjsApi/group/${dbGroup.groupId}/123`)
|
.delete(`/SASjsApi/group/${dbGroup.groupId}/123`)
|
||||||
.auth(adminAccessToken, { type: 'bearer' })
|
.auth(adminAccessToken, { type: 'bearer' })
|
||||||
.send()
|
.send()
|
||||||
.expect(403)
|
.expect(404)
|
||||||
|
|
||||||
expect(res.text).toEqual('Error: User not found.')
|
expect(res.text).toEqual('User not found.')
|
||||||
expect(res.body).toEqual({})
|
expect(res.body).toEqual({})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import Joi from 'joi'
|
|||||||
|
|
||||||
const usernameSchema = Joi.string().lowercase().alphanum().min(3).max(16)
|
const usernameSchema = Joi.string().lowercase().alphanum().min(3).max(16)
|
||||||
const passwordSchema = Joi.string().min(6).max(1024)
|
const passwordSchema = Joi.string().min(6).max(1024)
|
||||||
|
const groupnameSchema = Joi.string().lowercase().alphanum().min(3).max(16)
|
||||||
|
|
||||||
export const blockFileRegex = /\.(exe|sh|htaccess)$/i
|
export const blockFileRegex = /\.(exe|sh|htaccess)$/i
|
||||||
|
|
||||||
@@ -29,11 +30,16 @@ export const tokenValidation = (data: any): Joi.ValidationResult =>
|
|||||||
|
|
||||||
export const registerGroupValidation = (data: any): Joi.ValidationResult =>
|
export const registerGroupValidation = (data: any): Joi.ValidationResult =>
|
||||||
Joi.object({
|
Joi.object({
|
||||||
name: Joi.string().min(6).required(),
|
name: groupnameSchema.required(),
|
||||||
description: Joi.string(),
|
description: Joi.string(),
|
||||||
isActive: Joi.boolean()
|
isActive: Joi.boolean()
|
||||||
}).validate(data)
|
}).validate(data)
|
||||||
|
|
||||||
|
export const getGroupValidation = (data: any): Joi.ValidationResult =>
|
||||||
|
Joi.object({
|
||||||
|
name: groupnameSchema.required()
|
||||||
|
}).validate(data)
|
||||||
|
|
||||||
export const registerUserValidation = (data: any): Joi.ValidationResult =>
|
export const registerUserValidation = (data: any): Joi.ValidationResult =>
|
||||||
Joi.object({
|
Joi.object({
|
||||||
displayName: Joi.string().min(6).required(),
|
displayName: Joi.string().min(6).required(),
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ const Home = () => {
|
|||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<h2>Welcome to SASjs Server!</h2>
|
<h2>Welcome to SASjs Server!</h2>
|
||||||
<p>
|
<p>
|
||||||
This portal provides an interface for executing Stored Programs (drive)
|
SASjs Server provides a REST interface for executing Stored Programs
|
||||||
and ad hoc code (studio) against a SAS executable. The source code is
|
and ad hoc code (studio) against SAS and JS executables. The source is
|
||||||
available on{' '}
|
available on{' '}
|
||||||
<a
|
<a
|
||||||
href="https://github.com/sasjs/server"
|
href="https://github.com/sasjs/server"
|
||||||
|
|||||||
Reference in New Issue
Block a user