Skip to content

Commit

Permalink
style: apply lint fixes, major restructuring & guide
Browse files Browse the repository at this point in the history
  • Loading branch information
NamesMT committed Sep 9, 2024
1 parent 5099915 commit e910111
Show file tree
Hide file tree
Showing 32 changed files with 225 additions and 175 deletions.
34 changes: 34 additions & 0 deletions apps/backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,37 @@

## Features
- [x] Easy cross-function calls / Lambda triggers support with [hono-adapter-aws-lambda](https://github.com/NamesMT/hono-adapter-aws-lambda)

## Structuring cookbook:
#### Root level:
Things like 3rd party APIs, DBs, Storages connectors, etc, should be placed in `~/providers` folder, grouped by their purpose if possible, e.g: `~/providers/auth/kinde-main.ts`, `~/providers/auth/google-main.ts`.

Things that interact with `~/providers` should be placed in `~/services` folder. (like an `user` service)

Other globally reuseable code should be placed in `~/helpers` folder.

Locally reusable code should be placed in the same folder as the file that uses it, its name should be its usable scope, suffixing the file name with `.helper`, e.g: `/api/hello.helper.ts`, `/api/app.helper.ts`.

#### `api` folder:
You could create folders to group/prefix the routes, e.g: `/api/auth` folder.

The main app entry should be `app.ts`.

Each route should be placed in a separate file according to the route path, e.g: `/api/hello.ts`, `/api/greet.ts`,
Alternatively, you could create a `routes.ts` for multiple routes declaration in file one, e.g: `/api/auth/routes.ts`.

#### `import` order:
The import order is as following, and they should be separated with a line break:

1. Package imports
2. Alias imports
3. Relative imports

e.g:
```ts
import { env } from 'std-env'

import { appFactory } from '~/factory'

import { apiApp } from './api/app'
```
45 changes: 17 additions & 28 deletions apps/backend/src/api/app.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,26 @@
// import type { TypedResponse } from 'hono'
// import { streamText } from 'hono/streaming'
import { type } from 'arktype'
import { appFactory } from '~/factory'

import { authApp } from './auth/app'
import { greetRouteApp } from './greet'
import { helloRouteApp } from './hello'

import { appFactory } from '~/factory'
import { customArktypeValidator } from '~/helpers/arktype'

const app = appFactory.createApp()
// $Auth - you'll need to setup Kinde environment variables.
export const apiApp = appFactory.createApp()
// Auth app - you'll need to setup Kinde environment variables.
.route('/auth', authApp)

// Disabling the streaming API because https://github.com/sst/ion/issues/63
// Simple health check route
.route('/hello', helloRouteApp)

// Simple greet route for arktype input validation demo
.route('/greet', greetRouteApp)

// ### This block contains the sample code for streaming APIs,
// import type { TypedResponse } from 'hono'
// import { streamText } from 'hono/streaming'

// Do note that SST doesn't support Live Development for Lambda streaming API yet: https://github.com/sst/ion/issues/63

// For RPC to know the type of streamed endpoints you could manually cast it with TypedResponse 👌
// .get('/helloStream', c => streamText(c, async (stream) => {
// await stream.writeln('Hello from Hono `/api/helloStream`!')
// }) as Response & TypedResponse<'Hello from Hono `/api/helloStream`!'>)

// Simple health check route
.get('/hello', c => c.text(`Hello from Hono \`/api/hello\`! - ${Date.now()}`))

// Simple arktype input validation demo
.get(
'/hello/:name',
customArktypeValidator('param', type({
name: 'string>0',
})),
async (c) => {
const { name } = c.req.valid('param')
return c.text(`Hello ${name}!`)
},
)

export {
app as apiApp,
}
107 changes: 3 additions & 104 deletions apps/backend/src/api/auth/app.ts
Original file line number Diff line number Diff line change
@@ -1,107 +1,6 @@
import type { ClaimTokenType, FlagType } from '@kinde-oss/kinde-typescript-sdk'
import { env } from 'std-env'

import { kindeClient } from './kindeClients'
import { getSessionManager } from './sessionManager'

import { appFactory } from '~/factory'

const app = appFactory.createApp()
.get('/health', async (c) => {
return c.text('Good', 200)
})

.get('/login', async (c) => {
const org_code = c.req.query('org_code')
const loginUrl = await kindeClient.login(getSessionManager(c), { org_code })

c.get('session').set('backToPath', c.req.query('path'))

return c.redirect(loginUrl.toString())
})

.get('/register', async (c) => {
const org_code = c.req.query('org_code')
const registerUrl = await kindeClient.register(getSessionManager(c), { org_code })
return c.redirect(registerUrl.toString())
})

.get('/callback', async (c) => {
await kindeClient.handleRedirectToApp(getSessionManager(c), new URL(c.req.url))

let backToPath = c.get('session').get('backToPath') as string || '/'
if (!backToPath.startsWith('/'))
backToPath = `/${backToPath}`

return c.redirect(`${env.FRONTEND_URL!}${backToPath}`)
})

.get('/logout', async (c) => {
const logoutUrl = await kindeClient.logout(getSessionManager(c))
return c.redirect(logoutUrl.toString())
})

.get('/isAuth', async (c) => {
const isAuthenticated = await kindeClient.isAuthenticated(getSessionManager(c)) // Boolean: true or false
return c.json(isAuthenticated)
})

.get('/profile', async (c) => {
const profile = await kindeClient.getUserProfile(getSessionManager(c))
return c.json(profile)
})

.get('/createOrg', async (c) => {
const org_name = c.req.query('org_name')?.toString()
const createUrl = await kindeClient.createOrg(getSessionManager(c), { org_name })
return c.redirect(createUrl.toString())
})

.get('/getOrg', async (c) => {
const org = await kindeClient.getOrganization(getSessionManager(c))
return c.json(org)
})

.get('/getOrgs', async (c) => {
const orgs = await kindeClient.getUserOrganizations(getSessionManager(c))
return c.json(orgs)
})

.get('/getPerm/:perm', async (c) => {
const perm = await kindeClient.getPermission(getSessionManager(c), c.req.param('perm'))
return c.json(perm)
})

.get('/getPerms', async (c) => {
const perms = await kindeClient.getPermissions(getSessionManager(c))
return c.json(perms)
})

// Try: /api/auth/getClaim/aud, /api/auth/getClaim/email/id_token
.get('/getClaim/:claim', async (c) => {
const type = (c.req.query('type') ?? 'access_token') as ClaimTokenType
if (!/^(?:access_token|id_token)$/.test(type))
return c.text('Bad request: type', 400)

const claim = await kindeClient.getClaim(getSessionManager(c), c.req.param('claim'), type)
return c.json(claim)
})

.get('/getFlag/:code', async (c) => {
const claim = await kindeClient.getFlag(
getSessionManager(c),
c.req.param('code'),
c.req.query('default'),
c.req.query('flagType') as keyof FlagType | undefined,
)
return c.json(claim)
})

.get('/getToken', async (c) => {
const accessToken = await kindeClient.getToken(getSessionManager(c))
return c.text(accessToken)
})
import { authRoutesApp } from './routes'

export {
app as authApp,
}
export const authApp = appFactory.createApp()
.route('', authRoutesApp)
102 changes: 102 additions & 0 deletions apps/backend/src/api/auth/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { env } from 'std-env'
import type { ClaimTokenType, FlagType } from '@kinde-oss/kinde-typescript-sdk'

import { appFactory } from '~/factory'
import { getSessionManager } from '~/helpers/kinde'
import { kindeClient } from '~/providers/auth/kinde-main'

export const authRoutesApp = appFactory.createApp()
.get('/health', async (c) => {
return c.text('Good', 200)
})

.get('/login', async (c) => {
const org_code = c.req.query('org_code')
const loginUrl = await kindeClient.login(getSessionManager(c), { org_code })

c.get('session').set('backToPath', c.req.query('path'))

return c.redirect(loginUrl.toString())
})

.get('/register', async (c) => {
const org_code = c.req.query('org_code')
const registerUrl = await kindeClient.register(getSessionManager(c), { org_code })
return c.redirect(registerUrl.toString())
})

.get('/callback', async (c) => {
await kindeClient.handleRedirectToApp(getSessionManager(c), new URL(c.req.url))

let backToPath = c.get('session').get('backToPath') as string || '/'
if (!backToPath.startsWith('/'))
backToPath = `/${backToPath}`

return c.redirect(`${env.FRONTEND_URL!}${backToPath}`)
})

.get('/logout', async (c) => {
const logoutUrl = await kindeClient.logout(getSessionManager(c))
return c.redirect(logoutUrl.toString())
})

.get('/isAuth', async (c) => {
const isAuthenticated = await kindeClient.isAuthenticated(getSessionManager(c)) // Boolean: true or false
return c.json(isAuthenticated)
})

.get('/profile', async (c) => {
const profile = await kindeClient.getUserProfile(getSessionManager(c))
return c.json(profile)
})

.get('/createOrg', async (c) => {
const org_name = c.req.query('org_name')?.toString()
const createUrl = await kindeClient.createOrg(getSessionManager(c), { org_name })
return c.redirect(createUrl.toString())
})

.get('/getOrg', async (c) => {
const org = await kindeClient.getOrganization(getSessionManager(c))
return c.json(org)
})

.get('/getOrgs', async (c) => {
const orgs = await kindeClient.getUserOrganizations(getSessionManager(c))
return c.json(orgs)
})

.get('/getPerm/:perm', async (c) => {
const perm = await kindeClient.getPermission(getSessionManager(c), c.req.param('perm'))
return c.json(perm)
})

.get('/getPerms', async (c) => {
const perms = await kindeClient.getPermissions(getSessionManager(c))
return c.json(perms)
})

// Try: /api/auth/getClaim/aud, /api/auth/getClaim/email/id_token
.get('/getClaim/:claim', async (c) => {
const type = (c.req.query('type') ?? 'access_token') as ClaimTokenType
if (!/^(?:access_token|id_token)$/.test(type))
return c.text('Bad request: type', 400)

const claim = await kindeClient.getClaim(getSessionManager(c), c.req.param('claim'), type)
return c.json(claim)
})

.get('/getFlag/:code', async (c) => {
const claim = await kindeClient.getFlag(
getSessionManager(c),
c.req.param('code'),
c.req.query('default'),
c.req.query('flagType') as keyof FlagType | undefined,
)
return c.json(claim)
})

.get('/getToken', async (c) => {
const accessToken = await kindeClient.getToken(getSessionManager(c))
return c.text(accessToken)
})
16 changes: 16 additions & 0 deletions apps/backend/src/api/greet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { type } from 'arktype'

import { appFactory } from '~/factory'
import { customArktypeValidator } from '~/middlewares/arktype'

export const greetRouteApp = appFactory.createApp()
.get(
'',
customArktypeValidator('query', type({
name: 'string>0',
})),
async (c) => {
const { name } = c.req.valid('query')
return c.text(`Hello ${name}!`)
},
)
2 changes: 2 additions & 0 deletions apps/backend/src/api/hello.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// This isa sample for structuring guide
export const getHelloMessage = () => `Hello from Hono! - ${Date.now()}`
6 changes: 6 additions & 0 deletions apps/backend/src/api/hello.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { appFactory } from '~/factory'

import { getHelloMessage } from './hello.helper'

export const helloRouteApp = appFactory.createApp()
.get('', async c => c.text(getHelloMessage()))
1 change: 1 addition & 0 deletions apps/backend/src/dev.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { env, isDevelopment } from 'std-env'
import type { Hono, MiddlewareHandler } from 'hono'

import { logger } from '~/logger'

// TODO: This middleware populate AWS context in local development
Expand Down
1 change: 1 addition & 0 deletions apps/backend/src/factory.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { createFactory } from 'hono/factory'
import { createTriggerFactory } from 'hono-adapter-aws-lambda'

import type { HonoEnv } from '~/types'

export const appFactory = createFactory<HonoEnv>()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HTTPException } from 'hono/http-exception'
import type { SessionManager } from '@kinde-oss/kinde-typescript-sdk'
import type { Context } from 'hono'
import type { Session } from 'hono-sessions'
import { HTTPException } from 'hono/http-exception'

/**
* This is a wrapper on top of hono-sessions for Kinde compatibility
Expand Down
10 changes: 5 additions & 5 deletions apps/backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { handle } from 'hono-adapter-aws-lambda'
import { cors } from 'hono/cors'
import { logger as loggerMiddleware } from 'hono/logger'
import { HTTPException } from 'hono/http-exception'
import { logger as loggerMiddleware } from 'hono/logger'
import { handle } from 'hono-adapter-aws-lambda'
import { env, isDevelopment } from 'std-env'

import { apiApp } from './api/app'

import { devAdapter, tryServeApp } from '~/dev'
import { appFactory, triggerFactory } from '~/factory'
import { logger } from '~/logger'
import { cookieSession } from '~/middlewares/session'
import { devAdapter, tryServeApp } from '~/dev'

import { apiApp } from './api/app'

const _app = appFactory.createApp()
// Registers an adapter middleware for development only
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createConsola, LogLevels } from 'consola'
import { isDevelopment } from 'std-env'
import { LogLevels, createConsola } from 'consola'

export const logger = createConsola(
{
Expand Down
File renamed without changes.
Loading

0 comments on commit e910111

Please sign in to comment.