Skip to content

Commit

Permalink
Allow trial team to be manually created
Browse files Browse the repository at this point in the history
  • Loading branch information
knolleary committed Dec 18, 2024
1 parent 33ac8f7 commit e28d5ed
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 5 deletions.
7 changes: 7 additions & 0 deletions forge/db/models/Team.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,13 @@ module.exports = {
}]
})
},
countForUser: async function (User) {
return M.TeamMember.count({
where: {
UserId: User.id
}
})
},
forUser: async function (User) {
return M.TeamMember.findAll({
where: {
Expand Down
2 changes: 1 addition & 1 deletion forge/lib/userTeam.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ async function completeUserSignup (app, user) {
// return
}
// only create a personal team if no other teams exist
if (!((await app.db.models.Team.forUser(user)).length)) {
if ((await app.db.models.Team.countForUser(user)) === 0) {
let teamTypeId = app.settings.get('user:team:auto-create:teamType')

if (!teamTypeId) {
Expand Down
72 changes: 68 additions & 4 deletions forge/routes/api/team.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const crypto = require('crypto')

Check failure on line 1 in forge/routes/api/team.js

View workflow job for this annotation

GitHub Actions / Postgres tests (18.x)

There should be at least one empty line between import groups
const { Op } = require('sequelize')

const { Roles } = require('../../lib/roles')
Expand Down Expand Up @@ -395,7 +396,8 @@ module.exports = async function (app) {
properties: {
name: { type: 'string' },
type: { type: 'string' },
slug: { type: 'string' }
slug: { type: 'string' },
trial: { type: 'boolean' }
}
},
response: {
Expand Down Expand Up @@ -429,6 +431,34 @@ module.exports = async function (app) {
return
}

let trialMode = false
if (app.license.active() && app.billing && request.body.trial) {
// Check this user is allowed to create a trial team of this type.
// Rules:
// 1. teamType must have trial mode enabled
const teamTrialActive = await teamType.getProperty('trial.active', false)
if (!teamTrialActive) {
reply.code(400).send({ code: 'invalid_request', error: 'trial mode not available' })
return
}
// 2. user must have no existing teams
const existingTeamCount = await app.db.models.Team.countForUser(request.session.User)
if (existingTeamCount > 0) {
reply.code(400).send({ code: 'invalid_request', error: 'trial mode not available' })
return
}
// 3. user must be < 1 week old
const delta = Date.now() - request.session.User.createdAt.getTime()
if (delta > 1000 * 60 * 60 * 24 * 7) {
reply.code(400).send({ code: 'invalid_request', error: 'trial mode not available' })
return
}
trialMode = true
} else if (request.body.trial) {
reply.code(400).send({ code: 'invalid_request', error: 'trial mode not available' })
return
}

let team

try {
Expand All @@ -444,9 +474,43 @@ module.exports = async function (app) {
const teamView = app.db.views.Team.team(team)

if (app.license.active() && app.billing) {
const session = await app.billing.createSubscriptionSession(team, request.session.User)
app.auditLog.Team.billing.session.created(request.session.User, null, team, session)
teamView.billingURL = session.url
if (trialMode) {
await app.billing.setupTrialTeamSubscription(team, request.session.User)
// In trial mode, we may also auto-create their first application and instance
if (app.settings.get('user:team:auto-create:instanceType')) {
const instanceTypeId = app.settings.get('user:team:auto-create:instanceType')
const instanceType = await app.db.models.ProjectType.byId(instanceTypeId)
const instanceStack = await instanceType?.getDefaultStack() || (await instanceType.getProjectStacks())?.[0]
const instanceTemplate = await app.db.models.ProjectTemplate.findOne({ where: { active: true } })
if (!instanceType) {
app.log.warn(`Unable to create Trial Instance in team ${team.hashid}: Instance type with id ${instanceTypeId} from 'user:team:auto-create:instanceType' not found`)
} else if (!instanceStack) {
app.log.warn(`Unable to create Trial Instance in team ${team.hashid}: Unable to find a stack for use with instance type ${instanceTypeId}`)
} else if (!instanceTemplate) {
app.log.warn(`Unable to create Trial Instance in team ${team.hashid}: Unable to find the default instance template`)
} else {
const applicationName = `${request.session.User.name}'s Application`
const application = await app.db.models.Application.create({
name: applicationName.charAt(0).toUpperCase() + applicationName.slice(1),
TeamId: team.id
})
await app.auditLog.Team.application.created(request.session.User, null, team, application)
await app.auditLog.Application.application.created(request.session.User, null, application)

const safeTeamName = team.name.toLowerCase().replace(/[\W_]/g, '-')
const safeUserName = request.session.User.username.toLowerCase().replace(/[\W_]/g, '-')

const instanceProperties = {
name: `${safeTeamName}-${safeUserName}-${crypto.randomBytes(4).toString('hex')}`
}
await app.db.controllers.Project.create(team, application, request.session.User, instanceType, instanceStack, instanceTemplate, instanceProperties)
}
}
} else {
const session = await app.billing.createSubscriptionSession(team, request.session.User)
app.auditLog.Team.billing.session.created(request.session.User, null, team, session)
teamView.billingURL = session.url
}
}

reply.send(teamView)
Expand Down

0 comments on commit e28d5ed

Please sign in to comment.