├── .env.example ├── .gitignore ├── docker-compose.yml ├── file-management ├── .editorconfig ├── .env.example ├── .gitattributes ├── .gitignore ├── Dockerfile ├── artisan ├── bootstrap │ ├── app.php │ ├── cache │ │ └── .gitignore │ └── providers.php ├── composer.json ├── composer.lock ├── config │ ├── kafka.php │ └── services.php ├── database │ └── .gitignore ├── docker │ ├── php.ini │ ├── start-container │ └── supervisord.conf ├── public │ ├── .htaccess │ ├── favicon.ico │ ├── index.php │ └── robots.txt ├── src │ ├── Actions │ │ ├── DownloadFile.php │ │ ├── GetFiles.php │ │ └── UploadFile.php │ ├── Clients │ │ ├── LicenseClient.php │ │ └── S3Client.php │ ├── Commands │ │ └── ConsumeMessages.php │ ├── Exceptions │ │ ├── DailyLimitException.php │ │ ├── EntityTooLargeException.php │ │ ├── LicenseExpiredException.php │ │ └── QuotaReachedException.php │ ├── Handlers │ │ └── KafkaMessageHandler.php │ ├── Jobs │ │ └── CreateNewBucket.php │ ├── Middlewares │ │ └── EnsureTokenIsValid.php │ ├── Migrations │ │ ├── 2024_06_07_020305_create_jobs_table.php │ │ ├── 2024_06_07_020313_create_failed_jobs_table.php │ │ ├── 2024_06_07_054155_create_files_table.php │ │ └── 2024_06_07_064602_create_cache_table.php │ ├── Models │ │ ├── File.php │ │ └── Limits.php │ ├── Providers │ │ ├── AppServiceProvider.php │ │ └── RouteServiceProvider.php │ ├── Resources │ │ └── FileResource.php │ └── Routes │ │ └── ApiV1.php └── storage │ ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore │ ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore │ └── logs │ └── .gitignore ├── license ├── .editorconfig ├── .env.example ├── .gitattributes ├── .gitignore ├── Dockerfile ├── artisan ├── bootstrap │ ├── app.php │ ├── cache │ │ └── .gitignore │ └── providers.php ├── composer.json ├── composer.lock ├── config │ └── kafka.php ├── docker │ ├── php.ini │ ├── start-container │ └── supervisord.conf ├── public │ ├── .htaccess │ ├── favicon.ico │ ├── index.php │ └── robots.txt ├── src │ ├── Actions │ │ ├── GetLicenseOfUser.php │ │ ├── LicenseUpdated.php │ │ └── MeLicense.php │ ├── Commands │ │ └── ConsumeMessages.php │ ├── Handlers │ │ └── KafkaMessageHandler.php │ ├── Middlewares │ │ └── EnsureTokenIsValid.php │ ├── Migrations │ │ ├── 2024_06_06_135026_create_licenses_table.php │ │ ├── 2024_06_07_021508_create_failed_jobs_table.php │ │ └── 2024_06_07_021519_create_jobs_table.php │ ├── Models │ │ ├── License.php │ │ └── LicenseType.php │ ├── Providers │ │ ├── AppServiceProvider.php │ │ ├── DatabaseServiceProvider.php │ │ └── RouteServiceProvider.php │ ├── Resources │ │ └── LicenseResource.php │ └── Routes │ │ └── ApiV1.php └── storage │ ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore │ ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore │ └── logs │ └── .gitignore ├── notification ├── .editorconfig ├── .env.example ├── .gitattributes ├── .gitignore ├── Dockerfile ├── artisan ├── bootstrap │ ├── app.php │ ├── cache │ │ └── .gitignore │ └── providers.php ├── composer.json ├── composer.lock ├── config │ ├── kafka.php │ └── mail.php ├── database │ └── .gitignore ├── docker │ ├── php.ini │ ├── start-container │ └── supervisord.conf ├── src │ ├── Commands │ │ └── ConsumeMessages.php │ ├── Handlers │ │ └── KafkaMessageHandler.php │ ├── Jobs │ │ └── SendWelcomeEmail.php │ ├── Mail │ │ └── WelcomeEmail.php │ ├── Migrations │ │ ├── 2024_06_07_012217_create_jobs_table.php │ │ └── 2024_06_07_013016_create_failed_jobs_table.php │ └── Providers │ │ └── AppServiceProvider.php ├── storage │ ├── app │ │ ├── .gitignore │ │ └── public │ │ │ └── .gitignore │ ├── framework │ │ ├── .gitignore │ │ ├── cache │ │ │ ├── .gitignore │ │ │ └── data │ │ │ │ └── .gitignore │ │ ├── sessions │ │ │ └── .gitignore │ │ ├── testing │ │ │ └── .gitignore │ │ └── views │ │ │ └── .gitignore │ └── logs │ │ └── .gitignore └── views │ └── emails │ ├── welcome-plaintext.blade.php │ └── welcome.blade.php ├── readme.md ├── secrets └── .gitignore └── security ├── .editorconfig ├── .env.example ├── .gitattributes ├── .gitignore ├── Dockerfile ├── artisan ├── bootstrap ├── app.php ├── cache │ └── .gitignore └── providers.php ├── composer.json ├── composer.lock ├── config └── kafka.php ├── database ├── .gitignore └── migrations │ ├── 0001_01_01_000000_create_users_table.php │ ├── 0001_01_01_000001_create_cache_table.php │ ├── 0001_01_01_000002_create_jobs_table.php │ ├── 2024_06_04_130737_create_oauth_personal_access_clients_table.php │ ├── 2024_06_04_130738_create_oauth_refresh_tokens_table.php │ ├── 2024_06_04_130739_create_oauth_clients_table.php │ ├── 2024_06_04_130740_create_oauth_access_tokens_table.php │ └── 2024_06_04_130741_create_oauth_auth_codes_table.php ├── docker ├── php.ini ├── start-container └── supervisord.conf ├── public ├── .htaccess ├── favicon.ico ├── index.php └── robots.txt ├── src ├── Actions │ ├── CreateNewUser.php │ └── PublishUserCreatedMessage.php ├── Models │ └── User.php ├── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ └── RouteServiceProvider.php └── Routes │ └── ApiV1.php └── storage ├── app ├── .gitignore └── public │ └── .gitignore ├── framework ├── .gitignore ├── cache │ ├── .gitignore │ └── data │ │ └── .gitignore ├── sessions │ └── .gitignore ├── testing │ └── .gitignore └── views │ └── .gitignore └── logs └── .gitignore /.env.example: -------------------------------------------------------------------------------- 1 | ZOOKEEPER_IMAGE=confluentinc/cp-zookeeper 2 | ZOOKEEPER_IMAGE_VERSION=7.6.1 3 | 4 | ZOONAVIGATOR_VERSION=1.1.2 5 | 6 | KAFKA_IMAGE=confluentinc/cp-kafka 7 | KAFKA_IMAGE_VERSION=7.6.1 8 | 9 | SCHEMA_REGISTRY_IMAGE=confluentinc/cp-schema-registry 10 | SCHEMA_REGISTRY_IMAGE_VERSION=7.6.1 11 | 12 | KAFKA_CONNECT_IMAGE=confluentinc/cp-kafka-connect 13 | KAFKA_CONNECT_IMAGE_VERSION=7.6.1 14 | 15 | MINIO_IMAGE_VERSION=RELEASE.2024-06-04T19-20-08Z.fips 16 | MINIO_USER= 17 | MINIO_PASSWORD= 18 | 19 | WWWUSER=1000 20 | WWWGROUP=1000 21 | 22 | APP_SECURITY_DB_IMAGE_VERSION=15 23 | APP_SECURITY_PORT=8082 24 | APP_SECURITY_XDEBUG_MODE=off 25 | APP_SECURITY_XDEBUG_CONFIG=-client_host=host.docker.internal 26 | APP_SECURITY_DB_PORT=5432 27 | 28 | APP_LICENSE_DB_IMAGE_VERSION=15 29 | APP_LICENSE_PORT=8083 30 | APP_LICENSE_XDEBUG_MODE=off 31 | APP_LICENSE_XDEBUG_CONFIG=-client_host=host.docker.internal 32 | APP_LICENSE_DB_PORT=5433 33 | 34 | APP_FILEMANAGEMENT_DB_IMAGE_VERSION=15 35 | APP_FILEMANAGEMENT_PORT=8084 36 | APP_FILEMANAGEMENT_XDEBUG_MODE=off 37 | APP_FILEMANAGEMENT_XDEBUG_CONFIG=-client_host=host.docker.internal 38 | APP_FILEMANAGEMENT_DB_PORT=5434 39 | 40 | APP_NOTIFICATION_DB_IMAGE_VERSION=15 41 | APP_NOTIFICATION_PORT=8085 42 | APP_NOTIFICATION_XDEBUG_MODE=off 43 | APP_NOTIFICATION_XDEBUG_CONFIG=-client_host=host.docker.internal 44 | APP_NOTIFICATION_DB_PORT=5435 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Project 2 | /.env 3 | /.env.backup 4 | /.env.production 5 | 6 | ### Intellij 7 | /.idea 8 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | security: 3 | build: 4 | context: ./security 5 | dockerfile: Dockerfile 6 | args: 7 | WWWGROUP: '${WWWGROUP}' 8 | extra_hosts: 9 | - 'host.docker.internal:host-gateway' 10 | ports: 11 | - '${APP_SECURITY_PORT:-8082}:80' 12 | env_file: 13 | - ./security/.env 14 | environment: 15 | WWWUSER: '${WWWUSER}' 16 | APP_IN_DOCKER: 1 17 | XDEBUG_MODE: '${APP_SECURITY_XDEBUG_MODE:-off}' 18 | XDEBUG_CONFIG: '${APP_SECURITY_XDEBUG_CONFIG:-client_host=host.docker.internal}' 19 | IGNITION_LOCAL_SITES_PATH: '${PWD}' 20 | volumes: 21 | - './security:/var/www/html' 22 | - './secrets:/tmp/secrets' 23 | networks: 24 | - security 25 | - backend 26 | - vpc 27 | depends_on: 28 | - security-db 29 | security-db: 30 | image: postgres:${APP_SECURITY_DB_IMAGE_VERSION} 31 | ports: 32 | - '${APP_SECURITY_DB_PORT:-5432}:5432' 33 | env_file: 34 | - ./security/.env 35 | environment: 36 | PGPASSWD: '${DB_PASSWORD:-secret}' 37 | POSTGRES_DB: '${DB_DATABASE:-security}' 38 | POSTGRES_USER: '${DB_USERNAME:-security}' 39 | POSTGRES_PASSWORD: '${DB_PASSWORD:-secret}' 40 | volumes: 41 | - security-db-data:/var/lib/postgresql/data 42 | networks: 43 | - security 44 | healthcheck: 45 | test: ["CMD", "pg_isready", "-q", "-d", "${DB_DATABASE:-security}", "-U", "${DB_USERNAME:-security}"] 46 | retries: 3 47 | timeout: 5s 48 | license: 49 | build: 50 | context: ./license 51 | dockerfile: Dockerfile 52 | args: 53 | WWWGROUP: '${WWWGROUP}' 54 | extra_hosts: 55 | - 'host.docker.internal:host-gateway' 56 | ports: 57 | - '${APP_LICENSE_PORT:-8083}:80' 58 | env_file: 59 | - ./license/.env 60 | environment: 61 | WWWUSER: '${WWWUSER}' 62 | APP_IN_DOCKER: 1 63 | XDEBUG_MODE: '${APP_LICENSE_XDEBUG_MODE:-off}' 64 | XDEBUG_CONFIG: '${APP_LICENSE_XDEBUG_CONFIG:-client_host=host.docker.internal}' 65 | IGNITION_LOCAL_SITES_PATH: '${PWD}' 66 | volumes: 67 | - './license:/var/www/html' 68 | - './secrets:/tmp/secrets' 69 | networks: 70 | - license 71 | - file-management 72 | - backend 73 | - vpc 74 | depends_on: 75 | - license-db 76 | license-db: 77 | image: postgres:${APP_LICENSE_DB_IMAGE_VERSION} 78 | ports: 79 | - '${APP_LICENSE_DB_PORT:-5433}:5432' 80 | env_file: 81 | - ./license/.env 82 | environment: 83 | PGPASSWD: '${DB_PASSWORD:-secret}' 84 | POSTGRES_DB: '${DB_DATABASE:-license}' 85 | POSTGRES_USER: '${DB_USERNAME:-license}' 86 | POSTGRES_PASSWORD: '${DB_PASSWORD:-secret}' 87 | volumes: 88 | - license-db-data:/var/lib/postgresql/data 89 | networks: 90 | - license 91 | healthcheck: 92 | test: ["CMD", "pg_isready", "-q", "-d", "${DB_DATABASE:-license}", "-U", "${DB_USERNAME:-license}"] 93 | retries: 3 94 | timeout: 5s 95 | file-management: 96 | build: 97 | context: ./file-management 98 | dockerfile: Dockerfile 99 | args: 100 | WWWGROUP: '${WWWGROUP}' 101 | extra_hosts: 102 | - 'host.docker.internal:host-gateway' 103 | ports: 104 | - '${APP_FILEMANAGEMENT_PORT:-8084}:80' 105 | env_file: 106 | - ./file-management/.env 107 | environment: 108 | WWWUSER: '${WWWUSER}' 109 | APP_IN_DOCKER: 1 110 | XDEBUG_MODE: '${APP_FILEMANAGEMENT_XDEBUG_MODE:-off}' 111 | XDEBUG_CONFIG: '${APP_FILEMANAGEMENT_XDEBUG_CONFIG:-client_host=host.docker.internal}' 112 | IGNITION_LOCAL_SITES_PATH: '${PWD}' 113 | volumes: 114 | - './file-management:/var/www/html' 115 | - './secrets:/tmp/secrets' 116 | networks: 117 | - file-management 118 | - license 119 | - backend 120 | - vpc 121 | depends_on: 122 | - file-management-db 123 | file-management-db: 124 | image: postgres:${APP_FILEMANAGEMENT_DB_IMAGE_VERSION} 125 | ports: 126 | - '${APP_FILEMANAGEMENT_DB_PORT:-5434}:5432' 127 | env_file: 128 | - ./file-management/.env 129 | environment: 130 | PGPASSWD: '${DB_PASSWORD:-secret}' 131 | POSTGRES_DB: '${DB_DATABASE:-filemanagement}' 132 | POSTGRES_USER: '${DB_USERNAME:-filemanagement}' 133 | POSTGRES_PASSWORD: '${DB_PASSWORD:-secret}' 134 | volumes: 135 | - file-management-db-data:/var/lib/postgresql/data 136 | networks: 137 | - file-management 138 | healthcheck: 139 | test: ["CMD", "pg_isready", "-q", "-d", "${DB_DATABASE:-filemanagement}", "-U", "${DB_USERNAME:-filemanagement}"] 140 | retries: 3 141 | timeout: 5s 142 | notification: 143 | build: 144 | context: ./notification 145 | dockerfile: Dockerfile 146 | args: 147 | WWWGROUP: '${WWWGROUP}' 148 | extra_hosts: 149 | - 'host.docker.internal:host-gateway' 150 | ports: 151 | - '${APP_NOTIFICATION_PORT:-8085}:80' 152 | env_file: 153 | - ./notification/.env 154 | environment: 155 | WWWUSER: '${WWWUSER}' 156 | APP_IN_DOCKER: 1 157 | XDEBUG_MODE: '${APP_NOTIFICATION_XDEBUG_MODE:-off}' 158 | XDEBUG_CONFIG: '${APP_NOTIFICATION_XDEBUG_CONFIG:-client_host=host.docker.internal}' 159 | IGNITION_LOCAL_SITES_PATH: '${PWD}' 160 | volumes: 161 | - './notification:/var/www/html' 162 | - './secrets:/tmp/secrets' 163 | networks: 164 | - notification 165 | - backend 166 | - vpc 167 | depends_on: 168 | - notification-db 169 | notification-db: 170 | image: postgres:${APP_NOTIFICATION_DB_IMAGE_VERSION} 171 | ports: 172 | - '${APP_NOTIFICATION_DB_PORT:-5435}:5432' 173 | env_file: 174 | - ./notification/.env 175 | environment: 176 | PGPASSWD: '${DB_PASSWORD:-secret}' 177 | POSTGRES_DB: '${DB_DATABASE:-notification}' 178 | POSTGRES_USER: '${DB_USERNAME:-notification}' 179 | POSTGRES_PASSWORD: '${DB_PASSWORD:-secret}' 180 | volumes: 181 | - notification-db-data:/var/lib/postgresql/data 182 | networks: 183 | - notification 184 | healthcheck: 185 | test: ["CMD", "pg_isready", "-q", "-d", "${DB_DATABASE:-notification}", "-U", "${DB_USERNAME:-notification}"] 186 | retries: 3 187 | timeout: 5s 188 | zoo1: 189 | image: ${ZOOKEEPER_IMAGE}:${ZOOKEEPER_IMAGE_VERSION} 190 | restart: unless-stopped 191 | ports: 192 | - "2181:2181" 193 | environment: 194 | ZOOKEEPER_SERVER_ID: 1 195 | ZOOKEEPER_CLIENT_PORT: 2181 196 | ZOOKEEPER_TICK_TIME: 2000 197 | ZOOKEEPER_INIT_LIMIT: 10 198 | ZOOKEEPER_SYNC_LIMIT: 5 199 | ZOOKEEPER_PEER_PORT: 2888 200 | ZOOKEEPER_LEADER_PORT: 3888 201 | ZOOKEEPER_SERVERS: zoo1:2888:3888;zoo2:2888:3888;zoo3:2888:3888 202 | volumes: 203 | - zookeeper1-data:/var/lib/zookeeper/data 204 | - zookeeper1-logs:/var/lib/zookeeper/log 205 | networks: 206 | - backend 207 | zoo2: 208 | image: ${ZOOKEEPER_IMAGE}:${ZOOKEEPER_IMAGE_VERSION} 209 | restart: unless-stopped 210 | ports: 211 | - "2182:2182" 212 | environment: 213 | ZOOKEEPER_SERVER_ID: 2 214 | ZOOKEEPER_CLIENT_PORT: 2182 215 | ZOOKEEPER_TICK_TIME: 2000 216 | ZOOKEEPER_INIT_LIMIT: 10 217 | ZOOKEEPER_SYNC_LIMIT: 5 218 | ZOOKEEPER_PEER_PORT: 2888 219 | ZOOKEEPER_LEADER_PORT: 3888 220 | ZOOKEEPER_SERVERS: zoo1:2888:3888;zoo2:2888:3888;zoo3:2888:3888 221 | volumes: 222 | - zookeeper2-data:/var/lib/zookeeper/data 223 | - zookeeper2-logs:/var/lib/zookeeper/log 224 | networks: 225 | - backend 226 | zoo3: 227 | image: ${ZOOKEEPER_IMAGE}:${ZOOKEEPER_IMAGE_VERSION} 228 | restart: unless-stopped 229 | ports: 230 | - "2183:2183" 231 | environment: 232 | ZOOKEEPER_SERVER_ID: 3 233 | ZOOKEEPER_CLIENT_PORT: 2183 234 | ZOOKEEPER_TICK_TIME: 2000 235 | ZOOKEEPER_INIT_LIMIT: 10 236 | ZOOKEEPER_SYNC_LIMIT: 5 237 | ZOOKEEPER_PEER_PORT: 2888 238 | ZOOKEEPER_LEADER_PORT: 3888 239 | ZOOKEEPER_SERVERS: zoo1:2888:3888;zoo2:2888:3888;zoo3:2888:3888 240 | volumes: 241 | - zookeeper3-data:/var/lib/zookeeper/data 242 | - zookeeper3-logs:/var/lib/zookeeper/log 243 | networks: 244 | - backend 245 | zoonavigator: 246 | image: elkozmon/zoonavigator:${ZOONAVIGATOR_VERSION} 247 | restart: unless-stopped 248 | ports: 249 | - "19000:9000" 250 | environment: 251 | - CONNECTION_ZK1_NAME=zoo1 252 | - CONNECTION_ZK1_CONN=zoo1:2181 253 | - CONNECTION_ZK2_NAME=zoo2 254 | - CONNECTION_ZK2_CONN=zoo2:2182 255 | - CONNECTION_ZK3_NAME=zoo3 256 | - CONNECTION_ZK3_CONN=zoo3:2183 257 | - AUTO_CONNECT_CONNECTION_ID=ZK1 258 | depends_on: 259 | - zoo1 260 | - zoo2 261 | - zoo3 262 | networks: 263 | - backend 264 | - vpc 265 | kafka1: 266 | image: ${KAFKA_IMAGE}:${KAFKA_IMAGE_VERSION} 267 | ports: 268 | - "9092:9092" 269 | - "29092:29092" 270 | - "9997:9997" 271 | environment: 272 | KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka1:19092,EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9092,DOCKER://host.docker.internal:29092 273 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,DOCKER:PLAINTEXT 274 | KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL 275 | KAFKA_ZOOKEEPER_CONNECT: "zoo1:2181,zoo2:2182,zoo3:2183" 276 | KAFKA_BROKER_ID: 1 277 | KAFKA_JMX_PORT: 9997 278 | KAFKA_JMX_OPTS: -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=kafka1 -Dcom.sun.management.jmxremote.rmi.port=9997 279 | KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO" 280 | KAFKA_AUTHORIZER_CLASS_NAME: kafka.security.authorizer.AclAuthorizer 281 | KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true" 282 | depends_on: 283 | - zoo1 284 | - zoo2 285 | - zoo3 286 | networks: 287 | - backend 288 | volumes: 289 | - kafka1-data:/var/lib/kafka/data 290 | kafka2: 291 | image: ${KAFKA_IMAGE}:${KAFKA_IMAGE_VERSION} 292 | ports: 293 | - "9093:9093" 294 | - "29093:29093" 295 | - "9998:9998" 296 | environment: 297 | KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka2:19093,EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9093,DOCKER://host.docker.internal:29093 298 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,DOCKER:PLAINTEXT 299 | KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL 300 | KAFKA_ZOOKEEPER_CONNECT: "zoo1:2181,zoo2:2182,zoo3:2183" 301 | KAFKA_BROKER_ID: 2 302 | KAFKA_JMX_PORT: 9998 303 | KAFKA_JMX_OPTS: -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=kafka1 -Dcom.sun.management.jmxremote.rmi.port=9998 304 | KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO" 305 | KAFKA_AUTHORIZER_CLASS_NAME: kafka.security.authorizer.AclAuthorizer 306 | KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true" 307 | depends_on: 308 | - zoo1 309 | - zoo2 310 | - zoo3 311 | networks: 312 | - backend 313 | volumes: 314 | - kafka2-data:/var/lib/kafka/data 315 | kafka3: 316 | image: ${KAFKA_IMAGE}:${KAFKA_IMAGE_VERSION} 317 | ports: 318 | - "9094:9094" 319 | - "29094:29094" 320 | - "9999:9999" 321 | environment: 322 | KAFKA_ADVERTISED_LISTENERS: INTERNAL://kafka3:19094,EXTERNAL://${DOCKER_HOST_IP:-127.0.0.1}:9094,DOCKER://host.docker.internal:29094 323 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: INTERNAL:PLAINTEXT,EXTERNAL:PLAINTEXT,DOCKER:PLAINTEXT 324 | KAFKA_INTER_BROKER_LISTENER_NAME: INTERNAL 325 | KAFKA_ZOOKEEPER_CONNECT: "zoo1:2181,zoo2:2182,zoo3:2183" 326 | KAFKA_BROKER_ID: 3 327 | KAFKA_JMX_PORT: 9999 328 | KAFKA_JMX_OPTS: -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=kafka2 -Dcom.sun.management.jmxremote.rmi.port=9999 329 | KAFKA_LOG4J_LOGGERS: "kafka.controller=INFO,kafka.producer.async.DefaultEventHandler=INFO,state.change.logger=INFO" 330 | KAFKA_AUTHORIZER_CLASS_NAME: kafka.security.authorizer.AclAuthorizer 331 | KAFKA_ALLOW_EVERYONE_IF_NO_ACL_FOUND: "true" 332 | depends_on: 333 | - zoo1 334 | - zoo2 335 | - zoo3 336 | networks: 337 | - backend 338 | volumes: 339 | - kafka3-data:/var/lib/kafka/data 340 | schemaregistry1: 341 | image: ${SCHEMA_REGISTRY_IMAGE}:${SCHEMA_REGISTRY_IMAGE_VERSION} 342 | ports: 343 | - 8085:8085 344 | environment: 345 | SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka1:19092 346 | SCHEMA_REGISTRY_KAFKASTORE_SECURITY_PROTOCOL: PLAINTEXT 347 | SCHEMA_REGISTRY_HOST_NAME: schemaregistry1 348 | SCHEMA_REGISTRY_LISTENERS: http://schemaregistry1:8085 349 | 350 | SCHEMA_REGISTRY_SCHEMA_REGISTRY_INTER_INSTANCE_PROTOCOL: "http" 351 | SCHEMA_REGISTRY_LOG4J_ROOT_LOGLEVEL: INFO 352 | SCHEMA_REGISTRY_KAFKASTORE_TOPIC: _schemas 353 | depends_on: 354 | - kafka1 355 | networks: 356 | - backend 357 | schemaregistry2: 358 | image: ${SCHEMA_REGISTRY_IMAGE}:${SCHEMA_REGISTRY_IMAGE_VERSION} 359 | ports: 360 | - 18085:8085 361 | environment: 362 | SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS: PLAINTEXT://kafka2:19093 363 | SCHEMA_REGISTRY_KAFKASTORE_SECURITY_PROTOCOL: PLAINTEXT 364 | SCHEMA_REGISTRY_HOST_NAME: schemaregistry2 365 | SCHEMA_REGISTRY_LISTENERS: http://schemaregistry2:8085 366 | 367 | SCHEMA_REGISTRY_SCHEMA_REGISTRY_INTER_INSTANCE_PROTOCOL: "http" 368 | SCHEMA_REGISTRY_LOG4J_ROOT_LOGLEVEL: INFO 369 | SCHEMA_REGISTRY_KAFKASTORE_TOPIC: _schemas 370 | depends_on: 371 | - kafka1 372 | networks: 373 | - backend 374 | kafka-connect1: 375 | image: ${KAFKA_CONNECT_IMAGE}:${KAFKA_CONNECT_IMAGE_VERSION} 376 | ports: 377 | - 8083:8083 378 | environment: 379 | CONNECT_BOOTSTRAP_SERVERS: kafka1:19092 380 | CONNECT_GROUP_ID: compose-connect-group 381 | CONNECT_CONFIG_STORAGE_TOPIC: _connect_configs 382 | CONNECT_CONFIG_STORAGE_REPLICATION_FACTOR: 1 383 | CONNECT_OFFSET_STORAGE_TOPIC: _connect_offset 384 | CONNECT_OFFSET_STORAGE_REPLICATION_FACTOR: 1 385 | CONNECT_STATUS_STORAGE_TOPIC: _connect_status 386 | CONNECT_STATUS_STORAGE_REPLICATION_FACTOR: 1 387 | CONNECT_KEY_CONVERTER: org.apache.kafka.connect.storage.StringConverter 388 | CONNECT_KEY_CONVERTER_SCHEMA_REGISTRY_URL: http://schemaregistry1:8085 389 | CONNECT_VALUE_CONVERTER: org.apache.kafka.connect.storage.StringConverter 390 | CONNECT_VALUE_CONVERTER_SCHEMA_REGISTRY_URL: http://schemaregistry1:8085 391 | CONNECT_INTERNAL_KEY_CONVERTER: org.apache.kafka.connect.json.JsonConverter 392 | CONNECT_INTERNAL_VALUE_CONVERTER: org.apache.kafka.connect.json.JsonConverter 393 | CONNECT_REST_ADVERTISED_HOST_NAME: kafka-connect1 394 | CONNECT_PLUGIN_PATH: "/usr/share/java,/usr/share/confluent-hub-components" 395 | depends_on: 396 | - kafka1 397 | - schemaregistry1 398 | networks: 399 | - backend 400 | kafkaui: 401 | image: provectuslabs/kafka-ui:latest 402 | ports: 403 | - 8081:8080 404 | environment: 405 | KAFKA_CLUSTERS_0_NAME: local 406 | KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka1:19092 407 | KAFKA_CLUSTERS_0_METRICS_PORT: 9997 408 | KAFKA_CLUSTERS_0_SCHEMAREGISTRY: http://schemaregistry1:8085 409 | KAFKA_CLUSTERS_0_KAFKACONNECT_0_NAME: first 410 | KAFKA_CLUSTERS_0_KAFKACONNECT_0_ADDRESS: http://kafka-connect1:8083 411 | KAFKA_CLUSTERS_1_NAME: secondLocal 412 | KAFKA_CLUSTERS_1_BOOTSTRAPSERVERS: kafka2:19093 413 | KAFKA_CLUSTERS_1_METRICS_PORT: 9998 414 | KAFKA_CLUSTERS_1_SCHEMAREGISTRY: http://schemaregistry2:8085 415 | KAFKA_CLUSTERS_2_NAME: thirdLocal 416 | KAFKA_CLUSTERS_2_BOOTSTRAPSERVERS: kafka3:19094 417 | KAFKA_CLUSTERS_2_METRICS_PORT: 9999 418 | KAFKA_CLUSTERS_2_SCHEMAREGISTRY: http://schemaregistry2:8085 419 | DYNAMIC_CONFIG_ENABLED: 'true' 420 | networks: 421 | - backend 422 | - vpc 423 | depends_on: 424 | - kafka1 425 | - kafka2 426 | - kafka3 427 | - schemaregistry1 428 | - schemaregistry2 429 | - kafka-connect1 430 | minio: 431 | image: quay.io/minio/minio:${MINIO_IMAGE_VERSION} 432 | ports: 433 | - "9000:9000" 434 | - "9001:9001" 435 | command: server /data --console-address ":9001" 436 | networks: 437 | - file-management 438 | - vpc 439 | volumes: 440 | - minio-data:/data 441 | environment: 442 | - MINIO_ROOT_USER=${MINIO_USER} 443 | - MINIO_ROOT_PASSWORD=${MINIO_PASSWORD} 444 | networks: 445 | backend: 446 | driver: bridge 447 | internal: true 448 | security: 449 | driver: bridge 450 | internal: true 451 | license: 452 | driver: bridge 453 | internal: true 454 | file-management: 455 | driver: bridge 456 | internal: true 457 | notification: 458 | driver: bridge 459 | internal: true 460 | vpc: 461 | driver: bridge 462 | volumes: 463 | zookeeper1-data: 464 | zookeeper1-logs: 465 | zookeeper2-data: 466 | zookeeper2-logs: 467 | zookeeper3-data: 468 | zookeeper3-logs: 469 | kafka1-data: 470 | kafka2-data: 471 | kafka3-data: 472 | security-db-data: 473 | license-db-data: 474 | file-management-db-data: 475 | notification-db-data: 476 | minio-data: 477 | -------------------------------------------------------------------------------- /file-management/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /file-management/.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_TIMEZONE=UTC 6 | APP_URL=http://localhost 7 | 8 | APP_LOCALE=en 9 | APP_FALLBACK_LOCALE=en 10 | APP_FAKER_LOCALE=en_US 11 | 12 | APP_MAINTENANCE_DRIVER=file 13 | APP_MAINTENANCE_STORE=database 14 | 15 | BCRYPT_ROUNDS=12 16 | 17 | LOG_CHANNEL=stack 18 | LOG_STACK=single 19 | LOG_DEPRECATIONS_CHANNEL=null 20 | LOG_LEVEL=debug 21 | 22 | DB_CONNECTION=pgsql 23 | DB_HOST=127.0.0.1 24 | DB_PORT=5432 25 | DB_DATABASE=filemanagement 26 | DB_USERNAME=root 27 | DB_PASSWORD= 28 | 29 | SESSION_DRIVER=database 30 | SESSION_LIFETIME=120 31 | SESSION_ENCRYPT=false 32 | SESSION_PATH=/ 33 | SESSION_DOMAIN=null 34 | 35 | KAFKA_BROKERS= 36 | KAFKA_CONSUMER_GROUP_ID=file-management 37 | BROADCAST_CONNECTION=log 38 | FILESYSTEM_DISK=local 39 | QUEUE_CONNECTION=database 40 | 41 | CACHE_STORE=database 42 | CACHE_PREFIX= 43 | 44 | MEMCACHED_HOST=127.0.0.1 45 | 46 | REDIS_CLIENT=phpredis 47 | REDIS_HOST=127.0.0.1 48 | REDIS_PASSWORD=null 49 | REDIS_PORT=6379 50 | 51 | MAIL_MAILER=log 52 | MAIL_HOST=127.0.0.1 53 | MAIL_PORT=2525 54 | MAIL_USERNAME=null 55 | MAIL_PASSWORD=null 56 | MAIL_ENCRYPTION=null 57 | MAIL_FROM_ADDRESS="hello@example.com" 58 | MAIL_FROM_NAME="${APP_NAME}" 59 | 60 | AWS_ACCESS_KEY_ID= 61 | AWS_SECRET_ACCESS_KEY= 62 | AWS_DEFAULT_REGION=us-east-1 63 | AWS_BUCKET= 64 | AWS_USE_PATH_STYLE_ENDPOINT=false 65 | 66 | VITE_APP_NAME="${APP_NAME}" 67 | 68 | MINIO_ENDPOINT=http://127.0.0.1:9000/ 69 | MINIO_ACCESS_KEY=mservice 70 | MINIO_SECRET_KEY=topsecret 71 | -------------------------------------------------------------------------------- /file-management/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.md diff=markdown 4 | *.php diff=php 5 | -------------------------------------------------------------------------------- /file-management/.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /node_modules 3 | /public/build 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /vendor 8 | .env 9 | .env.backup 10 | .env.production 11 | .phpactor.json 12 | .phpunit.result.cache 13 | Homestead.json 14 | Homestead.yaml 15 | auth.json 16 | npm-debug.log 17 | yarn-error.log 18 | /.fleet 19 | /.idea 20 | /.vscode 21 | -------------------------------------------------------------------------------- /file-management/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | ARG WWWGROUP=1000 4 | ARG NODE_VERSION=20 5 | ARG POSTGRES_VERSION=15 6 | 7 | WORKDIR /var/www/html 8 | 9 | ENV DEBIAN_FRONTEND noninteractive 10 | ENV TZ=UTC 11 | ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80" 12 | ENV SUPERVISOR_WORKER_COMMAND="/usr/bin/php /var/www/html/artisan queue:listen" 13 | ENV SUPERVISOR_CONSUMER_COMMAND="/usr/bin/php /var/www/html/artisan app:consume-messages" 14 | ENV SUPERVISOR_PHP_USER="app" 15 | 16 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 17 | 18 | RUN apt-get update \ 19 | && mkdir -p /etc/apt/keyrings \ 20 | && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python2 dnsutils librsvg2-bin fswatch ffmpeg nano \ 21 | && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x14aa40ec0831756756d7f66c4f4ea0aae5267a6c' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \ 22 | && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu jammy main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ 23 | && apt-get update \ 24 | && apt-get install -y librdkafka-dev php8.2-cli php8.2-dev \ 25 | php8.2-pgsql php8.2-sqlite3 php8.2-gd php8.2-imagick \ 26 | php8.2-curl \ 27 | php8.2-imap php8.2-mysql php8.2-mbstring \ 28 | php8.2-xml php8.2-zip php8.2-bcmath php8.2-soap \ 29 | php8.2-intl php8.2-readline \ 30 | php8.2-ldap \ 31 | php8.2-msgpack php8.2-igbinary php8.2-redis php8.2-swoole \ 32 | php8.2-memcached php8.2-pcov php8.2-xdebug \ 33 | && pecl install rdkafka \ 34 | && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \ 35 | && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ 36 | && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \ 37 | && apt-get update \ 38 | && apt-get install -y nodejs \ 39 | && npm install -g npm \ 40 | && npm install -g pnpm \ 41 | && npm install -g bun \ 42 | && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \ 43 | && echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ 44 | && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \ 45 | && echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ 46 | && apt-get update \ 47 | && apt-get install -y yarn \ 48 | && apt-get install -y mysql-client \ 49 | && apt-get install -y postgresql-client-$POSTGRES_VERSION \ 50 | && apt-get -y autoremove \ 51 | && apt-get clean \ 52 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 53 | 54 | RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.2 55 | 56 | RUN groupadd --force -g $WWWGROUP app 57 | RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 app 58 | 59 | COPY docker/start-container /usr/local/bin/start-container 60 | COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf 61 | COPY docker/php.ini /etc/php/8.2/cli/conf.d/99-app.ini 62 | RUN chmod +x /usr/local/bin/start-container 63 | 64 | EXPOSE 8000 65 | 66 | ENTRYPOINT ["start-container"] 67 | -------------------------------------------------------------------------------- /file-management/artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | handleCommand(new ArgvInput); 14 | 15 | exit($status); 16 | -------------------------------------------------------------------------------- /file-management/bootstrap/app.php: -------------------------------------------------------------------------------- 1 | withCommands([ConsumeMessages::class]) 11 | ->withRouting( 12 | health: '/up', 13 | ) 14 | ->withMiddleware(function (Middleware $middleware) { 15 | // 16 | }) 17 | ->withExceptions(function (Exceptions $exceptions) { 18 | // 19 | })->create(); 20 | 21 | $app->useAppPath(join_paths(dirname(__DIR__), 'src')); 22 | 23 | return $app; 24 | -------------------------------------------------------------------------------- /file-management/bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /file-management/bootstrap/providers.php: -------------------------------------------------------------------------------- 1 | env('KAFKA_BROKERS', 'localhost:9092'), 8 | 9 | /* 10 | | Default security protocol 11 | */ 12 | 'securityProtocol' => env('KAFKA_SECURITY_PROTOCOL', 'PLAINTEXT'), 13 | 14 | /* 15 | | Default sasl configuration 16 | */ 17 | 'sasl' => [ 18 | 'mechanisms' => env('KAFKA_MECHANISMS', 'PLAINTEXT'), 19 | 'username' => env('KAFKA_USERNAME', null), 20 | 'password' => env('KAFKA_PASSWORD', null) 21 | ], 22 | 23 | /* 24 | | Kafka consumers belonging to the same consumer group share a group id. 25 | | The consumers in a group then divides the topic partitions as fairly amongst themselves as possible by 26 | | establishing that each partition is only consumed by a single consumer from the group. 27 | | This config defines the consumer group id you want to use for your project. 28 | */ 29 | 'consumer_group_id' => env('KAFKA_CONSUMER_GROUP_ID', 'group'), 30 | 31 | 'consumer_timeout_ms' => env("KAFKA_CONSUMER_DEFAULT_TIMEOUT", 2000), 32 | 33 | /* 34 | | After the consumer receives its assignment from the coordinator, 35 | | it must determine the initial position for each assigned partition. 36 | | When the group is first created, before any messages have been consumed, the position is set according to a configurable 37 | | offset reset policy (auto.offset.reset). Typically, consumption starts either at the earliest offset or the latest offset. 38 | | You can choose between "latest", "earliest" or "none". 39 | */ 40 | 'offset_reset' => env('KAFKA_OFFSET_RESET', 'latest'), 41 | 42 | /* 43 | | If you set enable.auto.commit (which is the default), then the consumer will automatically commit offsets periodically at the 44 | | interval set by auto.commit.interval.ms. 45 | */ 46 | 'auto_commit' => env('KAFKA_AUTO_COMMIT', true), 47 | 48 | 'sleep_on_error' => env('KAFKA_ERROR_SLEEP', 5), 49 | 50 | 'partition' => env('KAFKA_PARTITION', 0), 51 | 52 | /* 53 | | Kafka supports 4 compression codecs: none , gzip , lz4 and snappy 54 | */ 55 | 'compression' => env('KAFKA_COMPRESSION_TYPE', 'snappy'), 56 | 57 | /* 58 | | Choose if debug is enabled or not. 59 | */ 60 | 'debug' => env('KAFKA_DEBUG', false), 61 | 62 | /* 63 | | Repository for batching messages together 64 | | Implement BatchRepositoryInterface to save batches in different storage 65 | */ 66 | 'batch_repository' => env('KAFKA_BATCH_REPOSITORY', \Junges\Kafka\BatchRepositories\InMemoryBatchRepository::class), 67 | 68 | /* 69 | | The sleep time in milliseconds that will be used when retrying flush 70 | */ 71 | 'flush_retry_sleep_in_ms' => 100, 72 | 73 | /* 74 | | The cache driver that will be used 75 | */ 76 | 'cache_driver' => env('KAFKA_CACHE_DRIVER', env('CACHE_DRIVER', 'file')), 77 | ]; 78 | -------------------------------------------------------------------------------- /file-management/config/services.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'endpoint' => env('MINIO_ENDPOINT', 'http://127.0.0.1:9000'), 6 | 'access_key' => env('MINIO_ACCESS_KEY', 'mservice'), 7 | 'secret_key' => env('MINIO_SECRET_KEY', 'topsecret'), 8 | ], 9 | 10 | 'license' => [ 11 | 'base_uri' => env('LICENSE_SERVICE_URL', 'http://license/api/v1/'), 12 | 'timeout' => env('LICENSE_SERVICE_TIMEOUT', 30), 13 | ] 14 | ]; 15 | -------------------------------------------------------------------------------- /file-management/database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /file-management/docker/php.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | post_max_size = 100M 3 | upload_max_filesize = 100M 4 | variables_order = EGPCS 5 | pcov.directory = . 6 | 7 | extension=rdkafka.so 8 | -------------------------------------------------------------------------------- /file-management/docker/start-container: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "app" ]; then 4 | echo "You should set SUPERVISOR_PHP_USER to either 'app' or 'root'." 5 | exit 1 6 | fi 7 | 8 | if [ ! -z "$WWWUSER" ]; then 9 | usermod -u $WWWUSER app 10 | fi 11 | 12 | if [ ! -d /.composer ]; then 13 | mkdir /.composer 14 | fi 15 | 16 | chmod -R ugo+rw /.composer 17 | 18 | if [ $# -gt 0 ]; then 19 | if [ "$SUPERVISOR_PHP_USER" = "root" ]; then 20 | exec "$@" 21 | else 22 | exec gosu $WWWUSER "$@" 23 | fi 24 | else 25 | exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf 26 | fi 27 | -------------------------------------------------------------------------------- /file-management/docker/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | user=root 4 | logfile=/var/log/supervisor/supervisord.log 5 | pidfile=/var/run/supervisord.pid 6 | 7 | [program:php] 8 | command=%(ENV_SUPERVISOR_PHP_COMMAND)s 9 | user=%(ENV_SUPERVISOR_PHP_USER)s 10 | environment=APP_IN_DOCKER="1" 11 | stdout_logfile=/dev/stdout 12 | stdout_logfile_maxbytes=0 13 | stderr_logfile=/dev/stderr 14 | stderr_logfile_maxbytes=0 15 | 16 | [program:worker] 17 | command=%(ENV_SUPERVISOR_WORKER_COMMAND)s 18 | user=%(ENV_SUPERVISOR_PHP_USER)s 19 | environment=APP_IN_DOCKER="1" 20 | stdout_logfile=/dev/stdout 21 | stdout_logfile_maxbytes=0 22 | stderr_logfile=/dev/stderr 23 | stderr_logfile_maxbytes=0 24 | 25 | [program:consumer] 26 | command=%(ENV_SUPERVISOR_CONSUMER_COMMAND)s 27 | user=%(ENV_SUPERVISOR_PHP_USER)s 28 | environment=APP_IN_DOCKER="1" 29 | stdout_logfile=/dev/stdout 30 | stdout_logfile_maxbytes=0 31 | stderr_logfile=/dev/stderr 32 | stderr_logfile_maxbytes=0 33 | -------------------------------------------------------------------------------- /file-management/public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Send Requests To Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /file-management/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erayaydin/microservice-laravel/c54fe589bb22ddca910b8da295537b82cdf20096/file-management/public/favicon.ico -------------------------------------------------------------------------------- /file-management/public/index.php: -------------------------------------------------------------------------------- 1 | handleRequest(Request::capture()); 18 | -------------------------------------------------------------------------------- /file-management/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /file-management/src/Actions/DownloadFile.php: -------------------------------------------------------------------------------- 1 | s3Client->getObject([ 23 | 'Bucket' => $bucket, 24 | 'Key' => $fileKey, 25 | ]); 26 | } 27 | 28 | public function asController(ActionRequest $request, File $file): Response 29 | { 30 | $userId = $request->input('user_id'); 31 | if ($file->getAttribute('user_id') != $userId) 32 | abort(403); 33 | 34 | $key = $file->getAttribute('key'); 35 | $object = $this->handle("files-$userId", $key); 36 | 37 | return response($object['Body'], 200) 38 | ->header('Content-Type', $object['ContentType']) 39 | ->header( 40 | 'Content-Disposition', 41 | 'attachment; filename="' . ($file->getAttribute('filename') ?: $key) . '"' 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /file-management/src/Actions/GetFiles.php: -------------------------------------------------------------------------------- 1 | fileModel->newQuery()->ofUser($userId)->latest()->paginate(20); 23 | } 24 | 25 | public function asController(ActionRequest $request): AnonymousResourceCollection 26 | { 27 | $files = $this->handle($request->input('user_id')); 28 | 29 | return FileResource::collection($files); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /file-management/src/Actions/UploadFile.php: -------------------------------------------------------------------------------- 1 | remember("{$userId}_limits", 15 * 60, fn() => Limits::forUser($userId)); 49 | 50 | if ($limits->isExpired) { 51 | throw new LicenseExpiredException; 52 | } 53 | 54 | if ($file->getSize() > $limits->maxFileSize) { 55 | throw new EntityTooLargeException; 56 | } 57 | 58 | if ($this->fileModel->ofUser($userId)->in24h()->count() > $limits->dailyLimit) { 59 | throw new DailyLimitException; 60 | } 61 | 62 | /** @var int $balance */ 63 | $balance = cache()->rememberForever( 64 | "{$userId}_balance", 65 | fn() => $this->fileModel->ofUser($userId)->sum('size') 66 | ); 67 | 68 | if ($balance + $file->getSize() > $limits->quota) { 69 | throw new QuotaReachedException; 70 | } 71 | 72 | $key = Str::uuid() . "." . $file->extension(); 73 | 74 | $uploadResult = $this->s3Client->putObject([ 75 | 'Bucket' => $bucketName, 76 | 'Key' => $key, 77 | 'Body' => fopen($file->getPathname(), 'r'), 78 | 'ContentType' => $file->getMimeType(), 79 | ]); 80 | 81 | /** @var File $record */ 82 | $record = $this->fileModel->query()->create([ 83 | 'user_id' => $userId, 84 | 'bucket' => $bucketName, 85 | 'key' => $key, 86 | 'object_url' => $uploadResult['ObjectURL'], 87 | 'filename' => $file->getClientOriginalName(), 88 | 'description' => $description, 89 | 'is_public' => $isPublic, 90 | 'checksum' => hash('md5', $file->getContent()), 91 | 'mimetype' => $file->getMimeType(), 92 | 'size' => $file->getSize(), 93 | ]); 94 | 95 | cache()->set("{$userId}_balance", $balance + $file->getSize()); 96 | 97 | return $record; 98 | } 99 | 100 | /** 101 | * @param ActionRequest $request 102 | * @return JsonResponse|FileResource 103 | * @throws DailyLimitException 104 | * @throws EntityTooLargeException 105 | * @throws GuzzleException 106 | * @throws QuotaReachedException 107 | * @throws LicenseExpiredException|InvalidArgumentException 108 | */ 109 | public function asController(ActionRequest $request): JsonResponse|FileResource 110 | { 111 | $userId = $request->input('user_id'); 112 | 113 | $file = $this->handle( 114 | $userId, 115 | "files-$userId", 116 | $request->file('file'), 117 | $request->validated('description'), 118 | $request->validated('is_public') ?: false, 119 | ); 120 | 121 | return new FileResource($file); 122 | } 123 | 124 | public function rules(): array 125 | { 126 | return [ 127 | 'file' => ['required', 'file'], 128 | 'description' => ['string', 'max:1000'], 129 | 'is_public' => ['bool'], 130 | ]; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /file-management/src/Clients/LicenseClient.php: -------------------------------------------------------------------------------- 1 | client = new Client([ 19 | 'base_uri' => config('services.license.base_uri'), 20 | 'timeout' => config('services.license.timeout', 30), 21 | ]); 22 | } 23 | 24 | /** 25 | * Pass dynamic methods onto the router instance. 26 | * 27 | * @param string $method 28 | * @param array $parameters 29 | * @return mixed 30 | */ 31 | public function __call(string $method, array $parameters) 32 | { 33 | return $this->forwardCallTo( 34 | $this->client, $method, $parameters 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /file-management/src/Clients/S3Client.php: -------------------------------------------------------------------------------- 1 | client = new Client([ 21 | 'region' => 'us-east-1', 22 | 'endpoint' => config('services.minio.endpoint'), 23 | 'use_path_style_endpoint' => true, 24 | 'credentials' => [ 25 | 'key' => config('services.minio.access_key'), 26 | 'secret' => config('services.minio.secret_key'), 27 | ], 28 | ]); 29 | } 30 | 31 | /** 32 | * Pass dynamic methods onto the router instance. 33 | * 34 | * @param string $method 35 | * @param array $parameters 36 | * @return mixed 37 | */ 38 | public function __call(string $method, array $parameters) 39 | { 40 | return $this->forwardCallTo( 41 | $this->client, $method, $parameters 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /file-management/src/Commands/ConsumeMessages.php: -------------------------------------------------------------------------------- 1 | subscribe('license.updated') 39 | ->withHandler(new KafkaMessageHandler) 40 | ->build() 41 | ->consume(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /file-management/src/Exceptions/DailyLimitException.php: -------------------------------------------------------------------------------- 1 | getBody()); 20 | 21 | $user = $body->get('user_id'); 22 | $oldLicense = $body->get('old_license'); 23 | 24 | $bucketName = "files-$user"; 25 | 26 | if (is_null($oldLicense)) { 27 | Bus::dispatchChain([ 28 | // TODO: generate bucket name with guid. 29 | new CreateNewBucket($user, $bucketName), 30 | // TODO: Add `SetBucketQuota` job to enforce quota. 31 | ]); 32 | 33 | return; 34 | } 35 | 36 | // TODO: implement job chain for "CheckQuotaApplicable" and "SetBucketQuota" with using old/new diff values 37 | cache()->set("{$user}_limits", new Limits( 38 | $body->get('new_quota'), 39 | $body->get('new_max_file_size'), 40 | $body->get('new_daily_object_limit'), 41 | false, 42 | )); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /file-management/src/Jobs/CreateNewBucket.php: -------------------------------------------------------------------------------- 1 | createBucket([ 31 | 'Bucket' => $this->bucketName, 32 | ]); 33 | 34 | logger()->info("Bucket $this->bucketName for user $this->userId created."); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /file-management/src/Middlewares/EnsureTokenIsValid.php: -------------------------------------------------------------------------------- 1 | bearerToken(); 22 | 23 | if (is_null($jwt)) { 24 | return response(status: 403); 25 | } 26 | 27 | $pKey = file_get_contents("/tmp/secrets/oauth-public.key"); 28 | 29 | try { 30 | $decoded = JWT::decode($jwt, new Key($pKey, 'RS256')); 31 | } catch (Exception) { 32 | return response(status: 403); 33 | } 34 | 35 | $request->merge(['user_id' => $decoded->sub]); 36 | 37 | return $next($request); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /file-management/src/Migrations/2024_06_07_020305_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 16 | $table->string('queue')->index(); 17 | $table->longText('payload'); 18 | $table->unsignedTinyInteger('attempts'); 19 | $table->unsignedInteger('reserved_at')->nullable(); 20 | $table->unsignedInteger('available_at'); 21 | $table->unsignedInteger('created_at'); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('jobs'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /file-management/src/Migrations/2024_06_07_020313_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('uuid')->unique(); 17 | $table->text('connection'); 18 | $table->text('queue'); 19 | $table->longText('payload'); 20 | $table->longText('exception'); 21 | $table->timestamp('failed_at')->useCurrent(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('failed_jobs'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /file-management/src/Migrations/2024_06_07_054155_create_files_table.php: -------------------------------------------------------------------------------- 1 | uuid('id')->primary(); 16 | 17 | $table->unsignedBigInteger('user_id'); 18 | $table->string('bucket'); 19 | $table->string('key'); 20 | $table->string('object_url'); 21 | // It can be stream upload and doesn't have any filename. It's still downloadable with the key information. 22 | $table->string('filename')->nullable(); 23 | $table->text('description')->nullable(); 24 | $table->boolean('is_public')->default(false); 25 | $table->string('checksum')->nullable(); 26 | $table->timestamp('expired_at')->nullable(); 27 | $table->string('mimetype')->nullable(); 28 | $table->unsignedBigInteger('size')->nullable(); 29 | $table->json('metadata')->nullable(); 30 | 31 | $table->timestamps(); 32 | }); 33 | } 34 | 35 | /** 36 | * Reverse the migrations. 37 | */ 38 | public function down(): void 39 | { 40 | Schema::dropIfExists('files'); 41 | } 42 | }; 43 | -------------------------------------------------------------------------------- /file-management/src/Migrations/2024_06_07_064602_create_cache_table.php: -------------------------------------------------------------------------------- 1 | string('key')->primary(); 16 | $table->mediumText('value'); 17 | $table->integer('expiration'); 18 | }); 19 | 20 | Schema::create('cache_locks', function (Blueprint $table) { 21 | $table->string('key')->primary(); 22 | $table->string('owner'); 23 | $table->integer('expiration'); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('cache'); 33 | Schema::dropIfExists('cache_locks'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /file-management/src/Models/File.php: -------------------------------------------------------------------------------- 1 | 'boolean', 21 | ]; 22 | 23 | public $incrementing = false; 24 | 25 | public $keyType = 'string'; 26 | 27 | protected static function boot(): void 28 | { 29 | parent::boot(); 30 | 31 | static::creating(function ($file) { 32 | if (empty($file->id)) { 33 | $file->id = Str::uuid()->toString(); 34 | } 35 | }); 36 | } 37 | 38 | public function scopeOfUser(Builder $query, int $userId): Builder 39 | { 40 | return $query->where('user_id', $userId); 41 | } 42 | 43 | public function scopeIn24h(Builder $query): Builder 44 | { 45 | return $query->where('created_at', '>=', now()->subDay()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /file-management/src/Models/Limits.php: -------------------------------------------------------------------------------- 1 | get("users/$userId"); 26 | 27 | if ($response->getStatusCode() != 200) { 28 | throw new RuntimeException('User license information could not retrieve!'); 29 | } 30 | 31 | $license = collect(json_decode($response->getBody())->data); 32 | 33 | return new Limits( 34 | $license->get('quota'), 35 | $license->get('max_file_size'), 36 | $license->get('daily_object_limit'), 37 | now()->gte($license->get('expired_at')), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /file-management/src/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadMigrationsFrom($this->app->path('Migrations')); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /file-management/src/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | booted(function () { 29 | if (! is_null($this->namespace)) { 30 | $this->app[UrlGenerator::class]->setRootControllerNamespace($this->namespace); 31 | } 32 | 33 | if ($this->app->routesAreCached()) { 34 | $this->app->booted(function () { 35 | require $this->app->getCachedRoutesPath(); 36 | }); 37 | } else { 38 | $this->app->call([$this, 'routing']); 39 | 40 | $this->app->booted(function () { 41 | $this->app['router']->getRoutes()->refreshNameLookups(); 42 | $this->app['router']->getRoutes()->refreshActionLookups(); 43 | }); 44 | } 45 | }); 46 | } 47 | 48 | public function routing(): void 49 | { 50 | Route::middleware(['api', EnsureTokenIsValid::class]) 51 | ->prefix('api/v1') 52 | ->group($this->app->path('Routes/ApiV1.php')); 53 | 54 | Route::get('/health', function () { 55 | Event::dispatch(new DiagnosingHealth); 56 | 57 | return response(status: 200); 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /file-management/src/Resources/FileResource.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | public function toArray(Request $request): array 20 | { 21 | return [ 22 | 'id' => $this->resource->getKey(), 23 | 'filename' => $this->resource->getAttribute('filename'), 24 | 'description' => $this->resource->getAttribute('description'), 25 | 'is_public' => $this->resource->getAttribute('is_public'), 26 | 'checksum' => $this->resource->getAttribute('checksum'), 27 | 'mimetype' => $this->resource->getAttribute('mimetype'), 28 | 'size' => $this->resource->getAttribute('size'), 29 | 'metadata' => $this->resource->getAttribute('metadata'), 30 | 'expired_at' => $this->resource->getAttribute('expired_at'), 31 | 'created_at' => $this->resource->getAttribute('created_at'), 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /file-management/src/Routes/ApiV1.php: -------------------------------------------------------------------------------- 1 | make(Router::class); 12 | } catch (BindingResolutionException $e) { 13 | abort(500, $e->getMessage()); 14 | } 15 | 16 | $route->group(['prefix' => 'files', 'as' => 'file.'], function (Router $route) { 17 | $route->get('/', GetFiles::class)->name('index'); 18 | $route->post('/', UploadFile::class)->name('store'); 19 | $route->get('{file}/download', DownloadFile::class)->name('download'); 20 | }); 21 | -------------------------------------------------------------------------------- /file-management/storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /file-management/storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /file-management/storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | compiled.php 2 | config.php 3 | down 4 | events.scanned.php 5 | maintenance.php 6 | routes.php 7 | routes.scanned.php 8 | schedule-* 9 | services.json 10 | -------------------------------------------------------------------------------- /file-management/storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /file-management/storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /file-management/storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /file-management/storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /file-management/storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /file-management/storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /license/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /license/.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_TIMEZONE=UTC 6 | APP_URL=http://localhost 7 | 8 | APP_LOCALE=en 9 | APP_FALLBACK_LOCALE=en 10 | APP_FAKER_LOCALE=en_US 11 | 12 | APP_MAINTENANCE_DRIVER=file 13 | APP_MAINTENANCE_STORE=database 14 | 15 | BCRYPT_ROUNDS=12 16 | 17 | LOG_CHANNEL=stack 18 | LOG_STACK=single 19 | LOG_DEPRECATIONS_CHANNEL=null 20 | LOG_LEVEL=debug 21 | 22 | DB_CONNECTION=pgsql 23 | DB_HOST=127.0.0.1 24 | DB_PORT=5432 25 | DB_DATABASE=license 26 | DB_USERNAME=root 27 | DB_PASSWORD= 28 | 29 | SESSION_DRIVER=database 30 | SESSION_LIFETIME=120 31 | SESSION_ENCRYPT=false 32 | SESSION_PATH=/ 33 | SESSION_DOMAIN=null 34 | 35 | KAFKA_BROKERS= 36 | KAFKA_CONSUMER_GROUP_ID=license 37 | BROADCAST_CONNECTION=log 38 | FILESYSTEM_DISK=local 39 | QUEUE_CONNECTION=database 40 | 41 | CACHE_STORE=database 42 | CACHE_PREFIX= 43 | 44 | MEMCACHED_HOST=127.0.0.1 45 | 46 | REDIS_CLIENT=phpredis 47 | REDIS_HOST=127.0.0.1 48 | REDIS_PASSWORD=null 49 | REDIS_PORT=6379 50 | 51 | MAIL_MAILER=log 52 | MAIL_HOST=127.0.0.1 53 | MAIL_PORT=2525 54 | MAIL_USERNAME=null 55 | MAIL_PASSWORD=null 56 | MAIL_ENCRYPTION=null 57 | MAIL_FROM_ADDRESS="hello@example.com" 58 | MAIL_FROM_NAME="${APP_NAME}" 59 | 60 | AWS_ACCESS_KEY_ID= 61 | AWS_SECRET_ACCESS_KEY= 62 | AWS_DEFAULT_REGION=us-east-1 63 | AWS_BUCKET= 64 | AWS_USE_PATH_STYLE_ENDPOINT=false 65 | 66 | VITE_APP_NAME="${APP_NAME}" 67 | -------------------------------------------------------------------------------- /license/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.md diff=markdown 4 | *.php diff=php 5 | -------------------------------------------------------------------------------- /license/.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /node_modules 3 | /public/build 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /vendor 8 | .env 9 | .env.backup 10 | .env.production 11 | .phpactor.json 12 | .phpunit.result.cache 13 | Homestead.json 14 | Homestead.yaml 15 | auth.json 16 | npm-debug.log 17 | yarn-error.log 18 | /.fleet 19 | /.idea 20 | /.vscode 21 | -------------------------------------------------------------------------------- /license/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | ARG WWWGROUP=1000 4 | ARG NODE_VERSION=20 5 | ARG POSTGRES_VERSION=15 6 | 7 | WORKDIR /var/www/html 8 | 9 | ENV DEBIAN_FRONTEND noninteractive 10 | ENV TZ=UTC 11 | ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80" 12 | ENV SUPERVISOR_WORKER_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan queue:listen" 13 | ENV SUPERVISOR_KAFKA_CONSUMER_COMMAND="/usr/bin/php /var/www/html/artisan app:consume-messages" 14 | ENV SUPERVISOR_PHP_USER="app" 15 | 16 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 17 | 18 | RUN apt-get update \ 19 | && mkdir -p /etc/apt/keyrings \ 20 | && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python2 dnsutils librsvg2-bin fswatch ffmpeg nano \ 21 | && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x14aa40ec0831756756d7f66c4f4ea0aae5267a6c' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \ 22 | && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu jammy main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ 23 | && apt-get update \ 24 | && apt-get install -y librdkafka-dev php8.2-cli php8.2-dev \ 25 | php8.2-pgsql php8.2-sqlite3 php8.2-gd php8.2-imagick \ 26 | php8.2-curl \ 27 | php8.2-imap php8.2-mysql php8.2-mbstring \ 28 | php8.2-xml php8.2-zip php8.2-bcmath php8.2-soap \ 29 | php8.2-intl php8.2-readline \ 30 | php8.2-ldap \ 31 | php8.2-msgpack php8.2-igbinary php8.2-redis php8.2-swoole \ 32 | php8.2-memcached php8.2-pcov php8.2-xdebug \ 33 | && pecl install rdkafka \ 34 | && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \ 35 | && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ 36 | && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \ 37 | && apt-get update \ 38 | && apt-get install -y nodejs \ 39 | && npm install -g npm \ 40 | && npm install -g pnpm \ 41 | && npm install -g bun \ 42 | && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \ 43 | && echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ 44 | && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \ 45 | && echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ 46 | && apt-get update \ 47 | && apt-get install -y yarn \ 48 | && apt-get install -y mysql-client \ 49 | && apt-get install -y postgresql-client-$POSTGRES_VERSION \ 50 | && apt-get -y autoremove \ 51 | && apt-get clean \ 52 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 53 | 54 | RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.2 55 | 56 | RUN groupadd --force -g $WWWGROUP app 57 | RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 app 58 | 59 | COPY docker/start-container /usr/local/bin/start-container 60 | COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf 61 | COPY docker/php.ini /etc/php/8.2/cli/conf.d/99-app.ini 62 | RUN chmod +x /usr/local/bin/start-container 63 | 64 | EXPOSE 8000 65 | 66 | ENTRYPOINT ["start-container"] 67 | -------------------------------------------------------------------------------- /license/artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | handleCommand(new ArgvInput); 14 | 15 | exit($status); 16 | -------------------------------------------------------------------------------- /license/bootstrap/app.php: -------------------------------------------------------------------------------- 1 | withCommands([ConsumeMessages::class]) 11 | ->withRouting( 12 | health: '/up', 13 | ) 14 | ->withMiddleware(function (Middleware $middleware) { 15 | // 16 | }) 17 | ->withExceptions(function (Exceptions $exceptions) { 18 | // 19 | })->create(); 20 | 21 | $app->useAppPath(join_paths(dirname(__DIR__), 'src')); 22 | 23 | return $app; 24 | -------------------------------------------------------------------------------- /license/bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /license/bootstrap/providers.php: -------------------------------------------------------------------------------- 1 | env('KAFKA_BROKERS', 'localhost:9092'), 8 | 9 | /* 10 | | Default security protocol 11 | */ 12 | 'securityProtocol' => env('KAFKA_SECURITY_PROTOCOL', 'PLAINTEXT'), 13 | 14 | /* 15 | | Default sasl configuration 16 | */ 17 | 'sasl' => [ 18 | 'mechanisms' => env('KAFKA_MECHANISMS', 'PLAINTEXT'), 19 | 'username' => env('KAFKA_USERNAME', null), 20 | 'password' => env('KAFKA_PASSWORD', null) 21 | ], 22 | 23 | /* 24 | | Kafka consumers belonging to the same consumer group share a group id. 25 | | The consumers in a group then divides the topic partitions as fairly amongst themselves as possible by 26 | | establishing that each partition is only consumed by a single consumer from the group. 27 | | This config defines the consumer group id you want to use for your project. 28 | */ 29 | 'consumer_group_id' => env('KAFKA_CONSUMER_GROUP_ID', 'group'), 30 | 31 | 'consumer_timeout_ms' => env("KAFKA_CONSUMER_DEFAULT_TIMEOUT", 2000), 32 | 33 | /* 34 | | After the consumer receives its assignment from the coordinator, 35 | | it must determine the initial position for each assigned partition. 36 | | When the group is first created, before any messages have been consumed, the position is set according to a configurable 37 | | offset reset policy (auto.offset.reset). Typically, consumption starts either at the earliest offset or the latest offset. 38 | | You can choose between "latest", "earliest" or "none". 39 | */ 40 | 'offset_reset' => env('KAFKA_OFFSET_RESET', 'latest'), 41 | 42 | /* 43 | | If you set enable.auto.commit (which is the default), then the consumer will automatically commit offsets periodically at the 44 | | interval set by auto.commit.interval.ms. 45 | */ 46 | 'auto_commit' => env('KAFKA_AUTO_COMMIT', true), 47 | 48 | 'sleep_on_error' => env('KAFKA_ERROR_SLEEP', 5), 49 | 50 | 'partition' => env('KAFKA_PARTITION', 0), 51 | 52 | /* 53 | | Kafka supports 4 compression codecs: none , gzip , lz4 and snappy 54 | */ 55 | 'compression' => env('KAFKA_COMPRESSION_TYPE', 'snappy'), 56 | 57 | /* 58 | | Choose if debug is enabled or not. 59 | */ 60 | 'debug' => env('KAFKA_DEBUG', false), 61 | 62 | /* 63 | | Repository for batching messages together 64 | | Implement BatchRepositoryInterface to save batches in different storage 65 | */ 66 | 'batch_repository' => env('KAFKA_BATCH_REPOSITORY', \Junges\Kafka\BatchRepositories\InMemoryBatchRepository::class), 67 | 68 | /* 69 | | The sleep time in milliseconds that will be used when retrying flush 70 | */ 71 | 'flush_retry_sleep_in_ms' => 100, 72 | 73 | /* 74 | | The cache driver that will be used 75 | */ 76 | 'cache_driver' => env('KAFKA_CACHE_DRIVER', env('CACHE_DRIVER', 'file')), 77 | ]; 78 | -------------------------------------------------------------------------------- /license/docker/php.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | post_max_size = 100M 3 | upload_max_filesize = 100M 4 | variables_order = EGPCS 5 | pcov.directory = . 6 | 7 | extension=rdkafka.so 8 | -------------------------------------------------------------------------------- /license/docker/start-container: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "app" ]; then 4 | echo "You should set SUPERVISOR_PHP_USER to either 'app' or 'root'." 5 | exit 1 6 | fi 7 | 8 | if [ ! -z "$WWWUSER" ]; then 9 | usermod -u $WWWUSER app 10 | fi 11 | 12 | if [ ! -d /.composer ]; then 13 | mkdir /.composer 14 | fi 15 | 16 | chmod -R ugo+rw /.composer 17 | 18 | if [ $# -gt 0 ]; then 19 | if [ "$SUPERVISOR_PHP_USER" = "root" ]; then 20 | exec "$@" 21 | else 22 | exec gosu $WWWUSER "$@" 23 | fi 24 | else 25 | exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf 26 | fi 27 | -------------------------------------------------------------------------------- /license/docker/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | user=root 4 | logfile=/var/log/supervisor/supervisord.log 5 | pidfile=/var/run/supervisord.pid 6 | 7 | [program:php] 8 | command=%(ENV_SUPERVISOR_PHP_COMMAND)s 9 | user=%(ENV_SUPERVISOR_PHP_USER)s 10 | environment=APP_IN_DOCKER="1" 11 | stdout_logfile=/dev/stdout 12 | stdout_logfile_maxbytes=0 13 | stderr_logfile=/dev/stderr 14 | stderr_logfile_maxbytes=0 15 | 16 | [program:worker] 17 | command=%(ENV_SUPERVISOR_WORKER_COMMAND)s 18 | user=%(ENV_SUPERVISOR_PHP_USER)s 19 | environment=APP_IN_DOCKER="1" 20 | stdout_logfile=/dev/stdout 21 | stdout_logfile_maxbytes=0 22 | stderr_logfile=/dev/stderr 23 | stderr_logfile_maxbytes=0 24 | 25 | [program:kafkaconsumer] 26 | command=%(ENV_SUPERVISOR_KAFKA_CONSUMER_COMMAND)s 27 | user=%(ENV_SUPERVISOR_PHP_USER)s 28 | environment=APP_IN_DOCKER="1" 29 | stdout_logfile=/dev/stdout 30 | stdout_logfile_maxbytes=0 31 | stderr_logfile=/dev/stderr 32 | stderr_logfile_maxbytes=0 33 | -------------------------------------------------------------------------------- /license/public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Send Requests To Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /license/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erayaydin/microservice-laravel/c54fe589bb22ddca910b8da295537b82cdf20096/license/public/favicon.ico -------------------------------------------------------------------------------- /license/public/index.php: -------------------------------------------------------------------------------- 1 | handleRequest(Request::capture()); 18 | -------------------------------------------------------------------------------- /license/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /license/src/Actions/GetLicenseOfUser.php: -------------------------------------------------------------------------------- 1 | licenseModel->query()->forUser($userId)->notExpired()->first(); 21 | } 22 | 23 | public function asController(ActionRequest $request, int $userId): LicenseResource 24 | { 25 | // TODO: check `admin.license` scope to internal RestAPI 26 | return new LicenseResource( 27 | $this->handler($userId) 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /license/src/Actions/LicenseUpdated.php: -------------------------------------------------------------------------------- 1 | onTopic('license.updated') 21 | ->withHeaders([ 22 | 'user_id' => $userId, 23 | ]) 24 | ->withBodyKey('user_id', $userId) 25 | ->withBodyKey('old_license', $oldLicense?->getAttribute('license_type')->value) 26 | ->withBodyKey('new_license', $newLicense->getAttribute('license_type')->value) 27 | ->withBodyKey('old_max_file_size', $oldLicense?->getAttribute('max_file_size')) 28 | ->withBodyKey('new_max_file_size', $newLicense->getAttribute('max_file_size')) 29 | ->withBodyKey('old_daily_object_limit', $oldLicense?->getAttribute('daily_object_limit')) 30 | ->withBodyKey('new_daily_object_limit', $newLicense->getAttribute('daily_object_limit')) 31 | ->withBodyKey('old_quota', $oldLicense?->getAttribute('quota')) 32 | ->withBodyKey('new_quota', $newLicense->getAttribute('quota')) 33 | ->send(); 34 | } 35 | 36 | /** 37 | * @throws Exception 38 | */ 39 | public function asJob(int $userId, License $newLicense, ?License $oldLicense = null): void 40 | { 41 | $this->handle( 42 | $userId, 43 | $newLicense, 44 | $oldLicense 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /license/src/Actions/MeLicense.php: -------------------------------------------------------------------------------- 1 | handler($request->input('user_id')) 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /license/src/Commands/ConsumeMessages.php: -------------------------------------------------------------------------------- 1 | subscribe('user.created') 39 | ->withHandler(new KafkaMessageHandler) 40 | ->build() 41 | ->consume(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /license/src/Handlers/KafkaMessageHandler.php: -------------------------------------------------------------------------------- 1 | getBody()); 16 | $user = $body->get('user_id'); 17 | 18 | if (License::query()->forUser($user)->notExpired()->count() > 0) 19 | return; 20 | 21 | $demoLicense = LicenseType::DEMO; 22 | 23 | $newLicense = License::query()->create([ 24 | 'user_id' => $user, 25 | 'email' => $body->get('email'), 26 | 'started_at' => now(), 27 | 'expired_at' => now()->addDays(30), 28 | 'license_type' => $demoLicense, 29 | 'max_file_size' => $demoLicense->getMaxFileSize(), 30 | 'daily_object_limit' => $demoLicense->getDailyObjectLimit(), 31 | 'quota' => $demoLicense->getQuota(), 32 | ]); 33 | 34 | LicenseUpdated::dispatch($user, $newLicense); 35 | 36 | logger()->info(LicenseType::DEMO->name . " license created for user: " . $user); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /license/src/Middlewares/EnsureTokenIsValid.php: -------------------------------------------------------------------------------- 1 | bearerToken(); 22 | 23 | if (is_null($jwt)) { 24 | return response(status: 403); 25 | } 26 | 27 | $pKey = file_get_contents("/tmp/secrets/oauth-public.key"); 28 | 29 | try { 30 | $decoded = JWT::decode($jwt, new Key($pKey, 'RS256')); 31 | } catch (Exception) { 32 | return response(status: 403); 33 | } 34 | 35 | $request->merge(['user_id' => $decoded->sub]); 36 | 37 | return $next($request); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /license/src/Migrations/2024_06_06_135026_create_licenses_table.php: -------------------------------------------------------------------------------- 1 | id(); 17 | 18 | $table->unsignedBigInteger('user_id'); 19 | $table->enum('license_type', ['DEMO', 'STARTER', 'ULTIMATE']); 20 | $table->timestamp('started_at')->default(DB::raw('NOW()')); 21 | $table->timestamp('expired_at'); 22 | $table->unsignedBigInteger('max_file_size'); 23 | $table->unsignedBigInteger('daily_object_limit'); 24 | $table->unsignedBigInteger('quota'); 25 | $table->string('email')->nullable(); 26 | 27 | $table->timestamps(); 28 | }); 29 | } 30 | 31 | /** 32 | * Reverse the migrations. 33 | */ 34 | public function down(): void 35 | { 36 | Schema::dropIfExists('licenses'); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /license/src/Migrations/2024_06_07_021508_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('uuid')->unique(); 17 | $table->text('connection'); 18 | $table->text('queue'); 19 | $table->longText('payload'); 20 | $table->longText('exception'); 21 | $table->timestamp('failed_at')->useCurrent(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('failed_jobs'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /license/src/Migrations/2024_06_07_021519_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 16 | $table->string('queue')->index(); 17 | $table->longText('payload'); 18 | $table->unsignedTinyInteger('attempts'); 19 | $table->unsignedInteger('reserved_at')->nullable(); 20 | $table->unsignedInteger('available_at'); 21 | $table->unsignedInteger('created_at'); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('jobs'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /license/src/Models/License.php: -------------------------------------------------------------------------------- 1 | LicenseType::class, 21 | ]; 22 | } 23 | 24 | public function scopeForUser(Builder $query, string $user): Builder 25 | { 26 | return $query->where('user_id', $user); 27 | } 28 | 29 | public function scopeNotExpired(Builder $query): Builder 30 | { 31 | return $query->where('expired_at', '>=', now()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /license/src/Models/LicenseType.php: -------------------------------------------------------------------------------- 1 | 100 * 1024 * 1024, 15 | LicenseType::STARTER => 1024 * 1024 * 1024, 16 | LicenseType::ULTIMATE => 64 * 1024 * 1024 * 1024, 17 | }; 18 | } 19 | 20 | public function getDailyObjectLimit(): int 21 | { 22 | return match($this) { 23 | LicenseType::DEMO => 5, 24 | LicenseType::STARTER => 50, 25 | LicenseType::ULTIMATE => 300, 26 | }; 27 | } 28 | 29 | public function getQuota(): int 30 | { 31 | return match($this) { 32 | LicenseType::DEMO => 1024 * 1024 * 1024, 33 | LicenseType::STARTER => 30 * 1024 * 1024 * 1024, 34 | LicenseType::ULTIMATE => 1024 * 1024 * 1024 * 1024, 35 | }; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /license/src/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadMigrationsFrom(join_paths(dirname(__DIR__), 'Migrations')); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /license/src/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | booted(function () { 28 | if (! is_null($this->namespace)) { 29 | $this->app[UrlGenerator::class]->setRootControllerNamespace($this->namespace); 30 | } 31 | 32 | if ($this->app->routesAreCached()) { 33 | $this->app->booted(function () { 34 | require $this->app->getCachedRoutesPath(); 35 | }); 36 | } else { 37 | $this->app->call([$this, 'routing']); 38 | 39 | $this->app->booted(function () { 40 | $this->app['router']->getRoutes()->refreshNameLookups(); 41 | $this->app['router']->getRoutes()->refreshActionLookups(); 42 | }); 43 | } 44 | }); 45 | } 46 | 47 | public function routing(): void 48 | { 49 | Route::middleware(['api']) 50 | ->prefix('api/v1') 51 | ->group($this->app->path('Routes/ApiV1.php')); 52 | 53 | Route::get('/health', function () { 54 | Event::dispatch(new DiagnosingHealth); 55 | 56 | return response(status: 200); 57 | }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /license/src/Resources/LicenseResource.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | public function toArray(Request $request): array 20 | { 21 | return [ 22 | 'type' => $this->resource->getAttribute('license_type'), 23 | 'started_at' => $this->resource->getAttribute('started_at'), 24 | 'expired_at' => $this->resource->getAttribute('expired_at'), 25 | 'max_file_size' => $this->resource->getAttribute('max_file_size'), 26 | 'daily_object_limit' => $this->resource->getAttribute('daily_object_limit'), 27 | 'quota' => $this->resource->getAttribute('quota'), 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /license/src/Routes/ApiV1.php: -------------------------------------------------------------------------------- 1 | make(Router::class); 12 | } catch (BindingResolutionException $e) { 13 | abort(500, $e->getMessage()); 14 | } 15 | 16 | $route->get('me', MeLicense::class)->middleware(EnsureTokenIsValid::class); 17 | // TODO: check `admin.license` scope to internal RestAPI 18 | $route->get('users/{user}', GetLicenseOfUser::class); 19 | -------------------------------------------------------------------------------- /license/storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /license/storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /license/storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | compiled.php 2 | config.php 3 | down 4 | events.scanned.php 5 | maintenance.php 6 | routes.php 7 | routes.scanned.php 8 | schedule-* 9 | services.json 10 | -------------------------------------------------------------------------------- /license/storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /license/storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /license/storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /license/storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /license/storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /license/storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /notification/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /notification/.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_TIMEZONE=UTC 6 | APP_URL=http://localhost 7 | 8 | APP_LOCALE=en 9 | APP_FALLBACK_LOCALE=en 10 | APP_FAKER_LOCALE=en_US 11 | 12 | APP_MAINTENANCE_DRIVER=file 13 | APP_MAINTENANCE_STORE=database 14 | 15 | BCRYPT_ROUNDS=12 16 | 17 | LOG_CHANNEL=stack 18 | LOG_STACK=single 19 | LOG_DEPRECATIONS_CHANNEL=null 20 | LOG_LEVEL=debug 21 | 22 | DB_CONNECTION=pgsql 23 | DB_HOST=127.0.0.1 24 | DB_PORT=5432 25 | DB_DATABASE=notification 26 | DB_USERNAME=root 27 | DB_PASSWORD= 28 | 29 | SESSION_DRIVER=database 30 | SESSION_LIFETIME=120 31 | SESSION_ENCRYPT=false 32 | SESSION_PATH=/ 33 | SESSION_DOMAIN=null 34 | 35 | KAFKA_BROKERS= 36 | KAFKA_CONSUMER_GROUP_ID=notification 37 | BROADCAST_CONNECTION=log 38 | FILESYSTEM_DISK=local 39 | QUEUE_CONNECTION=database 40 | 41 | CACHE_STORE=database 42 | CACHE_PREFIX= 43 | 44 | MEMCACHED_HOST=127.0.0.1 45 | 46 | REDIS_CLIENT=phpredis 47 | REDIS_HOST=127.0.0.1 48 | REDIS_PASSWORD=null 49 | REDIS_PORT=6379 50 | 51 | MAIL_MAILER=log 52 | MAIL_HOST=127.0.0.1 53 | MAIL_PORT=2525 54 | MAIL_USERNAME=null 55 | MAIL_PASSWORD=null 56 | MAIL_ENCRYPTION=null 57 | MAIL_FROM_ADDRESS="hello@example.com" 58 | MAIL_FROM_NAME="${APP_NAME}" 59 | MAIL_NO_REPLY_ADDRESS="no-reply@example.com" 60 | MAIL_NO_REPLY_NAME="${APP_NAME}" 61 | 62 | AWS_ACCESS_KEY_ID= 63 | AWS_SECRET_ACCESS_KEY= 64 | AWS_DEFAULT_REGION=us-east-1 65 | AWS_BUCKET= 66 | AWS_USE_PATH_STYLE_ENDPOINT=false 67 | 68 | VITE_APP_NAME="${APP_NAME}" 69 | -------------------------------------------------------------------------------- /notification/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.md diff=markdown 4 | *.php diff=php 5 | -------------------------------------------------------------------------------- /notification/.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /node_modules 3 | /public/build 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /vendor 8 | .env 9 | .env.backup 10 | .env.production 11 | .phpactor.json 12 | .phpunit.result.cache 13 | Homestead.json 14 | Homestead.yaml 15 | auth.json 16 | npm-debug.log 17 | yarn-error.log 18 | /.fleet 19 | /.idea 20 | /.vscode 21 | -------------------------------------------------------------------------------- /notification/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | ARG WWWGROUP=1000 4 | ARG NODE_VERSION=20 5 | ARG POSTGRES_VERSION=15 6 | 7 | WORKDIR /var/www/html 8 | 9 | ENV DEBIAN_FRONTEND noninteractive 10 | ENV TZ=UTC 11 | ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php /var/www/html/artisan app:consume-messages" 12 | ENV SUPERVISOR_WORKER_COMMAND="/usr/bin/php /var/www/html/artisan queue:listen" 13 | ENV SUPERVISOR_PHP_USER="app" 14 | 15 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 16 | 17 | RUN apt-get update \ 18 | && mkdir -p /etc/apt/keyrings \ 19 | && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python2 dnsutils librsvg2-bin fswatch ffmpeg nano \ 20 | && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x14aa40ec0831756756d7f66c4f4ea0aae5267a6c' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \ 21 | && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu jammy main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ 22 | && apt-get update \ 23 | && apt-get install -y librdkafka-dev php8.2-cli php8.2-dev \ 24 | php8.2-pgsql php8.2-sqlite3 php8.2-gd php8.2-imagick \ 25 | php8.2-curl \ 26 | php8.2-imap php8.2-mysql php8.2-mbstring \ 27 | php8.2-xml php8.2-zip php8.2-bcmath php8.2-soap \ 28 | php8.2-intl php8.2-readline \ 29 | php8.2-ldap \ 30 | php8.2-msgpack php8.2-igbinary php8.2-redis php8.2-swoole \ 31 | php8.2-memcached php8.2-pcov php8.2-xdebug \ 32 | && pecl install rdkafka \ 33 | && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \ 34 | && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ 35 | && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \ 36 | && apt-get update \ 37 | && apt-get install -y nodejs \ 38 | && npm install -g npm \ 39 | && npm install -g pnpm \ 40 | && npm install -g bun \ 41 | && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \ 42 | && echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ 43 | && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \ 44 | && echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ 45 | && apt-get update \ 46 | && apt-get install -y yarn \ 47 | && apt-get install -y mysql-client \ 48 | && apt-get install -y postgresql-client-$POSTGRES_VERSION \ 49 | && apt-get -y autoremove \ 50 | && apt-get clean \ 51 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 52 | 53 | RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.2 54 | 55 | RUN groupadd --force -g $WWWGROUP app 56 | RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 app 57 | 58 | COPY docker/start-container /usr/local/bin/start-container 59 | COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf 60 | COPY docker/php.ini /etc/php/8.2/cli/conf.d/99-app.ini 61 | RUN chmod +x /usr/local/bin/start-container 62 | 63 | EXPOSE 8000 64 | 65 | ENTRYPOINT ["start-container"] 66 | -------------------------------------------------------------------------------- /notification/artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | handleCommand(new ArgvInput); 14 | 15 | exit($status); 16 | -------------------------------------------------------------------------------- /notification/bootstrap/app.php: -------------------------------------------------------------------------------- 1 | withCommands([ConsumeMessages::class]) 11 | ->withMiddleware(function (Middleware $middleware) { 12 | // 13 | }) 14 | ->withExceptions(function (Exceptions $exceptions) { 15 | // 16 | })->create(); 17 | 18 | $app->useAppPath(join_paths(dirname(__DIR__), 'src')); 19 | 20 | return $app; 21 | -------------------------------------------------------------------------------- /notification/bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /notification/bootstrap/providers.php: -------------------------------------------------------------------------------- 1 | env('KAFKA_BROKERS', 'localhost:9092'), 8 | 9 | /* 10 | | Default security protocol 11 | */ 12 | 'securityProtocol' => env('KAFKA_SECURITY_PROTOCOL', 'PLAINTEXT'), 13 | 14 | /* 15 | | Default sasl configuration 16 | */ 17 | 'sasl' => [ 18 | 'mechanisms' => env('KAFKA_MECHANISMS', 'PLAINTEXT'), 19 | 'username' => env('KAFKA_USERNAME', null), 20 | 'password' => env('KAFKA_PASSWORD', null) 21 | ], 22 | 23 | /* 24 | | Kafka consumers belonging to the same consumer group share a group id. 25 | | The consumers in a group then divides the topic partitions as fairly amongst themselves as possible by 26 | | establishing that each partition is only consumed by a single consumer from the group. 27 | | This config defines the consumer group id you want to use for your project. 28 | */ 29 | 'consumer_group_id' => env('KAFKA_CONSUMER_GROUP_ID', 'group'), 30 | 31 | 'consumer_timeout_ms' => env("KAFKA_CONSUMER_DEFAULT_TIMEOUT", 2000), 32 | 33 | /* 34 | | After the consumer receives its assignment from the coordinator, 35 | | it must determine the initial position for each assigned partition. 36 | | When the group is first created, before any messages have been consumed, the position is set according to a configurable 37 | | offset reset policy (auto.offset.reset). Typically, consumption starts either at the earliest offset or the latest offset. 38 | | You can choose between "latest", "earliest" or "none". 39 | */ 40 | 'offset_reset' => env('KAFKA_OFFSET_RESET', 'latest'), 41 | 42 | /* 43 | | If you set enable.auto.commit (which is the default), then the consumer will automatically commit offsets periodically at the 44 | | interval set by auto.commit.interval.ms. 45 | */ 46 | 'auto_commit' => env('KAFKA_AUTO_COMMIT', true), 47 | 48 | 'sleep_on_error' => env('KAFKA_ERROR_SLEEP', 5), 49 | 50 | 'partition' => env('KAFKA_PARTITION', 0), 51 | 52 | /* 53 | | Kafka supports 4 compression codecs: none , gzip , lz4 and snappy 54 | */ 55 | 'compression' => env('KAFKA_COMPRESSION_TYPE', 'snappy'), 56 | 57 | /* 58 | | Choose if debug is enabled or not. 59 | */ 60 | 'debug' => env('KAFKA_DEBUG', false), 61 | 62 | /* 63 | | Repository for batching messages together 64 | | Implement BatchRepositoryInterface to save batches in different storage 65 | */ 66 | 'batch_repository' => env('KAFKA_BATCH_REPOSITORY', \Junges\Kafka\BatchRepositories\InMemoryBatchRepository::class), 67 | 68 | /* 69 | | The sleep time in milliseconds that will be used when retrying flush 70 | */ 71 | 'flush_retry_sleep_in_ms' => 100, 72 | 73 | /* 74 | | The cache driver that will be used 75 | */ 76 | 'cache_driver' => env('KAFKA_CACHE_DRIVER', env('CACHE_DRIVER', 'file')), 77 | ]; 78 | -------------------------------------------------------------------------------- /notification/config/mail.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'address' => env('MAIL_NO_REPLY_ADDRESS', 'no-reply@example.com'), 6 | 'name' => env('MAIL_NO_REPLY_NAME', 'No Reply'), 7 | ], 8 | ]; 9 | -------------------------------------------------------------------------------- /notification/database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /notification/docker/php.ini: -------------------------------------------------------------------------------- 1 | extension=rdkafka.so 2 | -------------------------------------------------------------------------------- /notification/docker/start-container: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "app" ]; then 4 | echo "You should set SUPERVISOR_PHP_USER to either 'app' or 'root'." 5 | exit 1 6 | fi 7 | 8 | if [ ! -z "$WWWUSER" ]; then 9 | usermod -u $WWWUSER app 10 | fi 11 | 12 | if [ ! -d /.composer ]; then 13 | mkdir /.composer 14 | fi 15 | 16 | chmod -R ugo+rw /.composer 17 | 18 | if [ $# -gt 0 ]; then 19 | if [ "$SUPERVISOR_PHP_USER" = "root" ]; then 20 | exec "$@" 21 | else 22 | exec gosu $WWWUSER "$@" 23 | fi 24 | else 25 | exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf 26 | fi 27 | -------------------------------------------------------------------------------- /notification/docker/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | user=root 4 | logfile=/var/log/supervisor/supervisord.log 5 | pidfile=/var/run/supervisord.pid 6 | 7 | [program:php] 8 | command=%(ENV_SUPERVISOR_PHP_COMMAND)s 9 | user=%(ENV_SUPERVISOR_PHP_USER)s 10 | environment=APP_IN_DOCKER="1" 11 | stdout_logfile=/dev/stdout 12 | stdout_logfile_maxbytes=0 13 | stderr_logfile=/dev/stderr 14 | stderr_logfile_maxbytes=0 15 | 16 | [program:worker] 17 | command=%(ENV_SUPERVISOR_WORKER_COMMAND)s 18 | user=%(ENV_SUPERVISOR_PHP_USER)s 19 | environment=APP_IN_DOCKER="1" 20 | stdout_logfile=/dev/stdout 21 | stdout_logfile_maxbytes=0 22 | stderr_logfile=/dev/stderr 23 | stderr_logfile_maxbytes=0 24 | -------------------------------------------------------------------------------- /notification/src/Commands/ConsumeMessages.php: -------------------------------------------------------------------------------- 1 | info("Listening kafka messages..."); 36 | 37 | // TODO: add parallelism or use minimal worker 38 | Kafka::consumer() 39 | ->subscribe('user.created') 40 | ->withHandler(new KafkaMessageHandler) 41 | ->build() 42 | ->consume(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /notification/src/Handlers/KafkaMessageHandler.php: -------------------------------------------------------------------------------- 1 | info("New Message!"); 14 | $email = $message->getBody()['email']; 15 | 16 | SendWelcomeEmail::dispatch($email); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /notification/src/Jobs/SendWelcomeEmail.php: -------------------------------------------------------------------------------- 1 | email)->send(new WelcomeEmail()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /notification/src/Mail/WelcomeEmail.php: -------------------------------------------------------------------------------- 1 | 45 | */ 46 | public function attachments(): array 47 | { 48 | return []; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /notification/src/Migrations/2024_06_07_012217_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 16 | $table->string('queue')->index(); 17 | $table->longText('payload'); 18 | $table->unsignedTinyInteger('attempts'); 19 | $table->unsignedInteger('reserved_at')->nullable(); 20 | $table->unsignedInteger('available_at'); 21 | $table->unsignedInteger('created_at'); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('jobs'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /notification/src/Migrations/2024_06_07_013016_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('uuid')->unique(); 17 | $table->text('connection'); 18 | $table->text('queue'); 19 | $table->longText('payload'); 20 | $table->longText('exception'); 21 | $table->timestamp('failed_at')->useCurrent(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('failed_jobs'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /notification/src/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | loadViewsFrom($this->app->basePath('views'), "notification"); 12 | $this->loadMigrationsFrom($this->app->path('Migrations')); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /notification/storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /notification/storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /notification/storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | compiled.php 2 | config.php 3 | down 4 | events.scanned.php 5 | maintenance.php 6 | routes.php 7 | routes.scanned.php 8 | schedule-* 9 | services.json 10 | -------------------------------------------------------------------------------- /notification/storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /notification/storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /notification/storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /notification/storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /notification/storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /notification/storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /notification/views/emails/welcome-plaintext.blade.php: -------------------------------------------------------------------------------- 1 | Welcome to our application! 2 | -------------------------------------------------------------------------------- /notification/views/emails/welcome.blade.php: -------------------------------------------------------------------------------- 1 |

