-
${escapeHtml(truncateText(result.image.description || 'Untitled', 60))}
- ${formatDate(result.image.created_at)}
+ ${formatDate(result.image.upload_date)}
${result.image.tags && result.image.tags.length > 0 ? `
diff --git a/deployment/terraform/pubsub.tf b/deployment/terraform/pubsub.tf
index 27f4134..4ed9393 100644
--- a/deployment/terraform/pubsub.tf
+++ b/deployment/terraform/pubsub.tf
@@ -1,3 +1,14 @@
+# Compute default service accounts
+locals {
+ cloud_run_service_account = var.cloud_run_service_account != "" ? var.cloud_run_service_account : "${data.google_project.current.number}-compute@developer.gserviceaccount.com"
+ cloud_function_service_account = var.cloud_function_service_account != "" ? var.cloud_function_service_account : "${data.google_project.current.number}-compute@developer.gserviceaccount.com"
+}
+
+# Get current project information
+data "google_project" "current" {
+ project_id = var.project_id
+}
+
# Pub/Sub topic for image processing tasks
resource "google_pubsub_topic" "image_processing" {
name = var.pubsub_topic_name
@@ -73,7 +84,7 @@ resource "google_pubsub_topic_iam_binding" "image_processing_publisher" {
role = "roles/pubsub.publisher"
members = [
- "serviceAccount:${var.cloud_run_service_account}",
+ "serviceAccount:${local.cloud_run_service_account}",
]
}
@@ -83,7 +94,7 @@ resource "google_pubsub_subscription_iam_binding" "image_processing_subscriber"
role = "roles/pubsub.subscriber"
members = [
- "serviceAccount:${var.cloud_function_service_account}",
+ "serviceAccount:${local.cloud_function_service_account}",
]
}
diff --git a/deployment/terraform/terraform.tfstate b/deployment/terraform/terraform.tfstate
index 84b0c9e..1349896 100644
--- a/deployment/terraform/terraform.tfstate
+++ b/deployment/terraform/terraform.tfstate
@@ -1,9 +1,371 @@
{
"version": 4,
"terraform_version": "1.10.1",
- "serial": 28,
+ "serial": 41,
"lineage": "a183cd95-f987-8698-c6dd-84e933c394a5",
- "outputs": {},
- "resources": [],
+ "outputs": {
+ "container_registry_url": {
+ "value": "gcr.io/gen-lang-client-0424120530/sereact",
+ "type": "string"
+ },
+ "pubsub_dlq_topic_name": {
+ "value": "image-processing-topic-dlq",
+ "type": "string"
+ },
+ "pubsub_subscription_name": {
+ "value": "image-processing-topic-subscription",
+ "type": "string"
+ },
+ "pubsub_topic_name": {
+ "value": "image-processing-topic",
+ "type": "string"
+ },
+ "storage_bucket_name": {
+ "value": "sereact-storage-bucket",
+ "type": "string"
+ }
+ },
+ "resources": [
+ {
+ "mode": "data",
+ "type": "google_project",
+ "name": "current",
+ "provider": "provider[\"registry.terraform.io/hashicorp/google\"]",
+ "instances": [
+ {
+ "schema_version": 0,
+ "attributes": {
+ "auto_create_network": null,
+ "billing_account": "00CA97-62E5BD-4A62B5",
+ "deletion_policy": "PREVENT",
+ "effective_labels": {
+ "generative-language": "enabled"
+ },
+ "folder_id": null,
+ "id": "projects/gen-lang-client-0424120530",
+ "labels": {
+ "generative-language": "enabled"
+ },
+ "name": "Gemini API",
+ "number": "761163285547",
+ "org_id": null,
+ "project_id": "gen-lang-client-0424120530",
+ "tags": null,
+ "terraform_labels": {
+ "generative-language": "enabled"
+ }
+ },
+ "sensitive_attributes": []
+ }
+ ]
+ },
+ {
+ "mode": "managed",
+ "type": "google_project_service",
+ "name": "services",
+ "provider": "provider[\"registry.terraform.io/hashicorp/google\"]",
+ "instances": [
+ {
+ "index_key": "cloudresourcemanager.googleapis.com",
+ "schema_version": 0,
+ "attributes": {
+ "disable_dependent_services": null,
+ "disable_on_destroy": false,
+ "id": "gen-lang-client-0424120530/cloudresourcemanager.googleapis.com",
+ "project": "gen-lang-client-0424120530",
+ "service": "cloudresourcemanager.googleapis.com",
+ "timeouts": null
+ },
+ "sensitive_attributes": [],
+ "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInJlYWQiOjYwMDAwMDAwMDAwMCwidXBkYXRlIjoxMjAwMDAwMDAwMDAwfX0="
+ },
+ {
+ "index_key": "containerregistry.googleapis.com",
+ "schema_version": 0,
+ "attributes": {
+ "disable_dependent_services": null,
+ "disable_on_destroy": false,
+ "id": "gen-lang-client-0424120530/containerregistry.googleapis.com",
+ "project": "gen-lang-client-0424120530",
+ "service": "containerregistry.googleapis.com",
+ "timeouts": null
+ },
+ "sensitive_attributes": [],
+ "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInJlYWQiOjYwMDAwMDAwMDAwMCwidXBkYXRlIjoxMjAwMDAwMDAwMDAwfX0="
+ },
+ {
+ "index_key": "firestore.googleapis.com",
+ "schema_version": 0,
+ "attributes": {
+ "disable_dependent_services": null,
+ "disable_on_destroy": false,
+ "id": "gen-lang-client-0424120530/firestore.googleapis.com",
+ "project": "gen-lang-client-0424120530",
+ "service": "firestore.googleapis.com",
+ "timeouts": null
+ },
+ "sensitive_attributes": [],
+ "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInJlYWQiOjYwMDAwMDAwMDAwMCwidXBkYXRlIjoxMjAwMDAwMDAwMDAwfX0="
+ },
+ {
+ "index_key": "run.googleapis.com",
+ "schema_version": 0,
+ "attributes": {
+ "disable_dependent_services": null,
+ "disable_on_destroy": false,
+ "id": "gen-lang-client-0424120530/run.googleapis.com",
+ "project": "gen-lang-client-0424120530",
+ "service": "run.googleapis.com",
+ "timeouts": null
+ },
+ "sensitive_attributes": [],
+ "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInJlYWQiOjYwMDAwMDAwMDAwMCwidXBkYXRlIjoxMjAwMDAwMDAwMDAwfX0="
+ },
+ {
+ "index_key": "storage.googleapis.com",
+ "schema_version": 0,
+ "attributes": {
+ "disable_dependent_services": null,
+ "disable_on_destroy": false,
+ "id": "gen-lang-client-0424120530/storage.googleapis.com",
+ "project": "gen-lang-client-0424120530",
+ "service": "storage.googleapis.com",
+ "timeouts": null
+ },
+ "sensitive_attributes": [],
+ "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInJlYWQiOjYwMDAwMDAwMDAwMCwidXBkYXRlIjoxMjAwMDAwMDAwMDAwfX0="
+ }
+ ]
+ },
+ {
+ "mode": "managed",
+ "type": "google_pubsub_subscription",
+ "name": "image_processing_dlq",
+ "provider": "provider[\"registry.terraform.io/hashicorp/google\"]",
+ "instances": [
+ {
+ "schema_version": 0,
+ "attributes": {
+ "ack_deadline_seconds": 10,
+ "bigquery_config": [],
+ "cloud_storage_config": [],
+ "dead_letter_policy": [],
+ "effective_labels": {
+ "component": "image-processing-dlq",
+ "environment": "dev",
+ "goog-terraform-provisioned": "true",
+ "service": "sereact"
+ },
+ "enable_exactly_once_delivery": false,
+ "enable_message_ordering": false,
+ "expiration_policy": [
+ {
+ "ttl": "2678400s"
+ }
+ ],
+ "filter": "",
+ "id": "projects/gen-lang-client-0424120530/subscriptions/image-processing-topic-dlq-subscription",
+ "labels": {
+ "component": "image-processing-dlq",
+ "environment": "dev",
+ "service": "sereact"
+ },
+ "message_retention_duration": "2592000s",
+ "name": "image-processing-topic-dlq-subscription",
+ "project": "gen-lang-client-0424120530",
+ "push_config": [],
+ "retain_acked_messages": true,
+ "retry_policy": [],
+ "terraform_labels": {
+ "component": "image-processing-dlq",
+ "environment": "dev",
+ "goog-terraform-provisioned": "true",
+ "service": "sereact"
+ },
+ "timeouts": null,
+ "topic": "projects/gen-lang-client-0424120530/topics/image-processing-topic-dlq"
+ },
+ "sensitive_attributes": [],
+ "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInVwZGF0ZSI6MTIwMDAwMDAwMDAwMH19",
+ "dependencies": [
+ "google_pubsub_topic.image_processing_dlq"
+ ]
+ }
+ ]
+ },
+ {
+ "mode": "managed",
+ "type": "google_pubsub_topic",
+ "name": "image_processing",
+ "provider": "provider[\"registry.terraform.io/hashicorp/google\"]",
+ "instances": [
+ {
+ "schema_version": 0,
+ "attributes": {
+ "effective_labels": {
+ "component": "image-processing",
+ "environment": "dev",
+ "goog-terraform-provisioned": "true",
+ "service": "sereact"
+ },
+ "id": "projects/gen-lang-client-0424120530/topics/image-processing-topic",
+ "ingestion_data_source_settings": [],
+ "kms_key_name": "",
+ "labels": {
+ "component": "image-processing",
+ "environment": "dev",
+ "service": "sereact"
+ },
+ "message_retention_duration": "",
+ "message_storage_policy": [],
+ "name": "image-processing-topic",
+ "project": "gen-lang-client-0424120530",
+ "schema_settings": [],
+ "terraform_labels": {
+ "component": "image-processing",
+ "environment": "dev",
+ "goog-terraform-provisioned": "true",
+ "service": "sereact"
+ },
+ "timeouts": null
+ },
+ "sensitive_attributes": [],
+ "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInVwZGF0ZSI6MTIwMDAwMDAwMDAwMH19"
+ }
+ ]
+ },
+ {
+ "mode": "managed",
+ "type": "google_pubsub_topic",
+ "name": "image_processing_dlq",
+ "provider": "provider[\"registry.terraform.io/hashicorp/google\"]",
+ "instances": [
+ {
+ "schema_version": 0,
+ "attributes": {
+ "effective_labels": {
+ "component": "image-processing-dlq",
+ "environment": "dev",
+ "goog-terraform-provisioned": "true",
+ "service": "sereact"
+ },
+ "id": "projects/gen-lang-client-0424120530/topics/image-processing-topic-dlq",
+ "ingestion_data_source_settings": [],
+ "kms_key_name": "",
+ "labels": {
+ "component": "image-processing-dlq",
+ "environment": "dev",
+ "service": "sereact"
+ },
+ "message_retention_duration": "",
+ "message_storage_policy": [],
+ "name": "image-processing-topic-dlq",
+ "project": "gen-lang-client-0424120530",
+ "schema_settings": [],
+ "terraform_labels": {
+ "component": "image-processing-dlq",
+ "environment": "dev",
+ "goog-terraform-provisioned": "true",
+ "service": "sereact"
+ },
+ "timeouts": null
+ },
+ "sensitive_attributes": [],
+ "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInVwZGF0ZSI6MTIwMDAwMDAwMDAwMH19"
+ }
+ ]
+ },
+ {
+ "mode": "managed",
+ "type": "google_pubsub_topic_iam_binding",
+ "name": "image_processing_publisher",
+ "provider": "provider[\"registry.terraform.io/hashicorp/google\"]",
+ "instances": [
+ {
+ "schema_version": 0,
+ "attributes": {
+ "condition": [],
+ "etag": "BwY14zaElfE=",
+ "id": "projects/gen-lang-client-0424120530/topics/image-processing-topic/roles/pubsub.publisher",
+ "members": [
+ "serviceAccount:761163285547-compute@developer.gserviceaccount.com"
+ ],
+ "project": "gen-lang-client-0424120530",
+ "role": "roles/pubsub.publisher",
+ "topic": "projects/gen-lang-client-0424120530/topics/image-processing-topic"
+ },
+ "sensitive_attributes": [],
+ "private": "bnVsbA==",
+ "dependencies": [
+ "data.google_project.current",
+ "google_pubsub_topic.image_processing"
+ ]
+ }
+ ]
+ },
+ {
+ "mode": "managed",
+ "type": "google_storage_bucket",
+ "name": "app_bucket",
+ "provider": "provider[\"registry.terraform.io/hashicorp/google\"]",
+ "instances": [
+ {
+ "schema_version": 3,
+ "attributes": {
+ "autoclass": [],
+ "cors": [],
+ "custom_placement_config": [],
+ "default_event_based_hold": false,
+ "effective_labels": {
+ "goog-terraform-provisioned": "true"
+ },
+ "enable_object_retention": false,
+ "encryption": [],
+ "force_destroy": false,
+ "hierarchical_namespace": [
+ {
+ "enabled": false
+ }
+ ],
+ "id": "sereact-storage-bucket",
+ "labels": null,
+ "lifecycle_rule": [],
+ "location": "US-CENTRAL1",
+ "logging": [],
+ "name": "sereact-storage-bucket",
+ "project": "gen-lang-client-0424120530",
+ "project_number": 761163285547,
+ "public_access_prevention": "inherited",
+ "requester_pays": false,
+ "retention_policy": [],
+ "rpo": null,
+ "self_link": "https://www.googleapis.com/storage/v1/b/sereact-storage-bucket",
+ "soft_delete_policy": [
+ {
+ "effective_time": "2025-05-24T15:17:40.181Z",
+ "retention_duration_seconds": 604800
+ }
+ ],
+ "storage_class": "STANDARD",
+ "terraform_labels": {
+ "goog-terraform-provisioned": "true"
+ },
+ "time_created": "2025-05-24T15:17:40.181Z",
+ "timeouts": null,
+ "uniform_bucket_level_access": true,
+ "updated": "2025-05-24T15:17:40.181Z",
+ "url": "gs://sereact-storage-bucket",
+ "versioning": [],
+ "website": []
+ },
+ "sensitive_attributes": [],
+ "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsInJlYWQiOjI0MDAwMDAwMDAwMCwidXBkYXRlIjoyNDAwMDAwMDAwMDB9LCJzY2hlbWFfdmVyc2lvbiI6IjMifQ==",
+ "dependencies": [
+ "google_project_service.services"
+ ]
+ }
+ ]
+ }
+ ],
"check_results": null
}
diff --git a/deployment/terraform/terraform.tfstate.backup b/deployment/terraform/terraform.tfstate.backup
index 9652ce7..84b0c9e 100644
--- a/deployment/terraform/terraform.tfstate.backup
+++ b/deployment/terraform/terraform.tfstate.backup
@@ -1,416 +1,9 @@
{
"version": 4,
"terraform_version": "1.10.1",
- "serial": 18,
+ "serial": 28,
"lineage": "a183cd95-f987-8698-c6dd-84e933c394a5",
- "outputs": {
- "cloud_run_url": {
- "value": "https://sereact-p64zpdtkta-uc.a.run.app",
- "type": "string"
- },
- "container_registry_url": {
- "value": "gcr.io/gen-lang-client-0424120530/sereact",
- "type": "string"
- },
- "firestore_database_id": {
- "value": "projects/gen-lang-client-0424120530/databases/imagedb",
- "type": "string"
- },
- "storage_bucket_name": {
- "value": "sereact-storage-bucket",
- "type": "string"
- }
- },
- "resources": [
- {
- "mode": "managed",
- "type": "google_cloud_run_service",
- "name": "sereact",
- "provider": "provider[\"registry.terraform.io/hashicorp/google\"]",
- "instances": [
- {
- "schema_version": 2,
- "attributes": {
- "autogenerate_revision_name": false,
- "id": "locations/us-central1/namespaces/gen-lang-client-0424120530/services/sereact",
- "location": "us-central1",
- "metadata": [
- {
- "annotations": {},
- "effective_annotations": {
- "run.googleapis.com/ingress": "all",
- "run.googleapis.com/ingress-status": "all",
- "run.googleapis.com/operation-id": "0982194f-b3e8-45b8-a33f-7ed5fd529307",
- "run.googleapis.com/urls": "[\"https://sereact-761163285547.us-central1.run.app\",\"https://sereact-p64zpdtkta-uc.a.run.app\"]",
- "serving.knative.dev/creator": "johnpccd3@gmail.com",
- "serving.knative.dev/lastModifier": "johnpccd3@gmail.com"
- },
- "effective_labels": {
- "cloud.googleapis.com/location": "us-central1"
- },
- "generation": 1,
- "labels": {},
- "namespace": "gen-lang-client-0424120530",
- "resource_version": "AAY108GXDwM",
- "self_link": "/apis/serving.knative.dev/v1/namespaces/761163285547/services/sereact",
- "terraform_labels": {},
- "uid": "5b695749-5095-4fb5-86bd-4d86f77dc7da"
- }
- ],
- "name": "sereact",
- "project": "gen-lang-client-0424120530",
- "status": [
- {
- "conditions": [
- {
- "message": "",
- "reason": "",
- "status": "True",
- "type": "Ready"
- },
- {
- "message": "",
- "reason": "",
- "status": "True",
- "type": "ConfigurationsReady"
- },
- {
- "message": "",
- "reason": "",
- "status": "True",
- "type": "RoutesReady"
- }
- ],
- "latest_created_revision_name": "sereact-00001-bt2",
- "latest_ready_revision_name": "sereact-00001-bt2",
- "observed_generation": 1,
- "traffic": [
- {
- "latest_revision": true,
- "percent": 100,
- "revision_name": "sereact-00001-bt2",
- "tag": "",
- "url": ""
- }
- ],
- "url": "https://sereact-p64zpdtkta-uc.a.run.app"
- }
- ],
- "template": [
- {
- "metadata": [
- {
- "annotations": {
- "autoscaling.knative.dev/maxScale": "3"
- },
- "generation": 0,
- "labels": {
- "run.googleapis.com/startupProbeType": "Default"
- },
- "name": "",
- "namespace": "",
- "resource_version": "",
- "self_link": "",
- "uid": ""
- }
- ],
- "spec": [
- {
- "container_concurrency": 80,
- "containers": [
- {
- "args": null,
- "command": null,
- "env": [
- {
- "name": "DATABASE_NAME",
- "value": "imagedb",
- "value_from": []
- },
- {
- "name": "GCS_BUCKET_NAME",
- "value": "sereact-storage-bucket",
- "value_from": []
- },
- {
- "name": "LOG_LEVEL",
- "value": "INFO",
- "value_from": []
- }
- ],
- "env_from": [],
- "image": "gcr.io/google-samples/hello-app:1.0",
- "liveness_probe": [],
- "name": "",
- "ports": [
- {
- "container_port": 8080,
- "name": "http1",
- "protocol": ""
- }
- ],
- "resources": [
- {
- "limits": {
- "cpu": "1",
- "memory": "512Mi"
- },
- "requests": null
- }
- ],
- "startup_probe": [
- {
- "failure_threshold": 1,
- "grpc": [],
- "http_get": [],
- "initial_delay_seconds": 0,
- "period_seconds": 240,
- "tcp_socket": [
- {
- "port": 8080
- }
- ],
- "timeout_seconds": 240
- }
- ],
- "volume_mounts": [],
- "working_dir": ""
- }
- ],
- "node_selector": null,
- "service_account_name": "761163285547-compute@developer.gserviceaccount.com",
- "serving_state": "",
- "timeout_seconds": 300,
- "volumes": []
- }
- ]
- }
- ],
- "timeouts": null,
- "traffic": [
- {
- "latest_revision": true,
- "percent": 100,
- "revision_name": "",
- "tag": "",
- "url": ""
- }
- ]
- },
- "sensitive_attributes": [],
- "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInVwZGF0ZSI6MTIwMDAwMDAwMDAwMH0sInNjaGVtYV92ZXJzaW9uIjoiMiJ9",
- "dependencies": [
- "google_project_service.services"
- ]
- }
- ]
- },
- {
- "mode": "managed",
- "type": "google_cloud_run_service_iam_member",
- "name": "public_access",
- "provider": "provider[\"registry.terraform.io/hashicorp/google\"]",
- "instances": [
- {
- "schema_version": 0,
- "attributes": {
- "condition": [],
- "etag": "BwY108GyUf0=",
- "id": "v1/projects/gen-lang-client-0424120530/locations/us-central1/services/sereact/roles/run.invoker/allUsers",
- "location": "us-central1",
- "member": "allUsers",
- "project": "gen-lang-client-0424120530",
- "role": "roles/run.invoker",
- "service": "v1/projects/gen-lang-client-0424120530/locations/us-central1/services/sereact"
- },
- "sensitive_attributes": [],
- "private": "bnVsbA==",
- "dependencies": [
- "google_cloud_run_service.sereact",
- "google_project_service.services"
- ]
- }
- ]
- },
- {
- "mode": "managed",
- "type": "google_firestore_database",
- "name": "database",
- "provider": "provider[\"registry.terraform.io/hashicorp/google\"]",
- "instances": [
- {
- "schema_version": 0,
- "attributes": {
- "app_engine_integration_mode": "DISABLED",
- "cmek_config": [],
- "concurrency_mode": "PESSIMISTIC",
- "create_time": "",
- "database_edition": "STANDARD",
- "delete_protection_state": "DELETE_PROTECTION_DISABLED",
- "deletion_policy": "ABANDON",
- "earliest_version_time": "2025-05-23T20:47:31.801717Z",
- "etag": "IIelp4e8uo0DMPX0nai7uo0D",
- "id": "projects/gen-lang-client-0424120530/databases/imagedb",
- "key_prefix": "",
- "location_id": "us-central1",
- "name": "imagedb",
- "point_in_time_recovery_enablement": "POINT_IN_TIME_RECOVERY_DISABLED",
- "project": "gen-lang-client-0424120530",
- "timeouts": null,
- "type": "FIRESTORE_NATIVE",
- "uid": "177e30f2-9b67-428c-9979-22e625b929c4",
- "update_time": "",
- "version_retention_period": "3600s"
- },
- "sensitive_attributes": [],
- "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInVwZGF0ZSI6MTIwMDAwMDAwMDAwMH19",
- "dependencies": [
- "google_project_service.services"
- ]
- }
- ]
- },
- {
- "mode": "managed",
- "type": "google_project_service",
- "name": "services",
- "provider": "provider[\"registry.terraform.io/hashicorp/google\"]",
- "instances": [
- {
- "index_key": "cloudresourcemanager.googleapis.com",
- "schema_version": 0,
- "attributes": {
- "disable_dependent_services": null,
- "disable_on_destroy": false,
- "id": "gen-lang-client-0424120530/cloudresourcemanager.googleapis.com",
- "project": "gen-lang-client-0424120530",
- "service": "cloudresourcemanager.googleapis.com",
- "timeouts": null
- },
- "sensitive_attributes": [],
- "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInJlYWQiOjYwMDAwMDAwMDAwMCwidXBkYXRlIjoxMjAwMDAwMDAwMDAwfX0="
- },
- {
- "index_key": "containerregistry.googleapis.com",
- "schema_version": 0,
- "attributes": {
- "disable_dependent_services": null,
- "disable_on_destroy": false,
- "id": "gen-lang-client-0424120530/containerregistry.googleapis.com",
- "project": "gen-lang-client-0424120530",
- "service": "containerregistry.googleapis.com",
- "timeouts": null
- },
- "sensitive_attributes": [],
- "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInJlYWQiOjYwMDAwMDAwMDAwMCwidXBkYXRlIjoxMjAwMDAwMDAwMDAwfX0="
- },
- {
- "index_key": "firestore.googleapis.com",
- "schema_version": 0,
- "attributes": {
- "disable_dependent_services": null,
- "disable_on_destroy": false,
- "id": "gen-lang-client-0424120530/firestore.googleapis.com",
- "project": "gen-lang-client-0424120530",
- "service": "firestore.googleapis.com",
- "timeouts": null
- },
- "sensitive_attributes": [],
- "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInJlYWQiOjYwMDAwMDAwMDAwMCwidXBkYXRlIjoxMjAwMDAwMDAwMDAwfX0="
- },
- {
- "index_key": "run.googleapis.com",
- "schema_version": 0,
- "attributes": {
- "disable_dependent_services": null,
- "disable_on_destroy": false,
- "id": "gen-lang-client-0424120530/run.googleapis.com",
- "project": "gen-lang-client-0424120530",
- "service": "run.googleapis.com",
- "timeouts": null
- },
- "sensitive_attributes": [],
- "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInJlYWQiOjYwMDAwMDAwMDAwMCwidXBkYXRlIjoxMjAwMDAwMDAwMDAwfX0="
- },
- {
- "index_key": "storage.googleapis.com",
- "schema_version": 0,
- "attributes": {
- "disable_dependent_services": null,
- "disable_on_destroy": false,
- "id": "gen-lang-client-0424120530/storage.googleapis.com",
- "project": "gen-lang-client-0424120530",
- "service": "storage.googleapis.com",
- "timeouts": null
- },
- "sensitive_attributes": [],
- "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjoxMjAwMDAwMDAwMDAwLCJkZWxldGUiOjEyMDAwMDAwMDAwMDAsInJlYWQiOjYwMDAwMDAwMDAwMCwidXBkYXRlIjoxMjAwMDAwMDAwMDAwfX0="
- }
- ]
- },
- {
- "mode": "managed",
- "type": "google_storage_bucket",
- "name": "app_bucket",
- "provider": "provider[\"registry.terraform.io/hashicorp/google\"]",
- "instances": [
- {
- "schema_version": 3,
- "attributes": {
- "autoclass": [],
- "cors": [],
- "custom_placement_config": [],
- "default_event_based_hold": false,
- "effective_labels": {
- "goog-terraform-provisioned": "true"
- },
- "enable_object_retention": false,
- "encryption": [],
- "force_destroy": false,
- "hierarchical_namespace": [
- {
- "enabled": false
- }
- ],
- "id": "sereact-storage-bucket",
- "labels": {},
- "lifecycle_rule": [],
- "location": "US-CENTRAL1",
- "logging": [],
- "name": "sereact-storage-bucket",
- "project": "gen-lang-client-0424120530",
- "project_number": 761163285547,
- "public_access_prevention": "inherited",
- "requester_pays": false,
- "retention_policy": [],
- "rpo": null,
- "self_link": "https://www.googleapis.com/storage/v1/b/sereact-storage-bucket",
- "soft_delete_policy": [
- {
- "effective_time": "2025-05-23T20:47:26.788Z",
- "retention_duration_seconds": 604800
- }
- ],
- "storage_class": "STANDARD",
- "terraform_labels": {
- "goog-terraform-provisioned": "true"
- },
- "time_created": "2025-05-23T20:47:26.788Z",
- "timeouts": null,
- "uniform_bucket_level_access": true,
- "updated": "2025-05-23T20:47:26.788Z",
- "url": "gs://sereact-storage-bucket",
- "versioning": [],
- "website": []
- },
- "sensitive_attributes": [],
- "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjo2MDAwMDAwMDAwMDAsInJlYWQiOjI0MDAwMDAwMDAwMCwidXBkYXRlIjoyNDAwMDAwMDAwMDB9LCJzY2hlbWFfdmVyc2lvbiI6IjMifQ==",
- "dependencies": [
- "google_project_service.services"
- ]
- }
- ]
- }
- ],
+ "outputs": {},
+ "resources": [],
"check_results": null
}
diff --git a/deployment/terraform/variables.tf b/deployment/terraform/variables.tf
index 120a5ae..ea8d231 100644
--- a/deployment/terraform/variables.tf
+++ b/deployment/terraform/variables.tf
@@ -41,9 +41,11 @@ variable "pubsub_topic_name" {
variable "cloud_run_service_account" {
description = "The service account email for Cloud Run"
type = string
+ default = ""
}
variable "cloud_function_service_account" {
description = "The service account email for Cloud Functions"
type = string
+ default = ""
}
\ No newline at end of file
diff --git a/src/api/v1/images.py b/src/api/v1/images.py
index 79240f1..be35d61 100644
--- a/src/api/v1/images.py
+++ b/src/api/v1/images.py
@@ -62,6 +62,9 @@ async def upload_image(
file, str(current_user.team_id)
)
+ # Generate public URL
+ public_url = storage_service.generate_public_url(storage_path)
+
# Process tags
tag_list = []
if tags:
@@ -74,6 +77,7 @@ async def upload_image(
file_size=file_size,
content_type=content_type,
storage_path=storage_path,
+ public_url=public_url,
team_id=current_user.team_id,
uploader_id=current_user.id,
description=description,
@@ -105,6 +109,7 @@ async def upload_image(
file_size=created_image.file_size,
content_type=created_image.content_type,
storage_path=created_image.storage_path,
+ public_url=created_image.public_url,
team_id=str(created_image.team_id),
uploader_id=str(created_image.uploader_id),
upload_date=created_image.upload_date,
@@ -163,6 +168,19 @@ async def list_images(
# Convert to response
response_images = []
for image in images:
+ # Generate public URL if not set
+ public_url = image.public_url
+ if not public_url and image.storage_path:
+ # Make the file public and generate URL
+ try:
+ storage_service.make_file_public(image.storage_path)
+ public_url = storage_service.generate_public_url(image.storage_path)
+ # Update the database with the public URL
+ await image_repository.update(image.id, {"public_url": public_url})
+ except Exception as e:
+ logger.warning(f"Failed to make file public or update URL for image {image.id}: {e}")
+ public_url = storage_service.generate_public_url(image.storage_path)
+
response_images.append(ImageResponse(
id=str(image.id),
filename=image.filename,
@@ -170,6 +188,7 @@ async def list_images(
file_size=image.file_size,
content_type=image.content_type,
storage_path=image.storage_path,
+ public_url=public_url,
team_id=str(image.team_id),
uploader_id=str(image.uploader_id),
upload_date=image.upload_date,
@@ -214,6 +233,19 @@ async def get_image(
# Update last accessed
await image_repository.update_last_accessed(obj_id)
+ # Generate public URL if not set
+ public_url = image.public_url
+ if not public_url and image.storage_path:
+ # Make the file public and generate URL
+ try:
+ storage_service.make_file_public(image.storage_path)
+ public_url = storage_service.generate_public_url(image.storage_path)
+ # Update the database with the public URL
+ await image_repository.update(image.id, {"public_url": public_url})
+ except Exception as e:
+ logger.warning(f"Failed to make file public or update URL for image {image.id}: {e}")
+ public_url = storage_service.generate_public_url(image.storage_path)
+
# Convert to response
response = ImageResponse(
id=str(image.id),
@@ -222,6 +254,7 @@ async def get_image(
file_size=image.file_size,
content_type=image.content_type,
storage_path=image.storage_path,
+ public_url=public_url,
team_id=str(image.team_id),
uploader_id=str(image.uploader_id),
upload_date=image.upload_date,
diff --git a/src/services/storage.py b/src/services/storage.py
index 0611dff..f398af7 100644
--- a/src/services/storage.py
+++ b/src/services/storage.py
@@ -122,7 +122,10 @@ class StorageService:
# Upload the file
blob.upload_from_string(content, content_type=content_type)
- logger.info(f"File uploaded: {storage_path}")
+ # Make the blob publicly readable
+ blob.make_public()
+
+ logger.info(f"File uploaded and made public: {storage_path}")
# Seek back to the beginning for future reads
await file.seek(0)
@@ -222,6 +225,29 @@ class StorageService:
except Exception as e:
logger.error(f"Error generating signed URL: {e}")
raise
+
+ def make_file_public(self, storage_path: str) -> bool:
+ """
+ Make a file publicly accessible
+
+ Args:
+ storage_path: Storage path of the file
+
+ Returns:
+ True if file was made public, False if not found
+ """
+ try:
+ blob = self.bucket.blob(storage_path)
+ if not blob.exists():
+ logger.warning(f"File not found for making public: {storage_path}")
+ return False
+
+ blob.make_public()
+ logger.info(f"File made public: {storage_path}")
+ return True
+ except Exception as e:
+ logger.error(f"Error making file public: {e}")
+ raise
# Create a singleton service
storage_service = StorageService()
\ No newline at end of file
diff --git a/test_images_api.py b/test_images_api.py
new file mode 100644
index 0000000..ae45b2e
--- /dev/null
+++ b/test_images_api.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python3
+
+import requests
+import json
+
+# Test the images API
+def test_images_api():
+ base_url = "http://localhost:8000"
+
+ # First, let's check if we need to bootstrap or if there are existing API keys
+ try:
+ # Try to get images without API key first to see the error
+ response = requests.get(f"{base_url}/api/v1/images")
+ print(f"Images API without auth: {response.status_code}")
+ print(f"Response: {response.text[:200]}...")
+
+ except Exception as e:
+ print(f"Error testing images API: {e}")
+
+if __name__ == "__main__":
+ test_images_api()
\ No newline at end of file