Skip to content

Commit

Permalink
Add missing internal actions and a command to run them
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobmischka committed Feb 20, 2024
1 parent 6e94996 commit fb1c6d3
Show file tree
Hide file tree
Showing 27 changed files with 1,671 additions and 25 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@interval/server",
"description": "Interval Server is the central server for Interval apps",
"version": "1.0.1",
"version": "1.1.0-dev.0+internal",
"license": "MIT",
"engines": {
"node": ">=16"
Expand All @@ -22,7 +22,7 @@
"build": "rm -rf dist && prisma generate && yarn server:build && yarn wss:build && yarn client:build",
"pkg": "./scripts/pkg.sh",
"pub": "./scripts/pub.sh",
"start": "NODE_ENV=production ./dist/src/entry.js",
"start": "NODE_ENV=production node ./dist/src/entry.js",
"docker": "./scripts/docker-build.sh",
"client:build": "vite build",
"client:clean": "rm -r node_modules/.vite",
Expand Down
71 changes: 48 additions & 23 deletions src/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,16 @@ program
.name('interval-server')
.description('Interval Server is the central server for Interval apps')
.option('-v, --verbose', 'verbose output')
.addCommand(new Command('start').description('starts Interval Server'))
.addCommand(
new Command('start')
.description('starts Interval Server')
.addOption(
new Option(
'--internal-actions',
'start interval internal actions along with the server'
)
)
)
.addCommand(
new Command('db-init').addOption(
new Option(
Expand All @@ -177,34 +186,50 @@ program

const [cmd, ...args] = program.parse().args
async function main() {
if (cmd === 'start') {
const envVars = (await import('./env')).default
// start the internal web socket server
import('./wss/index')
switch (cmd) {
case 'start': {
const envVars = (await import('./env')).default
// start the internal web socket server
import('./wss/index')

const app = express()
const app = express()

const mainAppServer = (await import('./server/index')).default
const mainAppServer = (await import('./server/index')).default

app.use(mainAppServer)
app.use(mainAppServer)

const server = http.createServer(app)
const server = http.createServer(app)

const wss = new WebSocketServer({ server, path: '/websocket' })
const { setupWebSocketServer } = await import('./wss/wss')
setupWebSocketServer(wss)
const wss = new WebSocketServer({ server, path: '/websocket' })
const { setupWebSocketServer } = await import('./wss/wss')
setupWebSocketServer(wss)

server.listen(Number(envVars.PORT), () => {
logger.info(
`📡 Interval Server listening at http://localhost:${envVars.PORT}`
)
})
} else if (cmd === 'db-init') {
logger.info('Initializing a database...')
initDb({ skipCreate: args.includes('--skip-create') }).catch(() => {
logger.error(`Failed to initialize database.`)
process.exit(1)
})
server.listen(Number(envVars.PORT), () => {
logger.info(
`📡 Interval Server listening at http://localhost:${envVars.PORT}`
)

if (args.includes('--internal-actions')) {
import('./interval').catch(err => {
logger.error('Failed starting internal actions:', err)
})
}
})

break
}
case 'internal': {
await import('./interval')
break
}
case 'db-init': {
logger.info('Initializing a database...')
initDb({ skipCreate: args.includes('--skip-create') }).catch(() => {
logger.error('Failed to initialize database.')
process.exit(1)
})
break
}
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ const schema = z.object({
WSS_API_SECRET: z.string(),
AUTH_COOKIE_SECRET: z.string(),

// for internal interval host
INTERVAL_KEY: z.string().optional(),

GIT_COMMIT: z.string().optional(),
PORT: z.string().optional().default('3000'),

Expand Down
21 changes: 21 additions & 0 deletions src/interval/helpers/findUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import prisma from '~/server/prisma'
export default async function findUsers(query: string) {
return prisma.user.findMany({
where: {
OR: [
{
firstName: {
mode: 'insensitive',
contains: query,
},
},
{
lastName: {
mode: 'insensitive',
contains: query,
},
},
],
},
})
}
45 changes: 45 additions & 0 deletions src/interval/helpers/orgRowMenuItems.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { MenuItem } from '@interval/sdk/dist/types'
import { Organization, OrganizationSSO } from '@prisma/client'

export default function orgRowMenuItems(
row: Organization & { sso?: Pick<OrganizationSSO, 'id'> | null }
): MenuItem[] {
return [
{
label: 'Browse app structure',
action: 'organizations/app_structure',
params: { org: row.slug },
},
{
label: 'Change slug',
action: 'organizations/change_slug',
params: { org: row.slug },
},
{
label: 'Enable SSO',
action: 'organizations/create_org_sso',
params: { org: row.slug },
},
{
label: 'Disable SSO',
action: 'organizations/disable_org_sso',
params: { org: row.slug },
disabled: row.sso === null,
},
{
label: 'Toggle feature flag',
action: 'organizations/org_feature_flag',
params: { org: row.slug },
},
{
label: 'Transfer owner',
action: 'organizations/transfer_ownership',
params: { org: row.slug },
},
{
label: 'Create transaction history export',
action: 'organizations/create_transaction_history_export',
params: { org: row.slug },
},
]
}
8 changes: 8 additions & 0 deletions src/interval/helpers/renderUserResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { User } from '@prisma/client'

export default function renderUserResult(user: User) {
return {
label: [user.firstName, user.lastName].join(' '),
description: user.email,
}
}
21 changes: 21 additions & 0 deletions src/interval/helpers/requireOrg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ctx } from '@interval/sdk'
import prisma from '~/server/prisma'
import selectOrganization from './selectOrganization'

export default async function requireOrg(paramName = 'org') {
if (paramName in ctx.params) {
const org = await prisma.organization.findFirst({
where: { slug: String(ctx.params[paramName]) },
include: {
sso: true,
environments: true,
},
})

if (!org) throw new Error('Org not found')

return org
}

return selectOrganization()
}
9 changes: 9 additions & 0 deletions src/interval/helpers/requireParam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ctx } from '@interval/sdk'

export default function requireParam(key: string) {
if (ctx.params[key] === undefined) {
throw new Error(`Missing required param: ${key}`)
}

return String(ctx.params[key])
}
40 changes: 40 additions & 0 deletions src/interval/helpers/searchForOrganization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { io } from '@interval/sdk'
import { Prisma } from '@prisma/client'

import prisma from '~/server/prisma'

export default async function searchForOrganization() {
return io.search<Prisma.OrganizationGetPayload<{ include: { owner: true } }>>(
'Select organization',
{
async onSearch(query) {
return prisma.organization.findMany({
where: {
OR: [
{
name: {
search: query,
mode: 'insensitive',
},
},
{
name: {
contains: query,
mode: 'insensitive',
},
},
],
},
include: {
owner: true,
},
})
},
renderResult: org => ({
value: org.id,
label: org.name,
description: `Owner: ${org.owner.firstName} ${org.owner.lastName} (${org.owner.email}), Slug: ${org.slug}`,
}),
}
)
}
22 changes: 22 additions & 0 deletions src/interval/helpers/selectOrganization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import prisma from '~/server/prisma'
import searchForOrganization from './searchForOrganization'

export default async function selectOrganization() {
const selected = await searchForOrganization()

const org = await prisma.organization.findUnique({
where: {
id: selected.id,
},
include: {
sso: true,
environments: true,
},
})

if (!org) {
throw new Error("Organization doesn't exist?")
}

return org
}
10 changes: 10 additions & 0 deletions src/interval/helpers/selectUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { io } from '@interval/sdk'
import findUsers from './findUsers'
import renderUserResult from './renderUserResult'

export default function selectUser() {
return io.search('Select a user', {
onSearch: async q => await findUsers(q),
renderResult: renderUserResult,
})
}
9 changes: 9 additions & 0 deletions src/interval/helpers/staticQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Don't use this with any query strings that aren't static

import { io } from '@interval/sdk'
import prisma from '~/server/prisma'

export default async function staticQuery(query: string) {
const rows: Record<string, string>[] = await prisma.$queryRawUnsafe(query)
await io.display.table('Rows', { data: rows })
}
17 changes: 17 additions & 0 deletions src/interval/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import path from 'path'
import { Interval } from '@interval/sdk'
import env from '../env'

if (!env.INTERVAL_KEY) {
throw new Error(
'Environment variable INTERVAL_KEY required for internal actions.'
)
}

const interval = new Interval({
endpoint: `${env.APP_URL}/websocket`,
apiKey: env.INTERVAL_KEY,
routesDirectory: path.resolve(__dirname, 'routes'),
})

interval.listen()
Loading

0 comments on commit fb1c6d3

Please sign in to comment.