From b1cdda3120e970e6487163318bf0f0f88f01f238 Mon Sep 17 00:00:00 2001
From: Marco de Jongh <1107647+marcodejongh@users.noreply.github.com>
Date: Sun, 29 Dec 2024 09:04:44 +1100
Subject: [PATCH 1/4] Fix duplicate rows issue in climb_stats tables
The climb stats tables were getting a new row each time
the shared sync ran due to a schema error. This commit changes that
and moves the duplicates of climb_stats to $boardname_climb_stats_history.
The history tables are still relevant for doing any kind of analytics or
query for recently added & hot climbs, since we dont have fulld atabase access
---
.env.development.local | 7 +-
app/lib/db/relations.ts | 479 +++++++++++
app/lib/db/schema.ts | 1218 +++++++++++++++++++++++++++
drizzle.config.ts | 18 +-
drizzle/0002_unique_climbstats.sql | 34 +
drizzle/0003_add_climbs_indexes.sql | 4 +
drizzle/README.md | 13 +
7 files changed, 1768 insertions(+), 5 deletions(-)
create mode 100644 app/lib/db/relations.ts
create mode 100644 app/lib/db/schema.ts
create mode 100644 drizzle/0002_unique_climbstats.sql
create mode 100644 drizzle/0003_add_climbs_indexes.sql
create mode 100644 drizzle/README.md
diff --git a/.env.development.local b/.env.development.local
index b70a709..b9c0bdf 100644
--- a/.env.development.local
+++ b/.env.development.local
@@ -4,4 +4,9 @@
# docker container
VERCEL_ENV=development
POSTGRES_URL=postgresql://postgres:password@localhost:54320/verceldb
-BASE_URL=http://localhost:3000
\ No newline at end of file
+BASE_URL=http://localhost:3000
+POSTGRES_HOST=localhost
+POSTGRES_PORT=54320
+POSTGRES_USER=postgres
+POSTGRES_PASSWORD=password
+POSTGRES_DATABASE=verceldb
\ No newline at end of file
diff --git a/app/lib/db/relations.ts b/app/lib/db/relations.ts
new file mode 100644
index 0000000..ce576bf
--- /dev/null
+++ b/app/lib/db/relations.ts
@@ -0,0 +1,479 @@
+import { relations } from 'drizzle-orm/relations';
+import {
+ kilterClimbs,
+ kilterClimbCacheFields,
+ kilterProducts,
+ kilterLayouts,
+ tensionClimbs,
+ tensionClimbCacheFields,
+ kilterHoles,
+ kilterLeds,
+ kilterProductSizes,
+ kilterBids,
+ kilterUsers,
+ kilterPlacementRoles,
+ kilterPlacements,
+ kilterSets,
+ kilterProductSizesLayoutsSets,
+ kilterWalls,
+ tensionBids,
+ tensionUsers,
+ tensionHoles,
+ tensionProducts,
+ tensionLayouts,
+ tensionPlacementRoles,
+ tensionPlacements,
+ tensionSets,
+ tensionLeds,
+ tensionProductSizes,
+ tensionProductSizesLayoutsSets,
+ kilterAttempts,
+ kilterAscents,
+ kilterDifficultyGrades,
+ tensionWalls,
+ tensionAttempts,
+ tensionAscents,
+ tensionDifficultyGrades,
+ kilterProductsAngles,
+ kilterWallsSets,
+ tensionWallsSets,
+ tensionProductsAngles,
+ kilterUserSyncs,
+ tensionUserSyncs,
+ kilterBetaLinks,
+ tensionBetaLinks,
+} from './schema';
+
+export const kilterClimbCacheFieldsRelations = relations(kilterClimbCacheFields, ({ one }) => ({
+ kilterClimb: one(kilterClimbs, {
+ fields: [kilterClimbCacheFields.climbUuid],
+ references: [kilterClimbs.uuid],
+ }),
+}));
+
+export const kilterClimbsRelations = relations(kilterClimbs, ({ one, many }) => ({
+ kilterClimbCacheFields: many(kilterClimbCacheFields),
+ kilterBids: many(kilterBids),
+ kilterLayout: one(kilterLayouts, {
+ fields: [kilterClimbs.layoutId],
+ references: [kilterLayouts.id],
+ }),
+ kilterAscents: many(kilterAscents),
+ kilterBetaLinks: many(kilterBetaLinks),
+}));
+
+export const kilterLayoutsRelations = relations(kilterLayouts, ({ one, many }) => ({
+ kilterProduct: one(kilterProducts, {
+ fields: [kilterLayouts.productId],
+ references: [kilterProducts.id],
+ }),
+ kilterPlacements: many(kilterPlacements),
+ kilterClimbs: many(kilterClimbs),
+ kilterProductSizesLayoutsSets: many(kilterProductSizesLayoutsSets),
+ kilterWalls: many(kilterWalls),
+}));
+
+export const kilterProductsRelations = relations(kilterProducts, ({ many }) => ({
+ kilterLayouts: many(kilterLayouts),
+ kilterProductSizes: many(kilterProductSizes),
+ kilterHoles: many(kilterHoles),
+ kilterPlacementRoles: many(kilterPlacementRoles),
+ kilterWalls: many(kilterWalls),
+ kilterProductsAngles: many(kilterProductsAngles),
+}));
+
+export const tensionClimbCacheFieldsRelations = relations(tensionClimbCacheFields, ({ one }) => ({
+ tensionClimb: one(tensionClimbs, {
+ fields: [tensionClimbCacheFields.climbUuid],
+ references: [tensionClimbs.uuid],
+ }),
+}));
+
+export const tensionClimbsRelations = relations(tensionClimbs, ({ one, many }) => ({
+ tensionClimbCacheFields: many(tensionClimbCacheFields),
+ tensionBids: many(tensionBids),
+ tensionLayout: one(tensionLayouts, {
+ fields: [tensionClimbs.layoutId],
+ references: [tensionLayouts.id],
+ }),
+ tensionAscents: many(tensionAscents),
+ tensionBetaLinks: many(tensionBetaLinks),
+}));
+
+export const kilterLedsRelations = relations(kilterLeds, ({ one }) => ({
+ kilterHole: one(kilterHoles, {
+ fields: [kilterLeds.holeId],
+ references: [kilterHoles.id],
+ }),
+ kilterProductSize: one(kilterProductSizes, {
+ fields: [kilterLeds.productSizeId],
+ references: [kilterProductSizes.id],
+ }),
+}));
+
+export const kilterHolesRelations = relations(kilterHoles, ({ one, many }) => ({
+ kilterLeds: many(kilterLeds),
+ kilterProduct: one(kilterProducts, {
+ fields: [kilterHoles.productId],
+ references: [kilterProducts.id],
+ }),
+ kilterPlacements: many(kilterPlacements),
+}));
+
+export const kilterProductSizesRelations = relations(kilterProductSizes, ({ one, many }) => ({
+ kilterLeds: many(kilterLeds),
+ kilterProduct: one(kilterProducts, {
+ fields: [kilterProductSizes.productId],
+ references: [kilterProducts.id],
+ }),
+ kilterProductSizesLayoutsSets: many(kilterProductSizesLayoutsSets),
+ kilterWalls: many(kilterWalls),
+}));
+
+export const kilterBidsRelations = relations(kilterBids, ({ one }) => ({
+ kilterClimb: one(kilterClimbs, {
+ fields: [kilterBids.climbUuid],
+ references: [kilterClimbs.uuid],
+ }),
+ kilterUser: one(kilterUsers, {
+ fields: [kilterBids.userId],
+ references: [kilterUsers.id],
+ }),
+}));
+
+export const kilterUsersRelations = relations(kilterUsers, ({ many }) => ({
+ kilterBids: many(kilterBids),
+ kilterWalls: many(kilterWalls),
+ kilterAscents: many(kilterAscents),
+ kilterUserSyncs: many(kilterUserSyncs),
+}));
+
+export const kilterPlacementRolesRelations = relations(kilterPlacementRoles, ({ one, many }) => ({
+ kilterProduct: one(kilterProducts, {
+ fields: [kilterPlacementRoles.productId],
+ references: [kilterProducts.id],
+ }),
+ kilterPlacements: many(kilterPlacements),
+}));
+
+export const kilterPlacementsRelations = relations(kilterPlacements, ({ one }) => ({
+ kilterPlacementRole: one(kilterPlacementRoles, {
+ fields: [kilterPlacements.defaultPlacementRoleId],
+ references: [kilterPlacementRoles.id],
+ }),
+ kilterHole: one(kilterHoles, {
+ fields: [kilterPlacements.holeId],
+ references: [kilterHoles.id],
+ }),
+ kilterLayout: one(kilterLayouts, {
+ fields: [kilterPlacements.layoutId],
+ references: [kilterLayouts.id],
+ }),
+ kilterSet: one(kilterSets, {
+ fields: [kilterPlacements.setId],
+ references: [kilterSets.id],
+ }),
+}));
+
+export const kilterSetsRelations = relations(kilterSets, ({ many }) => ({
+ kilterPlacements: many(kilterPlacements),
+ kilterProductSizesLayoutsSets: many(kilterProductSizesLayoutsSets),
+ kilterWallsSets: many(kilterWallsSets),
+}));
+
+export const kilterProductSizesLayoutsSetsRelations = relations(kilterProductSizesLayoutsSets, ({ one }) => ({
+ kilterLayout: one(kilterLayouts, {
+ fields: [kilterProductSizesLayoutsSets.layoutId],
+ references: [kilterLayouts.id],
+ }),
+ kilterProductSize: one(kilterProductSizes, {
+ fields: [kilterProductSizesLayoutsSets.productSizeId],
+ references: [kilterProductSizes.id],
+ }),
+ kilterSet: one(kilterSets, {
+ fields: [kilterProductSizesLayoutsSets.setId],
+ references: [kilterSets.id],
+ }),
+}));
+
+export const kilterWallsRelations = relations(kilterWalls, ({ one, many }) => ({
+ kilterLayout: one(kilterLayouts, {
+ fields: [kilterWalls.layoutId],
+ references: [kilterLayouts.id],
+ }),
+ kilterProduct: one(kilterProducts, {
+ fields: [kilterWalls.productId],
+ references: [kilterProducts.id],
+ }),
+ kilterProductSize: one(kilterProductSizes, {
+ fields: [kilterWalls.productSizeId],
+ references: [kilterProductSizes.id],
+ }),
+ kilterUser: one(kilterUsers, {
+ fields: [kilterWalls.userId],
+ references: [kilterUsers.id],
+ }),
+ kilterWallsSets: many(kilterWallsSets),
+}));
+
+export const tensionBidsRelations = relations(tensionBids, ({ one }) => ({
+ tensionClimb: one(tensionClimbs, {
+ fields: [tensionBids.climbUuid],
+ references: [tensionClimbs.uuid],
+ }),
+ tensionUser: one(tensionUsers, {
+ fields: [tensionBids.userId],
+ references: [tensionUsers.id],
+ }),
+}));
+
+export const tensionUsersRelations = relations(tensionUsers, ({ many }) => ({
+ tensionBids: many(tensionBids),
+ tensionWalls: many(tensionWalls),
+ tensionAscents: many(tensionAscents),
+ tensionUserSyncs: many(tensionUserSyncs),
+}));
+
+export const tensionHolesRelations = relations(tensionHoles, ({ one, many }) => ({
+ tensionHole: one(tensionHoles, {
+ fields: [tensionHoles.mirroredHoleId],
+ references: [tensionHoles.id],
+ relationName: 'tensionHoles_mirroredHoleId_tensionHoles_id',
+ }),
+ tensionHoles: many(tensionHoles, {
+ relationName: 'tensionHoles_mirroredHoleId_tensionHoles_id',
+ }),
+ tensionProduct: one(tensionProducts, {
+ fields: [tensionHoles.productId],
+ references: [tensionProducts.id],
+ }),
+ tensionPlacements: many(tensionPlacements),
+ tensionLeds: many(tensionLeds),
+}));
+
+export const tensionProductsRelations = relations(tensionProducts, ({ many }) => ({
+ tensionHoles: many(tensionHoles),
+ tensionPlacementRoles: many(tensionPlacementRoles),
+ tensionLayouts: many(tensionLayouts),
+ tensionProductSizes: many(tensionProductSizes),
+ tensionWalls: many(tensionWalls),
+ tensionProductsAngles: many(tensionProductsAngles),
+}));
+
+export const tensionLayoutsRelations = relations(tensionLayouts, ({ one, many }) => ({
+ tensionClimbs: many(tensionClimbs),
+ tensionPlacements: many(tensionPlacements),
+ tensionProduct: one(tensionProducts, {
+ fields: [tensionLayouts.productId],
+ references: [tensionProducts.id],
+ }),
+ tensionProductSizesLayoutsSets: many(tensionProductSizesLayoutsSets),
+ tensionWalls: many(tensionWalls),
+}));
+
+export const tensionPlacementsRelations = relations(tensionPlacements, ({ one }) => ({
+ tensionPlacementRole: one(tensionPlacementRoles, {
+ fields: [tensionPlacements.defaultPlacementRoleId],
+ references: [tensionPlacementRoles.id],
+ }),
+ tensionHole: one(tensionHoles, {
+ fields: [tensionPlacements.holeId],
+ references: [tensionHoles.id],
+ }),
+ tensionLayout: one(tensionLayouts, {
+ fields: [tensionPlacements.layoutId],
+ references: [tensionLayouts.id],
+ }),
+ tensionSet: one(tensionSets, {
+ fields: [tensionPlacements.setId],
+ references: [tensionSets.id],
+ }),
+}));
+
+export const tensionPlacementRolesRelations = relations(tensionPlacementRoles, ({ one, many }) => ({
+ tensionPlacements: many(tensionPlacements),
+ tensionProduct: one(tensionProducts, {
+ fields: [tensionPlacementRoles.productId],
+ references: [tensionProducts.id],
+ }),
+}));
+
+export const tensionSetsRelations = relations(tensionSets, ({ many }) => ({
+ tensionPlacements: many(tensionPlacements),
+ tensionProductSizesLayoutsSets: many(tensionProductSizesLayoutsSets),
+ tensionWallsSets: many(tensionWallsSets),
+}));
+
+export const tensionLedsRelations = relations(tensionLeds, ({ one }) => ({
+ tensionHole: one(tensionHoles, {
+ fields: [tensionLeds.holeId],
+ references: [tensionHoles.id],
+ }),
+ tensionProductSize: one(tensionProductSizes, {
+ fields: [tensionLeds.productSizeId],
+ references: [tensionProductSizes.id],
+ }),
+}));
+
+export const tensionProductSizesRelations = relations(tensionProductSizes, ({ one, many }) => ({
+ tensionLeds: many(tensionLeds),
+ tensionProductSizesLayoutsSets: many(tensionProductSizesLayoutsSets),
+ tensionProduct: one(tensionProducts, {
+ fields: [tensionProductSizes.productId],
+ references: [tensionProducts.id],
+ }),
+ tensionWalls: many(tensionWalls),
+}));
+
+export const tensionProductSizesLayoutsSetsRelations = relations(tensionProductSizesLayoutsSets, ({ one }) => ({
+ tensionLayout: one(tensionLayouts, {
+ fields: [tensionProductSizesLayoutsSets.layoutId],
+ references: [tensionLayouts.id],
+ }),
+ tensionProductSize: one(tensionProductSizes, {
+ fields: [tensionProductSizesLayoutsSets.productSizeId],
+ references: [tensionProductSizes.id],
+ }),
+ tensionSet: one(tensionSets, {
+ fields: [tensionProductSizesLayoutsSets.setId],
+ references: [tensionSets.id],
+ }),
+}));
+
+export const kilterAscentsRelations = relations(kilterAscents, ({ one }) => ({
+ kilterAttempt: one(kilterAttempts, {
+ fields: [kilterAscents.attemptId],
+ references: [kilterAttempts.id],
+ }),
+ kilterClimb: one(kilterClimbs, {
+ fields: [kilterAscents.climbUuid],
+ references: [kilterClimbs.uuid],
+ }),
+ kilterDifficultyGrade: one(kilterDifficultyGrades, {
+ fields: [kilterAscents.difficulty],
+ references: [kilterDifficultyGrades.difficulty],
+ }),
+ kilterUser: one(kilterUsers, {
+ fields: [kilterAscents.userId],
+ references: [kilterUsers.id],
+ }),
+}));
+
+export const kilterAttemptsRelations = relations(kilterAttempts, ({ many }) => ({
+ kilterAscents: many(kilterAscents),
+}));
+
+export const kilterDifficultyGradesRelations = relations(kilterDifficultyGrades, ({ many }) => ({
+ kilterAscents: many(kilterAscents),
+}));
+
+export const tensionWallsRelations = relations(tensionWalls, ({ one, many }) => ({
+ tensionLayout: one(tensionLayouts, {
+ fields: [tensionWalls.layoutId],
+ references: [tensionLayouts.id],
+ }),
+ tensionProduct: one(tensionProducts, {
+ fields: [tensionWalls.productId],
+ references: [tensionProducts.id],
+ }),
+ tensionProductSize: one(tensionProductSizes, {
+ fields: [tensionWalls.productSizeId],
+ references: [tensionProductSizes.id],
+ }),
+ tensionUser: one(tensionUsers, {
+ fields: [tensionWalls.userId],
+ references: [tensionUsers.id],
+ }),
+ tensionWallsSets: many(tensionWallsSets),
+}));
+
+export const tensionAscentsRelations = relations(tensionAscents, ({ one }) => ({
+ tensionAttempt: one(tensionAttempts, {
+ fields: [tensionAscents.attemptId],
+ references: [tensionAttempts.id],
+ }),
+ tensionClimb: one(tensionClimbs, {
+ fields: [tensionAscents.climbUuid],
+ references: [tensionClimbs.uuid],
+ }),
+ tensionDifficultyGrade: one(tensionDifficultyGrades, {
+ fields: [tensionAscents.difficulty],
+ references: [tensionDifficultyGrades.difficulty],
+ }),
+ tensionUser: one(tensionUsers, {
+ fields: [tensionAscents.userId],
+ references: [tensionUsers.id],
+ }),
+}));
+
+export const tensionAttemptsRelations = relations(tensionAttempts, ({ many }) => ({
+ tensionAscents: many(tensionAscents),
+}));
+
+export const tensionDifficultyGradesRelations = relations(tensionDifficultyGrades, ({ many }) => ({
+ tensionAscents: many(tensionAscents),
+}));
+
+export const kilterProductsAnglesRelations = relations(kilterProductsAngles, ({ one }) => ({
+ kilterProduct: one(kilterProducts, {
+ fields: [kilterProductsAngles.productId],
+ references: [kilterProducts.id],
+ }),
+}));
+
+export const kilterWallsSetsRelations = relations(kilterWallsSets, ({ one }) => ({
+ kilterSet: one(kilterSets, {
+ fields: [kilterWallsSets.setId],
+ references: [kilterSets.id],
+ }),
+ kilterWall: one(kilterWalls, {
+ fields: [kilterWallsSets.wallUuid],
+ references: [kilterWalls.uuid],
+ }),
+}));
+
+export const tensionWallsSetsRelations = relations(tensionWallsSets, ({ one }) => ({
+ tensionSet: one(tensionSets, {
+ fields: [tensionWallsSets.setId],
+ references: [tensionSets.id],
+ }),
+ tensionWall: one(tensionWalls, {
+ fields: [tensionWallsSets.wallUuid],
+ references: [tensionWalls.uuid],
+ }),
+}));
+
+export const tensionProductsAnglesRelations = relations(tensionProductsAngles, ({ one }) => ({
+ tensionProduct: one(tensionProducts, {
+ fields: [tensionProductsAngles.productId],
+ references: [tensionProducts.id],
+ }),
+}));
+
+export const kilterUserSyncsRelations = relations(kilterUserSyncs, ({ one }) => ({
+ kilterUser: one(kilterUsers, {
+ fields: [kilterUserSyncs.userId],
+ references: [kilterUsers.id],
+ }),
+}));
+
+export const tensionUserSyncsRelations = relations(tensionUserSyncs, ({ one }) => ({
+ tensionUser: one(tensionUsers, {
+ fields: [tensionUserSyncs.userId],
+ references: [tensionUsers.id],
+ }),
+}));
+
+export const kilterBetaLinksRelations = relations(kilterBetaLinks, ({ one }) => ({
+ kilterClimb: one(kilterClimbs, {
+ fields: [kilterBetaLinks.climbUuid],
+ references: [kilterClimbs.uuid],
+ }),
+}));
+
+export const tensionBetaLinksRelations = relations(tensionBetaLinks, ({ one }) => ({
+ tensionClimb: one(tensionClimbs, {
+ fields: [tensionBetaLinks.climbUuid],
+ references: [tensionClimbs.uuid],
+ }),
+}));
diff --git a/app/lib/db/schema.ts b/app/lib/db/schema.ts
new file mode 100644
index 0000000..d265668
--- /dev/null
+++ b/app/lib/db/schema.ts
@@ -0,0 +1,1218 @@
+import {
+ pgTable,
+ foreignKey,
+ bigserial,
+ text,
+ integer,
+ doublePrecision,
+ boolean,
+ bigint,
+ timestamp,
+ uniqueIndex,
+ primaryKey,
+ index,
+} from 'drizzle-orm/pg-core';
+import { sql } from 'drizzle-orm';
+
+export const kilterClimbCacheFields = pgTable(
+ 'kilter_climb_cache_fields',
+ {
+ id: bigserial({ mode: 'bigint' }).primaryKey().notNull(),
+ climbUuid: text('climb_uuid'),
+ ascensionistCount: integer('ascensionist_count'),
+ displayDifficulty: doublePrecision('display_difficulty'),
+ qualityAverage: doublePrecision('quality_average'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.climbUuid],
+ foreignColumns: [kilterClimbs.uuid],
+ name: 'climb_cache_fields_climb_uuid_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const kilterAttempts = pgTable('kilter_attempts', {
+ id: integer().primaryKey().notNull(),
+ position: integer(),
+ name: text(),
+});
+
+export const kilterClimbRandomPositions = pgTable('kilter_climb_random_positions', {
+ climbUuid: text('climb_uuid').primaryKey().notNull(),
+ position: integer(),
+});
+
+export const kilterLayouts = pgTable(
+ 'kilter_layouts',
+ {
+ id: integer().primaryKey().notNull(),
+ productId: integer('product_id'),
+ name: text(),
+ instagramCaption: text('instagram_caption'),
+ isMirrored: boolean('is_mirrored'),
+ isListed: boolean('is_listed'),
+ password: text(),
+ createdAt: text('created_at'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.productId],
+ foreignColumns: [kilterProducts.id],
+ name: 'layouts_product_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const kilterAndroidMetadata = pgTable('kilter_android_metadata', {
+ locale: text(),
+});
+
+export const kilterCircuits = pgTable('kilter_circuits', {
+ uuid: text().primaryKey().notNull(),
+ name: text(),
+ description: text(),
+ color: text(),
+ userId: integer('user_id'),
+ isPublic: boolean('is_public'),
+ createdAt: text('created_at'),
+ updatedAt: text('updated_at'),
+});
+
+// Update existing tables with unique constraints
+export const tensionClimbStats = pgTable(
+ 'tension_climb_stats',
+ {
+ id: bigserial({ mode: 'bigint' }).notNull(),
+ climbUuid: text('climb_uuid').notNull(),
+ angle: integer('angle').notNull(),
+ displayDifficulty: doublePrecision('display_difficulty'),
+ benchmarkDifficulty: doublePrecision('benchmark_difficulty'),
+ ascensionistCount: bigint('ascensionist_count', { mode: 'number' }),
+ difficultyAverage: doublePrecision('difficulty_average'),
+ qualityAverage: doublePrecision('quality_average'),
+ faUsername: text('fa_username'),
+ faAt: timestamp('fa_at', { mode: 'string' }),
+ },
+ (table) => ({
+ compositePk: primaryKey({ name: 'tension_climb_stats_pk', columns: [table.climbUuid, table.angle] }),
+ }),
+);
+
+// Add new history tables
+export const tensionClimbStatsHistory = pgTable('tension_climb_stats_history', {
+ id: bigserial({ mode: 'bigint' }).primaryKey().notNull(),
+ climbUuid: text('climb_uuid').notNull(),
+ angle: integer('angle').notNull(),
+ displayDifficulty: doublePrecision('display_difficulty'),
+ benchmarkDifficulty: doublePrecision('benchmark_difficulty'),
+ ascensionistCount: bigint('ascensionist_count', { mode: 'number' }),
+ difficultyAverage: doublePrecision('difficulty_average'),
+ qualityAverage: doublePrecision('quality_average'),
+ faUsername: text('fa_username'),
+ faAt: timestamp('fa_at', { mode: 'string' }),
+ createdAt: timestamp('created_at', { mode: 'string' }).defaultNow().notNull(),
+});
+
+export const tensionClimbCacheFields = pgTable(
+ 'tension_climb_cache_fields',
+ {
+ id: bigserial({ mode: 'bigint' }).primaryKey().notNull(),
+ climbUuid: text('climb_uuid'),
+ ascensionistCount: integer('ascensionist_count'),
+ displayDifficulty: doublePrecision('display_difficulty'),
+ qualityAverage: doublePrecision('quality_average'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.climbUuid],
+ foreignColumns: [tensionClimbs.uuid],
+ name: 'climb_cache_fields_climb_uuid_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const kilterLeds = pgTable(
+ 'kilter_leds',
+ {
+ id: integer().primaryKey().notNull(),
+ productSizeId: integer('product_size_id'),
+ holeId: integer('hole_id'),
+ position: integer(),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.holeId],
+ foreignColumns: [kilterHoles.id],
+ name: 'leds_hole_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ foreignKey({
+ columns: [table.productSizeId],
+ foreignColumns: [kilterProductSizes.id],
+ name: 'leds_product_size_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const kilterClimbStats = pgTable(
+ 'kilter_climb_stats',
+ {
+ id: bigserial({ mode: 'bigint' }).primaryKey().notNull(),
+ climbUuid: text('climb_uuid').notNull(),
+ angle: integer('angle').notNull(),
+ displayDifficulty: doublePrecision('display_difficulty'),
+ benchmarkDifficulty: doublePrecision('benchmark_difficulty'),
+ ascensionistCount: bigint('ascensionist_count', { mode: 'number' }),
+ difficultyAverage: doublePrecision('difficulty_average'),
+ qualityAverage: doublePrecision('quality_average'),
+ faUsername: text('fa_username'),
+ faAt: timestamp('fa_at', { mode: 'string' }),
+ },
+ (table) => ({
+ compositePk: primaryKey({ name: 'kilter_climb_stats_pk', columns: [table.climbUuid, table.angle] }),
+ }),
+);
+
+export const kilterClimbStatsHistory = pgTable('kilter_climb_stats_history', {
+ id: bigserial({ mode: 'bigint' }).primaryKey().notNull(),
+ climbUuid: text('climb_uuid').notNull(),
+ angle: integer('angle').notNull(),
+ displayDifficulty: doublePrecision('display_difficulty'),
+ benchmarkDifficulty: doublePrecision('benchmark_difficulty'),
+ ascensionistCount: bigint('ascensionist_count', { mode: 'number' }),
+ difficultyAverage: doublePrecision('difficulty_average'),
+ qualityAverage: doublePrecision('quality_average'),
+ faUsername: text('fa_username'),
+ faAt: timestamp('fa_at', { mode: 'string' }),
+ createdAt: timestamp('created_at', { mode: 'string' }).defaultNow().notNull(),
+});
+
+export const kilterProductSizes = pgTable(
+ 'kilter_product_sizes',
+ {
+ id: integer().primaryKey().notNull(),
+ productId: integer('product_id'),
+ edgeLeft: integer('edge_left'),
+ edgeRight: integer('edge_right'),
+ edgeBottom: integer('edge_bottom'),
+ edgeTop: integer('edge_top'),
+ name: text(),
+ description: text(),
+ imageFilename: text('image_filename'),
+ position: integer(),
+ isListed: boolean('is_listed'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.productId],
+ foreignColumns: [kilterProducts.id],
+ name: 'product_sizes_product_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const kilterKits = pgTable('kilter_kits', {
+ serialNumber: text('serial_number').primaryKey().notNull(),
+ name: text(),
+ isAutoconnect: boolean('is_autoconnect'),
+ isListed: boolean('is_listed'),
+ createdAt: text('created_at'),
+ updatedAt: text('updated_at'),
+});
+
+export const kilterBids = pgTable(
+ 'kilter_bids',
+ {
+ uuid: text().primaryKey().notNull(),
+ userId: integer('user_id'),
+ climbUuid: text('climb_uuid'),
+ angle: integer(),
+ isMirror: boolean('is_mirror'),
+ bidCount: integer('bid_count').default(1),
+ comment: text().default(''),
+ climbedAt: text('climbed_at'),
+ createdAt: text('created_at'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.climbUuid],
+ foreignColumns: [kilterClimbs.uuid],
+ name: 'bids_climb_uuid_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.userId],
+ foreignColumns: [kilterUsers.id],
+ name: 'bids_user_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const kilterHoles = pgTable(
+ 'kilter_holes',
+ {
+ id: integer().primaryKey().notNull(),
+ productId: integer('product_id'),
+ name: text(),
+ x: integer(),
+ y: integer(),
+ mirroredHoleId: integer('mirrored_hole_id'),
+ mirrorGroup: integer('mirror_group').default(0),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.productId],
+ foreignColumns: [kilterProducts.id],
+ name: 'holes_product_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const kilterPlacementRoles = pgTable(
+ 'kilter_placement_roles',
+ {
+ id: integer().primaryKey().notNull(),
+ productId: integer('product_id'),
+ position: integer(),
+ name: text(),
+ fullName: text('full_name'),
+ ledColor: text('led_color'),
+ screenColor: text('screen_color'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.productId],
+ foreignColumns: [kilterProducts.id],
+ name: 'placement_roles_product_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const kilterDifficultyGrades = pgTable('kilter_difficulty_grades', {
+ difficulty: integer().primaryKey().notNull(),
+ boulderName: text('boulder_name'),
+ routeName: text('route_name'),
+ isListed: boolean('is_listed'),
+});
+
+export const kilterPlacements = pgTable(
+ 'kilter_placements',
+ {
+ id: integer().primaryKey().notNull(),
+ layoutId: integer('layout_id'),
+ holeId: integer('hole_id'),
+ setId: integer('set_id'),
+ defaultPlacementRoleId: integer('default_placement_role_id'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.defaultPlacementRoleId],
+ foreignColumns: [kilterPlacementRoles.id],
+ name: 'placements_default_placement_role_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.holeId],
+ foreignColumns: [kilterHoles.id],
+ name: 'placements_hole_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ foreignKey({
+ columns: [table.layoutId],
+ foreignColumns: [kilterLayouts.id],
+ name: 'placements_layout_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ foreignKey({
+ columns: [table.setId],
+ foreignColumns: [kilterSets.id],
+ name: 'placements_set_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const kilterClimbs = pgTable(
+ 'kilter_climbs',
+ {
+ uuid: text().primaryKey().notNull(),
+ layoutId: integer('layout_id'),
+ setterId: integer('setter_id'),
+ setterUsername: text('setter_username'),
+ name: text(),
+ description: text().default(''),
+ hsm: integer(),
+ edgeLeft: integer('edge_left'),
+ edgeRight: integer('edge_right'),
+ edgeBottom: integer('edge_bottom'),
+ edgeTop: integer('edge_top'),
+ angle: integer(),
+ framesCount: integer('frames_count').default(1),
+ framesPace: integer('frames_pace').default(0),
+ frames: text(),
+ isDraft: boolean('is_draft').default(false),
+ isListed: boolean('is_listed'),
+ createdAt: text('created_at'),
+ },
+ (table) => ({
+ // Indexes
+ layoutFilterIdx: index('kilter_climbs_layout_filter_idx').on(
+ table.layoutId,
+ table.isListed,
+ table.isDraft,
+ table.framesCount,
+ ),
+ edgesIdx: index('kilter_climbs_edges_idx').on(table.edgeLeft, table.edgeRight, table.edgeBottom, table.edgeTop),
+
+ // Foreign Keys
+ layoutIdFkey: foreignKey({
+ columns: [table.layoutId],
+ foreignColumns: [kilterLayouts.id],
+ name: 'climbs_layout_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ }),
+);
+
+export const kilterProductSizesLayoutsSets = pgTable(
+ 'kilter_product_sizes_layouts_sets',
+ {
+ id: integer().primaryKey().notNull(),
+ productSizeId: integer('product_size_id'),
+ layoutId: integer('layout_id'),
+ setId: integer('set_id'),
+ imageFilename: text('image_filename'),
+ isListed: boolean('is_listed'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.layoutId],
+ foreignColumns: [kilterLayouts.id],
+ name: 'product_sizes_layouts_sets_layout_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ foreignKey({
+ columns: [table.productSizeId],
+ foreignColumns: [kilterProductSizes.id],
+ name: 'product_sizes_layouts_sets_product_size_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ foreignKey({
+ columns: [table.setId],
+ foreignColumns: [kilterSets.id],
+ name: 'product_sizes_layouts_sets_set_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const kilterSharedSyncs = pgTable('kilter_shared_syncs', {
+ tableName: text('table_name').primaryKey().notNull(),
+ lastSynchronizedAt: text('last_synchronized_at'),
+});
+
+export const kilterUsers = pgTable('kilter_users', {
+ id: integer().primaryKey().notNull(),
+ username: text(),
+ createdAt: text('created_at'),
+});
+
+export const kilterProducts = pgTable('kilter_products', {
+ id: integer().primaryKey().notNull(),
+ name: text(),
+ isListed: boolean('is_listed'),
+ password: text(),
+ minCountInFrame: integer('min_count_in_frame'),
+ maxCountInFrame: integer('max_count_in_frame'),
+});
+
+export const kilterSets = pgTable('kilter_sets', {
+ id: integer().primaryKey().notNull(),
+ name: text(),
+ hsm: integer(),
+});
+
+export const kilterWalls = pgTable(
+ 'kilter_walls',
+ {
+ uuid: text().primaryKey().notNull(),
+ userId: integer('user_id'),
+ name: text(),
+ productId: integer('product_id'),
+ isAdjustable: boolean('is_adjustable'),
+ angle: integer(),
+ layoutId: integer('layout_id'),
+ productSizeId: integer('product_size_id'),
+ hsm: integer(),
+ serialNumber: text('serial_number'),
+ createdAt: text('created_at'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.layoutId],
+ foreignColumns: [kilterLayouts.id],
+ name: 'walls_layout_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.productId],
+ foreignColumns: [kilterProducts.id],
+ name: 'walls_product_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.productSizeId],
+ foreignColumns: [kilterProductSizes.id],
+ name: 'walls_product_size_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.userId],
+ foreignColumns: [kilterUsers.id],
+ name: 'walls_user_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const tensionAndroidMetadata = pgTable('tension_android_metadata', {
+ locale: text(),
+});
+
+export const tensionAttempts = pgTable('tension_attempts', {
+ id: integer().primaryKey().notNull(),
+ position: integer(),
+ name: text(),
+});
+
+export const tensionBids = pgTable(
+ 'tension_bids',
+ {
+ uuid: text().primaryKey().notNull(),
+ userId: integer('user_id'),
+ climbUuid: text('climb_uuid'),
+ angle: integer(),
+ isMirror: boolean('is_mirror'),
+ bidCount: integer('bid_count').default(1),
+ comment: text().default(''),
+ climbedAt: text('climbed_at'),
+ createdAt: text('created_at'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.climbUuid],
+ foreignColumns: [tensionClimbs.uuid],
+ name: 'bids_climb_uuid_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.userId],
+ foreignColumns: [tensionUsers.id],
+ name: 'bids_user_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const tensionHoles = pgTable(
+ 'tension_holes',
+ {
+ id: integer().primaryKey().notNull(),
+ productId: integer('product_id'),
+ name: text(),
+ x: integer(),
+ y: integer(),
+ mirroredHoleId: integer('mirrored_hole_id'),
+ mirrorGroup: integer('mirror_group').default(0),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.mirroredHoleId],
+ foreignColumns: [table.id],
+ name: 'holes_mirrored_hole_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.productId],
+ foreignColumns: [tensionProducts.id],
+ name: 'holes_product_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const tensionCircuits = pgTable('tension_circuits', {
+ uuid: text().primaryKey().notNull(),
+ name: text(),
+ description: text(),
+ color: text(),
+ userId: integer('user_id'),
+ isPublic: boolean('is_public'),
+ createdAt: text('created_at'),
+ updatedAt: text('updated_at'),
+});
+
+export const tensionKits = pgTable('tension_kits', {
+ serialNumber: text('serial_number').primaryKey().notNull(),
+ name: text(),
+ isAutoconnect: boolean('is_autoconnect'),
+ isListed: boolean('is_listed'),
+ createdAt: text('created_at'),
+ updatedAt: text('updated_at'),
+});
+
+export const tensionClimbRandomPositions = pgTable('tension_climb_random_positions', {
+ climbUuid: text('climb_uuid').primaryKey().notNull(),
+ position: integer(),
+});
+
+export const tensionClimbs = pgTable(
+ 'tension_climbs',
+ {
+ uuid: text().primaryKey().notNull(),
+ layoutId: integer('layout_id'),
+ setterId: integer('setter_id'),
+ setterUsername: text('setter_username'),
+ name: text(),
+ description: text().default(''),
+ hsm: integer(),
+ edgeLeft: integer('edge_left'),
+ edgeRight: integer('edge_right'),
+ edgeBottom: integer('edge_bottom'),
+ edgeTop: integer('edge_top'),
+ angle: integer(),
+ framesCount: integer('frames_count').default(1),
+ framesPace: integer('frames_pace').default(0),
+ frames: text(),
+ isDraft: boolean('is_draft').default(false),
+ isListed: boolean('is_listed'),
+ createdAt: text('created_at'),
+ },
+ (table) => ({
+ // Indexes
+ layoutFilterIdx: index('tension_climbs_layout_filter_idx').on(
+ table.layoutId,
+ table.isListed,
+ table.isDraft,
+ table.framesCount,
+ ),
+ edgesIdx: index('tension_climbs_edges_idx').on(table.edgeLeft, table.edgeRight, table.edgeBottom, table.edgeTop),
+
+ // Foreign Keys
+ layoutIdFkey: foreignKey({
+ columns: [table.layoutId],
+ foreignColumns: [tensionLayouts.id],
+ name: 'climbs_layout_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ }),
+);
+
+export const tensionSets = pgTable('tension_sets', {
+ id: integer().primaryKey().notNull(),
+ name: text(),
+ hsm: integer(),
+});
+
+export const tensionPlacements = pgTable(
+ 'tension_placements',
+ {
+ id: integer().primaryKey().notNull(),
+ layoutId: integer('layout_id'),
+ holeId: integer('hole_id'),
+ setId: integer('set_id'),
+ defaultPlacementRoleId: integer('default_placement_role_id'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.defaultPlacementRoleId],
+ foreignColumns: [tensionPlacementRoles.id],
+ name: 'placements_default_placement_role_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.holeId],
+ foreignColumns: [tensionHoles.id],
+ name: 'placements_hole_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ foreignKey({
+ columns: [table.layoutId],
+ foreignColumns: [tensionLayouts.id],
+ name: 'placements_layout_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ foreignKey({
+ columns: [table.setId],
+ foreignColumns: [tensionSets.id],
+ name: 'placements_set_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const tensionPlacementRoles = pgTable(
+ 'tension_placement_roles',
+ {
+ id: integer().primaryKey().notNull(),
+ productId: integer('product_id'),
+ position: integer(),
+ name: text(),
+ fullName: text('full_name'),
+ ledColor: text('led_color'),
+ screenColor: text('screen_color'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.productId],
+ foreignColumns: [tensionProducts.id],
+ name: 'placement_roles_product_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const tensionLeds = pgTable(
+ 'tension_leds',
+ {
+ id: integer().primaryKey().notNull(),
+ productSizeId: integer('product_size_id'),
+ holeId: integer('hole_id'),
+ position: integer(),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.holeId],
+ foreignColumns: [tensionHoles.id],
+ name: 'leds_hole_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ foreignKey({
+ columns: [table.productSizeId],
+ foreignColumns: [tensionProductSizes.id],
+ name: 'leds_product_size_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const tensionLayouts = pgTable(
+ 'tension_layouts',
+ {
+ id: integer().primaryKey().notNull(),
+ productId: integer('product_id'),
+ name: text(),
+ instagramCaption: text('instagram_caption'),
+ isMirrored: boolean('is_mirrored'),
+ isListed: boolean('is_listed'),
+ password: text(),
+ createdAt: text('created_at'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.productId],
+ foreignColumns: [tensionProducts.id],
+ name: 'layouts_product_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const tensionSharedSyncs = pgTable('tension_shared_syncs', {
+ tableName: text('table_name').primaryKey().notNull(),
+ lastSynchronizedAt: text('last_synchronized_at'),
+});
+
+export const tensionProductSizesLayoutsSets = pgTable(
+ 'tension_product_sizes_layouts_sets',
+ {
+ id: integer().primaryKey().notNull(),
+ productSizeId: integer('product_size_id'),
+ layoutId: integer('layout_id'),
+ setId: integer('set_id'),
+ imageFilename: text('image_filename'),
+ isListed: boolean('is_listed'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.layoutId],
+ foreignColumns: [tensionLayouts.id],
+ name: 'product_sizes_layouts_sets_layout_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ foreignKey({
+ columns: [table.productSizeId],
+ foreignColumns: [tensionProductSizes.id],
+ name: 'product_sizes_layouts_sets_product_size_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ foreignKey({
+ columns: [table.setId],
+ foreignColumns: [tensionSets.id],
+ name: 'product_sizes_layouts_sets_set_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const tensionProductSizes = pgTable(
+ 'tension_product_sizes',
+ {
+ id: integer().primaryKey().notNull(),
+ productId: integer('product_id'),
+ edgeLeft: integer('edge_left'),
+ edgeRight: integer('edge_right'),
+ edgeBottom: integer('edge_bottom'),
+ edgeTop: integer('edge_top'),
+ name: text(),
+ description: text(),
+ imageFilename: text('image_filename'),
+ position: integer(),
+ isListed: boolean('is_listed'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.productId],
+ foreignColumns: [tensionProducts.id],
+ name: 'product_sizes_product_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const kilterAscents = pgTable(
+ 'kilter_ascents',
+ {
+ uuid: text().primaryKey().notNull(),
+ climbUuid: text('climb_uuid'),
+ angle: integer(),
+ isMirror: boolean('is_mirror'),
+ userId: integer('user_id'),
+ attemptId: integer('attempt_id'),
+ bidCount: integer('bid_count').default(1),
+ quality: integer(),
+ difficulty: integer(),
+ isBenchmark: integer('is_benchmark').default(0),
+ comment: text().default(''),
+ climbedAt: text('climbed_at'),
+ createdAt: text('created_at'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.attemptId],
+ foreignColumns: [kilterAttempts.id],
+ name: 'ascents_attempt_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.climbUuid],
+ foreignColumns: [kilterClimbs.uuid],
+ name: 'ascents_climb_uuid_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.difficulty],
+ foreignColumns: [kilterDifficultyGrades.difficulty],
+ name: 'ascents_difficulty_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.userId],
+ foreignColumns: [kilterUsers.id],
+ name: 'ascents_user_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const tensionWalls = pgTable(
+ 'tension_walls',
+ {
+ uuid: text().primaryKey().notNull(),
+ userId: integer('user_id'),
+ name: text(),
+ productId: integer('product_id'),
+ isAdjustable: boolean('is_adjustable'),
+ angle: integer(),
+ layoutId: integer('layout_id'),
+ productSizeId: integer('product_size_id'),
+ hsm: integer(),
+ serialNumber: text('serial_number'),
+ createdAt: text('created_at'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.layoutId],
+ foreignColumns: [tensionLayouts.id],
+ name: 'walls_layout_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.productId],
+ foreignColumns: [tensionProducts.id],
+ name: 'walls_product_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.productSizeId],
+ foreignColumns: [tensionProductSizes.id],
+ name: 'walls_product_size_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.userId],
+ foreignColumns: [tensionUsers.id],
+ name: 'walls_user_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const tensionProducts = pgTable('tension_products', {
+ id: integer().primaryKey().notNull(),
+ name: text(),
+ isListed: boolean('is_listed'),
+ password: text(),
+ minCountInFrame: integer('min_count_in_frame'),
+ maxCountInFrame: integer('max_count_in_frame'),
+});
+
+export const tensionDifficultyGrades = pgTable('tension_difficulty_grades', {
+ difficulty: integer().primaryKey().notNull(),
+ boulderName: text('boulder_name'),
+ routeName: text('route_name'),
+ isListed: boolean('is_listed'),
+});
+
+export const tensionAscents = pgTable(
+ 'tension_ascents',
+ {
+ uuid: text().primaryKey().notNull(),
+ climbUuid: text('climb_uuid'),
+ angle: integer(),
+ isMirror: boolean('is_mirror'),
+ userId: integer('user_id'),
+ attemptId: integer('attempt_id'),
+ bidCount: integer('bid_count').default(1),
+ quality: integer(),
+ difficulty: integer(),
+ isBenchmark: integer('is_benchmark').default(0),
+ comment: text().default(''),
+ climbedAt: text('climbed_at'),
+ createdAt: text('created_at'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.attemptId],
+ foreignColumns: [tensionAttempts.id],
+ name: 'ascents_attempt_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.climbUuid],
+ foreignColumns: [tensionClimbs.uuid],
+ name: 'ascents_climb_uuid_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.difficulty],
+ foreignColumns: [tensionDifficultyGrades.difficulty],
+ name: 'ascents_difficulty_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.userId],
+ foreignColumns: [tensionUsers.id],
+ name: 'ascents_user_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const tensionUsers = pgTable('tension_users', {
+ id: integer().primaryKey().notNull(),
+ username: text(),
+ createdAt: text('created_at'),
+});
+
+export const kilterProductsAngles = pgTable(
+ 'kilter_products_angles',
+ {
+ productId: integer('product_id').notNull(),
+ angle: integer().notNull(),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.productId],
+ foreignColumns: [kilterProducts.id],
+ name: 'products_angles_product_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const kilterWallsSets = pgTable(
+ 'kilter_walls_sets',
+ {
+ wallUuid: text('wall_uuid').notNull(),
+ setId: integer('set_id').notNull(),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.setId],
+ foreignColumns: [kilterSets.id],
+ name: 'walls_sets_set_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.wallUuid],
+ foreignColumns: [kilterWalls.uuid],
+ name: 'walls_sets_wall_uuid_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const kilterUserPermissions = pgTable(
+ 'kilter_user_permissions',
+ {
+ userId: integer('user_id').notNull(),
+ name: text().notNull(),
+ },
+ (table) => [],
+);
+
+export const tensionUserPermissions = pgTable(
+ 'tension_user_permissions',
+ {
+ userId: integer('user_id').notNull(),
+ name: text().notNull(),
+ },
+ (table) => [],
+);
+
+export const tensionWallsSets = pgTable(
+ 'tension_walls_sets',
+ {
+ wallUuid: text('wall_uuid').notNull(),
+ setId: integer('set_id').notNull(),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.setId],
+ foreignColumns: [tensionSets.id],
+ name: 'walls_sets_set_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ foreignKey({
+ columns: [table.wallUuid],
+ foreignColumns: [tensionWalls.uuid],
+ name: 'walls_sets_wall_uuid_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const kilterCircuitsClimbs = pgTable(
+ 'kilter_circuits_climbs',
+ {
+ circuitUuid: text('circuit_uuid').notNull(),
+ climbUuid: text('climb_uuid').notNull(),
+ position: integer(),
+ },
+ (table) => [],
+);
+
+export const tensionProductsAngles = pgTable(
+ 'tension_products_angles',
+ {
+ productId: integer('product_id').notNull(),
+ angle: integer().notNull(),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.productId],
+ foreignColumns: [tensionProducts.id],
+ name: 'products_angles_product_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const kilterUserSyncs = pgTable(
+ 'kilter_user_syncs',
+ {
+ userId: integer('user_id').notNull(),
+ tableName: text('table_name').notNull(),
+ lastSynchronizedAt: text('last_synchronized_at'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.userId],
+ foreignColumns: [kilterUsers.id],
+ name: 'user_syncs_user_id_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const tensionCircuitsClimbs = pgTable(
+ 'tension_circuits_climbs',
+ {
+ circuitUuid: text('circuit_uuid').notNull(),
+ climbUuid: text('climb_uuid').notNull(),
+ position: integer(),
+ },
+ (table) => [],
+);
+
+export const tensionUserSyncs = pgTable(
+ 'tension_user_syncs',
+ {
+ userId: integer('user_id').notNull(),
+ tableName: text('table_name').notNull(),
+ lastSynchronizedAt: text('last_synchronized_at'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.userId],
+ foreignColumns: [tensionUsers.id],
+ name: 'user_syncs_user_id_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('cascade'),
+ ],
+);
+
+export const kilterTags = pgTable(
+ 'kilter_tags',
+ {
+ entityUuid: text('entity_uuid').notNull(),
+ userId: integer('user_id').notNull(),
+ name: text().notNull(),
+ isListed: boolean('is_listed'),
+ },
+ (table) => [],
+);
+
+export const tensionTags = pgTable(
+ 'tension_tags',
+ {
+ entityUuid: text('entity_uuid').notNull(),
+ userId: integer('user_id').notNull(),
+ name: text().notNull(),
+ isListed: boolean('is_listed'),
+ },
+ (table) => [],
+);
+
+export const kilterBetaLinks = pgTable(
+ 'kilter_beta_links',
+ {
+ climbUuid: text('climb_uuid').notNull(),
+ link: text().notNull(),
+ foreignUsername: text('foreign_username'),
+ angle: integer(),
+ thumbnail: text(),
+ isListed: boolean('is_listed'),
+ createdAt: text('created_at'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.climbUuid],
+ foreignColumns: [kilterClimbs.uuid],
+ name: 'beta_links_climb_uuid_fkey1',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ ],
+);
+
+export const tensionBetaLinks = pgTable(
+ 'tension_beta_links',
+ {
+ climbUuid: text('climb_uuid').notNull(),
+ link: text().notNull(),
+ foreignUsername: text('foreign_username'),
+ angle: integer(),
+ thumbnail: text(),
+ isListed: boolean('is_listed'),
+ createdAt: text('created_at'),
+ },
+ (table) => [
+ foreignKey({
+ columns: [table.climbUuid],
+ foreignColumns: [tensionClimbs.uuid],
+ name: 'beta_links_climb_uuid_fkey',
+ })
+ .onUpdate('cascade')
+ .onDelete('restrict'),
+ ],
+);
diff --git a/drizzle.config.ts b/drizzle.config.ts
index 98e96d6..1c3b878 100644
--- a/drizzle.config.ts
+++ b/drizzle.config.ts
@@ -1,10 +1,20 @@
-import 'dotenv/config';
+import { config } from 'dotenv';
import { defineConfig } from 'drizzle-kit';
+import path from 'path';
+
+// Load .env.development.local for local dev
+config({ path: path.resolve(process.cwd(), '.env.development.local') });
+
export default defineConfig({
out: './drizzle',
- schema: './src/db/schema.ts',
+ schema: './app/lib/db/schema.ts',
dialect: 'postgresql',
dbCredentials: {
- url: process.env.POSTGRES_URL!,
+ host: process.env.POSTGRES_HOST!,
+ database: process.env.POSTGRES_DATABASE!,
+ port: Number(process.env.POSTGRES_PORT || 5432),
+ user: process.env.POSTGRES_USER,
+ password: process.env.POSTGRES_PASSWORD,
+ ssl: process.env.VERCEL_ENV !== 'development',
},
-});
\ No newline at end of file
+});
diff --git a/drizzle/0002_unique_climbstats.sql b/drizzle/0002_unique_climbstats.sql
new file mode 100644
index 0000000..6340f04
--- /dev/null
+++ b/drizzle/0002_unique_climbstats.sql
@@ -0,0 +1,34 @@
+DROP INDEX IF EXISTS "kilter_climb_angle_idx";--> statement-breakpoint
+DROP INDEX IF EXISTS "idx_16791_sqlite_autoindex_holes_2";--> statement-breakpoint
+DROP INDEX IF EXISTS "idx_16791_sqlite_autoindex_holes_3";--> statement-breakpoint
+DROP INDEX IF EXISTS "idx_16797_sqlite_autoindex_leds_2";--> statement-breakpoint
+DROP INDEX IF EXISTS "idx_16904_sqlite_autoindex_placements_2";--> statement-breakpoint
+DROP INDEX IF EXISTS "idx_16813_sqlite_autoindex_product_sizes_layouts_sets_2";--> statement-breakpoint
+DROP INDEX IF EXISTS "tension_climb_angle_idx";--> statement-breakpoint
+DROP INDEX IF EXISTS "idx_16405_sqlite_autoindex_holes_2";--> statement-breakpoint
+DROP INDEX IF EXISTS "idx_16405_sqlite_autoindex_holes_3";--> statement-breakpoint
+DROP INDEX IF EXISTS "idx_16411_sqlite_autoindex_leds_2";--> statement-breakpoint
+DROP INDEX IF EXISTS "idx_16518_sqlite_autoindex_placements_2";--> statement-breakpoint
+DROP INDEX IF EXISTS "idx_16427_sqlite_autoindex_product_sizes_layouts_sets_2";--> statement-breakpoint
+ALTER TABLE "kilter_beta_links" DROP CONSTRAINT "idx_16883_sqlite_autoindex_beta_links_1";--> statement-breakpoint
+ALTER TABLE "kilter_circuits_climbs" DROP CONSTRAINT "idx_16868_sqlite_autoindex_circuits_climbs_1";--> statement-breakpoint
+ALTER TABLE "kilter_products_angles" DROP CONSTRAINT "idx_16800_sqlite_autoindex_products_angles_1";--> statement-breakpoint
+ALTER TABLE "kilter_tags" DROP CONSTRAINT "idx_16853_sqlite_autoindex_tags_1";--> statement-breakpoint
+ALTER TABLE "kilter_user_permissions" DROP CONSTRAINT "idx_16828_sqlite_autoindex_user_permissions_1";--> statement-breakpoint
+ALTER TABLE "kilter_user_syncs" DROP CONSTRAINT "idx_16833_sqlite_autoindex_user_syncs_1";--> statement-breakpoint
+ALTER TABLE "kilter_walls_sets" DROP CONSTRAINT "idx_16838_sqlite_autoindex_walls_sets_1";--> statement-breakpoint
+ALTER TABLE "tension_beta_links" DROP CONSTRAINT "idx_16497_sqlite_autoindex_beta_links_1";--> statement-breakpoint
+ALTER TABLE "tension_circuits_climbs" DROP CONSTRAINT "idx_16482_sqlite_autoindex_circuits_climbs_1";--> statement-breakpoint
+ALTER TABLE "tension_products_angles" DROP CONSTRAINT "idx_16414_sqlite_autoindex_products_angles_1";--> statement-breakpoint
+ALTER TABLE "tension_tags" DROP CONSTRAINT "idx_16472_sqlite_autoindex_tags_1";--> statement-breakpoint
+ALTER TABLE "tension_user_permissions" DROP CONSTRAINT "idx_16447_sqlite_autoindex_user_permissions_1";--> statement-breakpoint
+ALTER TABLE "tension_user_syncs" DROP CONSTRAINT "idx_16452_sqlite_autoindex_user_syncs_1";--> statement-breakpoint
+ALTER TABLE "tension_walls_sets" DROP CONSTRAINT "idx_16457_sqlite_autoindex_walls_sets_1";--> statement-breakpoint
+-- Drop the existing primary key constraints
+ALTER TABLE tension_climb_stats DROP CONSTRAINT IF EXISTS idx_33308_climb_stats_pkey;
+ALTER TABLE kilter_climb_stats DROP CONSTRAINT IF EXISTS idx_32922_climb_stats_pkey;
+
+ALTER TABLE tension_climb_stats DROP COLUMN IF EXISTS id;
+ALTER TABLE kilter_climb_stats DROP COLUMN IF EXISTS id;
+ALTER TABLE "kilter_climb_stats" ADD CONSTRAINT "kilter_climb_stats_pk" PRIMARY KEY("climb_uuid","angle");--> statement-breakpoint
+ALTER TABLE "tension_climb_stats" ADD CONSTRAINT "tension_climb_stats_pk" PRIMARY KEY("climb_uuid","angle");--> statement-breakpoint
\ No newline at end of file
diff --git a/drizzle/0003_add_climbs_indexes.sql b/drizzle/0003_add_climbs_indexes.sql
new file mode 100644
index 0000000..9eff1b9
--- /dev/null
+++ b/drizzle/0003_add_climbs_indexes.sql
@@ -0,0 +1,4 @@
+CREATE INDEX "kilter_climbs_layout_filter_idx" ON "kilter_climbs" USING btree ("layout_id","is_listed","is_draft","frames_count");--> statement-breakpoint
+CREATE INDEX "kilter_climbs_edges_idx" ON "kilter_climbs" USING btree ("edge_left","edge_right","edge_bottom","edge_top");--> statement-breakpoint
+CREATE INDEX "tension_climbs_layout_filter_idx" ON "tension_climbs" USING btree ("layout_id","is_listed","is_draft","frames_count");--> statement-breakpoint
+CREATE INDEX "tension_climbs_edges_idx" ON "tension_climbs" USING btree ("edge_left","edge_right","edge_bottom","edge_top");
\ No newline at end of file
diff --git a/drizzle/README.md b/drizzle/README.md
new file mode 100644
index 0000000..e1e9129
--- /dev/null
+++ b/drizzle/README.md
@@ -0,0 +1,13 @@
+# Drizzle in Boardsesh
+Writing this down because I will otherwise forget myself.
+I had issues adopting Prisma, so I decided to try some other random ORM.
+For no particular reason I ended up with Drizzle, which seems mostly okay.
+
+
+I made the mistake of deleting some of the snapshots, so dont do that again.
+
+To generate a new database migration follow these steps:
+1. Modify the lib/db/schema.ts file with the changes you want
+2. Run `npx drizzle-kit generate --name=$MigrationName$`
+3. Edit the generated SQL file, and make sure you're happy with it
+4. Run `npx drizzle-kit migrate` to exucute the migration
\ No newline at end of file
From df18a11c30fde24be7d4a4554f7c2909192a387e Mon Sep 17 00:00:00 2001
From: Marco de Jongh <1107647+marcodejongh@users.noreply.github.com>
Date: Sat, 28 Dec 2024 21:40:06 +1100
Subject: [PATCH 2/4] Revert "Only join latest climb_stats result"
This reverts commit 1f1134c520ce23fb33e263d5f8019c4652f40a96.
---
app/components/rest-api/api.ts | 2 +-
app/lib/data/queries.ts | 37 ++++++++++++++++------------------
2 files changed, 18 insertions(+), 21 deletions(-)
diff --git a/app/components/rest-api/api.ts b/app/components/rest-api/api.ts
index 29dc2e3..89fe275 100644
--- a/app/components/rest-api/api.ts
+++ b/app/components/rest-api/api.ts
@@ -36,7 +36,7 @@ export const fetchClimbs = async (
// Build the URL using the new route structure
const response = await fetch(
- `${API_BASE_URL}/v1/${routeParameters.board_name}/${routeParameters.layout_id}/${routeParameters.size_id}/${routeParameters.set_ids}/${routeParameters.angle}/search?${urlParams}&bustCache=38`,
+ `${API_BASE_URL}/v1/${routeParameters.board_name}/${routeParameters.layout_id}/${routeParameters.size_id}/${routeParameters.set_ids}/${routeParameters.angle}/search?${urlParams}&bustCache=40`,
);
const rawResults = await response.json();
diff --git a/app/lib/data/queries.ts b/app/lib/data/queries.ts
index adfbddd..f253d4a 100644
--- a/app/lib/data/queries.ts
+++ b/app/lib/data/queries.ts
@@ -196,26 +196,23 @@ export const searchClimbs = async (
const query = await sql.query({
text: `
- WITH latest_stats AS (
- SELECT DISTINCT ON (climb_uuid) *
- FROM ${getTableName(params.board_name, 'climb_stats')}
- WHERE angle = $11
- ORDER BY climb_uuid, id DESC
- ),
- filtered_climbs AS (
+ WITH filtered_climbs AS (
SELECT
climbs.uuid, climbs.setter_username, climbs.name, climbs.description,
- climbs.frames, latest_stats.angle, latest_stats.ascensionist_count,
+ climbs.frames, climb_stats.angle, climb_stats.ascensionist_count,
dg.boulder_name as difficulty,
- ROUND(latest_stats.quality_average::numeric, 2) as quality_average,
- ROUND(latest_stats.difficulty_average::numeric - latest_stats.display_difficulty::numeric, 2) AS difficulty_error,
- latest_stats.benchmark_difficulty
+ ROUND(climb_stats.quality_average::numeric, 2) as quality_average,
+ ROUND(climb_stats.difficulty_average::numeric - climb_stats.display_difficulty::numeric, 2) AS difficulty_error,
+ climb_stats.benchmark_difficulty
FROM ${getTableName(params.board_name, 'climbs')} climbs
- LEFT JOIN latest_stats ON latest_stats.climb_uuid = climbs.uuid
+ LEFT JOIN ${getTableName(
+ params.board_name,
+ 'climb_stats',
+ )} climb_stats ON climb_stats.climb_uuid = climbs.uuid
LEFT JOIN ${getTableName(
params.board_name,
'difficulty_grades',
- )} dg on dg.difficulty = ROUND(latest_stats.display_difficulty::numeric)
+ )} dg on dg.difficulty = ROUND(climb_stats.display_difficulty::numeric)
INNER JOIN ${getTableName(params.board_name, 'product_sizes')} product_sizes ON product_sizes.id = $1
WHERE climbs.layout_id = $2
AND climbs.is_listed = true
@@ -224,8 +221,8 @@ export const searchClimbs = async (
-- Ensures only boulder problems are found and not routes
AND climbs.frames_count = 1
AND product_sizes.id = $3
- AND latest_stats.angle = $11
- AND latest_stats.ascensionist_count >= $4
+ AND climb_stats.angle = $11
+ AND climb_stats.ascensionist_count >= $4
AND climbs.edge_left > product_sizes.edge_left
AND climbs.edge_right < product_sizes.edge_right
@@ -233,12 +230,12 @@ export const searchClimbs = async (
AND climbs.edge_top < product_sizes.edge_top
${
searchParams.minGrade && searchParams.maxGrade
- ? 'AND ROUND(latest_stats.display_difficulty::numeric, 0) BETWEEN $5 AND $6'
+ ? 'AND ROUND(climb_stats.display_difficulty::numeric, 0) BETWEEN $5 AND $6'
: ''
}
- AND latest_stats.quality_average >= $7
- AND ABS(ROUND(latest_stats.display_difficulty::numeric, 0) - latest_stats.difficulty_average::numeric) <= $8
- ${climbNameClause}
+ AND climb_stats.quality_average >= $7
+ AND ABS(ROUND(climb_stats.display_difficulty::numeric, 0) - climb_stats.difficulty_average::numeric) <= $8
+ ${climbNameClause} -- Conditionally add the name filter
)
SELECT *,
(SELECT COUNT(*) FROM filtered_climbs) as total_count
@@ -246,7 +243,7 @@ export const searchClimbs = async (
ORDER BY ${safeSortBy} ${searchParams.sortOrder === 'asc' ? 'ASC' : 'DESC'}, filtered_climbs.uuid ASC
LIMIT $9 OFFSET $10
`,
- values: queryParameters,
+ values: queryParameters, // Remove any null values that don't match query clauses
});
return {
From 9ab0d225405cdb49d9cbdcb06aa3dcc1ed15ffe9 Mon Sep 17 00:00:00 2001
From: Marco de Jongh <1107647+marcodejongh@users.noreply.github.com>
Date: Sat, 28 Dec 2024 19:26:31 +1100
Subject: [PATCH 3/4] Make climbStats only save 1 row per climb_uuid/angle
combination
Climbstats should be overwritten for every update, but the existin code
adds more rows for every sync, this resulted in the climb list showing duplicate rows
due to the join matching those duplicate climb stats.
As a temporary fix, I added expensive deduplication ot the climb list query,
but with this change it'll only write one row, and will allow me to simplify the query
---
app/lib/data-sync/aurora/shared-sync.ts | 66 +-
drizzle/0001_unique_climbstats.sql | 87 +
drizzle/meta/0001_snapshot.json | 4328 +++++++++++++++++++++++
drizzle/meta/_journal.json | 7 +
drizzle/schema.ts | 83 +-
package.json | 2 +-
6 files changed, 4518 insertions(+), 55 deletions(-)
create mode 100644 drizzle/0001_unique_climbstats.sql
create mode 100644 drizzle/meta/0001_snapshot.json
diff --git a/app/lib/data-sync/aurora/shared-sync.ts b/app/lib/data-sync/aurora/shared-sync.ts
index bbb17ae..5c54366 100644
--- a/app/lib/data-sync/aurora/shared-sync.ts
+++ b/app/lib/data-sync/aurora/shared-sync.ts
@@ -35,6 +35,8 @@ import {
tensionPlacements,
tensionClimbs,
tensionBetaLinks,
+ tensionClimbStatsHistory,
+ kilterClimbStatsHistory,
} from '@/drizzle/schema';
import { PgQueryResultHKT, PgTransaction } from 'drizzle-orm/pg-core';
import { VercelPgQueryResultHKT } from 'drizzle-orm/vercel-postgres';
@@ -77,6 +79,7 @@ function getSharedSchemas(board: BoardName) {
climbStats: kilterClimbStats,
betaLinks: kilterBetaLinks,
sharedSyncs: kilterSharedSyncs,
+ climbStatsHistory: kilterClimbStatsHistory,
};
} else if (board === 'tension') {
return {
@@ -95,6 +98,7 @@ function getSharedSchemas(board: BoardName) {
climbStats: tensionClimbStats,
betaLinks: tensionBetaLinks,
sharedSyncs: tensionSharedSyncs,
+ climbStatsHistory: tensionClimbStatsHistory,
};
}
throw new Error(`Unsupported board type: ${board}`);
@@ -201,31 +205,38 @@ async function upsertSharedTableData(
}
case 'climb_stats': {
- // Drop constraints if they exist (using raw SQL since Drizzle doesn't have a direct way)
- await db.execute(sql`
- ALTER TABLE IF EXISTS ${sql.identifier(boardName + '_climb_stats')}
- DROP CONSTRAINT IF EXISTS climb_stats_climb_uuid_fkey1;
- ALTER TABLE IF EXISTS ${sql.identifier(boardName + '_climb_stats')}
- DROP CONSTRAINT IF EXISTS climb_stats_climb_uuid_fkey;
- `);
+ await Promise.all(
+ data.map((item) =>
+ Promise.all([
+ // Update current stats
+ db
+ .insert(schemas.climbStats)
+ .values({
+ climbUuid: item.climb_uuid,
+ angle: Number(item.angle),
+ displayDifficulty: Number(item.display_difficulty || item.difficulty_average),
+ benchmarkDifficulty: Number(item.benchmark_difficulty),
+ ascensionistCount: Number(item.ascensionist_count),
+ difficultyAverage: Number(item.difficulty_average),
+ qualityAverage: Number(item.quality_average),
+ faUsername: item.fa_username,
+ faAt: item.fa_at,
+ })
+ .onConflictDoUpdate({
+ target: [schemas.climbStats.climbUuid, schemas.climbStats.angle], // Updated to use new unique constraint
+ set: {
+ displayDifficulty: Number(item.display_difficulty || item.difficulty_average),
+ benchmarkDifficulty: Number(item.benchmark_difficulty),
+ ascensionistCount: Number(item.ascensionist_count),
+ difficultyAverage: Number(item.difficulty_average),
+ qualityAverage: Number(item.quality_average),
+ faUsername: item.fa_username,
+ faAt: item.fa_at,
+ },
+ }),
- await processBatch(data, async (item) => {
- await db
- .insert(schemas.climbStats)
- .values({
- climbUuid: item.climb_uuid,
- angle: Number(item.angle),
- displayDifficulty: Number(item.display_difficulty || item.difficulty_average),
- benchmarkDifficulty: Number(item.benchmark_difficulty),
- ascensionistCount: Number(item.ascensionist_count),
- difficultyAverage: Number(item.difficulty_average),
- qualityAverage: Number(item.quality_average),
- faUsername: item.fa_username,
- faAt: item.fa_at,
- })
- .onConflictDoUpdate({
- target: schemas.climbStats.id,
- set: {
+ // Also insert into history table
+ db.insert(schemas.climbStatsHistory).values({
climbUuid: item.climb_uuid,
angle: Number(item.angle),
displayDifficulty: Number(item.display_difficulty || item.difficulty_average),
@@ -235,9 +246,10 @@ async function upsertSharedTableData(
qualityAverage: Number(item.quality_average),
faUsername: item.fa_username,
faAt: item.fa_at,
- },
- });
- });
+ }),
+ ]),
+ ),
+ );
break;
}
diff --git a/drizzle/0001_unique_climbstats.sql b/drizzle/0001_unique_climbstats.sql
new file mode 100644
index 0000000..2a6bd50
--- /dev/null
+++ b/drizzle/0001_unique_climbstats.sql
@@ -0,0 +1,87 @@
+CREATE TABLE "kilter_climb_stats_history" (
+ "id" bigserial PRIMARY KEY NOT NULL,
+ "climb_uuid" text NOT NULL,
+ "angle" bigint NOT NULL,
+ "display_difficulty" double precision,
+ "benchmark_difficulty" double precision,
+ "ascensionist_count" bigint,
+ "difficulty_average" double precision,
+ "quality_average" double precision,
+ "fa_username" text,
+ "fa_at" timestamp,
+ "created_at" timestamp DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "tension_climb_stats_history" (
+ "id" bigserial PRIMARY KEY NOT NULL,
+ "climb_uuid" text NOT NULL,
+ "angle" bigint NOT NULL,
+ "display_difficulty" double precision,
+ "benchmark_difficulty" double precision,
+ "ascensionist_count" bigint,
+ "difficulty_average" double precision,
+ "quality_average" double precision,
+ "fa_username" text,
+ "fa_at" timestamp,
+ "created_at" timestamp DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+ALTER TABLE "tension_climb_stats" ALTER COLUMN "climb_uuid" SET NOT NULL;
+--> statement-breakpoint
+ALTER TABLE "tension_climb_stats" ALTER COLUMN "angle" SET NOT NULL;
+--> statement-breakpoint
+ALTER TABLE "kilter_climb_stats" ALTER COLUMN "climb_uuid" SET NOT NULL;
+--> statement-breakpoint
+ALTER TABLE "kilter_climb_stats" ALTER COLUMN "angle" SET NOT NULL;
+--> statement-breakpoint
+
+-- Copy existing records into history tables
+INSERT INTO tension_climb_stats_history (
+ climb_uuid, angle, display_difficulty, benchmark_difficulty,
+ ascensionist_count, difficulty_average, quality_average,
+ fa_username, fa_at
+)
+SELECT
+ climb_uuid, angle, display_difficulty, benchmark_difficulty,
+ ascensionist_count, difficulty_average, quality_average,
+ fa_username, fa_at
+FROM tension_climb_stats;
+--> statement-breakpoint
+
+INSERT INTO kilter_climb_stats_history (
+ climb_uuid, angle, display_difficulty, benchmark_difficulty,
+ ascensionist_count, difficulty_average, quality_average,
+ fa_username, fa_at
+)
+SELECT
+ climb_uuid, angle, display_difficulty, benchmark_difficulty,
+ ascensionist_count, difficulty_average, quality_average,
+ fa_username, fa_at
+FROM kilter_climb_stats;
+--> statement-breakpoint
+
+-- Deduplicate existing climb_stats by keeping highest id
+DELETE FROM tension_climb_stats a USING (
+ SELECT climb_uuid, angle, MAX(id) as max_id
+ FROM tension_climb_stats
+ GROUP BY climb_uuid, angle
+) b
+WHERE a.climb_uuid = b.climb_uuid
+AND a.angle = b.angle
+AND a.id < b.max_id;
+--> statement-breakpoint
+
+DELETE FROM kilter_climb_stats a USING (
+ SELECT climb_uuid, angle, MAX(id) as max_id
+ FROM kilter_climb_stats
+ GROUP BY climb_uuid, angle
+) b
+WHERE a.climb_uuid = b.climb_uuid
+AND a.angle = b.angle
+AND a.id < b.max_id;
+--> statement-breakpoint
+
+-- Add unique constraints
+CREATE UNIQUE INDEX "tension_climb_angle_idx" ON "tension_climb_stats" USING btree ("climb_uuid","angle");
+--> statement-breakpoint
+CREATE UNIQUE INDEX "kilter_climb_angle_idx" ON "kilter_climb_stats" USING btree ("climb_uuid","angle");
\ No newline at end of file
diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json
new file mode 100644
index 0000000..1860fba
--- /dev/null
+++ b/drizzle/meta/0001_snapshot.json
@@ -0,0 +1,4328 @@
+{
+ "id": "b5d3052c-b0d5-4aa1-98e7-aa987fcbf458",
+ "prevId": "00000000-0000-0000-0000-000000000000",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.kilter_android_metadata": {
+ "name": "kilter_android_metadata",
+ "schema": "",
+ "columns": {
+ "locale": {
+ "name": "locale",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_ascents": {
+ "name": "kilter_ascents",
+ "schema": "",
+ "columns": {
+ "uuid": {
+ "name": "uuid",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "climb_uuid": {
+ "name": "climb_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "angle": {
+ "name": "angle",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_mirror": {
+ "name": "is_mirror",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "attempt_id": {
+ "name": "attempt_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bid_count": {
+ "name": "bid_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 1
+ },
+ "quality": {
+ "name": "quality",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "difficulty": {
+ "name": "difficulty",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_benchmark": {
+ "name": "is_benchmark",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "comment": {
+ "name": "comment",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "''"
+ },
+ "climbed_at": {
+ "name": "climbed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ascents_attempt_id_fkey1": {
+ "name": "ascents_attempt_id_fkey1",
+ "tableFrom": "kilter_ascents",
+ "tableTo": "kilter_attempts",
+ "columnsFrom": [
+ "attempt_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "ascents_climb_uuid_fkey1": {
+ "name": "ascents_climb_uuid_fkey1",
+ "tableFrom": "kilter_ascents",
+ "tableTo": "kilter_climbs",
+ "columnsFrom": [
+ "climb_uuid"
+ ],
+ "columnsTo": [
+ "uuid"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "ascents_difficulty_fkey1": {
+ "name": "ascents_difficulty_fkey1",
+ "tableFrom": "kilter_ascents",
+ "tableTo": "kilter_difficulty_grades",
+ "columnsFrom": [
+ "difficulty"
+ ],
+ "columnsTo": [
+ "difficulty"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "ascents_user_id_fkey1": {
+ "name": "ascents_user_id_fkey1",
+ "tableFrom": "kilter_ascents",
+ "tableTo": "kilter_users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_attempts": {
+ "name": "kilter_attempts",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_beta_links": {
+ "name": "kilter_beta_links",
+ "schema": "",
+ "columns": {
+ "climb_uuid": {
+ "name": "climb_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "link": {
+ "name": "link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "foreign_username": {
+ "name": "foreign_username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "angle": {
+ "name": "angle",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thumbnail": {
+ "name": "thumbnail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "beta_links_climb_uuid_fkey1": {
+ "name": "beta_links_climb_uuid_fkey1",
+ "tableFrom": "kilter_beta_links",
+ "tableTo": "kilter_climbs",
+ "columnsFrom": [
+ "climb_uuid"
+ ],
+ "columnsTo": [
+ "uuid"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {
+ "idx_16883_sqlite_autoindex_beta_links_1": {
+ "name": "idx_16883_sqlite_autoindex_beta_links_1",
+ "columns": [
+ "climb_uuid",
+ "link"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_bids": {
+ "name": "kilter_bids",
+ "schema": "",
+ "columns": {
+ "uuid": {
+ "name": "uuid",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "climb_uuid": {
+ "name": "climb_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "angle": {
+ "name": "angle",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_mirror": {
+ "name": "is_mirror",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bid_count": {
+ "name": "bid_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 1
+ },
+ "comment": {
+ "name": "comment",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "''"
+ },
+ "climbed_at": {
+ "name": "climbed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bids_climb_uuid_fkey1": {
+ "name": "bids_climb_uuid_fkey1",
+ "tableFrom": "kilter_bids",
+ "tableTo": "kilter_climbs",
+ "columnsFrom": [
+ "climb_uuid"
+ ],
+ "columnsTo": [
+ "uuid"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "bids_user_id_fkey1": {
+ "name": "bids_user_id_fkey1",
+ "tableFrom": "kilter_bids",
+ "tableTo": "kilter_users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_circuits": {
+ "name": "kilter_circuits",
+ "schema": "",
+ "columns": {
+ "uuid": {
+ "name": "uuid",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_public": {
+ "name": "is_public",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_circuits_climbs": {
+ "name": "kilter_circuits_climbs",
+ "schema": "",
+ "columns": {
+ "circuit_uuid": {
+ "name": "circuit_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "climb_uuid": {
+ "name": "climb_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "idx_16868_sqlite_autoindex_circuits_climbs_1": {
+ "name": "idx_16868_sqlite_autoindex_circuits_climbs_1",
+ "columns": [
+ "circuit_uuid",
+ "climb_uuid"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_climb_cache_fields": {
+ "name": "kilter_climb_cache_fields",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "climb_uuid": {
+ "name": "climb_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ascensionist_count": {
+ "name": "ascensionist_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "display_difficulty": {
+ "name": "display_difficulty",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "quality_average": {
+ "name": "quality_average",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "climb_cache_fields_climb_uuid_fkey1": {
+ "name": "climb_cache_fields_climb_uuid_fkey1",
+ "tableFrom": "kilter_climb_cache_fields",
+ "tableTo": "kilter_climbs",
+ "columnsFrom": [
+ "climb_uuid"
+ ],
+ "columnsTo": [
+ "uuid"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_climb_random_positions": {
+ "name": "kilter_climb_random_positions",
+ "schema": "",
+ "columns": {
+ "climb_uuid": {
+ "name": "climb_uuid",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_climb_stats": {
+ "name": "kilter_climb_stats",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "climb_uuid": {
+ "name": "climb_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "angle": {
+ "name": "angle",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_difficulty": {
+ "name": "display_difficulty",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "benchmark_difficulty": {
+ "name": "benchmark_difficulty",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ascensionist_count": {
+ "name": "ascensionist_count",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "difficulty_average": {
+ "name": "difficulty_average",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "quality_average": {
+ "name": "quality_average",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "fa_username": {
+ "name": "fa_username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "fa_at": {
+ "name": "fa_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "kilter_climb_angle_idx": {
+ "name": "kilter_climb_angle_idx",
+ "columns": [
+ {
+ "expression": "climb_uuid",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "angle",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_climb_stats_history": {
+ "name": "kilter_climb_stats_history",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "climb_uuid": {
+ "name": "climb_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "angle": {
+ "name": "angle",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_difficulty": {
+ "name": "display_difficulty",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "benchmark_difficulty": {
+ "name": "benchmark_difficulty",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ascensionist_count": {
+ "name": "ascensionist_count",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "difficulty_average": {
+ "name": "difficulty_average",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "quality_average": {
+ "name": "quality_average",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "fa_username": {
+ "name": "fa_username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "fa_at": {
+ "name": "fa_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_climbs": {
+ "name": "kilter_climbs",
+ "schema": "",
+ "columns": {
+ "uuid": {
+ "name": "uuid",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "layout_id": {
+ "name": "layout_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "setter_id": {
+ "name": "setter_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "setter_username": {
+ "name": "setter_username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "''"
+ },
+ "hsm": {
+ "name": "hsm",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "edge_left": {
+ "name": "edge_left",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "edge_right": {
+ "name": "edge_right",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "edge_bottom": {
+ "name": "edge_bottom",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "edge_top": {
+ "name": "edge_top",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "angle": {
+ "name": "angle",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "frames_count": {
+ "name": "frames_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 1
+ },
+ "frames_pace": {
+ "name": "frames_pace",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "frames": {
+ "name": "frames",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_draft": {
+ "name": "is_draft",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "climbs_layout_id_fkey1": {
+ "name": "climbs_layout_id_fkey1",
+ "tableFrom": "kilter_climbs",
+ "tableTo": "kilter_layouts",
+ "columnsFrom": [
+ "layout_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_difficulty_grades": {
+ "name": "kilter_difficulty_grades",
+ "schema": "",
+ "columns": {
+ "difficulty": {
+ "name": "difficulty",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "boulder_name": {
+ "name": "boulder_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "route_name": {
+ "name": "route_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_holes": {
+ "name": "kilter_holes",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "product_id": {
+ "name": "product_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "x": {
+ "name": "x",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "y": {
+ "name": "y",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mirrored_hole_id": {
+ "name": "mirrored_hole_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mirror_group": {
+ "name": "mirror_group",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "idx_16791_sqlite_autoindex_holes_2": {
+ "name": "idx_16791_sqlite_autoindex_holes_2",
+ "columns": [
+ {
+ "expression": "product_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "text_ops"
+ },
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "text_ops"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_16791_sqlite_autoindex_holes_3": {
+ "name": "idx_16791_sqlite_autoindex_holes_3",
+ "columns": [
+ {
+ "expression": "product_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ },
+ {
+ "expression": "x",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ },
+ {
+ "expression": "y",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "holes_product_id_fkey1": {
+ "name": "holes_product_id_fkey1",
+ "tableFrom": "kilter_holes",
+ "tableTo": "kilter_products",
+ "columnsFrom": [
+ "product_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_kits": {
+ "name": "kilter_kits",
+ "schema": "",
+ "columns": {
+ "serial_number": {
+ "name": "serial_number",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_autoconnect": {
+ "name": "is_autoconnect",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_layouts": {
+ "name": "kilter_layouts",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "product_id": {
+ "name": "product_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "instagram_caption": {
+ "name": "instagram_caption",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_mirrored": {
+ "name": "is_mirrored",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "layouts_product_id_fkey1": {
+ "name": "layouts_product_id_fkey1",
+ "tableFrom": "kilter_layouts",
+ "tableTo": "kilter_products",
+ "columnsFrom": [
+ "product_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_leds": {
+ "name": "kilter_leds",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "product_size_id": {
+ "name": "product_size_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hole_id": {
+ "name": "hole_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_16797_sqlite_autoindex_leds_2": {
+ "name": "idx_16797_sqlite_autoindex_leds_2",
+ "columns": [
+ {
+ "expression": "product_size_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ },
+ {
+ "expression": "position",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "leds_hole_id_fkey1": {
+ "name": "leds_hole_id_fkey1",
+ "tableFrom": "kilter_leds",
+ "tableTo": "kilter_holes",
+ "columnsFrom": [
+ "hole_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "leds_product_size_id_fkey1": {
+ "name": "leds_product_size_id_fkey1",
+ "tableFrom": "kilter_leds",
+ "tableTo": "kilter_product_sizes",
+ "columnsFrom": [
+ "product_size_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_placement_roles": {
+ "name": "kilter_placement_roles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "product_id": {
+ "name": "product_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "full_name": {
+ "name": "full_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "led_color": {
+ "name": "led_color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "screen_color": {
+ "name": "screen_color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "placement_roles_product_id_fkey1": {
+ "name": "placement_roles_product_id_fkey1",
+ "tableFrom": "kilter_placement_roles",
+ "tableTo": "kilter_products",
+ "columnsFrom": [
+ "product_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_placements": {
+ "name": "kilter_placements",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "layout_id": {
+ "name": "layout_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hole_id": {
+ "name": "hole_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "set_id": {
+ "name": "set_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "default_placement_role_id": {
+ "name": "default_placement_role_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_16904_sqlite_autoindex_placements_2": {
+ "name": "idx_16904_sqlite_autoindex_placements_2",
+ "columns": [
+ {
+ "expression": "layout_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ },
+ {
+ "expression": "hole_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "placements_default_placement_role_id_fkey1": {
+ "name": "placements_default_placement_role_id_fkey1",
+ "tableFrom": "kilter_placements",
+ "tableTo": "kilter_placement_roles",
+ "columnsFrom": [
+ "default_placement_role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "placements_hole_id_fkey1": {
+ "name": "placements_hole_id_fkey1",
+ "tableFrom": "kilter_placements",
+ "tableTo": "kilter_holes",
+ "columnsFrom": [
+ "hole_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "placements_layout_id_fkey1": {
+ "name": "placements_layout_id_fkey1",
+ "tableFrom": "kilter_placements",
+ "tableTo": "kilter_layouts",
+ "columnsFrom": [
+ "layout_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "placements_set_id_fkey1": {
+ "name": "placements_set_id_fkey1",
+ "tableFrom": "kilter_placements",
+ "tableTo": "kilter_sets",
+ "columnsFrom": [
+ "set_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_product_sizes": {
+ "name": "kilter_product_sizes",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "product_id": {
+ "name": "product_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "edge_left": {
+ "name": "edge_left",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "edge_right": {
+ "name": "edge_right",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "edge_bottom": {
+ "name": "edge_bottom",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "edge_top": {
+ "name": "edge_top",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_filename": {
+ "name": "image_filename",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "product_sizes_product_id_fkey1": {
+ "name": "product_sizes_product_id_fkey1",
+ "tableFrom": "kilter_product_sizes",
+ "tableTo": "kilter_products",
+ "columnsFrom": [
+ "product_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_product_sizes_layouts_sets": {
+ "name": "kilter_product_sizes_layouts_sets",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "product_size_id": {
+ "name": "product_size_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "layout_id": {
+ "name": "layout_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "set_id": {
+ "name": "set_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_filename": {
+ "name": "image_filename",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_16813_sqlite_autoindex_product_sizes_layouts_sets_2": {
+ "name": "idx_16813_sqlite_autoindex_product_sizes_layouts_sets_2",
+ "columns": [
+ {
+ "expression": "product_size_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ },
+ {
+ "expression": "layout_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ },
+ {
+ "expression": "set_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "product_sizes_layouts_sets_layout_id_fkey1": {
+ "name": "product_sizes_layouts_sets_layout_id_fkey1",
+ "tableFrom": "kilter_product_sizes_layouts_sets",
+ "tableTo": "kilter_layouts",
+ "columnsFrom": [
+ "layout_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "product_sizes_layouts_sets_product_size_id_fkey1": {
+ "name": "product_sizes_layouts_sets_product_size_id_fkey1",
+ "tableFrom": "kilter_product_sizes_layouts_sets",
+ "tableTo": "kilter_product_sizes",
+ "columnsFrom": [
+ "product_size_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "product_sizes_layouts_sets_set_id_fkey1": {
+ "name": "product_sizes_layouts_sets_set_id_fkey1",
+ "tableFrom": "kilter_product_sizes_layouts_sets",
+ "tableTo": "kilter_sets",
+ "columnsFrom": [
+ "set_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_products": {
+ "name": "kilter_products",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "min_count_in_frame": {
+ "name": "min_count_in_frame",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "max_count_in_frame": {
+ "name": "max_count_in_frame",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_products_angles": {
+ "name": "kilter_products_angles",
+ "schema": "",
+ "columns": {
+ "product_id": {
+ "name": "product_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "angle": {
+ "name": "angle",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "products_angles_product_id_fkey1": {
+ "name": "products_angles_product_id_fkey1",
+ "tableFrom": "kilter_products_angles",
+ "tableTo": "kilter_products",
+ "columnsFrom": [
+ "product_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {
+ "idx_16800_sqlite_autoindex_products_angles_1": {
+ "name": "idx_16800_sqlite_autoindex_products_angles_1",
+ "columns": [
+ "product_id",
+ "angle"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_sets": {
+ "name": "kilter_sets",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hsm": {
+ "name": "hsm",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_shared_syncs": {
+ "name": "kilter_shared_syncs",
+ "schema": "",
+ "columns": {
+ "table_name": {
+ "name": "table_name",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "last_synchronized_at": {
+ "name": "last_synchronized_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_tags": {
+ "name": "kilter_tags",
+ "schema": "",
+ "columns": {
+ "entity_uuid": {
+ "name": "entity_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "idx_16853_sqlite_autoindex_tags_1": {
+ "name": "idx_16853_sqlite_autoindex_tags_1",
+ "columns": [
+ "entity_uuid",
+ "user_id",
+ "name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_user_permissions": {
+ "name": "kilter_user_permissions",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "idx_16828_sqlite_autoindex_user_permissions_1": {
+ "name": "idx_16828_sqlite_autoindex_user_permissions_1",
+ "columns": [
+ "user_id",
+ "name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_user_syncs": {
+ "name": "kilter_user_syncs",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "table_name": {
+ "name": "table_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_synchronized_at": {
+ "name": "last_synchronized_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_syncs_user_id_fkey1": {
+ "name": "user_syncs_user_id_fkey1",
+ "tableFrom": "kilter_user_syncs",
+ "tableTo": "kilter_users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {
+ "idx_16833_sqlite_autoindex_user_syncs_1": {
+ "name": "idx_16833_sqlite_autoindex_user_syncs_1",
+ "columns": [
+ "user_id",
+ "table_name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_users": {
+ "name": "kilter_users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_walls": {
+ "name": "kilter_walls",
+ "schema": "",
+ "columns": {
+ "uuid": {
+ "name": "uuid",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "product_id": {
+ "name": "product_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_adjustable": {
+ "name": "is_adjustable",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "angle": {
+ "name": "angle",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "layout_id": {
+ "name": "layout_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "product_size_id": {
+ "name": "product_size_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hsm": {
+ "name": "hsm",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serial_number": {
+ "name": "serial_number",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "walls_layout_id_fkey1": {
+ "name": "walls_layout_id_fkey1",
+ "tableFrom": "kilter_walls",
+ "tableTo": "kilter_layouts",
+ "columnsFrom": [
+ "layout_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "walls_product_id_fkey1": {
+ "name": "walls_product_id_fkey1",
+ "tableFrom": "kilter_walls",
+ "tableTo": "kilter_products",
+ "columnsFrom": [
+ "product_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "walls_product_size_id_fkey1": {
+ "name": "walls_product_size_id_fkey1",
+ "tableFrom": "kilter_walls",
+ "tableTo": "kilter_product_sizes",
+ "columnsFrom": [
+ "product_size_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "walls_user_id_fkey1": {
+ "name": "walls_user_id_fkey1",
+ "tableFrom": "kilter_walls",
+ "tableTo": "kilter_users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.kilter_walls_sets": {
+ "name": "kilter_walls_sets",
+ "schema": "",
+ "columns": {
+ "wall_uuid": {
+ "name": "wall_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "set_id": {
+ "name": "set_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "walls_sets_set_id_fkey1": {
+ "name": "walls_sets_set_id_fkey1",
+ "tableFrom": "kilter_walls_sets",
+ "tableTo": "kilter_sets",
+ "columnsFrom": [
+ "set_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "walls_sets_wall_uuid_fkey1": {
+ "name": "walls_sets_wall_uuid_fkey1",
+ "tableFrom": "kilter_walls_sets",
+ "tableTo": "kilter_walls",
+ "columnsFrom": [
+ "wall_uuid"
+ ],
+ "columnsTo": [
+ "uuid"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {
+ "idx_16838_sqlite_autoindex_walls_sets_1": {
+ "name": "idx_16838_sqlite_autoindex_walls_sets_1",
+ "columns": [
+ "wall_uuid",
+ "set_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_android_metadata": {
+ "name": "tension_android_metadata",
+ "schema": "",
+ "columns": {
+ "locale": {
+ "name": "locale",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_ascents": {
+ "name": "tension_ascents",
+ "schema": "",
+ "columns": {
+ "uuid": {
+ "name": "uuid",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "climb_uuid": {
+ "name": "climb_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "angle": {
+ "name": "angle",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_mirror": {
+ "name": "is_mirror",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "attempt_id": {
+ "name": "attempt_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bid_count": {
+ "name": "bid_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 1
+ },
+ "quality": {
+ "name": "quality",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "difficulty": {
+ "name": "difficulty",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_benchmark": {
+ "name": "is_benchmark",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "comment": {
+ "name": "comment",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "''"
+ },
+ "climbed_at": {
+ "name": "climbed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "ascents_attempt_id_fkey": {
+ "name": "ascents_attempt_id_fkey",
+ "tableFrom": "tension_ascents",
+ "tableTo": "tension_attempts",
+ "columnsFrom": [
+ "attempt_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "ascents_climb_uuid_fkey": {
+ "name": "ascents_climb_uuid_fkey",
+ "tableFrom": "tension_ascents",
+ "tableTo": "tension_climbs",
+ "columnsFrom": [
+ "climb_uuid"
+ ],
+ "columnsTo": [
+ "uuid"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "ascents_difficulty_fkey": {
+ "name": "ascents_difficulty_fkey",
+ "tableFrom": "tension_ascents",
+ "tableTo": "tension_difficulty_grades",
+ "columnsFrom": [
+ "difficulty"
+ ],
+ "columnsTo": [
+ "difficulty"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "ascents_user_id_fkey": {
+ "name": "ascents_user_id_fkey",
+ "tableFrom": "tension_ascents",
+ "tableTo": "tension_users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_attempts": {
+ "name": "tension_attempts",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_beta_links": {
+ "name": "tension_beta_links",
+ "schema": "",
+ "columns": {
+ "climb_uuid": {
+ "name": "climb_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "link": {
+ "name": "link",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "foreign_username": {
+ "name": "foreign_username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "angle": {
+ "name": "angle",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "thumbnail": {
+ "name": "thumbnail",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "beta_links_climb_uuid_fkey": {
+ "name": "beta_links_climb_uuid_fkey",
+ "tableFrom": "tension_beta_links",
+ "tableTo": "tension_climbs",
+ "columnsFrom": [
+ "climb_uuid"
+ ],
+ "columnsTo": [
+ "uuid"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {
+ "idx_16497_sqlite_autoindex_beta_links_1": {
+ "name": "idx_16497_sqlite_autoindex_beta_links_1",
+ "columns": [
+ "climb_uuid",
+ "link"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_bids": {
+ "name": "tension_bids",
+ "schema": "",
+ "columns": {
+ "uuid": {
+ "name": "uuid",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "climb_uuid": {
+ "name": "climb_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "angle": {
+ "name": "angle",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_mirror": {
+ "name": "is_mirror",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bid_count": {
+ "name": "bid_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 1
+ },
+ "comment": {
+ "name": "comment",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "''"
+ },
+ "climbed_at": {
+ "name": "climbed_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "bids_climb_uuid_fkey": {
+ "name": "bids_climb_uuid_fkey",
+ "tableFrom": "tension_bids",
+ "tableTo": "tension_climbs",
+ "columnsFrom": [
+ "climb_uuid"
+ ],
+ "columnsTo": [
+ "uuid"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "bids_user_id_fkey": {
+ "name": "bids_user_id_fkey",
+ "tableFrom": "tension_bids",
+ "tableTo": "tension_users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_circuits": {
+ "name": "tension_circuits",
+ "schema": "",
+ "columns": {
+ "uuid": {
+ "name": "uuid",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "color": {
+ "name": "color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_public": {
+ "name": "is_public",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_circuits_climbs": {
+ "name": "tension_circuits_climbs",
+ "schema": "",
+ "columns": {
+ "circuit_uuid": {
+ "name": "circuit_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "climb_uuid": {
+ "name": "climb_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "idx_16482_sqlite_autoindex_circuits_climbs_1": {
+ "name": "idx_16482_sqlite_autoindex_circuits_climbs_1",
+ "columns": [
+ "circuit_uuid",
+ "climb_uuid"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_climb_cache_fields": {
+ "name": "tension_climb_cache_fields",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "climb_uuid": {
+ "name": "climb_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ascensionist_count": {
+ "name": "ascensionist_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "display_difficulty": {
+ "name": "display_difficulty",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "quality_average": {
+ "name": "quality_average",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "climb_cache_fields_climb_uuid_fkey": {
+ "name": "climb_cache_fields_climb_uuid_fkey",
+ "tableFrom": "tension_climb_cache_fields",
+ "tableTo": "tension_climbs",
+ "columnsFrom": [
+ "climb_uuid"
+ ],
+ "columnsTo": [
+ "uuid"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_climb_random_positions": {
+ "name": "tension_climb_random_positions",
+ "schema": "",
+ "columns": {
+ "climb_uuid": {
+ "name": "climb_uuid",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_climb_stats": {
+ "name": "tension_climb_stats",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "climb_uuid": {
+ "name": "climb_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "angle": {
+ "name": "angle",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_difficulty": {
+ "name": "display_difficulty",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "benchmark_difficulty": {
+ "name": "benchmark_difficulty",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ascensionist_count": {
+ "name": "ascensionist_count",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "difficulty_average": {
+ "name": "difficulty_average",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "quality_average": {
+ "name": "quality_average",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "fa_username": {
+ "name": "fa_username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "fa_at": {
+ "name": "fa_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "tension_climb_angle_idx": {
+ "name": "tension_climb_angle_idx",
+ "columns": [
+ {
+ "expression": "climb_uuid",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "angle",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_climb_stats_history": {
+ "name": "tension_climb_stats_history",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "bigserial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "climb_uuid": {
+ "name": "climb_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "angle": {
+ "name": "angle",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "display_difficulty": {
+ "name": "display_difficulty",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "benchmark_difficulty": {
+ "name": "benchmark_difficulty",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ascensionist_count": {
+ "name": "ascensionist_count",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "difficulty_average": {
+ "name": "difficulty_average",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "quality_average": {
+ "name": "quality_average",
+ "type": "double precision",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "fa_username": {
+ "name": "fa_username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "fa_at": {
+ "name": "fa_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_climbs": {
+ "name": "tension_climbs",
+ "schema": "",
+ "columns": {
+ "uuid": {
+ "name": "uuid",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "layout_id": {
+ "name": "layout_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "setter_id": {
+ "name": "setter_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "setter_username": {
+ "name": "setter_username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "''"
+ },
+ "hsm": {
+ "name": "hsm",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "edge_left": {
+ "name": "edge_left",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "edge_right": {
+ "name": "edge_right",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "edge_bottom": {
+ "name": "edge_bottom",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "edge_top": {
+ "name": "edge_top",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "angle": {
+ "name": "angle",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "frames_count": {
+ "name": "frames_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 1
+ },
+ "frames_pace": {
+ "name": "frames_pace",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ },
+ "frames": {
+ "name": "frames",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_draft": {
+ "name": "is_draft",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "climbs_layout_id_fkey": {
+ "name": "climbs_layout_id_fkey",
+ "tableFrom": "tension_climbs",
+ "tableTo": "tension_layouts",
+ "columnsFrom": [
+ "layout_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_difficulty_grades": {
+ "name": "tension_difficulty_grades",
+ "schema": "",
+ "columns": {
+ "difficulty": {
+ "name": "difficulty",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "boulder_name": {
+ "name": "boulder_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "route_name": {
+ "name": "route_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_holes": {
+ "name": "tension_holes",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "product_id": {
+ "name": "product_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "x": {
+ "name": "x",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "y": {
+ "name": "y",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mirrored_hole_id": {
+ "name": "mirrored_hole_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "mirror_group": {
+ "name": "mirror_group",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 0
+ }
+ },
+ "indexes": {
+ "idx_16405_sqlite_autoindex_holes_2": {
+ "name": "idx_16405_sqlite_autoindex_holes_2",
+ "columns": [
+ {
+ "expression": "product_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "text_ops"
+ },
+ {
+ "expression": "name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "text_ops"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_16405_sqlite_autoindex_holes_3": {
+ "name": "idx_16405_sqlite_autoindex_holes_3",
+ "columns": [
+ {
+ "expression": "product_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ },
+ {
+ "expression": "x",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ },
+ {
+ "expression": "y",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "holes_mirrored_hole_id_fkey": {
+ "name": "holes_mirrored_hole_id_fkey",
+ "tableFrom": "tension_holes",
+ "tableTo": "tension_holes",
+ "columnsFrom": [
+ "mirrored_hole_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "holes_product_id_fkey": {
+ "name": "holes_product_id_fkey",
+ "tableFrom": "tension_holes",
+ "tableTo": "tension_products",
+ "columnsFrom": [
+ "product_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_kits": {
+ "name": "tension_kits",
+ "schema": "",
+ "columns": {
+ "serial_number": {
+ "name": "serial_number",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_autoconnect": {
+ "name": "is_autoconnect",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_layouts": {
+ "name": "tension_layouts",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "product_id": {
+ "name": "product_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "instagram_caption": {
+ "name": "instagram_caption",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_mirrored": {
+ "name": "is_mirrored",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "layouts_product_id_fkey": {
+ "name": "layouts_product_id_fkey",
+ "tableFrom": "tension_layouts",
+ "tableTo": "tension_products",
+ "columnsFrom": [
+ "product_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_leds": {
+ "name": "tension_leds",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "product_size_id": {
+ "name": "product_size_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hole_id": {
+ "name": "hole_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_16411_sqlite_autoindex_leds_2": {
+ "name": "idx_16411_sqlite_autoindex_leds_2",
+ "columns": [
+ {
+ "expression": "product_size_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ },
+ {
+ "expression": "position",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "leds_hole_id_fkey": {
+ "name": "leds_hole_id_fkey",
+ "tableFrom": "tension_leds",
+ "tableTo": "tension_holes",
+ "columnsFrom": [
+ "hole_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "leds_product_size_id_fkey": {
+ "name": "leds_product_size_id_fkey",
+ "tableFrom": "tension_leds",
+ "tableTo": "tension_product_sizes",
+ "columnsFrom": [
+ "product_size_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_placement_roles": {
+ "name": "tension_placement_roles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "product_id": {
+ "name": "product_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "full_name": {
+ "name": "full_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "led_color": {
+ "name": "led_color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "screen_color": {
+ "name": "screen_color",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "placement_roles_product_id_fkey": {
+ "name": "placement_roles_product_id_fkey",
+ "tableFrom": "tension_placement_roles",
+ "tableTo": "tension_products",
+ "columnsFrom": [
+ "product_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_placements": {
+ "name": "tension_placements",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "layout_id": {
+ "name": "layout_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hole_id": {
+ "name": "hole_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "set_id": {
+ "name": "set_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "default_placement_role_id": {
+ "name": "default_placement_role_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_16518_sqlite_autoindex_placements_2": {
+ "name": "idx_16518_sqlite_autoindex_placements_2",
+ "columns": [
+ {
+ "expression": "layout_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ },
+ {
+ "expression": "hole_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "placements_default_placement_role_id_fkey": {
+ "name": "placements_default_placement_role_id_fkey",
+ "tableFrom": "tension_placements",
+ "tableTo": "tension_placement_roles",
+ "columnsFrom": [
+ "default_placement_role_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "placements_hole_id_fkey": {
+ "name": "placements_hole_id_fkey",
+ "tableFrom": "tension_placements",
+ "tableTo": "tension_holes",
+ "columnsFrom": [
+ "hole_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "placements_layout_id_fkey": {
+ "name": "placements_layout_id_fkey",
+ "tableFrom": "tension_placements",
+ "tableTo": "tension_layouts",
+ "columnsFrom": [
+ "layout_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "placements_set_id_fkey": {
+ "name": "placements_set_id_fkey",
+ "tableFrom": "tension_placements",
+ "tableTo": "tension_sets",
+ "columnsFrom": [
+ "set_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_product_sizes": {
+ "name": "tension_product_sizes",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "product_id": {
+ "name": "product_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "edge_left": {
+ "name": "edge_left",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "edge_right": {
+ "name": "edge_right",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "edge_bottom": {
+ "name": "edge_bottom",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "edge_top": {
+ "name": "edge_top",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_filename": {
+ "name": "image_filename",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "product_sizes_product_id_fkey": {
+ "name": "product_sizes_product_id_fkey",
+ "tableFrom": "tension_product_sizes",
+ "tableTo": "tension_products",
+ "columnsFrom": [
+ "product_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_product_sizes_layouts_sets": {
+ "name": "tension_product_sizes_layouts_sets",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "product_size_id": {
+ "name": "product_size_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "layout_id": {
+ "name": "layout_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "set_id": {
+ "name": "set_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "image_filename": {
+ "name": "image_filename",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_16427_sqlite_autoindex_product_sizes_layouts_sets_2": {
+ "name": "idx_16427_sqlite_autoindex_product_sizes_layouts_sets_2",
+ "columns": [
+ {
+ "expression": "product_size_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ },
+ {
+ "expression": "layout_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ },
+ {
+ "expression": "set_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last",
+ "opclass": "int4_ops"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "product_sizes_layouts_sets_layout_id_fkey": {
+ "name": "product_sizes_layouts_sets_layout_id_fkey",
+ "tableFrom": "tension_product_sizes_layouts_sets",
+ "tableTo": "tension_layouts",
+ "columnsFrom": [
+ "layout_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "product_sizes_layouts_sets_product_size_id_fkey": {
+ "name": "product_sizes_layouts_sets_product_size_id_fkey",
+ "tableFrom": "tension_product_sizes_layouts_sets",
+ "tableTo": "tension_product_sizes",
+ "columnsFrom": [
+ "product_size_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ },
+ "product_sizes_layouts_sets_set_id_fkey": {
+ "name": "product_sizes_layouts_sets_set_id_fkey",
+ "tableFrom": "tension_product_sizes_layouts_sets",
+ "tableTo": "tension_sets",
+ "columnsFrom": [
+ "set_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_products": {
+ "name": "tension_products",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "min_count_in_frame": {
+ "name": "min_count_in_frame",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "max_count_in_frame": {
+ "name": "max_count_in_frame",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_products_angles": {
+ "name": "tension_products_angles",
+ "schema": "",
+ "columns": {
+ "product_id": {
+ "name": "product_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "angle": {
+ "name": "angle",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "products_angles_product_id_fkey": {
+ "name": "products_angles_product_id_fkey",
+ "tableFrom": "tension_products_angles",
+ "tableTo": "tension_products",
+ "columnsFrom": [
+ "product_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {
+ "idx_16414_sqlite_autoindex_products_angles_1": {
+ "name": "idx_16414_sqlite_autoindex_products_angles_1",
+ "columns": [
+ "product_id",
+ "angle"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_sets": {
+ "name": "tension_sets",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hsm": {
+ "name": "hsm",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_shared_syncs": {
+ "name": "tension_shared_syncs",
+ "schema": "",
+ "columns": {
+ "table_name": {
+ "name": "table_name",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "last_synchronized_at": {
+ "name": "last_synchronized_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_tags": {
+ "name": "tension_tags",
+ "schema": "",
+ "columns": {
+ "entity_uuid": {
+ "name": "entity_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_listed": {
+ "name": "is_listed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "idx_16472_sqlite_autoindex_tags_1": {
+ "name": "idx_16472_sqlite_autoindex_tags_1",
+ "columns": [
+ "entity_uuid",
+ "user_id",
+ "name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_user_permissions": {
+ "name": "tension_user_permissions",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "idx_16447_sqlite_autoindex_user_permissions_1": {
+ "name": "idx_16447_sqlite_autoindex_user_permissions_1",
+ "columns": [
+ "user_id",
+ "name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_user_syncs": {
+ "name": "tension_user_syncs",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "table_name": {
+ "name": "table_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_synchronized_at": {
+ "name": "last_synchronized_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "user_syncs_user_id_fkey": {
+ "name": "user_syncs_user_id_fkey",
+ "tableFrom": "tension_user_syncs",
+ "tableTo": "tension_users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {
+ "idx_16452_sqlite_autoindex_user_syncs_1": {
+ "name": "idx_16452_sqlite_autoindex_user_syncs_1",
+ "columns": [
+ "user_id",
+ "table_name"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_users": {
+ "name": "tension_users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "username": {
+ "name": "username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_walls": {
+ "name": "tension_walls",
+ "schema": "",
+ "columns": {
+ "uuid": {
+ "name": "uuid",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "product_id": {
+ "name": "product_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_adjustable": {
+ "name": "is_adjustable",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "angle": {
+ "name": "angle",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "layout_id": {
+ "name": "layout_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "product_size_id": {
+ "name": "product_size_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "hsm": {
+ "name": "hsm",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "serial_number": {
+ "name": "serial_number",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "walls_layout_id_fkey": {
+ "name": "walls_layout_id_fkey",
+ "tableFrom": "tension_walls",
+ "tableTo": "tension_layouts",
+ "columnsFrom": [
+ "layout_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "walls_product_id_fkey": {
+ "name": "walls_product_id_fkey",
+ "tableFrom": "tension_walls",
+ "tableTo": "tension_products",
+ "columnsFrom": [
+ "product_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "walls_product_size_id_fkey": {
+ "name": "walls_product_size_id_fkey",
+ "tableFrom": "tension_walls",
+ "tableTo": "tension_product_sizes",
+ "columnsFrom": [
+ "product_size_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "walls_user_id_fkey": {
+ "name": "walls_user_id_fkey",
+ "tableFrom": "tension_walls",
+ "tableTo": "tension_users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tension_walls_sets": {
+ "name": "tension_walls_sets",
+ "schema": "",
+ "columns": {
+ "wall_uuid": {
+ "name": "wall_uuid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "set_id": {
+ "name": "set_id",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "walls_sets_set_id_fkey": {
+ "name": "walls_sets_set_id_fkey",
+ "tableFrom": "tension_walls_sets",
+ "tableTo": "tension_sets",
+ "columnsFrom": [
+ "set_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "cascade"
+ },
+ "walls_sets_wall_uuid_fkey": {
+ "name": "walls_sets_wall_uuid_fkey",
+ "tableFrom": "tension_walls_sets",
+ "tableTo": "tension_walls",
+ "columnsFrom": [
+ "wall_uuid"
+ ],
+ "columnsTo": [
+ "uuid"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {
+ "idx_16457_sqlite_autoindex_walls_sets_1": {
+ "name": "idx_16457_sqlite_autoindex_walls_sets_1",
+ "columns": [
+ "wall_uuid",
+ "set_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index 2c91883..3ab343a 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -8,6 +8,13 @@
"when": 1734255307302,
"tag": "0000_cloudy_carlie_cooper",
"breakpoints": true
+ },
+ {
+ "idx": 1,
+ "version": "7",
+ "when": 1735369313427,
+ "tag": "0001_unique_climbstats",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/drizzle/schema.ts b/drizzle/schema.ts
index f4df148..bc1211c 100644
--- a/drizzle/schema.ts
+++ b/drizzle/schema.ts
@@ -1,8 +1,6 @@
-import { pgTable, foreignKey, bigserial, text, integer, doublePrecision, boolean, bigint, timestamp, uniqueIndex, primaryKey } from "drizzle-orm/pg-core"
+import { pgTable, foreignKey, bigserial, text, integer, doublePrecision, boolean, bigint, timestamp, uniqueIndex, primaryKey } from "drizzle-orm/pg-core";
import { sql } from "drizzle-orm"
-
-
export const kilterClimbCacheFields = pgTable("kilter_climb_cache_fields", {
id: bigserial({ mode: "bigint" }).primaryKey().notNull(),
climbUuid: text("climb_uuid"),
@@ -60,19 +58,36 @@ export const kilterCircuits = pgTable("kilter_circuits", {
updatedAt: text("updated_at"),
});
+// Update existing tables with unique constraints
export const tensionClimbStats = pgTable("tension_climb_stats", {
- id: bigserial({ mode: "bigint" }).primaryKey().notNull(),
- climbUuid: text("climb_uuid"),
- // You can use { mode: "bigint" } if numbers are exceeding js number limitations
- angle: bigint({ mode: "number" }),
- displayDifficulty: doublePrecision("display_difficulty"),
- benchmarkDifficulty: doublePrecision("benchmark_difficulty"),
- // You can use { mode: "bigint" } if numbers are exceeding js number limitations
- ascensionistCount: bigint("ascensionist_count", { mode: "number" }),
- difficultyAverage: doublePrecision("difficulty_average"),
- qualityAverage: doublePrecision("quality_average"),
- faUsername: text("fa_username"),
- faAt: timestamp("fa_at", { mode: 'string' }),
+ id: bigserial({ mode: "bigint" }).primaryKey().notNull(),
+ climbUuid: text("climb_uuid").notNull(),
+ angle: bigint({ mode: "number" }).notNull(),
+ displayDifficulty: doublePrecision("display_difficulty"),
+ benchmarkDifficulty: doublePrecision("benchmark_difficulty"),
+ ascensionistCount: bigint("ascensionist_count", { mode: "number" }),
+ difficultyAverage: doublePrecision("difficulty_average"),
+ qualityAverage: doublePrecision("quality_average"),
+ faUsername: text("fa_username"),
+ faAt: timestamp("fa_at", { mode: 'string' }),
+}, (table) => ({
+ climbAngleIdx: uniqueIndex("tension_climb_angle_idx").on(table.climbUuid, table.angle),
+}));
+
+
+// Add new history tables
+export const tensionClimbStatsHistory = pgTable("tension_climb_stats_history", {
+ id: bigserial({ mode: "bigint" }).primaryKey().notNull(),
+ climbUuid: text("climb_uuid").notNull(),
+ angle: bigint({ mode: "number" }).notNull(),
+ displayDifficulty: doublePrecision("display_difficulty"),
+ benchmarkDifficulty: doublePrecision("benchmark_difficulty"),
+ ascensionistCount: bigint("ascensionist_count", { mode: "number" }),
+ difficultyAverage: doublePrecision("difficulty_average"),
+ qualityAverage: doublePrecision("quality_average"),
+ faUsername: text("fa_username"),
+ faAt: timestamp("fa_at", { mode: 'string' }),
+ createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
});
export const tensionClimbCacheFields = pgTable("tension_climb_cache_fields", {
@@ -109,18 +124,32 @@ export const kilterLeds = pgTable("kilter_leds", {
]);
export const kilterClimbStats = pgTable("kilter_climb_stats", {
- id: bigserial({ mode: "bigint" }).primaryKey().notNull(),
- climbUuid: text("climb_uuid"),
- // You can use { mode: "bigint" } if numbers are exceeding js number limitations
- angle: bigint({ mode: "number" }),
- displayDifficulty: doublePrecision("display_difficulty"),
- benchmarkDifficulty: doublePrecision("benchmark_difficulty"),
- // You can use { mode: "bigint" } if numbers are exceeding js number limitations
- ascensionistCount: bigint("ascensionist_count", { mode: "number" }),
- difficultyAverage: doublePrecision("difficulty_average"),
- qualityAverage: doublePrecision("quality_average"),
- faUsername: text("fa_username"),
- faAt: timestamp("fa_at", { mode: 'string' }),
+ id: bigserial({ mode: "bigint" }).primaryKey().notNull(),
+ climbUuid: text("climb_uuid").notNull(),
+ angle: bigint({ mode: "number" }).notNull(),
+ displayDifficulty: doublePrecision("display_difficulty"),
+ benchmarkDifficulty: doublePrecision("benchmark_difficulty"),
+ ascensionistCount: bigint("ascensionist_count", { mode: "number" }),
+ difficultyAverage: doublePrecision("difficulty_average"),
+ qualityAverage: doublePrecision("quality_average"),
+ faUsername: text("fa_username"),
+ faAt: timestamp("fa_at", { mode: 'string' }),
+}, (table) => ({
+ climbAngleIdx: uniqueIndex("kilter_climb_angle_idx").on(table.climbUuid, table.angle),
+}));
+
+export const kilterClimbStatsHistory = pgTable("kilter_climb_stats_history", {
+ id: bigserial({ mode: "bigint" }).primaryKey().notNull(),
+ climbUuid: text("climb_uuid").notNull(),
+ angle: bigint({ mode: "number" }).notNull(),
+ displayDifficulty: doublePrecision("display_difficulty"),
+ benchmarkDifficulty: doublePrecision("benchmark_difficulty"),
+ ascensionistCount: bigint("ascensionist_count", { mode: "number" }),
+ difficultyAverage: doublePrecision("difficulty_average"),
+ qualityAverage: doublePrecision("quality_average"),
+ faUsername: text("fa_username"),
+ faAt: timestamp("fa_at", { mode: 'string' }),
+ createdAt: timestamp("created_at", { mode: 'string' }).defaultNow().notNull(),
});
export const kilterProductSizes = pgTable("kilter_product_sizes", {
diff --git a/package.json b/package.json
index bcc4e36..a23b872 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"dev": "next dev",
- "build": "next build",
+ "build": "drizzle-kit push:pg && next build",
"start": "next start",
"lint": "next lint",
"lint-fix": "eslint --fix .",
From ee5b6fc7a6bcffc03ca03c077bc6e291113f0744 Mon Sep 17 00:00:00 2001
From: Marco de Jongh <1107647+marcodejongh@users.noreply.github.com>
Date: Sun, 29 Dec 2024 08:56:52 +1100
Subject: [PATCH 4/4] Prettier
---
app/api/internal/shared-sync/route.ts | 5 +-
app/components/climb-card/climb-card.tsx | 2 +-
app/components/logbook/logascent-form.tsx | 2 +-
app/components/logbook/logbook-stats.tsx | 132 +-
.../setup-wizard/sets-selection.tsx | 5 +-
.../setup-wizard/size-selection.tsx | 14 +-
app/lib/api-wrappers/aurora/types.ts | 1 -
.../api-wrappers/aurora/user/follows-save.ts | 2 +-
.../api-wrappers/aurora/user/getLogbook.ts | 2 -
app/lib/api-wrappers/aurora/user/getUser.ts | 6 +-
app/lib/api-wrappers/aurora/util.ts | 5 +-
app/lib/data-sync/aurora/getTableName.ts | 2 +-
app/lib/data-sync/aurora/shared-sync.ts | 4 +-
app/lib/data-sync/aurora/user-sync.ts | 42 +-
app/lib/data/get-logbook.ts | 6 +-
db/README.md | 1 -
db/docker-compose.yml | 12 +-
drizzle/0000_cloudy_carlie_cooper.sql | 1238 ++++++++---------
drizzle/0001_unique_climbstats.sql | 27 +-
drizzle/meta/0000_snapshot.json | 562 ++------
...{0001_snapshot.json => 0003_snapshot.json} | 634 +++------
drizzle/meta/_journal.json | 14 +
drizzle/relations.ts | 436 ------
drizzle/schema.ts | 934 -------------
package.json | 4 +-
tsconfig.json | 29 +-
26 files changed, 1088 insertions(+), 3033 deletions(-)
rename drizzle/meta/{0001_snapshot.json => 0003_snapshot.json} (90%)
delete mode 100644 drizzle/relations.ts
delete mode 100644 drizzle/schema.ts
diff --git a/app/api/internal/shared-sync/route.ts b/app/api/internal/shared-sync/route.ts
index fcf0c48..a63d2ae 100644
--- a/app/api/internal/shared-sync/route.ts
+++ b/app/api/internal/shared-sync/route.ts
@@ -16,10 +16,7 @@ export async function GET(request: Request) {
}
// Sync both board types
- const results = await Promise.all([
- syncSharedData('tension'),
- syncSharedData('kilter')
- ]);
+ const results = await Promise.all([syncSharedData('tension'), syncSharedData('kilter')]);
return NextResponse.json({
success: true,
diff --git a/app/components/climb-card/climb-card.tsx b/app/components/climb-card/climb-card.tsx
index 6abd054..ba00c9b 100644
--- a/app/components/climb-card/climb-card.tsx
+++ b/app/components/climb-card/climb-card.tsx
@@ -17,7 +17,7 @@ type ClimbCardProps = {
const ClimbCard = ({ climb, boardDetails, onCoverClick, selected, actions }: ClimbCardProps) => {
const cover =