fix(number-fields): избегаем потери точности длинных чисел
- number-поля теперь рендерятся как text + inputMode=numeric, чтобы браузер не округлял значения через input type=number - пробелы при вставке в number-поля удаляются - бэкенд нормализует значения number-полей в строку перед сохранением - добавлен хелпер normalizeFieldValueForStorage Closes: искажение расчётного счёта и других длинных числовых полей
This commit is contained in:
566
server/storage/content.storage.ts
Normal file
566
server/storage/content.storage.ts
Normal file
@@ -0,0 +1,566 @@
|
||||
import { forms, formTabs, fieldTemplates, customTabModules, taskTabValues, deviceTokens, userPresence, webPushSubscriptions, customPages, type User, type Organization, type FormTab, type Task, type FieldTemplate, type InsertFieldTemplate, type CustomTabModule, type InsertCustomTabModule, type TaskTabValue, type DeviceToken, type UserPresence, type WebPushSubscription, type CustomPage, type InsertCustomPage } from "@shared/schema";
|
||||
import { organizationApiKeys, type OrganizationApiKey } from "@shared/schema";
|
||||
import { automations, type Automation, type InsertAutomation } from "@shared/schema";
|
||||
import { systemConfig } from "@shared/schema";
|
||||
import { db } from "../db";
|
||||
import { eq, and, desc, asc, sql, inArray } from "drizzle-orm";
|
||||
import crypto from "crypto";
|
||||
import { hashApiKey, legacySha256Hash } from "../utils/api-key";
|
||||
import { DataTablesStorage } from "./data-tables.storage";
|
||||
export class ContentStorage extends DataTablesStorage {
|
||||
// Field Templates
|
||||
async getFieldTemplates(organizationId: number): Promise<FieldTemplate[]> {
|
||||
return await db
|
||||
.select()
|
||||
.from(fieldTemplates)
|
||||
.where(eq(fieldTemplates.organizationId, organizationId))
|
||||
.orderBy(asc(fieldTemplates.name));
|
||||
}
|
||||
|
||||
async getFieldTemplate(id: number, organizationId: number): Promise<FieldTemplate | undefined> {
|
||||
const [field] = await db
|
||||
.select()
|
||||
.from(fieldTemplates)
|
||||
.where(and(
|
||||
eq(fieldTemplates.id, id),
|
||||
eq(fieldTemplates.organizationId, organizationId)
|
||||
));
|
||||
return field || undefined;
|
||||
}
|
||||
|
||||
async getFieldTemplateByCode(code: string, organizationId: number): Promise<FieldTemplate | undefined> {
|
||||
const [field] = await db
|
||||
.select()
|
||||
.from(fieldTemplates)
|
||||
.where(and(
|
||||
eq(fieldTemplates.code, code),
|
||||
eq(fieldTemplates.organizationId, organizationId)
|
||||
));
|
||||
return field || undefined;
|
||||
}
|
||||
|
||||
async createFieldTemplate(field: InsertFieldTemplate): Promise<FieldTemplate> {
|
||||
const [created] = await db
|
||||
.insert(fieldTemplates)
|
||||
.values(field)
|
||||
.returning();
|
||||
return created;
|
||||
}
|
||||
|
||||
async updateFieldTemplate(id: number, organizationId: number, updates: Partial<FieldTemplate>): Promise<FieldTemplate> {
|
||||
const [updated] = await db
|
||||
.update(fieldTemplates)
|
||||
.set({ ...updates, updatedAt: new Date() })
|
||||
.where(and(
|
||||
eq(fieldTemplates.id, id),
|
||||
eq(fieldTemplates.organizationId, organizationId)
|
||||
))
|
||||
.returning();
|
||||
|
||||
if (!updated) {
|
||||
throw new Error("Шаблон поля не найден");
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
async deleteFieldTemplate(id: number, organizationId: number): Promise<void> {
|
||||
await db
|
||||
.delete(fieldTemplates)
|
||||
.where(and(
|
||||
eq(fieldTemplates.id, id),
|
||||
eq(fieldTemplates.organizationId, organizationId)
|
||||
));
|
||||
}
|
||||
|
||||
// Custom Tab Modules
|
||||
async getCustomTabModules(organizationId: number): Promise<CustomTabModule[]> {
|
||||
return await db
|
||||
.select()
|
||||
.from(customTabModules)
|
||||
.where(eq(customTabModules.organizationId, organizationId))
|
||||
.orderBy(asc(customTabModules.label));
|
||||
}
|
||||
|
||||
async getCustomTabModule(id: number, organizationId: number): Promise<CustomTabModule | undefined> {
|
||||
const [module] = await db
|
||||
.select()
|
||||
.from(customTabModules)
|
||||
.where(and(
|
||||
eq(customTabModules.id, id),
|
||||
eq(customTabModules.organizationId, organizationId)
|
||||
));
|
||||
return module || undefined;
|
||||
}
|
||||
|
||||
async getCustomTabModuleByType(type: string, organizationId: number): Promise<CustomTabModule | undefined> {
|
||||
const [module] = await db
|
||||
.select()
|
||||
.from(customTabModules)
|
||||
.where(and(
|
||||
eq(customTabModules.type, type),
|
||||
eq(customTabModules.organizationId, organizationId)
|
||||
));
|
||||
return module || undefined;
|
||||
}
|
||||
|
||||
async createCustomTabModule(module: InsertCustomTabModule): Promise<CustomTabModule> {
|
||||
const [created] = await db
|
||||
.insert(customTabModules)
|
||||
.values(module)
|
||||
.returning();
|
||||
return created;
|
||||
}
|
||||
|
||||
async updateCustomTabModule(id: number, organizationId: number, updates: Partial<CustomTabModule>): Promise<CustomTabModule> {
|
||||
const [updated] = await db
|
||||
.update(customTabModules)
|
||||
.set({ ...updates, updatedAt: new Date() })
|
||||
.where(and(
|
||||
eq(customTabModules.id, id),
|
||||
eq(customTabModules.organizationId, organizationId)
|
||||
))
|
||||
.returning();
|
||||
|
||||
if (!updated) {
|
||||
throw new Error("Модуль вкладки не найден");
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
async deleteCustomTabModule(id: number, organizationId: number): Promise<void> {
|
||||
await db
|
||||
.delete(customTabModules)
|
||||
.where(and(
|
||||
eq(customTabModules.id, id),
|
||||
eq(customTabModules.organizationId, organizationId)
|
||||
));
|
||||
}
|
||||
|
||||
async getFormIdsByTabModuleType(type: string, organizationId: number): Promise<number[]> {
|
||||
const moduleCode = `mod_${type}`;
|
||||
const results = await db
|
||||
.select({ formId: formTabs.formId })
|
||||
.from(formTabs)
|
||||
.innerJoin(forms, eq(formTabs.formId, forms.id))
|
||||
.where(and(
|
||||
eq(formTabs.type, type),
|
||||
eq(formTabs.code, moduleCode),
|
||||
eq(forms.organizationId, organizationId)
|
||||
));
|
||||
return results.map(r => r.formId);
|
||||
}
|
||||
|
||||
async getFormTabByModuleType(formId: number, type: string, organizationId: number): Promise<FormTab | undefined> {
|
||||
const moduleCode = `mod_${type}`;
|
||||
const results = await db
|
||||
.select({ formTab: formTabs })
|
||||
.from(formTabs)
|
||||
.innerJoin(forms, eq(formTabs.formId, forms.id))
|
||||
.where(and(
|
||||
eq(formTabs.formId, formId),
|
||||
eq(formTabs.type, type),
|
||||
eq(formTabs.code, moduleCode),
|
||||
eq(forms.organizationId, organizationId)
|
||||
));
|
||||
return results[0]?.formTab;
|
||||
}
|
||||
|
||||
// Task Tab Values
|
||||
async getTaskTabValues(taskId: number, tabId: number): Promise<TaskTabValue | undefined> {
|
||||
const [row] = await db
|
||||
.select()
|
||||
.from(taskTabValues)
|
||||
.where(and(
|
||||
eq(taskTabValues.taskId, taskId),
|
||||
eq(taskTabValues.tabId, tabId)
|
||||
));
|
||||
return row;
|
||||
}
|
||||
|
||||
async getTaskTabValuesByTask(taskId: number): Promise<TaskTabValue[]> {
|
||||
return await db
|
||||
.select()
|
||||
.from(taskTabValues)
|
||||
.where(eq(taskTabValues.taskId, taskId));
|
||||
}
|
||||
|
||||
async upsertTaskTabValues(taskId: number, tabId: number, values: Record<string, any>): Promise<TaskTabValue> {
|
||||
const existing = await this.getTaskTabValues(taskId, tabId);
|
||||
|
||||
if (existing) {
|
||||
const [updated] = await db
|
||||
.update(taskTabValues)
|
||||
.set({ values, updatedAt: new Date() })
|
||||
.where(and(
|
||||
eq(taskTabValues.taskId, taskId),
|
||||
eq(taskTabValues.tabId, tabId)
|
||||
))
|
||||
.returning();
|
||||
return updated;
|
||||
} else {
|
||||
const [created] = await db
|
||||
.insert(taskTabValues)
|
||||
.values({ taskId, tabId, values })
|
||||
.returning();
|
||||
return created;
|
||||
}
|
||||
}
|
||||
// =====================
|
||||
// Device Tokens and User Presence
|
||||
// =====================
|
||||
|
||||
async registerDeviceToken(userId: number, organizationId: number, token: string, platform: string, deviceInfo?: any): Promise<DeviceToken> {
|
||||
// Сначала удаляем токен если он принадлежит другому пользователю (устройство перешло к другому)
|
||||
await db.delete(deviceTokens)
|
||||
.where(and(
|
||||
eq(deviceTokens.token, token),
|
||||
sql`${deviceTokens.userId} != ${userId}`
|
||||
));
|
||||
|
||||
// Проверяем, есть ли уже этот токен у текущего пользователя в этой организации
|
||||
const existing = await db.select().from(deviceTokens)
|
||||
.where(and(
|
||||
eq(deviceTokens.userId, userId),
|
||||
eq(deviceTokens.organizationId, organizationId),
|
||||
eq(deviceTokens.token, token)
|
||||
))
|
||||
.limit(1);
|
||||
|
||||
if (existing.length > 0) {
|
||||
// Просто обновляем lastActiveAt
|
||||
const [updated] = await db.update(deviceTokens)
|
||||
.set({ lastActiveAt: new Date(), deviceInfo: deviceInfo || existing[0].deviceInfo })
|
||||
.where(eq(deviceTokens.id, existing[0].id))
|
||||
.returning();
|
||||
return updated;
|
||||
} else {
|
||||
// Создаём новую запись
|
||||
const [created] = await db.insert(deviceTokens)
|
||||
.values({ userId, organizationId, token, platform, deviceInfo: deviceInfo || null })
|
||||
.returning();
|
||||
return created;
|
||||
}
|
||||
}
|
||||
|
||||
async unregisterDeviceToken(userId: number, organizationId: number, token: string): Promise<void> {
|
||||
await db.delete(deviceTokens)
|
||||
.where(and(
|
||||
eq(deviceTokens.userId, userId),
|
||||
eq(deviceTokens.organizationId, organizationId),
|
||||
eq(deviceTokens.token, token)
|
||||
));
|
||||
|
||||
// Пересчитываем presence после удаления устройства
|
||||
await this.updateUserPresence(userId, organizationId, false);
|
||||
}
|
||||
|
||||
async getDeviceTokensByUser(userId: number, organizationId: number): Promise<DeviceToken[]> {
|
||||
return await db.select().from(deviceTokens)
|
||||
.where(and(
|
||||
eq(deviceTokens.userId, userId),
|
||||
eq(deviceTokens.organizationId, organizationId)
|
||||
))
|
||||
.orderBy(desc(deviceTokens.lastActiveAt));
|
||||
}
|
||||
|
||||
async getDeviceTokensByUsers(userIds: number[], organizationId: number): Promise<DeviceToken[]> {
|
||||
if (userIds.length === 0) return [];
|
||||
return await db.select().from(deviceTokens)
|
||||
.where(and(
|
||||
inArray(deviceTokens.userId, userIds),
|
||||
eq(deviceTokens.organizationId, organizationId)
|
||||
));
|
||||
}
|
||||
|
||||
async deleteInvalidDeviceTokens(tokens: string[]): Promise<number> {
|
||||
if (tokens.length === 0) return 0;
|
||||
|
||||
const result = await db.delete(deviceTokens)
|
||||
.where(inArray(deviceTokens.token, tokens))
|
||||
.returning();
|
||||
|
||||
return result.length;
|
||||
}
|
||||
|
||||
async updateUserPresence(userId: number, organizationId: number, isOnline: boolean): Promise<UserPresence> {
|
||||
// Считаем реальное количество зарегистрированных устройств для данной организации
|
||||
const deviceCount = await db.select({ count: sql<number>`count(*)::int` })
|
||||
.from(deviceTokens)
|
||||
.where(and(
|
||||
eq(deviceTokens.userId, userId),
|
||||
eq(deviceTokens.organizationId, organizationId)
|
||||
));
|
||||
const activeDevices = deviceCount[0]?.count || 0;
|
||||
|
||||
const existing = await db.select().from(userPresence)
|
||||
.where(and(
|
||||
eq(userPresence.userId, userId),
|
||||
eq(userPresence.organizationId, organizationId)
|
||||
))
|
||||
.limit(1);
|
||||
|
||||
if (existing.length > 0) {
|
||||
const [updated] = await db.update(userPresence)
|
||||
.set({
|
||||
isOnline: activeDevices > 0 ? isOnline : false,
|
||||
lastSeenAt: new Date(),
|
||||
activeDevices
|
||||
})
|
||||
.where(and(
|
||||
eq(userPresence.userId, userId),
|
||||
eq(userPresence.organizationId, organizationId)
|
||||
))
|
||||
.returning();
|
||||
return updated;
|
||||
} else {
|
||||
const [created] = await db.insert(userPresence)
|
||||
.values({ userId, organizationId, isOnline: activeDevices > 0 ? isOnline : false, lastSeenAt: new Date(), activeDevices })
|
||||
.returning();
|
||||
return created;
|
||||
}
|
||||
}
|
||||
|
||||
async heartbeatUserPresence(userId: number, organizationId: number): Promise<void> {
|
||||
// Проверяем наличие устройств в данной организации - нельзя быть online без устройств
|
||||
const deviceCount = await db.select({ count: sql<number>`count(*)::int` })
|
||||
.from(deviceTokens)
|
||||
.where(and(
|
||||
eq(deviceTokens.userId, userId),
|
||||
eq(deviceTokens.organizationId, organizationId)
|
||||
));
|
||||
const hasDevices = (deviceCount[0]?.count || 0) > 0;
|
||||
|
||||
if (!hasDevices) {
|
||||
// Без устройств нельзя быть online
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = await db.select().from(userPresence)
|
||||
.where(and(
|
||||
eq(userPresence.userId, userId),
|
||||
eq(userPresence.organizationId, organizationId)
|
||||
))
|
||||
.limit(1);
|
||||
|
||||
if (existing.length > 0) {
|
||||
await db.update(userPresence)
|
||||
.set({ isOnline: true, lastSeenAt: new Date() })
|
||||
.where(and(
|
||||
eq(userPresence.userId, userId),
|
||||
eq(userPresence.organizationId, organizationId)
|
||||
));
|
||||
} else {
|
||||
await db.insert(userPresence)
|
||||
.values({ userId, organizationId, isOnline: true, lastSeenAt: new Date(), activeDevices: deviceCount[0]?.count || 0 });
|
||||
}
|
||||
}
|
||||
|
||||
async getUserPresence(userId: number, organizationId: number): Promise<UserPresence | undefined> {
|
||||
const [result] = await db.select().from(userPresence)
|
||||
.where(and(
|
||||
eq(userPresence.userId, userId),
|
||||
eq(userPresence.organizationId, organizationId)
|
||||
));
|
||||
return result;
|
||||
}
|
||||
|
||||
async isUserOnline(userId: number, organizationId: number): Promise<boolean> {
|
||||
const presence = await this.getUserPresence(userId, organizationId);
|
||||
if (!presence) return false;
|
||||
|
||||
// Consider user offline if last seen more than 5 minutes ago
|
||||
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
|
||||
return !!presence.isOnline && !!presence.lastSeenAt && presence.lastSeenAt > fiveMinutesAgo;
|
||||
}
|
||||
|
||||
async getOfflineUsers(userIds: number[], organizationId: number): Promise<number[]> {
|
||||
if (userIds.length === 0) return [];
|
||||
|
||||
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
|
||||
|
||||
// Get all presences for this organization
|
||||
const presences = await db.select().from(userPresence)
|
||||
.where(and(
|
||||
inArray(userPresence.userId, userIds),
|
||||
eq(userPresence.organizationId, organizationId)
|
||||
));
|
||||
|
||||
const onlineUserIds = new Set(
|
||||
presences
|
||||
.filter(p => p.isOnline && p.lastSeenAt && p.lastSeenAt > fiveMinutesAgo)
|
||||
.map(p => p.userId)
|
||||
);
|
||||
|
||||
return userIds.filter(id => !onlineUserIds.has(id));
|
||||
}
|
||||
|
||||
// Web Push Subscriptions
|
||||
async registerWebPushSubscription(userId: number, organizationId: number, endpoint: string, subscription: string): Promise<void> {
|
||||
await db.delete(webPushSubscriptions).where(eq(webPushSubscriptions.endpoint, endpoint));
|
||||
await db.insert(webPushSubscriptions).values({ userId, organizationId, endpoint, subscription });
|
||||
}
|
||||
|
||||
async unregisterWebPushSubscription(endpoint: string): Promise<void> {
|
||||
await db.delete(webPushSubscriptions).where(eq(webPushSubscriptions.endpoint, endpoint));
|
||||
}
|
||||
|
||||
async getWebPushSubscriptions(userId: number, organizationId: number): Promise<WebPushSubscription[]> {
|
||||
return await db.select().from(webPushSubscriptions)
|
||||
.where(and(
|
||||
eq(webPushSubscriptions.userId, userId),
|
||||
eq(webPushSubscriptions.organizationId, organizationId)
|
||||
));
|
||||
}
|
||||
|
||||
async getWebPushSubscriptionsByOrganization(organizationId: number): Promise<WebPushSubscription[]> {
|
||||
return await db.select().from(webPushSubscriptions)
|
||||
.where(eq(webPushSubscriptions.organizationId, organizationId));
|
||||
}
|
||||
|
||||
async clearAllWebPushSubscriptions(): Promise<number> {
|
||||
const deleted = await db.delete(webPushSubscriptions).returning({ id: webPushSubscriptions.id });
|
||||
return deleted.length;
|
||||
}
|
||||
|
||||
// System Config
|
||||
async getSystemConfig(key: string): Promise<string | null> {
|
||||
const [row] = await db.select().from(systemConfig).where(eq(systemConfig.key, key));
|
||||
return row ? row.value : null;
|
||||
}
|
||||
|
||||
async setSystemConfig(key: string, value: string): Promise<void> {
|
||||
await db.insert(systemConfig)
|
||||
.values({ key, value })
|
||||
.onConflictDoUpdate({ target: systemConfig.key, set: { value, updatedAt: new Date() } });
|
||||
}
|
||||
|
||||
// Organization API Keys (MCP)
|
||||
async createApiKey(organizationId: number, createdBy: number, label: string): Promise<{ key: string; record: OrganizationApiKey }> {
|
||||
const rawKey = "wf_" + crypto.randomBytes(32).toString("base64url");
|
||||
const keyHash = hashApiKey(rawKey);
|
||||
const keyPrefix = rawKey.substring(0, 10);
|
||||
const [record] = await db.insert(organizationApiKeys).values({
|
||||
organizationId, createdBy, label, keyHash, keyPrefix, isLegacy: false,
|
||||
}).returning();
|
||||
return { key: rawKey, record };
|
||||
}
|
||||
|
||||
async listApiKeys(organizationId: number): Promise<OrganizationApiKey[]> {
|
||||
return await db.select().from(organizationApiKeys)
|
||||
.where(eq(organizationApiKeys.organizationId, organizationId))
|
||||
.orderBy(desc(organizationApiKeys.createdAt));
|
||||
}
|
||||
|
||||
async deleteApiKey(id: number, organizationId: number): Promise<void> {
|
||||
await db.delete(organizationApiKeys)
|
||||
.where(and(eq(organizationApiKeys.id, id), eq(organizationApiKeys.organizationId, organizationId)));
|
||||
}
|
||||
|
||||
async getApiKeyByHash(rawKey: string): Promise<OrganizationApiKey | undefined> {
|
||||
// hashApiKey throws a descriptive Error if API_KEY_HMAC_SECRET is missing.
|
||||
// We intentionally do NOT catch that error here — a misconfigured server should
|
||||
// fail loudly (500) rather than silently reject every key with a 401.
|
||||
const keyHash = hashApiKey(rawKey);
|
||||
const [record] = await db.select().from(organizationApiKeys)
|
||||
.where(and(
|
||||
eq(organizationApiKeys.keyHash, keyHash),
|
||||
eq(organizationApiKeys.isActive, true),
|
||||
eq(organizationApiKeys.isLegacy, false),
|
||||
));
|
||||
return record || undefined;
|
||||
}
|
||||
|
||||
async getApiKeyByLegacyHash(rawKey: string): Promise<OrganizationApiKey | undefined> {
|
||||
const keyHash = legacySha256Hash(rawKey);
|
||||
const [record] = await db.select().from(organizationApiKeys)
|
||||
.where(and(
|
||||
eq(organizationApiKeys.keyHash, keyHash),
|
||||
eq(organizationApiKeys.isActive, true),
|
||||
eq(organizationApiKeys.isLegacy, true),
|
||||
));
|
||||
return record || undefined;
|
||||
}
|
||||
|
||||
async touchApiKey(id: number): Promise<void> {
|
||||
await db.update(organizationApiKeys).set({ lastUsedAt: new Date() }).where(eq(organizationApiKeys.id, id));
|
||||
}
|
||||
|
||||
// ── Custom JS Pages ──────────────────────────────────────────────────
|
||||
async getCustomPages(organizationId: number): Promise<CustomPage[]> {
|
||||
return db.select().from(customPages)
|
||||
.where(eq(customPages.organizationId, organizationId))
|
||||
.orderBy(asc(customPages.name));
|
||||
}
|
||||
|
||||
async getCustomPage(id: number, organizationId: number): Promise<CustomPage | undefined> {
|
||||
const [row] = await db.select().from(customPages)
|
||||
.where(and(eq(customPages.id, id), eq(customPages.organizationId, organizationId)));
|
||||
return row ?? undefined;
|
||||
}
|
||||
|
||||
async getCustomPageBySlug(slug: string, organizationId: number): Promise<CustomPage | undefined> {
|
||||
const [row] = await db.select().from(customPages)
|
||||
.where(and(eq(customPages.slug, slug), eq(customPages.organizationId, organizationId)));
|
||||
return row ?? undefined;
|
||||
}
|
||||
|
||||
async createCustomPage(page: InsertCustomPage): Promise<CustomPage> {
|
||||
const [row] = await db.insert(customPages).values(page).returning();
|
||||
return row;
|
||||
}
|
||||
|
||||
async updateCustomPage(id: number, organizationId: number, updates: Partial<InsertCustomPage>): Promise<CustomPage> {
|
||||
const [row] = await db.update(customPages)
|
||||
.set({ ...updates, updatedAt: new Date() })
|
||||
.where(and(eq(customPages.id, id), eq(customPages.organizationId, organizationId)))
|
||||
.returning();
|
||||
return row;
|
||||
}
|
||||
|
||||
async deleteCustomPage(id: number, organizationId: number): Promise<void> {
|
||||
await db.delete(customPages)
|
||||
.where(and(eq(customPages.id, id), eq(customPages.organizationId, organizationId)));
|
||||
}
|
||||
|
||||
// ── Automations ──────────────────────────────────────────────────────────
|
||||
async getAutomations(organizationId: number): Promise<Automation[]> {
|
||||
return db.select().from(automations)
|
||||
.where(eq(automations.organizationId, organizationId))
|
||||
.orderBy(asc(automations.name));
|
||||
}
|
||||
|
||||
async getOfflineAutomations(organizationId: number): Promise<Automation[]> {
|
||||
return db.select().from(automations)
|
||||
.where(and(
|
||||
eq(automations.organizationId, organizationId),
|
||||
eq(automations.isActive, true),
|
||||
eq(automations.runOffline, true)
|
||||
))
|
||||
.orderBy(asc(automations.name));
|
||||
}
|
||||
|
||||
async getAutomation(id: number, organizationId: number): Promise<Automation | undefined> {
|
||||
const [row] = await db.select().from(automations)
|
||||
.where(and(eq(automations.id, id), eq(automations.organizationId, organizationId)));
|
||||
return row ?? undefined;
|
||||
}
|
||||
|
||||
async createAutomation(automation: InsertAutomation): Promise<Automation> {
|
||||
const [row] = await db.insert(automations).values(automation).returning();
|
||||
return row;
|
||||
}
|
||||
|
||||
async updateAutomation(id: number, organizationId: number, updates: Partial<InsertAutomation>): Promise<Automation> {
|
||||
const [row] = await db.update(automations)
|
||||
.set({ ...updates, updatedAt: new Date() })
|
||||
.where(and(eq(automations.id, id), eq(automations.organizationId, organizationId)))
|
||||
.returning();
|
||||
return row;
|
||||
}
|
||||
|
||||
async deleteAutomation(id: number, organizationId: number): Promise<void> {
|
||||
await db.delete(automations)
|
||||
.where(and(eq(automations.id, id), eq(automations.organizationId, organizationId)));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user