Skip to content
Open
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
36 changes: 2 additions & 34 deletions supabase/functions/_backend/private/channel_stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { middlewareAuth } from '../utils/hono_jwt.ts'
import { cloudlog } from '../utils/logging.ts'
import { checkPermission } from '../utils/rbac.ts'
import { readDeviceVersionCounts, readStatsVersion } from '../utils/stats.ts'
import { generateDateLabels } from '../utils/stats_date_range.ts'
import { supabaseWithAuth } from '../utils/supabase.ts'
import { buildDailyReportedCountsByName, convertCountsToPercentagesByName, fillMissingDailyCounts } from '../utils/version_stats_helpers.ts'
import { buildDailyReportedCountsByName, convertCountsToPercentagesByName, createPercentageDatasetsByName, fillMissingDailyCounts } from '../utils/version_stats_helpers.ts'

dayjs.extend(utc)

Expand Down Expand Up @@ -70,22 +71,6 @@ function getStatsPeriod(requestedDays: StatsPeriodDays, endDate: Date, currentVe
}
}

function generateDateLabels(from: Date, to: Date) {
const start = dayjs(from).utc().startOf('day')
const end = dayjs(to).utc().startOf('day')

if (start.isAfter(end))
return [] as string[]

const labels: string[] = []
let cursor = start
while (cursor.isBefore(end) || cursor.isSame(end)) {
labels.push(cursor.format('YYYY-MM-DD'))
cursor = cursor.add(1, 'day')
}

return labels
}

