diff --git a/.adonisjs/client/data.d.ts b/.adonisjs/client/data.d.ts
index dc95553..fbfcce0 100644
--- a/.adonisjs/client/data.d.ts
+++ b/.adonisjs/client/data.d.ts
@@ -6,28 +6,32 @@
///
import type { InferData, InferVariants } from '@adonisjs/core/types/transformers'
import type { InferSharedProps, InferFlashData } from '@adonisjs/inertia/types'
-import type BuildLogTransformer from '#transformers/build_log_transformer'
-import type KitRecordTransformer from '#transformers/kit_record_transformer'
-import type KitTransformer from '#transformers/kit_transformer'
-import type UserTransformer from '#transformers/user_transformer'
-import type InertiaMiddleware from '#middleware/inertia_middleware'
+import type AuthUserTransformer from '#app/auth/transformers/user_transformer'
+import type KitsBuildLogTransformer from '#app/kits/transformers/build_log_transformer'
+import type KitsKitRecordTransformer from '#app/kits/transformers/kit_record_transformer'
+import type KitsKitTransformer from '#app/kits/transformers/kit_transformer'
+import type InertiaMiddleware from '#core/middleware/inertia_middleware'
export namespace Data {
- export type BuildLog = InferData
- export namespace BuildLog {
- export type Variants = InferVariants
+ export namespace Auth {
+ export type User = InferData
+ export namespace User {
+ export type Variants = InferVariants
+ }
}
- export type KitRecord = InferData
- export namespace KitRecord {
- export type Variants = InferVariants
- }
- export type Kit = InferData
- export namespace Kit {
- export type Variants = InferVariants
- }
- export type User = InferData
- export namespace User {
- export type Variants = InferVariants
+ export namespace Kits {
+ export type BuildLog = InferData
+ export namespace BuildLog {
+ export type Variants = InferVariants
+ }
+ export type KitRecord = InferData
+ export namespace KitRecord {
+ export type Variants = InferVariants
+ }
+ export type Kit = InferData
+ export namespace Kit {
+ export type Variants = InferVariants
+ }
}
export type SharedProps = InferSharedProps
export type FlashMessages = InferFlashData
diff --git a/.adonisjs/client/registry/index.ts b/.adonisjs/client/registry/index.ts
index 9b337f3..4d0d408 100644
--- a/.adonisjs/client/registry/index.ts
+++ b/.adonisjs/client/registry/index.ts
@@ -12,12 +12,6 @@ const routes = {
tokens: [{"old":"/","type":0,"val":"/","end":""}],
types: placeholder as Registry['home']['types'],
},
- 'profile': {
- methods: ["GET","HEAD"],
- pattern: '/profile/:handle',
- tokens: [{"old":"/profile/:handle","type":0,"val":"profile","end":""},{"old":"/profile/:handle","type":1,"val":"handle","end":""}],
- types: placeholder as Registry['profile']['types'],
- },
'login': {
methods: ["GET","HEAD"],
pattern: '/login',
@@ -78,6 +72,12 @@ const routes = {
tokens: [{"old":"/kits/:rkey","type":0,"val":"kits","end":""},{"old":"/kits/:rkey","type":1,"val":"rkey","end":""}],
types: placeholder as Registry['kits.update']['types'],
},
+ 'profile': {
+ methods: ["GET","HEAD"],
+ pattern: '/profile/:handle',
+ tokens: [{"old":"/profile/:handle","type":0,"val":"profile","end":""},{"old":"/profile/:handle","type":1,"val":"handle","end":""}],
+ types: placeholder as Registry['profile']['types'],
+ },
'event_stream': {
methods: ["GET","HEAD"],
pattern: '/__transmit/events',
diff --git a/.adonisjs/client/registry/schema.d.ts b/.adonisjs/client/registry/schema.d.ts
index c0e9bed..21cc446 100644
--- a/.adonisjs/client/registry/schema.d.ts
+++ b/.adonisjs/client/registry/schema.d.ts
@@ -15,20 +15,8 @@ export interface Registry {
paramsTuple: []
params: {}
query: {}
- response: ExtractResponse>>
- errorResponse: ExtractErrorResponse>>
- }
- }
- 'profile': {
- methods: ["GET","HEAD"]
- pattern: '/profile/:handle'
- types: {
- body: {}
- paramsTuple: [ParamValue]
- params: { handle: ParamValue }
- query: {}
- response: ExtractResponse>>
- errorResponse: ExtractErrorResponse>>
+ response: ExtractResponse>>
+ errorResponse: ExtractErrorResponse>>
}
}
'login': {
@@ -39,8 +27,8 @@ export interface Registry {
paramsTuple: []
params: {}
query: {}
- response: unknown
- errorResponse: unknown
+ response: ExtractResponse>>
+ errorResponse: ExtractErrorResponse>>
}
}
'oauth.logout': {
@@ -51,32 +39,32 @@ export interface Registry {
paramsTuple: []
params: {}
query: {}
- response: ExtractResponse>>
- errorResponse: ExtractErrorResponse>>
+ response: ExtractResponse>>
+ errorResponse: ExtractErrorResponse>>
}
}
'oauth.login': {
methods: ["POST"]
pattern: '/oauth/login'
types: {
- body: ExtractBody>
+ body: ExtractBody>
paramsTuple: []
params: {}
- query: ExtractQuery>
- response: ExtractResponse>>
- errorResponse: ExtractErrorResponse>> | { status: 422; response: { errors: SimpleError[] } }
+ query: ExtractQuery>
+ response: ExtractResponse>>
+ errorResponse: ExtractErrorResponse>> | { status: 422; response: { errors: SimpleError[] } }
}
}
'oauth.signup': {
methods: ["POST"]
pattern: '/oauth/signup'
types: {
- body: ExtractBody>
+ body: ExtractBody>
paramsTuple: []
params: {}
- query: ExtractQuery>
- response: ExtractResponse>>
- errorResponse: ExtractErrorResponse>> | { status: 422; response: { errors: SimpleError[] } }
+ query: ExtractQuery>
+ response: ExtractResponse>>
+ errorResponse: ExtractErrorResponse>> | { status: 422; response: { errors: SimpleError[] } }
}
}
'oauth.callback': {
@@ -87,8 +75,8 @@ export interface Registry {
paramsTuple: []
params: {}
query: {}
- response: ExtractResponse>>
- errorResponse: ExtractErrorResponse>>
+ response: ExtractResponse>>
+ errorResponse: ExtractErrorResponse>>
}
}
'dashboard': {
@@ -99,8 +87,8 @@ export interface Registry {
paramsTuple: []
params: {}
query: {}
- response: ExtractResponse>>
- errorResponse: ExtractErrorResponse>>
+ response: ExtractResponse>>
+ errorResponse: ExtractErrorResponse>>
}
}
'kits.new': {
@@ -111,20 +99,20 @@ export interface Registry {
paramsTuple: []
params: {}
query: {}
- response: ExtractResponse>>
- errorResponse: ExtractErrorResponse>>
+ response: ExtractResponse>>
+ errorResponse: ExtractErrorResponse>>
}
}
'kits.store': {
methods: ["POST"]
pattern: '/kits'
types: {
- body: ExtractBody>
+ body: ExtractBody>
paramsTuple: []
params: {}
- query: ExtractQuery>
- response: ExtractResponse>>
- errorResponse: ExtractErrorResponse>> | { status: 422; response: { errors: SimpleError[] } }
+ query: ExtractQuery>
+ response: ExtractResponse>>
+ errorResponse: ExtractErrorResponse>> | { status: 422; response: { errors: SimpleError[] } }
}
}
'kits.edit': {
@@ -135,20 +123,32 @@ export interface Registry {
paramsTuple: [ParamValue]
params: { rkey: ParamValue }
query: {}
- response: ExtractResponse>>
- errorResponse: ExtractErrorResponse>>
+ response: ExtractResponse>>
+ errorResponse: ExtractErrorResponse>>
}
}
'kits.update': {
methods: ["PUT"]
pattern: '/kits/:rkey'
types: {
- body: ExtractBody>
+ body: ExtractBody>
paramsTuple: [ParamValue]
params: { rkey: ParamValue }
- query: ExtractQuery>
- response: ExtractResponse>>
- errorResponse: ExtractErrorResponse>> | { status: 422; response: { errors: SimpleError[] } }
+ query: ExtractQuery>
+ response: ExtractResponse>>
+ errorResponse: ExtractErrorResponse>> | { status: 422; response: { errors: SimpleError[] } }
+ }
+ }
+ 'profile': {
+ methods: ["GET","HEAD"]
+ pattern: '/profile/:handle'
+ types: {
+ body: {}
+ paramsTuple: [ParamValue]
+ params: { handle: ParamValue }
+ query: {}
+ response: ExtractResponse>>
+ errorResponse: ExtractErrorResponse>>
}
}
'event_stream': {
diff --git a/.adonisjs/client/registry/tree.d.ts b/.adonisjs/client/registry/tree.d.ts
index de11eb5..249a693 100644
--- a/.adonisjs/client/registry/tree.d.ts
+++ b/.adonisjs/client/registry/tree.d.ts
@@ -3,7 +3,6 @@ import type { routes } from './index.ts'
export interface ApiDefinition {
home: typeof routes['home']
- profile: typeof routes['profile']
login: typeof routes['login']
oauth: {
logout: typeof routes['oauth.logout']
@@ -18,6 +17,7 @@ export interface ApiDefinition {
edit: typeof routes['kits.edit']
update: typeof routes['kits.update']
}
+ profile: typeof routes['profile']
eventStream: typeof routes['event_stream']
subscribe: typeof routes['subscribe']
unsubscribe: typeof routes['unsubscribe']
diff --git a/.adonisjs/server/controllers.ts b/.adonisjs/server/controllers.ts
index 69ccb3b..78a45bd 100644
--- a/.adonisjs/server/controllers.ts
+++ b/.adonisjs/server/controllers.ts
@@ -4,8 +4,21 @@
*/
export const controllers = {
- Home: () => import('#controllers/home_controller'),
- Kits: () => import('#controllers/kits_controller'),
- Oauth: () => import('#controllers/oauth_controller'),
- Profile: () => import('#controllers/profile_controller'),
+ auth: {
+ Callback: () => import('#app/auth/controllers/callback_controller'),
+ Login: () => import('#app/auth/controllers/login_controller'),
+ Logout: () => import('#app/auth/controllers/logout_controller'),
+ Signup: () => import('#app/auth/controllers/signup_controller'),
+ },
+ feed: {
+ Feed: () => import('#app/feed/controllers/feed_controller'),
+ },
+ kits: {
+ ListKits: () => import('#app/kits/controllers/list_kits_controller'),
+ StoreKit: () => import('#app/kits/controllers/store_kit_controller'),
+ UpdateKit: () => import('#app/kits/controllers/update_kit_controller'),
+ },
+ profile: {
+ ShowProfile: () => import('#app/profile/controllers/show_profile_controller'),
+ },
}
diff --git a/.adonisjs/server/routes.d.ts b/.adonisjs/server/routes.d.ts
index 3abb7c2..e0e0621 100644
--- a/.adonisjs/server/routes.d.ts
+++ b/.adonisjs/server/routes.d.ts
@@ -5,7 +5,6 @@ type ParamValue = string | number | bigint | boolean
export type ScannedRoutes = {
ALL: {
'home': { paramsTuple?: []; params?: {} }
- 'profile': { paramsTuple: [ParamValue]; params: {'handle': ParamValue} }
'login': { paramsTuple?: []; params?: {} }
'oauth.logout': { paramsTuple?: []; params?: {} }
'oauth.login': { paramsTuple?: []; params?: {} }
@@ -16,28 +15,29 @@ export type ScannedRoutes = {
'kits.store': { paramsTuple?: []; params?: {} }
'kits.edit': { paramsTuple: [ParamValue]; params: {'rkey': ParamValue} }
'kits.update': { paramsTuple: [ParamValue]; params: {'rkey': ParamValue} }
+ 'profile': { paramsTuple: [ParamValue]; params: {'handle': ParamValue} }
'event_stream': { paramsTuple?: []; params?: {} }
'subscribe': { paramsTuple?: []; params?: {} }
'unsubscribe': { paramsTuple?: []; params?: {} }
}
GET: {
'home': { paramsTuple?: []; params?: {} }
- 'profile': { paramsTuple: [ParamValue]; params: {'handle': ParamValue} }
'login': { paramsTuple?: []; params?: {} }
'oauth.callback': { paramsTuple?: []; params?: {} }
'dashboard': { paramsTuple?: []; params?: {} }
'kits.new': { paramsTuple?: []; params?: {} }
'kits.edit': { paramsTuple: [ParamValue]; params: {'rkey': ParamValue} }
+ 'profile': { paramsTuple: [ParamValue]; params: {'handle': ParamValue} }
'event_stream': { paramsTuple?: []; params?: {} }
}
HEAD: {
'home': { paramsTuple?: []; params?: {} }
- 'profile': { paramsTuple: [ParamValue]; params: {'handle': ParamValue} }
'login': { paramsTuple?: []; params?: {} }
'oauth.callback': { paramsTuple?: []; params?: {} }
'dashboard': { paramsTuple?: []; params?: {} }
'kits.new': { paramsTuple?: []; params?: {} }
'kits.edit': { paramsTuple: [ParamValue]; params: {'rkey': ParamValue} }
+ 'profile': { paramsTuple: [ParamValue]; params: {'handle': ParamValue} }
'event_stream': { paramsTuple?: []; params?: {} }
}
POST: {
diff --git a/.gitignore b/.gitignore
index f71f559..a955375 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,6 @@ yarn-error.log
# Platform specific
.DS_Store
+
+# Notes
+.notes
diff --git a/adonisrc.ts b/adonisrc.ts
index 65a5b2d..ba3f19d 100644
--- a/adonisrc.ts
+++ b/adonisrc.ts
@@ -130,7 +130,20 @@ export default defineConfig({
hooks: {
init: [
indexEntities({
- transformers: { enabled: true, withSharedProps: true },
+ controllers: {
+ enabled: true,
+ source: './app',
+ glob: ['**/*_controller.ts'],
+ importAlias: '#app',
+ },
+ transformers: {
+ enabled: true,
+ source: './app',
+ glob: ['**/*_transformer.ts'],
+ importAlias: '#app',
+ withSharedProps: true,
+ inertiaMiddlewareImportPath: '#core/middleware/inertia_middleware',
+ },
}),
indexPages({ framework: 'react' }),
generateRegistry(),
diff --git a/app/auth/controllers/callback_controller.ts b/app/auth/controllers/callback_controller.ts
new file mode 100644
index 0000000..02c1650
--- /dev/null
+++ b/app/auth/controllers/callback_controller.ts
@@ -0,0 +1,18 @@
+import { inject } from '@adonisjs/core'
+import type { HttpContext } from '@adonisjs/core/http'
+import { AuthService } from '#auth/services/auth_service'
+
+@inject()
+export default class CallbackController {
+ constructor(private authService: AuthService) {}
+
+ async handle({ response, logger }: HttpContext) {
+ try {
+ await this.authService.handleCallback()
+ return response.redirect().toRoute('dashboard')
+ } catch (err) {
+ logger.error(err, 'Error completing AT Protocol OAuth flow')
+ return response.redirect().toRoute('login')
+ }
+ }
+}
diff --git a/app/auth/controllers/login_controller.ts b/app/auth/controllers/login_controller.ts
new file mode 100644
index 0000000..7d481d0
--- /dev/null
+++ b/app/auth/controllers/login_controller.ts
@@ -0,0 +1,28 @@
+import { inject } from '@adonisjs/core'
+import type { HttpContext } from '@adonisjs/core/http'
+import { OAuthResolverError } from '@atproto/oauth-client-node'
+import { loginRequestValidator } from '#auth/validators/oauth'
+import { AuthService } from '#auth/services/auth_service'
+
+@inject()
+export default class LoginController {
+ constructor(private authService: AuthService) {}
+
+ async render({ inertia }: HttpContext) {
+ return inertia.render('auth/login', {})
+ }
+
+ async handle({ request, response, inertia, logger }: HttpContext) {
+ const { input } = await request.validateUsing(loginRequestValidator)
+ try {
+ const authorizationUrl = await this.authService.authorize(input)
+ inertia.location(authorizationUrl)
+ } catch (err) {
+ logger.error(err, 'Error starting AT Protocol OAuth flow')
+ if (err instanceof OAuthResolverError) {
+ // Handle the input not being AT Protocol OAuth compatible
+ }
+ return response.redirect().back()
+ }
+ }
+}
diff --git a/app/auth/controllers/logout_controller.ts b/app/auth/controllers/logout_controller.ts
new file mode 100644
index 0000000..9017ef0
--- /dev/null
+++ b/app/auth/controllers/logout_controller.ts
@@ -0,0 +1,13 @@
+import { inject } from '@adonisjs/core'
+import type { HttpContext } from '@adonisjs/core/http'
+import { AuthService } from '#auth/services/auth_service'
+
+@inject()
+export default class LogoutController {
+ constructor(private authService: AuthService) {}
+
+ async handle({ response }: HttpContext) {
+ await this.authService.logout()
+ return response.redirect().back()
+ }
+}
diff --git a/app/auth/controllers/signup_controller.ts b/app/auth/controllers/signup_controller.ts
new file mode 100644
index 0000000..00b39ba
--- /dev/null
+++ b/app/auth/controllers/signup_controller.ts
@@ -0,0 +1,22 @@
+import { inject } from '@adonisjs/core'
+import type { HttpContext } from '@adonisjs/core/http'
+import { signupRequestValidator } from '#auth/validators/oauth'
+import { AuthService } from '#auth/services/auth_service'
+
+@inject()
+export default class SignupController {
+ constructor(private authService: AuthService) {}
+
+ async handle({ request, response, inertia }: HttpContext) {
+ const { input } = await request.validateUsing(signupRequestValidator)
+ const service = input ?? 'https://bsky.social'
+ const registrationSupported = await this.authService.canRegister(service)
+
+ if (!registrationSupported) {
+ return response.abort('Registration not supported')
+ }
+
+ const authorizationUrl = await this.authService.register(service)
+ inertia.location(authorizationUrl)
+ }
+}
diff --git a/app/models/oauth_session.ts b/app/auth/models/oauth_session.ts
similarity index 100%
rename from app/models/oauth_session.ts
rename to app/auth/models/oauth_session.ts
diff --git a/app/models/oauth_state.ts b/app/auth/models/oauth_state.ts
similarity index 100%
rename from app/models/oauth_state.ts
rename to app/auth/models/oauth_state.ts
diff --git a/app/models/user.ts b/app/auth/models/user.ts
similarity index 100%
rename from app/models/user.ts
rename to app/auth/models/user.ts
diff --git a/app/auth/routes.ts b/app/auth/routes.ts
new file mode 100644
index 0000000..ff17b99
--- /dev/null
+++ b/app/auth/routes.ts
@@ -0,0 +1,26 @@
+import router from '@adonisjs/core/services/router'
+import { middleware } from '#start/kernel'
+import { controllers } from '#generated/controllers'
+
+const {
+ auth: {
+ Login: LoginController,
+ Signup: SignupController,
+ Callback: CallbackController,
+ Logout: LogoutController,
+ },
+} = controllers
+
+router
+ .group(() => {
+ router.get('/login', [LoginController, 'render']).as('login')
+ })
+ .use(middleware.guest())
+
+router.post('/oauth/logout', [LogoutController, 'handle']).as('oauth.logout').use(middleware.auth())
+
+router.group(() => {
+ router.post('/oauth/login', [LoginController, 'handle']).as('oauth.login')
+ router.post('/oauth/signup', [SignupController, 'handle']).as('oauth.signup')
+ router.get('/oauth/callback', [CallbackController, 'handle']).as('oauth.callback')
+})
diff --git a/app/auth/services/auth_service.ts b/app/auth/services/auth_service.ts
new file mode 100644
index 0000000..89ef48c
--- /dev/null
+++ b/app/auth/services/auth_service.ts
@@ -0,0 +1,32 @@
+import { inject } from '@adonisjs/core'
+import { HttpContext } from '@adonisjs/core/http'
+
+@inject()
+export class AuthService {
+ constructor(private ctx: HttpContext) {}
+
+ async authorize(input: string) {
+ return this.ctx.oauth.authorize(input)
+ }
+
+ async canRegister(service: string) {
+ return this.ctx.oauth.canRegister(service)
+ }
+
+ async register(service: string) {
+ return this.ctx.oauth.register(service)
+ }
+
+ async handleCallback() {
+ const session = await this.ctx.oauth.handleCallback()
+ await this.ctx.auth.use('web').login(session.user)
+ return session
+ }
+
+ async logout() {
+ if (this.ctx.auth.user?.did) {
+ await this.ctx.oauth.logout(this.ctx.auth.user.did)
+ }
+ await this.ctx.auth.use('web').logout()
+ }
+}
diff --git a/app/transformers/user_transformer.ts b/app/auth/transformers/user_transformer.ts
similarity index 87%
rename from app/transformers/user_transformer.ts
rename to app/auth/transformers/user_transformer.ts
index c772ff7..145a46f 100644
--- a/app/transformers/user_transformer.ts
+++ b/app/auth/transformers/user_transformer.ts
@@ -1,4 +1,4 @@
-import type User from '#models/user'
+import type User from '#auth/models/user'
import { BaseTransformer } from '@adonisjs/core/transformers'
export default class UserTransformer extends BaseTransformer {
diff --git a/app/validators/oauth.ts b/app/auth/validators/oauth.ts
similarity index 100%
rename from app/validators/oauth.ts
rename to app/auth/validators/oauth.ts
diff --git a/app/validators/user.ts b/app/auth/validators/user.ts
similarity index 100%
rename from app/validators/user.ts
rename to app/auth/validators/user.ts
diff --git a/app/controllers/home_controller.ts b/app/controllers/home_controller.ts
deleted file mode 100644
index a63325f..0000000
--- a/app/controllers/home_controller.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import type { HttpContext } from '@adonisjs/core/http'
-import Kit from '#models/kit'
-import KitTransformer from '#transformers/kit_transformer'
-
-export default class HomeController {
- async index({ inertia }: HttpContext) {
- const kits = await Kit.query().orderBy('indexed_at', 'desc').limit(50)
- return inertia.render('home', {
- kits: KitTransformer.transform(kits),
- })
- }
-}
diff --git a/app/controllers/kits_controller.ts b/app/controllers/kits_controller.ts
deleted file mode 100644
index c894d33..0000000
--- a/app/controllers/kits_controller.ts
+++ /dev/null
@@ -1,115 +0,0 @@
-import type { HttpContext } from '@adonisjs/core/http'
-import { l } from '@atproto/lex'
-import gunplaKit from '#lexicons/build/gunpla/kit'
-import Kit from '#models/kit'
-import { createKitValidator, updateKitValidator } from '#validators/kit'
-import KitRecordTransformer from '#transformers/kit_record_transformer'
-import { DateTime } from 'luxon'
-
-export default class KitsController {
- async index({ auth, inertia }: HttpContext) {
- const { records } = await auth.user!.client.list(gunplaKit)
- const kits = KitRecordTransformer.transform(
- records.filter((r) => r.valid).map((r) => ({ uri: r.uri as string, value: r.value }))
- )
- return inertia.render('dashboard', { kits })
- }
-
- async new({ inertia }: HttpContext) {
- return inertia.render('kits/new', {})
- }
-
- async store({ request, auth, response, session }: HttpContext) {
- const data = await request.validateUsing(createKitValidator)
-
- const record = await auth.user!.client.create(gunplaKit, {
- ...data,
- createdAt: l.currentDatetimeString(),
- })
-
- const existingKit = await Kit.query()
- .where('did', auth.user!.did)
- .whereNotNull('handle')
- .first()
- const handle = existingKit?.handle ?? null
-
- await Kit.updateOrCreate(
- { uri: record.uri },
- {
- did: auth.user!.did,
- handle,
- name: data.name,
- grade: data.grade ?? null,
- scale: data.scale ?? null,
- status: data.status,
- recordCreatedAt: DateTime.now(),
- indexedAt: DateTime.now(),
- }
- )
-
- session.flash('success', `Kit "${data.name}" added to your shelf`)
- return response.redirect().toRoute('dashboard')
- }
-
- async edit({ params, auth, inertia, response }: HttpContext) {
- const rkey = params.rkey as string
-
- try {
- const remote = await auth.user!.client.get(gunplaKit, { rkey })
- return inertia.render('kits/edit', {
- kit: {
- rkey,
- uri: remote.uri,
- name: remote.value.name,
- grade: remote.value.grade ?? '',
- scale: remote.value.scale ?? '',
- status: remote.value.status,
- },
- })
- } catch {
- return response.notFound('Kit not found')
- }
- }
-
- async update({ params, request, auth, response, session }: HttpContext) {
- const rkey = params.rkey as string
- const data = await request.validateUsing(updateKitValidator)
-
- try {
- const remote = await auth.user!.client.get(gunplaKit, { rkey })
- const val = remote.value
-
- const updatedVal = {
- ...val,
- name: data.name,
- status: data.status,
- grade: data.grade || undefined,
- scale: data.scale || undefined,
- }
-
- if (data.status === 'in-progress' && !val.startedAt) {
- updatedVal.startedAt = l.currentDatetimeString()
- } else if (data.status === 'done' && !val.completedAt) {
- updatedVal.completedAt = l.currentDatetimeString()
- }
-
- await auth.user!.client.put(gunplaKit, updatedVal, { rkey })
-
- const kitUri = `at://${auth.user!.did}/build.gunpla.kit/${rkey}`
- await Kit.query()
- .where('uri', kitUri)
- .update({
- name: data.name,
- status: data.status,
- grade: data.grade || null,
- scale: data.scale || null,
- })
-
- session.flash('success', `Updated "${data.name}"`)
- return response.redirect().toRoute('dashboard')
- } catch {
- session.flash('error', 'Failed to update kit on ATProto')
- return response.redirect().back()
- }
- }
-}
diff --git a/app/controllers/oauth_controller.ts b/app/controllers/oauth_controller.ts
deleted file mode 100644
index 7333a1f..0000000
--- a/app/controllers/oauth_controller.ts
+++ /dev/null
@@ -1,65 +0,0 @@
-import type { HttpContext } from '@adonisjs/core/http'
-import { OAuthResolverError } from '@atproto/oauth-client-node'
-import { loginRequestValidator, signupRequestValidator } from '#validators/oauth'
-
-export default class OAuthController {
- async login({ request, response, inertia, oauth, logger }: HttpContext) {
- // input should be a handle or service URL:
- const { input } = await request.validateUsing(loginRequestValidator)
- try {
- const authorizationUrl = await oauth.authorize(input)
-
- inertia.location(authorizationUrl)
- } catch (err) {
- logger.error(err, 'Error starting AT Protocol OAuth flow')
- if (err instanceof OAuthResolverError) {
- // Handle the input not being AT Protocol OAuth compatible
- }
-
- response.redirect().back()
- }
- }
-
- async signup({ request, response, inertia, oauth }: HttpContext) {
- // input should be a service URL:
- const { input } = await request.validateUsing(signupRequestValidator)
- const service = input ?? 'https://bsky.social'
- const registrationSupported = await oauth.canRegister(service)
-
- if (!registrationSupported) {
- // Handle registration not supported, you may want to special case for
- // bsky.social which has public registration behind a click.
-
- return response.abort('Registration not supported')
- }
-
- const authorizationUrl = await oauth.register(service)
-
- inertia.location(authorizationUrl)
- }
-
- async logout({ auth, oauth, response }: HttpContext) {
- await oauth.logout(auth.user?.did)
- await auth.use('web').logout()
-
- return response.redirect().back()
- }
-
- async callback({ response, oauth, auth, logger }: HttpContext) {
- try {
- const session = await oauth.handleCallback()
-
- await auth.use('web').login(session.user)
-
- // You'll probably want to check if you have an "account" according to Tap
- // or some other method, and if not redirect to an onboarding flow
-
- return response.redirect().toRoute('dashboard')
- } catch (err) {
- // Handle OAuth failing
- logger.error(err, 'Error completing AT Protocol OAuth flow')
-
- return response.redirect().toRoute('login')
- }
- }
-}
diff --git a/app/controllers/profile_controller.ts b/app/controllers/profile_controller.ts
deleted file mode 100644
index a2209f8..0000000
--- a/app/controllers/profile_controller.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import type { HttpContext } from '@adonisjs/core/http'
-import Kit from '#models/kit'
-import BuildLog from '#models/build_log'
-import KitTransformer from '#transformers/kit_transformer'
-import BuildLogTransformer from '#transformers/build_log_transformer'
-
-export default class ProfileController {
- async show({ params, inertia }: HttpContext) {
- const handle = params.handle as string
- const [kits, buildLogs] = await Promise.all([
- Kit.query().where('handle', handle).orderBy('indexed_at', 'desc').limit(100),
- BuildLog.query().where('handle', handle).orderBy('indexed_at', 'desc').limit(50),
- ])
- return inertia.render('profile', {
- handle,
- kits: KitTransformer.transform(kits),
- buildLogs: BuildLogTransformer.transform(buildLogs),
- })
- }
-}
diff --git a/app/exceptions/handler.ts b/app/core/exceptions/handler.ts
similarity index 100%
rename from app/exceptions/handler.ts
rename to app/core/exceptions/handler.ts
diff --git a/app/middleware/auth_middleware.ts b/app/core/middleware/auth_middleware.ts
similarity index 77%
rename from app/middleware/auth_middleware.ts
rename to app/core/middleware/auth_middleware.ts
index f5a2ba3..4bf594a 100644
--- a/app/middleware/auth_middleware.ts
+++ b/app/core/middleware/auth_middleware.ts
@@ -1,6 +1,7 @@
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'
import type { Authenticators } from '@adonisjs/auth/types'
+import { AtprotoUser } from '@thisismissem/adonisjs-atproto-oauth'
/**
* Auth middleware is used authenticate HTTP requests and deny
@@ -20,6 +21,11 @@ export default class AuthMiddleware {
} = {}
) {
await ctx.auth.authenticateUsing(options.guards, { loginRoute: this.redirectTo })
+
+ if (ctx.auth.user instanceof AtprotoUser) {
+ ctx.containerResolver.bindValue(AtprotoUser, ctx.auth.user)
+ }
+
return next()
}
}
diff --git a/app/middleware/container_bindings_middleware.ts b/app/core/middleware/container_bindings_middleware.ts
similarity index 100%
rename from app/middleware/container_bindings_middleware.ts
rename to app/core/middleware/container_bindings_middleware.ts
diff --git a/app/middleware/guest_middleware.ts b/app/core/middleware/guest_middleware.ts
similarity index 100%
rename from app/middleware/guest_middleware.ts
rename to app/core/middleware/guest_middleware.ts
diff --git a/app/middleware/inertia_middleware.ts b/app/core/middleware/inertia_middleware.ts
similarity index 100%
rename from app/middleware/inertia_middleware.ts
rename to app/core/middleware/inertia_middleware.ts
diff --git a/app/middleware/silent_auth_middleware.ts b/app/core/middleware/silent_auth_middleware.ts
similarity index 100%
rename from app/middleware/silent_auth_middleware.ts
rename to app/core/middleware/silent_auth_middleware.ts
diff --git a/start/routes/internal.ts b/app/core/routes.ts
similarity index 77%
rename from start/routes/internal.ts
rename to app/core/routes.ts
index 0d71b9e..3924652 100644
--- a/start/routes/internal.ts
+++ b/app/core/routes.ts
@@ -5,6 +5,8 @@ import env from '#start/env'
// Register Server-Sent Events routes for Transmit
transmit.registerRoutes()
+type BroadcastPayload = Parameters[1]
+
// Internal broadcast endpoint for worker container
router.post('/internal/broadcast', async ({ request, response }) => {
const secret = request.header('x-internal-secret')
@@ -13,10 +15,10 @@ router.post('/internal/broadcast', async ({ request, response }) => {
}
const { channel, payload } = request.body() as {
channel?: string
- payload?: Record
+ payload?: BroadcastPayload
}
- if (channel) {
- transmit.broadcast(channel, (payload ?? {}) as any)
+ if (channel && payload !== undefined) {
+ transmit.broadcast(channel, payload)
}
return response.ok({ broadcasted: true })
})
diff --git a/app/feed/controllers/feed_controller.ts b/app/feed/controllers/feed_controller.ts
new file mode 100644
index 0000000..242e529
--- /dev/null
+++ b/app/feed/controllers/feed_controller.ts
@@ -0,0 +1,13 @@
+import { inject } from '@adonisjs/core'
+import type { HttpContext } from '@adonisjs/core/http'
+import { FeedService } from '#feed/services/feed_service'
+
+@inject()
+export default class FeedController {
+ constructor(private feedService: FeedService) {}
+
+ async render({ inertia }: HttpContext) {
+ const kits = await this.feedService.getRecentKits(50)
+ return inertia.render('home', { kits })
+ }
+}
diff --git a/app/feed/routes.ts b/app/feed/routes.ts
new file mode 100644
index 0000000..e358506
--- /dev/null
+++ b/app/feed/routes.ts
@@ -0,0 +1,8 @@
+import router from '@adonisjs/core/services/router'
+import { controllers } from '#generated/controllers'
+
+const {
+ feed: { Feed: FeedController },
+} = controllers
+
+router.get('/', [FeedController, 'render']).as('home')
diff --git a/app/feed/services/feed_service.ts b/app/feed/services/feed_service.ts
new file mode 100644
index 0000000..985898e
--- /dev/null
+++ b/app/feed/services/feed_service.ts
@@ -0,0 +1,9 @@
+import Kit from '#kits/models/kit'
+import KitTransformer from '#kits/transformers/kit_transformer'
+
+export class FeedService {
+ async getRecentKits(limit = 50) {
+ const kits = await Kit.query().orderBy('indexed_at', 'desc').limit(limit)
+ return KitTransformer.transform(kits)
+ }
+}
diff --git a/app/kits/controllers/list_kits_controller.ts b/app/kits/controllers/list_kits_controller.ts
new file mode 100644
index 0000000..3a7c0a8
--- /dev/null
+++ b/app/kits/controllers/list_kits_controller.ts
@@ -0,0 +1,13 @@
+import { inject } from '@adonisjs/core'
+import type { HttpContext } from '@adonisjs/core/http'
+import { KitsService } from '#kits/services/kits_service'
+
+@inject()
+export default class ListKitsController {
+ constructor(private kitsService: KitsService) {}
+
+ async render({ inertia }: HttpContext) {
+ const kits = await this.kitsService.listForUser()
+ return inertia.render('dashboard', { kits })
+ }
+}
diff --git a/app/kits/controllers/store_kit_controller.ts b/app/kits/controllers/store_kit_controller.ts
new file mode 100644
index 0000000..875038b
--- /dev/null
+++ b/app/kits/controllers/store_kit_controller.ts
@@ -0,0 +1,22 @@
+import { inject } from '@adonisjs/core'
+import type { HttpContext } from '@adonisjs/core/http'
+import { createKitValidator } from '#kits/validators/kit'
+import { KitsService } from '#kits/services/kits_service'
+
+@inject()
+export default class StoreKitController {
+ constructor(private kitsService: KitsService) {}
+
+ async render({ inertia }: HttpContext) {
+ return inertia.render('kits/new', {})
+ }
+
+ async handle({ request, response, session }: HttpContext) {
+ const data = await request.validateUsing(createKitValidator)
+
+ await this.kitsService.create(data)
+
+ session.flash('success', `Kit "${data.name}" added to your shelf`)
+ return response.redirect().toRoute('dashboard')
+ }
+}
diff --git a/app/kits/controllers/update_kit_controller.ts b/app/kits/controllers/update_kit_controller.ts
new file mode 100644
index 0000000..052cd32
--- /dev/null
+++ b/app/kits/controllers/update_kit_controller.ts
@@ -0,0 +1,44 @@
+import { inject } from '@adonisjs/core'
+import type { HttpContext } from '@adonisjs/core/http'
+import { updateKitValidator } from '#kits/validators/kit'
+import { KitsService } from '#kits/services/kits_service'
+
+@inject()
+export default class UpdateKitController {
+ constructor(private kitsService: KitsService) {}
+
+ async render({ params, inertia, response }: HttpContext) {
+ const rkey = params.rkey as string
+
+ try {
+ const remote = await this.kitsService.getByRkey(rkey)
+ return inertia.render('kits/edit', {
+ kit: {
+ rkey,
+ uri: remote.uri,
+ name: remote.value.name,
+ grade: remote.value.grade ?? '',
+ scale: remote.value.scale ?? '',
+ status: remote.value.status,
+ },
+ })
+ } catch {
+ return response.notFound('Kit not found')
+ }
+ }
+
+ async handle({ params, request, response, session }: HttpContext) {
+ const rkey = params.rkey as string
+ const data = await request.validateUsing(updateKitValidator)
+
+ try {
+ await this.kitsService.update(rkey, data)
+
+ session.flash('success', `Updated "${data.name}"`)
+ return response.redirect().toRoute('dashboard')
+ } catch {
+ session.flash('error', 'Failed to update kit on ATProto')
+ return response.redirect().back()
+ }
+ }
+}
diff --git a/app/models/build_log.ts b/app/kits/models/build_log.ts
similarity index 100%
rename from app/models/build_log.ts
rename to app/kits/models/build_log.ts
diff --git a/app/models/kit.ts b/app/kits/models/kit.ts
similarity index 100%
rename from app/models/kit.ts
rename to app/kits/models/kit.ts
diff --git a/app/kits/routes.ts b/app/kits/routes.ts
new file mode 100644
index 0000000..17fdd49
--- /dev/null
+++ b/app/kits/routes.ts
@@ -0,0 +1,21 @@
+import router from '@adonisjs/core/services/router'
+import { middleware } from '#start/kernel'
+import { controllers } from '#generated/controllers'
+
+const {
+ kits: {
+ ListKits: ListKitsController,
+ StoreKit: StoreKitController,
+ UpdateKit: UpdateKitController,
+ },
+} = controllers
+
+router
+ .group(() => {
+ router.get('/dashboard', [ListKitsController, 'render']).as('dashboard')
+ router.get('/kits/new', [StoreKitController, 'render']).as('kits.new')
+ router.post('/kits', [StoreKitController, 'handle']).as('kits.store')
+ router.get('/kits/:rkey/edit', [UpdateKitController, 'render']).as('kits.edit')
+ router.put('/kits/:rkey', [UpdateKitController, 'handle']).as('kits.update')
+ })
+ .use(middleware.auth())
diff --git a/app/kits/services/kits_service.ts b/app/kits/services/kits_service.ts
new file mode 100644
index 0000000..7c823d8
--- /dev/null
+++ b/app/kits/services/kits_service.ts
@@ -0,0 +1,86 @@
+import { inject } from '@adonisjs/core'
+import { AtprotoUser } from '@thisismissem/adonisjs-atproto-oauth'
+import { l } from '@atproto/lex'
+import { DateTime } from 'luxon'
+import gunplaKit from '#lexicons/build/gunpla/kit'
+import Kit from '#kits/models/kit'
+import KitRecordTransformer from '#kits/transformers/kit_record_transformer'
+import type { Main } from '#lexicons/build/gunpla/kit.defs'
+import type { Infer } from '@vinejs/vine/types'
+import type { createKitValidator, updateKitValidator } from '#kits/validators/kit'
+
+@inject()
+export class KitsService {
+ constructor(protected user: AtprotoUser) {}
+
+ async listForUser() {
+ const { records } = await this.user.client.list(gunplaKit)
+ const items: Array<{ uri: string; value: Main }> = records
+ .filter((r) => r.valid)
+ .map((r) => ({ uri: r.uri, value: r.value }))
+ return KitRecordTransformer.transform(items)
+ }
+
+ async getByRkey(rkey: string) {
+ return this.user.client.get(gunplaKit, { rkey })
+ }
+
+ async create(data: Infer) {
+ const record = await this.user.client.create(gunplaKit, {
+ ...data,
+ createdAt: l.currentDatetimeString(),
+ })
+
+ const existingKit = await Kit.query().where('did', this.user.did).whereNotNull('handle').first()
+ const handle = existingKit?.handle ?? null
+
+ await Kit.updateOrCreate(
+ { uri: record.uri },
+ {
+ did: this.user.did,
+ handle,
+ name: data.name,
+ grade: data.grade ?? null,
+ scale: data.scale ?? null,
+ status: data.status,
+ recordCreatedAt: DateTime.now(),
+ indexedAt: DateTime.now(),
+ }
+ )
+
+ return record
+ }
+
+ async update(rkey: string, data: Infer) {
+ const remote = await this.user.client.get(gunplaKit, { rkey })
+ const val = remote.value
+
+ const updatedVal: typeof val = {
+ ...val,
+ name: data.name,
+ status: data.status,
+ grade: data.grade || undefined,
+ scale: data.scale || undefined,
+ }
+
+ if (data.status === 'in-progress' && !val.startedAt) {
+ updatedVal.startedAt = l.currentDatetimeString()
+ } else if (data.status === 'done' && !val.completedAt) {
+ updatedVal.completedAt = l.currentDatetimeString()
+ }
+
+ await this.user.client.put(gunplaKit, updatedVal, { rkey })
+
+ const kitUri = `at://${this.user.did}/build.gunpla.kit/${rkey}`
+ await Kit.query()
+ .where('uri', kitUri)
+ .update({
+ name: data.name,
+ status: data.status,
+ grade: data.grade || null,
+ scale: data.scale || null,
+ })
+
+ return { uri: kitUri, value: updatedVal }
+ }
+}
diff --git a/app/transformers/build_log_transformer.ts b/app/kits/transformers/build_log_transformer.ts
similarity index 89%
rename from app/transformers/build_log_transformer.ts
rename to app/kits/transformers/build_log_transformer.ts
index 227b9d2..62aec0c 100644
--- a/app/transformers/build_log_transformer.ts
+++ b/app/kits/transformers/build_log_transformer.ts
@@ -1,5 +1,5 @@
import { BaseTransformer } from '@adonisjs/core/transformers'
-import type BuildLog from '#models/build_log'
+import type BuildLog from '#kits/models/build_log'
export default class BuildLogTransformer extends BaseTransformer {
toObject() {
diff --git a/app/transformers/kit_record_transformer.ts b/app/kits/transformers/kit_record_transformer.ts
similarity index 100%
rename from app/transformers/kit_record_transformer.ts
rename to app/kits/transformers/kit_record_transformer.ts
diff --git a/app/transformers/kit_transformer.ts b/app/kits/transformers/kit_transformer.ts
similarity index 92%
rename from app/transformers/kit_transformer.ts
rename to app/kits/transformers/kit_transformer.ts
index 2d15efb..3f7a423 100644
--- a/app/transformers/kit_transformer.ts
+++ b/app/kits/transformers/kit_transformer.ts
@@ -1,5 +1,5 @@
import { BaseTransformer } from '@adonisjs/core/transformers'
-import type Kit from '#models/kit'
+import type Kit from '#kits/models/kit'
export default class KitTransformer extends BaseTransformer {
toObject() {
diff --git a/app/validators/kit.ts b/app/kits/validators/kit.ts
similarity index 100%
rename from app/validators/kit.ts
rename to app/kits/validators/kit.ts
diff --git a/app/profile/controllers/show_profile_controller.ts b/app/profile/controllers/show_profile_controller.ts
new file mode 100644
index 0000000..fb7484c
--- /dev/null
+++ b/app/profile/controllers/show_profile_controller.ts
@@ -0,0 +1,19 @@
+import { inject } from '@adonisjs/core'
+import type { HttpContext } from '@adonisjs/core/http'
+import { ProfileService } from '#profile/services/profile_service'
+
+@inject()
+export default class ShowProfileController {
+ constructor(private profileService: ProfileService) {}
+
+ async render({ params, inertia }: HttpContext) {
+ const handle = params.handle as string
+ const { kits, buildLogs } = await this.profileService.getProfileData(handle)
+
+ return inertia.render('profile', {
+ handle,
+ kits,
+ buildLogs,
+ })
+ }
+}
diff --git a/app/profile/routes.ts b/app/profile/routes.ts
new file mode 100644
index 0000000..421564c
--- /dev/null
+++ b/app/profile/routes.ts
@@ -0,0 +1,8 @@
+import router from '@adonisjs/core/services/router'
+import { controllers } from '#generated/controllers'
+
+const {
+ profile: { ShowProfile: ShowProfileController },
+} = controllers
+
+router.get('/profile/:handle', [ShowProfileController, 'render']).as('profile')
diff --git a/app/profile/services/profile_service.ts b/app/profile/services/profile_service.ts
new file mode 100644
index 0000000..e6c3597
--- /dev/null
+++ b/app/profile/services/profile_service.ts
@@ -0,0 +1,18 @@
+import Kit from '#kits/models/kit'
+import BuildLog from '#kits/models/build_log'
+import KitTransformer from '#kits/transformers/kit_transformer'
+import BuildLogTransformer from '#kits/transformers/build_log_transformer'
+
+export class ProfileService {
+ async getProfileData(handle: string) {
+ const [kits, buildLogs] = await Promise.all([
+ Kit.query().where('handle', handle).orderBy('indexed_at', 'desc').limit(100),
+ BuildLog.query().where('handle', handle).orderBy('indexed_at', 'desc').limit(50),
+ ])
+
+ return {
+ kits: KitTransformer.transform(kits),
+ buildLogs: BuildLogTransformer.transform(buildLogs),
+ }
+ }
+}
diff --git a/config/atproto_oauth.ts b/config/atproto_oauth.ts
index dbf2770..477e059 100644
--- a/config/atproto_oauth.ts
+++ b/config/atproto_oauth.ts
@@ -32,7 +32,7 @@ export default defineConfig({
jwks: [env.get('ATPROTO_OAUTH_JWT_PRIVATE_KEY')],
// Models to store OAuth State and Sessions:
stores: {
- states: lucidStateStore(() => import('#models/oauth_state')),
- sessions: lucidSessionStore(() => import('#models/oauth_session')),
+ states: lucidStateStore(() => import('#auth/models/oauth_state')),
+ sessions: lucidSessionStore(() => import('#auth/models/oauth_session')),
},
})
diff --git a/inertia/pages/dashboard.tsx b/inertia/pages/dashboard.tsx
index 172ada0..b7caee9 100644
--- a/inertia/pages/dashboard.tsx
+++ b/inertia/pages/dashboard.tsx
@@ -4,7 +4,7 @@ import { Link } from '@adonisjs/inertia/react'
import { Plus, Pencil } from 'lucide-react'
import type { InertiaProps } from '~/types'
import type { InferData } from '@adonisjs/http-transformers/types'
-import type KitRecordTransformer from '#transformers/kit_record_transformer'
+import type KitRecordTransformer from '#kits/transformers/kit_record_transformer'
type KitItem = InferData
diff --git a/inertia/pages/home.tsx b/inertia/pages/home.tsx
index 6e0d6a5..858b86c 100644
--- a/inertia/pages/home.tsx
+++ b/inertia/pages/home.tsx
@@ -5,7 +5,7 @@ import { transmit } from '~/services/transmit'
import MarketingLayout from '~/layouts/marketing'
import type { InertiaProps } from '~/types'
import type { InferData } from '@adonisjs/http-transformers/types'
-import type KitTransformer from '#transformers/kit_transformer'
+import type KitTransformer from '#kits/transformers/kit_transformer'
type KitRow = InferData
diff --git a/inertia/pages/profile.tsx b/inertia/pages/profile.tsx
index 409cc8e..b6512cc 100644
--- a/inertia/pages/profile.tsx
+++ b/inertia/pages/profile.tsx
@@ -1,8 +1,8 @@
import MarketingLayout from '~/layouts/marketing'
import type { InertiaProps } from '~/types'
import type { InferData } from '@adonisjs/http-transformers/types'
-import type KitTransformer from '#transformers/kit_transformer'
-import type BuildLogTransformer from '#transformers/build_log_transformer'
+import type KitTransformer from '#kits/transformers/kit_transformer'
+import type BuildLogTransformer from '#kits/transformers/build_log_transformer'
type KitRow = InferData
type BuildLogRow = InferData
diff --git a/inertia/tsconfig.json b/inertia/tsconfig.json
index d1d10d1..9850403 100644
--- a/inertia/tsconfig.json
+++ b/inertia/tsconfig.json
@@ -7,7 +7,13 @@
"~/*": ["./*"],
"@generated/*": ["../.adonisjs/client/*"],
"#models/*": ["../app/models/*"],
- "#transformers/*": ["../app/transformers/*"]
+ "#transformers/*": ["../app/transformers/*"],
+ "#app/*": ["../app/*"],
+ "#kits/*": ["../app/kits/*"],
+ "#auth/*": ["../app/auth/*"],
+ "#feed/*": ["../app/feed/*"],
+ "#profile/*": ["../app/profile/*"],
+ "#core/*": ["../app/core/*"]
}
},
"include": [
diff --git a/package.json b/package.json
index 9a49a65..127c980 100644
--- a/package.json
+++ b/package.json
@@ -18,16 +18,22 @@
"typecheck": "tsc --noEmit && tsc --noEmit --project inertia/tsconfig.json"
},
"imports": {
+ "#app/*": "./app/*.js",
+ "#kits/*": "./app/kits/*.js",
+ "#auth/*": "./app/auth/*.js",
+ "#feed/*": "./app/feed/*.js",
+ "#profile/*": "./app/profile/*.js",
+ "#core/*": "./app/core/*.js",
"#lexicons/*": "./app/lexicons/*.js",
"#controllers/*": "./app/controllers/*.js",
- "#exceptions/*": "./app/exceptions/*.js",
+ "#exceptions/*": "./app/core/exceptions/*.js",
"#models/*": "./app/models/*.js",
"#mails/*": "./app/mails/*.js",
- "#services/*": "./app/services/*.js",
+ "#services/*": "./services/*.js",
"#listeners/*": "./app/listeners/*.js",
"#events/*": "./app/events/*.js",
"#generated/*": "./.adonisjs/server/*.js",
- "#middleware/*": "./app/middleware/*.js",
+ "#middleware/*": "./app/core/middleware/*.js",
"#transformers/*": "./app/transformers/*.js",
"#validators/*": "./app/validators/*.js",
"#providers/*": "./providers/*.js",
@@ -97,8 +103,8 @@
},
"hotHook": {
"boundaries": [
- "./app/controllers/**/*.ts",
- "./app/middleware/*.ts"
+ "./app/**/controllers/**/*.ts",
+ "./app/**/middleware/**/*.ts"
]
},
"allowScripts": {
diff --git a/app/services/jetstream_service.ts b/services/jetstream_service.ts
similarity index 98%
rename from app/services/jetstream_service.ts
rename to services/jetstream_service.ts
index ebf1607..12c5e37 100644
--- a/app/services/jetstream_service.ts
+++ b/services/jetstream_service.ts
@@ -1,8 +1,9 @@
import app from '@adonisjs/core/services/app'
import logger from '@adonisjs/core/services/logger'
+import { inject } from '@adonisjs/core'
import env from '#start/env'
-import Kit from '#models/kit'
-import BuildLog from '#models/build_log'
+import Kit from '#kits/models/kit'
+import BuildLog from '#kits/models/build_log'
import { DateTime } from 'luxon'
import fs from 'node:fs'
@@ -31,6 +32,7 @@ export interface JetstreamServiceOptions {
cursorFilePath?: string
}
+@inject()
export class JetstreamService {
private endpoint: string
private wantedCollections: string[]
diff --git a/start/kernel.ts b/start/kernel.ts
index 15ef29b..dc1fe06 100644
--- a/start/kernel.ts
+++ b/start/kernel.ts
@@ -15,7 +15,7 @@ import server from '@adonisjs/core/services/server'
* The error handler is used to convert an exception
* to a HTTP response.
*/
-server.errorHandler(() => import('#exceptions/handler'))
+server.errorHandler(() => import('#core/exceptions/handler'))
/**
* The server middleware stack runs middleware on all the HTTP
@@ -23,11 +23,11 @@ server.errorHandler(() => import('#exceptions/handler'))
* the request URL.
*/
server.use([
- () => import('#middleware/container_bindings_middleware'),
+ () => import('#core/middleware/container_bindings_middleware'),
() => import('@adonisjs/static/static_middleware'),
() => import('@adonisjs/cors/cors_middleware'),
() => import('@adonisjs/vite/vite_middleware'),
- () => import('#middleware/inertia_middleware'),
+ () => import('#core/middleware/inertia_middleware'),
])
/**
@@ -39,7 +39,7 @@ router.use([
() => import('@adonisjs/session/session_middleware'),
() => import('@adonisjs/shield/shield_middleware'),
() => import('@adonisjs/auth/initialize_auth_middleware'),
- () => import('#middleware/silent_auth_middleware'),
+ () => import('#core/middleware/silent_auth_middleware'),
() => import('@thisismissem/adonisjs-atproto-oauth/initialize_atproto_auth_middleware'),
])
@@ -48,6 +48,6 @@ router.use([
* the routes or the routes group.
*/
export const middleware = router.named({
- guest: () => import('#middleware/guest_middleware'),
- auth: () => import('#middleware/auth_middleware'),
+ guest: () => import('#core/middleware/guest_middleware'),
+ auth: () => import('#core/middleware/auth_middleware'),
})
diff --git a/start/routes.ts b/start/routes.ts
index 6c820c4..b134ad2 100644
--- a/start/routes.ts
+++ b/start/routes.ts
@@ -7,7 +7,8 @@
|
*/
-import '#start/routes/internal'
-import '#start/routes/web'
-import '#start/routes/oauth'
-import '#start/routes/kits'
+import '#core/routes'
+import '#feed/routes'
+import '#auth/routes'
+import '#kits/routes'
+import '#profile/routes'
diff --git a/start/routes/kits.ts b/start/routes/kits.ts
deleted file mode 100644
index 86c121d..0000000
--- a/start/routes/kits.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import router from '@adonisjs/core/services/router'
-import { middleware } from '#start/kernel'
-import { controllers } from '#generated/controllers'
-
-router
- .group(() => {
- router.get('/dashboard', [controllers.Kits, 'index']).as('dashboard')
- router.get('/kits/new', [controllers.Kits, 'new']).as('kits.new')
- router.post('/kits', [controllers.Kits, 'store']).as('kits.store')
- router.get('/kits/:rkey/edit', [controllers.Kits, 'edit']).as('kits.edit')
- router.put('/kits/:rkey', [controllers.Kits, 'update']).as('kits.update')
- })
- .use(middleware.auth())
diff --git a/start/routes/oauth.ts b/start/routes/oauth.ts
deleted file mode 100644
index ecc8ad3..0000000
--- a/start/routes/oauth.ts
+++ /dev/null
@@ -1,17 +0,0 @@
-import router from '@adonisjs/core/services/router'
-import { middleware } from '#start/kernel'
-import { controllers } from '#generated/controllers'
-
-router
- .group(() => {
- router.on('/login').renderInertia('auth/login', {}).as('login')
- })
- .use(middleware.guest())
-
-router.post('/oauth/logout', [controllers.Oauth, 'logout']).use(middleware.auth())
-
-router.group(() => {
- router.post('/oauth/login', [controllers.Oauth, 'login'])
- router.post('/oauth/signup', [controllers.Oauth, 'signup'])
- router.get('/oauth/callback', [controllers.Oauth, 'callback'])
-})
diff --git a/start/routes/web.ts b/start/routes/web.ts
deleted file mode 100644
index 1a07ea6..0000000
--- a/start/routes/web.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import router from '@adonisjs/core/services/router'
-import { controllers } from '#generated/controllers'
-
-router.get('/', [controllers.Home, 'index']).as('home')
-router.get('/profile/:handle', [controllers.Profile, 'show']).as('profile')