feat: implement automation feature
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import * as schema from '@db/schema';
|
||||
|
||||
import { and, desc, eq, isNull } from 'drizzle-orm';
|
||||
import { and, count, desc, eq, isNull } from 'drizzle-orm';
|
||||
|
||||
import { db } from '@db/drizzle';
|
||||
import type { TNewOrganizationApiKey, TOrganizationApiKey, TOrganizationApiKeyType } from '@db/types';
|
||||
@@ -42,6 +42,29 @@ export const listOrganizationApiKeys = async (
|
||||
}
|
||||
};
|
||||
|
||||
export const countActiveOrganizationApiKeys = async (
|
||||
organizationId: string,
|
||||
type: TOrganizationApiKeyType
|
||||
): Promise<number> => {
|
||||
try {
|
||||
const [row] = await db
|
||||
.select({ total: count() })
|
||||
.from(schema.organizationApiKey)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.organizationApiKey.organizationId, organizationId),
|
||||
eq(schema.organizationApiKey.type, type),
|
||||
isNull(schema.organizationApiKey.revokedAt)
|
||||
)
|
||||
);
|
||||
|
||||
return Number(row?.total ?? 0);
|
||||
} catch (error) {
|
||||
console.error('countActiveOrganizationApiKeys error:', error);
|
||||
throw new Error('Failed to count active organization API keys');
|
||||
}
|
||||
};
|
||||
|
||||
export const getOrganizationApiKeyById = async (
|
||||
organizationId: string,
|
||||
keyId: string
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import * as schema from '@db/schema';
|
||||
|
||||
import { and, count, desc, eq, gte, sql } from 'drizzle-orm';
|
||||
|
||||
import { db } from '@db/drizzle';
|
||||
import type { TAutomationUsageCategory, TNewOrganizationAutomationUsage, TOrganizationApiKeyType } from '@db/types';
|
||||
|
||||
export const createOrganizationAutomationUsage = async (data: TNewOrganizationAutomationUsage) => {
|
||||
try {
|
||||
const [row] = await db.insert(schema.organizationAutomationUsage).values(data).returning();
|
||||
|
||||
if (!row) {
|
||||
throw new Error('Failed to create organization automation usage');
|
||||
}
|
||||
|
||||
return row;
|
||||
} catch (error) {
|
||||
console.error('createOrganizationAutomationUsage error:', error);
|
||||
throw new Error('Failed to create organization automation usage');
|
||||
}
|
||||
};
|
||||
|
||||
export const countOrganizationAutomationUsageSinceByKey = async (
|
||||
organizationApiKeyId: string,
|
||||
category: TAutomationUsageCategory,
|
||||
since: string
|
||||
): Promise<number> => {
|
||||
try {
|
||||
const [row] = await db
|
||||
.select({ total: count() })
|
||||
.from(schema.organizationAutomationUsage)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.organizationAutomationUsage.organizationApiKeyId, organizationApiKeyId),
|
||||
eq(schema.organizationAutomationUsage.category, category),
|
||||
gte(schema.organizationAutomationUsage.createdAt, since)
|
||||
)
|
||||
);
|
||||
|
||||
return Number(row?.total ?? 0);
|
||||
} catch (error) {
|
||||
console.error('countOrganizationAutomationUsageSinceByKey error:', error);
|
||||
throw new Error('Failed to count organization automation usage by key');
|
||||
}
|
||||
};
|
||||
|
||||
export const countOrganizationAutomationUsageSince = async (
|
||||
organizationId: string,
|
||||
type: TOrganizationApiKeyType,
|
||||
category: TAutomationUsageCategory,
|
||||
since: string
|
||||
): Promise<number> => {
|
||||
try {
|
||||
const [row] = await db
|
||||
.select({ total: count() })
|
||||
.from(schema.organizationAutomationUsage)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.organizationAutomationUsage.organizationId, organizationId),
|
||||
eq(schema.organizationAutomationUsage.type, type),
|
||||
eq(schema.organizationAutomationUsage.category, category),
|
||||
gte(schema.organizationAutomationUsage.createdAt, since)
|
||||
)
|
||||
);
|
||||
|
||||
return Number(row?.total ?? 0);
|
||||
} catch (error) {
|
||||
console.error('countOrganizationAutomationUsageSince error:', error);
|
||||
throw new Error('Failed to count organization automation usage');
|
||||
}
|
||||
};
|
||||
|
||||
export const getOrganizationAutomationCreditsUsedSince = async (
|
||||
organizationId: string,
|
||||
type: TOrganizationApiKeyType,
|
||||
since: string
|
||||
): Promise<number> => {
|
||||
try {
|
||||
const [row] = await db
|
||||
.select({
|
||||
total: sql<number>`COALESCE(SUM(${schema.organizationAutomationUsage.creditsConsumed}), 0)`
|
||||
})
|
||||
.from(schema.organizationAutomationUsage)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.organizationAutomationUsage.organizationId, organizationId),
|
||||
eq(schema.organizationAutomationUsage.type, type),
|
||||
gte(schema.organizationAutomationUsage.createdAt, since)
|
||||
)
|
||||
);
|
||||
|
||||
return Number(row?.total ?? 0);
|
||||
} catch (error) {
|
||||
console.error('getOrganizationAutomationCreditsUsedSince error:', error);
|
||||
throw new Error('Failed to get organization automation credits used');
|
||||
}
|
||||
};
|
||||
|
||||
export const listRecentOrganizationAutomationUsage = async (
|
||||
organizationId: string,
|
||||
type: TOrganizationApiKeyType,
|
||||
limit = 10
|
||||
) => {
|
||||
try {
|
||||
return await db
|
||||
.select({
|
||||
action: schema.organizationAutomationUsage.action,
|
||||
creditsConsumed: schema.organizationAutomationUsage.creditsConsumed,
|
||||
createdAt: schema.organizationAutomationUsage.createdAt
|
||||
})
|
||||
.from(schema.organizationAutomationUsage)
|
||||
.where(
|
||||
and(
|
||||
eq(schema.organizationAutomationUsage.organizationId, organizationId),
|
||||
eq(schema.organizationAutomationUsage.type, type)
|
||||
)
|
||||
)
|
||||
.orderBy(desc(schema.organizationAutomationUsage.createdAt))
|
||||
.limit(limit);
|
||||
} catch (error) {
|
||||
console.error('listRecentOrganizationAutomationUsage error:', error);
|
||||
throw new Error('Failed to list recent organization automation usage');
|
||||
}
|
||||
};
|
||||
@@ -2,3 +2,4 @@ export * from './organization';
|
||||
export * from './quiz';
|
||||
export * from './invite';
|
||||
export * from './automation-key';
|
||||
export * from './automation-usage';
|
||||
|
||||
@@ -677,3 +677,13 @@ export const getOrganizationPlanStatus = async (orgId: string) => {
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const getActiveOrganizationPlan = async (orgId: string) => {
|
||||
const [plan] = await db
|
||||
.select()
|
||||
.from(schema.organizationPlan)
|
||||
.where(and(eq(schema.organizationPlan.orgId, orgId), eq(schema.organizationPlan.isActive, true)))
|
||||
.limit(1);
|
||||
|
||||
return plan ?? null;
|
||||
};
|
||||
|
||||
@@ -25,9 +25,10 @@ import { sql } from 'drizzle-orm';
|
||||
export const courseType = pgEnum('COURSE_TYPE', ['SELF_PACED', 'LIVE_CLASS']);
|
||||
export const locale = pgEnum('LOCALE', ['en', 'hi', 'fr', 'pt', 'de', 'vi', 'ru', 'es', 'pl', 'da']);
|
||||
export const plan = pgEnum('PLAN', ['EARLY_ADOPTER', 'ENTERPRISE', 'BASIC']);
|
||||
export const courseImportSourceType = pgEnum('COURSE_IMPORT_SOURCE_TYPE', ['prompt', 'pdf']);
|
||||
export const courseImportSourceType = pgEnum('COURSE_IMPORT_SOURCE_TYPE', ['prompt', 'pdf', 'course']);
|
||||
export const courseImportDraftStatus = pgEnum('COURSE_IMPORT_DRAFT_STATUS', ['DRAFT', 'PUBLISHED', 'ARCHIVED']);
|
||||
export const organizationApiKeyType = pgEnum('ORGANIZATION_API_KEY_TYPE', ['mcp', 'api', 'zapier']);
|
||||
export const automationUsageCategory = pgEnum('AUTOMATION_USAGE_CATEGORY', ['read', 'write', 'publish']);
|
||||
export const courseInviteEventType = pgEnum('COURSE_INVITE_EVENT_TYPE', [
|
||||
'CREATED',
|
||||
'REVOKED',
|
||||
@@ -2035,6 +2036,35 @@ export const organizationApiKey = pgTable(
|
||||
]
|
||||
);
|
||||
|
||||
export const organizationAutomationUsage = pgTable(
|
||||
'organization_automation_usage',
|
||||
{
|
||||
id: uuid()
|
||||
.default(sql`gen_random_uuid()`)
|
||||
.primaryKey()
|
||||
.notNull(),
|
||||
organizationId: uuid('organization_id')
|
||||
.notNull()
|
||||
.references(() => organization.id),
|
||||
organizationApiKeyId: uuid('organization_api_key_id').references(() => organizationApiKey.id, {
|
||||
onDelete: 'set null'
|
||||
}),
|
||||
type: organizationApiKeyType('type').notNull(),
|
||||
action: varchar('action', { length: 120 }).notNull(),
|
||||
category: automationUsageCategory('category').notNull(),
|
||||
creditsConsumed: integer('credits_consumed').default(0).notNull(),
|
||||
metadata: jsonb('metadata').default({}).$type<Record<string, unknown>>().notNull(),
|
||||
createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' })
|
||||
.default(sql`timezone('utc'::text, now())`)
|
||||
.notNull()
|
||||
},
|
||||
(table) => [
|
||||
index('idx_org_automation_usage_org_created_at').on(table.organizationId, table.createdAt),
|
||||
index('idx_org_automation_usage_key_created_at').on(table.organizationApiKeyId, table.createdAt),
|
||||
index('idx_org_automation_usage_type_created_at').on(table.type, table.createdAt)
|
||||
]
|
||||
);
|
||||
|
||||
export const courseImportDraft = pgTable(
|
||||
'course_import_draft',
|
||||
{
|
||||
|
||||
@@ -171,6 +171,8 @@ export type TNewOrganizationTokenAuth = typeof schema.organizationTokenAuth.$inf
|
||||
|
||||
export type TOrganizationApiKey = typeof schema.organizationApiKey.$inferSelect;
|
||||
export type TNewOrganizationApiKey = typeof schema.organizationApiKey.$inferInsert;
|
||||
export type TOrganizationAutomationUsage = typeof schema.organizationAutomationUsage.$inferSelect;
|
||||
export type TNewOrganizationAutomationUsage = typeof schema.organizationAutomationUsage.$inferInsert;
|
||||
|
||||
export type TCourseImportDraft = typeof schema.courseImportDraft.$inferSelect;
|
||||
export type TNewCourseImportDraft = typeof schema.courseImportDraft.$inferInsert;
|
||||
@@ -188,6 +190,7 @@ export type TOrganizationInviteEventType = (typeof schema.organizationInviteEven
|
||||
export type TSsoProvider = (typeof schema.organizationSsoProviderType.enumValues)[number];
|
||||
|
||||
export type TOrganizationApiKeyType = (typeof schema.organizationApiKeyType.enumValues)[number];
|
||||
export type TAutomationUsageCategory = (typeof schema.automationUsageCategory.enumValues)[number];
|
||||
|
||||
export type TCourseImportSourceType = (typeof schema.courseImportSourceType.enumValues)[number];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user