mirror of
https://github.com/sasjs/server.git
synced 2025-12-10 19:34:34 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a61adbcac2 | ||
|
|
12000f4fc7 | ||
| 73792fb574 | |||
|
|
c08cfcbc38 | ||
|
|
8d38d5ac64 | ||
| e08bbcc543 |
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,3 +1,17 @@
|
||||
## [0.4.2](https://github.com/sasjs/server/compare/v0.4.1...v0.4.2) (2022-06-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* appStream redesign ([73792fb](https://github.com/sasjs/server/commit/73792fb574c90bd280c4324e0b41c6fee7d572b6))
|
||||
|
||||
## [0.4.1](https://github.com/sasjs/server/compare/v0.4.0...v0.4.1) (2022-06-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add/remove group to User when adding/removing user from group and return group membership on getting user ([e08bbcc](https://github.com/sasjs/server/commit/e08bbcc5435cbabaee40a41a7fb667d4a1f078e6))
|
||||
|
||||
# [0.4.0](https://github.com/sasjs/server/compare/v0.3.10...v0.4.0) (2022-06-14)
|
||||
|
||||
|
||||
|
||||
@@ -310,6 +310,21 @@ components:
|
||||
- displayName
|
||||
type: object
|
||||
additionalProperties: false
|
||||
GroupResponse:
|
||||
properties:
|
||||
groupId:
|
||||
type: number
|
||||
format: double
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
required:
|
||||
- groupId
|
||||
- name
|
||||
- description
|
||||
type: object
|
||||
additionalProperties: false
|
||||
UserDetailsResponse:
|
||||
properties:
|
||||
id:
|
||||
@@ -325,6 +340,10 @@ components:
|
||||
type: boolean
|
||||
autoExec:
|
||||
type: string
|
||||
groups:
|
||||
items:
|
||||
$ref: '#/components/schemas/GroupResponse'
|
||||
type: array
|
||||
required:
|
||||
- id
|
||||
- displayName
|
||||
@@ -364,21 +383,6 @@ components:
|
||||
- password
|
||||
type: object
|
||||
additionalProperties: false
|
||||
GroupResponse:
|
||||
properties:
|
||||
groupId:
|
||||
type: number
|
||||
format: double
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
required:
|
||||
- groupId
|
||||
- name
|
||||
- description
|
||||
type: object
|
||||
additionalProperties: false
|
||||
GroupDetailsResponse:
|
||||
properties:
|
||||
groupId:
|
||||
|
||||
@@ -14,7 +14,7 @@ import Group, { GroupPayload } from '../model/Group'
|
||||
import User from '../model/User'
|
||||
import { UserResponse } from './user'
|
||||
|
||||
interface GroupResponse {
|
||||
export interface GroupResponse {
|
||||
groupId: number
|
||||
name: string
|
||||
description: string
|
||||
@@ -210,6 +210,9 @@ const updateUsersListInGroup = async (
|
||||
|
||||
if (!updatedGroup) throw new Error('Unable to update group')
|
||||
|
||||
if (action === 'addUser') user.addGroup(group._id)
|
||||
else user.removeGroup(group._id)
|
||||
|
||||
return {
|
||||
groupId: updatedGroup.groupId,
|
||||
name: updatedGroup.name,
|
||||
|
||||
@@ -18,6 +18,7 @@ import { desktopUser } from '../middlewares'
|
||||
|
||||
import User, { UserPayload } from '../model/User'
|
||||
import { getUserAutoExec, updateUserAutoExec, ModeType } from '../utils'
|
||||
import { GroupResponse } from './group'
|
||||
|
||||
export interface UserResponse {
|
||||
id: number
|
||||
@@ -32,6 +33,7 @@ interface UserDetailsResponse {
|
||||
isActive: boolean
|
||||
isAdmin: boolean
|
||||
autoExec?: string
|
||||
groups?: GroupResponse[]
|
||||
}
|
||||
|
||||
@Security('bearerAuth')
|
||||
@@ -242,7 +244,13 @@ const getUser = async (
|
||||
findBy: GetUserBy,
|
||||
getAutoExec: boolean
|
||||
): Promise<UserDetailsResponse> => {
|
||||
const user = await User.findOne(findBy)
|
||||
const user = (await User.findOne(
|
||||
findBy,
|
||||
`id displayName username isActive isAdmin autoExec -_id`
|
||||
).populate(
|
||||
'groups',
|
||||
'groupId name description -_id'
|
||||
)) as unknown as UserDetailsResponse
|
||||
|
||||
if (!user) throw new Error('User is not found.')
|
||||
|
||||
@@ -252,7 +260,8 @@ const getUser = async (
|
||||
username: user.username,
|
||||
isActive: user.isActive,
|
||||
isAdmin: user.isAdmin,
|
||||
autoExec: getAutoExec ? user.autoExec ?? '' : undefined
|
||||
autoExec: getAutoExec ? user.autoExec ?? '' : undefined,
|
||||
groups: user.groups
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ interface IUserDocument extends UserPayload, Document {
|
||||
|
||||
interface IUser extends IUserDocument {
|
||||
comparePassword(password: string): boolean
|
||||
addGroup(groupObjectId: Schema.Types.ObjectId): Promise<IUser>
|
||||
removeGroup(groupObjectId: Schema.Types.ObjectId): Promise<IUser>
|
||||
}
|
||||
interface IUserModel extends Model<IUser> {
|
||||
hashPassword(password: string): string
|
||||
@@ -106,6 +108,28 @@ userSchema.method('comparePassword', function (password: string): boolean {
|
||||
if (bcrypt.compareSync(password, this.password)) return true
|
||||
return false
|
||||
})
|
||||
userSchema.method(
|
||||
'addGroup',
|
||||
async function (groupObjectId: Schema.Types.ObjectId) {
|
||||
const groupIdIndex = this.groups.indexOf(groupObjectId)
|
||||
if (groupIdIndex === -1) {
|
||||
this.groups.push(groupObjectId)
|
||||
}
|
||||
this.markModified('groups')
|
||||
return this.save()
|
||||
}
|
||||
)
|
||||
userSchema.method(
|
||||
'removeGroup',
|
||||
async function (groupObjectId: Schema.Types.ObjectId) {
|
||||
const groupIdIndex = this.groups.indexOf(groupObjectId)
|
||||
if (groupIdIndex > -1) {
|
||||
this.groups.splice(groupIdIndex, 1)
|
||||
}
|
||||
this.markModified('groups')
|
||||
return this.save()
|
||||
}
|
||||
)
|
||||
|
||||
export const User: IUserModel = model<IUser, IUserModel>('User', userSchema)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import mongoose, { Mongoose } from 'mongoose'
|
||||
import { MongoMemoryServer } from 'mongodb-memory-server'
|
||||
import request from 'supertest'
|
||||
import appPromise from '../../../app'
|
||||
import { UserController } from '../../../controllers/'
|
||||
import { UserController, GroupController } from '../../../controllers/'
|
||||
import { generateAccessToken, saveTokensInDB } from '../../../utils'
|
||||
|
||||
const clientId = 'someclientID'
|
||||
@@ -571,6 +571,7 @@ describe('user', () => {
|
||||
expect(res.body.isAdmin).toEqual(user.isAdmin)
|
||||
expect(res.body.isActive).toEqual(user.isActive)
|
||||
expect(res.body.autoExec).toEqual(user.autoExec)
|
||||
expect(res.body.groups).toEqual([])
|
||||
})
|
||||
|
||||
it('should respond with user autoExec when admin user requests', async () => {
|
||||
@@ -588,6 +589,7 @@ describe('user', () => {
|
||||
expect(res.body.isAdmin).toEqual(user.isAdmin)
|
||||
expect(res.body.isActive).toEqual(user.isActive)
|
||||
expect(res.body.autoExec).toEqual(user.autoExec)
|
||||
expect(res.body.groups).toEqual([])
|
||||
})
|
||||
|
||||
it('should respond with user when access token is not of an admin account', async () => {
|
||||
@@ -610,6 +612,34 @@ describe('user', () => {
|
||||
expect(res.body.isAdmin).toEqual(user.isAdmin)
|
||||
expect(res.body.isActive).toEqual(user.isActive)
|
||||
expect(res.body.autoExec).toBeUndefined()
|
||||
expect(res.body.groups).toEqual([])
|
||||
})
|
||||
|
||||
it('should respond with user along with associated groups', async () => {
|
||||
const dbUser = await controller.createUser(user)
|
||||
const userId = dbUser.id
|
||||
const accessToken = await generateAndSaveToken(userId)
|
||||
|
||||
const group = {
|
||||
name: 'DCGroup1',
|
||||
description: 'DC group for testing purposes.'
|
||||
}
|
||||
const groupController = new GroupController()
|
||||
const dbGroup = await groupController.createGroup(group)
|
||||
await groupController.addUserToGroup(dbGroup.groupId, dbUser.id)
|
||||
|
||||
const res = await request(app)
|
||||
.get(`/SASjsApi/user/${userId}`)
|
||||
.auth(accessToken, { type: 'bearer' })
|
||||
.send()
|
||||
.expect(200)
|
||||
|
||||
expect(res.body.username).toEqual(user.username)
|
||||
expect(res.body.displayName).toEqual(user.displayName)
|
||||
expect(res.body.isAdmin).toEqual(user.isAdmin)
|
||||
expect(res.body.isActive).toEqual(user.isActive)
|
||||
expect(res.body.autoExec).toEqual(user.autoExec)
|
||||
expect(res.body.groups.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('should respond with Unauthorized if access token is not present', async () => {
|
||||
|
||||
@@ -23,13 +23,21 @@ export const appStreamHtml = (appStreamConfig: AppStreamConfig) => `
|
||||
${style}
|
||||
</head>
|
||||
<body>
|
||||
<h1>App Stream</h1>
|
||||
<header>
|
||||
<a href="/"><img src="/logo.png" alt="logo" class="logo"></a>
|
||||
<h1>App Stream</h1>
|
||||
</header>
|
||||
<div class="app-container">
|
||||
${Object.entries(appStreamConfig)
|
||||
.map(([streamServiceName, entry]) =>
|
||||
singleAppStreamHtml(streamServiceName, entry.appLoc, entry.streamLogo)
|
||||
)
|
||||
.join('')}
|
||||
${Object.entries(appStreamConfig)
|
||||
.map(([streamServiceName, entry]) =>
|
||||
singleAppStreamHtml(
|
||||
streamServiceName,
|
||||
entry.appLoc,
|
||||
entry.streamLogo
|
||||
)
|
||||
)
|
||||
.join('')}
|
||||
|
||||
<a class="app" title="Upload build.json">
|
||||
<input id="fileId" type="file" hidden />
|
||||
<button id="uploadButton" style="margin-bottom: 5px; cursor: pointer">
|
||||
|
||||
@@ -5,18 +5,71 @@ export const style = `<style>
|
||||
.app-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-top: 50px;
|
||||
}
|
||||
.app-container .app {
|
||||
width: 150px;
|
||||
height: 180px;
|
||||
margin: 10px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
|
||||
background: #efefef;
|
||||
padding: 10px;
|
||||
border-radius: 7px;
|
||||
border: 1px solid #d7d7d7;
|
||||
}
|
||||
.app-container .app img{
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
#uploadButton {
|
||||
border: 0
|
||||
}
|
||||
|
||||
#uploadButton:focus {
|
||||
outline: 0
|
||||
}
|
||||
|
||||
#uploadMessage {
|
||||
position: relative;
|
||||
bottom: -5px;
|
||||
}
|
||||
|
||||
header {
|
||||
transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
||||
box-shadow: rgb(0 0 0 / 20%) 0px 2px 4px -1px, rgb(0 0 0 / 14%) 0px 4px 5px 0px, rgb(0 0 0 / 12%) 0px 1px 10px 0px;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: auto;
|
||||
right: 0px;
|
||||
background-color: rgb(0, 0, 0);
|
||||
color: rgb(255, 255, 255);
|
||||
z-index: 1201;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
margin: 13px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
header a {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
header .logo {
|
||||
width: 35px;
|
||||
margin-left: 10px;
|
||||
align-self: center;
|
||||
}
|
||||
</style>`
|
||||
|
||||
Reference in New Issue
Block a user