diff --git a/api/drizzle/20260617013849_flippant_peter_parker/migration.sql b/api/drizzle/20260617013849_flippant_peter_parker/migration.sql new file mode 100644 index 00000000..20ec2f42 --- /dev/null +++ b/api/drizzle/20260617013849_flippant_peter_parker/migration.sql @@ -0,0 +1 @@ +ALTER TABLE "beep" ADD COLUMN "rider_live_activity_token" varchar(255); \ No newline at end of file diff --git a/api/drizzle/20260617013849_flippant_peter_parker/snapshot.json b/api/drizzle/20260617013849_flippant_peter_parker/snapshot.json new file mode 100644 index 00000000..1b1e7b90 --- /dev/null +++ b/api/drizzle/20260617013849_flippant_peter_parker/snapshot.json @@ -0,0 +1,1707 @@ +{ + "version": "8", + "dialect": "postgres", + "id": "721eac3e-185b-4bec-80b2-241c9c707b16", + "prevIds": [ + "927a46d5-918f-49f9-ae18-0a4ea2ccf091" + ], + "ddl": [ + { + "values": [ + "canceled", + "denied", + "waiting", + "accepted", + "on_the_way", + "here", + "in_progress", + "complete" + ], + "name": "beep_status", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "top_of_beeper_list_1_hour", + "top_of_beeper_list_2_hours", + "top_of_beeper_list_3_hours" + ], + "name": "payment_product", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "play_store", + "app_store" + ], + "name": "payment_store", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "sha256", + "bcrypt" + ], + "name": "user_password_type", + "entityType": "enums", + "schema": "public" + }, + { + "values": [ + "user", + "admin" + ], + "name": "user_role", + "entityType": "enums", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "beep", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "car", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "feedback", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "forgot_password", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "payment", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "rating", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "report", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "token", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "user", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "verify_email", + "entityType": "tables", + "schema": "public" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "beep" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "beeper_id", + "entityType": "columns", + "schema": "public", + "table": "beep" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "rider_id", + "entityType": "columns", + "schema": "public", + "table": "beep" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "origin", + "entityType": "columns", + "schema": "public", + "table": "beep" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "destination", + "entityType": "columns", + "schema": "public", + "table": "beep" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "group_size", + "entityType": "columns", + "schema": "public", + "table": "beep" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "start", + "entityType": "columns", + "schema": "public", + "table": "beep" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "end", + "entityType": "columns", + "schema": "public", + "table": "beep" + }, + { + "type": "beep_status", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'waiting'", + "generated": null, + "identity": null, + "name": "status", + "entityType": "columns", + "schema": "public", + "table": "beep" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "rider_live_activity_token", + "entityType": "columns", + "schema": "public", + "table": "beep" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "car" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "car" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "make", + "entityType": "columns", + "schema": "public", + "table": "car" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "model", + "entityType": "columns", + "schema": "public", + "table": "car" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "color", + "entityType": "columns", + "schema": "public", + "table": "car" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "photo", + "entityType": "columns", + "schema": "public", + "table": "car" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "year", + "entityType": "columns", + "schema": "public", + "table": "car" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "default", + "entityType": "columns", + "schema": "public", + "table": "car" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "car" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated", + "entityType": "columns", + "schema": "public", + "table": "car" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "feedback" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "feedback" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "message", + "entityType": "columns", + "schema": "public", + "table": "feedback" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "feedback" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "forgot_password" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "forgot_password" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "time", + "entityType": "columns", + "schema": "public", + "table": "forgot_password" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "payment" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "payment" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "store_id", + "entityType": "columns", + "schema": "public", + "table": "payment" + }, + { + "type": "payment_product", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "product_id", + "entityType": "columns", + "schema": "public", + "table": "payment" + }, + { + "type": "numeric", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "price", + "entityType": "columns", + "schema": "public", + "table": "payment" + }, + { + "type": "payment_store", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "store", + "entityType": "columns", + "schema": "public", + "table": "payment" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "payment" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires", + "entityType": "columns", + "schema": "public", + "table": "payment" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "rating" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "rater_id", + "entityType": "columns", + "schema": "public", + "table": "rating" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "rated_id", + "entityType": "columns", + "schema": "public", + "table": "rating" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "stars", + "entityType": "columns", + "schema": "public", + "table": "rating" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "message", + "entityType": "columns", + "schema": "public", + "table": "rating" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "timestamp", + "entityType": "columns", + "schema": "public", + "table": "rating" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "beep_id", + "entityType": "columns", + "schema": "public", + "table": "rating" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "report" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "reporter_id", + "entityType": "columns", + "schema": "public", + "table": "report" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "reported_id", + "entityType": "columns", + "schema": "public", + "table": "report" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "handled_by_id", + "entityType": "columns", + "schema": "public", + "table": "report" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "reason", + "entityType": "columns", + "schema": "public", + "table": "report" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "notes", + "entityType": "columns", + "schema": "public", + "table": "report" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "timestamp", + "entityType": "columns", + "schema": "public", + "table": "report" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "handled", + "entityType": "columns", + "schema": "public", + "table": "report" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "beep_id", + "entityType": "columns", + "schema": "public", + "table": "report" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "rating_id", + "entityType": "columns", + "schema": "public", + "table": "report" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "token" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tokenid", + "entityType": "columns", + "schema": "public", + "table": "token" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "token" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "first", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "username", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "phone", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "venmo", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "cashapp", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "password", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "user_password_type", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'bcrypt'", + "generated": null, + "identity": null, + "name": "password_type", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "is_beeping", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "is_email_verified", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "is_student", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "4", + "generated": null, + "identity": null, + "name": "group_rate", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "3", + "generated": null, + "identity": null, + "name": "singles_rate", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "4", + "generated": null, + "identity": null, + "name": "capacity", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "queue_size", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "numeric", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "rating", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "user_role", + "typeSchema": "public", + "notNull": true, + "dimensions": 0, + "default": "'user'", + "generated": null, + "identity": null, + "name": "role", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "push_token", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "photo", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "geometry", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "location", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created", + "entityType": "columns", + "schema": "public", + "table": "user" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "verify_email" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "verify_email" + }, + { + "type": "timestamp with time zone", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "time", + "entityType": "columns", + "schema": "public", + "table": "verify_email" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "email", + "entityType": "columns", + "schema": "public", + "table": "verify_email" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "beeper_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "beeper_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "beep" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "beeper_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "rider_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "beeper_id_rider_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "beep" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "rider_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "rider_id_idx", + "entityType": "indexes", + "schema": "public", + "table": "beep" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "start", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "start_idx", + "entityType": "indexes", + "schema": "public", + "table": "beep" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "status", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "status_idx", + "entityType": "indexes", + "schema": "public", + "table": "beep" + }, + { + "nameExplicit": false, + "columns": [ + "beeper_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "name": "beep_beeper_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "beep" + }, + { + "nameExplicit": false, + "columns": [ + "rider_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "name": "beep_rider_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "beep" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "name": "car_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "car" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "name": "feedback_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "feedback" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "name": "forgot_password_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "forgot_password" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "name": "payment_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "payment" + }, + { + "nameExplicit": false, + "columns": [ + "rater_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "name": "rating_rater_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "rating" + }, + { + "nameExplicit": false, + "columns": [ + "rated_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "name": "rating_rated_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "rating" + }, + { + "nameExplicit": false, + "columns": [ + "beep_id" + ], + "schemaTo": "public", + "tableTo": "beep", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "name": "rating_beep_id_beep_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "rating" + }, + { + "nameExplicit": false, + "columns": [ + "reporter_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "name": "report_reporter_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "report" + }, + { + "nameExplicit": false, + "columns": [ + "reported_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "name": "report_reported_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "report" + }, + { + "nameExplicit": false, + "columns": [ + "handled_by_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "SET NULL", + "name": "report_handled_by_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "report" + }, + { + "nameExplicit": false, + "columns": [ + "beep_id" + ], + "schemaTo": "public", + "tableTo": "beep", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "SET NULL", + "name": "report_beep_id_beep_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "report" + }, + { + "nameExplicit": false, + "columns": [ + "rating_id" + ], + "schemaTo": "public", + "tableTo": "rating", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "SET NULL", + "name": "report_rating_id_rating_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "report" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "name": "token_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "token" + }, + { + "nameExplicit": false, + "columns": [ + "user_id" + ], + "schemaTo": "public", + "tableTo": "user", + "columnsTo": [ + "id" + ], + "onUpdate": "CASCADE", + "onDelete": "CASCADE", + "name": "verify_email_user_id_user_id_fkey", + "entityType": "fks", + "schema": "public", + "table": "verify_email" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "beep_pkey", + "schema": "public", + "table": "beep", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "car_pkey", + "schema": "public", + "table": "car", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "feedback_pkey", + "schema": "public", + "table": "feedback", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "forgot_password_pkey", + "schema": "public", + "table": "forgot_password", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "payment_pkey", + "schema": "public", + "table": "payment", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "rating_pkey", + "schema": "public", + "table": "rating", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "report_pkey", + "schema": "public", + "table": "report", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "token_pkey", + "schema": "public", + "table": "token", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "user_pkey", + "schema": "public", + "table": "user", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "verify_email_pkey", + "schema": "public", + "table": "verify_email", + "entityType": "pks" + }, + { + "nameExplicit": true, + "columns": [ + "rater_id", + "beep_id" + ], + "nullsNotDistinct": false, + "name": "rating_beep_id_rater_id_unique", + "entityType": "uniques", + "schema": "public", + "table": "rating" + }, + { + "nameExplicit": true, + "columns": [ + "username" + ], + "nullsNotDistinct": false, + "name": "user_username_unique", + "entityType": "uniques", + "schema": "public", + "table": "user" + }, + { + "nameExplicit": true, + "columns": [ + "email" + ], + "nullsNotDistinct": false, + "name": "user_email_unique", + "entityType": "uniques", + "schema": "public", + "table": "user" + } + ], + "renames": [] +} \ No newline at end of file diff --git a/api/drizzle/schema.ts b/api/drizzle/schema.ts index 767c3e6f..41e081d6 100644 --- a/api/drizzle/schema.ts +++ b/api/drizzle/schema.ts @@ -183,6 +183,9 @@ export const beep = pgTable( start: timestamp("start", { withTimezone: true, mode: "date" }).notNull(), end: timestamp("end", { withTimezone: true, mode: "date" }), status: beepStatusEnum("status").default("waiting").notNull(), + rider_live_activity_token: varchar("rider_live_activity_token", { + length: 255, + }), }, (table) => [ index("beeper_id_idx").using("btree", table.beeper_id), diff --git a/api/package.json b/api/package.json index 62ddd6bc..aa82ab32 100644 --- a/api/package.json +++ b/api/package.json @@ -28,6 +28,7 @@ "car-info": "0.2.5", "drizzle-orm": "1.0.0-rc.3", "ioredis": "^5.10.1", + "jose": "^6.2.3", "nodemailer": "^8.0.5", "pg": "^8.21.0", "redlock-universal": "^0.8.4", diff --git a/api/src/logic/beep.ts b/api/src/logic/beep.ts index 3be806fe..7c9a723d 100644 --- a/api/src/logic/beep.ts +++ b/api/src/logic/beep.ts @@ -4,9 +4,9 @@ import { db } from "../utils/db"; import { beep } from "../../drizzle/schema"; import { pubSub, User } from "../utils/pubsub"; import { sendNotification } from "../utils/notifications"; +import { updateLiveActivity } from "../utils/live-activities"; type Beep = typeof beep.$inferSelect; -type BeepStatus = Beep["status"]; export const inProgressBeep = or( eq(beep.status, "waiting"), @@ -103,89 +103,182 @@ export function getDerivedRiderFields(beep: Beep, queue: Beep[]) { } export async function sendBeepUpdateNotificationToRider( - riderId: string, - status: BeepStatus, + beep: Beep, beeper: User, ) { - const rider = await db.query.user.findFirst({ - columns: { - pushToken: true, - }, - where: { id: riderId }, - }); + switch (beep.status) { + case "canceled": { + if (beep.rider_live_activity_token) { + updateLiveActivity(beep.rider_live_activity_token, { + action: "end", + name: "RiderActivity", + }); + } - if (!rider?.pushToken) { - return; - } + const riderPushToken = await getUsersPushToken(beep.rider_id); - switch (status) { - case "canceled": - sendNotification({ - to: rider.pushToken, - title: `${beeper.first} ${beeper.last} has canceled your beep 🚫`, - body: "Open your app to find a new beep", - }); + if (riderPushToken) { + sendNotification({ + to: riderPushToken, + title: `${beeper.first} ${beeper.last} has canceled your beep`, + body: "Open your app to find a new beep", + }); + } break; - case "denied": - sendNotification({ - to: rider.pushToken, - title: `${beeper.first} ${beeper.last} has denied your beep request 🚫`, - body: "Open your app to find a different beeper", - }); + } + case "denied": { + if (beep.rider_live_activity_token) { + updateLiveActivity(beep.rider_live_activity_token, { + action: "end", + name: "RiderActivity", + }); + } + + const riderPushToken = await getUsersPushToken(beep.rider_id); + + if (riderPushToken) { + sendNotification({ + to: riderPushToken, + title: `${beeper.first} ${beeper.last} has denied your beep`, + body: "Open your app to find a different beeper", + }); + } break; - case "accepted": - sendNotification({ - to: rider.pushToken, - title: `${beeper.first} ${beeper.last} has accepted your beep request ✅`, + } + case "accepted": { + const alert = { + title: `${beeper.first} ${beeper.last} has accepted your beep request`, body: "You will receive another notification when they are on their way to pick you up", - }); + }; + + if (beep.rider_live_activity_token) { + updateLiveActivity(beep.rider_live_activity_token, { + action: "update", + name: "RiderActivity", + props: { + name: `${beeper.first} ${beeper.last}`, + status: beep.status, + }, + alert, + }); + } else { + const riderPushToken = await getUsersPushToken(beep.rider_id); + + if (riderPushToken) { + sendNotification({ + to: riderPushToken, + ...alert, + }); + } + } break; + } case "on_the_way": { + const alert = { + title: `${beeper.first} ${beeper.last} is on their way 🚕`, + body: "Your beeper is on their way.", + }; + const c = await db.query.car.findFirst({ where: { user_id: beeper.id, default: true }, }); - let body = "Your beeper is on their way."; - if (c) { - body = `Your beeper is on their way in a ${c.color} ${c.make} ${c.model}`; + alert.body = `Your beeper is on their way in a ${c.color} ${c.make} ${c.model}`; } - sendNotification({ - to: rider.pushToken, - title: `${beeper.first} ${beeper.last} is on their way 🚕`, - body, - }); + if (beep.rider_live_activity_token) { + updateLiveActivity(beep.rider_live_activity_token, { + action: "update", + alert, + name: "RiderActivity", + props: { + etaMinutes: 5, + name: `${beeper.first} ${beeper.last}`, + status: beep.status, + }, + }); + } else { + const riderPushToken = await getUsersPushToken(beep.rider_id); + + if (riderPushToken) { + sendNotification({ + to: riderPushToken, + ...alert, + }); + } + } break; } case "here": { + const alert = { + title: `${beeper.first} ${beeper.last} is here`, + body: "Your beeper is here to pick you up.", + }; const c = await db.query.car.findFirst({ where: { user_id: beeper.id, default: true }, }); - let body = "Your beeper is here to pick you up."; - if (c) { - body = `Look for a ${c.color} ${c.make} ${c.model}`; + alert.body = `Look for a ${c.color} ${c.make} ${c.model}`; } - sendNotification({ - to: rider.pushToken, - title: `${beeper.first} ${beeper.last} is here 📍`, - body, - }); + if (beep.rider_live_activity_token) { + } + + if (beep.rider_live_activity_token) { + updateLiveActivity(beep.rider_live_activity_token, { + action: "update", + alert, + name: "RiderActivity", + props: { + name: `${beeper.first} ${beeper.last}`, + status: beep.status, + }, + }); + } else { + const riderPushToken = await getUsersPushToken(beep.rider_id); + + if (riderPushToken) { + sendNotification({ + to: riderPushToken, + ...alert, + }); + } + } break; } case "in_progress": - // Beep is in progress - no notification needed at this stage. + if (beep.rider_live_activity_token) { + updateLiveActivity(beep.rider_live_activity_token, { + action: "update", + name: "RiderActivity", + props: { + name: `${beeper.first} ${beeper.last}`, + status: beep.status, + }, + }); + } break; - case "complete": - sendNotification({ - to: rider.pushToken, - title: `Your beep with ${beeper.first} ${beeper.last} is complete 🎉`, - body: "Please rate your beeper in the app.", - }); + case "complete": { + if (beep.rider_live_activity_token) { + updateLiveActivity(beep.rider_live_activity_token, { + action: "end", + name: "RiderActivity", + }); + } + + const riderPushToken = await getUsersPushToken(beep.rider_id); + + if (riderPushToken) { + sendNotification({ + to: riderPushToken, + title: `Your beep with ${beeper.first} ${beeper.last} is complete 🎉`, + body: "Please rate your beeper in the app.", + }); + } break; + } default: Sentry.captureException( "Our beeper's state notification switch statement reached a point that is should not have", @@ -193,6 +286,17 @@ export async function sendBeepUpdateNotificationToRider( } } +async function getUsersPushToken(userId: string) { + const rider = await db.query.user.findFirst({ + columns: { + pushToken: true, + }, + where: { id: userId }, + }); + + return rider?.pushToken ?? null; +} + export async function getBeepsCount() { return await db.$count(beep); } diff --git a/api/src/routers/beeper.ts b/api/src/routers/beeper.ts index 0d293547..aa8cc365 100644 --- a/api/src/routers/beeper.ts +++ b/api/src/routers/beeper.ts @@ -134,11 +134,7 @@ export const beeperRouter = router({ pubSub.publish("ride", queueEntry.rider.id, { ride: null }); } - sendBeepUpdateNotificationToRider( - queueEntry.rider.id, - queueEntry.status, - ctx.user, - ); + sendBeepUpdateNotificationToRider(queueEntry, ctx.user); queue = queue.filter(getIsInProgressBeep); diff --git a/api/src/routers/rider.ts b/api/src/routers/rider.ts index 38c8878c..45640d5d 100644 --- a/api/src/routers/rider.ts +++ b/api/src/routers/rider.ts @@ -155,6 +155,7 @@ export const riderRouter = router({ start: new Date(), status: "waiting", end: null, + rider_live_activity_token: null, } as const; const currentRide = await db.query.beep.findFirst({ @@ -474,4 +475,31 @@ export const riderRouter = router({ return mostRecentCompletedBeep; }), + setBeepLiveActivityToken: authedProcedure + .input(z.object({ beepId: z.string(), token: z.string() })) + .mutation(async ({ ctx, input }) => { + const b = await db.query.beep.findFirst({ + where: { id: input.beepId }, + }); + + if (!b) { + throw new TRPCError({ code: "NOT_FOUND", message: "Beep not found." }); + } + + if (ctx.user.id !== b.rider_id) { + throw new TRPCError({ + code: "FORBIDDEN", + message: + "You must be the rider of the beep to set the rider live activity token", + }); + } + console.log("Got token", input.token); + + await db + .update(beep) + .set({ rider_live_activity_token: input.token }) + .where(eq(beep.id, b.id)); + + return {}; + }), }); diff --git a/api/src/utils/constants.ts b/api/src/utils/constants.ts index 2f1ffced..d2b2e810 100644 --- a/api/src/utils/constants.ts +++ b/api/src/utils/constants.ts @@ -88,3 +88,7 @@ export const CAR_COLOR_OPTIONS = [ export const PHOTON_BASE_URL = "http://192.168.0.110:2322"; export const OSRM_BASE_URL = "http://192.168.0.104:5000"; + +export const APPLE_APN_KEY_ID = process.env.APPLE_APN_KEY_ID; +export const APPLE_APN_KEY = process.env.APPLE_APN_KEY; +export const APPLE_TEAM_ID = process.env.APPLE_TEAM_ID; diff --git a/api/src/utils/live-activities.ts b/api/src/utils/live-activities.ts new file mode 100644 index 00000000..496182d7 --- /dev/null +++ b/api/src/utils/live-activities.ts @@ -0,0 +1,88 @@ +import { importPKCS8, SignJWT } from "jose"; +import http2 from "http2"; +import { APPLE_APN_KEY, APPLE_APN_KEY_ID, APPLE_TEAM_ID } from "./constants"; + +type ActivityUpdate = + | { + name: "RiderActivity"; + action: "update"; + props: { status: string; etaMinutes?: number; name: string }; + alert?: { title: string; body: string }; + } + | { + name: "RiderActivity"; + action: "end"; + props?: never; + alert?: never; + }; + +export async function updateLiveActivity( + deviceToken: string, + options: ActivityUpdate, +) { + const devicePath = `/3/device/${deviceToken}`; + + if (!APPLE_APN_KEY || !APPLE_TEAM_ID) { + throw new Error("Apple Keys not setup in the Beep API."); + } + + const privateKey = await importPKCS8(APPLE_APN_KEY, "ES256"); + + const token = await new SignJWT() + .setIssuer(APPLE_TEAM_ID) + .setIssuedAt() + .setProtectedHeader({ + alg: "ES256", + kid: APPLE_APN_KEY_ID, + }) + .sign(privateKey); + + const body = { + aps: { + timestamp: Date.now(), + event: options.action, + "content-state": + options.action === "update" + ? { + name: options.name, + props: JSON.stringify(options.props), + } + : {}, + alert: options.alert, + ...(options.action === "end" + ? { + "dismissal-date": Date.now() / 1000, + } + : {}), + sound: "default", + }, + }; + + const client = http2.connect("https://api.push.apple.com"); + + client.on("error", (err) => console.error(err)); + + const headers = { + ":method": "POST", + "apns-push-type": "liveactivity", + "apns-topic": "app.ridebeep.App.push-type.liveactivity", + "apns-priority": 10, + ":scheme": "https", + ":path": devicePath, + authorization: `bearer ${token}`, + }; + + const request = client.request(headers); + + request.on("response", (headers, flags) => { + console.log("From APN:", headers, flags); + }); + request.on("data", (data) => { + console.log(data); + }); + + request.setEncoding("utf8"); + + request.write(JSON.stringify(body)); + request.end(); +} diff --git a/app/app.config.ts b/app/app.config.ts index 243f4e4e..c7376498 100644 --- a/app/app.config.ts +++ b/app/app.config.ts @@ -57,6 +57,7 @@ const config: ExpoConfig = { }), expoWidgets({ enablePushNotifications: true, + frequentUpdates: true, }), [ "react-native-maps", diff --git a/app/src/app/(tabs)/(ride)/ride/index.tsx b/app/src/app/(tabs)/(ride)/ride/index.tsx index b364c164..0b5ac8af 100644 --- a/app/src/app/(tabs)/(ride)/ride/index.tsx +++ b/app/src/app/(tabs)/(ride)/ride/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import { useEffect } from "react"; import { useTRPC } from "@/utils/trpc"; import { skipToken, useQuery } from "@tanstack/react-query"; import { useSubscription } from "@trpc/tanstack-react-query"; @@ -8,7 +8,7 @@ import { BottomSheet } from "@/components/BottomSheet"; import { Pressable, View } from "react-native"; import { RideMap } from "@/components/RideMap"; import { BottomSheetView } from "@gorhom/bottom-sheet"; -import { Controller, useForm, useFormContext } from "react-hook-form"; +import { Controller, useFormContext } from "react-hook-form"; import { KeyboardAwareScrollView } from "react-native-keyboard-controller"; import { Input, TextField, FieldError } from "heroui-native"; import { LocationInput } from "@/components/LocationInput"; @@ -16,13 +16,8 @@ import { Button } from "@/components/Button"; import { BeepersMap } from "@/components/BeepersMap"; import { RideMenu } from "@/components/RideToolbar"; import { Label } from "@/components/Label"; -import { - Link, - SplashScreen, - useLocalSearchParams, - useRouter, -} from "expo-router"; -// import { RateLastBeeper } from "@/components/RateLastBeeper"; +import { Link, SplashScreen, useRouter } from "expo-router"; +import { ensureRiderLiveActivity } from "@/live-activities/utils"; export default function MainFindBeepScreen() { const trpc = useTRPC(); @@ -63,6 +58,10 @@ export default function MainFindBeepScreen() { SplashScreen.hide(); }, []); + useEffect(() => { + ensureRiderLiveActivity(beep); + }, [beep]); + const router = useRouter(); const { control, handleSubmit, setFocus, getValues } = useFormContext(); diff --git a/app/src/app/(tabs)/(ride)/ride/pick.tsx b/app/src/app/(tabs)/(ride)/ride/pick.tsx index 9d9852a4..27c144ba 100644 --- a/app/src/app/(tabs)/(ride)/ride/pick.tsx +++ b/app/src/app/(tabs)/(ride)/ride/pick.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import { useEffect } from "react"; import { useNavigation } from "expo-router/react-navigation"; import { Avatar } from "@/components/Avatar"; import { useLocation } from "@/utils/location"; @@ -12,7 +12,7 @@ import { useQueryClient } from "@tanstack/react-query"; import { printStars } from "@/components/Stars"; import { useLocalSearchParams } from "expo-router"; import { tryCatch } from "@/utils/errors"; -import { captureException, captureMessage } from "@sentry/react-native"; +import { captureException } from "@sentry/react-native"; import { getContentContainerStyle } from "@/utils/styles"; export default function PickBeepScreen() { diff --git a/app/src/live-activities/rider-activity.tsx b/app/src/live-activities/rider-activity.tsx new file mode 100644 index 00000000..6ac5436c --- /dev/null +++ b/app/src/live-activities/rider-activity.tsx @@ -0,0 +1,69 @@ +import { HStack, Spacer, Text, VStack } from "@expo/ui/swift-ui"; +import { font, padding } from "@expo/ui/swift-ui/modifiers"; +import { createLiveActivity, type LiveActivityEnvironment } from "expo-widgets"; + +export interface RiderActivityProps { + name: string; + etaMinutes?: number; + status: string; +} + +const RiderActivity = ( + props: RiderActivityProps, + environment: LiveActivityEnvironment, +) => { + "widget"; + + return { + banner: ( + + 🚕 + + {props.name} + {props.status} + + + {props.etaMinutes !== undefined && ( + + + {props.etaMinutes} + + minutes + + )} + + ), + compactLeading: 🚕, + compactTrailing: {props.etaMinutes} min, + minimal: 🚕, + expandedLeading: ( + + 🚕 + + ), + expandedTrailing: + props.etaMinutes !== undefined ? ( + + + {props.etaMinutes} + + minutes + + ) : null, + expandedBottom: ( + + + {props.name} + {props.status} + + + + ), + }; +}; + +export default createLiveActivity("RiderActivity", RiderActivity); diff --git a/app/src/live-activities/rider-activity.web.tsx b/app/src/live-activities/rider-activity.web.tsx new file mode 100644 index 00000000..db3abfd4 --- /dev/null +++ b/app/src/live-activities/rider-activity.web.tsx @@ -0,0 +1,11 @@ +const RiderActivity = { + getInstances: () => [], + start: () => { + return { + addPushTokenListener: () => {}, + end: () => {}, + }; + }, +}; + +export default RiderActivity; diff --git a/app/src/live-activities/utils.ts b/app/src/live-activities/utils.ts new file mode 100644 index 00000000..f7bde8ac --- /dev/null +++ b/app/src/live-activities/utils.ts @@ -0,0 +1,67 @@ +import { RouterOutput, trpcClient } from "@/utils/trpc"; +import { getCurrentStatusMessage } from "@/utils/utils"; +import { LiveActivity, PushTokenEvent } from "expo-widgets"; +import RiderActivity, { + RiderActivityProps, +} from "@/live-activities/rider-activity"; + +type Beep = RouterOutput["rider"]["startBeep"]; + +let riderActivity: LiveActivity | null = + RiderActivity.getInstances()[0] ?? null; +let riderActivityTokenListener: { remove(): void } | null = null; + +async function handleRiderPushTokenUpdate(event: PushTokenEvent, beep: Beep) { + trpcClient.rider.setBeepLiveActivityToken + .mutate({ + beepId: beep.id, + token: event.pushToken, + }) + .then(() => { + alert( + `Live Activity Token sent to the API ${event.pushToken} from listener`, + ); + }); +} + +export async function ensureRiderLiveActivity(beep: Beep | null | undefined) { + if (beep === undefined) { + return; + } + + // If the user is not in a beep, ensure there is no live activity and listener + if (beep === null) { + if (riderActivityTokenListener) { + riderActivityTokenListener.remove(); + riderActivityTokenListener = null; + } + if (riderActivity) { + riderActivity.end("immediate"); + riderActivity = null; + } + return; + } + + if (riderActivity && !riderActivityTokenListener) { + // If there is an active live activity, but a listener is not setup, setup one. + // This is so that we continue listening for tokens after the app is killed and re-opened. + riderActivityTokenListener = riderActivity.addPushTokenListener((event) => + handleRiderPushTokenUpdate(event, beep), + ); + return; + } + + if (riderActivity && riderActivityTokenListener) { + return; + } + + // User is in a beep. Start a live activity and listen for token updates + riderActivity = RiderActivity.start({ + status: getCurrentStatusMessage(beep), + name: `${beep.beeper.first} ${beep.beeper.last}`, + }); + + riderActivityTokenListener = riderActivity.addPushTokenListener((event) => + handleRiderPushTokenUpdate(event, beep), + ); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ce10fa9..4a4b58e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,6 +40,9 @@ importers: ioredis: specifier: ^5.10.1 version: 5.10.1 + jose: + specifier: ^6.2.3 + version: 6.2.3 nodemailer: specifier: ^8.0.5 version: 8.0.5 @@ -4589,6 +4592,9 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + jose@6.2.3: + resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -11300,6 +11306,8 @@ snapshots: jiti@2.6.1: {} + jose@6.2.3: {} + js-tokens@4.0.0: {} js-yaml@3.14.2: