diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock new file mode 100644 index 000000000..47b6a5022 --- /dev/null +++ b/MODULE.bazel.lock @@ -0,0 +1,1604 @@ +{ + "lockFileVersion": 3, + "moduleFileHash": "29a4baeb93c5035b39c3916b8375fa537395f30fc770cb11631f8a0fb1267b16", + "flags": { + "cmdRegistries": [ + "https://bcr.bazel.build/" + ], + "cmdModuleOverrides": {}, + "allowedYankedVersions": [], + "envVarAllowedYankedVersions": "", + "ignoreDevDependency": false, + "directDependenciesMode": "WARNING", + "compatibilityMode": "ERROR" + }, + "localOverrideHashes": { + "bazel_tools": "922ea6752dc9105de5af957f7a99a6933c0a6a712d23df6aad16a9c399f7e787" + }, + "moduleDepGraph": { + "": { + "name": "veza", + "version": "0.0.1", + "key": "", + "repoName": "veza", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [], + "extensionUsages": [ + { + "extensionBzlFile": "@gazelle//:extensions.bzl", + "extensionName": "go_deps", + "usingModule": "", + "location": { + "file": "@@//:MODULE.bazel", + "line": 13, + "column": 24 + }, + "imports": { + "bazel_gazelle_go_repository_config": "bazel_gazelle_go_repository_config", + "cat_dario_mergo": "cat_dario_mergo", + "com_github_adalogics_go_fuzz_headers": "com_github_adalogics_go_fuzz_headers", + "com_github_adamkorcz_go_118_fuzz_build": "com_github_adamkorcz_go_118_fuzz_build", + "com_github_alecthomas_kingpin_v2": "com_github_alecthomas_kingpin_v2", + "com_github_alecthomas_units": "com_github_alecthomas_units", + "com_github_aws_aws_sdk_go_v2": "com_github_aws_aws_sdk_go_v2", + "com_github_aws_aws_sdk_go_v2_aws_protocol_eventstream": "com_github_aws_aws_sdk_go_v2_aws_protocol_eventstream", + "com_github_aws_aws_sdk_go_v2_config": "com_github_aws_aws_sdk_go_v2_config", + "com_github_aws_aws_sdk_go_v2_credentials": "com_github_aws_aws_sdk_go_v2_credentials", + "com_github_aws_aws_sdk_go_v2_feature_ec2_imds": "com_github_aws_aws_sdk_go_v2_feature_ec2_imds", + "com_github_aws_aws_sdk_go_v2_feature_s3_manager": "com_github_aws_aws_sdk_go_v2_feature_s3_manager", + "com_github_aws_aws_sdk_go_v2_internal_configsources": "com_github_aws_aws_sdk_go_v2_internal_configsources", + "com_github_aws_aws_sdk_go_v2_internal_endpoints_v2": "com_github_aws_aws_sdk_go_v2_internal_endpoints_v2", + "com_github_aws_aws_sdk_go_v2_internal_ini": "com_github_aws_aws_sdk_go_v2_internal_ini", + "com_github_aws_aws_sdk_go_v2_internal_v4a": "com_github_aws_aws_sdk_go_v2_internal_v4a", + "com_github_aws_aws_sdk_go_v2_service_internal_accept_encoding": "com_github_aws_aws_sdk_go_v2_service_internal_accept_encoding", + "com_github_aws_aws_sdk_go_v2_service_internal_checksum": "com_github_aws_aws_sdk_go_v2_service_internal_checksum", + "com_github_aws_aws_sdk_go_v2_service_internal_presigned_url": "com_github_aws_aws_sdk_go_v2_service_internal_presigned_url", + "com_github_aws_aws_sdk_go_v2_service_internal_s3shared": "com_github_aws_aws_sdk_go_v2_service_internal_s3shared", + "com_github_aws_aws_sdk_go_v2_service_s3": "com_github_aws_aws_sdk_go_v2_service_s3", + "com_github_aws_aws_sdk_go_v2_service_signin": "com_github_aws_aws_sdk_go_v2_service_signin", + "com_github_aws_aws_sdk_go_v2_service_sso": "com_github_aws_aws_sdk_go_v2_service_sso", + "com_github_aws_aws_sdk_go_v2_service_ssooidc": "com_github_aws_aws_sdk_go_v2_service_ssooidc", + "com_github_aws_aws_sdk_go_v2_service_sts": "com_github_aws_aws_sdk_go_v2_service_sts", + "com_github_aws_smithy_go": "com_github_aws_smithy_go", + "com_github_azure_go_ansiterm": "com_github_azure_go_ansiterm", + "com_github_beorn7_perks": "com_github_beorn7_perks", + "com_github_blang_semver_v4": "com_github_blang_semver_v4", + "com_github_boombuler_barcode": "com_github_boombuler_barcode", + "com_github_bsm_ginkgo_v2": "com_github_bsm_ginkgo_v2", + "com_github_bsm_gomega": "com_github_bsm_gomega", + "com_github_bytedance_sonic": "com_github_bytedance_sonic", + "com_github_cenkalti_backoff_v4": "com_github_cenkalti_backoff_v4", + "com_github_cespare_xxhash_v2": "com_github_cespare_xxhash_v2", + "com_github_chenzhuoyu_base64x": "com_github_chenzhuoyu_base64x", + "com_github_cilium_ebpf": "com_github_cilium_ebpf", + "com_github_containerd_aufs": "com_github_containerd_aufs", + "com_github_containerd_btrfs_v2": "com_github_containerd_btrfs_v2", + "com_github_containerd_cgroups": "com_github_containerd_cgroups", + "com_github_containerd_cgroups_v3": "com_github_containerd_cgroups_v3", + "com_github_containerd_console": "com_github_containerd_console", + "com_github_containerd_containerd": "com_github_containerd_containerd", + "com_github_containerd_continuity": "com_github_containerd_continuity", + "com_github_containerd_errdefs": "com_github_containerd_errdefs", + "com_github_containerd_fifo": "com_github_containerd_fifo", + "com_github_containerd_go_cni": "com_github_containerd_go_cni", + "com_github_containerd_go_runc": "com_github_containerd_go_runc", + "com_github_containerd_imgcrypt": "com_github_containerd_imgcrypt", + "com_github_containerd_log": "com_github_containerd_log", + "com_github_containerd_nri": "com_github_containerd_nri", + "com_github_containerd_platforms": "com_github_containerd_platforms", + "com_github_containerd_ttrpc": "com_github_containerd_ttrpc", + "com_github_containerd_typeurl": "com_github_containerd_typeurl", + "com_github_containerd_typeurl_v2": "com_github_containerd_typeurl_v2", + "com_github_containerd_zfs": "com_github_containerd_zfs", + "com_github_containernetworking_cni": "com_github_containernetworking_cni", + "com_github_containernetworking_plugins": "com_github_containernetworking_plugins", + "com_github_containers_ocicrypt": "com_github_containers_ocicrypt", + "com_github_coreos_go_systemd_v22": "com_github_coreos_go_systemd_v22", + "com_github_cpuguy83_dockercfg": "com_github_cpuguy83_dockercfg", + "com_github_cpuguy83_go_md2man_v2": "com_github_cpuguy83_go_md2man_v2", + "com_github_creack_pty": "com_github_creack_pty", + "com_github_data_dog_go_sqlmock": "com_github_data_dog_go_sqlmock", + "com_github_davecgh_go_spew": "com_github_davecgh_go_spew", + "com_github_dgryski_go_rendezvous": "com_github_dgryski_go_rendezvous", + "com_github_dhowden_itl": "com_github_dhowden_itl", + "com_github_dhowden_plist": "com_github_dhowden_plist", + "com_github_dhowden_tag": "com_github_dhowden_tag", + "com_github_disintegration_imaging": "com_github_disintegration_imaging", + "com_github_distribution_reference": "com_github_distribution_reference", + "com_github_docker_docker": "com_github_docker_docker", + "com_github_docker_go_connections": "com_github_docker_go_connections", + "com_github_docker_go_events": "com_github_docker_go_events", + "com_github_docker_go_metrics": "com_github_docker_go_metrics", + "com_github_docker_go_units": "com_github_docker_go_units", + "com_github_dutchcoders_go_clamd": "com_github_dutchcoders_go_clamd", + "com_github_emicklei_go_restful_v3": "com_github_emicklei_go_restful_v3", + "com_github_felixge_httpsnoop": "com_github_felixge_httpsnoop", + "com_github_fsnotify_fsnotify": "com_github_fsnotify_fsnotify", + "com_github_gabriel_vasile_mimetype": "com_github_gabriel_vasile_mimetype", + "com_github_getsentry_sentry_go": "com_github_getsentry_sentry_go", + "com_github_gin_contrib_gzip": "com_github_gin_contrib_gzip", + "com_github_gin_contrib_sse": "com_github_gin_contrib_sse", + "com_github_gin_gonic_gin": "com_github_gin_gonic_gin", + "com_github_goccy_go_json": "com_github_goccy_go_json", + "com_github_godbus_dbus_v5": "com_github_godbus_dbus_v5", + "com_github_go_errors_errors": "com_github_go_errors_errors", + "com_github_gogo_protobuf": "com_github_gogo_protobuf", + "com_github_go_jose_go_jose_v3": "com_github_go_jose_go_jose_v3", + "com_github_golang_groupcache": "com_github_golang_groupcache", + "com_github_golang_jwt_jwt_v5": "com_github_golang_jwt_jwt_v5", + "com_github_golang_protobuf": "com_github_golang_protobuf", + "com_github_go_logr_logr": "com_github_go_logr_logr", + "com_github_go_logr_stdr": "com_github_go_logr_stdr", + "com_github_google_go_cmp": "com_github_google_go_cmp", + "com_github_google_gofuzz": "com_github_google_gofuzz", + "com_github_google_uuid": "com_github_google_uuid", + "com_github_go_ole_go_ole": "com_github_go_ole_go_ole", + "com_github_go_openapi_jsonpointer": "com_github_go_openapi_jsonpointer", + "com_github_go_openapi_jsonreference": "com_github_go_openapi_jsonreference", + "com_github_go_openapi_spec": "com_github_go_openapi_spec", + "com_github_go_openapi_swag": "com_github_go_openapi_swag", + "com_github_go_playground_assert_v2": "com_github_go_playground_assert_v2", + "com_github_go_playground_locales": "com_github_go_playground_locales", + "com_github_go_playground_universal_translator": "com_github_go_playground_universal_translator", + "com_github_go_playground_validator_v10": "com_github_go_playground_validator_v10", + "com_github_gorilla_websocket": "com_github_gorilla_websocket", + "com_github_grpc_ecosystem_go_grpc_middleware": "com_github_grpc_ecosystem_go_grpc_middleware", + "com_github_grpc_ecosystem_go_grpc_prometheus": "com_github_grpc_ecosystem_go_grpc_prometheus", + "com_github_grpc_ecosystem_grpc_gateway_v2": "com_github_grpc_ecosystem_grpc_gateway_v2", + "com_github_hashicorp_errwrap": "com_github_hashicorp_errwrap", + "com_github_hashicorp_go_multierror": "com_github_hashicorp_go_multierror", + "com_github_intel_goresctrl": "com_github_intel_goresctrl", + "com_github_jackc_pgpassfile": "com_github_jackc_pgpassfile", + "com_github_jackc_pgservicefile": "com_github_jackc_pgservicefile", + "com_github_jackc_pgx_v5": "com_github_jackc_pgx_v5", + "com_github_jackc_puddle_v2": "com_github_jackc_puddle_v2", + "com_github_jinzhu_inflection": "com_github_jinzhu_inflection", + "com_github_jinzhu_now": "com_github_jinzhu_now", + "com_github_joho_godotenv": "com_github_joho_godotenv", + "com_github_josharian_intern": "com_github_josharian_intern", + "com_github_jpillora_backoff": "com_github_jpillora_backoff", + "com_github_json_iterator_go": "com_github_json_iterator_go", + "com_github_julienschmidt_httprouter": "com_github_julienschmidt_httprouter", + "com_github_kisielk_errcheck": "com_github_kisielk_errcheck", + "com_github_kisielk_gotool": "com_github_kisielk_gotool", + "com_github_kisielk_sqlstruct": "com_github_kisielk_sqlstruct", + "com_github_klauspost_compress": "com_github_klauspost_compress", + "com_github_klauspost_cpuid_v2": "com_github_klauspost_cpuid_v2", + "com_github_kr_pretty": "com_github_kr_pretty", + "com_github_kr_pty": "com_github_kr_pty", + "com_github_kr_text": "com_github_kr_text", + "com_github_kylebanks_depth": "com_github_kylebanks_depth", + "com_github_kylelemons_godebug": "com_github_kylelemons_godebug", + "com_github_leodido_go_urn": "com_github_leodido_go_urn", + "com_github_lib_pq": "com_github_lib_pq", + "com_github_lufia_plan9stats": "com_github_lufia_plan9stats", + "com_github_magiconair_properties": "com_github_magiconair_properties", + "com_github_mailru_easyjson": "com_github_mailru_easyjson", + "com_github_mattn_go_isatty": "com_github_mattn_go_isatty", + "com_github_mattn_go_sqlite3": "com_github_mattn_go_sqlite3", + "com_github_matttproud_golang_protobuf_extensions": "com_github_matttproud_golang_protobuf_extensions", + "com_github_microsoft_go_winio": "com_github_microsoft_go_winio", + "com_github_microsoft_hcsshim": "com_github_microsoft_hcsshim", + "com_github_miekg_pkcs11": "com_github_miekg_pkcs11", + "com_github_minio_sha256_simd": "com_github_minio_sha256_simd", + "com_github_mistifyio_go_zfs_v3": "com_github_mistifyio_go_zfs_v3", + "com_github_moby_docker_image_spec": "com_github_moby_docker_image_spec", + "com_github_moby_locker": "com_github_moby_locker", + "com_github_moby_patternmatcher": "com_github_moby_patternmatcher", + "com_github_moby_spdystream": "com_github_moby_spdystream", + "com_github_moby_sys_mountinfo": "com_github_moby_sys_mountinfo", + "com_github_moby_sys_sequential": "com_github_moby_sys_sequential", + "com_github_moby_sys_signal": "com_github_moby_sys_signal", + "com_github_moby_sys_symlink": "com_github_moby_sys_symlink", + "com_github_moby_sys_user": "com_github_moby_sys_user", + "com_github_moby_term": "com_github_moby_term", + "com_github_modern_go_concurrent": "com_github_modern_go_concurrent", + "com_github_modern_go_reflect2": "com_github_modern_go_reflect2", + "com_github_morikuni_aec": "com_github_morikuni_aec", + "com_github_munnerz_goautoneg": "com_github_munnerz_goautoneg", + "com_github_mwitkow_go_conntrack": "com_github_mwitkow_go_conntrack", + "com_github_niemeyer_pretty": "com_github_niemeyer_pretty", + "com_github_opencontainers_go_digest": "com_github_opencontainers_go_digest", + "com_github_opencontainers_image_spec": "com_github_opencontainers_image_spec", + "com_github_opencontainers_runtime_spec": "com_github_opencontainers_runtime_spec", + "com_github_opencontainers_runtime_tools": "com_github_opencontainers_runtime_tools", + "com_github_opencontainers_selinux": "com_github_opencontainers_selinux", + "com_github_pelletier_go_toml": "com_github_pelletier_go_toml", + "com_github_pelletier_go_toml_v2": "com_github_pelletier_go_toml_v2", + "com_github_pingcap_errors": "com_github_pingcap_errors", + "com_github_pkg_errors": "com_github_pkg_errors", + "com_github_pmezard_go_difflib": "com_github_pmezard_go_difflib", + "com_github_power_devops_perfstat": "com_github_power_devops_perfstat", + "com_github_pquerna_otp": "com_github_pquerna_otp", + "com_github_prometheus_client_golang": "com_github_prometheus_client_golang", + "com_github_prometheus_client_model": "com_github_prometheus_client_model", + "com_github_prometheus_common": "com_github_prometheus_common", + "com_github_prometheus_procfs": "com_github_prometheus_procfs", + "com_github_puerkitobio_purell": "com_github_puerkitobio_purell", + "com_github_puerkitobio_urlesc": "com_github_puerkitobio_urlesc", + "com_github_rabbitmq_amqp091_go": "com_github_rabbitmq_amqp091_go", + "com_github_redis_go_redis_v9": "com_github_redis_go_redis_v9", + "com_github_rogpeppe_go_internal": "com_github_rogpeppe_go_internal", + "com_github_russross_blackfriday": "com_github_russross_blackfriday", + "com_github_russross_blackfriday_v2": "com_github_russross_blackfriday_v2", + "com_github_shirou_gopsutil_v3": "com_github_shirou_gopsutil_v3", + "com_github_shoenig_go_m1cpu": "com_github_shoenig_go_m1cpu", + "com_github_shoenig_test": "com_github_shoenig_test", + "com_github_shurcool_sanitized_anchor_name": "com_github_shurcool_sanitized_anchor_name", + "com_github_sirupsen_logrus": "com_github_sirupsen_logrus", + "com_github_sony_gobreaker": "com_github_sony_gobreaker", + "com_github_spf13_pflag": "com_github_spf13_pflag", + "com_github_stefanberger_go_pkcs11uri": "com_github_stefanberger_go_pkcs11uri", + "com_github_stretchr_objx": "com_github_stretchr_objx", + "com_github_stretchr_testify": "com_github_stretchr_testify", + "com_github_swaggo_files": "com_github_swaggo_files", + "com_github_swaggo_gin_swagger": "com_github_swaggo_gin_swagger", + "com_github_swaggo_swag": "com_github_swaggo_swag", + "com_github_syndtr_gocapability": "com_github_syndtr_gocapability", + "com_github_tchap_go_patricia_v2": "com_github_tchap_go_patricia_v2", + "com_github_testcontainers_testcontainers_go": "com_github_testcontainers_testcontainers_go", + "com_github_testcontainers_testcontainers_go_modules_postgres": "com_github_testcontainers_testcontainers_go_modules_postgres", + "com_github_tklauser_go_sysconf": "com_github_tklauser_go_sysconf", + "com_github_tklauser_numcpus": "com_github_tklauser_numcpus", + "com_github_twitchyliquid64_golang_asm": "com_github_twitchyliquid64_golang_asm", + "com_github_ugorji_go_codec": "com_github_ugorji_go_codec", + "com_github_urfave_cli": "com_github_urfave_cli", + "com_github_urfave_cli_v2": "com_github_urfave_cli_v2", + "com_github_vishvananda_netlink": "com_github_vishvananda_netlink", + "com_github_vishvananda_netns": "com_github_vishvananda_netns", + "com_github_xeipuuv_gojsonpointer": "com_github_xeipuuv_gojsonpointer", + "com_github_xeipuuv_gojsonreference": "com_github_xeipuuv_gojsonreference", + "com_github_xeipuuv_gojsonschema": "com_github_xeipuuv_gojsonschema", + "com_github_xhit_go_str2duration_v2": "com_github_xhit_go_str2duration_v2", + "com_github_yuin_goldmark": "com_github_yuin_goldmark", + "com_github_yusufpapurcu_wmi": "com_github_yusufpapurcu_wmi", + "com_google_cloud_go_compute_metadata": "com_google_cloud_go_compute_metadata", + "in_gopkg_check_v1": "in_gopkg_check_v1", + "in_gopkg_inf_v0": "in_gopkg_inf_v0", + "in_gopkg_natefinch_lumberjack_v2": "in_gopkg_natefinch_lumberjack_v2", + "in_gopkg_yaml_v2": "in_gopkg_yaml_v2", + "in_gopkg_yaml_v3": "in_gopkg_yaml_v3", + "io_cncf_tags_container_device_interface": "io_cncf_tags_container_device_interface", + "io_cncf_tags_container_device_interface_specs_go": "io_cncf_tags_container_device_interface_specs_go", + "io_etcd_go_bbolt": "io_etcd_go_bbolt", + "io_gorm_driver_postgres": "io_gorm_driver_postgres", + "io_gorm_driver_sqlite": "io_gorm_driver_sqlite", + "io_gorm_gorm": "io_gorm_gorm", + "io_k8s_api": "io_k8s_api", + "io_k8s_apimachinery": "io_k8s_apimachinery", + "io_k8s_apiserver": "io_k8s_apiserver", + "io_k8s_client_go": "io_k8s_client_go", + "io_k8s_component_base": "io_k8s_component_base", + "io_k8s_cri_api": "io_k8s_cri_api", + "io_k8s_klog_v2": "io_k8s_klog_v2", + "io_k8s_sigs_json": "io_k8s_sigs_json", + "io_k8s_sigs_structured_merge_diff_v4": "io_k8s_sigs_structured_merge_diff_v4", + "io_k8s_sigs_yaml": "io_k8s_sigs_yaml", + "io_k8s_utils": "io_k8s_utils", + "io_opencensus_go": "io_opencensus_go", + "io_opentelemetry_go_contrib_instrumentation_google_golang_org_grpc_otelgrpc": "io_opentelemetry_go_contrib_instrumentation_google_golang_org_grpc_otelgrpc", + "io_opentelemetry_go_contrib_instrumentation_net_http_otelhttp": "io_opentelemetry_go_contrib_instrumentation_net_http_otelhttp", + "io_opentelemetry_go_otel": "io_opentelemetry_go_otel", + "io_opentelemetry_go_otel_exporters_otlp_otlptrace": "io_opentelemetry_go_otel_exporters_otlp_otlptrace", + "io_opentelemetry_go_otel_exporters_otlp_otlptrace_otlptracegrpc": "io_opentelemetry_go_otel_exporters_otlp_otlptrace_otlptracegrpc", + "io_opentelemetry_go_otel_exporters_otlp_otlptrace_otlptracehttp": "io_opentelemetry_go_otel_exporters_otlp_otlptrace_otlptracehttp", + "io_opentelemetry_go_otel_metric": "io_opentelemetry_go_otel_metric", + "io_opentelemetry_go_otel_sdk": "io_opentelemetry_go_otel_sdk", + "io_opentelemetry_go_otel_trace": "io_opentelemetry_go_otel_trace", + "io_opentelemetry_go_proto_otlp": "io_opentelemetry_go_proto_otlp", + "io_rsc_pdf": "io_rsc_pdf", + "org_golang_google_appengine": "org_golang_google_appengine", + "org_golang_google_genproto": "org_golang_google_genproto", + "org_golang_google_genproto_googleapis_api": "org_golang_google_genproto_googleapis_api", + "org_golang_google_genproto_googleapis_rpc": "org_golang_google_genproto_googleapis_rpc", + "org_golang_google_grpc": "org_golang_google_grpc", + "org_golang_google_protobuf": "org_golang_google_protobuf", + "org_golang_x_arch": "org_golang_x_arch", + "org_golang_x_crypto": "org_golang_x_crypto", + "org_golang_x_image": "org_golang_x_image", + "org_golang_x_mod": "org_golang_x_mod", + "org_golang_x_net": "org_golang_x_net", + "org_golang_x_oauth2": "org_golang_x_oauth2", + "org_golang_x_sync": "org_golang_x_sync", + "org_golang_x_sys": "org_golang_x_sys", + "org_golang_x_telemetry": "org_golang_x_telemetry", + "org_golang_x_term": "org_golang_x_term", + "org_golang_x_text": "org_golang_x_text", + "org_golang_x_time": "org_golang_x_time", + "org_golang_x_tools": "org_golang_x_tools", + "org_golang_x_xerrors": "org_golang_x_xerrors", + "org_mozilla_go_pkcs7": "org_mozilla_go_pkcs7", + "org_uber_go_goleak": "org_uber_go_goleak", + "org_uber_go_multierr": "org_uber_go_multierr", + "org_uber_go_zap": "org_uber_go_zap", + "tools_gotest_v3": "tools_gotest_v3" + }, + "devImports": [], + "tags": [ + { + "tagName": "from_file", + "attributeValues": { + "go_mod": "//veza-backend-api:go.mod" + }, + "devDependency": false, + "location": { + "file": "@@//:MODULE.bazel", + "line": 14, + "column": 18 + } + } + ], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + } + ], + "deps": { + "gazelle": "gazelle@0.47.0", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + } + }, + "gazelle@0.47.0": { + "name": "gazelle", + "version": "0.47.0", + "key": "gazelle@0.47.0", + "repoName": "bazel_gazelle", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [], + "extensionUsages": [ + { + "extensionBzlFile": "@io_bazel_rules_go//go:extensions.bzl", + "extensionName": "go_sdk", + "usingModule": "gazelle@0.47.0", + "location": { + "file": "https://bcr.bazel.build/modules/gazelle/0.47.0/MODULE.bazel", + "line": 18, + "column": 23 + }, + "imports": { + "go_host_compatible_sdk_label": "go_host_compatible_sdk_label" + }, + "devImports": [], + "tags": [], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + }, + { + "extensionBzlFile": "@bazel_gazelle//internal/bzlmod:non_module_deps.bzl", + "extensionName": "non_module_deps", + "usingModule": "gazelle@0.47.0", + "location": { + "file": "https://bcr.bazel.build/modules/gazelle/0.47.0/MODULE.bazel", + "line": 26, + "column": 32 + }, + "imports": { + "bazel_gazelle_go_repository_cache": "bazel_gazelle_go_repository_cache", + "bazel_gazelle_go_repository_tools": "bazel_gazelle_go_repository_tools", + "bazel_gazelle_is_bazel_module": "bazel_gazelle_is_bazel_module" + }, + "devImports": [], + "tags": [], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + }, + { + "extensionBzlFile": "@bazel_gazelle//:extensions.bzl", + "extensionName": "go_deps", + "usingModule": "gazelle@0.47.0", + "location": { + "file": "https://bcr.bazel.build/modules/gazelle/0.47.0/MODULE.bazel", + "line": 34, + "column": 24 + }, + "imports": { + "com_github_bazelbuild_buildtools": "com_github_bazelbuild_buildtools", + "com_github_bmatcuk_doublestar_v4": "com_github_bmatcuk_doublestar_v4", + "com_github_fsnotify_fsnotify": "com_github_fsnotify_fsnotify", + "com_github_google_go_cmp": "com_github_google_go_cmp", + "com_github_pmezard_go_difflib": "com_github_pmezard_go_difflib", + "org_golang_x_mod": "org_golang_x_mod", + "org_golang_x_sync": "org_golang_x_sync", + "org_golang_x_tools_go_vcs": "org_golang_x_tools_go_vcs", + "bazel_gazelle_go_repository_config": "bazel_gazelle_go_repository_config", + "com_github_golang_protobuf": "com_github_golang_protobuf", + "org_golang_google_protobuf": "org_golang_google_protobuf" + }, + "devImports": [], + "tags": [ + { + "tagName": "from_file", + "attributeValues": { + "go_mod": "//:go.mod" + }, + "devDependency": false, + "location": { + "file": "https://bcr.bazel.build/modules/gazelle/0.47.0/MODULE.bazel", + "line": 35, + "column": 18 + } + } + ], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + } + ], + "deps": { + "bazel_features": "bazel_features@1.19.0", + "bazel_skylib": "bazel_skylib@1.7.1", + "com_google_protobuf": "protobuf@27.0", + "rules_cc": "rules_cc@0.0.17", + "io_bazel_rules_go": "rules_go@0.53.0", + "rules_license": "rules_license@1.0.0", + "package_metadata": "package_metadata@0.0.5", + "rules_proto": "rules_proto@6.0.0", + "rules_shell": "rules_shell@0.3.0", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "gazelle~0.47.0", + "urls": [ + "https://github.com/bazel-contrib/bazel-gazelle/releases/download/v0.47.0/bazel-gazelle-v0.47.0.tar.gz" + ], + "integrity": "sha256-Z1EU2LQz0Kn1TYEXGDO+luvEETEVZkt5Hm8gTVjpNEY=", + "strip_prefix": "", + "remote_patches": { + "https://bcr.bazel.build/modules/gazelle/0.47.0/patches/module_dot_bazel_version.patch": "sha256-VwEoxf/HO2EKSmBo3LmLab0MvUF39UWDxF0hC6+kmWQ=" + }, + "remote_patch_strip": 1 + } + } + }, + "bazel_tools@_": { + "name": "bazel_tools", + "version": "", + "key": "bazel_tools@_", + "repoName": "bazel_tools", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [ + "@local_config_cc_toolchains//:all", + "@local_config_sh//:local_sh_toolchain" + ], + "extensionUsages": [ + { + "extensionBzlFile": "@bazel_tools//tools/cpp:cc_configure.bzl", + "extensionName": "cc_configure_extension", + "usingModule": "bazel_tools@_", + "location": { + "file": "@@bazel_tools//:MODULE.bazel", + "line": 17, + "column": 29 + }, + "imports": { + "local_config_cc": "local_config_cc", + "local_config_cc_toolchains": "local_config_cc_toolchains" + }, + "devImports": [], + "tags": [], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + }, + { + "extensionBzlFile": "@bazel_tools//tools/osx:xcode_configure.bzl", + "extensionName": "xcode_configure_extension", + "usingModule": "bazel_tools@_", + "location": { + "file": "@@bazel_tools//:MODULE.bazel", + "line": 21, + "column": 32 + }, + "imports": { + "local_config_xcode": "local_config_xcode" + }, + "devImports": [], + "tags": [], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + }, + { + "extensionBzlFile": "@rules_java//java:extensions.bzl", + "extensionName": "toolchains", + "usingModule": "bazel_tools@_", + "location": { + "file": "@@bazel_tools//:MODULE.bazel", + "line": 24, + "column": 32 + }, + "imports": { + "local_jdk": "local_jdk", + "remote_java_tools": "remote_java_tools", + "remote_java_tools_linux": "remote_java_tools_linux", + "remote_java_tools_windows": "remote_java_tools_windows", + "remote_java_tools_darwin_x86_64": "remote_java_tools_darwin_x86_64", + "remote_java_tools_darwin_arm64": "remote_java_tools_darwin_arm64" + }, + "devImports": [], + "tags": [], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + }, + { + "extensionBzlFile": "@bazel_tools//tools/sh:sh_configure.bzl", + "extensionName": "sh_configure_extension", + "usingModule": "bazel_tools@_", + "location": { + "file": "@@bazel_tools//:MODULE.bazel", + "line": 35, + "column": 39 + }, + "imports": { + "local_config_sh": "local_config_sh" + }, + "devImports": [], + "tags": [], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + }, + { + "extensionBzlFile": "@bazel_tools//tools/test:extensions.bzl", + "extensionName": "remote_coverage_tools_extension", + "usingModule": "bazel_tools@_", + "location": { + "file": "@@bazel_tools//:MODULE.bazel", + "line": 39, + "column": 48 + }, + "imports": { + "remote_coverage_tools": "remote_coverage_tools" + }, + "devImports": [], + "tags": [], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + }, + { + "extensionBzlFile": "@bazel_tools//tools/android:android_extensions.bzl", + "extensionName": "remote_android_tools_extensions", + "usingModule": "bazel_tools@_", + "location": { + "file": "@@bazel_tools//:MODULE.bazel", + "line": 42, + "column": 42 + }, + "imports": { + "android_gmaven_r8": "android_gmaven_r8", + "android_tools": "android_tools" + }, + "devImports": [], + "tags": [], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + } + ], + "deps": { + "rules_cc": "rules_cc@0.0.17", + "rules_java": "rules_java@7.1.0", + "rules_license": "rules_license@1.0.0", + "rules_proto": "rules_proto@6.0.0", + "rules_python": "rules_python@0.10.2", + "platforms": "platforms@0.0.10", + "com_google_protobuf": "protobuf@27.0", + "zlib": "zlib@1.3", + "build_bazel_apple_support": "apple_support@1.5.0", + "local_config_platform": "local_config_platform@_" + } + }, + "local_config_platform@_": { + "name": "local_config_platform", + "version": "", + "key": "local_config_platform@_", + "repoName": "local_config_platform", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [], + "extensionUsages": [], + "deps": { + "platforms": "platforms@0.0.10", + "bazel_tools": "bazel_tools@_" + } + }, + "bazel_features@1.19.0": { + "name": "bazel_features", + "version": "1.19.0", + "key": "bazel_features@1.19.0", + "repoName": "bazel_features", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [], + "extensionUsages": [ + { + "extensionBzlFile": "@bazel_features//private:extensions.bzl", + "extensionName": "version_extension", + "usingModule": "bazel_features@1.19.0", + "location": { + "file": "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel", + "line": 15, + "column": 24 + }, + "imports": { + "bazel_features_globals": "bazel_features_globals", + "bazel_features_version": "bazel_features_version" + }, + "devImports": [], + "tags": [], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + } + ], + "deps": { + "bazel_skylib": "bazel_skylib@1.7.1", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "bazel_features~1.19.0", + "urls": [ + "https://github.com/bazel-contrib/bazel_features/releases/download/v1.19.0/bazel_features-v1.19.0.tar.gz" + ], + "integrity": "sha256-Nkb/1Ed1NJC3fSOA+mP01V3Zci5WXYTf2gFTa0jhg9o=", + "strip_prefix": "bazel_features-1.19.0", + "remote_patches": { + "https://bcr.bazel.build/modules/bazel_features/1.19.0/patches/module_dot_bazel_version.patch": "sha256-PYPpjeCJB6UPdLGFi3WQTUZg3QcWYGizPZCv0UydI8M=" + }, + "remote_patch_strip": 1 + } + } + }, + "bazel_skylib@1.7.1": { + "name": "bazel_skylib", + "version": "1.7.1", + "key": "bazel_skylib@1.7.1", + "repoName": "bazel_skylib", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [ + "//toolchains/unittest:cmd_toolchain", + "//toolchains/unittest:bash_toolchain" + ], + "extensionUsages": [], + "deps": { + "platforms": "platforms@0.0.10", + "rules_license": "rules_license@1.0.0", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "bazel_skylib~1.7.1", + "urls": [ + "https://github.com/bazelbuild/bazel-skylib/releases/download/1.7.1/bazel-skylib-1.7.1.tar.gz" + ], + "integrity": "sha256-vCg8381SalLDIBJ5zaS8KYZS76iYsQtNsIN9xRZSdW8=", + "strip_prefix": "", + "remote_patches": {}, + "remote_patch_strip": 0 + } + } + }, + "protobuf@27.0": { + "name": "protobuf", + "version": "27.0", + "key": "protobuf@27.0", + "repoName": "com_google_protobuf", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [], + "extensionUsages": [], + "deps": { + "com_google_absl": "abseil-cpp@20230802.0.bcr.1", + "bazel_skylib": "bazel_skylib@1.7.1", + "jsoncpp": "jsoncpp@1.9.5", + "rules_cc": "rules_cc@0.0.17", + "rules_java": "rules_java@7.1.0", + "rules_jvm_external": "rules_jvm_external@5.1", + "rules_pkg": "rules_pkg@0.7.0", + "rules_python": "rules_python@0.10.2", + "platforms": "platforms@0.0.10", + "zlib": "zlib@1.3", + "rules_proto": "rules_proto@6.0.0", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "protobuf~27.0", + "urls": [ + "https://github.com/protocolbuffers/protobuf/releases/download/v27.0/protobuf-27.0.zip" + ], + "integrity": "sha256-PhFI2wkP8hImwYiO85+nvHeQBCviH/Qon9Ic4XNfNFU=", + "strip_prefix": "protobuf-27.0", + "remote_patches": {}, + "remote_patch_strip": 0 + } + } + }, + "rules_cc@0.0.17": { + "name": "rules_cc", + "version": "0.0.17", + "key": "rules_cc@0.0.17", + "repoName": "rules_cc", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [ + "@local_config_cc_toolchains//:all" + ], + "extensionUsages": [ + { + "extensionBzlFile": "@rules_cc//cc:extensions.bzl", + "extensionName": "cc_configure_extension", + "usingModule": "rules_cc@0.0.17", + "location": { + "file": "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel", + "line": 12, + "column": 29 + }, + "imports": { + "local_config_cc": "local_config_cc", + "local_config_cc_toolchains": "local_config_cc_toolchains" + }, + "devImports": [], + "tags": [], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + } + ], + "deps": { + "bazel_features": "bazel_features@1.19.0", + "bazel_skylib": "bazel_skylib@1.7.1", + "platforms": "platforms@0.0.10", + "com_google_protobuf": "protobuf@27.0", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "rules_cc~0.0.17", + "urls": [ + "https://github.com/bazelbuild/rules_cc/releases/download/0.0.17/rules_cc-0.0.17.tar.gz" + ], + "integrity": "sha256-q8YF3YUPgTuzcAS3fbIBBqGTEalrLaHJK3idpSnSj+E=", + "strip_prefix": "rules_cc-0.0.17", + "remote_patches": { + "https://bcr.bazel.build/modules/rules_cc/0.0.17/patches/module_dot_bazel_version.patch": "sha256-sfVrLZhaiHSPIY77QupwdEJksH2dHNAaq9lFTB60OYI=" + }, + "remote_patch_strip": 1 + } + } + }, + "rules_go@0.53.0": { + "name": "rules_go", + "version": "0.53.0", + "key": "rules_go@0.53.0", + "repoName": "io_bazel_rules_go", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [ + "@go_toolchains//:all" + ], + "extensionUsages": [ + { + "extensionBzlFile": "@io_bazel_rules_go//go:extensions.bzl", + "extensionName": "go_sdk", + "usingModule": "rules_go@0.53.0", + "location": { + "file": "https://bcr.bazel.build/modules/rules_go/0.53.0/MODULE.bazel", + "line": 18, + "column": 23 + }, + "imports": { + "go_host_compatible_sdk_label": "go_host_compatible_sdk_label", + "go_toolchains": "go_toolchains", + "io_bazel_rules_nogo": "io_bazel_rules_nogo" + }, + "devImports": [], + "tags": [ + { + "tagName": "download", + "attributeValues": { + "name": "go_default_sdk", + "version": "1.22.7" + }, + "devDependency": false, + "location": { + "file": "https://bcr.bazel.build/modules/rules_go/0.53.0/MODULE.bazel", + "line": 19, + "column": 16 + } + } + ], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + }, + { + "extensionBzlFile": "@gazelle//:extensions.bzl", + "extensionName": "go_deps", + "usingModule": "rules_go@0.53.0", + "location": { + "file": "https://bcr.bazel.build/modules/rules_go/0.53.0/MODULE.bazel", + "line": 35, + "column": 24 + }, + "imports": { + "com_github_gogo_protobuf": "com_github_gogo_protobuf", + "com_github_golang_mock": "com_github_golang_mock", + "com_github_golang_protobuf": "com_github_golang_protobuf", + "com_github_pmezard_go_difflib": "com_github_pmezard_go_difflib", + "org_golang_google_genproto": "org_golang_google_genproto", + "org_golang_google_grpc": "org_golang_google_grpc", + "org_golang_google_grpc_cmd_protoc_gen_go_grpc": "org_golang_google_grpc_cmd_protoc_gen_go_grpc", + "org_golang_google_protobuf": "org_golang_google_protobuf", + "org_golang_x_net": "org_golang_x_net", + "org_golang_x_tools": "org_golang_x_tools", + "bazel_gazelle_go_repository_config": "bazel_gazelle_go_repository_config" + }, + "devImports": [], + "tags": [ + { + "tagName": "from_file", + "attributeValues": { + "go_mod": "//:go.mod" + }, + "devDependency": false, + "location": { + "file": "https://bcr.bazel.build/modules/rules_go/0.53.0/MODULE.bazel", + "line": 36, + "column": 18 + } + } + ], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + } + ], + "deps": { + "io_bazel_rules_go_bazel_features": "bazel_features@1.19.0", + "bazel_skylib": "bazel_skylib@1.7.1", + "platforms": "platforms@0.0.10", + "rules_proto": "rules_proto@6.0.0", + "com_google_protobuf": "protobuf@27.0", + "rules_shell": "rules_shell@0.3.0", + "gazelle": "gazelle@0.47.0", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "rules_go~0.53.0", + "urls": [ + "https://github.com/bazel-contrib/rules_go/releases/download/v0.53.0/rules_go-v0.53.0.zip" + ], + "integrity": "sha256-t493RY53Fi9FtFZNayC2+S9WQx7VnqqrCeeBnR2FAxM=", + "strip_prefix": "", + "remote_patches": { + "https://bcr.bazel.build/modules/rules_go/0.53.0/patches/module_dot_bazel_version.patch": "sha256-9ERDhxlMpIRIsm1YsK0B+6biqo+37Wz4u8oQ6XeQi8Q=" + }, + "remote_patch_strip": 1 + } + } + }, + "rules_license@1.0.0": { + "name": "rules_license", + "version": "1.0.0", + "key": "rules_license@1.0.0", + "repoName": "rules_license", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [], + "extensionUsages": [], + "deps": { + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "rules_license~1.0.0", + "urls": [ + "https://github.com/bazelbuild/rules_license/releases/download/1.0.0/rules_license-1.0.0.tar.gz" + ], + "integrity": "sha256-JtQCH2iY4juC75UweDid1JrCtWGKxWSt5O+HzO0Uezg=", + "strip_prefix": "", + "remote_patches": {}, + "remote_patch_strip": 0 + } + } + }, + "package_metadata@0.0.5": { + "name": "package_metadata", + "version": "0.0.5", + "key": "package_metadata@0.0.5", + "repoName": "package_metadata", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [], + "extensionUsages": [], + "deps": { + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "package_metadata~0.0.5", + "urls": [ + "https://github.com/bazel-contrib/supply-chain/releases/download/v0.0.5/supply-chain-v0.0.5.tar.gz" + ], + "integrity": "sha256-Se0R5da3UsVfpTnLsQsnNpdPNHsIHXvVAKgNrLfb7AY=", + "strip_prefix": "supply-chain-0.0.5/metadata", + "remote_patches": {}, + "remote_patch_strip": 0 + } + } + }, + "rules_proto@6.0.0": { + "name": "rules_proto", + "version": "6.0.0", + "key": "rules_proto@6.0.0", + "repoName": "rules_proto", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [], + "extensionUsages": [], + "deps": { + "rules_license": "rules_license@1.0.0", + "bazel_skylib": "bazel_skylib@1.7.1", + "bazel_features": "bazel_features@1.19.0", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "rules_proto~6.0.0", + "urls": [ + "https://github.com/bazelbuild/rules_proto/releases/download/6.0.0/rules_proto-6.0.0.tar.gz" + ], + "integrity": "sha256-MD6G5yKlIPbzJqULQc/Ba5j+bRlVzkZkKlt6Z8EcD10=", + "strip_prefix": "rules_proto-6.0.0", + "remote_patches": { + "https://bcr.bazel.build/modules/rules_proto/6.0.0/patches/module_dot_bazel_version.patch": "sha256-fjQjxMdkMeumhvx9JdFSYeHH+Ex4TaTXNFMi554NF8E=" + }, + "remote_patch_strip": 1 + } + } + }, + "rules_shell@0.3.0": { + "name": "rules_shell", + "version": "0.3.0", + "key": "rules_shell@0.3.0", + "repoName": "rules_shell", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [ + "@local_config_shell//:all" + ], + "extensionUsages": [ + { + "extensionBzlFile": "@rules_shell//shell/private/extensions:sh_configure.bzl", + "extensionName": "sh_configure", + "usingModule": "rules_shell@0.3.0", + "location": { + "file": "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel", + "line": 10, + "column": 29 + }, + "imports": { + "local_config_shell": "local_config_shell" + }, + "devImports": [], + "tags": [], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + } + ], + "deps": { + "bazel_features": "bazel_features@1.19.0", + "bazel_skylib": "bazel_skylib@1.7.1", + "platforms": "platforms@0.0.10", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "rules_shell~0.3.0", + "urls": [ + "https://github.com/bazelbuild/rules_shell/releases/download/v0.3.0/rules_shell-v0.3.0.tar.gz" + ], + "integrity": "sha256-2M1KOpH8HcaNTH1rZV8J3vEJ9xhkN+P1Cptgq0NqDFM=", + "strip_prefix": "rules_shell-0.3.0", + "remote_patches": { + "https://bcr.bazel.build/modules/rules_shell/0.3.0/patches/module_dot_bazel_version.patch": "sha256-EmJAIbA/eRUtmeJTyvxoadXCXqGv5+NfMx2LAlAy+2Q=" + }, + "remote_patch_strip": 1 + } + } + }, + "rules_java@7.1.0": { + "name": "rules_java", + "version": "7.1.0", + "key": "rules_java@7.1.0", + "repoName": "rules_java", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [ + "//toolchains:all", + "@local_jdk//:runtime_toolchain_definition", + "@local_jdk//:bootstrap_runtime_toolchain_definition", + "@remotejdk11_linux_toolchain_config_repo//:all", + "@remotejdk11_linux_aarch64_toolchain_config_repo//:all", + "@remotejdk11_linux_ppc64le_toolchain_config_repo//:all", + "@remotejdk11_linux_s390x_toolchain_config_repo//:all", + "@remotejdk11_macos_toolchain_config_repo//:all", + "@remotejdk11_macos_aarch64_toolchain_config_repo//:all", + "@remotejdk11_win_toolchain_config_repo//:all", + "@remotejdk11_win_arm64_toolchain_config_repo//:all", + "@remotejdk17_linux_toolchain_config_repo//:all", + "@remotejdk17_linux_aarch64_toolchain_config_repo//:all", + "@remotejdk17_linux_ppc64le_toolchain_config_repo//:all", + "@remotejdk17_linux_s390x_toolchain_config_repo//:all", + "@remotejdk17_macos_toolchain_config_repo//:all", + "@remotejdk17_macos_aarch64_toolchain_config_repo//:all", + "@remotejdk17_win_toolchain_config_repo//:all", + "@remotejdk17_win_arm64_toolchain_config_repo//:all", + "@remotejdk21_linux_toolchain_config_repo//:all", + "@remotejdk21_linux_aarch64_toolchain_config_repo//:all", + "@remotejdk21_macos_toolchain_config_repo//:all", + "@remotejdk21_macos_aarch64_toolchain_config_repo//:all", + "@remotejdk21_win_toolchain_config_repo//:all" + ], + "extensionUsages": [ + { + "extensionBzlFile": "@rules_java//java:extensions.bzl", + "extensionName": "toolchains", + "usingModule": "rules_java@7.1.0", + "location": { + "file": "https://bcr.bazel.build/modules/rules_java/7.1.0/MODULE.bazel", + "line": 19, + "column": 27 + }, + "imports": { + "remote_java_tools": "remote_java_tools", + "remote_java_tools_linux": "remote_java_tools_linux", + "remote_java_tools_windows": "remote_java_tools_windows", + "remote_java_tools_darwin_x86_64": "remote_java_tools_darwin_x86_64", + "remote_java_tools_darwin_arm64": "remote_java_tools_darwin_arm64", + "local_jdk": "local_jdk", + "remotejdk11_linux_toolchain_config_repo": "remotejdk11_linux_toolchain_config_repo", + "remotejdk11_linux_aarch64_toolchain_config_repo": "remotejdk11_linux_aarch64_toolchain_config_repo", + "remotejdk11_linux_ppc64le_toolchain_config_repo": "remotejdk11_linux_ppc64le_toolchain_config_repo", + "remotejdk11_linux_s390x_toolchain_config_repo": "remotejdk11_linux_s390x_toolchain_config_repo", + "remotejdk11_macos_toolchain_config_repo": "remotejdk11_macos_toolchain_config_repo", + "remotejdk11_macos_aarch64_toolchain_config_repo": "remotejdk11_macos_aarch64_toolchain_config_repo", + "remotejdk11_win_toolchain_config_repo": "remotejdk11_win_toolchain_config_repo", + "remotejdk11_win_arm64_toolchain_config_repo": "remotejdk11_win_arm64_toolchain_config_repo", + "remotejdk17_linux_toolchain_config_repo": "remotejdk17_linux_toolchain_config_repo", + "remotejdk17_linux_aarch64_toolchain_config_repo": "remotejdk17_linux_aarch64_toolchain_config_repo", + "remotejdk17_linux_ppc64le_toolchain_config_repo": "remotejdk17_linux_ppc64le_toolchain_config_repo", + "remotejdk17_linux_s390x_toolchain_config_repo": "remotejdk17_linux_s390x_toolchain_config_repo", + "remotejdk17_macos_toolchain_config_repo": "remotejdk17_macos_toolchain_config_repo", + "remotejdk17_macos_aarch64_toolchain_config_repo": "remotejdk17_macos_aarch64_toolchain_config_repo", + "remotejdk17_win_toolchain_config_repo": "remotejdk17_win_toolchain_config_repo", + "remotejdk17_win_arm64_toolchain_config_repo": "remotejdk17_win_arm64_toolchain_config_repo", + "remotejdk21_linux_toolchain_config_repo": "remotejdk21_linux_toolchain_config_repo", + "remotejdk21_linux_aarch64_toolchain_config_repo": "remotejdk21_linux_aarch64_toolchain_config_repo", + "remotejdk21_macos_toolchain_config_repo": "remotejdk21_macos_toolchain_config_repo", + "remotejdk21_macos_aarch64_toolchain_config_repo": "remotejdk21_macos_aarch64_toolchain_config_repo", + "remotejdk21_win_toolchain_config_repo": "remotejdk21_win_toolchain_config_repo" + }, + "devImports": [], + "tags": [], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + } + ], + "deps": { + "platforms": "platforms@0.0.10", + "rules_cc": "rules_cc@0.0.17", + "bazel_skylib": "bazel_skylib@1.7.1", + "rules_proto": "rules_proto@6.0.0", + "rules_license": "rules_license@1.0.0", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "rules_java~7.1.0", + "urls": [ + "https://github.com/bazelbuild/rules_java/releases/download/7.1.0/rules_java-7.1.0.tar.gz" + ], + "integrity": "sha256-o3pOX2OrgnFuXdau75iO2EYcegC46TYnImKJn1h81OE=", + "strip_prefix": "", + "remote_patches": {}, + "remote_patch_strip": 0 + } + } + }, + "rules_python@0.10.2": { + "name": "rules_python", + "version": "0.10.2", + "key": "rules_python@0.10.2", + "repoName": "rules_python", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [ + "@bazel_tools//tools/python:autodetecting_toolchain" + ], + "extensionUsages": [ + { + "extensionBzlFile": "@rules_python//python:extensions.bzl", + "extensionName": "pip_install", + "usingModule": "rules_python@0.10.2", + "location": { + "file": "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel", + "line": 7, + "column": 28 + }, + "imports": { + "pypi__click": "pypi__click", + "pypi__colorama": "pypi__colorama", + "pypi__installer": "pypi__installer", + "pypi__pep517": "pypi__pep517", + "pypi__pip": "pypi__pip", + "pypi__pip_tools": "pypi__pip_tools", + "pypi__setuptools": "pypi__setuptools", + "pypi__tomli": "pypi__tomli", + "pypi__wheel": "pypi__wheel" + }, + "devImports": [], + "tags": [], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + } + ], + "deps": { + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "rules_python~0.10.2", + "urls": [ + "https://github.com/bazelbuild/rules_python/archive/refs/tags/0.10.2.tar.gz" + ], + "integrity": "sha256-o6bpn0l74In4HsCCiC5AJGv9Q19S9OgvN+iUSbBFc/Y=", + "strip_prefix": "rules_python-0.10.2", + "remote_patches": { + "https://bcr.bazel.build/modules/rules_python/0.10.2/patches/module_dot_bazel.patch": "sha256-TScILAmXmmMtjJIwhLrgNZgqGPs6G3OAzXaLXLDNFrA=" + }, + "remote_patch_strip": 0 + } + } + }, + "platforms@0.0.10": { + "name": "platforms", + "version": "0.0.10", + "key": "platforms@0.0.10", + "repoName": "platforms", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [], + "extensionUsages": [ + { + "extensionBzlFile": "@platforms//host:extension.bzl", + "extensionName": "host_platform", + "usingModule": "platforms@0.0.10", + "location": { + "file": "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel", + "line": 9, + "column": 30 + }, + "imports": { + "host_platform": "host_platform" + }, + "devImports": [], + "tags": [], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + } + ], + "deps": { + "rules_license": "rules_license@1.0.0", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "platforms", + "urls": [ + "https://github.com/bazelbuild/platforms/releases/download/0.0.10/platforms-0.0.10.tar.gz" + ], + "integrity": "sha256-IY7+juc20mo1cmY7N0olPAErcW2K8MB+hC6C8jigp+4=", + "strip_prefix": "", + "remote_patches": {}, + "remote_patch_strip": 0 + } + } + }, + "zlib@1.3": { + "name": "zlib", + "version": "1.3", + "key": "zlib@1.3", + "repoName": "zlib", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [], + "extensionUsages": [], + "deps": { + "platforms": "platforms@0.0.10", + "rules_cc": "rules_cc@0.0.17", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "zlib~1.3", + "urls": [ + "https://github.com/madler/zlib/releases/download/v1.3/zlib-1.3.tar.gz" + ], + "integrity": "sha256-/wukwpIBPbwnUws6geH5qBPNOd4Byl4Pi/NVcC76WT4=", + "strip_prefix": "zlib-1.3", + "remote_patches": { + "https://bcr.bazel.build/modules/zlib/1.3/patches/add_build_file.patch": "sha256-Ei+FYaaOo7A3jTKunMEodTI0Uw5NXQyZEcboMC8JskY=", + "https://bcr.bazel.build/modules/zlib/1.3/patches/module_dot_bazel.patch": "sha256-fPWLM+2xaF/kuy+kZc1YTfW6hNjrkG400Ho7gckuyJk=" + }, + "remote_patch_strip": 0 + } + } + }, + "apple_support@1.5.0": { + "name": "apple_support", + "version": "1.5.0", + "key": "apple_support@1.5.0", + "repoName": "build_bazel_apple_support", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [ + "@local_config_apple_cc_toolchains//:all" + ], + "extensionUsages": [ + { + "extensionBzlFile": "@build_bazel_apple_support//crosstool:setup.bzl", + "extensionName": "apple_cc_configure_extension", + "usingModule": "apple_support@1.5.0", + "location": { + "file": "https://bcr.bazel.build/modules/apple_support/1.5.0/MODULE.bazel", + "line": 17, + "column": 35 + }, + "imports": { + "local_config_apple_cc": "local_config_apple_cc", + "local_config_apple_cc_toolchains": "local_config_apple_cc_toolchains" + }, + "devImports": [], + "tags": [], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + } + ], + "deps": { + "bazel_skylib": "bazel_skylib@1.7.1", + "platforms": "platforms@0.0.10", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "apple_support~1.5.0", + "urls": [ + "https://github.com/bazelbuild/apple_support/releases/download/1.5.0/apple_support.1.5.0.tar.gz" + ], + "integrity": "sha256-miM41vja0yRPgj8txghKA+TQ+7J8qJLclw5okNW0gYQ=", + "strip_prefix": "", + "remote_patches": {}, + "remote_patch_strip": 0 + } + } + }, + "abseil-cpp@20230802.0.bcr.1": { + "name": "abseil-cpp", + "version": "20230802.0.bcr.1", + "key": "abseil-cpp@20230802.0.bcr.1", + "repoName": "abseil-cpp", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [], + "extensionUsages": [], + "deps": { + "rules_cc": "rules_cc@0.0.17", + "platforms": "platforms@0.0.10", + "bazel_skylib": "bazel_skylib@1.7.1", + "com_google_googletest": "googletest@1.14.0", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "abseil-cpp~20230802.0.bcr.1", + "urls": [ + "https://github.com/abseil/abseil-cpp/archive/refs/tags/20230802.0.tar.gz" + ], + "integrity": "sha256-WdKXavnW7PABqBo1dJpuVRozW5SdNJGM+t4Hc3udk8U=", + "strip_prefix": "abseil-cpp-20230802.0", + "remote_patches": { + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/patches/module_dot_bazel.patch": "sha256-Ku6wJ7uNqANq3fVmW03ySSUddevratZDsJ67NqtXIEY=" + }, + "remote_patch_strip": 0 + } + } + }, + "jsoncpp@1.9.5": { + "name": "jsoncpp", + "version": "1.9.5", + "key": "jsoncpp@1.9.5", + "repoName": "jsoncpp", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [], + "extensionUsages": [], + "deps": { + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "jsoncpp~1.9.5", + "urls": [ + "https://github.com/open-source-parsers/jsoncpp/archive/refs/tags/1.9.5.tar.gz" + ], + "integrity": "sha256-9AmFblkgwY0ML7hSduJO5gfSoJtefV8KNxNokDwnXaI=", + "strip_prefix": "jsoncpp-1.9.5", + "remote_patches": { + "https://bcr.bazel.build/modules/jsoncpp/1.9.5/patches/build_dot_bazel.patch": "sha256-Vj8diXSWps8I8h5cdEqBDYmKBA2ulvWxMZBEQlIgcpU=", + "https://bcr.bazel.build/modules/jsoncpp/1.9.5/patches/module_dot_bazel.patch": "sha256-7RC7fS8N11vcyeDEaUZ05yBqr0YY7OzuzqaWz5W2XDo=" + }, + "remote_patch_strip": 1 + } + } + }, + "rules_jvm_external@5.1": { + "name": "rules_jvm_external", + "version": "5.1", + "key": "rules_jvm_external@5.1", + "repoName": "rules_jvm_external", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [], + "extensionUsages": [ + { + "extensionBzlFile": "@rules_jvm_external//:non-module-deps.bzl", + "extensionName": "non_module_deps", + "usingModule": "rules_jvm_external@5.1", + "location": { + "file": "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel", + "line": 9, + "column": 32 + }, + "imports": { + "io_bazel_rules_kotlin": "io_bazel_rules_kotlin" + }, + "devImports": [], + "tags": [], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + }, + { + "extensionBzlFile": ":extensions.bzl", + "extensionName": "maven", + "usingModule": "rules_jvm_external@5.1", + "location": { + "file": "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel", + "line": 16, + "column": 22 + }, + "imports": { + "rules_jvm_external_deps": "rules_jvm_external_deps" + }, + "devImports": [], + "tags": [ + { + "tagName": "install", + "attributeValues": { + "name": "rules_jvm_external_deps", + "artifacts": [ + "com.google.auth:google-auth-library-credentials:0.22.0", + "com.google.auth:google-auth-library-oauth2-http:0.22.0", + "com.google.cloud:google-cloud-core:1.93.10", + "com.google.cloud:google-cloud-storage:1.113.4", + "com.google.code.gson:gson:2.9.0", + "com.google.googlejavaformat:google-java-format:1.15.0", + "com.google.guava:guava:31.1-jre", + "org.apache.maven:maven-artifact:3.8.6", + "software.amazon.awssdk:s3:2.17.183" + ], + "lock_file": "@rules_jvm_external//:rules_jvm_external_deps_install.json" + }, + "devDependency": false, + "location": { + "file": "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel", + "line": 18, + "column": 14 + } + } + ], + "hasDevUseExtension": false, + "hasNonDevUseExtension": true + } + ], + "deps": { + "bazel_skylib": "bazel_skylib@1.7.1", + "io_bazel_stardoc": "stardoc@0.5.3", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "rules_jvm_external~5.1", + "urls": [ + "https://github.com/bazelbuild/rules_jvm_external/releases/download/5.1/rules_jvm_external-5.1.tar.gz" + ], + "integrity": "sha256-jDsgdyLl+X8cgzEVgqbBHfmSJuZeJHEIbillYeV8yVQ=", + "strip_prefix": "rules_jvm_external-5.1", + "remote_patches": {}, + "remote_patch_strip": 0 + } + } + }, + "rules_pkg@0.7.0": { + "name": "rules_pkg", + "version": "0.7.0", + "key": "rules_pkg@0.7.0", + "repoName": "rules_pkg", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [], + "extensionUsages": [], + "deps": { + "rules_python": "rules_python@0.10.2", + "bazel_skylib": "bazel_skylib@1.7.1", + "rules_license": "rules_license@1.0.0", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "rules_pkg~0.7.0", + "urls": [ + "https://github.com/bazelbuild/rules_pkg/releases/download/0.7.0/rules_pkg-0.7.0.tar.gz" + ], + "integrity": "sha256-iimOgydi7aGDBZfWT+fbWBeKqEzVkm121bdE1lWJQcI=", + "strip_prefix": "", + "remote_patches": { + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/patches/module_dot_bazel.patch": "sha256-4OaEPZwYF6iC71ZTDg6MJ7LLqX7ZA0/kK4mT+4xKqiE=" + }, + "remote_patch_strip": 0 + } + } + }, + "googletest@1.14.0": { + "name": "googletest", + "version": "1.14.0", + "key": "googletest@1.14.0", + "repoName": "googletest", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [], + "extensionUsages": [], + "deps": { + "com_google_absl": "abseil-cpp@20230802.0.bcr.1", + "platforms": "platforms@0.0.10", + "rules_cc": "rules_cc@0.0.17", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "googletest~1.14.0", + "urls": [ + "https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz" + ], + "integrity": "sha256-itWYxzrXluDYKAsILOvYKmMNc+c808cAV5OKZQG7pdc=", + "strip_prefix": "googletest-1.14.0", + "remote_patches": { + "https://bcr.bazel.build/modules/googletest/1.14.0/patches/module_dot_bazel.patch": "sha256-CSomzvti38LCuURDG5EEoa3O1tQF3cKKt/mknnP1qcc=" + }, + "remote_patch_strip": 0 + } + } + }, + "stardoc@0.5.3": { + "name": "stardoc", + "version": "0.5.3", + "key": "stardoc@0.5.3", + "repoName": "stardoc", + "executionPlatformsToRegister": [], + "toolchainsToRegister": [], + "extensionUsages": [], + "deps": { + "bazel_skylib": "bazel_skylib@1.7.1", + "rules_java": "rules_java@7.1.0", + "bazel_tools": "bazel_tools@_", + "local_config_platform": "local_config_platform@_" + }, + "repoSpec": { + "bzlFile": "@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "name": "stardoc~0.5.3", + "urls": [ + "https://github.com/bazelbuild/stardoc/releases/download/0.5.3/stardoc-0.5.3.tar.gz" + ], + "integrity": "sha256-P9j+xN3sPGcL2BCQTi4zFwvt/hL5Ct+UNQgYS+RYyLs=", + "strip_prefix": "", + "remote_patches": { + "https://bcr.bazel.build/modules/stardoc/0.5.3/patches/module_dot_bazel.patch": "sha256-Lgpy9OCr0zBWYuHoyM1rJJrgxn23X/bwgICEF7XiEug=" + }, + "remote_patch_strip": 0 + } + } + } + }, + "moduleExtensions": { + "@@bazel_features~1.19.0//private:extensions.bzl%version_extension": { + "general": { + "bzlTransitiveDigest": "/vJOb4IBPnuxjLO8/iEqHTGFZi8aA7E5PheUA6nMtMk=", + "accumulatedFileDigests": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "bazel_features_version": { + "bzlFile": "@@bazel_features~1.19.0//private:version_repo.bzl", + "ruleClassName": "version_repo", + "attributes": { + "name": "bazel_features~1.19.0~version_extension~bazel_features_version" + } + }, + "bazel_features_globals": { + "bzlFile": "@@bazel_features~1.19.0//private:globals_repo.bzl", + "ruleClassName": "globals_repo", + "attributes": { + "name": "bazel_features~1.19.0~version_extension~bazel_features_globals", + "globals": { + "CcSharedLibraryInfo": "6.0.0-pre.20220630.1", + "CcSharedLibraryHintInfo": "7.0.0-pre.20230316.2", + "PackageSpecificationInfo": "6.4.0", + "RunEnvironmentInfo": "5.3.0", + "DefaultInfo": "0.0.1", + "__TestingOnly_NeverAvailable": "1000000000.0.0" + }, + "legacy_globals": { + "JavaInfo": "8.0.0", + "JavaPluginInfo": "8.0.0", + "ProtoInfo": "8.0.0", + "PyCcLinkParamsProvider": "8.0.0", + "PyInfo": "8.0.0", + "PyRuntimeInfo": "8.0.0" + } + } + } + }, + "moduleExtensionMetadata": { + "useAllRepos": "DEV" + } + } + } + } +} diff --git a/Makefile b/Makefile index c7fe0fb60..3aae9b815 100644 --- a/Makefile +++ b/Makefile @@ -3,644 +3,38 @@ # ============================================================================== # Stack: Docker + Incus (LXD) Support # System: Linux / Bash +# +# Configuration: edit make/config.mk (ports, services, paths). +# Add new targets in make/*.mk or below. # ============================================================================== -# --- Auto-Configuration --- --include .env - -# Shell setup SHELL := /bin/bash .ONESHELL: .DEFAULT_GOAL := help -# --- Variables --- -PROJECT_NAME := veza -COMPOSE_FILE := docker-compose.yml -COMPOSE_PROD := docker-compose.prod.yml +# --- Configuration (single source of truth) --- +include make/config.mk +include make/ui.mk -# Services -SERVICES := backend-api chat-server stream-server web haproxy -INFRA_SERVICES := postgres redis rabbitmq - -# Ports -PORT_GO ?= 8080 -PORT_CHAT ?= 3000 -PORT_STREAM ?= 3001 -PORT_WEB ?= 5173 -PORT_HAPROXY ?= 80 - -# Database & Infra -DB_USER ?= veza -DB_PASS ?= password -DB_NAME ?= veza -DB_HOST ?= localhost -DB_PORT ?= 5432 - -# Connection Strings -DATABASE_URL = postgres://$(DB_USER):$(DB_PASS)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=disable -REDIS_URL = redis://localhost:6379 -AMQP_URL = amqp://$(DB_USER):$(DB_PASS)@localhost:5672 - -# Directories -DIR_GO := veza-backend-api -DIR_CHAT := veza-chat-server -DIR_STREAM := veza-stream-server -DIR_WEB := apps/web - -# Deployment -DEPLOY_TARGET ?= docker -INCUS_PROFILE := veza-profile -INCUS_NETWORK := veza-network - -# --- Aesthetics & UI --- -BOLD := \033[1m -RED := \033[0;31m -GREEN := \033[0;32m -YELLOW := \033[0;33m -BLUE := \033[0;34m -PURPLE := \033[0;35m -CYAN := \033[0;36m -NC := \033[0m - -ECHO_CMD = echo -e +# --- All feature modules --- +include make/tools.mk +include make/infra.mk +include make/dev.mk +include make/build.mk +include make/test.mk +include make/services.mk +include make/high.mk +include make/incus.mk +include make/help.mk # ============================================================================== -# HELP & DASHBOARD +# PER-SERVICE CONVENIENCE (dev-*, test-*, lint-*, build-*) # ============================================================================== -.PHONY: help -help: ## Show this dashboard - @$(ECHO_CMD) "" - @$(ECHO_CMD) "${BOLD}${PURPLE}⚡ VEZA MONOREPO CLI ⚡${NC}" - @$(ECHO_CMD) "=================================================================" - @$(ECHO_CMD) "${BOLD}INFRASTRUCTURE:${NC}" - @printf " ${CYAN}%-15s${NC} %s\n" "Postgres" "${DATABASE_URL}" - @printf " ${CYAN}%-15s${NC} %s\n" "Redis" "${REDIS_URL}" - @printf " ${CYAN}%-15s${NC} %s\n" "RabbitMQ" "UI: http://localhost:15672 (veza/password)" - @$(ECHO_CMD) "" - @$(ECHO_CMD) "${BOLD}${GREEN}HIGH LEVEL COMMANDS:${NC}" - @grep -E '^[a-zA-Z0-9_-]+:.*?## \[HIGH\] .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " ${YELLOW}%-25s${NC} %s\n", $$1, $$2}' - @$(ECHO_CMD) "" - @$(ECHO_CMD) "${BOLD}${BLUE}INTERMEDIATE COMMANDS:${NC}" - @grep -E '^[a-zA-Z0-9_-]+:.*?## \[MID\] .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " ${CYAN}%-25s${NC} %s\n", $$1, $$2}' - @$(ECHO_CMD) "" - @$(ECHO_CMD) "${BOLD}${PURPLE}LOW LEVEL / DEBUG:${NC}" - @grep -E '^[a-zA-Z0-9_-]+:.*?## \[LOW\] .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " ${PURPLE}%-25s${NC} %s\n", $$1, $$2}' - @$(ECHO_CMD) "" - +# Usage: make dev-web, make test-backend-api, make lint-web, etc. +# Add new services in make/config.mk (SERVICES, SERVICE_DIR_*, PORT_*). # ============================================================================== -# HIGH LEVEL COMMANDS -# ============================================================================== -.PHONY: setup stop-all restart-all clean deploy-docker deploy-incus status-full -setup: check-tools install-tools install-deps ## [HIGH] Full project initialization - @$(ECHO_CMD) "${BOLD}${GREEN}✅ Setup Complete! Ready to rock with 'make dev'.${NC}" - -web-minimal: ## [HIGH] Start Veza Web Minimal Journey (Backend + Frontend + DB) - @./scripts/start_minimal.sh - -stop-minimal: ## [HIGH] Stop Minimal Stack - @./scripts/stop_minimal.sh - -stop-all: ## [HIGH] Stop all services (Docker + Local) - @$(ECHO_CMD) "${RED}🛑 Stopping all services...${NC}" - @docker compose -f $(COMPOSE_FILE) down 2>/dev/null || true - @docker compose -f $(COMPOSE_PROD) down 2>/dev/null || true - @$(MAKE) -s stop-local-services - @$(ECHO_CMD) "${GREEN}✅ All services stopped.${NC}" - -restart-all: stop-all ## [HIGH] Restart all services - @$(ECHO_CMD) "${BLUE}🔄 Restarting all services...${NC}" - @$(MAKE) -s infra-up - @$(MAKE) -s dev - @$(ECHO_CMD) "${GREEN}✅ All services restarted.${NC}" - -clean: ## [HIGH] Clean build artifacts and caches - @$(ECHO_CMD) "${YELLOW}🧹 Cleaning build artifacts...${NC}" - @rm -rf $(DIR_WEB)/node_modules/.cache - @rm -rf $(DIR_CHAT)/target/debug $(DIR_STREAM)/target/debug - @find . -type d -name "node_modules" -prune -o -type f -name "*.log" -delete - @$(ECHO_CMD) "${GREEN}✅ Clean complete.${NC}" - -clean-deep: ## [HIGH] ⚠️ Nuclear Clean (Confirm required) - @read -p "${RED}Are you sure? This will delete ALL builds, volumes, and caches! [y/N]${NC} " ans && [ $${ans:-N} = y ] - @$(ECHO_CMD) "${RED}☢️ DESTROYING ARTIFACTS...${NC}" - @rm -rf $(DIR_WEB)/node_modules - @rm -rf $(DIR_CHAT)/target $(DIR_STREAM)/target - @docker compose -f $(COMPOSE_FILE) down -v 2>/dev/null || true - @docker compose -f $(COMPOSE_PROD) down -v 2>/dev/null || true - @$(ECHO_CMD) "${GREEN}System Cleaned.${NC}" - -deploy-docker: build-all ## [HIGH] Deploy all services with Docker + HAProxy - @$(ECHO_CMD) "${BOLD}${BLUE}🐳 Deploying with Docker...${NC}" - @docker compose -f $(COMPOSE_PROD) up -d --build - @$(MAKE) -s wait-for-services - @$(ECHO_CMD) "${GREEN}✅ Deployment complete! Access via http://localhost:$(PORT_HAPROXY)${NC}" - -deploy-incus: build-all-native ## [HIGH] Deploy all services with Incus containers (native, no Docker) - @$(ECHO_CMD) "${BOLD}${BLUE}📦 Deploying with Incus (native)...${NC}" - @$(MAKE) -s incus-setup-network - @$(MAKE) -s incus-deploy-infra - @$(MAKE) -s incus-deploy-all-native - @$(MAKE) -s incus-start-all - @$(ECHO_CMD) "${GREEN}✅ Incus deployment complete!${NC}" - @$(ECHO_CMD) "${BLUE}Access services at:${NC}" - @$(ECHO_CMD) " Backend API: http://10.10.10.2:8080" - @$(ECHO_CMD) " Chat Server: http://10.10.10.3:8081" - @$(ECHO_CMD) " Stream Server: http://10.10.10.4:3002" - @$(ECHO_CMD) " Web Frontend: http://10.10.10.5:80" - @$(ECHO_CMD) " HAProxy: http://10.10.10.6:80" - -status-full: ## [HIGH] Show complete system status - @$(ECHO_CMD) "${BOLD}${CYAN}📊 SYSTEM STATUS${NC}" - @$(ECHO_CMD) "" - @$(ECHO_CMD) "${BOLD}Docker Containers:${NC}" - @docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "NAME|veza" || echo " No containers running" - @$(ECHO_CMD) "" - @$(ECHO_CMD) "${BOLD}Local Processes:${NC}" - @lsof -i :$(PORT_GO) -i :$(PORT_CHAT) -i :$(PORT_STREAM) -i :$(PORT_WEB) 2>/dev/null | grep LISTEN || echo " No local processes" - @$(ECHO_CMD) "" - @$(ECHO_CMD) "${BOLD}Incus Containers:${NC}" - @incus list veza- 2>/dev/null | grep -E "NAME|veza" || echo " No Incus containers" - @$(ECHO_CMD) "" - -# ============================================================================== -# INTERMEDIATE COMMANDS -# ============================================================================== -.PHONY: start-service stop-service restart-service logs-service build-service - -start-service: ## [MID] Start a specific service (usage: make start-service SERVICE=backend-api) - @if [ -z "$(SERVICE)" ]; then \ - $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; \ - exit 1; \ - fi - @$(ECHO_CMD) "${BLUE}🚀 Starting $(SERVICE)...${NC}" - @docker compose -f $(COMPOSE_PROD) up -d $(SERVICE) || \ - $(MAKE) -s start-local-service SERVICE=$(SERVICE) - @$(ECHO_CMD) "${GREEN}✅ $(SERVICE) started.${NC}" - -stop-service: ## [MID] Stop a specific service (usage: make stop-service SERVICE=backend-api) - @if [ -z "$(SERVICE)" ]; then \ - $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; \ - exit 1; \ - fi - @$(ECHO_CMD) "${YELLOW}🛑 Stopping $(SERVICE)...${NC}" - @docker compose -f $(COMPOSE_PROD) stop $(SERVICE) 2>/dev/null || \ - $(MAKE) -s stop-local-service SERVICE=$(SERVICE) - @$(ECHO_CMD) "${GREEN}✅ $(SERVICE) stopped.${NC}" - -restart-service: stop-service ## [MID] Restart a specific service (usage: make restart-service SERVICE=backend-api) - @$(ECHO_CMD) "${BLUE}🔄 Restarting $(SERVICE)...${NC}" - @$(MAKE) -s start-service SERVICE=$(SERVICE) - @$(ECHO_CMD) "${GREEN}✅ $(SERVICE) restarted.${NC}" - -logs-service: ## [MID] Show logs for a service (usage: make logs-service SERVICE=backend-api) - @if [ -z "$(SERVICE)" ]; then \ - $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; \ - exit 1; \ - fi - @docker compose -f $(COMPOSE_PROD) logs -f $(SERVICE) || \ - $(ECHO_CMD) "${YELLOW}Service not running in Docker, check local logs${NC}" - -build-service: ## [MID] Build a specific service (usage: make build-service SERVICE=backend-api) - @if [ -z "$(SERVICE)" ]; then \ - $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; \ - exit 1; \ - fi - @$(ECHO_CMD) "${BLUE}🔨 Building $(SERVICE)...${NC}" - @$(MAKE) -s build-$(SERVICE) - @$(ECHO_CMD) "${GREEN}✅ $(SERVICE) built.${NC}" - -build-all: ## [MID] Build all services (Docker images) - @$(ECHO_CMD) "${BLUE}🔨 Building all services...${NC}" - @$(MAKE) -s build-backend-api - @$(MAKE) -s build-chat-server - @$(MAKE) -s build-stream-server - @$(MAKE) -s build-web - @$(ECHO_CMD) "${GREEN}✅ All services built.${NC}" - -build-all-native: ## [MID] Build all services natively (for Incus) - @$(ECHO_CMD) "${BLUE}🔨 Building all services natively...${NC}" - @$(shell pwd)/config/incus/build-native.sh all - @$(ECHO_CMD) "${GREEN}✅ All services built natively.${NC}" - -# ============================================================================== -# LOW LEVEL / DEBUG COMMANDS -# ============================================================================== -.PHONY: check-tools install-tools install-deps check-ports - -check-tools: ## [LOW] Check required tools - @$(ECHO_CMD) "${BLUE}Checking core requirements...${NC}" - @for tool in docker go cargo npm; do \ - command -v $$tool >/dev/null 2>&1 || { $(ECHO_CMD) "${RED}❌ $$tool is missing!${NC}"; exit 1; }; \ - done - @$(ECHO_CMD) "${GREEN}✅ All tools present.${NC}" - -check-tools-incus: ## [LOW] Check required tools for Incus deployment - @$(ECHO_CMD) "${BLUE}Checking Incus deployment requirements...${NC}" - @command -v incus >/dev/null 2>&1 || { $(ECHO_CMD) "${RED}❌ incus is missing! Install with: sudo snap install incus${NC}"; exit 1; } - @command -v go >/dev/null 2>&1 || { $(ECHO_CMD) "${RED}❌ go is missing!${NC}"; exit 1; } - @command -v cargo >/dev/null 2>&1 || { $(ECHO_CMD) "${RED}❌ cargo is missing!${NC}"; exit 1; } - @command -v npm >/dev/null 2>&1 || { $(ECHO_CMD) "${RED}❌ npm is missing!${NC}"; exit 1; } - @$(ECHO_CMD) "${GREEN}✅ All Incus tools present.${NC}" - -install-tools: ## [LOW] Install Power User tools (Hot Reload, Linters) - @$(ECHO_CMD) "${BLUE}🛠️ Installing Dev Tools...${NC}" - @command -v air >/dev/null 2>&1 || go install github.com/air-verse/air@latest - @command -v cargo-watch >/dev/null 2>&1 || cargo install cargo-watch - @command -v sqlx >/dev/null 2>&1 || cargo install sqlx-cli --no-default-features --features native-tls,postgres - @$(ECHO_CMD) "${GREEN}✅ Tools installed.${NC}" - -install-deps: ## [LOW] Install code dependencies - @$(ECHO_CMD) "${BLUE}📦 Installing dependencies...${NC}" - @$(ECHO_CMD) " -> [Go] Downloading modules..." - @(cd $(DIR_GO) && go mod download) - @$(ECHO_CMD) " -> [Rust Chat] Fetching crates..." - @(cd $(DIR_CHAT) && cargo fetch) - @$(ECHO_CMD) " -> [Rust Stream] Fetching crates..." - @(cd $(DIR_STREAM) && cargo fetch) - @$(ECHO_CMD) " -> [Web] Installing npm packages..." - @(cd $(DIR_WEB) && npm install --silent) - -check-ports: ## [LOW] Check if ports are available - @$(ECHO_CMD) "${BLUE}🔍 Checking ports...${NC}" - @for port in $(PORT_GO) $(PORT_CHAT) $(PORT_STREAM) $(PORT_WEB); do \ - if lsof -i :$$port -t >/dev/null 2>&1; then \ - $(ECHO_CMD) "${YELLOW}⚠️ Port $$port is busy${NC}"; \ - else \ - $(ECHO_CMD) "${GREEN}✅ Port $$port is free${NC}"; \ - fi; \ - done - -# ============================================================================== -# INFRASTRUCTURE -# ============================================================================== -.PHONY: infra-up infra-down wait-for-infra db-shell redis-shell db-migrate - -infra-up: ## [MID] Start Docker Infra (with health checks) - @$(ECHO_CMD) "${BLUE}🐳 Starting Infrastructure...${NC}" - @docker compose -f $(COMPOSE_FILE) up -d - @$(MAKE) -s wait-for-infra - -infra-down: ## [MID] Stop Docker Infra - @$(ECHO_CMD) "${BLUE}🛑 Stopping Infrastructure...${NC}" - @docker compose -f $(COMPOSE_FILE) down - -wait-for-infra: ## [LOW] Wait for infrastructure to be ready - @printf "${BLUE}⏳ Waiting for services...${NC}" - @until docker compose -f $(COMPOSE_FILE) exec -T postgres pg_isready -U $(DB_USER) > /dev/null 2>&1; do printf "."; sleep 1; done - @until docker compose -f $(COMPOSE_FILE) exec -T redis redis-cli ping > /dev/null 2>&1; do printf "."; sleep 1; done - @$(ECHO_CMD) " ${GREEN}OK${NC}" - -wait-for-services: ## [LOW] Wait for all application services - @printf "${BLUE}⏳ Waiting for services...${NC}" - @for service in backend-api chat-server stream-server web; do \ - until docker compose -f $(COMPOSE_PROD) exec -T $$service echo "ready" > /dev/null 2>&1; do \ - printf "."; sleep 1; \ - done; \ - done - @$(ECHO_CMD) " ${GREEN}OK${NC}" - -db-shell: ## [MID] Connect to Postgres shell - @docker compose -f $(COMPOSE_FILE) exec postgres psql -U $(DB_USER) -d $(DB_NAME) - -redis-shell: ## [MID] Connect to Redis shell - @docker compose -f $(COMPOSE_FILE) exec redis redis-cli - -db-migrate: infra-up ## [MID] Run all database migrations - @$(ECHO_CMD) "${BLUE}🔄 Running Migrations...${NC}" - @$(ECHO_CMD) " -> [Go] Migrating..." - @(cd $(DIR_GO) && go run cmd/migrate_tool/main.go up || $(ECHO_CMD) "${YELLOW}Warning: Go migration failed${NC}") - @$(ECHO_CMD) " -> [Chat] Migrating..." - @(cd $(DIR_CHAT) && sqlx migrate run || $(ECHO_CMD) "${YELLOW}Warning: Chat migration failed${NC}") - @$(ECHO_CMD) " -> [Stream] Migrating..." - @(cd $(DIR_STREAM) && sqlx migrate run || $(ECHO_CMD) "${YELLOW}Warning: Stream migration failed${NC}") - @$(ECHO_CMD) "${GREEN}✅ Migrations done.${NC}" - -# ============================================================================== -# DEVELOPMENT -# ============================================================================== -.PHONY: dev dev-backend stop-local-services start-local-service stop-local-service - -dev: check-ports infra-up ## [HIGH] Start Everything (Detects Hot Reload tools) - @$(ECHO_CMD) "${BOLD}${PURPLE}🚀 STARTING HYBRID DEV ENVIRONMENT${NC}" - @$(ECHO_CMD) " Go: http://localhost:${PORT_GO}" - @$(ECHO_CMD) " Chat: http://localhost:${PORT_CHAT}" - @$(ECHO_CMD) " Web: http://localhost:${PORT_WEB}" - @$(ECHO_CMD) "${YELLOW}Hit Ctrl+C to stop all.${NC}" - @(trap 'kill 0' SIGINT; \ - if command -v air >/dev/null; then \ - $(ECHO_CMD) "${GREEN}[Go] Hot Reload Active (Air)${NC}" && cd $(DIR_GO) && air & \ - else \ - $(ECHO_CMD) "${YELLOW}[Go] Standard Run${NC}" && cd $(DIR_GO) && go run cmd/modern-server/main.go & \ - fi; \ - if command -v cargo-watch >/dev/null; then \ - $(ECHO_CMD) "${GREEN}[Chat] Hot Reload Active${NC}" && cd $(DIR_CHAT) && cargo watch -x run -q & \ - $(ECHO_CMD) "${GREEN}[Stream] Hot Reload Active${NC}" && cd $(DIR_STREAM) && cargo watch -x run -q & \ - else \ - $(ECHO_CMD) "${YELLOW}[Chat] Standard Run${NC}" && cd $(DIR_CHAT) && cargo run -q & \ - $(ECHO_CMD) "${YELLOW}[Stream] Standard Run${NC}" && cd $(DIR_STREAM) && cargo run -q & \ - fi; \ - $(ECHO_CMD) "${GREEN}[Web] Starting Vite...${NC}" && cd $(DIR_WEB) && npm run dev & \ - wait) - -dev-backend: check-ports infra-up ## [MID] Start Backends Only (Hot Reload supported) - @$(ECHO_CMD) "${BOLD}${PURPLE}🚀 STARTING BACKEND ONLY${NC}" - @(trap 'kill 0' SIGINT; \ - if command -v air >/dev/null; then cd $(DIR_GO) && air & else cd $(DIR_GO) && go run cmd/modern-server/main.go & fi; \ - if command -v cargo-watch >/dev/null; then cd $(DIR_CHAT) && cargo watch -x run -q & else cd $(DIR_CHAT) && cargo run -q & fi; \ - if command -v cargo-watch >/dev/null; then cd $(DIR_STREAM) && cargo watch -x run -q & else cd $(DIR_STREAM) && cargo run -q & fi; \ - wait) - -stop-local-services: ## [LOW] Stop all local processes - @pkill -f "air\|cargo watch\|npm run dev\|go run.*modern-server" 2>/dev/null || true - -start-local-service: ## [LOW] Start a service locally - @case "$(SERVICE)" in \ - backend-api) \ - if command -v air >/dev/null; then cd $(DIR_GO) && air & else cd $(DIR_GO) && go run cmd/modern-server/main.go & fi ;; \ - chat-server) \ - if command -v cargo-watch >/dev/null; then cd $(DIR_CHAT) && cargo watch -x run -q & else cd $(DIR_CHAT) && cargo run -q & fi ;; \ - stream-server) \ - if command -v cargo-watch >/dev/null; then cd $(DIR_STREAM) && cargo watch -x run -q & else cd $(DIR_STREAM) && cargo run -q & fi ;; \ - web) \ - cd $(DIR_WEB) && npm run dev & ;; \ - *) \ - $(ECHO_CMD) "${RED}Unknown service: $(SERVICE)${NC}"; exit 1 ;; \ - esac - -stop-local-service: ## [LOW] Stop a local service - @case "$(SERVICE)" in \ - backend-api) pkill -f "air\|go run.*modern-server" ;; \ - chat-server|stream-server) pkill -f "cargo.*$(SERVICE)" ;; \ - web) pkill -f "npm run dev\|vite" ;; \ - *) $(ECHO_CMD) "${RED}Unknown service: $(SERVICE)${NC}" ;; \ - esac - -# ============================================================================== -# BUILD COMMANDS -# ============================================================================== -.PHONY: build-backend-api build-chat-server build-stream-server build-web - -build-backend-api: ## [LOW] Build Go backend - @$(ECHO_CMD) "${BLUE}🔨 Building backend-api...${NC}" - @docker build -t $(PROJECT_NAME)-backend-api:latest -f $(DIR_GO)/Dockerfile.production $(DIR_GO) || \ - $(ECHO_CMD) "${YELLOW}Using local Dockerfile...${NC}" && \ - docker build -t $(PROJECT_NAME)-backend-api:latest -f $(DIR_GO)/Dockerfile $(DIR_GO) - -build-chat-server: ## [LOW] Build Rust chat server - @$(ECHO_CMD) "${BLUE}🔨 Building chat-server...${NC}" - @docker build -t $(PROJECT_NAME)-chat-server:latest -f $(DIR_CHAT)/Dockerfile.production $(DIR_CHAT) || \ - docker build -t $(PROJECT_NAME)-chat-server:latest -f $(DIR_CHAT)/Dockerfile $(DIR_CHAT) - -build-stream-server: ## [LOW] Build Rust stream server - @$(ECHO_CMD) "${BLUE}🔨 Building stream-server...${NC}" - @docker build -t $(PROJECT_NAME)-stream-server:latest -f $(DIR_STREAM)/Dockerfile.production $(DIR_STREAM) || \ - docker build -t $(PROJECT_NAME)-stream-server:latest -f $(DIR_STREAM)/Dockerfile $(DIR_STREAM) - -build-web: ## [LOW] Build web frontend - @$(ECHO_CMD) "${BLUE}🔨 Building web...${NC}" - @docker build -t $(PROJECT_NAME)-web:latest -f $(DIR_WEB)/Dockerfile.production $(DIR_WEB) || \ - docker build -t $(PROJECT_NAME)-web:latest -f $(DIR_WEB)/Dockerfile $(DIR_WEB) - -# ============================================================================== -# INCUS / LXD DEPLOYMENT -# ============================================================================== -.PHONY: incus-setup-network incus-deploy-all incus-deploy-all-native incus-deploy-service incus-deploy-service-native incus-deploy-infra incus-start-all incus-stop-all incus-logs - -incus-setup-network: ## [LOW] Setup Incus network profile - @$(ECHO_CMD) "${BLUE}📦 Setting up Incus network...${NC}" - @if ! incus network show $(INCUS_NETWORK) >/dev/null 2>&1; then \ - $(ECHO_CMD) "Creating network $(INCUS_NETWORK)..."; \ - incus network create $(INCUS_NETWORK) \ - ipv4.address=10.10.10.1/24 \ - ipv4.nat=true \ - ipv4.dhcp=true \ - dns.mode=managed \ - dns.nameservers=8.8.8.8,1.1.1.1; \ - else \ - $(ECHO_CMD) "Updating network configuration..."; \ - incus network set $(INCUS_NETWORK) ipv4.dhcp=true 2>/dev/null || true; \ - incus network set $(INCUS_NETWORK) dns.mode=managed 2>/dev/null || true; \ - incus network set $(INCUS_NETWORK) dns.nameservers=8.8.8.8,1.1.1.1 2>/dev/null || true; \ - fi - @if ! incus profile show $(INCUS_PROFILE) >/dev/null 2>&1; then \ - $(ECHO_CMD) "Creating profile $(INCUS_PROFILE)..."; \ - incus profile create $(INCUS_PROFILE); \ - incus profile device add $(INCUS_PROFILE) root disk path=/ pool=default 2>/dev/null || \ - incus profile device add $(INCUS_PROFILE) root disk path=/ 2>/dev/null || true; \ - incus profile device add $(INCUS_PROFILE) eth0 nic network=$(INCUS_NETWORK) 2>/dev/null || true; \ - else \ - $(ECHO_CMD) "Ensuring profile devices..."; \ - if ! incus profile show $(INCUS_PROFILE) | grep -q "root:"; then \ - incus profile device add $(INCUS_PROFILE) root disk path=/ pool=default 2>/dev/null || \ - incus profile device add $(INCUS_PROFILE) root disk path=/ 2>/dev/null || true; \ - fi; \ - if ! incus profile show $(INCUS_PROFILE) | grep -q "eth0:"; then \ - incus profile device add $(INCUS_PROFILE) eth0 nic network=$(INCUS_NETWORK) 2>/dev/null || true; \ - fi; \ - fi - @$(ECHO_CMD) "${GREEN}✅ Incus network ready.${NC}" - -incus-deploy-all: incus-setup-network ## [MID] Deploy all services to Incus (legacy Docker method) - @$(ECHO_CMD) "${BLUE}📦 Deploying all services to Incus (Docker)...${NC}" - @$(MAKE) -s incus-deploy-service SERVICE=backend-api - @$(MAKE) -s incus-deploy-service SERVICE=chat-server - @$(MAKE) -s incus-deploy-service SERVICE=stream-server - @$(MAKE) -s incus-deploy-service SERVICE=web - @$(MAKE) -s incus-deploy-service SERVICE=haproxy - @$(ECHO_CMD) "${GREEN}✅ All services deployed to Incus.${NC}" - -incus-deploy-all-native: incus-setup-network ## [MID] Deploy all services to Incus (native, no Docker) - excludes Rust services - @$(ECHO_CMD) "${BLUE}📦 Deploying all services to Incus (native, excluding Rust services)...${NC}" - @$(ECHO_CMD) "${YELLOW}⚠️ Note: chat-server and stream-server are excluded${NC}" - @$(MAKE) -s incus-deploy-service-native SERVICE=backend-api - @$(MAKE) -s incus-deploy-service-native SERVICE=web - @$(MAKE) -s incus-deploy-service-native SERVICE=haproxy - @$(ECHO_CMD) "${GREEN}✅ All services deployed to Incus.${NC}" - -incus-deploy-service: ## [LOW] Deploy a service to Incus with Docker (usage: make incus-deploy-service SERVICE=backend-api) - @if [ -z "$(SERVICE)" ]; then \ - $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; \ - exit 1; \ - fi - @$(ECHO_CMD) "${BLUE}📦 Deploying $(SERVICE) to Incus (Docker)...${NC}" - @if incus list -c n --format csv | grep -q "^veza-$(SERVICE)$$"; then \ - $(ECHO_CMD) "${YELLOW}Container exists, removing...${NC}"; \ - incus delete veza-$(SERVICE) --force; \ - fi - @incus init images:debian/13 veza-$(SERVICE) --profile $(INCUS_PROFILE) - @incus start veza-$(SERVICE) - @$(ECHO_CMD) "${BLUE}Installing Docker in container...${NC}" - @incus exec veza-$(SERVICE) -- bash -c "apt-get update && apt-get install -y docker.io docker-compose && systemctl enable docker && systemctl start docker" || true - @$(ECHO_CMD) "${GREEN}✅ $(SERVICE) deployed.${NC}" - -incus-deploy-service-native: ## [LOW] Deploy a service to Incus natively (usage: make incus-deploy-service-native SERVICE=backend-api) - @if [ -z "$(SERVICE)" ]; then \ - $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; \ - exit 1; \ - fi - @$(ECHO_CMD) "${BLUE}📦 Deploying $(SERVICE) to Incus (native)...${NC}" - @$(shell pwd)/config/incus/deploy-service-native.sh $(SERVICE) - -incus-deploy-infra: incus-setup-network ## [LOW] Deploy infrastructure services (PostgreSQL, Redis) - @$(ECHO_CMD) "${BLUE}📦 Deploying infrastructure services...${NC}" - @$(MAKE) -s incus-deploy-service-native SERVICE=infra - @$(ECHO_CMD) "${BLUE}Waiting for infrastructure to be ready...${NC}" - @for i in $$(seq 1 30); do \ - if incus exec veza-infra -- systemctl is-active postgresql >/dev/null 2>&1 && \ - incus exec veza-infra -- systemctl is-active redis-server >/dev/null 2>&1; then \ - $(ECHO_CMD) "${GREEN}✅ Infrastructure services ready${NC}"; \ - break; \ - fi; \ - sleep 1; \ - done - @$(ECHO_CMD) "${GREEN}✅ Infrastructure deployed.${NC}" - -incus-start-all: ## [MID] Start all Incus services (excluding Rust services) - @$(ECHO_CMD) "${BLUE}🚀 Starting all Incus services (excluding Rust services)...${NC}" - @for service in backend-api; do \ - if incus list -c n --format csv | grep -q "^veza-$$service$$"; then \ - $(ECHO_CMD) "Starting veza-$$service..."; \ - if incus exec veza-$$service -- systemctl start veza-$$service 2>/dev/null; then \ - $(ECHO_CMD) "${GREEN} ✅ veza-$$service started${NC}"; \ - else \ - $(ECHO_CMD) "${YELLOW} ⚠️ veza-$$service failed to start (check logs)${NC}"; \ - fi; \ - fi; \ - done - @if incus list -c n --format csv | grep -q "^veza-web$$"; then \ - $(ECHO_CMD) "Starting veza-web..."; \ - if incus exec veza-web -- systemctl start apache2 2>/dev/null; then \ - $(ECHO_CMD) "${GREEN} ✅ Apache started${NC}"; \ - else \ - $(ECHO_CMD) "${YELLOW} ⚠️ Apache failed to start${NC}"; \ - fi; \ - fi - @if incus list -c n --format csv | grep -q "^veza-haproxy$$"; then \ - $(ECHO_CMD) "Starting veza-haproxy..."; \ - if incus exec veza-haproxy -- systemctl start haproxy 2>/dev/null; then \ - $(ECHO_CMD) "${GREEN} ✅ HAProxy started${NC}"; \ - else \ - $(ECHO_CMD) "${YELLOW} ⚠️ HAProxy failed to start${NC}"; \ - fi; \ - fi - @if incus list -c n --format csv | grep -q "^veza-infra$$"; then \ - $(ECHO_CMD) "Starting infrastructure services..."; \ - if incus exec veza-infra -- systemctl start postgresql 2>/dev/null; then \ - $(ECHO_CMD) "${GREEN} ✅ PostgreSQL started${NC}"; \ - else \ - $(ECHO_CMD) "${YELLOW} ⚠️ PostgreSQL failed to start${NC}"; \ - fi; \ - if incus exec veza-infra -- systemctl start redis-server 2>/dev/null; then \ - $(ECHO_CMD) "${GREEN} ✅ Redis started${NC}"; \ - else \ - $(ECHO_CMD) "${YELLOW} ⚠️ Redis failed to start${NC}"; \ - fi; \ - fi - @$(ECHO_CMD) "${GREEN}✅ All services started.${NC}" - @$(ECHO_CMD) "${BLUE}Run 'make incus-status' to check service status${NC}" - -incus-stop-all: ## [MID] Stop all Incus containers - @$(ECHO_CMD) "${YELLOW}🛑 Stopping all Incus containers...${NC}" - @for container in $$(incus list -c n --format csv | grep veza-); do \ - incus stop $$container 2>/dev/null || true; \ - done - @$(ECHO_CMD) "${GREEN}✅ All Incus containers stopped.${NC}" - -incus-status: ## [MID] Show status of all Incus services - @$(ECHO_CMD) "${BOLD}${CYAN}📊 INCUS DEPLOYMENT STATUS${NC}" - @$(ECHO_CMD) "" - @$(ECHO_CMD) "${BOLD}Containers:${NC}" - @incus list veza- --format table 2>/dev/null || echo " No containers found" - @$(ECHO_CMD) "" - @$(ECHO_CMD) "${BOLD}Service Status:${NC}" - @for service in backend-api chat-server stream-server; do \ - if incus list -c n --format csv 2>/dev/null | grep -q "^veza-$$service$$"; then \ - STATUS=$$(incus exec veza-$$service -- systemctl is-active veza-$$service 2>/dev/null || echo "inactive"); \ - if [ "$$STATUS" = "active" ]; then \ - $(ECHO_CMD) " ${GREEN}✅ veza-$$service: active${NC}"; \ - else \ - $(ECHO_CMD) " ${YELLOW}⚠️ veza-$$service: $$STATUS${NC}"; \ - fi; \ - fi; \ - done - @if incus list -c n --format csv 2>/dev/null | grep -q "^veza-web$$"; then \ - STATUS=$$(incus exec veza-web -- systemctl is-active apache2 2>/dev/null || echo "inactive"); \ - if [ "$$STATUS" = "active" ]; then \ - $(ECHO_CMD) " ${GREEN}✅ veza-web (Apache): active${NC}"; \ - else \ - $(ECHO_CMD) " ${YELLOW}⚠️ veza-web (Apache): $$STATUS${NC}"; \ - fi; \ - fi - @if incus list -c n --format csv 2>/dev/null | grep -q "^veza-haproxy$$"; then \ - STATUS=$$(incus exec veza-haproxy -- systemctl is-active haproxy 2>/dev/null || echo "inactive"); \ - if [ "$$STATUS" = "active" ]; then \ - $(ECHO_CMD) " ${GREEN}✅ veza-haproxy: active${NC}"; \ - else \ - $(ECHO_CMD) " ${YELLOW}⚠️ veza-haproxy: $$STATUS${NC}"; \ - fi; \ - fi - @if incus list -c n --format csv 2>/dev/null | grep -q "^veza-infra$$"; then \ - PG_STATUS=$$(incus exec veza-infra -- systemctl is-active postgresql 2>/dev/null || echo "inactive"); \ - REDIS_STATUS=$$(incus exec veza-infra -- systemctl is-active redis-server 2>/dev/null || echo "inactive"); \ - if [ "$$PG_STATUS" = "active" ]; then \ - $(ECHO_CMD) " ${GREEN}✅ PostgreSQL: active${NC}"; \ - else \ - $(ECHO_CMD) " ${YELLOW}⚠️ PostgreSQL: $$PG_STATUS${NC}"; \ - fi; \ - if [ "$$REDIS_STATUS" = "active" ]; then \ - $(ECHO_CMD) " ${GREEN}✅ Redis: active${NC}"; \ - else \ - $(ECHO_CMD) " ${YELLOW}⚠️ Redis: $$REDIS_STATUS${NC}"; \ - fi; \ - fi - @$(ECHO_CMD) "" - -incus-logs: ## [LOW] Show logs from Incus container (usage: make incus-logs SERVICE=backend-api) - @if [ -z "$(SERVICE)" ]; then \ - $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; \ - exit 1; \ - fi - @incus exec veza-$(SERVICE) -- journalctl -f - -# ============================================================================== -# TEST & QUALITY -# ============================================================================== -.PHONY: test test-tmt lint fmt status - -test-tmt: ## [MID] Run Unified TMT Pipeline - @$(ECHO_CMD) "${BLUE}🧪 Running TMT Pipeline...${NC}" - @command -v tmt >/dev/null 2>&1 || { $(ECHO_CMD) "${RED}❌ tmt is missing! Install with 'pip install tmt'${NC}"; exit 1; } - @tmt run - -test: infra-up ## [MID] Run All Tests (Fastest strategy) - @$(ECHO_CMD) "${BLUE}🧪 Running Tests...${NC}" - @$(ECHO_CMD) " [Go] Unit Tests..." - @(cd $(DIR_GO) && go test ./... -short) - @$(ECHO_CMD) " [Rust] Unit Tests..." - @(cd $(DIR_CHAT) && cargo test --lib -q) - @(cd $(DIR_STREAM) && cargo test --lib -q) - @$(ECHO_CMD) " [Web] Unit Tests..." - @(cd $(DIR_WEB) && npm run test -- --run) - @$(ECHO_CMD) "${GREEN}✅ All tests passed.${NC}" - -lint: ## [MID] Lint everything - @$(ECHO_CMD) "${BLUE}🔍 Linting Codebase...${NC}" - @(cd $(DIR_CHAT) && cargo clippy -- -D warnings) || true - @(cd $(DIR_STREAM) && cargo clippy -- -D warnings) || true - @(cd $(DIR_GO) && golangci-lint run ./...) || true - @(cd $(DIR_WEB) && npm run lint) || true - -fmt: ## [MID] Format everything - @$(ECHO_CMD) "${BLUE}✨ Formatting...${NC}" - @(cd $(DIR_GO) && go fmt ./...) - @(cd $(DIR_CHAT) && cargo fmt) - @(cd $(DIR_STREAM) && cargo fmt) - @(cd $(DIR_WEB) && npm run format) || true - -status: ## [MID] Show system health & stats - @$(ECHO_CMD) "${BOLD}DOCKER STATS:${NC}" - @docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}" 2>/dev/null | grep -E "NAME|veza" || echo "No containers running" - @$(ECHO_CMD) "" - @$(ECHO_CMD) "${BOLD}LOCAL PORTS:${NC}" - @lsof -i :$(PORT_GO) -i :$(PORT_CHAT) -i :$(PORT_STREAM) -i :$(PORT_WEB) 2>/dev/null | grep LISTEN || echo "No apps listening." +.PHONY: dev-web dev-backend-api dev-chat-server dev-stream-server +.PHONY: test-web test-backend-api test-chat-server test-stream-server +.PHONY: lint-web lint-backend-api lint-chat-server lint-stream-server +# (targets defined in make/dev.mk and make/test.mk) diff --git a/apps/web/.env.example b/apps/web/.env.example index 01bb4d9d7..e41e265d0 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -3,6 +3,9 @@ # API Configuration # Base URL for the REST API (can be absolute URL or path starting with /) +# DEV (veza.fr or localhost): use /api/v1 so the Vite proxy forwards to the backend. +# - Same origin => cookies are sent => login and /auth/me work. Using http://localhost:8080 +# from veza.fr:5173 is cross-origin => cookies not sent => 401 and redirect loop. VITE_API_URL=/api/v1 # WebSocket Configuration diff --git a/apps/web/.env.local b/apps/web/.env.local index 62610d4f1..85d45632a 100644 --- a/apps/web/.env.local +++ b/apps/web/.env.local @@ -1,5 +1,5 @@ # Configuration API pour développement local # Backend Go tourne sur le port 8080 -VITE_API_URL=http://localhost:8080/api/v1 +VITE_API_URL=/api/v1 VITE_WS_URL=ws://localhost:8081/ws VITE_STREAM_URL=ws://localhost:8082/stream diff --git a/apps/web/e2e/playwright-report-visual/index.html b/apps/web/e2e/playwright-report-visual/index.html new file mode 100644 index 000000000..897bebda7 --- /dev/null +++ b/apps/web/e2e/playwright-report-visual/index.html @@ -0,0 +1,85 @@ + + + + + + + + + Playwright Test Report + + + + +
+ + + \ No newline at end of file diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/08104e856df2cee4994a0d4b4e2aaddd.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/08104e856df2cee4994a0d4b4e2aaddd.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/08104e856df2cee4994a0d4b4e2aaddd.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/1564aff5d3f7e819726685f6fb38475b.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/1564aff5d3f7e819726685f6fb38475b.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/1564aff5d3f7e819726685f6fb38475b.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/1db17eb3e8692e13f0b7f3d4b7c26345.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/1db17eb3e8692e13f0b7f3d4b7c26345.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/1db17eb3e8692e13f0b7f3d4b7c26345.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/22cffccf92e7c99ea6e91b1b40cc7492.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/22cffccf92e7c99ea6e91b1b40cc7492.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/22cffccf92e7c99ea6e91b1b40cc7492.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/4273308b97ec181317860f5dfe76cf4f.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/4273308b97ec181317860f5dfe76cf4f.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/4273308b97ec181317860f5dfe76cf4f.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/44420049079721778f90d2561b71c298.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/44420049079721778f90d2561b71c298.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/44420049079721778f90d2561b71c298.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/4611b82dc05dc0b08f92c4abdc2e70ae.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/4611b82dc05dc0b08f92c4abdc2e70ae.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/4611b82dc05dc0b08f92c4abdc2e70ae.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/476ba4bfca42b1b88b909884583db854.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/476ba4bfca42b1b88b909884583db854.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/476ba4bfca42b1b88b909884583db854.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/4c7da23391e9e375aaeb00fc8c084824.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/4c7da23391e9e375aaeb00fc8c084824.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/4c7da23391e9e375aaeb00fc8c084824.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/4c8adcb9f0956be33e955be11b36f053.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/4c8adcb9f0956be33e955be11b36f053.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/4c8adcb9f0956be33e955be11b36f053.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/4eecfbdcee1f41fb39d1ef52b2fa3702.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/4eecfbdcee1f41fb39d1ef52b2fa3702.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/4eecfbdcee1f41fb39d1ef52b2fa3702.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/6ac1fcb278abc4f125665a3ab742e70f.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/6ac1fcb278abc4f125665a3ab742e70f.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/6ac1fcb278abc4f125665a3ab742e70f.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/75884039f3ed7016c67bd3855614753e.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/75884039f3ed7016c67bd3855614753e.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/75884039f3ed7016c67bd3855614753e.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/775e01dc954aa567327e3be1757f248d.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/775e01dc954aa567327e3be1757f248d.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/775e01dc954aa567327e3be1757f248d.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/7c582d9213056a4c78faeff2764f374e.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/7c582d9213056a4c78faeff2764f374e.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/7c582d9213056a4c78faeff2764f374e.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/7d588284e06bfe34bcd8172a1aa18e2d.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/7d588284e06bfe34bcd8172a1aa18e2d.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/7d588284e06bfe34bcd8172a1aa18e2d.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/7d7278bbee18cd1566e495e5b8b75306.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/7d7278bbee18cd1566e495e5b8b75306.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/7d7278bbee18cd1566e495e5b8b75306.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/8a3009987785b376474fe6dd5023dd3c.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/8a3009987785b376474fe6dd5023dd3c.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/8a3009987785b376474fe6dd5023dd3c.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/95a6799ab68f0e005bc5370cab5eef09.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/95a6799ab68f0e005bc5370cab5eef09.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/95a6799ab68f0e005bc5370cab5eef09.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/9e69ed3d693c85ad53bb4e22a439c49f.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/9e69ed3d693c85ad53bb4e22a439c49f.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/9e69ed3d693c85ad53bb4e22a439c49f.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/b237e1c38702e0b666a8853ee624c954.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/b237e1c38702e0b666a8853ee624c954.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/b237e1c38702e0b666a8853ee624c954.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/b3125d9cc54d71cb87cb8ef8438c83c1.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/b3125d9cc54d71cb87cb8ef8438c83c1.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/b3125d9cc54d71cb87cb8ef8438c83c1.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/b6eee69c783db648b3178e8e9aa9ad55.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/b6eee69c783db648b3178e8e9aa9ad55.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/b6eee69c783db648b3178e8e9aa9ad55.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/b7d4f98c4c3062b334f9dad40b591f2a.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/b7d4f98c4c3062b334f9dad40b591f2a.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/b7d4f98c4c3062b334f9dad40b591f2a.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/bd6b7c0906eb1bffe2360fd5a053e028.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/bd6b7c0906eb1bffe2360fd5a053e028.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/bd6b7c0906eb1bffe2360fd5a053e028.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/c22da0768cf9bc51079dddab16c3975f.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/c22da0768cf9bc51079dddab16c3975f.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/c22da0768cf9bc51079dddab16c3975f.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/cb7f0a3da7738bec16e1e70b2cc47095.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/cb7f0a3da7738bec16e1e70b2cc47095.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/cb7f0a3da7738bec16e1e70b2cc47095.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/d91bd60ee88c954bdf55e305db3c212b.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/d91bd60ee88c954bdf55e305db3c212b.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/d91bd60ee88c954bdf55e305db3c212b.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/dceb2a40a30965c96c946895e23f374f.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/dceb2a40a30965c96c946895e23f374f.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/dceb2a40a30965c96c946895e23f374f.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/e461841dbcc709076b3a275050d656e4.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/e461841dbcc709076b3a275050d656e4.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/e461841dbcc709076b3a275050d656e4.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/e77ebaa1e2147787eefa1c3934565053.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/e77ebaa1e2147787eefa1c3934565053.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/e77ebaa1e2147787eefa1c3934565053.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/e7a2b48ae14a419fdc60a182d1933726.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/e7a2b48ae14a419fdc60a182d1933726.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/e7a2b48ae14a419fdc60a182d1933726.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/f32f1e44472ad2365807690e2d000cb1.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/f32f1e44472ad2365807690e2d000cb1.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/f32f1e44472ad2365807690e2d000cb1.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/ff9f2f59b44bb9d669ecdd165fd16e63.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/ff9f2f59b44bb9d669ecdd165fd16e63.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-0/ff9f2f59b44bb9d669ecdd165fd16e63.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/066ee6d9bb8073cfea71a66c8f937489.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/066ee6d9bb8073cfea71a66c8f937489.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/066ee6d9bb8073cfea71a66c8f937489.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/0e18fe38cf596e1aef0325b72d25df96.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/0e18fe38cf596e1aef0325b72d25df96.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/0e18fe38cf596e1aef0325b72d25df96.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/10e9c397b904959ec3e2f76e8c82f6ce.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/10e9c397b904959ec3e2f76e8c82f6ce.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/10e9c397b904959ec3e2f76e8c82f6ce.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/159dfdf12f37c03dfc521c0b66313e6a.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/159dfdf12f37c03dfc521c0b66313e6a.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/159dfdf12f37c03dfc521c0b66313e6a.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/177845541689c249755068c82fa0234c.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/177845541689c249755068c82fa0234c.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/177845541689c249755068c82fa0234c.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/2b97d2cfb03a7a1c22bdd42b4292fceb.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/2b97d2cfb03a7a1c22bdd42b4292fceb.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/2b97d2cfb03a7a1c22bdd42b4292fceb.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/3553e6b8def7801d7bf9071fb6444604.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/3553e6b8def7801d7bf9071fb6444604.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/3553e6b8def7801d7bf9071fb6444604.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/3929d8516fb53559eb9c722a15cc21e9.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/3929d8516fb53559eb9c722a15cc21e9.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/3929d8516fb53559eb9c722a15cc21e9.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/474b878f9cf499ca810904c7fe6017a1.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/474b878f9cf499ca810904c7fe6017a1.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/474b878f9cf499ca810904c7fe6017a1.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/49942c46eb917bf73393b639c82855c5.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/49942c46eb917bf73393b639c82855c5.png new file mode 100644 index 000000000..be15dadd9 Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/49942c46eb917bf73393b639c82855c5.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/4ed849f6666b429df76a2cf98e526e11.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/4ed849f6666b429df76a2cf98e526e11.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/4ed849f6666b429df76a2cf98e526e11.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/588220863074ed931cca2da34e3d7041.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/588220863074ed931cca2da34e3d7041.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/588220863074ed931cca2da34e3d7041.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/5fb390fe83e4e40b7dced75eaf28de7c.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/5fb390fe83e4e40b7dced75eaf28de7c.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/5fb390fe83e4e40b7dced75eaf28de7c.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/608d121b76ab6ccd8d0eeed6c5c2b1a5.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/608d121b76ab6ccd8d0eeed6c5c2b1a5.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/608d121b76ab6ccd8d0eeed6c5c2b1a5.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/83245a46416b8967e8ab9e4821d547ae.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/83245a46416b8967e8ab9e4821d547ae.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/83245a46416b8967e8ab9e4821d547ae.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/89c582cea322ae493dd96d2cc1be11a9.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/89c582cea322ae493dd96d2cc1be11a9.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/89c582cea322ae493dd96d2cc1be11a9.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/8b4d0abe6f412a7eadeb06e06d330814.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/8b4d0abe6f412a7eadeb06e06d330814.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/8b4d0abe6f412a7eadeb06e06d330814.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/8d00e3efd2de523b08819e324bb41e5f.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/8d00e3efd2de523b08819e324bb41e5f.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/8d00e3efd2de523b08819e324bb41e5f.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/92501b75a2760ea59cea2fd9406bcffc.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/92501b75a2760ea59cea2fd9406bcffc.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/92501b75a2760ea59cea2fd9406bcffc.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/99af1ba286fe30c4f80d09d355d30497.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/99af1ba286fe30c4f80d09d355d30497.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/99af1ba286fe30c4f80d09d355d30497.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/9a01930dc0ac4799e8d7963a4a1abcc9.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/9a01930dc0ac4799e8d7963a4a1abcc9.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/9a01930dc0ac4799e8d7963a4a1abcc9.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/aa90967a68feb269e54ec7b2dc838fd6.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/aa90967a68feb269e54ec7b2dc838fd6.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/aa90967a68feb269e54ec7b2dc838fd6.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/af2c74fa3dfe1e3924dbda06a8d62e83.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/af2c74fa3dfe1e3924dbda06a8d62e83.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/af2c74fa3dfe1e3924dbda06a8d62e83.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/b9a9d0e92c1002903bce3ca7fc1db54d.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/b9a9d0e92c1002903bce3ca7fc1db54d.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/b9a9d0e92c1002903bce3ca7fc1db54d.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/bebba21f399e9cb39e16c0f066ef9db5.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/bebba21f399e9cb39e16c0f066ef9db5.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/bebba21f399e9cb39e16c0f066ef9db5.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/c8de5bcf58d65582254addda5d47c9ce.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/c8de5bcf58d65582254addda5d47c9ce.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/c8de5bcf58d65582254addda5d47c9ce.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/cd5d44ec5db24bba84a1f5f16c0e281c.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/cd5d44ec5db24bba84a1f5f16c0e281c.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/cd5d44ec5db24bba84a1f5f16c0e281c.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/d697df38525a6a8a70165422e294eeb1.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/d697df38525a6a8a70165422e294eeb1.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/d697df38525a6a8a70165422e294eeb1.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/d8ee95fc8b8616d71f90a65fe9ccd06b.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/d8ee95fc8b8616d71f90a65fe9ccd06b.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/d8ee95fc8b8616d71f90a65fe9ccd06b.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/e38157779608205c734c7e9eb0846378.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/e38157779608205c734c7e9eb0846378.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/e38157779608205c734c7e9eb0846378.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/e6b8100a54ceaa5e533e9320c9a91804.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/e6b8100a54ceaa5e533e9320c9a91804.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/e6b8100a54ceaa5e533e9320c9a91804.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/ee9b2e409669b51b95a942504c0d5836.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/ee9b2e409669b51b95a942504c0d5836.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/ee9b2e409669b51b95a942504c0d5836.png differ diff --git a/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/fe201e339158d8d001c18c1aef203acd.png b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/fe201e339158d8d001c18c1aef203acd.png new file mode 100644 index 000000000..1ecf7c90b Binary files /dev/null and b/apps/web/e2e/test-results-storybook/.playwright-artifacts-1/fe201e339158d8d001c18c1aef203acd.png differ diff --git a/apps/web/e2e/test-results-visual/.last-run.json b/apps/web/e2e/test-results-visual/.last-run.json new file mode 100644 index 000000000..cbcc1fbac --- /dev/null +++ b/apps/web/e2e/test-results-visual/.last-run.json @@ -0,0 +1,4 @@ +{ + "status": "passed", + "failedTests": [] +} \ No newline at end of file diff --git a/apps/web/src/app/App.tsx b/apps/web/src/app/App.tsx index 0edb8c371..cdf2de41d 100644 --- a/apps/web/src/app/App.tsx +++ b/apps/web/src/app/App.tsx @@ -107,26 +107,18 @@ export function App() { }, [setTheme, theme, language, setLanguage]); // P1.2: Initialize auth state before rendering app - // This prevents race condition where router renders before auth is checked + // With httpOnly cookies we cannot read tokens in JS; always call refreshUser() + // so getMe() is used to verify auth (cookies sent automatically). useEffect(() => { const initAuth = async () => { try { - // Check if user has tokens - const { hasTokens } = await import('@/services/tokenStorage').then( - (m) => ({ hasTokens: m.TokenStorage.hasTokens() }) - ); - - if (hasTokens) { - // Wait for auth check to complete - await refreshUser(); - } + await refreshUser(); } catch (error) { logger.error('[App] Auth initialization failed', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, }); } finally { - // Always set ready, even if auth check fails setIsAuthReady(true); } }; diff --git a/apps/web/src/config/env.ts b/apps/web/src/config/env.ts index e0310e98d..ba620c2af 100644 --- a/apps/web/src/config/env.ts +++ b/apps/web/src/config/env.ts @@ -75,6 +75,25 @@ const parseEnv = () => { // Variables d'environnement validées const validatedEnv = parseEnv(); +// En dev, alerter si l'API est en cross-origin : les cookies ne seront pas envoyés (SameSite), +// ce qui provoque 401 après login et redirections en boucle. Utiliser VITE_API_URL=/api/v1 (proxy). +if (import.meta.env.DEV && typeof window !== 'undefined') { + const apiUrl = validatedEnv.VITE_API_URL; + if (apiUrl.startsWith('http')) { + try { + const apiOrigin = new URL(apiUrl).origin; + if (window.location.origin !== apiOrigin) { + logger.warn( + '[Config] API is cross-origin: cookies will not be sent, login may fail or redirect in a loop. Use VITE_API_URL=/api/v1 so the Vite proxy is used (same origin).', + { apiOrigin, pageOrigin: window.location.origin } + ); + } + } catch { + // ignore invalid URL + } + } +} + // Export de l'objet env avec types export const env = { API_URL: validatedEnv.VITE_API_URL, diff --git a/apps/web/src/features/auth/hooks/useLogin.ts b/apps/web/src/features/auth/hooks/useLogin.ts index ed5ee4e33..372908be0 100644 --- a/apps/web/src/features/auth/hooks/useLogin.ts +++ b/apps/web/src/features/auth/hooks/useLogin.ts @@ -10,31 +10,22 @@ export const useLogin = () => { return useMutation({ mutationFn: async (credentials: LoginRequest) => { - // loginStore appelle déjà loginService et met à jour le store - // Il attend aussi que la persistance soit complète - await loginStore(credentials); + const loginResponse = await loginStore(credentials); + const user = loginResponse.user; - // Vérifier que le store est bien mis à jour après la persistance - const { isAuthenticated } = useAuthStore.getState(); - if (!isAuthenticated) { - // Attendre un peu plus et réessayer - await new Promise((resolve) => setTimeout(resolve, 100)); - const retryState = useAuthStore.getState(); - if (!retryState.isAuthenticated) { - throw new Error( - 'Login failed: user not authenticated after persistence', - ); - } - } - - // Fetch user data and update React Query cache - const user = await getMe(); + // Populate React Query cache so the app has user data immediately queryClient.setQueryData(['user', 'me'], user); - - return { - user, - isAuthenticated, - }; + + // Optionally refresh from /auth/me; do not fail login if this fails (e.g. backend delay) + getMe() + .then((freshUser) => { + queryClient.setQueryData(['user', 'me'], freshUser); + }) + .catch(() => { + // Keep using user from login response + }); + + return { user, isAuthenticated: true }; }, }); }; diff --git a/apps/web/src/features/auth/pages/LoginPage.tsx b/apps/web/src/features/auth/pages/LoginPage.tsx index 81639d6f0..2fe7c21e5 100644 --- a/apps/web/src/features/auth/pages/LoginPage.tsx +++ b/apps/web/src/features/auth/pages/LoginPage.tsx @@ -7,10 +7,27 @@ import { OAuthButton } from '../components/OAuthButton'; import { useLogin } from '../hooks/useLogin'; import type { LoginFormData } from '../types'; import { logger } from '@/utils/logger'; +import { formatErrorMessage as formatApiErrorMessage } from '@/utils/apiErrorHandler'; +import type { ApiError } from '@/schemas/apiSchemas'; import { CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { AlertCircle } from 'lucide-react'; import { AuthLayout } from '../components/AuthLayout'; +function getLoginErrorMessage(error: unknown): string { + if (error == null) return ''; + if (typeof error === 'object' && error !== null && 'message' in error && 'code' in error) { + return formatApiErrorMessage(error as ApiError); + } + if (error instanceof Error) { + const msg = error.message?.toLowerCase() ?? ''; + if (msg.includes('invalid credentials') || msg.includes('401')) return 'Incorrect email or password'; + if (msg.includes('email not verified')) return "Your email is not verified. Check your inbox."; + if (msg.includes('network')) return 'Connection error. Check your internet.'; + return error.message || 'An error occurred. Please try again.'; + } + return String(error); +} + export function LoginPage() { const navigate = useNavigate(); const { isAuthenticated, isLoading } = useAuthStore(); @@ -117,23 +134,6 @@ export function LoginPage() { window.location.href = `/api/v1/auth/oauth/${provider}`; }; - const formatErrorMessage = (error: Error | null): string => { - if (!error) return ''; - const message = error.message.toLowerCase(); - const errorString = error.toString().toLowerCase(); - - if (message.includes('invalid credentials') || message.includes('401') || errorString.includes('401')) { - return 'Incorrect email or password'; - } - if (message.includes('email not verified')) { - return "Your email is not verified. Check your inbox."; - } - if (message.includes('network')) { - return 'Connection error. Check your internet.'; - } - return 'An error occurred. Please try again.'; - }; - return ( -

{formatErrorMessage(error)}

+

{getLoginErrorMessage(error)}

)} @@ -183,18 +183,18 @@ export function LoginPage() { /> -
-
+
+
- Forgot password? + Remember me + + Forgot password?
diff --git a/apps/web/src/features/auth/store/authStore.ts b/apps/web/src/features/auth/store/authStore.ts index 596dc1617..c2ff1b57a 100644 --- a/apps/web/src/features/auth/store/authStore.ts +++ b/apps/web/src/features/auth/store/authStore.ts @@ -6,6 +6,7 @@ import { logout as logoutService, getMe, type LoginRequest, + type LoginResponse, type RegisterRequest, } from '@/services/api/auth'; import { TokenStorage } from '@/services/tokenStorage'; @@ -26,7 +27,7 @@ export interface AuthState { } export interface AuthActions { - login: (credentials: LoginRequest) => Promise; + login: (credentials: LoginRequest) => Promise; register: (userData: RegisterRequest) => Promise; logout: () => Promise; logoutLocal: () => void; // Logout local sans appel API (pour éviter les boucles) @@ -54,25 +55,22 @@ export const useAuthStore = create()( login: async (credentials: LoginRequest) => { set({ isLoading: true, error: null }); try { - // Le service auth gère déjà le stockage des tokens - // Action 4.1.1.5: user field removed - user data managed by React Query - // Response contains user data but we don't store it (React Query handles that) - await loginService(credentials); + const response = await loginService(credentials); - // Mettre à jour l'état de manière atomique pour éviter les problèmes de timing set({ isAuthenticated: true, isLoading: false, error: null, }); - // Récupérer le token CSRF après login csrfService.refreshToken().catch((error) => { logger.warn('Failed to fetch CSRF token after login', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, }); }); + + return response; } catch (error: unknown) { set({ error: parseApiError(error), @@ -176,26 +174,15 @@ export const useAuthStore = create()( refreshUser: async () => { // Action 4.3.1.2: Simplified using React Query - no manual promise deduplication needed - // React Query's useUser hook handles deduplication automatically at the query level const currentState = useAuthStore.getState(); - - if (!TokenStorage.hasTokens()) { - // CRITIQUE FIX #2: Ne réinitialiser que si on n'était pas déjà authentifié - if (!currentState.isAuthenticated) { - set({ isAuthenticated: false, isLoading: false }); - } - return; - } - // CRITIQUE FIX #2: Ne pas réinitialiser isAuthenticated si on était déjà authentifié - // Cela évite les problèmes de timing après le login et la navigation const hasAuth = currentState.isAuthenticated; + // SECURITY: With httpOnly cookies, hasTokens() is always false in JS. + // Always try getMe() to verify auth; cookies are sent automatically. set({ isLoading: true }); try { - // Verify authentication by calling getMe() - // User data is managed by React Query (useUser hook), not stored here - // React Query will deduplicate this call if useUser hook is already fetching + // Verify authentication by calling getMe() (cookies sent automatically) await getMe(); set({ isAuthenticated: true, diff --git a/apps/web/src/mocks/handlers.ts b/apps/web/src/mocks/handlers.ts index d4bb18d3c..f2f7478f0 100644 --- a/apps/web/src/mocks/handlers.ts +++ b/apps/web/src/mocks/handlers.ts @@ -45,8 +45,6 @@ export const handlers = [ http.post('*/api/v1/auth/login', async () => { return HttpResponse.json({ - access_token: 'mock_access_token_generic', - refresh_token: 'mock_refresh_token_generic', user: { id: 1, username: 'StorybookUser', @@ -54,6 +52,11 @@ export const handlers = [ created_at: '2024-01-01T00:00:00Z', avatar_url: 'https://i.pravatar.cc/150?u=1', }, + token: { + access_token: 'mock_access_token_generic', + refresh_token: 'mock_refresh_token_generic', + expires_in: 3600, + }, }); }), diff --git a/apps/web/src/services/api/client.ts b/apps/web/src/services/api/client.ts index a63339064..7c89dab03 100644 --- a/apps/web/src/services/api/client.ts +++ b/apps/web/src/services/api/client.ts @@ -1247,10 +1247,11 @@ apiClient.interceptors.response.use( } // INT-AUTH-003: Détecter 401 et refresh automatiquement - // EXCLURE l'endpoint /auth/refresh pour éviter les boucles infinies - // EXCLURE aussi /auth/logout car si le logout échoue, on ne doit pas rafraîchir le token + // EXCLURE /auth/refresh et /auth/logout pour éviter les boucles. + // EXCLURE /auth/me : 401 = non connecté ; ne pas tenter de refresh ni rediriger (sinon boucle rechargement). const isRefreshEndpoint = originalRequest?.url?.includes('/auth/refresh'); const isLogoutEndpoint = originalRequest?.url?.includes('/auth/logout'); + const isAuthMeEndpoint = originalRequest?.url?.includes('/auth/me'); // INT-AUTH-003: Handle 401 and 400 on /auth/refresh endpoint - token expired/revoked/invalid, logout and redirect // FIX: Gérer aussi les erreurs 400 (Bad Request) qui indiquent un refresh token invalide @@ -1340,7 +1341,8 @@ apiClient.interceptors.response.use( originalRequest && !originalRequest._retry && !isRefreshEndpoint && - !isLogoutEndpoint + !isLogoutEndpoint && + !isAuthMeEndpoint ) { // INT-AUTH-003: Éviter les refresh multiples simultanés if (isRefreshing) { @@ -1732,22 +1734,22 @@ apiClient.interceptors.response.use( const apiError = parseApiError(error); // Action 3.2.1.4: Auth errors redirect to login - // Handle 401 errors that didn't trigger refresh (e.g., no originalRequest, already retried, etc.) - // EXCLURE aussi /auth/logout pour éviter les boucles - if (status === 401 && !isRefreshEndpoint && !isLogoutEndpoint && typeof window !== 'undefined') { + // isAuthMeEndpoint déjà défini plus haut : on ne redirige pas pour /auth/me (401 = non connecté, pas de redirect) + if ( + status === 401 && + !isRefreshEndpoint && + !isLogoutEndpoint && + !isAuthMeEndpoint && + typeof window !== 'undefined' + ) { const errorCategory = getErrorCategory(apiError); if (errorCategory === 'authentication') { - // Clear tokens TokenStorage.clearTokens(); csrfService.clearToken(); - // Clear auth store state - // FIX: Utiliser logoutLocal() pour éviter les boucles infinies import('@/features/auth/store/authStore') .then(({ useAuthStore }) => { const store = useAuthStore.getState(); - // Utiliser logoutLocal() au lieu de logout() pour éviter les appels API - // qui déclencheraient à nouveau le refresh store.logoutLocal(); }) .catch((err: unknown) => { @@ -1756,12 +1758,10 @@ apiClient.interceptors.response.use( }); }); - // Store error message for display after redirect sessionStorage.setItem( 'auth_error', 'Votre session a expiré. Veuillez vous reconnecter.', ); - // Redirect to login window.location.href = '/login'; } } diff --git a/apps/web/src/utils/apiErrorHandler.ts b/apps/web/src/utils/apiErrorHandler.ts index 5096175d0..3b79559f7 100644 --- a/apps/web/src/utils/apiErrorHandler.ts +++ b/apps/web/src/utils/apiErrorHandler.ts @@ -242,6 +242,17 @@ export function parseApiError(error: unknown): ApiError { }; } + if (status === 423) { + const data = responseData as { message?: string } | null; + return { + code: 423, + message: + data?.message || + 'This action cannot be completed right now. The resource may be locked or your account may be temporarily restricted. Please try again later.', + timestamp: new Date().toISOString(), + }; + } + // Erreur HTTP sans format standardisé const data = responseData as { message?: string } | null; return { @@ -357,7 +368,10 @@ export function formatErrorMessage( error: ApiError, includeRequestId: boolean = false, ): string { - let message = error.message; + const baseMessage = + typeof error.message === 'string' ? error.message : 'An error occurred'; + + let message = baseMessage; // Si l'erreur a des détails de validation, les inclure if ( @@ -366,9 +380,17 @@ export function formatErrorMessage( error.details.length > 0 ) { const detailsMessages = error.details - .map((detail) => `${detail.field}: ${detail.message}`) + .map((detail) => { + const f = + typeof detail.field === 'string' ? detail.field : String(detail.field); + const m = + typeof detail.message === 'string' + ? detail.message + : String(detail.message); + return `${f}: ${m}`; + }) .join(', '); - message = `${error.message} (${detailsMessages})`; + message = `${baseMessage} (${detailsMessages})`; } // Action 5.3.1.1: Always include request_id when requested (not just in development) diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 0795366dc..18dc6ee80 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -7,8 +7,11 @@ import { visualizer } from 'rollup-plugin-visualizer' // https://vitejs.dev/config/ export default defineConfig(({ mode }) => { const isProduction = mode === 'production' + const projectRoot = path.resolve(__dirname) return { + // Ensure dev server and dep scan use apps/web only (avoid picking up storybook-static when run from monorepo root) + root: projectRoot, plugins: [ react(), // Bundle analyzer for production builds @@ -39,6 +42,15 @@ export default defineConfig(({ mode }) => { server: { port: 5173, host: true, + // Allow dev access via local domain names (e.g. /etc/hosts: 127.0.0.1 veza.fr) + allowedHosts: ['veza.fr', 'veza.com', 'veza.talas.fr', 'veza.talas.com'], + // Exclude Storybook build output from watch and fs access so dep scan never touches it + watch: { + ignored: ['**/storybook-static/**', '**/dist_verification/**'], + }, + fs: { + deny: ['**/storybook-static/**', '**/dist_verification/**'], + }, // P2.1: Proxy API requests to backend in development // This eliminates CORS issues in dev by making all requests same-origin proxy: { @@ -78,7 +90,9 @@ export default defineConfig(({ mode }) => { }, // Standard optimization settings usually work best optimizeDeps: { - include: ['react', 'react-dom'] + include: ['react', 'react-dom'], + // Only scan from app entry; avoid storybook-static (and other build outputs) being picked up as entries + entries: ['index.html', 'src/main.tsx'], }, } }) \ No newline at end of file diff --git a/docs/FRONTEND_AUDIT_VISUAL.md b/docs/FRONTEND_AUDIT_VISUAL.md new file mode 100644 index 000000000..cfda9ecf7 --- /dev/null +++ b/docs/FRONTEND_AUDIT_VISUAL.md @@ -0,0 +1,193 @@ +# Audit visuel exhaustif du frontend Veza + +**Date:** 2026-02-07 +**Objectif:** Identifier les causes précises de la "mocheté" perçue (layout, composants, couleurs, contrastes, typographie, cohérence). + +--- + +## 1. Résumé exécutif + +Le frontend souffre de **plusieurs facteurs cumulés** : palette d’accents incohérente (teal + magenta/purple + vert + rouge), manque de profondeur (cartes trop plates), éléments "placeholder" visibles (ex. "0%" en rouge partout), barre de lecture disproportionnée, et typographie potentiellement dégradée (Rajdhani + erreurs glyph). Les correctifs ciblent des fichiers et variables précis ci-dessous. + +--- + +## 2. Palette et couleurs + +### 2.1 Incohérence des couleurs d’accent + +| Contexte | Couleur utilisée | Fichier / token | Problème | +|----------|------------------|-----------------|----------| +| Élément actif sidebar, bouton play, "NETWORK STABLE" | **Teal / cyan** (primary) | `--primary: oklch(0.75 0.18 195)` dans `index.css` | Cohérent comme accent principal. | +| Badges sidebar (Live Sessions 3, Channels 12) | **Magenta / violet** (`secondary`) | `Sidebar.tsx` L195 : `bg-secondary/20 text-secondary` ; `--secondary: oklch(0.65 0.25 330)` | **Hors palette** par rapport au teal ; donne une impression de "troisième couleur" non intégrée. | +| Pourcentages positifs, "ACTIVE", "NETWORK STABLE" (dot) | **Vert** (lime/success) | `StatCard.tsx` (lime), succès sémantique | Un **vert** distinct du teal pour "positif" crée une **double convention** (teal vs vert) pour des états similaires. | +| Tendances négatives, "Expired Warranty", Sign Out | **Rouge** (destructive) | `AdminDashboardStatCard.tsx`, `Sidebar.tsx` (Live icon) | Correct sémantiquement mais **trop présent** si utilisé aussi pour "0%" (voir §3). | + +**Recommandations :** + +- **Badges sidebar** : remplacer `secondary` (magenta) par une variante du primary (ex. `primary` ou `cyan-500`) ou un token dédié "badge" aligné sur la charte. Fichier : `apps/web/src/components/layout/Sidebar.tsx` (L194–201). +- **États "positif"** : unifier soit sur teal, soit sur vert, et documenter (ex. teal = interactif/actif, vert = succès/variation positive uniquement). + +### 2.2 Manque de profondeur (cartes, fonds) + +- **Cartes dashboard** : variante `glass` ou `default` avec bordures/ombres très légères (`border-white/5`, `shadow-black/5`). Fichiers : `components/ui/card.tsx` (variants `glass`, `default`), `AdminDashboardStatCard.tsx`, `AdminDashboardTrafficCard.tsx`. +- **Recherche header** : `bg-muted/50 border border-border` — contraste faible avec le fond, la zone "Search Network..." se fond dans le fond. +- **Dark mode** : `--card: oklch(0.18 0.02 265)` très proche de `--background: oklch(0.15 0.02 265)` dans `index.css` (.dark), donc **peu de relief**. + +**Recommandations :** + +- Augmenter légèrement la différence luminance card vs background (ex. card à 0.20–0.22, background 0.15). +- Donner aux cartes une bordure ou une ombre un peu plus marquée (ex. `border-border` plus visible, `shadow-lg` avec teinte légère). +- Barre de recherche : fond ou bordure un peu plus marqués pour l’affordance (ex. `bg-card` ou `border-white/10`). + +### 2.3 Fichiers à modifier (couleurs) + +- `apps/web/src/index.css` : variables `.dark` (--card, --background), éventuellement --border. +- `apps/web/src/components/layout/Sidebar.tsx` : classes des badges (remplacer secondary par primary/cyan). +- `apps/web/src/components/layout/Header.tsx` : input search (classes bg/border). + +--- + +## 3. Composants "placeholder" ou trompeurs + +### 3.1 "0%" en rouge sur toutes les cartes (Admin) + +- **Comportement** : `AdminDashboardStatCard` affiche un badge de tendance (`trend`) avec `trend > 0` → vert, sinon **rouge**. Si l’API ne renvoie pas de tendances (ou renvoie 0), on obtient **"0%" en rouge sur chaque carte**. +- **Fichiers** : `AdminDashboardStatCard.tsx` (L46–57), `AdminDashboardView.tsx` (L48–50 : `trend: stats.trends?.users` etc.), `useAdminDashboardView.ts` (stats venant de l’API). +- **Impact** : ressemble à une erreur ou à une donnée non implémentée, ce qui renforce l’impression d’interface inachevée. + +**Recommandations :** + +- Ne pas afficher le badge de tendance quand `trend === undefined` (ou null). Afficher "0%" seulement si la métrique a du sens (ex. "0% de variation" explicite). +- Si `trend === 0`, éviter le style "erreur" (rouge) : utiliser un style neutre (muted) ou masquer. + +### 3.2 Graphique "Traffic Flux" + +- **Comportement** : `AdminDashboardTrafficCard` utilise des barres **aléatoires** (`Math.random()`) et des labels factices ("SYS_INIT", "BUFFERING_NODES...", "LIVE_DATA"). Aucune donnée réelle, aucun axe Y, pas de grille lisible. +- **Fichier** : `apps/web/src/components/admin/admin-dashboard-view/AdminDashboardTrafficCard.tsx`. +- **Impact** : l’intitulé "HOLOGRAPHIC STREAMING INTERFACE" promet un élément avancé alors que le rendu est clairement un placeholder. + +**Recommandations :** + +- Soit brancher de vraies données + axes + légende claire, soit remplacer par un message du type "Données à venir" ou un skeleton, et éviter un faux graphique. + +### 3.3 Bouton "Sign In" alors que l’utilisateur est connecté + +- **Constat** (d’après captures) : un bouton "Sign In" peut apparaître à côté d’un utilisateur déjà identifié (ex. "vezadev"). +- **À vérifier** : `Header.tsx` / `Navbar.tsx` — affichage conditionnel du bouton de connexion vs profil. S’assurer que "Sign In" n’est affiché que lorsque `!isAuthenticated`. + +--- + +## 4. Layout et espacement + +### 4.1 Barre de lecture (MiniPlayer / GlobalPlayer) + +- **Taille** : `MiniPlayer` utilise `h-24` (96px) en barre fixe. Les contrôles (notamment le bouton play) sont très mis en avant (teal, grande taille). +- **Impact** : la barre occupe une part importante de la hauteur et attire trop l’attention sur les pages où la lecture n’est pas le focus (ex. Gear Locker, Academy, Admin). +- **Fichiers** : `apps/web/src/components/player/MiniPlayer.tsx` (L36 : `h-24`), `PlayerControls.tsx`, `PlayPauseButton.tsx`. + +**Recommandations :** + +- Réduire la hauteur sur desktop (ex. `h-20` ou `h-18`) et/ou rendre le contraste du bouton play un peu moins fort (même teal mais moins saturé ou plus petit). +- Barre de progression : déjà fine ; envisager une hauteur un peu plus visible pour la partie "remplie" (accessibilité + lisibilité). + +### 4.2 Espacement entre sections (sidebar) + +- **Constat** : les blocs "MY STUDIO", "VEZA NETWORK", etc. ont un espacement vertical serré entre le titre de section et le premier lien. +- **Fichier** : `apps/web/src/components/layout/Sidebar.tsx` (structure des sections). +- **Recommandation** : ajouter un peu de marge au-dessus des titres de section (ex. `mt-4` ou `space-y-1` entre titre et premier item) pour clarifier la hiérarchie. + +### 4.3 Cartes dashboard (Command Center) + +- **Constat** : les quatre petites cartes (Tracks Listened, Messages Sent, etc.) sont serrées ; le texte et les pourcentages peuvent sembler denses. +- **Fichiers** : vues dashboard qui utilisent `StatCard` ou équivalent ; grille (ex. `grid-cols-4`, `gap-6`). +- **Recommandation** : garder les layout primitives (pas de valeurs arbitraires) mais ajuster `gap` ou `padding` des cartes pour plus de respiration (ex. `p-6` déjà présent, éventuellement `gap-8`). + +--- + +## 5. Typographie + +### 5.1 Police Rajdhani + +- **Usage** : `--font-sans: 'Rajdhani', ...` dans `index.css` (@theme inline). Utilisée pour le corps et une grande partie de l’UI. +- **Problème connu** : les erreurs console "downloadable font: Glyph bbox was incorrect" (Rajdhani) indiquent des **glyphes mal déclarés** dans le fichier de police. Conséquences possibles : rendu moins net, décalages, ou fallback partiel vers une autre police. +- **Fichiers** : `index.html` (lien Google Fonts), `apps/web/src/index.css` (--font-sans). + +**Recommandations :** + +- Vérifier la source de la police (version, subset) et si possible utiliser une version mise à jour ou un autre fournisseur. +- En parallèle, prévoir un fallback explicite (ex. `'Rajdhani', 'Inter', system-ui, sans-serif`) pour limiter les effets si Rajdhani pose problème. + +### 5.2 Hiérarchie et lisibilité + +- **Texte secondaire** : `text-muted-foreground` (oklch(0.70 0.01 265) en dark) peut être trop proche du fond sur certains écrans, ce qui réduit le contraste et la hiérarchie. +- **Recommandation** : augmenter très légèrement la luminance ou le contraste de `--muted-foreground` en dark (ex. 0.72–0.75) et valider avec un outil WCAG. + +--- + +## 6. Contrastes et accessibilité + +### 6.1 Éléments à faible contraste + +- **Bordures** : `border-white/5`, `border-white/10` — très subtiles, peu visibles pour certains utilisateurs. +- **Icônes** : petites icônes en `text-muted-foreground` dans les cartes et le player ; contraste insuffisant pour une identification rapide. +- **Progress bar** (player) : barre de progression très fine ; partie "remplie" (teal) lisible, mais le rail peut manquer de contraste. + +**Recommandations :** + +- Utiliser au minimum `border-white/10` pour les séparations importantes, et réserver `white/5` aux détails purement décoratifs. +- Icônes secondaires : envisager une couleur un peu plus claire (ex. `text-foreground/70`) ou une taille légèrement supérieure pour les actions importantes. + +### 6.2 Champs mot de passe en HTTP + +- **Constat** : message navigateur "Password fields present on an insecure (http://) page" en dev. Ce n’est pas un problème de design mais de contexte (HTTPS en prod recommandé). + +--- + +## 7. Cohérence et système de design + +### 7.1 Double jeu de tokens (KŌDŌ vs design-tokens) + +- **index.css** : variables type `--primary`, `--cyan-500`, `--card`, etc. (oklch). +- **design-tokens.css** : variables `--kodo-void`, `--kodo-cyan`, `--kodo-text-dim`, etc. (rgb). +- **Composants** : certains utilisent `primary` / `cyan-500`, d’autres `text-kodo-cyan`, `bg-kodo-steel`, etc. (ex. `StatCard.tsx` : `text-kodo-cyan`, `bg-kodo-steel/10`). +- **Risque** : dérives de teintes entre les deux systèmes et maintenance plus difficile. + +**Recommandation :** + +- À moyen terme, unifier sur un seul jeu de tokens (idéalement celui de `index.css` étendu en Tailwind) et migrer progressivement les `kodo-*` vers les tokens sémantiques (primary, muted, etc.). + +### 7.2 Variantes de cartes + +- **card.tsx** propose plusieurs variants : `default`, `elevated`, `ghost`, `outline`, `muted`, `glass`, `interactive`, `glow`, `glowMagenta`, `spotlight`. L’usage de `glass` partout (admin, etc.) donne un rendu très uniforme et plat. +- **Recommandation** : utiliser `default` ou `elevated` pour les cartes de contenu principal afin de retrouver un peu d’ombre et de relief, et réserver `glass` à des blocs spécifiques (panneaux, overlays). + +--- + +## 8. Synthèse des actions prioritaires + +| Priorité | Action | Fichier(s) principal(aux) | +|----------|--------|----------------------------| +| P0 | Unifier la couleur des badges sidebar (primary au lieu de secondary) | `Sidebar.tsx` | +| P0 | Ne pas afficher le badge "0%" en rouge quand trend est 0 ou undefined ; style neutre ou masqué | `AdminDashboardStatCard.tsx` | +| P1 | Donner plus de relief aux cartes (card vs background, bordure/ombre) | `index.css` (.dark), `card.tsx` | +| P1 | Remplacer ou clarifier le graphique "Traffic Flux" (données réelles ou placeholder explicite) | `AdminDashboardTrafficCard.tsx` | +| P1 | Vérifier l’affichage "Sign In" quand l’utilisateur est connecté | `Header.tsx`, `Navbar.tsx` | +| P2 | Réduire la prééminence visuelle du player (hauteur, taille du bouton play) | `MiniPlayer.tsx`, `PlayerControls.tsx` | +| P2 | Améliorer l’affordance de la barre de recherche (fond/bordure) | `Header.tsx` | +| P2 | Renforcer le contraste du texte secondaire et des bordures en dark | `index.css` | +| P3 | Unifier les tokens (kodo-* vs primary/muted) et documenter la charte | `design-tokens.css`, `index.css`, composants | +| P3 | Corriger ou contourner les glyphes Rajdhani (source font, fallback) | `index.html`, `index.css` | + +--- + +## 9. Fichiers modifiables (référence rapide) + +- **Couleurs / thème** : `apps/web/src/index.css` (variables :root et .dark). +- **Sidebar** : `apps/web/src/components/layout/Sidebar.tsx`. +- **Header** : `apps/web/src/components/layout/Header.tsx`. +- **Cartes** : `apps/web/src/components/ui/card.tsx` ; `AdminDashboardStatCard.tsx`, `AdminDashboardTrafficCard.tsx`. +- **Player** : `apps/web/src/components/player/MiniPlayer.tsx`, `PlayerControls.tsx`, `PlayPauseButton.tsx`. +- **Dashboard** : `apps/web/src/components/admin/admin-dashboard-view/AdminDashboardView.tsx`, `useAdminDashboardView.ts`. +- **Typographie** : `apps/web/index.html` (fonts), `apps/web/src/index.css` (--font-sans, @layer base). + +Cet audit peut servir de base pour des tickets (P0 → P3) et pour une checklist avant refonte visuelle plus large. diff --git a/docs/MONOREPO_ORCHESTRATION.md b/docs/MONOREPO_ORCHESTRATION.md new file mode 100644 index 000000000..b8030ddc0 --- /dev/null +++ b/docs/MONOREPO_ORCHESTRATION.md @@ -0,0 +1,111 @@ +# Orchestration et gestion du monorepo Veza + +Ce document décrit la structure du Makefile modulaire et recommande des outils open source pour orchestrer le projet pendant son développement. + +--- + +## 1. Structure actuelle du Makefile + +### Organisation + +| Fichier | Rôle | +|--------|------| +| **Makefile** | Point d’entrée unique ; inclut les fragments et définit le goal par défaut (`help`) | +| **make/config.mk** | **Source de vérité** : services, ports, chemins (SERVICE_DIR_*, PORT_*, COMPOSE_*, ROOT, etc.) | +| **make/ui.mk** | Couleurs et `ECHO_CMD` | +| **make/help.mk** | Cible `help` et dashboard | +| **make/tools.mk** | `check-tools`, `install-tools`, `install-deps`, `check-ports` | +| **make/infra.mk** | `infra-up`, `infra-down`, `db-migrate`, `db-shell`, `redis-shell` | +| **make/dev.mk** | `dev`, `dev-backend`, `dev-web`, `dev-`, `stop-local-services` | +| **make/build.mk** | `build-*`, `build-all`, `build-all-native`, `build-service` | +| **make/test.mk** | `test`, `test-`, `lint`, `lint-`, `fmt`, `status` | +| **make/services.mk** | `start-service`, `stop-service`, `restart-service`, `logs-service` (Docker) | +| **make/high.mk** | `setup`, `stop-all`, `clean`, `deploy-docker`, `deploy-incus`, `status-full`, `web-minimal` | +| **make/incus.mk** | Toutes les cibles Incus (network, deploy, start, stop, status, logs) | + +### Personnalisation + +- **Ajouter un service** : dans `make/config.mk`, ajouter le nom dans `SERVICES`, puis définir `SERVICE_DIR_` et `PORT_` si besoin. Adapter les règles dans les `.mk` qui font du case-by-service (ex. `dev.mk`, `build.mk`). +- **Changer un port** : dans `make/config.mk` ou via `.env` (ex. `PORT_web=3000`). +- **Changer un chemin** : modifier `SERVICE_DIR_` dans `make/config.mk`. +- **Override global** : utiliser `.env` (chargé par `config.mk` avec `-include .env`). + +### Cibles par service + +- `make dev-web`, `make dev-backend-api`, `make dev-chat-server`, `make dev-stream-server` +- `make test-web`, `make test-backend-api`, … +- `make lint-web`, `make lint-backend-api`, … +- `make build-service SERVICE=backend-api` + +--- + +## 2. Outils open source recommandés + +En complément (ou en partie en remplacement) du Makefile, ces outils peuvent aider à orchestrer le monorepo. + +### 2.1 Turborepo (JS/TS) + +- **Site** : [turbo.build](https://turbo.build) +- **Rôle** : Cache des tâches et pipeline de build/test/lint pour les workspaces npm. +- **Atouts** : Cache distant optionnel, parallélisation, `turbo run build --filter=./apps/web`, intégration CI simple. +- **Limites** : Ciblé npm/pnpm workspaces (apps/web, packages/design-system). Les backends Go/Rust restent gérés par le Makefile ou des scripts. +- **Usage typique** : `turbo run build`, `turbo run test --filter=web`, `turbo run lint`. + +À envisager si tu veux un cache et un graphe de tâches fiable pour la partie Node/TS, tout en gardant Make pour l’infra et les backends. + +--- + +### 2.2 Nx + +- **Site** : [nx.dev](https://nx.dev) +- **Rôle** : Monorepo “full stack” : graphe de dépendances, cache, affected (build/test uniquement sur ce qui a changé), plugins (React, Node, Go, etc.). +- **Atouts** : Très flexible, “affected” puissant, support multi-langage (y compris Go via plugins ou custom targets). +- **Limites** : Plus lourd à configurer et à maintenir que Turborepo ; courbe d’apprentissage plus forte. + +Utile si le monorepo grossit encore et que tu veux “affected”, cache partagé et une seule interface pour build/test/lint (y compris backends). + +--- + +### 2.3 Just (command runner) + +- **Site** : [github.com/casey/just](https://github.com/casey/just) +- **Rôle** : Remplacer une partie des cibles Make par un fichier `justfile` (syntaxe plus lisible, arguments nommés, pas de tabs). +- **Atouts** : Scripts lisibles, facile à partager entre devs, multiplateforme (Windows possible). +- **Limites** : Pas de vrai “graphe de tâches” ni cache ; plutôt un complément ou un remplacement léger du Make pour les commandes métier. + +Exemple : `just dev`, `just test web`, `just deploy docker`. + +--- + +### 2.4 Mise (ex-asdf) + +- **Site** : [mise.jdx.dev](https://mise.jdx.dev) +- **Rôle** : Gestion des versions des runtimes (Node, Go, Rust, Python) au niveau du repo ou de la machine. +- **Atouts** : Un seul outil pour `.node-version`, `.go-version`, etc. ; reproductibilité des envs entre dev et CI. +- **Limites** : Ne remplace pas le Makefile ; il assure seulement que les bonnes versions sont actives. + +Recommandé pour figer Node/Go/Rust et éviter les “ça marche chez moi”. + +--- + +### 2.5 Tâche (Task) + +- **Site** : [taskfile.dev](https://taskfile.dev) +- **Rôle** : Alternative au Make en YAML, avec variables, includes, dépendances entre tâches. +- **Atouts** : Syntaxe YAML, parallélisation, bon pour des pipelines déclaratifs. +- **Limites** : Un outil de plus ; si le Make modulaire te convient, pas obligatoire. + +--- + +## 3. Synthèse + +| Besoin | Outil suggéré | +|--------|----------------| +| Un seul endroit pour config (ports, services, chemins) | **make/config.mk** (déjà en place) | +| Cache + pipeline pour JS/TS uniquement | **Turborepo** | +| Affected + cache + multi-langage (y compris Go/Rust) | **Nx** | +| Commandes lisibles, peu de dépendance à Make | **Just** | +| Versions Node/Go/Rust reproductibles | **Mise** | +| Tout garder en Make mais mieux structuré | **Makefile + make/*.mk** (actuel) | + +Recommandation pragmatique : garder le **Makefile modulaire** comme entrée principale (infra, dev, deploy, Incus). Si la partie frontend/npm devient la plus coûteuse (builds, tests, lint), ajouter **Turborepo** pour les workspaces npm et appeler `turbo` depuis le Makefile si besoin (ex. `make test` → infra-up + turbo run test + tests Go/Rust). Pour les versions des runtimes, **Mise** (ou asdf) dans le repo et en CI améliore la reproductibilité sans toucher au reste. diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 000000000..78265f6e8 --- /dev/null +++ b/go.work.sum @@ -0,0 +1,94 @@ +github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU= +github.com/Microsoft/hcsshim v0.11.5/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= +github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cilium/ebpf v0.9.1/go.mod h1:+OhNOIXx/Fnu1IE8bJz2dzOA+VSfyTfdNUVdlQnxUFY= +github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/btrfs/v2 v2.0.0/go.mod h1:swkD/7j9HApWpzl8OHfrHNxppPd9l44DFZdF94BUj9k= +github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= +github.com/containerd/cgroups/v3 v3.0.2/go.mod h1:JUgITrzdFqp42uI2ryGA+ge0ap/nxzYgkGmIcetmErE= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= +github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ= +github.com/containerd/errdefs v0.1.0/go.mod h1:YgWiiHtLmSeBrvpw+UfPijzbLaB77mEG1WwJTDETIV0= +github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= +github.com/containerd/go-cni v1.1.9/go.mod h1:XYrZJ1d5W6E2VOvjffL3IZq0Dz6bsVlERHbekNK90PM= +github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/imgcrypt v1.1.8/go.mod h1:x6QvFIkMyO2qGIY2zXc88ivEzcbgvLdWjoZyGqDap5U= +github.com/containerd/nri v0.6.1/go.mod h1:7+sX3wNx+LR7RzhjnJiUkFDhn18P5Bg/0VnJ/uXpRJM= +github.com/containerd/ttrpc v1.2.4/go.mod h1:ojvb8SJBSch0XkqNO0L0YX/5NxR3UnVk2LzFKBK0upc= +github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= +github.com/containerd/typeurl/v2 v2.1.1/go.mod h1:IDp2JFvbwZ31H8dQbEIY7sDl2L3o3HZj1hsSQlywkQ0= +github.com/containerd/zfs v1.1.0/go.mod h1:oZF9wBnrnQjpWLaPKEinrx3TQ9a+W/RJO7Zb41d8YLE= +github.com/containernetworking/cni v1.1.2/go.mod h1:sDpYKmGVENF3s6uvMvGgldDWeG8dMxakj/u+i9ht9vw= +github.com/containernetworking/plugins v1.2.0/go.mod h1:/VjX4uHecW5vVimFa1wkG4s+r/s9qIfPdqlLF4TW8c4= +github.com/containers/ocicrypt v1.1.10/go.mod h1:YfzSSr06PTHQwSTUKqDSjish9BeW1E4HUmreluQcMd8= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/dhowden/itl v0.0.0-20170329215456-9fbe21093131/go.mod h1:eVWQJVQ67aMvYhpkDwaH2Goy2vo6v8JCMfGXfQ9sPtw= +github.com/dhowden/plist v0.0.0-20141002110153-5db6e0d9931a/go.mod h1:sLjdR6uwx3L6/Py8F+QgAfeiuY87xuYGwCDqRFrvCzw= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/emicklei/go-restful/v3 v3.10.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/intel/goresctrl v0.3.0/go.mod h1:fdz3mD85cmP9sHD8JUlrNWAxvwM86CrbmVXltEKd7zk= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/mistifyio/go-zfs/v3 v3.0.1/go.mod h1:CzVgeB0RvF2EGzQnytKVvVSDwmKJXxkOTUGbNrTja/k= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/sys/signal v0.7.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= +github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= +github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.9.1-0.20221107090550-2e043c6bd626/go.mod h1:BRHJJd0E+cx42OybVYSgUvZmU0B8P9gZuRXlZUP7TKI= +github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stefanberger/go-pkcs11uri v0.0.0-20230803200340-78284954bff6/go.mod h1:39R/xuhNgVhi+K0/zst4TLrJrVmbm6LVgl4A0+ZFS5M= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= +github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/vishvananda/netlink v1.2.1-beta.2/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.45.0/go.mod h1:vsh3ySueQCiKPxFLvjWC4Z135gIa34TQ/NSqkDTZYUM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h1:0+KuTDyKL4gjKCF75pHOX4wuzYDUZYfAQdSu43o+Z2I= +golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20230920204549-e6e6cdab5c13/go.mod h1:CCviP9RmpZ1mxVr8MUjCnSiY09IbAXZxhLE6EhHIdPU= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +k8s.io/api v0.26.2/go.mod h1:1kjMQsFE+QHPfskEcVNgL3+Hp88B80uj0QtSOlj8itU= +k8s.io/apimachinery v0.26.2/go.mod h1:ats7nN1LExKHvJ9TmwootT00Yz05MuYqPXEXaVeOy5I= +k8s.io/apiserver v0.26.2/go.mod h1:GHcozwXgXsPuOJ28EnQ/jXEM9QeG6HT22YxSNmpYNh8= +k8s.io/client-go v0.26.2/go.mod h1:u5EjOuSyBa09yqqyY7m3abZeovO/7D/WehVVlZ2qcqU= +k8s.io/component-base v0.26.2/go.mod h1:DxbuIe9M3IZPRxPIzhch2m1eT7uFrSBJUBuVCQEBivs= +k8s.io/cri-api v0.27.1/go.mod h1:+Ts/AVYbIo04S86XbTD73UPp/DkTiYxtsFeOFEu32L0= +k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/utils v0.0.0-20230220204549-a5ecb0141aa5/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +tags.cncf.io/container-device-interface v0.7.2/go.mod h1:Xb1PvXv2BhfNb3tla4r9JL129ck1Lxv9KuU6eVOfKto= +tags.cncf.io/container-device-interface/specs-go v0.7.0/go.mod h1:hMAwAbMZyBLdmYqWgYcKH0F/yctNpV3P35f+/088A80= diff --git a/home/senke/git/talas/veza/apps/web/src/components/views/checkout-view/index.ts b/home/senke/git/talas/veza/apps/web/src/components/views/checkout-view/index.ts new file mode 100644 index 000000000..1d11c634f --- /dev/null +++ b/home/senke/git/talas/veza/apps/web/src/components/views/checkout-view/index.ts @@ -0,0 +1,7 @@ +export type { + CheckoutViewProps, + CheckoutFormState, +} from './types'; +export { CheckoutView } from './CheckoutView'; +export { CheckoutViewSkeleton } from './CheckoutViewSkeleton'; +export { useCheckoutView } from './useCheckoutView'; diff --git a/home/senke/git/talas/veza/apps/web/src/features/streaming/components/PlaybackHeatmap.stories.tsx b/home/senke/git/talas/veza/apps/web/src/features/streaming/components/PlaybackHeatmap.stories.tsx new file mode 100644 index 000000000..c7c1adbaa --- /dev/null +++ b/home/senke/git/talas/veza/apps/web/src/features/streaming/components/PlaybackHeatmap.stories.tsx @@ -0,0 +1,79 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { http, HttpResponse } from 'msw'; +import { + PlaybackHeatmap, + PlaybackHeatmapSkeleton, +} from './PlaybackHeatmap'; +import type { PlaybackHeatmapData } from './playback-heatmap'; + +const meta: Meta = { + title: 'Components/Features/Streaming/PlaybackHeatmap', + component: PlaybackHeatmap, + parameters: { layout: 'padded' }, + tags: ['autodocs'], +}; + +export default meta; + +type Story = StoryObj; + +const mockHeatmap: PlaybackHeatmapData = { + track_id: '123', + track_duration: 180, + segment_size: 5, + total_sessions: 10, + max_intensity: 1.0, + generated_at: '2024-01-01T00:00:00Z', + segments: [ + { start_time: 0, end_time: 5, listen_count: 10, skip_count: 0, intensity: 1.0, average_play_time: 5 }, + { start_time: 5, end_time: 10, listen_count: 8, skip_count: 2, intensity: 0.8, average_play_time: 4 }, + { start_time: 10, end_time: 15, listen_count: 5, skip_count: 3, intensity: 0.5, average_play_time: 2.5 }, + ], +}; + +/** Données chargées via MSW (GET /api/v1/tracks/:id/playback/heatmap). */ +export const Default: Story = { + args: { trackId: '123' }, +}; + +/** Taille de segment personnalisée */ +export const CustomSegmentSize: Story = { + args: { trackId: '123', segmentSize: 10 }, +}; + +/** État de chargement (skeleton) */ +export const Loading: Story = { + name: 'Chargement', + render: () => , +}; + +/** Données vides (aucun segment) */ +export const Empty: Story = { + name: 'Empty', + args: { + trackId: '123', + initialHeatmap: { + ...mockHeatmap, + segments: [], + total_sessions: 0, + }, + }, +}; + +/** Erreur chargement */ +export const Error: Story = { + name: 'Error', + parameters: { + msw: { + handlers: [ + http.get('*/api/v1/tracks/:id/playback/heatmap', () => + HttpResponse.json( + { success: false, error: { message: 'Failed to load heatmap' } }, + { status: 500 }, + ), + ), + ], + }, + }, + args: { trackId: '123' }, +}; diff --git a/make/build.mk b/make/build.mk new file mode 100644 index 000000000..c0c020d75 --- /dev/null +++ b/make/build.mk @@ -0,0 +1,46 @@ +# ============================================================================== +# BUILD (Docker images and native for Incus) +# ============================================================================== + +.PHONY: build-backend-api build-chat-server build-stream-server build-web +.PHONY: build-all build-all-native build-service + +build-backend-api: ## [LOW] Build Go backend Docker image + @$(ECHO_CMD) "${BLUE}🔨 Building backend-api...${NC}" + @docker build -t $(PROJECT_NAME)-backend-api:latest -f $(ROOT)/$(SERVICE_DIR_backend-api)/Dockerfile.production $(ROOT)/$(SERVICE_DIR_backend-api) || \ + ($(ECHO_CMD) "${YELLOW}Using local Dockerfile...${NC}" && \ + docker build -t $(PROJECT_NAME)-backend-api:latest -f $(ROOT)/$(SERVICE_DIR_backend-api)/Dockerfile $(ROOT)/$(SERVICE_DIR_backend-api)) + +build-chat-server: ## [LOW] Build Rust chat server Docker image + @$(ECHO_CMD) "${BLUE}🔨 Building chat-server...${NC}" + @docker build -t $(PROJECT_NAME)-chat-server:latest -f $(ROOT)/$(SERVICE_DIR_chat-server)/Dockerfile.production $(ROOT)/$(SERVICE_DIR_chat-server) || \ + docker build -t $(PROJECT_NAME)-chat-server:latest -f $(ROOT)/$(SERVICE_DIR_chat-server)/Dockerfile $(ROOT)/$(SERVICE_DIR_chat-server)) + +build-stream-server: ## [LOW] Build Rust stream server Docker image + @$(ECHO_CMD) "${BLUE}🔨 Building stream-server...${NC}" + @docker build -t $(PROJECT_NAME)-stream-server:latest -f $(ROOT)/$(SERVICE_DIR_stream-server)/Dockerfile.production $(ROOT)/$(SERVICE_DIR_stream-server) || \ + docker build -t $(PROJECT_NAME)-stream-server:latest -f $(ROOT)/$(SERVICE_DIR_stream-server)/Dockerfile $(ROOT)/$(SERVICE_DIR_stream-server)) + +build-web: ## [LOW] Build web frontend Docker image + @$(ECHO_CMD) "${BLUE}🔨 Building web...${NC}" + @docker build -t $(PROJECT_NAME)-web:latest -f $(ROOT)/$(SERVICE_DIR_web)/Dockerfile.production $(ROOT)/$(SERVICE_DIR_web) || \ + docker build -t $(PROJECT_NAME)-web:latest -f $(ROOT)/$(SERVICE_DIR_web)/Dockerfile $(ROOT)/$(SERVICE_DIR_web)) + +build-all: ## [MID] Build all services (Docker images) + @$(ECHO_CMD) "${BLUE}🔨 Building all services...${NC}" + @$(MAKE) -s build-backend-api + @$(MAKE) -s build-chat-server + @$(MAKE) -s build-stream-server + @$(MAKE) -s build-web + @$(ECHO_CMD) "${GREEN}✅ All services built.${NC}" + +build-all-native: ## [MID] Build all services natively (for Incus) + @$(ECHO_CMD) "${BLUE}🔨 Building all services natively...${NC}" + @$(INCUS_SCRIPTS)/build-native.sh all + @$(ECHO_CMD) "${GREEN}✅ All services built natively.${NC}" + +build-service: ## [MID] Build a specific service (usage: make build-service SERVICE=backend-api) + @if [ -z "$(SERVICE)" ]; then $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; exit 1; fi + @$(ECHO_CMD) "${BLUE}🔨 Building $(SERVICE)...${NC}" + @$(MAKE) -s build-$(SERVICE) + @$(ECHO_CMD) "${GREEN}✅ $(SERVICE) built.${NC}" diff --git a/make/config.mk b/make/config.mk new file mode 100644 index 000000000..fda0f432d --- /dev/null +++ b/make/config.mk @@ -0,0 +1,64 @@ +# ============================================================================== +# VEZA MONOREPO - CONFIGURATION (single source of truth) +# ============================================================================== +# Edit this file to add services, change ports, or paths. +# Override via .env or environment (e.g. PORT_WEB=3000 make dev). +# ============================================================================== + +-include .env + +# --- Project --- +PROJECT_NAME ?= veza +ROOT ?= $(CURDIR) + +# --- Compose --- +COMPOSE_FILE ?= docker-compose.yml +COMPOSE_PROD ?= docker-compose.prod.yml + +# --- Services (space-separated; must match keys in SERVICE_DIRS / SERVICE_PORTS) +SERVICES := backend-api chat-server stream-server web haproxy +INFRA_SERVICES := postgres redis rabbitmq + +# --- Service → Directory mapping (customize paths here) +SERVICE_DIR_backend-api := veza-backend-api +SERVICE_DIR_chat-server := veza-chat-server +SERVICE_DIR_stream-server := veza-stream-server +SERVICE_DIR_web := apps/web +SERVICE_DIR_haproxy := + +# --- Ports (override with PORT_=... from .env) +PORT_backend-api ?= 8080 +PORT_chat-server ?= 3000 +PORT_stream-server ?= 3001 +PORT_web ?= 5173 +PORT_haproxy ?= 80 + +# Legacy names for backward compatibility +PORT_GO ?= $(PORT_backend-api) +PORT_CHAT ?= $(PORT_chat-server) +PORT_STREAM ?= $(PORT_stream-server) +PORT_WEB ?= $(PORT_web) +PORT_HAPROXY ?= $(PORT_haproxy) + +# --- Database & Infra --- +DB_USER ?= veza +DB_PASS ?= password +DB_NAME ?= veza +DB_HOST ?= localhost +DB_PORT ?= 5432 + +DATABASE_URL = postgres://$(DB_USER):$(DB_PASS)@$(DB_HOST):$(DB_PORT)/$(DB_NAME)?sslmode=disable +REDIS_URL = redis://localhost:6379 +AMQP_URL = amqp://$(DB_USER):$(DB_PASS)@localhost:5672 + +# --- Incus --- +DEPLOY_TARGET ?= docker +INCUS_PROFILE ?= veza-profile +INCUS_NETWORK ?= veza-network +INCUS_SCRIPTS ?= $(ROOT)/config/incus + +# --- NPM workspaces (from root package.json; used for install-deps / lint scope) +NPM_WORKSPACES ?= apps/web packages/design-system + +# --- Scripts --- +SCRIPTS ?= $(ROOT)/scripts diff --git a/make/dev.mk b/make/dev.mk new file mode 100644 index 000000000..e0301f5c5 --- /dev/null +++ b/make/dev.mk @@ -0,0 +1,80 @@ +# ============================================================================== +# DEVELOPMENT (local run with optional hot reload) +# ============================================================================== + +.PHONY: dev dev-backend dev-web dev-backend-api dev-chat-server dev-stream-server +.PHONY: stop-local-services start-local-service stop-local-service + +dev: check-ports infra-up ## [HIGH] Start Everything (Detects Hot Reload tools) + @$(ECHO_CMD) "${BOLD}${PURPLE}🚀 STARTING HYBRID DEV ENVIRONMENT${NC}" + @$(ECHO_CMD) " Go: http://localhost:$(PORT_backend-api)" + @$(ECHO_CMD) " Chat: http://localhost:$(PORT_chat-server)" + @$(ECHO_CMD) " Web: http://localhost:$(PORT_web)" + @$(ECHO_CMD) "${YELLOW}Hit Ctrl+C to stop all.${NC}" + @(trap 'kill 0' SIGINT; \ + if command -v air >/dev/null; then \ + $(ECHO_CMD) "${GREEN}[Go] Hot Reload Active (Air)${NC}" && cd $(ROOT)/$(SERVICE_DIR_backend-api) && air & \ + else \ + $(ECHO_CMD) "${YELLOW}[Go] Standard Run${NC}" && cd $(ROOT)/$(SERVICE_DIR_backend-api) && go run cmd/modern-server/main.go & \ + fi; \ + if command -v cargo-watch >/dev/null; then \ + $(ECHO_CMD) "${GREEN}[Chat] Hot Reload Active${NC}" && cd $(ROOT)/$(SERVICE_DIR_chat-server) && cargo watch -x run -q & \ + $(ECHO_CMD) "${GREEN}[Stream] Hot Reload Active${NC}" && cd $(ROOT)/$(SERVICE_DIR_stream-server) && cargo watch -x run -q & \ + else \ + $(ECHO_CMD) "${YELLOW}[Chat] Standard Run${NC}" && cd $(ROOT)/$(SERVICE_DIR_chat-server) && cargo run -q & \ + $(ECHO_CMD) "${YELLOW}[Stream] Standard Run${NC}" && cd $(ROOT)/$(SERVICE_DIR_stream-server) && cargo run -q & \ + fi; \ + $(ECHO_CMD) "${GREEN}[Web] Starting Vite...${NC}" && cd $(ROOT)/$(SERVICE_DIR_web) && npm run dev & \ + wait) + +dev-backend: check-ports infra-up ## [MID] Start Backends Only (Hot Reload supported) + @$(ECHO_CMD) "${BOLD}${PURPLE}🚀 STARTING BACKEND ONLY${NC}" + @(trap 'kill 0' SIGINT; \ + if command -v air >/dev/null; then cd $(ROOT)/$(SERVICE_DIR_backend-api) && air & else cd $(ROOT)/$(SERVICE_DIR_backend-api) && go run cmd/modern-server/main.go & fi; \ + if command -v cargo-watch >/dev/null; then cd $(ROOT)/$(SERVICE_DIR_chat-server) && cargo watch -x run -q & else cd $(ROOT)/$(SERVICE_DIR_chat-server) && cargo run -q & fi; \ + if command -v cargo-watch >/dev/null; then cd $(ROOT)/$(SERVICE_DIR_stream-server) && cargo watch -x run -q & else cd $(ROOT)/$(SERVICE_DIR_stream-server) && cargo run -q & fi; \ + wait) + +dev-web: check-ports infra-up ## [MID] Start Web app only (assumes backend elsewhere or mocked) + @$(ECHO_CMD) "${GREEN}[Web] Starting Vite...${NC}" + @cd $(ROOT)/$(SERVICE_DIR_web) && npm run dev + +dev-backend-api: check-ports infra-up ## [MID] Start Go backend only + @$(ECHO_CMD) "${GREEN}[Backend API] Starting...${NC}" + @if command -v air >/dev/null; then cd $(ROOT)/$(SERVICE_DIR_backend-api) && air; else cd $(ROOT)/$(SERVICE_DIR_backend-api) && go run cmd/modern-server/main.go; fi + +dev-chat-server: check-ports infra-up ## [MID] Start Chat server only + @$(ECHO_CMD) "${GREEN}[Chat] Starting...${NC}" + @if command -v cargo-watch >/dev/null; then cd $(ROOT)/$(SERVICE_DIR_chat-server) && cargo watch -x run -q; else cd $(ROOT)/$(SERVICE_DIR_chat-server) && cargo run -q; fi + +dev-stream-server: check-ports infra-up ## [MID] Start Stream server only + @$(ECHO_CMD) "${GREEN}[Stream] Starting...${NC}" + @if command -v cargo-watch >/dev/null; then cd $(ROOT)/$(SERVICE_DIR_stream-server) && cargo watch -x run -q; else cd $(ROOT)/$(SERVICE_DIR_stream-server) && cargo run -q; fi + +stop-local-services: ## [LOW] Stop all local processes (air, cargo watch, vite) + @pkill -f "air\|cargo watch\|npm run dev\|go run.*modern-server" 2>/dev/null || true + +start-local-service: ## [LOW] Start a service locally (usage: make start-local-service SERVICE=backend-api) + @if [ -z "$(SERVICE)" ]; then $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; exit 1; fi + @$(ECHO_CMD) "${BLUE}🚀 Starting $(SERVICE)...${NC}" + @case "$(SERVICE)" in \ + backend-api) \ + if command -v air >/dev/null; then cd $(ROOT)/$(SERVICE_DIR_backend-api) && air & else cd $(ROOT)/$(SERVICE_DIR_backend-api) && go run cmd/modern-server/main.go & fi ;; \ + chat-server) \ + if command -v cargo-watch >/dev/null; then cd $(ROOT)/$(SERVICE_DIR_chat-server) && cargo watch -x run -q & else cd $(ROOT)/$(SERVICE_DIR_chat-server) && cargo run -q & fi ;; \ + stream-server) \ + if command -v cargo-watch >/dev/null; then cd $(ROOT)/$(SERVICE_DIR_stream-server) && cargo watch -x run -q & else cd $(ROOT)/$(SERVICE_DIR_stream-server) && cargo run -q & fi ;; \ + web) \ + cd $(ROOT)/$(SERVICE_DIR_web) && npm run dev & ;; \ + *) \ + $(ECHO_CMD) "${RED}Unknown service: $(SERVICE)${NC}"; exit 1 ;; \ + esac + +stop-local-service: ## [LOW] Stop a local service (usage: make stop-local-service SERVICE=backend-api) + @if [ -z "$(SERVICE)" ]; then $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; exit 1; fi + @case "$(SERVICE)" in \ + backend-api) pkill -f "air\|go run.*modern-server" 2>/dev/null || true ;; \ + chat-server|stream-server) pkill -f "cargo.*$(SERVICE)" 2>/dev/null || true ;; \ + web) pkill -f "npm run dev\|vite" 2>/dev/null || true ;; \ + *) $(ECHO_CMD) "${RED}Unknown service: $(SERVICE)${NC}" ;; \ + esac diff --git a/make/help.mk b/make/help.mk new file mode 100644 index 000000000..d60bd89c2 --- /dev/null +++ b/make/help.mk @@ -0,0 +1,27 @@ +# ============================================================================== +# HELP & DASHBOARD +# ============================================================================== + +.PHONY: help +help: ## [HIGH] Show this dashboard + @$(ECHO_CMD) "" + @$(ECHO_CMD) "${BOLD}${PURPLE}⚡ VEZA MONOREPO CLI ⚡${NC}" + @$(ECHO_CMD) "=================================================================" + @$(ECHO_CMD) "${BOLD}INFRASTRUCTURE:${NC}" + @printf " ${CYAN}%-15s${NC} %s\n" "Postgres" "$(DATABASE_URL)" + @printf " ${CYAN}%-15s${NC} %s\n" "Redis" "$(REDIS_URL)" + @printf " ${CYAN}%-15s${NC} %s\n" "RabbitMQ" "UI: http://localhost:15672 (veza/password)" + @$(ECHO_CMD) "" + @$(ECHO_CMD) "${BOLD}${GREEN}HIGH LEVEL:${NC}" + @grep -h -E '^[a-zA-Z0-9_-]+:.*?## \[HIGH\] .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " ${YELLOW}%-25s${NC} %s\n", $$1, $$2}' + @$(ECHO_CMD) "" + @$(ECHO_CMD) "${BOLD}${BLUE}INTERMEDIATE:${NC}" + @grep -h -E '^[a-zA-Z0-9_-]+:.*?## \[MID\] .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " ${CYAN}%-25s${NC} %s\n", $$1, $$2}' + @$(ECHO_CMD) "" + @$(ECHO_CMD) "${BOLD}${PURPLE}LOW LEVEL / DEBUG:${NC}" + @grep -h -E '^[a-zA-Z0-9_-]+:.*?## \[LOW\] .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " ${PURPLE}%-25s${NC} %s\n", $$1, $$2}' + @$(ECHO_CMD) "" + @$(ECHO_CMD) "${BOLD}PER-SERVICE (e.g. make dev-web, make test-backend-api):${NC}" + @$(ECHO_CMD) " ${CYAN}dev-${NC} test- lint- build-" + @$(ECHO_CMD) " Services: backend-api, chat-server, stream-server, web" + @$(ECHO_CMD) "" diff --git a/make/high.mk b/make/high.mk new file mode 100644 index 000000000..c0c73ab6e --- /dev/null +++ b/make/high.mk @@ -0,0 +1,77 @@ +# ============================================================================== +# HIGH LEVEL: setup, stop-all, clean, deploy, status +# ============================================================================== + +.PHONY: setup stop-all restart-all clean clean-deep deploy-docker deploy-incus status-full +.PHONY: web-minimal stop-minimal + +setup: check-tools install-tools install-deps ## [HIGH] Full project initialization + @$(ECHO_CMD) "${BOLD}${GREEN}✅ Setup Complete! Ready to rock with 'make dev'.${NC}" + +web-minimal: ## [HIGH] Start Veza Web Minimal Journey (Backend + Frontend + DB) + @$(SCRIPTS)/start_minimal.sh + +stop-minimal: ## [HIGH] Stop Minimal Stack + @$(SCRIPTS)/stop_minimal.sh + +stop-all: ## [HIGH] Stop all services (Docker + Local) + @$(ECHO_CMD) "${RED}🛑 Stopping all services...${NC}" + @docker compose -f $(COMPOSE_FILE) down 2>/dev/null || true + @docker compose -f $(COMPOSE_PROD) down 2>/dev/null || true + @$(MAKE) -s stop-local-services + @$(ECHO_CMD) "${GREEN}✅ All services stopped.${NC}" + +restart-all: stop-all ## [HIGH] Restart all services + @$(ECHO_CMD) "${BLUE}🔄 Restarting all services...${NC}" + @$(MAKE) -s infra-up + @$(MAKE) -s dev + @$(ECHO_CMD) "${GREEN}✅ All services restarted.${NC}" + +clean: ## [HIGH] Clean build artifacts and caches + @$(ECHO_CMD) "${YELLOW}🧹 Cleaning build artifacts...${NC}" + @rm -rf $(ROOT)/$(SERVICE_DIR_web)/node_modules/.cache + @rm -rf $(ROOT)/$(SERVICE_DIR_chat-server)/target/debug $(ROOT)/$(SERVICE_DIR_stream-server)/target/debug + @find $(ROOT) -type d -name "node_modules" -prune -o -type f -name "*.log" -delete 2>/dev/null || true + @$(ECHO_CMD) "${GREEN}✅ Clean complete.${NC}" + +clean-deep: ## [HIGH] ⚠️ Nuclear Clean (Confirm required) + @read -p "${RED}Are you sure? This will delete ALL builds, volumes, and caches! [y/N]${NC} " ans && [ $${ans:-N} = y ] + @$(ECHO_CMD) "${RED}☢️ DESTROYING ARTIFACTS...${NC}" + @rm -rf $(ROOT)/$(SERVICE_DIR_web)/node_modules + @rm -rf $(ROOT)/$(SERVICE_DIR_chat-server)/target $(ROOT)/$(SERVICE_DIR_stream-server)/target + @docker compose -f $(COMPOSE_FILE) down -v 2>/dev/null || true + @docker compose -f $(COMPOSE_PROD) down -v 2>/dev/null || true + @$(ECHO_CMD) "${GREEN}System Cleaned.${NC}" + +deploy-docker: build-all ## [HIGH] Deploy all services with Docker + HAProxy + @$(ECHO_CMD) "${BOLD}${BLUE}🐳 Deploying with Docker...${NC}" + @docker compose -f $(COMPOSE_PROD) up -d --build + @$(MAKE) -s wait-for-services + @$(ECHO_CMD) "${GREEN}✅ Deployment complete! Access via http://localhost:$(PORT_haproxy)${NC}" + +deploy-incus: build-all-native ## [HIGH] Deploy all services with Incus containers (native, no Docker) + @$(ECHO_CMD) "${BOLD}${BLUE}📦 Deploying with Incus (native)...${NC}" + @$(MAKE) -s incus-setup-network + @$(MAKE) -s incus-deploy-infra + @$(MAKE) -s incus-deploy-all-native + @$(MAKE) -s incus-start-all + @$(ECHO_CMD) "${GREEN}✅ Incus deployment complete!${NC}" + @$(ECHO_CMD) "${BLUE}Access services at:${NC}" + @$(ECHO_CMD) " Backend API: http://10.10.10.2:8080" + @$(ECHO_CMD) " Chat Server: http://10.10.10.3:8081" + @$(ECHO_CMD) " Stream Server: http://10.10.10.4:3002" + @$(ECHO_CMD) " Web Frontend: http://10.10.10.5:80" + @$(ECHO_CMD) " HAProxy: http://10.10.10.6:80" + +status-full: ## [HIGH] Show complete system status + @$(ECHO_CMD) "${BOLD}${CYAN}📊 SYSTEM STATUS${NC}" + @$(ECHO_CMD) "" + @$(ECHO_CMD) "${BOLD}Docker Containers:${NC}" + @docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" | grep -E "NAME|veza" || echo " No containers running" + @$(ECHO_CMD) "" + @$(ECHO_CMD) "${BOLD}Local Processes:${NC}" + @lsof -i :$(PORT_backend-api) -i :$(PORT_chat-server) -i :$(PORT_stream-server) -i :$(PORT_web) 2>/dev/null | grep LISTEN || echo " No local processes" + @$(ECHO_CMD) "" + @$(ECHO_CMD) "${BOLD}Incus Containers:${NC}" + @incus list veza- 2>/dev/null | grep -E "NAME|veza" || echo " No Incus containers" + @$(ECHO_CMD) "" diff --git a/make/incus.mk b/make/incus.mk new file mode 100644 index 000000000..8befa3eae --- /dev/null +++ b/make/incus.mk @@ -0,0 +1,201 @@ +# ============================================================================== +# INCUS / LXD DEPLOYMENT +# ============================================================================== + +.PHONY: incus-setup-network incus-deploy-all incus-deploy-all-native incus-deploy-service incus-deploy-service-native incus-deploy-infra incus-start-all incus-stop-all incus-status incus-logs + +incus-setup-network: ## [LOW] Setup Incus network profile + @$(ECHO_CMD) "${BLUE}📦 Setting up Incus network...${NC}" + @if ! incus network show $(INCUS_NETWORK) >/dev/null 2>&1; then \ + $(ECHO_CMD) "Creating network $(INCUS_NETWORK)..."; \ + incus network create $(INCUS_NETWORK) \ + ipv4.address=10.10.10.1/24 \ + ipv4.nat=true \ + ipv4.dhcp=true \ + dns.mode=managed \ + dns.nameservers=8.8.8.8,1.1.1.1; \ + else \ + $(ECHO_CMD) "Updating network configuration..."; \ + incus network set $(INCUS_NETWORK) ipv4.dhcp=true 2>/dev/null || true; \ + incus network set $(INCUS_NETWORK) dns.mode=managed 2>/dev/null || true; \ + incus network set $(INCUS_NETWORK) dns.nameservers=8.8.8.8,1.1.1.1 2>/dev/null || true; \ + fi + @if ! incus profile show $(INCUS_PROFILE) >/dev/null 2>&1; then \ + $(ECHO_CMD) "Creating profile $(INCUS_PROFILE)..."; \ + incus profile create $(INCUS_PROFILE); \ + incus profile device add $(INCUS_PROFILE) root disk path=/ pool=default 2>/dev/null || \ + incus profile device add $(INCUS_PROFILE) root disk path=/ 2>/dev/null || true; \ + incus profile device add $(INCUS_PROFILE) eth0 nic network=$(INCUS_NETWORK) 2>/dev/null || true; \ + else \ + $(ECHO_CMD) "Ensuring profile devices..."; \ + if ! incus profile show $(INCUS_PROFILE) | grep -q "root:"; then \ + incus profile device add $(INCUS_PROFILE) root disk path=/ pool=default 2>/dev/null || \ + incus profile device add $(INCUS_PROFILE) root disk path=/ 2>/dev/null || true; \ + fi; \ + if ! incus profile show $(INCUS_PROFILE) | grep -q "eth0:"; then \ + incus profile device add $(INCUS_PROFILE) eth0 nic network=$(INCUS_NETWORK) 2>/dev/null || true; \ + fi; \ + fi + @$(ECHO_CMD) "${GREEN}✅ Incus network ready.${NC}" + +incus-deploy-all: incus-setup-network ## [MID] Deploy all services to Incus (legacy Docker method) + @$(ECHO_CMD) "${BLUE}📦 Deploying all services to Incus (Docker)...${NC}" + @$(MAKE) -s incus-deploy-service SERVICE=backend-api + @$(MAKE) -s incus-deploy-service SERVICE=chat-server + @$(MAKE) -s incus-deploy-service SERVICE=stream-server + @$(MAKE) -s incus-deploy-service SERVICE=web + @$(MAKE) -s incus-deploy-service SERVICE=haproxy + @$(ECHO_CMD) "${GREEN}✅ All services deployed to Incus.${NC}" + +incus-deploy-all-native: incus-setup-network ## [MID] Deploy all services to Incus (native, no Docker) - excludes Rust services + @$(ECHO_CMD) "${BLUE}📦 Deploying all services to Incus (native, excluding Rust services)...${NC}" + @$(ECHO_CMD) "${YELLOW}⚠️ Note: chat-server and stream-server are excluded${NC}" + @$(MAKE) -s incus-deploy-service-native SERVICE=backend-api + @$(MAKE) -s incus-deploy-service-native SERVICE=web + @$(MAKE) -s incus-deploy-service-native SERVICE=haproxy + @$(ECHO_CMD) "${GREEN}✅ All services deployed to Incus.${NC}" + +incus-deploy-service: ## [LOW] Deploy a service to Incus with Docker (usage: make incus-deploy-service SERVICE=backend-api) + @if [ -z "$(SERVICE)" ]; then \ + $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; \ + exit 1; \ + fi + @$(ECHO_CMD) "${BLUE}📦 Deploying $(SERVICE) to Incus (Docker)...${NC}" + @if incus list -c n --format csv | grep -q "^veza-$(SERVICE)$$"; then \ + $(ECHO_CMD) "${YELLOW}Container exists, removing...${NC}"; \ + incus delete veza-$(SERVICE) --force; \ + fi + @incus init images:debian/13 veza-$(SERVICE) --profile $(INCUS_PROFILE) + @incus start veza-$(SERVICE) + @$(ECHO_CMD) "${BLUE}Installing Docker in container...${NC}" + @incus exec veza-$(SERVICE) -- bash -c "apt-get update && apt-get install -y docker.io docker-compose && systemctl enable docker && systemctl start docker" || true + @$(ECHO_CMD) "${GREEN}✅ $(SERVICE) deployed.${NC}" + +incus-deploy-service-native: ## [LOW] Deploy a service to Incus natively (usage: make incus-deploy-service-native SERVICE=backend-api) + @if [ -z "$(SERVICE)" ]; then \ + $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; \ + exit 1; \ + fi + @$(ECHO_CMD) "${BLUE}📦 Deploying $(SERVICE) to Incus (native)...${NC}" + @$(INCUS_SCRIPTS)/deploy-service-native.sh $(SERVICE) + +incus-deploy-infra: incus-setup-network ## [LOW] Deploy infrastructure services (PostgreSQL, Redis) + @$(ECHO_CMD) "${BLUE}📦 Deploying infrastructure services...${NC}" + @$(MAKE) -s incus-deploy-service-native SERVICE=infra + @$(ECHO_CMD) "${BLUE}Waiting for infrastructure to be ready...${NC}" + @for i in $$(seq 1 30); do \ + if incus exec veza-infra -- systemctl is-active postgresql >/dev/null 2>&1 && \ + incus exec veza-infra -- systemctl is-active redis-server >/dev/null 2>&1; then \ + $(ECHO_CMD) "${GREEN}✅ Infrastructure services ready${NC}"; \ + break; \ + fi; \ + sleep 1; \ + done + @$(ECHO_CMD) "${GREEN}✅ Infrastructure deployed.${NC}" + +incus-start-all: ## [MID] Start all Incus services (excluding Rust services) + @$(ECHO_CMD) "${BLUE}🚀 Starting all Incus services (excluding Rust services)...${NC}" + @for service in backend-api; do \ + if incus list -c n --format csv | grep -q "^veza-$$service$$"; then \ + $(ECHO_CMD) "Starting veza-$$service..."; \ + if incus exec veza-$$service -- systemctl start veza-$$service 2>/dev/null; then \ + $(ECHO_CMD) "${GREEN} ✅ veza-$$service started${NC}"; \ + else \ + $(ECHO_CMD) "${YELLOW} ⚠️ veza-$$service failed to start (check logs)${NC}"; \ + fi; \ + fi; \ + done + @if incus list -c n --format csv | grep -q "^veza-web$$"; then \ + $(ECHO_CMD) "Starting veza-web..."; \ + if incus exec veza-web -- systemctl start apache2 2>/dev/null; then \ + $(ECHO_CMD) "${GREEN} ✅ Apache started${NC}"; \ + else \ + $(ECHO_CMD) "${YELLOW} ⚠️ Apache failed to start${NC}"; \ + fi; \ + fi + @if incus list -c n --format csv | grep -q "^veza-haproxy$$"; then \ + $(ECHO_CMD) "Starting veza-haproxy..."; \ + if incus exec veza-haproxy -- systemctl start haproxy 2>/dev/null; then \ + $(ECHO_CMD) "${GREEN} ✅ HAProxy started${NC}"; \ + else \ + $(ECHO_CMD) "${YELLOW} ⚠️ HAProxy failed to start${NC}"; \ + fi; \ + fi + @if incus list -c n --format csv | grep -q "^veza-infra$$"; then \ + $(ECHO_CMD) "Starting infrastructure services..."; \ + if incus exec veza-infra -- systemctl start postgresql 2>/dev/null; then \ + $(ECHO_CMD) "${GREEN} ✅ PostgreSQL started${NC}"; \ + else \ + $(ECHO_CMD) "${YELLOW} ⚠️ PostgreSQL failed to start${NC}"; \ + fi; \ + if incus exec veza-infra -- systemctl start redis-server 2>/dev/null; then \ + $(ECHO_CMD) "${GREEN} ✅ Redis started${NC}"; \ + else \ + $(ECHO_CMD) "${YELLOW} ⚠️ Redis failed to start${NC}"; \ + fi; \ + fi + @$(ECHO_CMD) "${GREEN}✅ All services started.${NC}" + @$(ECHO_CMD) "${BLUE}Run 'make incus-status' to check service status${NC}" + +incus-stop-all: ## [MID] Stop all Incus containers + @$(ECHO_CMD) "${YELLOW}🛑 Stopping all Incus containers...${NC}" + @for container in $$(incus list -c n --format csv | grep veza-); do \ + incus stop $$container 2>/dev/null || true; \ + done + @$(ECHO_CMD) "${GREEN}✅ All Incus containers stopped.${NC}" + +incus-status: ## [MID] Show status of all Incus services + @$(ECHO_CMD) "${BOLD}${CYAN}📊 INCUS DEPLOYMENT STATUS${NC}" + @$(ECHO_CMD) "" + @$(ECHO_CMD) "${BOLD}Containers:${NC}" + @incus list veza- --format table 2>/dev/null || echo " No containers found" + @$(ECHO_CMD) "" + @$(ECHO_CMD) "${BOLD}Service Status:${NC}" + @for service in backend-api chat-server stream-server; do \ + if incus list -c n --format csv 2>/dev/null | grep -q "^veza-$$service$$"; then \ + STATUS=$$(incus exec veza-$$service -- systemctl is-active veza-$$service 2>/dev/null || echo "inactive"); \ + if [ "$$STATUS" = "active" ]; then \ + $(ECHO_CMD) " ${GREEN}✅ veza-$$service: active${NC}"; \ + else \ + $(ECHO_CMD) " ${YELLOW}⚠️ veza-$$service: $$STATUS${NC}"; \ + fi; \ + fi; \ + done + @if incus list -c n --format csv 2>/dev/null | grep -q "^veza-web$$"; then \ + STATUS=$$(incus exec veza-web -- systemctl is-active apache2 2>/dev/null || echo "inactive"); \ + if [ "$$STATUS" = "active" ]; then \ + $(ECHO_CMD) " ${GREEN}✅ veza-web (Apache): active${NC}"; \ + else \ + $(ECHO_CMD) " ${YELLOW}⚠️ veza-web (Apache): $$STATUS${NC}"; \ + fi; \ + fi + @if incus list -c n --format csv 2>/dev/null | grep -q "^veza-haproxy$$"; then \ + STATUS=$$(incus exec veza-haproxy -- systemctl is-active haproxy 2>/dev/null || echo "inactive"); \ + if [ "$$STATUS" = "active" ]; then \ + $(ECHO_CMD) " ${GREEN}✅ veza-haproxy: active${NC}"; \ + else \ + $(ECHO_CMD) " ${YELLOW}⚠️ veza-haproxy: $$STATUS${NC}"; \ + fi; \ + fi + @if incus list -c n --format csv 2>/dev/null | grep -q "^veza-infra$$"; then \ + PG_STATUS=$$(incus exec veza-infra -- systemctl is-active postgresql 2>/dev/null || echo "inactive"); \ + REDIS_STATUS=$$(incus exec veza-infra -- systemctl is-active redis-server 2>/dev/null || echo "inactive"); \ + if [ "$$PG_STATUS" = "active" ]; then \ + $(ECHO_CMD) " ${GREEN}✅ PostgreSQL: active${NC}"; \ + else \ + $(ECHO_CMD) " ${YELLOW}⚠️ PostgreSQL: $$PG_STATUS${NC}"; \ + fi; \ + if [ "$$REDIS_STATUS" = "active" ]; then \ + $(ECHO_CMD) " ${GREEN}✅ Redis: active${NC}"; \ + else \ + $(ECHO_CMD) " ${YELLOW}⚠️ Redis: $$REDIS_STATUS${NC}"; \ + fi; \ + fi + @$(ECHO_CMD) "" + +incus-logs: ## [LOW] Show logs from Incus container (usage: make incus-logs SERVICE=backend-api) + @if [ -z "$(SERVICE)" ]; then \ + $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; \ + exit 1; \ + fi + @incus exec veza-$(SERVICE) -- journalctl -f diff --git a/make/infra.mk b/make/infra.mk new file mode 100644 index 000000000..a12994c74 --- /dev/null +++ b/make/infra.mk @@ -0,0 +1,45 @@ +# ============================================================================== +# INFRASTRUCTURE (Docker: Postgres, Redis, RabbitMQ) +# ============================================================================== + +.PHONY: infra-up infra-down wait-for-infra wait-for-services db-shell redis-shell db-migrate + +infra-up: ## [MID] Start Docker Infra (with health checks) + @$(ECHO_CMD) "${BLUE}🐳 Starting Infrastructure...${NC}" + @docker compose -f $(COMPOSE_FILE) up -d + @$(MAKE) -s wait-for-infra + +infra-down: ## [MID] Stop Docker Infra + @$(ECHO_CMD) "${BLUE}🛑 Stopping Infrastructure...${NC}" + @docker compose -f $(COMPOSE_FILE) down + +wait-for-infra: ## [LOW] Wait for infrastructure to be ready + @printf "${BLUE}⏳ Waiting for services...${NC}" + @until docker compose -f $(COMPOSE_FILE) exec -T postgres pg_isready -U $(DB_USER) > /dev/null 2>&1; do printf "."; sleep 1; done + @until docker compose -f $(COMPOSE_FILE) exec -T redis redis-cli ping > /dev/null 2>&1; do printf "."; sleep 1; done + @$(ECHO_CMD) " ${GREEN}OK${NC}" + +wait-for-services: ## [LOW] Wait for all application services + @printf "${BLUE}⏳ Waiting for services...${NC}" + @for service in backend-api chat-server stream-server web; do \ + until docker compose -f $(COMPOSE_PROD) exec -T $$service echo "ready" > /dev/null 2>&1; do \ + printf "."; sleep 1; \ + done; \ + done + @$(ECHO_CMD) " ${GREEN}OK${NC}" + +db-shell: ## [MID] Connect to Postgres shell + @docker compose -f $(COMPOSE_FILE) exec postgres psql -U $(DB_USER) -d $(DB_NAME) + +redis-shell: ## [MID] Connect to Redis shell + @docker compose -f $(COMPOSE_FILE) exec redis redis-cli + +db-migrate: infra-up ## [MID] Run all database migrations + @$(ECHO_CMD) "${BLUE}🔄 Running Migrations...${NC}" + @$(ECHO_CMD) " -> [Go] Migrating..." + @(cd $(ROOT)/$(SERVICE_DIR_backend-api) && go run cmd/migrate_tool/main.go up || $(ECHO_CMD) "${YELLOW}Warning: Go migration failed${NC}") + @$(ECHO_CMD) " -> [Chat] Migrating..." + @(cd $(ROOT)/$(SERVICE_DIR_chat-server) && sqlx migrate run || $(ECHO_CMD) "${YELLOW}Warning: Chat migration failed${NC}") + @$(ECHO_CMD) " -> [Stream] Migrating..." + @(cd $(ROOT)/$(SERVICE_DIR_stream-server) && sqlx migrate run || $(ECHO_CMD) "${YELLOW}Warning: Stream migration failed${NC}") + @$(ECHO_CMD) "${GREEN}✅ Migrations done.${NC}" diff --git a/make/services.mk b/make/services.mk new file mode 100644 index 000000000..e8ad21ce7 --- /dev/null +++ b/make/services.mk @@ -0,0 +1,38 @@ +# ============================================================================== +# SERVICE LIFECYCLE (Docker: start/stop/restart/logs per service) +# ============================================================================== + +.PHONY: start-service stop-service restart-service logs-service + +start-service: ## [MID] Start a specific service (usage: make start-service SERVICE=backend-api) + @if [ -z "$(SERVICE)" ]; then \ + $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; \ + exit 1; \ + fi + @$(ECHO_CMD) "${BLUE}🚀 Starting $(SERVICE)...${NC}" + @docker compose -f $(COMPOSE_PROD) up -d $(SERVICE) 2>/dev/null || \ + $(MAKE) -s start-local-service SERVICE=$(SERVICE) + @$(ECHO_CMD) "${GREEN}✅ $(SERVICE) started.${NC}" + +stop-service: ## [MID] Stop a specific service (usage: make stop-service SERVICE=backend-api) + @if [ -z "$(SERVICE)" ]; then \ + $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; \ + exit 1; \ + fi + @$(ECHO_CMD) "${YELLOW}🛑 Stopping $(SERVICE)...${NC}" + @docker compose -f $(COMPOSE_PROD) stop $(SERVICE) 2>/dev/null || \ + $(MAKE) -s stop-local-service SERVICE=$(SERVICE) + @$(ECHO_CMD) "${GREEN}✅ $(SERVICE) stopped.${NC}" + +restart-service: stop-service ## [MID] Restart a specific service (usage: make restart-service SERVICE=backend-api) + @$(ECHO_CMD) "${BLUE}🔄 Restarting $(SERVICE)...${NC}" + @$(MAKE) -s start-service SERVICE=$(SERVICE) + @$(ECHO_CMD) "${GREEN}✅ $(SERVICE) restarted.${NC}" + +logs-service: ## [MID] Show logs for a service (usage: make logs-service SERVICE=backend-api) + @if [ -z "$(SERVICE)" ]; then \ + $(ECHO_CMD) "${RED}❌ Please specify SERVICE=name${NC}"; \ + exit 1; \ + fi + @docker compose -f $(COMPOSE_PROD) logs -f $(SERVICE) 2>/dev/null || \ + $(ECHO_CMD) "${YELLOW}Service not running in Docker, check local logs${NC}" diff --git a/make/test.mk b/make/test.mk new file mode 100644 index 000000000..77a62e17f --- /dev/null +++ b/make/test.mk @@ -0,0 +1,71 @@ +# ============================================================================== +# TEST & QUALITY (unit tests, lint, format) +# ============================================================================== + +.PHONY: test test-tmt lint fmt status test-web test-backend-api test-chat-server test-stream-server +.PHONY: lint-web lint-backend-api lint-chat-server lint-stream-server + +test: infra-up ## [MID] Run All Tests (Fastest strategy) + @$(ECHO_CMD) "${BLUE}🧪 Running Tests...${NC}" + @$(ECHO_CMD) " [Go] Unit Tests..." + @(cd $(ROOT)/$(SERVICE_DIR_backend-api) && go test ./... -short) + @$(ECHO_CMD) " [Rust] Unit Tests..." + @(cd $(ROOT)/$(SERVICE_DIR_chat-server) && cargo test --lib -q) + @(cd $(ROOT)/$(SERVICE_DIR_stream-server) && cargo test --lib -q) + @$(ECHO_CMD) " [Web] Unit Tests..." + @(cd $(ROOT)/$(SERVICE_DIR_web) && npm run test -- --run) + @$(ECHO_CMD) "${GREEN}✅ All tests passed.${NC}" + +test-tmt: ## [MID] Run Unified TMT Pipeline + @$(ECHO_CMD) "${BLUE}🧪 Running TMT Pipeline...${NC}" + @command -v tmt >/dev/null 2>&1 || { $(ECHO_CMD) "${RED}❌ tmt is missing! Install with 'pip install tmt'${NC}"; exit 1; } + @tmt run + +test-web: ## [MID] Run Web tests only + @$(ECHO_CMD) "${BLUE}🧪 Running Web tests...${NC}" + @(cd $(ROOT)/$(SERVICE_DIR_web) && npm run test -- --run) + +test-backend-api: ## [MID] Run Go backend tests only + @$(ECHO_CMD) "${BLUE}🧪 Running Backend API tests...${NC}" + @(cd $(ROOT)/$(SERVICE_DIR_backend-api) && go test ./... -short) + +test-chat-server: ## [MID] Run Chat server tests only + @$(ECHO_CMD) "${BLUE}🧪 Running Chat server tests...${NC}" + @(cd $(ROOT)/$(SERVICE_DIR_chat-server) && cargo test --lib -q) + +test-stream-server: ## [MID] Run Stream server tests only + @$(ECHO_CMD) "${BLUE}🧪 Running Stream server tests...${NC}" + @(cd $(ROOT)/$(SERVICE_DIR_stream-server) && cargo test --lib -q) + +lint: ## [MID] Lint everything + @$(ECHO_CMD) "${BLUE}🔍 Linting Codebase...${NC}" + @(cd $(ROOT)/$(SERVICE_DIR_chat-server) && cargo clippy -- -D warnings) || true + @(cd $(ROOT)/$(SERVICE_DIR_stream-server) && cargo clippy -- -D warnings) || true + @(cd $(ROOT)/$(SERVICE_DIR_backend-api) && golangci-lint run ./...) || true + @(cd $(ROOT)/$(SERVICE_DIR_web) && npm run lint) || true + +lint-web: ## [MID] Lint web app only + @(cd $(ROOT)/$(SERVICE_DIR_web) && npm run lint) + +lint-backend-api: ## [MID] Lint Go backend only + @(cd $(ROOT)/$(SERVICE_DIR_backend-api) && golangci-lint run ./...) + +lint-chat-server: ## [MID] Lint Chat server only + @(cd $(ROOT)/$(SERVICE_DIR_chat-server) && cargo clippy -- -D warnings) + +lint-stream-server: ## [MID] Lint Stream server only + @(cd $(ROOT)/$(SERVICE_DIR_stream-server) && cargo clippy -- -D warnings) + +fmt: ## [MID] Format everything + @$(ECHO_CMD) "${BLUE}✨ Formatting...${NC}" + @(cd $(ROOT)/$(SERVICE_DIR_backend-api) && go fmt ./...) + @(cd $(ROOT)/$(SERVICE_DIR_chat-server) && cargo fmt) + @(cd $(ROOT)/$(SERVICE_DIR_stream-server) && cargo fmt) + @(cd $(ROOT)/$(SERVICE_DIR_web) && npm run format) || true + +status: ## [MID] Show system health & stats + @$(ECHO_CMD) "${BOLD}DOCKER STATS:${NC}" + @docker stats --no-stream --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}" 2>/dev/null | grep -E "NAME|veza" || echo "No containers running" + @$(ECHO_CMD) "" + @$(ECHO_CMD) "${BOLD}LOCAL PORTS:${NC}" + @lsof -i :$(PORT_backend-api) -i :$(PORT_chat-server) -i :$(PORT_stream-server) -i :$(PORT_web) 2>/dev/null | grep LISTEN || echo "No apps listening." diff --git a/make/tools.mk b/make/tools.mk new file mode 100644 index 000000000..95cd4b100 --- /dev/null +++ b/make/tools.mk @@ -0,0 +1,49 @@ +# ============================================================================== +# TOOLS: check, install deps, ports +# ============================================================================== + +.PHONY: check-tools check-tools-incus install-tools install-deps check-ports + +check-tools: ## [LOW] Check required tools + @$(ECHO_CMD) "${BLUE}Checking core requirements...${NC}" + @for tool in docker go cargo npm; do \ + command -v $$tool >/dev/null 2>&1 || { $(ECHO_CMD) "${RED}❌ $$tool is missing!${NC}"; exit 1; }; \ + done + @$(ECHO_CMD) "${GREEN}✅ All tools present.${NC}" + +check-tools-incus: ## [LOW] Check required tools for Incus deployment + @$(ECHO_CMD) "${BLUE}Checking Incus deployment requirements...${NC}" + @command -v incus >/dev/null 2>&1 || { $(ECHO_CMD) "${RED}❌ incus is missing! Install with: sudo snap install incus${NC}"; exit 1; } + @command -v go >/dev/null 2>&1 || { $(ECHO_CMD) "${RED}❌ go is missing!${NC}"; exit 1; } + @command -v cargo >/dev/null 2>&1 || { $(ECHO_CMD) "${RED}❌ cargo is missing!${NC}"; exit 1; } + @command -v npm >/dev/null 2>&1 || { $(ECHO_CMD) "${RED}❌ npm is missing!${NC}"; exit 1; } + @$(ECHO_CMD) "${GREEN}✅ All Incus tools present.${NC}" + +install-tools: ## [LOW] Install Power User tools (Hot Reload, Linters) + @$(ECHO_CMD) "${BLUE}🛠️ Installing Dev Tools...${NC}" + @command -v air >/dev/null 2>&1 || go install github.com/air-verse/air@latest + @command -v cargo-watch >/dev/null 2>&1 || cargo install cargo-watch + @command -v sqlx >/dev/null 2>&1 || cargo install sqlx-cli --no-default-features --features native-tls,postgres + @$(ECHO_CMD) "${GREEN}✅ Tools installed.${NC}" + +install-deps: ## [LOW] Install code dependencies (all backends + npm workspaces) + @$(ECHO_CMD) "${BLUE}📦 Installing dependencies...${NC}" + @$(ECHO_CMD) " -> [Go] Downloading modules..." + @(cd $(ROOT)/$(SERVICE_DIR_backend-api) && go mod download) + @$(ECHO_CMD) " -> [Rust Chat] Fetching crates..." + @(cd $(ROOT)/$(SERVICE_DIR_chat-server) && cargo fetch) + @$(ECHO_CMD) " -> [Rust Stream] Fetching crates..." + @(cd $(ROOT)/$(SERVICE_DIR_stream-server) && cargo fetch) + @$(ECHO_CMD) " -> [Web] Installing npm packages..." + @(cd $(ROOT)/$(SERVICE_DIR_web) && npm install --silent) + @$(ECHO_CMD) "${GREEN}✅ Dependencies installed.${NC}" + +check-ports: ## [LOW] Check if ports are available + @$(ECHO_CMD) "${BLUE}🔍 Checking ports...${NC}" + @for port in $(PORT_backend-api) $(PORT_chat-server) $(PORT_stream-server) $(PORT_web); do \ + if lsof -i :$$port -t >/dev/null 2>&1; then \ + $(ECHO_CMD) "${YELLOW}⚠️ Port $$port is busy${NC}"; \ + else \ + $(ECHO_CMD) "${GREEN}✅ Port $$port is free${NC}"; \ + fi; \ + done diff --git a/make/ui.mk b/make/ui.mk new file mode 100644 index 000000000..f3eb2ebd9 --- /dev/null +++ b/make/ui.mk @@ -0,0 +1,14 @@ +# ============================================================================== +# UI: colors and echo helper +# ============================================================================== + +BOLD := \033[1m +RED := \033[0;31m +GREEN := \033[0;32m +YELLOW := \033[0;33m +BLUE := \033[0;34m +PURPLE := \033[0;35m +CYAN := \033[0;36m +NC := \033[0m + +ECHO_CMD := echo -e diff --git a/veza-backend-api/docs/ACCOUNT_LOCKOUT.md b/veza-backend-api/docs/ACCOUNT_LOCKOUT.md new file mode 100644 index 000000000..49e685f64 --- /dev/null +++ b/veza-backend-api/docs/ACCOUNT_LOCKOUT.md @@ -0,0 +1,72 @@ +# Account lockout (BE-SEC-007) + +## How it works + +After too many **failed login attempts**, the account is temporarily **locked** to slow down brute-force attacks. + +1. **Storage**: Lock state and attempt counts are stored in **Redis** (keys `account_lockout:attempts:{email}` and `account_lockout:locked:{email}`). +2. **Failed attempt**: Each failed login (wrong password, user not found, or email not verified) calls `RecordFailedAttempt(email)`. The attempt counter is incremented; it expires after a **window** (default 15 minutes). +3. **Lock**: When the number of failed attempts in the window reaches **max attempts** (default 5), the account is **locked** for a **lockout duration** (default 30 minutes). +4. **While locked**: Any login for that email returns **HTTP 423 Locked** with message *"Account is locked. Please try again later."* +5. **Unlock**: The lock key has a TTL; when it expires, the account is automatically unlocked. A **successful login** also clears the lock and the attempt counter. + +**Defaults** (see `internal/services/account_lockout_service.go`): + +- Max attempts: **5** +- Window: **15 minutes** +- Lockout duration: **30 minutes** + +If Redis is unavailable, lockout is disabled (no locking, no recording). + +--- + +## Unlock an account + +### Option 1: Admin API (recommended) + +As an **admin** user, send: + +```bash +curl -X POST http://localhost:8080/api/v1/admin/auth/unlock-account \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer " \ + -d '{"email":"user@example.com"}' +``` + +Response: `200 OK` with `{"message":"account unlocked","email":"user@example.com"}`. + +### Option 2: Redis CLI + +If you have access to Redis: + +```bash +# Replace with the locked user's email +EMAIL="user@example.com" + +redis-cli DEL "account_lockout:locked:${EMAIL}" "account_lockout:attempts:${EMAIL}" +``` + +--- + +## Disable lockout for specific accounts + +Use **exempt emails** so those accounts are never locked and failed attempts are not recorded. + +**Environment variable** (comma-separated list): + +```bash +ACCOUNT_LOCKOUT_EXEMPT_EMAILS=testuser@example.com,admin@test.com +``` + +Example in `.env` or `.env.development`: + +``` +ACCOUNT_LOCKOUT_EXEMPT_EMAILS=testuser@example.com +``` + +After restarting the API, that email will: + +- Never be considered locked (`IsAccountLocked` returns false). +- Not have failed attempts recorded (`RecordFailedAttempt` is a no-op). + +This is intended for **test / dev accounts** only; avoid exempting real user emails in production. diff --git a/veza-backend-api/internal/api/router.go b/veza-backend-api/internal/api/router.go index 98dfa7373..d0f06a416 100644 --- a/veza-backend-api/internal/api/router.go +++ b/veza-backend-api/internal/api/router.go @@ -48,6 +48,7 @@ type APIRouter struct { logger *zap.Logger versionManager *VersionManager // BE-SVC-019: API versioning manager monitoringService *services.MonitoringAlertingService // INT-021: API monitoring and alerting + authService *authcore.AuthService // Set by setupAuthRoutes for admin unlock } // NewAPIRouter crée une nouvelle instance de APIRouter @@ -280,12 +281,12 @@ func (r *APIRouter) Setup(router *gin.Engine) error { // Groupe API v1 (nouveau frontend React) v1 := router.Group("/api/v1") { - // Routes core protégées (sessions, uploads, audit, admin, conversations) - r.setupCoreProtectedRoutes(v1) - + // Auth routes first so r.authService is set for admin unlock in setupCoreProtectedRoutes if err := r.setupAuthRoutes(v1); err != nil { return err } + // Routes core protégées (sessions, uploads, audit, admin, conversations) + r.setupCoreProtectedRoutes(v1) // Action 5.2.1.1: Validation endpoint for pre-validation r.setupValidateRoutes(v1) @@ -407,11 +408,18 @@ func (r *APIRouter) setupAuthRoutes(router *gin.RouterGroup) error { // BE-SEC-007: Initialize account lockout service and set it on auth service if r.config.RedisClient != nil { - accountLockoutService := services.NewAccountLockoutService(r.config.RedisClient, r.logger) + lockoutConfig := &services.AccountLockoutConfig{ + MaxAttempts: 5, + LockoutDuration: 30 * time.Minute, + WindowDuration: 15 * time.Minute, + ExemptEmails: r.config.AccountLockoutExemptEmails, + } + accountLockoutService := services.NewAccountLockoutServiceWithConfig(r.config.RedisClient, r.logger, lockoutConfig) authService.SetAccountLockoutService(accountLockoutService) } else { r.logger.Warn("Redis not available - account lockout disabled") } + r.authService = authService // 2.5. User Service for GetMe endpoint userRepo := repositories.NewGormUserRepository(r.db.GormDB) @@ -1421,6 +1429,11 @@ func (r *APIRouter) setupCoreProtectedRoutes(v1 *gin.RouterGroup) { // MOD-P2-006: Profiling pprof (protégé par auth admin) admin.Any("/debug/pprof/*path", gin.WrapH(http.DefaultServeMux)) + + // BE-SEC-007: Unlock account locked by failed login attempts (admin only) + if r.authService != nil { + admin.POST("/auth/unlock-account", handlers.UnlockAccount(r.authService, r.logger)) + } } } diff --git a/veza-backend-api/internal/config/config.go b/veza-backend-api/internal/config/config.go index aa3bcff50..b433766ef 100644 --- a/veza-backend-api/internal/config/config.go +++ b/veza-backend-api/internal/config/config.go @@ -96,6 +96,7 @@ type Config struct { RateLimitWindow int // Fenêtre de temps en secondes pour le rate limiter simple AuthRateLimitLoginAttempts int // Max login attempts (PR-3) AuthRateLimitLoginWindow int // Login rate limit window in minutes (PR-3) + AccountLockoutExemptEmails []string // BE-SEC-007: Emails exempt from lockout (e.g. testuser@example.com) HandlerTimeout time.Duration // Global handler timeout (PR-6) LogLevel string // Niveau de log (T0027) DBMaxRetries int @@ -281,6 +282,7 @@ func NewConfig() (*Config, error) { // Augmenter les limites pour l'environnement de test/E2E AuthRateLimitLoginAttempts: getAuthRateLimitLoginAttempts(env), AuthRateLimitLoginWindow: getAuthRateLimitLoginWindow(env), + AccountLockoutExemptEmails: getEnvStringSlice("ACCOUNT_LOCKOUT_EXEMPT_EMAILS", nil), HandlerTimeout: getEnvDuration("HANDLER_TIMEOUT", 30*time.Second), // Default: 30 seconds LogLevel: logLevel, Logger: nil, // Sera initialisé après selon LOG_LEVEL et agrégation @@ -1148,35 +1150,63 @@ func (c *Config) ShouldUseSecureCookies() bool { return c.CookieSecure } -// getCORSOrigins charge les origines CORS avec defaults sécurisés selon l'environnement (P0-SECURITY) -// - development: defaults permissifs (localhost uniquement) si CORS_ALLOWED_ORIGINS non défini -// - test: liste vide ou configurée explicitement -// - production: CORS_ALLOWED_ORIGINS comportement: -// - si défini: utiliser -// - si absent/vide: liste vide (STRICT, reject all) -func getCORSOrigins(env string) []string { - // Si CORS_ALLOWED_ORIGINS est défini, l'utiliser - if value := os.Getenv("CORS_ALLOWED_ORIGINS"); value != "" { - origins := getEnvStringSlice("CORS_ALLOWED_ORIGINS", nil) - if len(origins) > 0 { - return origins +// devDefaultCORSOrigins returns origins always allowed in development/staging (localhost + local domains in /etc/hosts). +func devDefaultCORSOrigins() []string { + return []string{ + "http://localhost:3000", "http://127.0.0.1:3000", + "http://localhost:5173", "http://127.0.0.1:5173", + "http://veza.fr", "http://veza.fr:5173", + "http://veza.com", "http://veza.com:5173", + "http://veza.talas.fr", "http://veza.talas.fr:5173", + "http://veza.talas.com", "http://veza.talas.com:5173", + } +} + +// mergeCORSOrigins merges custom with base and deduplicates (order: base then custom). +func mergeCORSOrigins(base, custom []string) []string { + seen := make(map[string]bool) + out := make([]string, 0, len(base)+len(custom)) + for _, o := range base { + if !seen[o] { + seen[o] = true + out = append(out, o) } } + for _, o := range custom { + if !seen[o] { + seen[o] = true + out = append(out, o) + } + } + return out +} + +// getCORSOrigins charge les origines CORS avec defaults sécurisés selon l'environnement (P0-SECURITY) +// - development/staging: si CORS_ALLOWED_ORIGINS est défini, on le fusionne avec les defaults (localhost + veza.*) +// pour que veza.fr:5173 reste autorisé même avec une config partielle +// - production: CORS_ALLOWED_ORIGINS obligatoire, pas de merge +// - test: liste vide par défaut +func getCORSOrigins(env string) []string { + custom := getEnvStringSlice("CORS_ALLOWED_ORIGINS", nil) - // Defaults selon l'environnement switch env { case EnvProduction: - // Production: defaults to empty (Strict Mode) - // MOD-P0-002: "si CORS_ALLOWED_ORIGINS vide, appliquer un comportement strict par défaut (reject toutes origines)" + if len(custom) > 0 { + return custom + } return []string{} case EnvTest: - // Test: liste vide par défaut (peut être configurée explicitement) + if len(custom) > 0 { + return custom + } return []string{} case EnvDevelopment, EnvStaging: - // Development/Staging: defaults permissifs pour localhost - return []string{"http://localhost:3000", "http://127.0.0.1:3000", "http://localhost:5173", "http://127.0.0.1:5173"} + base := devDefaultCORSOrigins() + if len(custom) > 0 { + return mergeCORSOrigins(base, custom) + } + return base default: - // Fallback: development-like return []string{"http://localhost:3000", "http://127.0.0.1:3000"} } } diff --git a/veza-backend-api/internal/core/auth/service.go b/veza-backend-api/internal/core/auth/service.go index af580e670..b161a5e83 100644 --- a/veza-backend-api/internal/core/auth/service.go +++ b/veza-backend-api/internal/core/auth/service.go @@ -71,6 +71,14 @@ func (s *AuthService) SetAccountLockoutService(lockoutService *services.AccountL s.accountLockoutService = lockoutService } +// UnlockAccount déverrouille un compte (appelé par l'admin). No-op si le service de lockout est nil. +func (s *AuthService) UnlockAccount(ctx context.Context, email string) error { + if s.accountLockoutService == nil { + return nil + } + return s.accountLockoutService.UnlockAccount(ctx, email) +} + // GetUserByUsername récupère un utilisateur par son nom d'utilisateur func (s *AuthService) GetUserByUsername(ctx context.Context, username string) (*models.User, error) { var user models.User diff --git a/veza-backend-api/internal/handlers/auth.go b/veza-backend-api/internal/handlers/auth.go index 49bcb3b7a..0a5db8a97 100644 --- a/veza-backend-api/internal/handlers/auth.go +++ b/veza-backend-api/internal/handlers/auth.go @@ -19,6 +19,11 @@ import ( "go.uber.org/zap" ) +// UnlockAccountRequest is the JSON body for admin unlock account. +type UnlockAccountRequest struct { + Email string `json:"email" binding:"required,email"` +} + // Login gère la connexion des utilisateurs // @Summary User Login // @Description Authenticate user and return access token. Refresh token is set in httpOnly cookie. @@ -692,3 +697,32 @@ func GetMe(userService *services.UserService) gin.HandlerFunc { RespondSuccess(c, http.StatusOK, user) } } + +// UnlockAccount (admin only) unlocks an account that was locked due to failed login attempts. +// POST /admin/auth/unlock-account with body { "email": "user@example.com" } +func UnlockAccount(authService *auth.AuthService, logger *zap.Logger) gin.HandlerFunc { + return func(c *gin.Context) { + var req UnlockAccountRequest + if err := c.ShouldBindJSON(&req); err != nil { + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "invalid request: email is required and must be valid")) + return + } + email := strings.TrimSpace(strings.ToLower(req.Email)) + if email == "" { + RespondWithAppError(c, apperrors.New(apperrors.ErrCodeValidation, "email is required")) + return + } + ctx := c.Request.Context() + if err := authService.UnlockAccount(ctx, email); err != nil { + if logger != nil { + logger.Warn("Unlock account failed", zap.String("email", email), zap.Error(err)) + } + RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "failed to unlock account", err)) + return + } + if logger != nil { + logger.Info("Account unlocked by admin", zap.String("email", email)) + } + RespondSuccess(c, http.StatusOK, gin.H{"message": "account unlocked", "email": email}) + } +} diff --git a/veza-backend-api/internal/middleware/auth.go b/veza-backend-api/internal/middleware/auth.go index 1ae9982b8..2dc304934 100644 --- a/veza-backend-api/internal/middleware/auth.go +++ b/veza-backend-api/internal/middleware/auth.go @@ -65,6 +65,12 @@ func NewAuthMiddleware( } } +// isSessionCheckRequest returns true for GET /auth/me (or path ending with /auth/me). +// Used to avoid WARN logs when the frontend probes session without a token (expected case). +func isSessionCheckRequest(path string) bool { + return strings.HasSuffix(path, "/auth/me") || path == "/auth/me" +} + // authenticate performs the core authentication logic // Returns userID and true if successful, otherwise handles error response and returns false func (am *AuthMiddleware) authenticate(c *gin.Context) (uuid.UUID, bool) { @@ -84,10 +90,18 @@ func (am *AuthMiddleware) authenticate(c *gin.Context) (uuid.UUID, bool) { } if tokenString == "" { - am.logger.Warn("Missing access token (cookie or Authorization header)", - zap.String("ip", c.ClientIP()), - zap.String("user_agent", c.GetHeader("User-Agent")), - ) + // Session-check endpoint (GET /auth/me) is often called without token to probe login state; log at Debug to avoid noise + if isSessionCheckRequest(c.Request.URL.Path) { + am.logger.Debug("Missing access token (cookie or Authorization header)", + zap.String("path", c.Request.URL.Path), + zap.String("ip", c.ClientIP()), + ) + } else { + am.logger.Warn("Missing access token (cookie or Authorization header)", + zap.String("ip", c.ClientIP()), + zap.String("user_agent", c.GetHeader("User-Agent")), + ) + } response.Unauthorized(c, "Access token required") c.Abort() return uuid.Nil, false diff --git a/veza-backend-api/internal/services/account_lockout_service.go b/veza-backend-api/internal/services/account_lockout_service.go index 09597918e..d4417af88 100644 --- a/veza-backend-api/internal/services/account_lockout_service.go +++ b/veza-backend-api/internal/services/account_lockout_service.go @@ -3,6 +3,7 @@ package services import ( "context" "fmt" + "strings" "time" "github.com/redis/go-redis/v9" @@ -17,6 +18,7 @@ type AccountLockoutService struct { maxAttempts int // Nombre maximum de tentatives avant verrouillage (défaut: 5) lockoutDuration time.Duration // Durée du verrouillage (défaut: 30 minutes) windowDuration time.Duration // Fenêtre de temps pour compter les tentatives (défaut: 15 minutes) + exemptEmails map[string]struct{} // Emails exemptés du lockout (ex: testuser@example.com) } // AccountLockoutConfig configuration pour le service de verrouillage @@ -24,6 +26,7 @@ type AccountLockoutConfig struct { MaxAttempts int // Nombre maximum de tentatives (défaut: 5) LockoutDuration time.Duration // Durée du verrouillage (défaut: 30 minutes) WindowDuration time.Duration // Fenêtre de temps pour compter les tentatives (défaut: 15 minutes) + ExemptEmails []string // Emails qui ne sont jamais verrouillés (ex: comptes de test) } // DefaultAccountLockoutConfig retourne la configuration par défaut @@ -52,17 +55,37 @@ func NewAccountLockoutServiceWithConfig(redisClient *redis.Client, logger *zap.L if config == nil { config = DefaultAccountLockoutConfig() } + exempt := make(map[string]struct{}) + for _, e := range config.ExemptEmails { + normalized := strings.TrimSpace(strings.ToLower(e)) + if normalized != "" { + exempt[normalized] = struct{}{} + } + } return &AccountLockoutService{ redisClient: redisClient, logger: logger, maxAttempts: config.MaxAttempts, lockoutDuration: config.LockoutDuration, windowDuration: config.WindowDuration, + exemptEmails: exempt, } } +// isExempt returns true if the email is exempt from lockout (e.g. test accounts). +func (s *AccountLockoutService) isExempt(email string) bool { + if len(s.exemptEmails) == 0 { + return false + } + _, ok := s.exemptEmails[strings.TrimSpace(strings.ToLower(email))] + return ok +} + // RecordFailedAttempt enregistre une tentative de login échouée func (s *AccountLockoutService) RecordFailedAttempt(ctx context.Context, email string) error { + if s.isExempt(email) { + return nil + } if s.redisClient == nil { // Si Redis n'est pas disponible, on ne peut pas tracker les tentatives // On retourne nil pour ne pas bloquer le login @@ -131,6 +154,9 @@ func (s *AccountLockoutService) RecordSuccessfulLogin(ctx context.Context, email // IsAccountLocked vérifie si un compte est verrouillé func (s *AccountLockoutService) IsAccountLocked(ctx context.Context, email string) (bool, *time.Time, error) { + if s.isExempt(email) { + return false, nil, nil + } if s.redisClient == nil { return false, nil, nil }