Welcome to our application!

2 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Microservice Architecture for Laravel 2 | 3 | ## Table of Contents 4 | 5 | - [Overview](#overview) 6 | - [Technologies](#technologies) 7 | - [Getting Started](#getting-started) 8 | - [Installation](#installation) 9 | - [Configuration](#configuration) 10 | - [Generate RSA Keys](#generate-rsa-keys) 11 | - [Running the Services](#running-the-services) 12 | - [Microservices](#microservices) 13 | - [Security](#security-service) 14 | - [License](#license-service) 15 | - [File Management](#file-management-service) 16 | - [Need to Implement](#need-to-implement) 17 | - [Contributing](#contributing) 18 | - [License](#license) 19 | 20 | ## Overview 21 | 22 | This project is a microservice-based architecture designed to provide scalable and maintainable solutions. 23 | It includes various technologies for managing service discovery, messaging, and data integration. 24 | 25 | ## Technologies 26 | 27 | - **Zookeeper**: Used for service discovery and coordination. 28 | - **ZooNavigator**: A web-based UI for managing Zookeeper instances. 29 | - **Kafka**: A distributed streaming platform used for building real-time data pipelines and streaming applications. 30 | - **Schema Registry**: Manages and enforces schemas for Kafka topics. 31 | - **Kafka Connect**: A tool for scalable and reliably streaming data between Apache Kafka and other systems. 32 | - **Kafka UI**: A web-based UI for managing and monitoring Kafka clusters. 33 | - **PostgreSQL**: A powerful, open source object-relational database system. 34 | 35 | ## Getting Started 36 | 37 | Ensure you have the following software installed: 38 | - Docker 39 | - Docker Compose 40 | 41 | To get a local copy of this project up and running, follow these simple steps. 42 | 43 | ## Installation 44 | 45 | 1. Clone the repository: 46 | ```shell 47 | git clone https://github.com/erayaydin/microservice-laravel.git 48 | cd microservice-laravel 49 | ``` 50 | 2. Copy the sample `.env.example` to `.env`: 51 | ```shell 52 | cp .env.example .env 53 | ``` 54 | 3. Edit necessary parts in `.env` file as needed. 55 | 56 | ## Configuration 57 | 58 | The configuration settings are stored in the `.env` file. Customize the necessary parts as needed. 59 | 60 | - **ZOOKEEPER_IMAGE**: Docker image name to use for zookeeper instances. Default: `confluentinc/cp-zookeeper` 61 | - **ZOOKEEPER_IMAGE_VERSION**: Docker image version for _ZOOKEEPER_IMAGE_ image. Default: `7.6.1` 62 | - **ZOONAVIGATOR_VERSION**: ZooNavigator UI image version to install with services. Default: `1.1.2` 63 | - **KAFKA_IMAGE**: Docker image name to use for kafka instances. Default: `confluentinc/cp-kafka` 64 | - **KAFKA_IMAGE_VERSION**: Docker image version for _KAFKA_IMAGE_ image. Default: `7.6.1` 65 | - **SCHEMA_REGISTRY_IMAGE**: Docker image name to use for SchemaRegistry instances. Default: `confluentinc/cp-schema-registry` 66 | - **SCHEMA_REGISTRY_IMAGE_VERSION**: Docker image version for _SCHEMA_REGISTRY_IMAGE_ image. Default: `7.6.1` 67 | - **KAFKA_CONNECT_IMAGE**: Docker image name to use for kafka connect instances. Default: `confluentinc/cp-kafka-connect` 68 | - **KAFKA_CONNECT_IMAGE_VERSION**: Docker image version for _KAFKA_CONNECT_IMAGE_ image. Default: `7.6.1` 69 | - **WWWUSER**: User id for microservice instances. Default: `1000` 70 | - **WWWGROUP**: Group id for microservice instances. Default: `1000` 71 | - **APP_[SERVICE]_DB_IMAGE_VERSION**: PostgreSQL instance version for the `[SERVICE]` microservice. Default: `15` 72 | - **APP_[SERVICE]_PORT**: External port of the `[SERVICE]` microservice. It will be increased 1 by 1 starting from 73 | `8082`. 74 | - **APP_[SERVICE]_XDEBUG_MODE**: Enable xdebug extension for the `[SERVICE]` microservice. Default: `off` 75 | - **APP_[SERVICE]_XDEBUG_CONFIG**: If xdebug enabled, xdebug integration config. Edit for development environment needs. 76 | Default: `-client_host=host.docker.internal` 77 | - **APP_[SERVICE]_DB_PORT**: External port of the `[SERVICE]` microservice's database. It will be increased 1 by 1 78 | starting from `5432`. 79 | 80 | ## Generate RSA Keys 81 | 82 | You need to generate a pair of 4096-bit RSA private and public keys for inter-service authentication 83 | and authorization. These keys will be shared between services. 84 | 85 | ```shell 86 | openssl genrsa -out secrets/oauth-private.key 4096 87 | openssl rsa -in secrets/oauth-private.key -pubout -out secrets/oauth-public.key 88 | ``` 89 | 90 | ## Running the Services 91 | 92 | To start all the services, run: 93 | 94 | ```shell 95 | docker compose up -d 96 | ``` 97 | 98 | This command will start all the containers defined in the docker-compose.yml file. 99 | 100 | Remember to run migrations and necessary adjustments before testing services. Like: 101 | 102 | ```shell 103 | docker compose exec -u app security php artisan migrate 104 | ``` 105 | 106 | ## Microservices 107 | 108 | ### Security Service 109 | 110 | The Security Service is responsible for handling user authentication and authorization. 111 | Authentication and authorization will be handled with OAuth2. 112 | 113 | #### Endpoints 114 | 115 | - `GET /health`: Health check endpoint. It'll respond with **200** status code. 116 | - `POST /users`: Create new user. It'll respond with **201** status code if success. 117 | - `GET /oauth/authorize`: Show authorization to the end user. 118 | - `POST /oauth/authorize`: Approve authorization. 119 | - `DELETE /oauth/authorize`: Deny authorization. 120 | - `GET /oauth/clients`: Get oauth clients for the user. 121 | - `POST /oauth/clients`: Create new oauth client. 122 | - `PUT /oauth/clients/{client_id}`: Update an oauth client. 123 | - `DELETE /oauth/clients/{client_id}`: Delete an oauth client. 124 | - `GET /oauth/personal-access-tokens`: Get personal access token oauth clients for the user. 125 | - `POST /oauth/personal-access-tokens`: Create new personal access token oauth client. 126 | - `DELETE /oauth/personal-access-tokens/{token_id}`: Delete a personal access token oauth client. 127 | - `GET /oauth/scopes`: Get all registered scopes. 128 | - `POST /oauth/token`: Issue new token with specified strategy. 129 | - `POST /oauth/token/refresh`: Refresh access token with refresh token. 130 | - `GET /oauth/tokens`: Get authorized access token for the user. 131 | - `DELETE /oauth/tokens/{token_id}`: Delete an access token. 132 | 133 | #### Data Store 134 | 135 | - **PostgreSQL**: The Security Service uses PostgreSQL to store user credentials and authorization data. 136 | 137 | ### License Service 138 | 139 | The Security Service is responsible for handling user licenses. 140 | Auth verification will be handled with JWT key decoding. 141 | 142 | #### Endpoints 143 | 144 | - `GET /health`: Health check endpoint. It'll respond with 200 status code. 145 | - `GET /me`: Get current user license information. 146 | - `GET /users/{user}`: Get user's license information. (need `admin.licenses` scope). 147 | 148 | #### Data Store 149 | 150 | - **PostgreSQL**: The License Service uses PostgreSQL to store license information. 151 | 152 | ### File Management Service 153 | 154 | The File Management Service is responsible for handling file operations. 155 | End user can upload and download files. 156 | 157 | #### Endpoints 158 | 159 | - `GET /health`: Health check endpoint. It'll respond with 200 status code. 160 | - `GET /files`: List of current user's uploaded files. 161 | - `POST /files`: Upload new file to user's bucket. 162 | - `GET /files/{file}/download`: Downloads the given file in attachment mode. 163 | 164 | #### Data Store 165 | 166 | - **PostgreSQL**: The File Management Service uses PostgreSQL to store file metadata. 167 | 168 | ## Need to Implement 169 | 170 | - Use [`Kong`](https://github.com/Kong/kong) api-gateway to single entrypoint. 171 | - Implement permission and scope system to license `/users/{user}` endpoint. 172 | - Use `docker secret` to share oauth private and public keys. 173 | - Implement `ObjectStorage` service to manage buckets. 174 | - Use RDKafka data processor instead of the current one. 175 | - Define schemas for `user.created` and `license.updated` kafka messages. 176 | - Remove config files for `services` and `kafka`. Use provider to bind instances with values. 177 | - Use kafka-connect1 for connect services to kafka cluster. 178 | - Fix style issues and apply on CI. 179 | - Add ELK stack and/or Grafana metrics. 180 | - Add unit, integration and e2e tests. 181 | - Add k8s yaml files. 182 | - Add OpenAPI documentation for the api-gateway and services. 183 | - Use kong+security to validate and decode OAuth2 184 | 185 | ## Contributing 186 | 187 | Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any 188 | contributions you make are **greatly appreciated.** 189 | 190 | 1. Fork the Project 191 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 192 | 3. Commit your changes (`git commit`) 193 | 4. Push to the branch (`git push origin feature/amazing-feature`) 194 | 5. Open a pull request with detailed description. 195 | 196 | ## License 197 | 198 | Distributed under the **MIT License**. 199 | -------------------------------------------------------------------------------- /secrets/.gitignore: -------------------------------------------------------------------------------- 1 | *.key -------------------------------------------------------------------------------- /security/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /security/.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_TIMEZONE=UTC 6 | APP_URL=http://localhost 7 | 8 | APP_LOCALE=en 9 | APP_FALLBACK_LOCALE=en 10 | APP_FAKER_LOCALE=en_US 11 | 12 | APP_MAINTENANCE_DRIVER=file 13 | APP_MAINTENANCE_STORE=database 14 | 15 | BCRYPT_ROUNDS=12 16 | 17 | LOG_CHANNEL=stack 18 | LOG_STACK=single 19 | LOG_DEPRECATIONS_CHANNEL=null 20 | LOG_LEVEL=debug 21 | 22 | DB_CONNECTION=pgsql 23 | DB_HOST=127.0.0.1 24 | DB_PORT=5432 25 | DB_DATABASE=security 26 | DB_USERNAME=root 27 | DB_PASSWORD= 28 | 29 | SESSION_DRIVER=database 30 | SESSION_LIFETIME=120 31 | SESSION_ENCRYPT=false 32 | SESSION_PATH=/ 33 | SESSION_DOMAIN=null 34 | 35 | KAFKA_BROKERS= 36 | BROADCAST_CONNECTION=log 37 | FILESYSTEM_DISK=local 38 | QUEUE_CONNECTION=database 39 | 40 | CACHE_STORE=database 41 | CACHE_PREFIX= 42 | 43 | MEMCACHED_HOST=127.0.0.1 44 | 45 | REDIS_CLIENT=phpredis 46 | REDIS_HOST=127.0.0.1 47 | REDIS_PASSWORD=null 48 | REDIS_PORT=6379 49 | 50 | MAIL_MAILER=log 51 | MAIL_HOST=127.0.0.1 52 | MAIL_PORT=2525 53 | MAIL_USERNAME=null 54 | MAIL_PASSWORD=null 55 | MAIL_ENCRYPTION=null 56 | MAIL_FROM_ADDRESS="hello@example.com" 57 | MAIL_FROM_NAME="${APP_NAME}" 58 | 59 | AWS_ACCESS_KEY_ID= 60 | AWS_SECRET_ACCESS_KEY= 61 | AWS_DEFAULT_REGION=us-east-1 62 | AWS_BUCKET= 63 | AWS_USE_PATH_STYLE_ENDPOINT=false 64 | 65 | VITE_APP_NAME="${APP_NAME}" 66 | -------------------------------------------------------------------------------- /security/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.md diff=markdown 4 | *.php diff=php 5 | -------------------------------------------------------------------------------- /security/.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /node_modules 3 | /public/build 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /vendor 8 | .env 9 | .env.backup 10 | .env.production 11 | .phpactor.json 12 | .phpunit.result.cache 13 | Homestead.json 14 | Homestead.yaml 15 | auth.json 16 | npm-debug.log 17 | yarn-error.log 18 | /.fleet 19 | /.idea 20 | /.vscode 21 | -------------------------------------------------------------------------------- /security/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | ARG WWWGROUP=1000 4 | ARG NODE_VERSION=20 5 | ARG POSTGRES_VERSION=15 6 | 7 | WORKDIR /var/www/html 8 | 9 | ENV DEBIAN_FRONTEND noninteractive 10 | ENV TZ=UTC 11 | ENV SUPERVISOR_PHP_COMMAND="/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80" 12 | ENV SUPERVISOR_WORKER_COMMAND="/usr/bin/php /var/www/html/artisan queue:listen" 13 | ENV SUPERVISOR_PHP_USER="app" 14 | 15 | RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone 16 | 17 | RUN apt-get update \ 18 | && mkdir -p /etc/apt/keyrings \ 19 | && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python2 dnsutils librsvg2-bin fswatch ffmpeg nano \ 20 | && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x14aa40ec0831756756d7f66c4f4ea0aae5267a6c' | gpg --dearmor | tee /etc/apt/keyrings/ppa_ondrej_php.gpg > /dev/null \ 21 | && echo "deb [signed-by=/etc/apt/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu jammy main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ 22 | && apt-get update \ 23 | && apt-get install -y librdkafka-dev php8.2-cli php8.2-dev \ 24 | php8.2-pgsql php8.2-sqlite3 php8.2-gd php8.2-imagick \ 25 | php8.2-curl \ 26 | php8.2-imap php8.2-mysql php8.2-mbstring \ 27 | php8.2-xml php8.2-zip php8.2-bcmath php8.2-soap \ 28 | php8.2-intl php8.2-readline \ 29 | php8.2-ldap \ 30 | php8.2-msgpack php8.2-igbinary php8.2-redis php8.2-swoole \ 31 | php8.2-memcached php8.2-pcov php8.2-xdebug \ 32 | && pecl install rdkafka \ 33 | && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \ 34 | && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ 35 | && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \ 36 | && apt-get update \ 37 | && apt-get install -y nodejs \ 38 | && npm install -g npm \ 39 | && npm install -g pnpm \ 40 | && npm install -g bun \ 41 | && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /etc/apt/keyrings/yarn.gpg >/dev/null \ 42 | && echo "deb [signed-by=/etc/apt/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ 43 | && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/keyrings/pgdg.gpg >/dev/null \ 44 | && echo "deb [signed-by=/etc/apt/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ 45 | && apt-get update \ 46 | && apt-get install -y yarn \ 47 | && apt-get install -y mysql-client \ 48 | && apt-get install -y postgresql-client-$POSTGRES_VERSION \ 49 | && apt-get -y autoremove \ 50 | && apt-get clean \ 51 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 52 | 53 | RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.2 54 | 55 | RUN groupadd --force -g $WWWGROUP app 56 | RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 app 57 | 58 | COPY docker/start-container /usr/local/bin/start-container 59 | COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf 60 | COPY docker/php.ini /etc/php/8.2/cli/conf.d/99-app.ini 61 | RUN chmod +x /usr/local/bin/start-container 62 | 63 | EXPOSE 8000 64 | 65 | ENTRYPOINT ["start-container"] 66 | -------------------------------------------------------------------------------- /security/artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | handleCommand(new ArgvInput); 14 | 15 | exit($status); 16 | -------------------------------------------------------------------------------- /security/bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 20 | HttpKernelContract::class, 21 | HttpKernel::class, 22 | ); 23 | $app->singleton( 24 | ConsoleKernelContract::class, 25 | ConsoleKernel::class, 26 | ); 27 | 28 | // Register essential providers 29 | $app->register(EventServiceProvider::class); 30 | 31 | // Register default bootstrap providers 32 | RegisterProviders::merge([], $app->getBootstrapProvidersPath()); 33 | */ 34 | 35 | $app = Application::configure(basePath: dirname(__DIR__)) 36 | ->withRouting( 37 | health: '/up', 38 | ) 39 | ->withMiddleware(function (Middleware $middleware) { 40 | // 41 | }) 42 | ->withExceptions(function (Exceptions $exceptions) { 43 | // 44 | })->create(); 45 | 46 | $app->useAppPath(join_paths(dirname(__DIR__), 'src')); 47 | 48 | return $app; 49 | -------------------------------------------------------------------------------- /security/bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /security/bootstrap/providers.php: -------------------------------------------------------------------------------- 1 | env('KAFKA_BROKERS', 'localhost:9092'), 8 | 9 | /* 10 | | Default security protocol 11 | */ 12 | 'securityProtocol' => env('KAFKA_SECURITY_PROTOCOL', 'PLAINTEXT'), 13 | 14 | /* 15 | | Default sasl configuration 16 | */ 17 | 'sasl' => [ 18 | 'mechanisms' => env('KAFKA_MECHANISMS', 'PLAINTEXT'), 19 | 'username' => env('KAFKA_USERNAME', null), 20 | 'password' => env('KAFKA_PASSWORD', null) 21 | ], 22 | 23 | /* 24 | | Kafka consumers belonging to the same consumer group share a group id. 25 | | The consumers in a group then divides the topic partitions as fairly amongst themselves as possible by 26 | | establishing that each partition is only consumed by a single consumer from the group. 27 | | This config defines the consumer group id you want to use for your project. 28 | */ 29 | 'consumer_group_id' => env('KAFKA_CONSUMER_GROUP_ID', 'group'), 30 | 31 | 'consumer_timeout_ms' => env("KAFKA_CONSUMER_DEFAULT_TIMEOUT", 2000), 32 | 33 | /* 34 | | After the consumer receives its assignment from the coordinator, 35 | | it must determine the initial position for each assigned partition. 36 | | When the group is first created, before any messages have been consumed, the position is set according to a configurable 37 | | offset reset policy (auto.offset.reset). Typically, consumption starts either at the earliest offset or the latest offset. 38 | | You can choose between "latest", "earliest" or "none". 39 | */ 40 | 'offset_reset' => env('KAFKA_OFFSET_RESET', 'latest'), 41 | 42 | /* 43 | | If you set enable.auto.commit (which is the default), then the consumer will automatically commit offsets periodically at the 44 | | interval set by auto.commit.interval.ms. 45 | */ 46 | 'auto_commit' => env('KAFKA_AUTO_COMMIT', true), 47 | 48 | 'sleep_on_error' => env('KAFKA_ERROR_SLEEP', 5), 49 | 50 | 'partition' => env('KAFKA_PARTITION', 0), 51 | 52 | /* 53 | | Kafka supports 4 compression codecs: none , gzip , lz4 and snappy 54 | */ 55 | 'compression' => env('KAFKA_COMPRESSION_TYPE', 'snappy'), 56 | 57 | /* 58 | | Choose if debug is enabled or not. 59 | */ 60 | 'debug' => env('KAFKA_DEBUG', false), 61 | 62 | /* 63 | | Repository for batching messages together 64 | | Implement BatchRepositoryInterface to save batches in different storage 65 | */ 66 | 'batch_repository' => env('KAFKA_BATCH_REPOSITORY', \Junges\Kafka\BatchRepositories\InMemoryBatchRepository::class), 67 | 68 | /* 69 | | The sleep time in milliseconds that will be used when retrying flush 70 | */ 71 | 'flush_retry_sleep_in_ms' => 100, 72 | 73 | /* 74 | | The cache driver that will be used 75 | */ 76 | 'cache_driver' => env('KAFKA_CACHE_DRIVER', env('CACHE_DRIVER', 'file')), 77 | ]; 78 | -------------------------------------------------------------------------------- /security/database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /security/database/migrations/0001_01_01_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->string('email')->unique(); 18 | $table->timestamp('email_verified_at')->nullable(); 19 | $table->string('password'); 20 | $table->rememberToken(); 21 | $table->timestamps(); 22 | }); 23 | 24 | Schema::create('password_reset_tokens', function (Blueprint $table) { 25 | $table->string('email')->primary(); 26 | $table->string('token'); 27 | $table->timestamp('created_at')->nullable(); 28 | }); 29 | 30 | Schema::create('sessions', function (Blueprint $table) { 31 | $table->string('id')->primary(); 32 | $table->foreignId('user_id')->nullable()->index(); 33 | $table->string('ip_address', 45)->nullable(); 34 | $table->text('user_agent')->nullable(); 35 | $table->longText('payload'); 36 | $table->integer('last_activity')->index(); 37 | }); 38 | } 39 | 40 | /** 41 | * Reverse the migrations. 42 | */ 43 | public function down(): void 44 | { 45 | Schema::dropIfExists('users'); 46 | Schema::dropIfExists('password_reset_tokens'); 47 | Schema::dropIfExists('sessions'); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /security/database/migrations/0001_01_01_000001_create_cache_table.php: -------------------------------------------------------------------------------- 1 | string('key')->primary(); 16 | $table->mediumText('value'); 17 | $table->integer('expiration'); 18 | }); 19 | 20 | Schema::create('cache_locks', function (Blueprint $table) { 21 | $table->string('key')->primary(); 22 | $table->string('owner'); 23 | $table->integer('expiration'); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('cache'); 33 | Schema::dropIfExists('cache_locks'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /security/database/migrations/0001_01_01_000002_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('queue')->index(); 17 | $table->longText('payload'); 18 | $table->unsignedTinyInteger('attempts'); 19 | $table->unsignedInteger('reserved_at')->nullable(); 20 | $table->unsignedInteger('available_at'); 21 | $table->unsignedInteger('created_at'); 22 | }); 23 | 24 | Schema::create('job_batches', function (Blueprint $table) { 25 | $table->string('id')->primary(); 26 | $table->string('name'); 27 | $table->integer('total_jobs'); 28 | $table->integer('pending_jobs'); 29 | $table->integer('failed_jobs'); 30 | $table->longText('failed_job_ids'); 31 | $table->mediumText('options')->nullable(); 32 | $table->integer('cancelled_at')->nullable(); 33 | $table->integer('created_at'); 34 | $table->integer('finished_at')->nullable(); 35 | }); 36 | 37 | Schema::create('failed_jobs', function (Blueprint $table) { 38 | $table->id(); 39 | $table->string('uuid')->unique(); 40 | $table->text('connection'); 41 | $table->text('queue'); 42 | $table->longText('payload'); 43 | $table->longText('exception'); 44 | $table->timestamp('failed_at')->useCurrent(); 45 | }); 46 | } 47 | 48 | /** 49 | * Reverse the migrations. 50 | */ 51 | public function down(): void 52 | { 53 | Schema::dropIfExists('jobs'); 54 | Schema::dropIfExists('job_batches'); 55 | Schema::dropIfExists('failed_jobs'); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /security/database/migrations/2024_06_04_130737_create_oauth_personal_access_clients_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 16 | $table->unsignedBigInteger('client_id'); 17 | $table->timestamps(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('oauth_personal_access_clients'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /security/database/migrations/2024_06_04_130738_create_oauth_refresh_tokens_table.php: -------------------------------------------------------------------------------- 1 | string('id', 100)->primary(); 16 | $table->string('access_token_id', 100)->index(); 17 | $table->boolean('revoked'); 18 | $table->dateTime('expires_at')->nullable(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | */ 25 | public function down(): void 26 | { 27 | Schema::dropIfExists('oauth_refresh_tokens'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /security/database/migrations/2024_06_04_130739_create_oauth_clients_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 16 | $table->unsignedBigInteger('user_id')->nullable()->index(); 17 | $table->string('name'); 18 | $table->string('secret', 100)->nullable(); 19 | $table->string('provider')->nullable(); 20 | $table->text('redirect'); 21 | $table->boolean('personal_access_client'); 22 | $table->boolean('password_client'); 23 | $table->boolean('revoked'); 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | */ 31 | public function down(): void 32 | { 33 | Schema::dropIfExists('oauth_clients'); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /security/database/migrations/2024_06_04_130740_create_oauth_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | string('id', 100)->primary(); 16 | $table->unsignedBigInteger('user_id')->nullable()->index(); 17 | $table->unsignedBigInteger('client_id'); 18 | $table->string('name')->nullable(); 19 | $table->text('scopes')->nullable(); 20 | $table->boolean('revoked'); 21 | $table->timestamps(); 22 | $table->dateTime('expires_at')->nullable(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('oauth_access_tokens'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /security/database/migrations/2024_06_04_130741_create_oauth_auth_codes_table.php: -------------------------------------------------------------------------------- 1 | string('id', 100)->primary(); 16 | $table->unsignedBigInteger('user_id')->index(); 17 | $table->unsignedBigInteger('client_id'); 18 | $table->text('scopes')->nullable(); 19 | $table->boolean('revoked'); 20 | $table->dateTime('expires_at')->nullable(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::dropIfExists('oauth_auth_codes'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /security/docker/php.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | post_max_size = 100M 3 | upload_max_filesize = 100M 4 | variables_order = EGPCS 5 | pcov.directory = . 6 | 7 | extension=rdkafka.so 8 | -------------------------------------------------------------------------------- /security/docker/start-container: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ "$SUPERVISOR_PHP_USER" != "root" ] && [ "$SUPERVISOR_PHP_USER" != "app" ]; then 4 | echo "You should set SUPERVISOR_PHP_USER to either 'app' or 'root'." 5 | exit 1 6 | fi 7 | 8 | if [ ! -z "$WWWUSER" ]; then 9 | usermod -u $WWWUSER app 10 | fi 11 | 12 | if [ ! -d /.composer ]; then 13 | mkdir /.composer 14 | fi 15 | 16 | chmod -R ugo+rw /.composer 17 | 18 | if [ $# -gt 0 ]; then 19 | if [ "$SUPERVISOR_PHP_USER" = "root" ]; then 20 | exec "$@" 21 | else 22 | exec gosu $WWWUSER "$@" 23 | fi 24 | else 25 | exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf 26 | fi 27 | -------------------------------------------------------------------------------- /security/docker/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | nodaemon=true 3 | user=root 4 | logfile=/var/log/supervisor/supervisord.log 5 | pidfile=/var/run/supervisord.pid 6 | 7 | [program:php] 8 | command=%(ENV_SUPERVISOR_PHP_COMMAND)s 9 | user=%(ENV_SUPERVISOR_PHP_USER)s 10 | environment=APP_IN_DOCKER="1" 11 | stdout_logfile=/dev/stdout 12 | stdout_logfile_maxbytes=0 13 | stderr_logfile=/dev/stderr 14 | stderr_logfile_maxbytes=0 15 | 16 | [program:worker] 17 | command=%(ENV_SUPERVISOR_WORKER_COMMAND)s 18 | user=%(ENV_SUPERVISOR_PHP_USER)s 19 | environment=APP_IN_DOCKER="1" 20 | stdout_logfile=/dev/stdout 21 | stdout_logfile_maxbytes=0 22 | stderr_logfile=/dev/stderr 23 | stderr_logfile_maxbytes=0 24 | -------------------------------------------------------------------------------- /security/public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Send Requests To Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /security/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/erayaydin/microservice-laravel/c54fe589bb22ddca910b8da295537b82cdf20096/security/public/favicon.ico -------------------------------------------------------------------------------- /security/public/index.php: -------------------------------------------------------------------------------- 1 | handleRequest(Request::capture()); 18 | -------------------------------------------------------------------------------- /security/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /security/src/Actions/CreateNewUser.php: -------------------------------------------------------------------------------- 1 | userModel->query()->create([ 25 | 'name' => $name, 26 | 'email' => $email, 27 | 'password' => $this->hasher->make($password), 28 | ]); 29 | 30 | PublishUserCreatedMessage::dispatch($user); 31 | } 32 | 33 | public function asController(ActionRequest $request): Response 34 | { 35 | $this->handle( 36 | $request->validated('name'), 37 | $request->validated('email'), 38 | $request->validated('password'), 39 | ); 40 | 41 | return response(status: 201); 42 | } 43 | 44 | public function rules(): array 45 | { 46 | return [ 47 | 'name' => ['required', 'string', 'min:3'], 48 | 'email' => ['required', 'string', 'email', 'unique:users'], 49 | 'password' => ['required', Password::default()->symbols()->numbers()->letters()->uncompromised()], 50 | ]; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /security/src/Actions/PublishUserCreatedMessage.php: -------------------------------------------------------------------------------- 1 | onTopic('user.created') 21 | ->withHeaders([ 22 | 'user_id' => $userId, 23 | ]) 24 | ->withBodyKey('user_id', $userId) 25 | ->withBodyKey('email', $email) 26 | ->send(); 27 | } 28 | 29 | /** 30 | * @throws Exception 31 | */ 32 | public function asJob(User $user): void 33 | { 34 | $this->handle($user->getKey(), $user->getAttribute('email')); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /security/src/Models/User.php: -------------------------------------------------------------------------------- 1 | app->make(Repository::class); 30 | $config->set('kafka.brokers', env('KAFKA_BROKERS')); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /security/src/Providers/AuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | addHour()); 22 | Passport::refreshTokensExpireIn(now()->addWeek()); 23 | Passport::personalAccessTokensExpireIn(now()->addMonths(3)); 24 | 25 | /** @var ConfigRepository $config */ 26 | $config = $this->app->make(ConfigRepository::class); 27 | $config->set('auth.providers.users', [ 28 | 'driver' => 'eloquent', 29 | 'model' => User::class, 30 | ]); 31 | $config->set('auth.guards.api', [ 32 | 'driver' => 'passport', 33 | 'provider' => 'users', 34 | ]); 35 | } 36 | 37 | public function register(): void 38 | { 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /security/src/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | booted(function () { 28 | if (! is_null($this->namespace)) { 29 | $this->app[UrlGenerator::class]->setRootControllerNamespace($this->namespace); 30 | } 31 | 32 | if ($this->app->routesAreCached()) { 33 | $this->app->booted(function () { 34 | require $this->app->getCachedRoutesPath(); 35 | }); 36 | } else { 37 | $this->app->call([$this, 'routing']); 38 | 39 | $this->app->booted(function () { 40 | $this->app['router']->getRoutes()->refreshNameLookups(); 41 | $this->app['router']->getRoutes()->refreshActionLookups(); 42 | }); 43 | } 44 | }); 45 | } 46 | 47 | public function routing(): void 48 | { 49 | Route::middleware('api') 50 | ->prefix('api/v1') 51 | ->group($this->app->path('Routes/ApiV1.php')); 52 | 53 | Route::get('/health', function () { 54 | Event::dispatch(new DiagnosingHealth); 55 | 56 | return response(status: 200); 57 | }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /security/src/Routes/ApiV1.php: -------------------------------------------------------------------------------- 1 | make(Router::class); 10 | } catch (BindingResolutionException $e) { 11 | abort(500, $e->getMessage()); 12 | } 13 | 14 | $route->group(['prefix' => 'users', 'as' => 'user.'], function (Router $route) { 15 | $route->post('/', CreateNewUser::class); 16 | }); 17 | -------------------------------------------------------------------------------- /security/storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /security/storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /security/storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | compiled.php 2 | config.php 3 | down 4 | events.scanned.php 5 | maintenance.php 6 | routes.php 7 | routes.scanned.php 8 | schedule-* 9 | services.json 10 | -------------------------------------------------------------------------------- /security/storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /security/storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /security/storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /security/storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /security/storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /security/storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | --------------------------------------------------------------------------------