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 badges management to organization view #256

Merged
merged 21 commits into from
Apr 11, 2022
Merged
Show file tree
Hide file tree
Changes from 20 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
11 changes: 4 additions & 7 deletions app/db/migrations/20220302104250_add_user_badges.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
exports.up = async function (knex) {
await knex.schema.createTable('user_badge', (table) => {
await knex.schema.createTable('user_badges', (table) => {
table
.integer('badge_id')
.references('id')
.inTable('organization_badge')
.onDelete('CASCADE')
table
.integer('user_id')
.references('id')
.inTable('organization_badge')
.onDelete('CASCADE')
table.integer('user_id')
table.datetime('assigned_at').defaultTo(knex.fn.now())
table.datetime('valid_until')
table.unique(['badge_id', 'user_id'])
})
}

exports.down = async function (knex) {
await knex.schema.dropTable('user_badge')
await knex.schema.dropTable('user_badges')
}
6 changes: 3 additions & 3 deletions app/lib/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ async function getUserManageToken (id) {

async function getUserBadges (id) {
const conn = await db()
return conn('user_badge')
return conn('user_badges')
.select([
'id',
'assigned_at',
Expand All @@ -267,10 +267,10 @@ async function getUserBadges (id) {
])
.leftJoin(
'organization_badge',
'user_badge.badge_id',
'user_badges.badge_id',
'organization_badge.id'
)
.where('user_badge.user_id', id)
.where('user_badges.user_id', id)
}

module.exports = {
Expand Down
14 changes: 13 additions & 1 deletion app/lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,21 @@ function checkRequiredProperties (requiredProperties, object) {
})
}

/**
* Converts a date to the browser locale string
*
* @param {Number or String} timestamp
* @returns
*/
function toDateString (timestamp) {
const dateFormat = new Intl.DateTimeFormat(navigator.language).format
return dateFormat(new Date(timestamp))
}

module.exports = {
unpack,
ValidationError,
PropertyRequiredError,
checkRequiredProperties
checkRequiredProperties,
toDateString
}
117 changes: 97 additions & 20 deletions app/manage/badges.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const yup = require('yup')
const organization = require('../lib/organization')
const profile = require('../lib/profile')
const { routeWrapper } = require('./utils')
const team = require('../lib/team')

/**
* Get the list of badges of an organization
Expand All @@ -21,6 +22,7 @@ const listBadges = routeWrapper({
const badges = await conn('organization_badge')
.select('*')
.where('organization_id', req.params.id)
.orderBy('id')
reply.send(badges)
} catch (err) {
console.log(err)
Expand Down Expand Up @@ -63,6 +65,68 @@ const createBadge = routeWrapper({
}
})

/**
* Get organization badge
*/
const getBadge = routeWrapper({
validate: {
params: yup
.object({
id: yup.number().required().positive().integer(),
badgeId: yup.number().required().positive().integer()
})
.required()
},
handler: async function (req, reply) {
try {
const conn = await db()
const [badge] = await conn('organization_badge')
.select('*')
.where('id', req.params.badgeId)
.returning('*')

let users = await conn('user_badges')
.select({
id: 'user_badges.user_id',
assignedAt: 'user_badges.assigned_at',
validUntil: 'user_badges.valid_until'
})
.leftJoin(
'organization_badge',
'user_badges.badge_id',
'organization_badge.id'
)
.where('badge_id', req.params.badgeId)
.returning('*')

if (users.length > 0) {
// Get user profiles
const userProfiles = (
await team.resolveMemberNames(users.map((u) => u.id))
).reduce((acc, u) => {
acc[u.id] = u
return acc
}, {})

users = users.map((u) => ({
id: u.id,
assignedAt: u.assignedAt,
validUntil: u.validUntil,
displayName: userProfiles[u.id] ? userProfiles[u.id].name : ''
}))
}

reply.send({
...badge,
users
})
} catch (err) {
console.log(err)
return reply.boom.badRequest(err.message)
}
}
})

/**
* Edit organization badge
*/
Expand Down Expand Up @@ -110,10 +174,11 @@ const deleteBadge = routeWrapper({
handler: async function (req, reply) {
try {
const conn = await db()
await conn('organization_badge')
.delete()
.where('id', req.params.badgeId)
return reply.sendStatus(200)
await conn('organization_badge').delete().where('id', req.params.badgeId)
return reply.send({
status: 200,
message: `Badge ${req.params.badgeId} deleted successfully.`
})
} catch (err) {
console.log(err)
return reply.boom.badRequest(err.message)
Expand All @@ -133,21 +198,27 @@ const assignUserBadge = routeWrapper({
userId: yup.number().required().positive().integer()
})
.required(),
body: yup
.object({
assigned_at: yup.date().optional(),
valid_until: yup.date().optional()
})
body: yup.object({
assigned_at: yup.date().optional(),
valid_until: yup.date().optional()
})
},
handler: async function (req, reply) {
try {
const conn = await db()

// user is member
await organization.isMember(req.params.id, req.params.userId)
// user is related to org?
const isMemberOrStaff = await organization.isMemberOrStaff(
req.params.id,
req.params.userId
)

if (!isMemberOrStaff) {
return reply.boom.badRequest('User is not part of the organization.')
}

// assign badge
const [badge] = await conn('user_badge')
const [badge] = await conn('user_badges')
.insert({
user_id: req.params.userId,
badge_id: req.params.badgeId,
Expand All @@ -159,7 +230,13 @@ const assignUserBadge = routeWrapper({
reply.send(badge)
} catch (err) {
console.log(err)
return reply.boom.badRequest(err.message)
if (err.code === '23505') {
return reply.boom.badRequest('User is already assigned to badge.')
} else {
return reply.boom.badRequest(
'Unexpected error, please try again later.'
)
}
}
}
})
Expand Down Expand Up @@ -197,18 +274,17 @@ const updateUserBadge = routeWrapper({
userId: yup.number().required().positive().integer()
})
.required(),
body: yup
.object({
assigned_at: yup.date().optional(),
valid_until: yup.date().optional()
})
body: yup.object({
assigned_at: yup.date().optional(),
valid_until: yup.date().optional()
})
},
handler: async function (req, reply) {
try {
const conn = await db()

// assign badge
const [badge] = await conn('user_badge')
const [badge] = await conn('user_badges')
.update({
assigned_at: req.body.assigned_at,
valid_until: req.body.valid_until
Expand Down Expand Up @@ -244,7 +320,7 @@ const removeUserBadge = routeWrapper({
const conn = await db()

// delete user badge
await conn('user_badge').delete().where({
await conn('user_badges').delete().where({
user_id: req.params.userId,
badge_id: req.params.badgeId
})
Expand All @@ -260,6 +336,7 @@ const removeUserBadge = routeWrapper({
module.exports = {
listBadges,
createBadge,
getBadge,
patchBadge,
deleteBadge,
assignUserBadge,
Expand Down
27 changes: 27 additions & 0 deletions app/manage/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const {

const {
createBadge,
getBadge,
patchBadge,
deleteBadge,
listBadges,
Expand Down Expand Up @@ -152,6 +153,11 @@ function manageRouter (nextApp) {
can('organization:edit'),
createBadge
)
router.get(
'/api/organizations/:id/badges/:badgeId',
can('organization:edit'),
getBadge
)
router.patch(
'/api/organizations/:id/badges/:badgeId',
can('organization:edit'),
Expand Down Expand Up @@ -275,6 +281,27 @@ function manageRouter (nextApp) {
return nextApp.render(req, res, '/org-edit-team-profile', { id: req.params.id })
})

/**
* Badge pages
* */
router.get(
'/organizations/:id/badges/add',
can('organization:edit'),
(req, res) => {
return nextApp.render(req, res, '/badges/add', { id: req.params.id })
}
)
router.get(
'/organizations/:id/badges/:badgeId',
can('organization:edit'),
(req, res) => {
return nextApp.render(req, res, '/badges/edit', {
id: req.params.id,
badgeId: req.params.badgeId
})
}
)

return router
}

Expand Down
2 changes: 1 addition & 1 deletion app/tests/api/badges-api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ test.before(async () => {

// Add team member
await orgOwner.agent
.put(`/api/team/${orgTeam1.id}/${orgTeamMember.id}`)
.put(`/api/teams/add/${orgTeam1.id}/${orgTeamMember.id}`)
.expect(200)

// Add manager
Expand Down
6 changes: 6 additions & 0 deletions components/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,12 @@ function Layout (props) {
padding: 0.5rem 1rem 0.5rem 0.25rem;
margin-right: 1rem;
border: 2px solid ${theme.colors.primaryColor};

}

.form-control :global(input[type="color"]) {
padding: 3px;
height: 2.5rem;
}

.status--alert {
Expand Down
8 changes: 4 additions & 4 deletions components/table.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function TableHead ({ columns }) {
column.onClick && column.onClick()
}}
>
{column.key}
{column.label || column.key}
</th>
)
})}
Expand Down Expand Up @@ -102,12 +102,12 @@ export default function Table ({ columns, rows, onRowClick }) {

tbody tr {
background: #fff;
cursor: pointer;
${onRowClick && 'cursor: pointer'}
}

tbody tr:hover {
${onRowClick && `tbody tr:hover {
background: ${theme.colors.primaryLite};
}
}`}
`}
</style>
</table>
Expand Down
Loading