Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ make migrate-reset

```sh
bun commands
bun start
bun start-discord # run discord bot
```

<h3 id="develop-docker">🐳 Develop w/ Docker</h3>
Expand Down
26 changes: 26 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,15 @@ RUN apt-get update && apt-get install -y --no-install-recommends openssl ca-cert
# Dev deps (for Prisma generate)
RUN mkdir -p /temp/dev
COPY package.json bun.lock /temp/dev/
COPY packages/aksaria-core/package.json /temp/dev/packages/aksaria-core/
COPY packages/aksaria-discord/package.json /temp/dev/packages/aksaria-discord/
RUN cd /temp/dev && bun install

# install with --production (exclude devDependencies)
RUN mkdir -p /temp/prod
COPY package.json bun.lock /temp/prod/
COPY packages/aksaria-core/package.json /temp/prod/packages/aksaria-core/
COPY packages/aksaria-discord/package.json /temp/prod/packages/aksaria-discord/
COPY db /temp/prod/db
COPY prisma.config.ts /temp/prod/
RUN cd /temp/prod && bun install --production && bun prisma
Expand All @@ -27,7 +31,11 @@ ENV NODE_ENV=production
COPY --chmod=755 docker/entrypoint.sh /entrypoint.sh
COPY --from=install /temp/prod/node_modules node_modules
COPY --from=install /temp/prod/db ./db
COPY src ./src

# Copy monorepo packages
COPY packages/aksaria-core ./packages/aksaria-core
COPY packages/aksaria-discord ./packages/aksaria-discord

COPY prisma.config.ts .
COPY package.json .
COPY tsconfig.json .
Expand Down
2 changes: 1 addition & 1 deletion docker/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ echo "▶ Starting application..."
if [ "$NODE_ENV" = "production" ]; then
exec bun prod
else
exec bun start
exec bun start-discord
fi
13 changes: 8 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,16 @@
"keywords": [
"bot"
],
"main": "index.js",
"workspaces": [
"packages/*"
],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"prod": "bun src/index.ts",
"start": "bun --watch src/index.ts",
"commands": "bun src/deploy-commands.ts",
"prisma": "bunx prisma generate --schema db/schema.prisma"
"prod": "bun packages/aksaria-discord/src/index.ts",
"start-discord": "bun --watch packages/aksaria-discord/src/index.ts",
"commands": "bun packages/aksaria-discord/src/deploy-commands.ts",
"prisma": "bunx prisma generate --schema db/schema.prisma",
"build": "npm run build -w aksaria-core && npm run build -w aksaria-discord"
},
"dependencies": {
"@prisma/client": "6.13.0",
Expand Down
17 changes: 17 additions & 0 deletions packages/aksaria-core/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "aksaria-core",
"version": "0.1.0",
"description": "Core business logic for Aksaria platform",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
},
"dependencies": {
"@prisma/client": "6.13.0"
},
"devDependencies": {
"typescript": "^5.0.0"
}
}
28 changes: 28 additions & 0 deletions packages/aksaria-core/src/db/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { PrismaClient } from '@generatedDB/client'

// Singleton Prisma client instance
let prismaInstance: PrismaClient | null = null

/**
* Get the Prisma client singleton instance
*/
export function getPrismaClient(): PrismaClient {
if (!prismaInstance) {
prismaInstance = new PrismaClient()
}
return prismaInstance
}

/**
* Disconnect the Prisma client (useful for cleanup)
*/
export async function disconnectPrisma(): Promise<void> {
if (prismaInstance) {
await prismaInstance.$disconnect()
prismaInstance = null
}
}

// Re-export PrismaClient type for convenience
export type { PrismaClient }
export type { Prisma } from '@generatedDB/client'
1 change: 1 addition & 0 deletions packages/aksaria-core/src/db/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './client'
20 changes: 20 additions & 0 deletions packages/aksaria-core/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* Aksaria Core
* Platform-agnostic business logic for the Aksaria daily checkin system
*/

// Types
export * from './types'

// Interfaces
export * from './interfaces'

// Services
export * from './services'

// Utilities
export * from './utils'

// Database
export { getPrismaClient, disconnectPrisma } from './db'
export type { PrismaClient, Prisma } from './db'
1 change: 1 addition & 0 deletions packages/aksaria-core/src/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './platform-adapter'
59 changes: 59 additions & 0 deletions packages/aksaria-core/src/interfaces/platform-adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { ICheckin } from '../types'

/**
* Embed data structure for platform-agnostic message formatting
*/
export interface EmbedData {
title: string
description: string
color?: string
footer?: string
fields?: EmbedField[]
}

export interface EmbedField {
name: string
value: string
inline?: boolean
}

/**
* Platform adapter interface
* Implement this interface for each platform (Discord, Telegram, REST)
*/
export interface IPlatformAdapter {
/**
* Platform identifier
*/
readonly platform: string

/**
* Send a plain text message to a user
*/
sendMessage(userId: string, content: string): Promise<void>

/**
* Send a rich embed message to a user
*/
sendEmbed(userId: string, embed: EmbedData): Promise<void>

/**
* Notify user that their checkin was approved
*/
notifyCheckinApproved(userId: string, checkin: ICheckin, reviewerName: string): Promise<void>

/**
* Notify user that their checkin was rejected
*/
notifyCheckinRejected(userId: string, checkin: ICheckin, reviewerName: string, reason?: string): Promise<void>

/**
* Notify user of successful checkin submission
*/
notifyCheckinSubmitted(userId: string, checkin: ICheckin): Promise<void>

/**
* Get display name for a user
*/
getUserDisplayName(userId: string): Promise<string>
}
69 changes: 69 additions & 0 deletions packages/aksaria-core/src/services/attachment.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import type { PrismaClient } from '../db/client'
import type { AttachmentInput, IAttachment } from '../types'

/**
* Attachment service - handles attachment-related operations
*/
export class AttachmentService {
constructor(private prisma: PrismaClient) {}

/**
* Create attachments for a checkin
*/
async createAttachments(checkinId: number, attachments: AttachmentInput[]): Promise<IAttachment[]> {
if (attachments.length === 0) return []

await this.prisma.attachment.createMany({
data: attachments.map(a => ({
name: a.name,
url: a.url,
type: a.type,
size: a.size,
module_id: checkinId,
module_type: 'CHECKIN',
})),
})

return this.getAttachmentsByCheckin(checkinId)
}

/**
* Get all attachments for a checkin
*/
async getAttachmentsByCheckin(checkinId: number): Promise<IAttachment[]> {
const attachments = await this.prisma.attachment.findMany({
where: {
module_id: checkinId,
module_type: 'CHECKIN',
},
orderBy: { created_at: 'asc' },
})

return attachments.map(this.mapToIAttachment)
}

/**
* Delete all attachments for a checkin
*/
async deleteAttachmentsByCheckin(checkinId: number): Promise<void> {
await this.prisma.attachment.deleteMany({
where: {
module_id: checkinId,
module_type: 'CHECKIN',
},
})
}

private mapToIAttachment(attachment: any): IAttachment {
return {
id: attachment.id,
name: attachment.name,
url: attachment.url,
type: attachment.type,
size: attachment.size,
createdAt: attachment.created_at,
moduleId: attachment.module_id,
moduleType: attachment.module_type,
}
}
}
Loading