diff --git a/forge/db/models/Team.js b/forge/db/models/Team.js index 89d259562..d12912c16 100644 --- a/forge/db/models/Team.js +++ b/forge/db/models/Team.js @@ -231,36 +231,63 @@ module.exports = { where = { id: queryId } } } + const order = [['id', 'ASC']] + if (pagination.sort === 'createdAt-desc') { + order[0][1] = 'DESC' + // Also need to 'fix' the cursor pagination + if (pagination.cursor) { + where[Op.and].forEach(rule => { + if (rule.id) { + rule.id = { [Op.lt]: pagination.cursor } + } + }) + } + } + const include = [{ model: M.TeamType, attributes: ['hashid', 'id', 'name'] }] + if (app.billing) { + // Include subscription info + include.push({ model: app.db.models.Subscription }) + } + const [rows, count] = await Promise.all([ this.findAll({ where, - order: [['id', 'ASC']], + order, limit, - include: { model: M.TeamType, attributes: ['hashid', 'id', 'name'] }, + include, attributes: { include: [ [ literal(`( - SELECT COUNT(*) - FROM "Projects" AS "project" - WHERE - "project"."TeamId" = "Team"."id" - )`), + SELECT COUNT(*) + FROM "Projects" AS "project" + WHERE + "project"."TeamId" = "Team"."id" + )`), 'projectCount' ], [ literal(`( - SELECT COUNT(*) - FROM "TeamMembers" AS "members" - WHERE - "members"."TeamId" = "Team"."id" - )`), + SELECT COUNT(*) + FROM "TeamMembers" AS "members" + WHERE + "members"."TeamId" = "Team"."id" + )`), 'memberCount' + ], + [ + literal(`( + SELECT COUNT(*) + FROM "Devices" AS "devices" + WHERE + "devices"."TeamId" = "Team"."id" + )`), + 'deviceCount' ] ] } }), - this.count({ where }) + this.count({ where, include }) ]) return { meta: { diff --git a/forge/db/views/Team.js b/forge/db/views/Team.js index 56e6dba8b..7885aad12 100644 --- a/forge/db/views/Team.js +++ b/forge/db/views/Team.js @@ -60,6 +60,18 @@ module.exports = function (app) { updatedAt: result.updatedAt, links: result.links } + if (team.Subscription) { + filtered.billing = {} + filtered.billing.active = team.Subscription.isActive() + filtered.billing.unmanaged = team.Subscription.isUnmanaged() + filtered.billing.canceled = team.Subscription.isCanceled() + filtered.billing.pastDue = team.Subscription.isPastDue() + if (team.Subscription.isTrial()) { + filtered.billing.trial = true + filtered.billing.trialEnded = team.Subscription.isTrialEnded() + filtered.billing.trialEndsAt = team.Subscription.trialEndsAt + } + } return filtered } else { return null diff --git a/forge/ee/db/models/Subscription.js b/forge/ee/db/models/Subscription.js index 9c07d6d9b..663e80041 100644 --- a/forge/ee/db/models/Subscription.js +++ b/forge/ee/db/models/Subscription.js @@ -63,6 +63,7 @@ module.exports = { }, associations: function (M) { this.belongsTo(M.Team) + M.Team.hasOne(this) }, finders: function (M) { const self = this diff --git a/forge/routes/api/team.js b/forge/routes/api/team.js index 0ffb66fed..38a3dd136 100644 --- a/forge/routes/api/team.js +++ b/forge/routes/api/team.js @@ -1,3 +1,5 @@ +const { Op } = require('sequelize') + const { Roles } = require('../../lib/roles') const TeamDevices = require('./teamDevices.js') @@ -250,8 +252,24 @@ module.exports = async function (app) { } }, async (request, reply) => { // Admin request for all teams + const where = {} + const filters = [] + if (request.query.teamType) { + const teamTypes = request.query.teamType.split(',').map(app.db.models.TeamType.decodeHashid).flat() + filters.push({ TeamTypeId: { [Op.in]: teamTypes } }) + } + if (request.query.state === 'suspended') { + filters.push({ suspended: true }) + } else if (app.billing && request.query.billing) { + filters.push({ suspended: false }) + const billingStates = request.query.billing.split(',') + filters.push({ '$Subscription.status$': { [Op.in]: billingStates } }) + } + if (filters.length > 0) { + where[Op.and] = filters + } const paginationOptions = app.getPaginationOptions(request) - const teams = await app.db.models.Team.getAll(paginationOptions) + const teams = await app.db.models.Team.getAll(paginationOptions, where) teams.teams = teams.teams.map(t => app.db.views.Team.team(t)) reply.send(teams) }) diff --git a/frontend/src/api/teams.js b/frontend/src/api/teams.js index b85822c95..337111746 100644 --- a/frontend/src/api/teams.js +++ b/frontend/src/api/teams.js @@ -2,8 +2,8 @@ import paginateUrl from '../utils/paginateUrl.js' import client from './client.js' -const getTeams = async (cursor, limit, query) => { - const url = paginateUrl('/api/v1/teams', cursor, limit, query) +const getTeams = async (cursor, limit, query, filter) => { + const url = paginateUrl('/api/v1/teams', cursor, limit, query, filter) return client.get(url).then(res => { res.data.teams = res.data.teams.map(r => { r.link = { name: 'Team', params: { team_slug: r.slug } } diff --git a/frontend/src/pages/admin/Teams.vue b/frontend/src/pages/admin/Teams.vue index 754913218..6c4290168 100644 --- a/frontend/src/pages/admin/Teams.vue +++ b/frontend/src/pages/admin/Teams.vue @@ -1,30 +1,70 @@