diff --git a/src/server/api/routers/board/db/widget.ts b/src/server/api/routers/board/db/widget.ts index 6256afa66..1bf729abb 100644 --- a/src/server/api/routers/board/db/widget.ts +++ b/src/server/api/routers/board/db/widget.ts @@ -13,6 +13,11 @@ export const getWidgetsForSectionsAsync = async (sectionIds: string[]) => { return await db.query.widgets.findMany({ with: { options: true, + integrations: { + with: { + integration: true, + }, + }, item: { with: { layouts: { diff --git a/src/server/db/items.ts b/src/server/db/items.ts index d7432749e..0c93128a0 100644 --- a/src/server/db/items.ts +++ b/src/server/db/items.ts @@ -1,7 +1,8 @@ import { IconKey, IconPassword, IconUser, TablerIconsProps } from '@tabler/icons-react'; -import { objectEntries, objectKeys } from '~/tools/object'; import widgets from '~/widgets'; +import { objectEntries, objectKeys } from '../../tools/object'; + type IntegrationTypeDefinition = { secrets: IntegrationSecretKey[]; iconUrl: string; @@ -48,6 +49,14 @@ export const widgetOptionTypes = [ 'array', 'null', ] as const; +export const boardBackgroundImageAttachmentTypes = ['fixed', 'scroll'] as const; +export const boardBackgroundImageRepeatTypes = [ + 'repeat', + 'repeat-x', + 'repeat-y', + 'no-repeat', +] as const; +export const boardBackgroundImageSizeTypes = ['cover', 'contain'] as const; export const appNamePositions = ['right', 'left', 'top', 'bottom'] as const; export const appNameStyles = ['normal', 'hide', 'hover'] as const; export const statusCodeTypes = [ @@ -159,6 +168,10 @@ export type IntegrationSecretVisibility = (typeof integrationSecretVisibility)[n export type IntegrationSecretKey = keyof typeof integrationSecrets; export type WidgetSort = (typeof widgetSorts)[number]; export type WidgetOptionType = (typeof widgetOptionTypes)[number]; +export type BoardBackgroundImageAttachmentType = + (typeof boardBackgroundImageAttachmentTypes)[number]; +export type BoardBackgroundImageRepeatType = (typeof boardBackgroundImageRepeatTypes)[number]; +export type BoardBackgroundImageSizeType = (typeof boardBackgroundImageSizeTypes)[number]; export type AppNamePosition = (typeof appNamePositions)[number]; export type AppNameStyle = (typeof appNameStyles)[number]; export type StatusCodeType = (typeof statusCodeTypes)[number]; diff --git a/src/server/db/queries/app.ts b/src/server/db/queries/app.ts new file mode 100644 index 000000000..fff5065b6 --- /dev/null +++ b/src/server/db/queries/app.ts @@ -0,0 +1,31 @@ +import { and, eq } from 'drizzle-orm'; +import { User } from 'next-auth'; + +import { db } from '..'; +import { boards, items } from '../schema'; + +export const getAppAsync = async (boardId: string, id: string, user: User | null | undefined) => { + return await db.query.items.findFirst({ + where: and( + eq(items.boardId, boardId), + eq(items.id, id), + user ? undefined : eq(boards.allowGuests, true) + ), + with: { + app: { + with: { + statusCodes: { + columns: { + code: true, + }, + }, + }, + }, + board: { + columns: { + allowGuests: true, + }, + }, + }, + }); +}; diff --git a/src/server/db/queries/integrations.ts b/src/server/db/queries/integrations.ts new file mode 100644 index 000000000..f36df172f --- /dev/null +++ b/src/server/db/queries/integrations.ts @@ -0,0 +1,67 @@ +import { and, eq, inArray } from 'drizzle-orm'; +import { User } from 'next-auth'; + +import { db } from '..'; +import { IntegrationSecretKey, IntegrationType } from '../items'; +import { boards, integrations, items } from '../schema'; + +export async function getIntegrations( + boardId: string, + sorts: TIntegrations[], + user: User | null | undefined +) { + return await getIntegrationsForWidget(boardId, sorts, user, 'ignore'); +} + +export const getIntegrationsForWidget = async ( + boardId: string, + sorts: TIntegrations[], + + user: User | null | undefined, + widgetId: 'ignore' | (string & {}) +) => { + const widgetItems = await db.query.items.findMany({ + where: and( + eq(items.boardId, boardId), + user ? undefined : eq(boards.allowGuests, true), + sorts.length >= 1 ? inArray(integrations.sort, sorts) : undefined, + widgetId !== 'ignore' ? eq(items.id, widgetId) : undefined + ), + with: { + widget: { + with: { + integrations: { + with: { + integration: { + with: { + secrets: true, + }, + }, + }, + }, + options: true, + }, + }, + }, + }); + return widgetItems + .flatMap((x) => x.widget?.integrations ?? []) + .map((x) => ({ ...x.integration, sort: x.integration.sort as TIntegrations })); +}; + +export async function getIntegrationAsync(integrationId: string) { + return await db.query.integrations.findFirst({ + where: eq(integrations.id, integrationId), + with: { + secrets: true, + widgets: true, + }, + }); +} + +export function getSecret( + integration: Awaited>[number], + key: IntegrationSecretKey +) { + return integration.secrets.find((s) => s.key === key)?.value ?? ''; +} diff --git a/src/server/db/queries/widget.ts b/src/server/db/queries/widget.ts new file mode 100644 index 000000000..c95e8f6c8 --- /dev/null +++ b/src/server/db/queries/widget.ts @@ -0,0 +1,71 @@ +import { and, eq } from 'drizzle-orm'; +import { User } from 'next-auth'; +import { mapWidgetOptions } from '~/server/api/routers/board/mapping/options'; +import { objectEntries } from '~/tools/object'; +import widgetDefinitions from '~/widgets'; +import { InferWidgetOptions } from '~/widgets/widgets'; + +import { db } from '..'; +import { boards, items, widgets } from '../schema'; + +export const getWidgetAsync = async ( + boardId: string, + id: string, + user: User | null | undefined, + sort: TSort +) => { + const widgetItem = await db.query.items.findFirst({ + where: and( + eq(items.boardId, boardId), + eq(items.id, id), + user ? undefined : eq(boards.allowGuests, true), + eq(widgets.sort, sort) + ), + with: { + widget: { + with: { + integrations: { + with: { + integration: { + with: { + secrets: true, + }, + }, + }, + }, + options: true, + }, + }, + board: { + columns: { + allowGuests: true, + }, + }, + }, + }); + + if (!widgetItem) { + return null; + } + + const mappedOptions = mapWidgetOptions( + widgetItem.widget!.options.sort((a, b) => a.path.localeCompare(b.path)) + ); + objectEntries(widgetDefinitions[sort].options).forEach(([key, definition]) => { + mappedOptions[key] = mappedOptions[key] ?? definition.defaultValue; + }); + + return { + id: widgetItem.id, + sort: widgetItem.widget!.sort, + options: mappedOptions as InferWidgetOptions<(typeof widgetDefinitions)[TSort]>, + integrations: widgetItem.widget!.integrations.map((i) => ({ + ...i.integration, + })), + }; +}; + +export type WidgetIntegration = Exclude< + Awaited>, + null +>['integrations'][number];