diff --git a/apps/server/migrations/0140_outgoing_bruce_banner.sql b/apps/server/migrations/0140_outgoing_bruce_banner.sql new file mode 100644 index 00000000..30b9bb40 --- /dev/null +++ b/apps/server/migrations/0140_outgoing_bruce_banner.sql @@ -0,0 +1,3 @@ +ALTER TYPE "badge_award_object_type" ADD VALUE 'country'; +ALTER TYPE "badge_award_object_type" ADD VALUE 'region'; +ALTER TABLE "badges" ADD COLUMN "tracker" "badge_award_object_type" DEFAULT 'bottle' NOT NULL; \ No newline at end of file diff --git a/apps/server/migrations/meta/0140_snapshot.json b/apps/server/migrations/meta/0140_snapshot.json new file mode 100644 index 00000000..ff052ff8 --- /dev/null +++ b/apps/server/migrations/meta/0140_snapshot.json @@ -0,0 +1,3631 @@ +{ + "id": "1d48d9c8-2d1c-480f-805f-3fedf18ded59", + "prevId": "1b76f676-e7e1-4a8b-b36b-7cbcf8087538", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.badge_award_tracked_object": { + "name": "badge_award_tracked_object", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "award_id": { + "name": "award_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "object_type": { + "name": "object_type", + "type": "badge_award_object_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "object_id": { + "name": "object_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "badge_award_tracked_object_unq": { + "name": "badge_award_tracked_object_unq", + "columns": [ + { + "expression": "award_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "badge_award_tracked_object_award_id_badge_award_id_fk": { + "name": "badge_award_tracked_object_award_id_badge_award_id_fk", + "tableFrom": "badge_award_tracked_object", + "tableTo": "badge_award", + "columnsFrom": [ + "award_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.badge_award": { + "name": "badge_award", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "badge_id": { + "name": "badge_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "xp": { + "name": "xp", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "level": { + "name": "level", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "badge_award_unq": { + "name": "badge_award_unq", + "columns": [ + { + "expression": "badge_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "badge_award_badge_id_badges_id_fk": { + "name": "badge_award_badge_id_badges_id_fk", + "tableFrom": "badge_award", + "tableTo": "badges", + "columnsFrom": [ + "badge_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "badge_award_user_id_user_id_fk": { + "name": "badge_award_user_id_user_id_fk", + "tableFrom": "badge_award", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.badges": { + "name": "badges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "max_level": { + "name": "max_level", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 50 + }, + "tracker": { + "name": "tracker", + "type": "badge_award_object_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'bottle'" + }, + "checks": { + "name": "checks", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": { + "badge_name_unq": { + "name": "badge_name_unq", + "columns": [ + { + "expression": "LOWER(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.bottle_alias": { + "name": "bottle_alias", + "schema": "", + "columns": { + "bottle_id": { + "name": "bottle_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(3072)", + "primaryKey": false, + "notNull": false + }, + "ignored": { + "name": "ignored", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "bottle_alias_name_idx": { + "name": "bottle_alias_name_idx", + "columns": [ + { + "expression": "LOWER(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bottle_alias_bottle_idx": { + "name": "bottle_alias_bottle_idx", + "columns": [ + { + "expression": "bottle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bottle_alias_bottle_id_bottle_id_fk": { + "name": "bottle_alias_bottle_id_bottle_id_fk", + "tableFrom": "bottle_alias", + "tableTo": "bottle", + "columnsFrom": [ + "bottle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.bottle_flavor_profile": { + "name": "bottle_flavor_profile", + "schema": "", + "columns": { + "bottle_id": { + "name": "bottle_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "flavor_profile": { + "name": "flavor_profile", + "type": "flavor_profile", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "bottle_flavor_profile_bottle_id_bottle_id_fk": { + "name": "bottle_flavor_profile_bottle_id_bottle_id_fk", + "tableFrom": "bottle_flavor_profile", + "tableTo": "bottle", + "columnsFrom": [ + "bottle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "bottle_flavor_profile_bottle_id_flavor_profile_pk": { + "name": "bottle_flavor_profile_bottle_id_flavor_profile_pk", + "columns": [ + "bottle_id", + "flavor_profile" + ] + } + }, + "uniqueConstraints": {} + }, + "public.bottle_tag": { + "name": "bottle_tag", + "schema": "", + "columns": { + "bottle_id": { + "name": "bottle_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "tag": { + "name": "tag", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "count": { + "name": "count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "bottle_tag_bottle_id_bottle_id_fk": { + "name": "bottle_tag_bottle_id_bottle_id_fk", + "tableFrom": "bottle_tag", + "tableTo": "bottle", + "columnsFrom": [ + "bottle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "bottle_tag_bottle_id_tag_pk": { + "name": "bottle_tag_bottle_id_tag_pk", + "columns": [ + "bottle_id", + "tag" + ] + } + }, + "uniqueConstraints": {} + }, + "public.bottle_tombstone": { + "name": "bottle_tombstone", + "schema": "", + "columns": { + "bottle_id": { + "name": "bottle_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "new_bottle_id": { + "name": "new_bottle_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.bottle": { + "name": "bottle", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "full_name": { + "name": "full_name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "uniq_hash": { + "name": "uniq_hash", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "category", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "brand_id": { + "name": "brand_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "bottler_id": { + "name": "bottler_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "stated_age": { + "name": "stated_age", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "flavor_profile": { + "name": "flavor_profile", + "type": "flavor_profile", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "vintage_year": { + "name": "vintage_year", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "cask_size": { + "name": "cask_size", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "cask_type": { + "name": "cask_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "cask_fill": { + "name": "cask_fill", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "release_year": { + "name": "release_year", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description_src": { + "name": "description_src", + "type": "content_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "tasting_notes": { + "name": "tasting_notes", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "suggested_tags": { + "name": "suggested_tags", + "type": "varchar(64)[]", + "primaryKey": false, + "notNull": true, + "default": "array[]::varchar[]" + }, + "avg_rating": { + "name": "avg_rating", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "total_tastings": { + "name": "total_tastings", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by_id": { + "name": "created_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "bottle_search_idx": { + "name": "bottle_search_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "bottle_brand_idx": { + "name": "bottle_brand_idx", + "columns": [ + { + "expression": "brand_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bottle_bottler_idx": { + "name": "bottle_bottler_idx", + "columns": [ + { + "expression": "bottler_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bottle_created_by_idx": { + "name": "bottle_created_by_idx", + "columns": [ + { + "expression": "created_by_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bottle_category_idx": { + "name": "bottle_category_idx", + "columns": [ + { + "expression": "category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "bottle_flavor_profile_idx": { + "name": "bottle_flavor_profile_idx", + "columns": [ + { + "expression": "flavor_profile", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bottle_brand_id_entity_id_fk": { + "name": "bottle_brand_id_entity_id_fk", + "tableFrom": "bottle", + "tableTo": "entity", + "columnsFrom": [ + "brand_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "bottle_bottler_id_entity_id_fk": { + "name": "bottle_bottler_id_entity_id_fk", + "tableFrom": "bottle", + "tableTo": "entity", + "columnsFrom": [ + "bottler_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "bottle_created_by_id_user_id_fk": { + "name": "bottle_created_by_id_user_id_fk", + "tableFrom": "bottle", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.bottle_distiller": { + "name": "bottle_distiller", + "schema": "", + "columns": { + "bottle_id": { + "name": "bottle_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "distiller_id": { + "name": "distiller_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "bottle_distiller_bottle_id_bottle_id_fk": { + "name": "bottle_distiller_bottle_id_bottle_id_fk", + "tableFrom": "bottle_distiller", + "tableTo": "bottle", + "columnsFrom": [ + "bottle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "bottle_distiller_distiller_id_entity_id_fk": { + "name": "bottle_distiller_distiller_id_entity_id_fk", + "tableFrom": "bottle_distiller", + "tableTo": "entity", + "columnsFrom": [ + "distiller_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "bottle_distiller_bottle_id_distiller_id_pk": { + "name": "bottle_distiller_bottle_id_distiller_id_pk", + "columns": [ + "bottle_id", + "distiller_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.change": { + "name": "change", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "object_id": { + "name": "object_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "object_type": { + "name": "object_type", + "type": "object_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'add'" + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by_id": { + "name": "created_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "change_created_by_idx": { + "name": "change_created_by_idx", + "columns": [ + { + "expression": "created_by_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "change_created_by_id_user_id_fk": { + "name": "change_created_by_id_user_id_fk", + "tableFrom": "change", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.collection_bottle": { + "name": "collection_bottle", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "collection_id": { + "name": "collection_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "bottle_id": { + "name": "bottle_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "collection_bottle_unq": { + "name": "collection_bottle_unq", + "columns": [ + { + "expression": "collection_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "bottle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "collection_bottle_bottle_idx": { + "name": "collection_bottle_bottle_idx", + "columns": [ + { + "expression": "bottle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "collection_bottle_collection_id_collection_id_fk": { + "name": "collection_bottle_collection_id_collection_id_fk", + "tableFrom": "collection_bottle", + "tableTo": "collection", + "columnsFrom": [ + "collection_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "collection_bottle_bottle_id_bottle_id_fk": { + "name": "collection_bottle_bottle_id_bottle_id_fk", + "tableFrom": "collection_bottle", + "tableTo": "bottle", + "columnsFrom": [ + "bottle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.collection": { + "name": "collection", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "total_bottles": { + "name": "total_bottles", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by_id": { + "name": "created_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "collection_name_unq": { + "name": "collection_name_unq", + "columns": [ + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "collection_created_by_idx": { + "name": "collection_created_by_idx", + "columns": [ + { + "expression": "created_by_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "collection_created_by_id_user_id_fk": { + "name": "collection_created_by_id_user_id_fk", + "tableFrom": "collection", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.comments": { + "name": "comments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "tasting_id": { + "name": "tasting_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "comment": { + "name": "comment", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by_id": { + "name": "created_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "comment_unq": { + "name": "comment_unq", + "columns": [ + { + "expression": "tasting_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "comments_tasting_id_tasting_id_fk": { + "name": "comments_tasting_id_tasting_id_fk", + "tableFrom": "comments", + "tableTo": "tasting", + "columnsFrom": [ + "tasting_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "comments_created_by_id_user_id_fk": { + "name": "comments_created_by_id_user_id_fk", + "tableFrom": "comments", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.country": { + "name": "country", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "alpha2": { + "name": "alpha2", + "type": "char(2)", + "primaryKey": false, + "notNull": false + }, + "location": { + "name": "location", + "type": "geometry(Point, 4326)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description_src": { + "name": "description_src", + "type": "content_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_bottles": { + "name": "total_bottles", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_distillers": { + "name": "total_distillers", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "country_name_unq": { + "name": "country_name_unq", + "columns": [ + { + "expression": "LOWER(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "country_slug_unq": { + "name": "country_slug_unq", + "columns": [ + { + "expression": "LOWER(\"slug\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.entity": { + "name": "entity", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "short_name": { + "name": "short_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "search_vector": { + "name": "search_vector", + "type": "tsvector", + "primaryKey": false, + "notNull": false + }, + "country": { + "name": "country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country_id": { + "name": "country_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "region_id": { + "name": "region_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "location": { + "name": "location", + "type": "geometry(Point, 4326)", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "entity_type[]", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description_src": { + "name": "description_src", + "type": "content_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "year_established": { + "name": "year_established", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "website": { + "name": "website", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "total_bottles": { + "name": "total_bottles", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_tastings": { + "name": "total_tastings", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by_id": { + "name": "created_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "entity_name_unq": { + "name": "entity_name_unq", + "columns": [ + { + "expression": "LOWER(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "entity_search_idx": { + "name": "entity_search_idx", + "columns": [ + { + "expression": "search_vector", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "entity_country_by_idx": { + "name": "entity_country_by_idx", + "columns": [ + { + "expression": "country_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "entity_region_idx": { + "name": "entity_region_idx", + "columns": [ + { + "expression": "region_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "entity_created_by_idx": { + "name": "entity_created_by_idx", + "columns": [ + { + "expression": "created_by_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entity_country_id_country_id_fk": { + "name": "entity_country_id_country_id_fk", + "tableFrom": "entity", + "tableTo": "country", + "columnsFrom": [ + "country_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "entity_region_id_region_id_fk": { + "name": "entity_region_id_region_id_fk", + "tableFrom": "entity", + "tableTo": "region", + "columnsFrom": [ + "region_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "entity_created_by_id_user_id_fk": { + "name": "entity_created_by_id_user_id_fk", + "tableFrom": "entity", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.entity_alias": { + "name": "entity_alias", + "schema": "", + "columns": { + "entity_id": { + "name": "entity_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "entity_alias_entity_idx": { + "name": "entity_alias_entity_idx", + "columns": [ + { + "expression": "entity_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "entity_alias_name_idx": { + "name": "entity_alias_name_idx", + "columns": [ + { + "expression": "LOWER(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "entity_alias_entity_id_entity_id_fk": { + "name": "entity_alias_entity_id_entity_id_fk", + "tableFrom": "entity_alias", + "tableTo": "entity", + "columnsFrom": [ + "entity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.entity_tombstone": { + "name": "entity_tombstone", + "schema": "", + "columns": { + "entity_id": { + "name": "entity_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "new_entity_id": { + "name": "new_entity_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.event": { + "name": "event", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "date_start": { + "name": "date_start", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "date_end": { + "name": "date_end", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "website": { + "name": "website", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "country_id": { + "name": "country_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "location": { + "name": "location", + "type": "geometry(Point, 4326)", + "primaryKey": false, + "notNull": false + }, + "repeats": { + "name": "repeats", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "event_name_unq": { + "name": "event_name_unq", + "columns": [ + { + "expression": "date_start", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "LOWER(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "event_country_id": { + "name": "event_country_id", + "columns": [ + { + "expression": "country_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "event_country_id_country_id_fk": { + "name": "event_country_id_country_id_fk", + "tableFrom": "event", + "tableTo": "country", + "columnsFrom": [ + "country_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.external_site_config": { + "name": "external_site_config", + "schema": "", + "columns": { + "external_site_id": { + "name": "external_site_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "external_site_config_external_site_id_external_site_id_fk": { + "name": "external_site_config_external_site_id_external_site_id_fk", + "tableFrom": "external_site_config", + "tableTo": "external_site", + "columnsFrom": [ + "external_site_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "external_site_config_external_site_id_key_pk": { + "name": "external_site_config_external_site_id_key_pk", + "columns": [ + "external_site_id", + "key" + ] + } + }, + "uniqueConstraints": {} + }, + "public.external_site": { + "name": "external_site", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "type": { + "name": "type", + "type": "external_site_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_run_at": { + "name": "last_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "next_run_at": { + "name": "next_run_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "run_every": { + "name": "run_every", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 60 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "external_site_type": { + "name": "external_site_type", + "columns": [ + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.flight_bottle": { + "name": "flight_bottle", + "schema": "", + "columns": { + "flight_id": { + "name": "flight_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "bottle_id": { + "name": "bottle_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "flight_bottle_flight_id_flight_id_fk": { + "name": "flight_bottle_flight_id_flight_id_fk", + "tableFrom": "flight_bottle", + "tableTo": "flight", + "columnsFrom": [ + "flight_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "flight_bottle_bottle_id_bottle_id_fk": { + "name": "flight_bottle_bottle_id_bottle_id_fk", + "tableFrom": "flight_bottle", + "tableTo": "bottle", + "columnsFrom": [ + "bottle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "flight_bottle_flight_id_bottle_id_pk": { + "name": "flight_bottle_flight_id_bottle_id_pk", + "columns": [ + "flight_id", + "bottle_id" + ] + } + }, + "uniqueConstraints": {} + }, + "public.flight": { + "name": "flight", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "public_id": { + "name": "public_id", + "type": "varchar(12)", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public": { + "name": "public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by_id": { + "name": "created_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "flight_public_id": { + "name": "flight_public_id", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "flight_created_by_id_user_id_fk": { + "name": "flight_created_by_id_user_id_fk", + "tableFrom": "flight", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.follow": { + "name": "follow", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "from_user_id": { + "name": "from_user_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "to_user_id": { + "name": "to_user_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "follow_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "follow_unq": { + "name": "follow_unq", + "columns": [ + { + "expression": "from_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "to_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "follow_to_user_idx": { + "name": "follow_to_user_idx", + "columns": [ + { + "expression": "to_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "follow_from_user_id_user_id_fk": { + "name": "follow_from_user_id_user_id_fk", + "tableFrom": "follow", + "tableTo": "user", + "columnsFrom": [ + "from_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "follow_to_user_id_user_id_fk": { + "name": "follow_to_user_id_user_id_fk", + "tableFrom": "follow", + "tableTo": "user", + "columnsFrom": [ + "to_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.identity": { + "name": "identity", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "identity_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "identity_unq": { + "name": "identity_unq", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "external_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "identity_user_idx": { + "name": "identity_user_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "identity_user_id_user_id_fk": { + "name": "identity_user_id_user_id_fk", + "tableFrom": "identity", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.notifications": { + "name": "notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "from_user_id": { + "name": "from_user_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "object_id": { + "name": "object_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "notification_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "read": { + "name": "read", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "notifications_unq": { + "name": "notifications_unq", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "notifications_user_id_user_id_fk": { + "name": "notifications_user_id_user_id_fk", + "tableFrom": "notifications", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "notifications_from_user_id_user_id_fk": { + "name": "notifications_from_user_id_user_id_fk", + "tableFrom": "notifications", + "tableTo": "user", + "columnsFrom": [ + "from_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.region": { + "name": "region", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "country_id": { + "name": "country_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "location": { + "name": "location", + "type": "geometry(Point, 4326)", + "primaryKey": false, + "notNull": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description_src": { + "name": "description_src", + "type": "content_source", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "total_bottles": { + "name": "total_bottles", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_distillers": { + "name": "total_distillers", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "region_name_unq": { + "name": "region_name_unq", + "columns": [ + { + "expression": "country_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "LOWER(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "region_slug_unq": { + "name": "region_slug_unq", + "columns": [ + { + "expression": "country_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "LOWER(\"slug\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "region_country_idx": { + "name": "region_country_idx", + "columns": [ + { + "expression": "country_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "region_country_id_country_id_fk": { + "name": "region_country_id_country_id_fk", + "tableFrom": "region", + "tableTo": "country", + "columnsFrom": [ + "country_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.review": { + "name": "review", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "external_site_id": { + "name": "external_site_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bottle_id": { + "name": "bottle_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "hidden": { + "name": "hidden", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "rating": { + "name": "rating", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "issue": { + "name": "issue", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "review_unq_name": { + "name": "review_unq_name", + "columns": [ + { + "expression": "external_site_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "LOWER(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "issue", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "review_bottle_idx": { + "name": "review_bottle_idx", + "columns": [ + { + "expression": "bottle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "review_external_site_id_external_site_id_fk": { + "name": "review_external_site_id_external_site_id_fk", + "tableFrom": "review", + "tableTo": "external_site", + "columnsFrom": [ + "external_site_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "review_bottle_id_bottle_id_fk": { + "name": "review_bottle_id_bottle_id_fk", + "tableFrom": "review", + "tableTo": "bottle", + "columnsFrom": [ + "bottle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "review_url_unique": { + "name": "review_url_unique", + "nullsNotDistinct": false, + "columns": [ + "url" + ] + } + } + }, + "public.store_price_history": { + "name": "store_price_history", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "price_id": { + "name": "price_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "price": { + "name": "price", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "currency", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'usd'" + }, + "volume": { + "name": "volume", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "date", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "store_price_history_unq": { + "name": "store_price_history_unq", + "columns": [ + { + "expression": "price_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "volume", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "store_price_history_price_id_store_price_id_fk": { + "name": "store_price_history_price_id_store_price_id_fk", + "tableFrom": "store_price_history", + "tableTo": "store_price", + "columnsFrom": [ + "price_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.store_price": { + "name": "store_price", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "external_site_id": { + "name": "external_site_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bottle_id": { + "name": "bottle_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "hidden": { + "name": "hidden", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "price": { + "name": "price", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "currency", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "volume": { + "name": "volume", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "store_price_unq_name": { + "name": "store_price_unq_name", + "columns": [ + { + "expression": "external_site_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "LOWER(\"name\")", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "volume", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "store_price_bottle_idx": { + "name": "store_price_bottle_idx", + "columns": [ + { + "expression": "bottle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "store_price_external_site_id_external_site_id_fk": { + "name": "store_price_external_site_id_external_site_id_fk", + "tableFrom": "store_price", + "tableTo": "external_site", + "columnsFrom": [ + "external_site_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "store_price_bottle_id_bottle_id_fk": { + "name": "store_price_bottle_id_bottle_id_fk", + "tableFrom": "store_price", + "tableTo": "bottle", + "columnsFrom": [ + "bottle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "store_price_url_unique": { + "name": "store_price_url_unique", + "nullsNotDistinct": false, + "columns": [ + "url" + ] + } + } + }, + "public.tag": { + "name": "tag", + "schema": "", + "columns": { + "name": { + "name": "name", + "type": "varchar(64)", + "primaryKey": true, + "notNull": true + }, + "synonyms": { + "name": "synonyms", + "type": "varchar(64)[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "tag_category": { + "name": "tag_category", + "type": "tag_category", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "flavor_profile": { + "name": "flavor_profile", + "type": "flavor_profile[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.tasting_badge_award": { + "name": "tasting_badge_award", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "tasting_id": { + "name": "tasting_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "award_id": { + "name": "award_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tasting_badge_award_key": { + "name": "tasting_badge_award_key", + "columns": [ + { + "expression": "tasting_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "award_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasting_badge_award_award_id": { + "name": "tasting_badge_award_award_id", + "columns": [ + { + "expression": "award_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tasting_badge_award_tasting_id_tasting_id_fk": { + "name": "tasting_badge_award_tasting_id_tasting_id_fk", + "tableFrom": "tasting_badge_award", + "tableTo": "tasting", + "columnsFrom": [ + "tasting_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tasting_badge_award_award_id_badge_award_id_fk": { + "name": "tasting_badge_award_award_id_badge_award_id_fk", + "tableFrom": "tasting_badge_award", + "tableTo": "badge_award", + "columnsFrom": [ + "award_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.tasting": { + "name": "tasting", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "bottle_id": { + "name": "bottle_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "tags": { + "name": "tags", + "type": "varchar(64)[]", + "primaryKey": false, + "notNull": true, + "default": "array[]::varchar[]" + }, + "color": { + "name": "color", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "rating": { + "name": "rating", + "type": "double precision", + "primaryKey": false, + "notNull": false + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "serving_style": { + "name": "serving_style", + "type": "servingStyle", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "friends": { + "name": "friends", + "type": "bigint[]", + "primaryKey": false, + "notNull": true, + "default": "array[]::bigint[]" + }, + "flight_id": { + "name": "flight_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "comments": { + "name": "comments", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "toasts": { + "name": "toasts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by_id": { + "name": "created_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "tasting_unq": { + "name": "tasting_unq", + "columns": [ + { + "expression": "bottle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasting_bottle_idx": { + "name": "tasting_bottle_idx", + "columns": [ + { + "expression": "bottle_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasting_flight_idx": { + "name": "tasting_flight_idx", + "columns": [ + { + "expression": "flight_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "tasting_created_by_idx": { + "name": "tasting_created_by_idx", + "columns": [ + { + "expression": "created_by_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tasting_bottle_id_bottle_id_fk": { + "name": "tasting_bottle_id_bottle_id_fk", + "tableFrom": "tasting", + "tableTo": "bottle", + "columnsFrom": [ + "bottle_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tasting_flight_id_flight_id_fk": { + "name": "tasting_flight_id_flight_id_fk", + "tableFrom": "tasting", + "tableTo": "flight", + "columnsFrom": [ + "flight_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tasting_created_by_id_user_id_fk": { + "name": "tasting_created_by_id_user_id_fk", + "tableFrom": "tasting", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.toasts": { + "name": "toasts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "tasting_id": { + "name": "tasting_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by_id": { + "name": "created_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "toast_unq": { + "name": "toast_unq", + "columns": [ + { + "expression": "tasting_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_by_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "toasts_tasting_id_tasting_id_fk": { + "name": "toasts_tasting_id_tasting_id_fk", + "tableFrom": "toasts", + "tableTo": "tasting", + "columnsFrom": [ + "tasting_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "toasts_created_by_id_user_id_fk": { + "name": "toasts_created_by_id_user_id_fk", + "tableFrom": "toasts", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password_hash": { + "name": "password_hash", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "picture_url": { + "name": "picture_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified": { + "name": "verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "private": { + "name": "private", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "admin": { + "name": "admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "mod": { + "name": "mod", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notify_comments": { + "name": "notify_comments", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_email_unq": { + "name": "user_email_unq", + "columns": [ + { + "expression": "LOWER(\"email\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "user_username_unq": { + "name": "user_username_unq", + "columns": [ + { + "expression": "LOWER(\"username\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": { + "public.badge_award_object_type": { + "name": "badge_award_object_type", + "schema": "public", + "values": [ + "bottle", + "entity", + "country", + "region" + ] + }, + "public.badge_type": { + "name": "badge_type", + "schema": "public", + "values": [ + "age", + "bottle", + "entity", + "region", + "category", + "everyTasting" + ] + }, + "public.type": { + "name": "type", + "schema": "public", + "values": [ + "add", + "update", + "delete" + ] + }, + "public.entity_type": { + "name": "entity_type", + "schema": "public", + "values": [ + "brand", + "distiller", + "bottler" + ] + }, + "public.category": { + "name": "category", + "schema": "public", + "values": [ + "blend", + "bourbon", + "rye", + "single_grain", + "single_malt", + "single_pot_still", + "spirit" + ] + }, + "public.content_source": { + "name": "content_source", + "schema": "public", + "values": [ + "generated", + "user" + ] + }, + "public.flavor_profile": { + "name": "flavor_profile", + "schema": "public", + "values": [ + "young_spritely", + "sweet_fruit_mellow", + "spicy_sweet", + "spicy_dry", + "deep_rich_dried_fruit", + "old_dignified", + "light_delicate", + "juicy_oak_vanilla", + "oily_coastal", + "lightly_peated", + "peated", + "heavily_peated" + ] + }, + "public.object_type": { + "name": "object_type", + "schema": "public", + "values": [ + "bottle", + "comment", + "entity", + "tasting", + "toast", + "follow" + ] + }, + "public.tag_category": { + "name": "tag_category", + "schema": "public", + "values": [ + "cereal", + "fruity", + "floral", + "peaty", + "feinty", + "sulphury", + "woody", + "winey" + ] + }, + "public.external_site_type": { + "name": "external_site_type", + "schema": "public", + "values": [ + "astorwines", + "healthyspirits", + "reservebar", + "smws", + "smwsa", + "totalwine", + "woodencork", + "whiskyadvocate" + ] + }, + "public.follow_status": { + "name": "follow_status", + "schema": "public", + "values": [ + "none", + "pending", + "following" + ] + }, + "public.identity_provider": { + "name": "identity_provider", + "schema": "public", + "values": [ + "google" + ] + }, + "public.notification_type": { + "name": "notification_type", + "schema": "public", + "values": [ + "comment", + "toast", + "friend_request" + ] + }, + "public.currency": { + "name": "currency", + "schema": "public", + "values": [ + "usd", + "gbp", + "eur" + ] + }, + "public.servingStyle": { + "name": "servingStyle", + "schema": "public", + "values": [ + "neat", + "rocks", + "splash" + ] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/server/migrations/meta/_journal.json b/apps/server/migrations/meta/_journal.json index 56477430..2b8f2f83 100644 --- a/apps/server/migrations/meta/_journal.json +++ b/apps/server/migrations/meta/_journal.json @@ -981,6 +981,13 @@ "when": 1722563264938, "tag": "0139_many_miss_america", "breakpoints": false + }, + { + "idx": 140, + "version": "7", + "when": 1722622684309, + "tag": "0140_outgoing_bruce_banner", + "breakpoints": false } ] } \ No newline at end of file diff --git a/apps/server/src/constants.ts b/apps/server/src/constants.ts index c364e4f4..c197c9aa 100644 --- a/apps/server/src/constants.ts +++ b/apps/server/src/constants.ts @@ -37,7 +37,7 @@ export const EXTERNAL_SITE_TYPE_LIST = [ export const ENTITY_TYPE_LIST = ["brand", "bottler", "distiller"] as const; -export const BADGE_TYPE_LIST = [ +export const BADGE_CHECK_TYPE_LIST = [ "age", "bottle", "entity", @@ -46,6 +46,13 @@ export const BADGE_TYPE_LIST = [ "everyTasting", ] as const; +export const BADGE_TRACKER_LIST = [ + "bottle", + "entity", + "country", + "region", +] as const; + // https://whiskeytrends.com/whiskey-tasting-terminology/ // https://www.bonigala.com/25-ways-to-describe-whisky diff --git a/apps/server/src/db/schema/badges.ts b/apps/server/src/db/schema/badges.ts index 0c2288a7..e6b18b73 100644 --- a/apps/server/src/db/schema/badges.ts +++ b/apps/server/src/db/schema/badges.ts @@ -13,10 +13,15 @@ import { uniqueIndex, varchar, } from "drizzle-orm/pg-core"; -import { BADGE_TYPE_LIST } from "../../constants"; +import { BADGE_CHECK_TYPE_LIST, BADGE_TRACKER_LIST } from "../../constants"; import { users } from "./users"; -export const badgeTypeEnum = pgEnum("badge_type", BADGE_TYPE_LIST); +export const badgeCheckTypeEnum = pgEnum("badge_type", BADGE_CHECK_TYPE_LIST); + +export const badgeAwardTrackedObjectType = pgEnum( + "badge_award_object_type", + BADGE_TRACKER_LIST, +); export const badges = pgTable( "badges", @@ -25,7 +30,7 @@ export const badges = pgTable( name: varchar("name", { length: 255 }).notNull(), imageUrl: text("image_url"), maxLevel: integer("max_level").default(50).notNull(), - // TODO: config can be mapped to all badge type configs + tracker: badgeAwardTrackedObjectType("tracker").default("bottle").notNull(), checks: jsonb("checks").$type().default([]).notNull(), }, (table) => { @@ -79,11 +84,6 @@ export const badgeAwardsRelations = relations(badgeAwards, ({ one }) => ({ export type BadgeAward = typeof badgeAwards.$inferSelect; export type NewBadgeAward = typeof badgeAwards.$inferInsert; -export const badgeAwardTrackedObjectType = pgEnum("badge_award_object_type", [ - "bottle", - "entity", -]); - export const badgeAwardTrackedObjects = pgTable( "badge_award_tracked_object", { diff --git a/apps/server/src/lib/badges/index.test.ts b/apps/server/src/lib/badges/index.test.ts index 9cee4e99..39910ddf 100644 --- a/apps/server/src/lib/badges/index.test.ts +++ b/apps/server/src/lib/badges/index.test.ts @@ -66,6 +66,7 @@ describe("awardAllBadgeXp", () => { "imageUrl": null, "maxLevel": 1, "name": "Test", + "tracker": "bottle", } `); }); diff --git a/apps/server/src/lib/badges/index.ts b/apps/server/src/lib/badges/index.ts index 6e0e7a01..9278245d 100644 --- a/apps/server/src/lib/badges/index.ts +++ b/apps/server/src/lib/badges/index.ts @@ -1,5 +1,3 @@ -import type { BadgeType } from "../../types"; - import type { SQL } from "drizzle-orm"; import { and, eq, inArray, sql } from "drizzle-orm"; import { alias } from "drizzle-orm/pg-core"; @@ -15,15 +13,22 @@ import { tastings, type Badge, } from "../../db/schema"; +import type { BadgeCheckType, BadgeTracker } from "../../types"; import { AgeCheck } from "./checks/ageCheck"; +import type { Check } from "./checks/base"; import { BottleCheck } from "./checks/bottleCheck"; import { CategoryCheck } from "./checks/categoryCheck"; import { EntityCheck } from "./checks/entityCheck"; import { EveryTastingCheck } from "./checks/everyTastingCheck"; import { RegionCheck } from "./checks/regionCheck"; +import type { Tracker } from "./trackers/base"; +import { BottleTracker } from "./trackers/bottle"; +import { CountryTracker } from "./trackers/country"; +import { EntityTracker } from "./trackers/entity"; +import { RegionTracker } from "./trackers/region"; import { type TastingWithRelations, type TrackedObject } from "./types"; -export function getBadgeImpl(type: BadgeType) { +export function getCheckImpl(type: BadgeCheckType): Check { switch (type) { case "age": return new AgeCheck(); @@ -39,7 +44,22 @@ export function getBadgeImpl(type: BadgeType) { return new EveryTastingCheck(); default: - throw new Error(`Invalid badge type: ${type}`); + throw new Error(`Invalid type: ${type}`); + } +} + +export function getTrackerImpl(type: BadgeTracker): Tracker { + switch (type) { + case "bottle": + return new BottleTracker(); + case "entity": + return new EntityTracker(); + case "region": + return new RegionTracker(); + case "country": + return new CountryTracker(); + default: + throw new Error(`Invalid type: ${type}`); } } @@ -73,7 +93,7 @@ export async function rescanBadge(badge: Badge) { const checks = await Promise.all( badge.checks.map(async ({ type, config }) => { - const impl = getBadgeImpl(type); + const impl = getCheckImpl(type); return { impl, type, @@ -154,8 +174,8 @@ export async function rescanBadge(badge: Badge) { } // TODO: make this type safe -export async function checkBadgeConfig(type: BadgeType, config: unknown) { - const impl = getBadgeImpl(type); +export async function checkBadgeConfig(type: BadgeCheckType, config: unknown) { + const impl = getCheckImpl(type); return await impl.parseConfig(config); } @@ -168,7 +188,7 @@ async function awardXp( const checks = await Promise.all( badge.checks.map(async ({ type, config }) => { - const impl = getBadgeImpl(type); + const impl = getCheckImpl(type); return { impl, type, @@ -183,10 +203,12 @@ async function awardXp( console.info(`[badges] Badge ${badge.id} did not test successfully.`); return; } - for (const t of check.impl.track(check.config, tasting)) { - if (!trackedObjects.find((o) => o.type === t.type && o.id === t.id)) { - trackedObjects.push(t); - } + } + + const tracker = getTrackerImpl(badge.tracker); + for (const t of tracker.track(tasting)) { + if (!trackedObjects.find((o) => o.type === t.type && o.id === t.id)) { + trackedObjects.push(t); } } diff --git a/apps/server/src/lib/badges/trackers/base.ts b/apps/server/src/lib/badges/trackers/base.ts new file mode 100644 index 00000000..40d3a24c --- /dev/null +++ b/apps/server/src/lib/badges/trackers/base.ts @@ -0,0 +1,24 @@ +import type { TastingWithRelations, TrackedObject } from "../types"; + +export abstract class Tracker { + abstract track(tasting: TastingWithRelations): TrackedObject[]; + + getEntityList(tasting: TastingWithRelations) { + const { bottle } = tasting; + const resultIds = new Set([bottle.brand.id]); + const results = [bottle.brand]; + if (bottle.bottler && !resultIds.has(bottle.bottler.id)) { + results.push(bottle.bottler); + resultIds.add(bottle.bottler.id); + } + + for (const { distiller } of bottle.bottlesToDistillers) { + if (!resultIds.has(distiller.id)) { + results.push(distiller); + resultIds.add(distiller.id); + } + } + + return results; + } +} diff --git a/apps/server/src/lib/badges/trackers/bottle.ts b/apps/server/src/lib/badges/trackers/bottle.ts new file mode 100644 index 00000000..e8eb96b1 --- /dev/null +++ b/apps/server/src/lib/badges/trackers/bottle.ts @@ -0,0 +1,8 @@ +import type { TastingWithRelations } from "../types"; +import { Tracker } from "./base"; + +export class BottleTracker extends Tracker { + track(tasting: TastingWithRelations) { + return [{ type: "bottle" as const, id: tasting.bottle.id }]; + } +} diff --git a/apps/server/src/lib/badges/trackers/country.ts b/apps/server/src/lib/badges/trackers/country.ts new file mode 100644 index 00000000..331e4172 --- /dev/null +++ b/apps/server/src/lib/badges/trackers/country.ts @@ -0,0 +1,14 @@ +import { notEmpty } from "../../filter"; +import type { TastingWithRelations } from "../types"; +import { Tracker } from "./base"; + +export class CountryTracker extends Tracker { + track(tasting: TastingWithRelations) { + const entityList = this.getEntityList(tasting); + const countryIds = Array.from( + new Set(entityList.map((e) => e.countryId).filter(notEmpty)), + ); + + return countryIds.map((id) => ({ type: "country" as const, id })); + } +} diff --git a/apps/server/src/lib/badges/trackers/entity.ts b/apps/server/src/lib/badges/trackers/entity.ts new file mode 100644 index 00000000..888abb86 --- /dev/null +++ b/apps/server/src/lib/badges/trackers/entity.ts @@ -0,0 +1,11 @@ +import type { TastingWithRelations } from "../types"; +import { Tracker } from "./base"; + +export class EntityTracker extends Tracker { + track(tasting: TastingWithRelations) { + const entityList = this.getEntityList(tasting); + const entityIds = Array.from(new Set(entityList.map((e) => e.id))); + + return entityIds.map((id) => ({ type: "entity" as const, id })); + } +} diff --git a/apps/server/src/lib/badges/trackers/region.ts b/apps/server/src/lib/badges/trackers/region.ts new file mode 100644 index 00000000..cc31170c --- /dev/null +++ b/apps/server/src/lib/badges/trackers/region.ts @@ -0,0 +1,14 @@ +import { notEmpty } from "../../filter"; +import type { TastingWithRelations } from "../types"; +import { Tracker } from "./base"; + +export class RegionTracker extends Tracker { + track(tasting: TastingWithRelations) { + const entityList = this.getEntityList(tasting); + const regionIds = Array.from( + new Set(entityList.map((e) => e.regionId).filter(notEmpty)), + ); + + return regionIds.map((id) => ({ type: "region" as const, id })); + } +} diff --git a/apps/server/src/lib/badges/types.ts b/apps/server/src/lib/badges/types.ts index a37666f3..8549cef2 100644 --- a/apps/server/src/lib/badges/types.ts +++ b/apps/server/src/lib/badges/types.ts @@ -15,4 +15,7 @@ export type TastingWithRelations = Tasting & { }; }; -export type TrackedObject = { type: "bottle" | "entity"; id: number }; +export type TrackedObject = { + type: "bottle" | "entity" | "country" | "region"; + id: number; +}; diff --git a/apps/server/src/lib/test/fixtures.ts b/apps/server/src/lib/test/fixtures.ts index 8f7c7972..e15aad9b 100644 --- a/apps/server/src/lib/test/fixtures.ts +++ b/apps/server/src/lib/test/fixtures.ts @@ -1,7 +1,6 @@ import { faker } from "@faker-js/faker"; import * as dbSchema from "@peated/server/db/schema"; import { generatePublicId } from "@peated/server/lib/publicId"; -import type { BadgeCheck, BadgeType, Category } from "@peated/server/types"; import { type ExternalSiteType } from "@peated/server/types"; import slugify from "@sindresorhus/slugify"; import { eq, inArray, or, sql } from "drizzle-orm"; @@ -568,6 +567,7 @@ export const Badge = async ( .insert(badges) .values({ name: faker.word.noun(), + tracker: "bottle", checks: [ { type: "category", @@ -576,7 +576,7 @@ export const Badge = async ( }, }, ], - ...(data as Omit), + ...(data as Omit), }) .returning(); if (!result) throw new Error("Unable to create Badge fixture"); diff --git a/apps/server/src/schemas/badges.ts b/apps/server/src/schemas/badges.ts index b8fa2120..f1bcbbab 100644 --- a/apps/server/src/schemas/badges.ts +++ b/apps/server/src/schemas/badges.ts @@ -1,13 +1,15 @@ import { z } from "zod"; -import { BADGE_TYPE_LIST } from "../constants"; +import { BADGE_CHECK_TYPE_LIST, BADGE_TRACKER_LIST } from "../constants"; import { AgeCheckConfigSchema } from "../lib/badges/checks/ageCheck"; import { BottleCheckConfigSchema } from "../lib/badges/checks/bottleCheck"; import { CategoryCheckConfigSchema } from "../lib/badges/checks/categoryCheck"; import { EntityCheckConfigSchema } from "../lib/badges/checks/entityCheck"; import { RegionCheckConfigSchema } from "../lib/badges/checks/regionCheck"; -export const BadgeTypeEnum = z.enum(BADGE_TYPE_LIST); +export const BadgeCheckTypeEnum = z.enum(BADGE_CHECK_TYPE_LIST); + +export const BadgeTrackerEnum = z.enum(BADGE_TRACKER_LIST); // const BaseSchema = z.object({ // id: z.number(), @@ -73,11 +75,14 @@ export const BadgeSchema = z.object({ id: z.number(), name: z.string().trim().min(1, "Required"), maxLevel: z.number().min(1).max(100).default(25), + imageUrl: z.string().nullable().default(null), + + // only shown to admin so badges are 'secret' to users checks: z .array(BadgeCheckSchema) .min(1, "At least one check is required.") .optional(), - imageUrl: z.string().nullable().default(null), + tracker: BadgeTrackerEnum.optional(), }); export const BadgeInputSchema = BadgeSchema.omit({ @@ -85,6 +90,7 @@ export const BadgeInputSchema = BadgeSchema.omit({ imageUrl: true, }).required({ checks: true, + tracker: true, }); export const BadgeAwardSchema = z.object({ diff --git a/apps/server/src/serializers/badge.ts b/apps/server/src/serializers/badge.ts index 20a66daf..3c06a9ce 100644 --- a/apps/server/src/serializers/badge.ts +++ b/apps/server/src/serializers/badge.ts @@ -18,7 +18,9 @@ export const BadgeSerializer = serializer({ imageUrl: item.imageUrl ? absoluteUrl(config.API_SERVER, item.imageUrl) : null, - ...(currentUser?.admin ? { checks: item.checks } : {}), + ...(currentUser?.admin + ? { checks: item.checks, tracker: item.tracker } + : {}), }; }, }); diff --git a/apps/server/src/trpc/routes/badgeCreate.test.ts b/apps/server/src/trpc/routes/badgeCreate.test.ts index 594cc002..2c0e2c8b 100644 --- a/apps/server/src/trpc/routes/badgeCreate.test.ts +++ b/apps/server/src/trpc/routes/badgeCreate.test.ts @@ -9,6 +9,7 @@ test("requires admin", async ({ defaults }) => { const err = await waitError( caller.badgeCreate({ name: "Single Malts", + tracker: "bottle", checks: [{ type: "category", config: { category: ["single_malt"] } }], }), ); @@ -22,6 +23,7 @@ test("creates badge", async ({ fixtures }) => { const data = await caller.badgeCreate({ name: "Single Malts", + tracker: "bottle", checks: [{ type: "category", config: { category: ["single_malt"] } }], }); diff --git a/apps/server/src/trpc/routes/tastingCreate.test.ts b/apps/server/src/trpc/routes/tastingCreate.test.ts index c221c420..09fd4a8e 100644 --- a/apps/server/src/trpc/routes/tastingCreate.test.ts +++ b/apps/server/src/trpc/routes/tastingCreate.test.ts @@ -280,15 +280,6 @@ test("creates a new tasting with badge award", async ({ expect(data.awards.length).toEqual(1); expect(data.awards[0].badge).toMatchInlineSnapshot(` { - "checks": [ - { - "config": { - "maxAge": 10, - "minAge": 5, - }, - "type": "age", - }, - ], "id": 1, "imageUrl": "http://localhost:4000/images/foobar.png", "maxLevel": 10, diff --git a/apps/server/src/types.ts b/apps/server/src/types.ts index be602d6a..265fcfc3 100644 --- a/apps/server/src/types.ts +++ b/apps/server/src/types.ts @@ -2,8 +2,9 @@ import type { z } from "zod"; import type { BadgeAwardSchema, BadgeCheckSchema, + BadgeCheckTypeEnum, BadgeSchema, - BadgeTypeEnum, + BadgeTrackerEnum, BottleSchema, CaskFillEnum, CaskSizeEnum, @@ -49,7 +50,8 @@ export type CaskSize = z.infer; export type CaskFill = z.infer; export type ExternalSiteType = z.infer; -export type BadgeType = z.infer; +export type BadgeCheckType = z.infer; +export type BadgeTracker = z.infer; export type EntityType = z.infer; export type ObjectType = z.infer; diff --git a/apps/web/src/app/(admin)/admin/(default)/badges/[badgeId]/page.tsx b/apps/web/src/app/(admin)/admin/(default)/badges/[badgeId]/page.tsx index 3f4f936c..85048d5a 100644 --- a/apps/web/src/app/(admin)/admin/(default)/badges/[badgeId]/page.tsx +++ b/apps/web/src/app/(admin)/admin/(default)/badges/[badgeId]/page.tsx @@ -74,6 +74,8 @@ export default function Page({ Implementation + Tracker + {badge.tracker} Config
diff --git a/apps/web/src/components/admin/badgeForm.tsx b/apps/web/src/components/admin/badgeForm.tsx
index cbd0f4bc..871eab8c 100644
--- a/apps/web/src/components/admin/badgeForm.tsx
+++ b/apps/web/src/components/admin/badgeForm.tsx
@@ -1,11 +1,14 @@
 "use client";
 
 import { zodResolver } from "@hookform/resolvers/zod";
-import { BADGE_TYPE_LIST } from "@peated/server/constants";
+import {
+  BADGE_CHECK_TYPE_LIST,
+  BADGE_TRACKER_LIST,
+} from "@peated/server/constants";
 import { toTitleCase } from "@peated/server/lib/strings";
 import type { BadgeCheckInputSchema } from "@peated/server/schemas";
 import { BadgeCheckSchema, BadgeInputSchema } from "@peated/server/schemas";
-import type { BadgeCheck, BadgeType } from "@peated/server/types";
+import type { BadgeCheck, BadgeCheckType } from "@peated/server/types";
 import { type Badge } from "@peated/server/types";
 import Fieldset from "@peated/web/components/fieldset";
 import FormError from "@peated/web/components/formError";
@@ -16,12 +19,13 @@ import TextField from "@peated/web/components/textField";
 import { logError } from "@peated/web/lib/log";
 import { isTRPCClientError } from "@peated/web/lib/trpc/client";
 import { useState } from "react";
-import { useForm, type SubmitHandler } from "react-hook-form";
+import { Controller, useForm, type SubmitHandler } from "react-hook-form";
 import type { z } from "zod";
 import Button from "../button";
 import Form from "../form";
 import ImageField from "../imageField";
 import Legend from "../legend";
+import SelectField from "../selectField";
 import AgeCheckConfigForm from "./badgeConfigForms/ageCheckConfigForm";
 import BottleCheckConfigForm from "./badgeConfigForms/bottleCheckConfigForm";
 import CategoryCheckConfigForm from "./badgeConfigForms/categoryCheckConfigForm";
@@ -51,6 +55,7 @@ export default function BadgeForm({
   edit?: boolean;
 }) {
   const {
+    control,
     register,
     handleSubmit,
     setValue,
@@ -142,13 +147,40 @@ export default function BadgeForm({
             error={errors.maxLevel}
           />
         
+
+        
+ ( + onChange(value?.id)} + value={ + value + ? { + id: value, + name: toTitleCase(value), + } + : undefined + } + options={BADGE_TRACKER_LIST.map((t) => ({ + id: t, + name: toTitleCase(t), + }))} + simple + /> + )} + /> +
Add:
- {BADGE_TYPE_LIST.map((t) => { + {BADGE_CHECK_TYPE_LIST.map((t) => { return (
{renderBadgeConfig({ - badgeType: check.type, + checkType: check.type, onChange: (data) => { setValue( "checks", @@ -286,15 +318,15 @@ export default function BadgeForm({ } function renderBadgeConfig({ - badgeType, + checkType, initialData, onChange, }: { - badgeType: BadgeType; + checkType: BadgeCheckType; onChange: (data: Record) => void; initialData: Record; }) { - switch (badgeType) { + switch (checkType) { case "age": return (