function trimTrailingEmptyLabels(labels: string[], countsByDate: Record<string, Record<string, number>>) {
if (labels.length <= 1)
Expand All @@ -101,23 +86,6 @@ function trimTrailingEmptyLabels(labels: string[], countsByDate: Record<string,
return labels
}

function createPercentageDatasetsByName(
versions: string[],
dates: string[],
percentagesByDate: { [date: string]: { [version: string]: number } },
countsByDate: { [date: string]: { [version: string]: number } },
) {
return versions.map((version) => {
const percentageData = dates.map(date => percentagesByDate[date]?.[version] ?? 0)
const countData = dates.map(date => Math.round(countsByDate[date]?.[version] ?? 0))

return {
label: version,
data: percentageData,
metaCounts: countData,
}
})
}

function selectRecentChannelVersions(
deploymentHistory: DeploymentHistoryEntry[],
Expand Down
16 changes: 1 addition & 15 deletions supabase/functions/_backend/private/native_observe_stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { middlewareAuth } from '../utils/hono_jwt.ts'
import { cloudlog } from '../utils/logging.ts'
import { closeClient, getPgClient, logPgError } from '../utils/pg.ts'
import { checkPermission } from '../utils/rbac.ts'
import { generateDateLabels } from '../utils/stats_date_range.ts'

dayjs.extend(utc)

Expand Down Expand Up @@ -247,21 +248,6 @@ function normalizeNativeObserveView(value: unknown): NativeObserveView | null {
return 'plugins'
return null
}
function generateDateLabels(from: Date, to: Date) {
const start = dayjs(from).utc().startOf('day')
const end = dayjs(to).utc().startOf('day')
if (start.isAfter(end))
return [] as string[]

const labels: string[] = []
let cursor = start
while (cursor.isBefore(end) || cursor.isSame(end)) {
labels.push(cursor.format('YYYY-MM-DD'))
cursor = cursor.add(1, 'day')
}

return labels
}

function toCount(value: NativeObserveNumericValue) {
const numeric = Number(value ?? 0)
Expand Down
15 changes: 1 addition & 14 deletions supabase/functions/_backend/private/update_delivery_stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { middlewareAuth } from '../utils/hono_jwt.ts'
import { cloudlog, cloudlogErr, serializeError } from '../utils/logging.ts'
import { closeClient, getPgClient, logPgError } from '../utils/pg.ts'
import { checkPermission } from '../utils/rbac.ts'
import { generateDateLabels } from '../utils/stats_date_range.ts'
import { supabaseAdmin, supabaseClient as useSupabaseClient } from '../utils/supabase.ts'

dayjs.extend(utc)
Expand Down Expand Up @@ -225,20 +226,6 @@ function normalizeScope(value: unknown): UpdateDeliveryScope | null {
return null
}

function generateDateLabels(from: Date, to: Date) {
const start = dayjs(from).utc().startOf('day')
const end = dayjs(to).utc().startOf('day')
if (start.isAfter(end))
return [] as string[]

const labels: string[] = []
let cursor = start
while (cursor.isBefore(end) || cursor.isSame(end)) {
labels.push(cursor.format('YYYY-MM-DD'))
cursor = cursor.add(1, 'day')
}
return labels
}

function toCount(value: NumericValue) {
const numeric = Number(value ?? 0)
Expand Down
27 changes: 17 additions & 10 deletions supabase/functions/_backend/public/bundle/set_channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Context } from 'hono'
import type { MiddlewareKeyVariables } from '../../utils/hono.ts'
import type { Database } from '../../utils/supabase.types.ts'
import { HTTPException } from 'hono/http-exception'
import { requireEffectiveApikey } from '../../utils/effective_apikey.ts'
import { simpleError } from '../../utils/hono.ts'
import { closeClient, getDrizzleClient, getPgClient, logPgError } from '../../utils/pg.ts'
import { checkPermissionPg } from '../../utils/rbac.ts'
Expand All @@ -27,6 +28,20 @@ export interface SetChannelResult {

type DrizzleClient = ReturnType<typeof getDrizzleClient>


function requireSetChannelApikey(
c: Context<MiddlewareKeyVariables>,
apikey: Database['public']['Tables']['apikeys']['Row'],
) {
return requireEffectiveApikey(
c,
apikey,
'cannot_set_bundle_to_channel',
'Cannot set bundle to channel',
{ error: 'Missing API key context for audit logging' },
)
}

function validateSetChannelBody(body: SetChannelBody) {
if (!body.app_id || !body.version_id || !body.channel_id) {
throw simpleError('missing_required_fields', 'Missing required fields', { app_id: body.app_id, version_id: body.version_id, channel_id: body.channel_id })
Expand All @@ -37,14 +52,6 @@ function validateSetChannelBody(body: SetChannelBody) {
}
}

function getEffectiveApikey(c: Context<MiddlewareKeyVariables>, apikey: Database['public']['Tables']['apikeys']['Row']) {
const effectiveApikey = apikey.key ?? c.get('capgkey')
if (!effectiveApikey) {
throw simpleError('cannot_set_bundle_to_channel', 'Cannot set bundle to channel', { error: 'Missing API key context for audit logging' })
}
return effectiveApikey
}

async function fetchTargetChannel(dbClient: PgQueryClient, body: SetChannelBody) {
const channelResult = await dbClient.query<ChannelRow>(
`SELECT name, owner_org
Expand Down Expand Up @@ -104,7 +111,7 @@ export async function assertCanPromoteChannelInTransaction(
checkAppScope ? { appId: body.app_id } : { appId: body.app_id, channelId: body.channel_id },
drizzle,
apikey.user_id,
getEffectiveApikey(c, apikey),
requireSetChannelApikey(c, apikey),
)

if (!canPromote) {
Expand Down Expand Up @@ -137,7 +144,7 @@ export async function setChannelInTransaction(

await dbClient.query(
'SELECT set_config(\'request.headers\', $1, true)',
[JSON.stringify({ capgkey: getEffectiveApikey(c, apikey) })],
[JSON.stringify({ capgkey: requireSetChannelApikey(c, apikey) })],
)
await updateChannelVersion(dbClient, body, channel.owner_org)
return { channelName: channel.name, versionName }
Expand Down
11 changes: 2 additions & 9 deletions supabase/functions/_backend/public/channel/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Context } from 'hono'
import type { MiddlewareKeyVariables } from '../../utils/hono.ts'
import type { Database } from '../../utils/supabase.types.ts'
import { HTTPException } from 'hono/http-exception'
import { requireEffectiveApikey } from '../../utils/effective_apikey.ts'
import { BRES, simpleError } from '../../utils/hono.ts'
import { closeClient, getPgClient, logPgError } from '../../utils/pg.ts'
import { checkPermission } from '../../utils/rbac.ts'
Expand Down Expand Up @@ -48,14 +49,6 @@ interface OwnerOrgRow {
owner_org: string
}

function getEffectiveApikey(c: Context<MiddlewareKeyVariables>, apikey: Database['public']['Tables']['apikeys']['Row']) {
const effectiveApikey = apikey.key ?? c.get('capgkey')
if (!effectiveApikey) {
throw simpleError('cannot_access_app', 'You can\'t access this app')
}
return effectiveApikey
}

async function loadPreviewChannelForUpdate(dbClient: PgQueryClient, body: ChannelSet) {
const result = await dbClient.query<PreviewChannelRow>(
`SELECT id, app_id, owner_org, rbac_id, version, rollout_version
Expand Down Expand Up @@ -168,7 +161,7 @@ async function deletePreviewChannelAndBundle(
body: ChannelSet,
apikey: Database['public']['Tables']['apikeys']['Row'],
) {
const effectiveApikey = getEffectiveApikey(c, apikey)
const effectiveApikey = requireEffectiveApikey(c, apikey, 'cannot_access_app', 'You can\'t access this app')
const pgClient = getPgClient(c)
let dbClient: PgQueryClient | null = null
let transactionStarted = false
Expand Down
110 changes: 64 additions & 46 deletions supabase/functions/_backend/public/channel/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,68 @@ interface GetDevice {
page?: number
}

function mapChannelRowToPublic<T extends {
disable_auto_update_under_native: unknown
disable_auto_update: unknown
rollout_percentage_bps: unknown
rollout_enabled: unknown
rollout_paused_at: unknown
rollout_pause_reason: unknown
rollout_cache_ttl_seconds: unknown
auto_pause_enabled: unknown
auto_pause_window_minutes: unknown
auto_pause_failure_rate_bps: unknown
auto_pause_confidence: unknown
auto_pause_min_attempts: unknown
auto_pause_min_failures: unknown
auto_pause_action: unknown
auto_pause_cooldown_minutes: unknown
auto_pause_last_triggered_at: unknown
auto_pause_last_checked_at: unknown
}>(row: T) {
const {
disable_auto_update_under_native,
disable_auto_update,
rollout_percentage_bps,
rollout_enabled,
rollout_paused_at,
rollout_pause_reason,
rollout_cache_ttl_seconds,
auto_pause_enabled,
auto_pause_window_minutes,
auto_pause_failure_rate_bps,
auto_pause_confidence,
auto_pause_min_attempts,
auto_pause_min_failures,
auto_pause_action,
auto_pause_cooldown_minutes,
auto_pause_last_triggered_at,
auto_pause_last_checked_at,
...rest
} = row

return {
...rest,
disableAutoUpdateUnderNative: disable_auto_update_under_native,
disableAutoUpdate: disable_auto_update,
rolloutPercentageBps: rollout_percentage_bps,
rolloutEnabled: rollout_enabled,
rolloutPausedAt: rollout_paused_at,
rolloutPauseReason: rollout_pause_reason,
rolloutCacheTtlSeconds: rollout_cache_ttl_seconds,
autoPauseEnabled: auto_pause_enabled,
autoPauseWindowMinutes: auto_pause_window_minutes,
autoPauseFailureRateBps: auto_pause_failure_rate_bps,
autoPauseConfidence: auto_pause_confidence,
autoPauseMinAttempts: auto_pause_min_attempts,
autoPauseMinFailures: auto_pause_min_failures,
autoPauseAction: auto_pause_action,
autoPauseCooldownMinutes: auto_pause_cooldown_minutes,
autoPauseLastTriggeredAt: auto_pause_last_triggered_at,
autoPauseLastCheckedAt: auto_pause_last_checked_at,
}
}

async function getAll(c: Context, body: GetDevice, apikey: Database['public']['Tables']['apikeys']['Row']) {
const fetchOffset = body.page ?? 0
const from = fetchOffset * fetchLimit
Expand Down Expand Up @@ -66,29 +128,7 @@ async function getAll(c: Context, body: GetDevice, apikey: Database['public']['T
if (dbError || !dataChannels) {
throw simpleError('cannot_find_channels', 'Cannot find channels', { supabaseError: dbError })
}
return c.json(dataChannels.map((o) => {
const { disable_auto_update_under_native, disable_auto_update, rollout_percentage_bps, rollout_enabled, rollout_paused_at, rollout_pause_reason, rollout_cache_ttl_seconds, auto_pause_enabled, auto_pause_window_minutes, auto_pause_failure_rate_bps, auto_pause_confidence, auto_pause_min_attempts, auto_pause_min_failures, auto_pause_action, auto_pause_cooldown_minutes, auto_pause_last_triggered_at, auto_pause_last_checked_at, ...rest } = o
return {
...rest,
disableAutoUpdateUnderNative: disable_auto_update_under_native,
disableAutoUpdate: disable_auto_update,
rolloutPercentageBps: rollout_percentage_bps,
rolloutEnabled: rollout_enabled,
rolloutPausedAt: rollout_paused_at,
rolloutPauseReason: rollout_pause_reason,
rolloutCacheTtlSeconds: rollout_cache_ttl_seconds,
autoPauseEnabled: auto_pause_enabled,
autoPauseWindowMinutes: auto_pause_window_minutes,
autoPauseFailureRateBps: auto_pause_failure_rate_bps,
autoPauseConfidence: auto_pause_confidence,
autoPauseMinAttempts: auto_pause_min_attempts,
autoPauseMinFailures: auto_pause_min_failures,
autoPauseAction: auto_pause_action,
autoPauseCooldownMinutes: auto_pause_cooldown_minutes,
autoPauseLastTriggeredAt: auto_pause_last_triggered_at,
autoPauseLastCheckedAt: auto_pause_last_checked_at,
}
}))
return c.json(dataChannels.map(mapChannelRowToPublic))
}

async function getOne(c: Context, body: GetDevice, apikey: Database['public']['Tables']['apikeys']['Row']) {
Expand Down Expand Up @@ -144,29 +184,7 @@ async function getOne(c: Context, body: GetDevice, apikey: Database['public']['T
throw simpleError('cannot_find_version', 'Cannot find version', { supabaseError: dbError })
}

const { disable_auto_update_under_native, disable_auto_update, rollout_percentage_bps, rollout_enabled, rollout_paused_at, rollout_pause_reason, rollout_cache_ttl_seconds, auto_pause_enabled, auto_pause_window_minutes, auto_pause_failure_rate_bps, auto_pause_confidence, auto_pause_min_attempts, auto_pause_min_failures, auto_pause_action, auto_pause_cooldown_minutes, auto_pause_last_triggered_at, auto_pause_last_checked_at, ...rest } = dataChannel
const newObject = {
...rest,
disableAutoUpdateUnderNative: disable_auto_update_under_native,
disableAutoUpdate: disable_auto_update,
rolloutPercentageBps: rollout_percentage_bps,
rolloutEnabled: rollout_enabled,
rolloutPausedAt: rollout_paused_at,
rolloutPauseReason: rollout_pause_reason,
rolloutCacheTtlSeconds: rollout_cache_ttl_seconds,
autoPauseEnabled: auto_pause_enabled,
autoPauseWindowMinutes: auto_pause_window_minutes,
autoPauseFailureRateBps: auto_pause_failure_rate_bps,
autoPauseConfidence: auto_pause_confidence,
autoPauseMinAttempts: auto_pause_min_attempts,
autoPauseMinFailures: auto_pause_min_failures,
autoPauseAction: auto_pause_action,
autoPauseCooldownMinutes: auto_pause_cooldown_minutes,
autoPauseLastTriggeredAt: auto_pause_last_triggered_at,
autoPauseLastCheckedAt: auto_pause_last_checked_at,
}

return c.json(newObject)
return c.json(mapChannelRowToPublic(dataChannel))
}

export async function get(c: Context<MiddlewareKeyVariables>, body: GetDevice, apikey: Database['public']['Tables']['apikeys']['Row']): Promise<Response> {
Expand Down
12 changes: 8 additions & 4 deletions supabase/functions/_backend/public/channel/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Context } from 'hono'
import type { MiddlewareKeyVariables } from '../../utils/hono.ts'
import type { Database } from '../../utils/supabase.types.ts'
import { HTTPException } from 'hono/http-exception'
import { requireEffectiveApikey } from '../../utils/effective_apikey.ts'
import { BRES, simpleError } from '../../utils/hono.ts'
import { cloudlogErr } from '../../utils/logging.ts'
import { closeClient, getDrizzleClient, getPgClient, logPgError } from '../../utils/pg.ts'
Expand Down Expand Up @@ -259,10 +260,13 @@ async function createAndPromoteChannelInTransaction(
versionName: string,
apikey: Database['public']['Tables']['apikeys']['Row'],
): Promise<CreatedChannel> {
const effectiveApikey = apikey.key ?? c.get('capgkey')
if (!effectiveApikey) {
throw simpleError('cannot_set_bundle_to_channel', 'Cannot set bundle to channel', { error: 'Missing API key context for audit logging' })
}
const effectiveApikey = requireEffectiveApikey(
c,
apikey,
'cannot_set_bundle_to_channel',
'Cannot set bundle to channel',
{ error: 'Missing API key context for audit logging' },
)

const pgClient = getPgClient(c)
let dbClient: PgQueryClient | null = null
Expand Down
3 changes: 2 additions & 1 deletion supabase/functions/_backend/public/device/post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { MiddlewareKeyVariables } from '../../utils/hono.ts'
import type { Database } from '../../utils/supabase.types.ts'
import type { DeviceLink } from './delete.ts'
import { syncLegacyChannelSelfOverrideForDevice } from '../../utils/channelSelfStore.ts'
import { getEffectiveApikey } from '../../utils/effective_apikey.ts'
import { BRES, quickError, simpleError } from '../../utils/hono.ts'
import { checkPermission } from '../../utils/rbac.ts'
import { supabaseApikey, updateOrCreateChannelDevice } from '../../utils/supabase.ts'
Expand All @@ -21,7 +22,7 @@ export async function post(c: Context<MiddlewareKeyVariables, any, object>, body
throw simpleError('invalid_version_id', 'Cannot set version to device, use channel instead')
}

const effectiveApikey = apikey.key ?? (c.get('capgkey') as string)
const effectiveApikey = getEffectiveApikey(c, apikey) as string
const supabase = supabaseApikey(c, effectiveApikey)

// if channel set channel_override to it
Expand Down
Loading
Loading