├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── Readme.md ├── docker ├── alertmanager │ └── config.yml ├── docker-compose.yml ├── elk │ ├── elasticsearch │ │ ├── .dockerignore │ │ ├── Dockerfile │ │ └── config │ │ │ └── elasticsearch.yml │ ├── filebeat │ │ ├── .dockerignore │ │ ├── Dockerfile │ │ └── config │ │ │ └── filebeat.yml │ ├── kibana │ │ ├── .dockerignore │ │ ├── Dockerfile │ │ └── config │ │ │ └── kibana.yml │ └── setup │ │ ├── .dockerignore │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── entrypoint.sh │ │ ├── helpers.sh │ │ └── roles │ │ └── filebeat_writer.json ├── grafana │ ├── config.monitoring │ └── provisioning │ │ ├── dashboards │ │ ├── Docker Prometheus Monitoring-1571332751387.json │ │ ├── Golang Web API Dashboard-Default.json │ │ ├── Node Exporter Full with Node Name.json │ │ ├── Server Status.json │ │ └── dashboard.yml │ │ └── datasources │ │ └── datasource.yml ├── prometheus │ ├── alert.rules.yml │ └── prometheus.yml └── redis │ └── redis.conf ├── docs └── files │ ├── db_diagram.png │ ├── grafana.png │ ├── kibana.png │ ├── swagger.png │ └── system_diagram.png └── src ├── Dockerfile ├── api ├── api.go ├── dto │ ├── car.go │ ├── city.go │ ├── color.go │ ├── company.go │ ├── country.go │ ├── file.go │ ├── property.go │ ├── user.go │ └── year.go ├── handler │ ├── base_generic_crud.go │ ├── car_model.go │ ├── car_model_color.go │ ├── car_model_comment.go │ ├── car_model_image.go │ ├── car_model_price_history.go │ ├── car_model_property.go │ ├── car_model_year.go │ ├── car_type.go │ ├── city.go │ ├── color.go │ ├── company.go │ ├── country.go │ ├── file.go │ ├── gearbox.go │ ├── health.go │ ├── property.go │ ├── property_category.go │ ├── property_simple.go │ ├── test_handler.go │ ├── user.go │ └── year.go ├── helper │ ├── base_response.go │ ├── result_code.go │ └── status_code_mapping.go ├── middleware │ ├── auth.go │ ├── cors.go │ ├── custom_recovery.go │ ├── limiter.go │ ├── logger.go │ ├── otp_limiter.go │ ├── prometheus.go │ └── test_middleware.go ├── router │ ├── basic.go │ ├── cars.go │ ├── health.go │ ├── property.go │ ├── test_router.go │ └── users.go └── validation │ ├── custom.go │ ├── mobile.go │ └── password.go ├── cmd └── main.go ├── common ├── mapper.go ├── persian.go └── strings.go ├── config ├── config-development.yml ├── config-docker.yml ├── config-production.yml └── config.go ├── constant └── constanst.go ├── dependency └── dependency.go ├── docs ├── docs.go ├── swagger.json └── swagger.yaml ├── domain ├── filter │ ├── dynamic_filter.go │ └── pagination.go ├── model │ ├── base.go │ ├── base_model.go │ ├── car.go │ ├── property.go │ └── user.go └── repository │ └── repository.go ├── go.mod ├── go.sum ├── infra ├── cache │ └── redis.go └── persistence │ ├── database │ ├── postgres.go │ └── query_builder.go │ ├── migration │ ├── 1_Init.go │ └── default_values.go │ └── repository │ ├── postgres_repository.go │ └── postgres_user_repository.go ├── pkg ├── limiter │ └── ip_limiter.go ├── logging │ ├── category.go │ ├── log_helper.go │ ├── logger.go │ ├── zap_logger.go │ └── zero_logger.go ├── metrics │ ├── counters.go │ └── histograms.go └── service_errors │ ├── error_code.go │ └── service_error.go ├── tests ├── integration │ └── .gitkeep └── unit │ └── .gitkeep └── usecase ├── base_usecase.go ├── carModelColor_usecase.go ├── carModelComment_usecase.go ├── carModelImage_usecase.go ├── carModelPriceHistory_usecase.go ├── carModelProperty_usecase.go ├── carModelYear_usecase.go ├── carModel_usecase.go ├── carType_usecase.go ├── city_usecase.go ├── color_usecase.go ├── company_usecase.go ├── country_usecase.go ├── dto ├── base.go ├── car.go ├── property.go └── user.go ├── file_usecase.go ├── gearbox_usecase.go ├── otp_usecase.go ├── propertyCategory_usecase.go ├── property_usecase.go ├── token_usecase.go ├── user_usecase.go └── year_usecase.go /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | 27 | #Log file 28 | *.log 29 | *.log.gz 30 | 31 | __debug_bin* 32 | 33 | */cmd/uploads 34 | 35 | # vscode folder 36 | #.vscode/ 37 | 38 | # idea folder 39 | .idea/ 40 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch Package", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${workspaceFolder}/src/cmd", 13 | "preLaunchTask": "swag" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "swag", 6 | "command": "cd src && swag init -g cmd/main.go --parseDependency --parseInternal", 7 | "type": "shell" 8 | } 9 | ] 10 | } -------------------------------------------------------------------------------- /docker/alertmanager/config.yml: -------------------------------------------------------------------------------- 1 | route: 2 | repeat_interval: 30m 3 | receiver: "sms-notifications" 4 | routes: 5 | - receiver: "email-notifications" 6 | group_wait: 30s 7 | match_re: 8 | severity: critical|warning 9 | continue: true 10 | 11 | - receiver: "sms-notifications" 12 | group_wait: 30s 13 | match_re: 14 | severity: critical 15 | continue: true 16 | 17 | receivers: 18 | - name: "email-notifications" 19 | email_configs: 20 | - to: omid.haqi@outlook.com 21 | from: yourmail@gmail.com 22 | smarthost: smtp.gmail.com:587 23 | auth_username: yourmail@gmail.com 24 | auth_identity: yourmail@gmail.com 25 | auth_password: xxxxxxxxxxxxxxxxxx 26 | # create your own auth_password => https://support.google.com/mail/answer/185833?hl=en 27 | send_resolved: true 28 | 29 | - name: "sms-notifications" 30 | webhook_configs: 31 | - url: http://my-api/api/notify/send 32 | send_resolved: true 33 | -------------------------------------------------------------------------------- /docker/elk/elasticsearch/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore Docker build files 2 | Dockerfile 3 | .dockerignore 4 | 5 | # Ignore OS artifacts 6 | **/.DS_Store 7 | -------------------------------------------------------------------------------- /docker/elk/elasticsearch/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ELASTIC_VERSION 2 | 3 | # https://www.docker.elastic.co/ 4 | FROM elasticsearch:${ELASTIC_VERSION} 5 | 6 | # Add your elasticsearch plugins setup here 7 | # Example: RUN elasticsearch-plugin install analysis-icu 8 | -------------------------------------------------------------------------------- /docker/elk/elasticsearch/config/elasticsearch.yml: -------------------------------------------------------------------------------- 1 | ## Default Elasticsearch configuration from Elasticsearch base image. 2 | ## https://github.com/elastic/elasticsearch/blob/main/distribution/docker/src/docker/config/elasticsearch.yml 3 | # 4 | cluster.name: docker-cluster 5 | network.host: 0.0.0.0 6 | 7 | ## X-Pack settings 8 | ## see https://www.elastic.co/guide/en/elasticsearch/reference/current/security-settings.html 9 | # 10 | xpack.license.self_generated.type: trial 11 | xpack.security.enabled: true 12 | -------------------------------------------------------------------------------- /docker/elk/filebeat/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore Docker build files 2 | Dockerfile 3 | .dockerignore 4 | 5 | # Ignore OS artifacts 6 | **/.DS_Store 7 | -------------------------------------------------------------------------------- /docker/elk/filebeat/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ELASTIC_VERSION 2 | 3 | FROM elastic/filebeat:${ELASTIC_VERSION} 4 | -------------------------------------------------------------------------------- /docker/elk/filebeat/config/filebeat.yml: -------------------------------------------------------------------------------- 1 | ## Filebeat configuration 2 | ## https://github.com/elastic/beats/blob/main/deploy/docker/filebeat.docker.yml 3 | # 4 | 5 | name: filebeat 6 | filebeat.inputs: 7 | - type: log 8 | enabled: true 9 | paths: 10 | - "/var/log/filebeat/service/*.log" 11 | - "/app/*.log" 12 | fields: 13 | environment: Development 14 | tags: ["gocourse"] 15 | multiline.pattern: "^{" 16 | multiline.negate: true 17 | multiline.match: after 18 | logging.level: debug 19 | 20 | filebeat.config: 21 | modules: 22 | path: ${path.config}/modules.d/*.yml 23 | reload.enabled: true 24 | 25 | setup.template.name: "logs-service" 26 | setup.template.pattern: "logs-service-*" 27 | setup.kibana.host: kibana:5601 28 | setup.template.settings: 29 | index.number_of_shards: 1 30 | 31 | processors: 32 | - decode_json_fields: 33 | fields: ["message"] 34 | process_array: false 35 | max_depth: 1 36 | target: "" 37 | overwrite_keys: false 38 | add_error_key: true 39 | 40 | # filebeat.autodiscover: 41 | # providers: 42 | # - type: docker 43 | # enabled: true 44 | # templates: 45 | # - condition: 46 | # equals: 47 | # contains.container.image: car-sale-api 48 | # config: 49 | # - type: docker 50 | # containers.ids: 51 | # - "${data.docker.container.id}" 52 | # paths: 53 | # - /var/lib/docker/containers/${data.docker.container.id}/app/logs/*.json 54 | # fields: 55 | # environment: Docker 56 | # tags: ["gocourse"] 57 | # multiline.pattern: "^{" 58 | # multiline.negate: true 59 | # multiline.match: after 60 | 61 | filebeat.shutdown_timeout: 5s 62 | 63 | # filebeat.autodiscover: 64 | # providers: 65 | # # The Docker autodiscover provider automatically retrieves logs from Docker 66 | # # containers as they start and stop. 67 | # - type: docker 68 | # hints.enabled: true 69 | 70 | # monitoring: 71 | # enabled: true 72 | # elasticsearch: 73 | # username: beats_system 74 | # password: ${BEATS_SYSTEM_PASSWORD} 75 | 76 | output.elasticsearch: 77 | hosts: ["http://elasticsearch:9200"] 78 | username: filebeat_internal 79 | password: ${FILEBEAT_INTERNAL_PASSWORD} 80 | index: "logs-service-%{+yyyy.MM.dd}" 81 | 82 | ## HTTP endpoint for health checking 83 | ## https://www.elastic.co/guide/en/beats/filebeat/current/http-endpoint.html 84 | # 85 | 86 | fields_under_root: true 87 | fields: 88 | host.ip: 127.0.0.1 89 | -------------------------------------------------------------------------------- /docker/elk/kibana/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore Docker build files 2 | Dockerfile 3 | .dockerignore 4 | 5 | # Ignore OS artifacts 6 | **/.DS_Store 7 | -------------------------------------------------------------------------------- /docker/elk/kibana/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ELASTIC_VERSION 2 | 3 | # https://www.docker.elastic.co/ 4 | FROM kibana:${ELASTIC_VERSION} 5 | 6 | # Add your kibana plugins setup here 7 | # Example: RUN kibana-plugin install 8 | -------------------------------------------------------------------------------- /docker/elk/kibana/config/kibana.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ## Default Kibana configuration from Kibana base image. 3 | ## https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/templates/kibana_yml.template.ts 4 | # 5 | server.name: kibana 6 | server.host: 0.0.0.0 7 | elasticsearch.hosts: [http://elasticsearch:9200] 8 | 9 | monitoring.ui.container.elasticsearch.enabled: true 10 | monitoring.ui.container.logstash.enabled: true 11 | 12 | ## X-Pack security credentials 13 | # 14 | elasticsearch.username: kibana_system 15 | elasticsearch.password: ${KIBANA_SYSTEM_PASSWORD} 16 | 17 | ## Fleet 18 | ## https://www.elastic.co/guide/en/kibana/current/fleet-settings-kb.html 19 | # 20 | xpack.fleet.agents.fleet_server.hosts: [http://fleet-server:8220] 21 | 22 | xpack.fleet.outputs: 23 | - id: fleet-default-output 24 | name: default 25 | type: elasticsearch 26 | hosts: [http://elasticsearch:9200] 27 | is_default: true 28 | is_default_monitoring: true 29 | 30 | xpack.fleet.packages: 31 | - name: fleet_server 32 | version: latest 33 | - name: system 34 | version: latest 35 | - name: elastic_agent 36 | version: latest 37 | - name: apm 38 | version: latest 39 | 40 | xpack.fleet.agentPolicies: 41 | - name: Fleet Server Policy 42 | id: fleet-server-policy 43 | description: Static agent policy for Fleet Server 44 | monitoring_enabled: 45 | - logs 46 | - metrics 47 | package_policies: 48 | - name: fleet_server-1 49 | package: 50 | name: fleet_server 51 | - name: system-1 52 | package: 53 | name: system 54 | - name: elastic_agent-1 55 | package: 56 | name: elastic_agent 57 | - name: Agent Policy APM Server 58 | id: agent-policy-apm-server 59 | description: Static agent policy for the APM Server integration 60 | monitoring_enabled: 61 | - logs 62 | - metrics 63 | package_policies: 64 | - name: system-1 65 | package: 66 | name: system 67 | - name: elastic_agent-1 68 | package: 69 | name: elastic_agent 70 | - name: apm-1 71 | package: 72 | name: apm 73 | # See the APM package manifest for a list of possible inputs. 74 | # https://github.com/elastic/apm-server/blob/v8.5.0/apmpackage/apm/manifest.yml#L41-L168 75 | inputs: 76 | - type: apm 77 | vars: 78 | - name: host 79 | value: 0.0.0.0:8200 80 | - name: url 81 | value: http://apm-server:8200 82 | -------------------------------------------------------------------------------- /docker/elk/setup/.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore Docker build files 2 | Dockerfile 3 | .dockerignore 4 | 5 | # Ignore OS artifacts 6 | **/.DS_Store 7 | 8 | # Ignore Git files 9 | .gitignore 10 | 11 | # Ignore setup state 12 | state/ 13 | -------------------------------------------------------------------------------- /docker/elk/setup/.gitignore: -------------------------------------------------------------------------------- 1 | /state/ 2 | -------------------------------------------------------------------------------- /docker/elk/setup/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG ELASTIC_VERSION 2 | 3 | # https://www.docker.elastic.co/ 4 | FROM elasticsearch:${ELASTIC_VERSION} 5 | 6 | USER root 7 | 8 | RUN set -eux; \ 9 | mkdir /state; \ 10 | chmod 0775 /state; \ 11 | chown elasticsearch:root /state 12 | 13 | USER elasticsearch:root 14 | 15 | ENTRYPOINT ["/entrypoint.sh"] 16 | -------------------------------------------------------------------------------- /docker/elk/setup/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -eu 4 | set -o pipefail 5 | 6 | source "${BASH_SOURCE[0]%/*}"/helpers.sh 7 | 8 | 9 | # -------------------------------------------------------- 10 | # Users declarations 11 | 12 | declare -A users_passwords 13 | users_passwords=( 14 | [kibana_system]="${KIBANA_SYSTEM_PASSWORD:-}" 15 | [filebeat_internal]="${FILEBEAT_INTERNAL_PASSWORD:-}" 16 | ) 17 | 18 | declare -A users_roles 19 | users_roles=( 20 | [filebeat_internal]='filebeat_writer' 21 | ) 22 | 23 | # -------------------------------------------------------- 24 | # Roles declarations 25 | 26 | declare -A roles_files 27 | roles_files=( 28 | [filebeat_writer]='filebeat_writer.json' 29 | ) 30 | 31 | # -------------------------------------------------------- 32 | 33 | 34 | echo "-------- $(date --rfc-3339=seconds) --------" 35 | 36 | state_file="${BASH_SOURCE[0]%/*}"/state/.done 37 | if [[ -e "$state_file" ]]; then 38 | declare state_birthtime 39 | state_birthtime="$(stat -c '%Y' "$state_file")" 40 | state_birthtime="$(date --rfc-3339=seconds --date="@${state_birthtime}")" 41 | 42 | log "Setup has already run successfully on ${state_birthtime}. Skipping" 43 | exit 0 44 | fi 45 | 46 | log 'Waiting for availability of Elasticsearch. This can take several minutes.' 47 | 48 | declare -i exit_code=0 49 | wait_for_elasticsearch || exit_code=$? 50 | 51 | if ((exit_code)); then 52 | case $exit_code in 53 | 6) 54 | suberr 'Could not resolve host. Is Elasticsearch running?' 55 | ;; 56 | 7) 57 | suberr 'Failed to connect to host. Is Elasticsearch healthy?' 58 | ;; 59 | 28) 60 | suberr 'Timeout connecting to host. Is Elasticsearch healthy?' 61 | ;; 62 | *) 63 | suberr "Connection to Elasticsearch failed. Exit code: ${exit_code}" 64 | ;; 65 | esac 66 | 67 | exit $exit_code 68 | fi 69 | 70 | sublog 'Elasticsearch is running' 71 | 72 | log 'Waiting for initialization of built-in users' 73 | 74 | wait_for_builtin_users || exit_code=$? 75 | 76 | if ((exit_code)); then 77 | suberr 'Timed out waiting for condition' 78 | exit $exit_code 79 | fi 80 | 81 | sublog 'Built-in users were initialized' 82 | 83 | for role in "${!roles_files[@]}"; do 84 | log "Role '$role'" 85 | 86 | declare body_file 87 | body_file="${BASH_SOURCE[0]%/*}/roles/${roles_files[$role]:-}" 88 | if [[ ! -f "${body_file:-}" ]]; then 89 | sublog "No role body found at '${body_file}', skipping" 90 | continue 91 | fi 92 | 93 | sublog 'Creating/updating' 94 | ensure_role "$role" "$(<"${body_file}")" 95 | done 96 | 97 | for user in "${!users_passwords[@]}"; do 98 | log "User '$user'" 99 | if [[ -z "${users_passwords[$user]:-}" ]]; then 100 | sublog 'No password defined, skipping' 101 | continue 102 | fi 103 | 104 | declare -i user_exists=0 105 | user_exists="$(check_user_exists "$user")" 106 | 107 | if ((user_exists)); then 108 | sublog 'User exists, setting password' 109 | set_user_password "$user" "${users_passwords[$user]}" 110 | else 111 | if [[ -z "${users_roles[$user]:-}" ]]; then 112 | suberr ' No role defined, skipping creation' 113 | continue 114 | fi 115 | 116 | sublog 'User does not exist, creating' 117 | create_user "$user" "${users_passwords[$user]}" "${users_roles[$user]}" 118 | fi 119 | done 120 | 121 | mkdir -p "${state_file%/*}" 122 | touch "$state_file" 123 | -------------------------------------------------------------------------------- /docker/elk/setup/roles/filebeat_writer.json: -------------------------------------------------------------------------------- 1 | { 2 | "cluster": [ 3 | "manage_ilm", 4 | "manage_index_templates", 5 | "monitor", 6 | "read_pipeline" 7 | ], 8 | "indices": [ 9 | { 10 | "names": ["logs-service*"], 11 | "privileges": [ 12 | "create_doc", 13 | "manage", 14 | "auto_configure", 15 | "create_index", 16 | "all" 17 | ] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /docker/grafana/config.monitoring: -------------------------------------------------------------------------------- 1 | GF_SECURITY_ADMIN_PASSWORD=foobar 2 | GF_USERS_ALLOW_SIGN_UP=false 3 | -------------------------------------------------------------------------------- /docker/grafana/provisioning/dashboards/dashboard.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: 'Prometheus' 5 | orgId: 1 6 | folder: '' 7 | type: file 8 | disableDeletion: false 9 | editable: true 10 | options: 11 | path: /etc/grafana/provisioning/dashboards 12 | -------------------------------------------------------------------------------- /docker/grafana/provisioning/datasources/datasource.yml: -------------------------------------------------------------------------------- 1 | # config file version 2 | apiVersion: 1 3 | 4 | # list of datasources that should be deleted from the database 5 | deleteDatasources: 6 | - name: Prometheus 7 | orgId: 1 8 | 9 | # list of datasources to insert/update depending 10 | # whats available in the database 11 | datasources: 12 | # name of the datasource. Required 13 | - name: Prometheus 14 | # datasource type. Required 15 | type: prometheus 16 | # access mode. direct or proxy. Required 17 | access: proxy 18 | # org id. will default to orgId 1 if not specified 19 | orgId: 1 20 | # url 21 | url: http://prometheus:9090 22 | # database password, if used 23 | password: 24 | # database user, if used 25 | user: 26 | # database name, if used 27 | database: 28 | # enable/disable basic auth 29 | basicAuth: false 30 | # basic auth username, if used 31 | basicAuthUser: 32 | # basic auth password, if used 33 | basicAuthPassword: 34 | # enable/disable with credentials headers 35 | withCredentials: 36 | # mark as default datasource. Max one per org 37 | isDefault: true 38 | # fields that will be converted to json and stored in json_data 39 | jsonData: 40 | graphiteVersion: "1.1" 41 | tlsAuth: false 42 | tlsAuthWithCACert: false 43 | # json object of data that will be encrypted. 44 | secureJsonData: 45 | tlsCACert: "..." 46 | tlsClientCert: "..." 47 | tlsClientKey: "..." 48 | version: 1 49 | # allow users to edit datasources from the UI. 50 | editable: true 51 | -------------------------------------------------------------------------------- /docker/prometheus/alert.rules.yml: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: duration 3 | rules: 4 | - alert: response_time_high 5 | expr: sum (rate(application_request_duration[1m])) / sum (rate(application_request_counter[1m])) > 3000 6 | for: 30s 7 | labels: 8 | severity: critical 9 | annotations: 10 | summary: "Instance {{ $labels.instance }} response time very high: {{$value}}" 11 | description: "" 12 | 13 | - name: error_percent_increased 14 | rules: 15 | - alert: error_percent_increased 16 | expr: sum (rate(application_request_counter{responsecode!="0"}[5m])) / sum (rate(application_request_counter[5m])) * 100 > 10 17 | for: 30s 18 | labels: 19 | severity: critical 20 | annotations: 21 | summary: "Instance {{ $labels.instance }} error percent is {{$value}}" 22 | description: "" 23 | 24 | - name: errors_increased 25 | rules: 26 | - alert: errors_increased 27 | expr: sum (increase(application_request_counter{responsecode!="0"}[1m])) > 100 28 | for: 30s 29 | labels: 30 | severity: critical 31 | annotations: 32 | summary: "Instance {{ $labels.instance }} errors count in minutes is {{$value}}" 33 | description: "." 34 | -------------------------------------------------------------------------------- /docker/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | # my global config 2 | global: 3 | scrape_interval: 5s # By default, scrape targets every 15 seconds. 4 | evaluation_interval: 15s # By default, scrape targets every 15 seconds. 5 | # scrape_timeout is set to the global default (10s). 6 | 7 | # Attach these labels to any time series or alerts when communicating with 8 | # external systems (federation, remote storage, Alertmanager). 9 | external_labels: 10 | monitor: "my-project" 11 | 12 | # Load and evaluate rules in this file every 'evaluation_interval' seconds. 13 | rule_files: 14 | - "alert.rules.yml" 15 | # - "first.rules" 16 | # - "second.rules" 17 | 18 | # alert 19 | alerting: 20 | alertmanagers: 21 | - scheme: http 22 | static_configs: 23 | - targets: 24 | - "alertmanager:9093" 25 | 26 | # A scrape configuration containing exactly one endpoint to scrape: 27 | # Here it's Prometheus itself. 28 | scrape_configs: 29 | # The job name is added as a label `job=` to any timeseries scraped from this config. 30 | 31 | - job_name: "prometheus" 32 | 33 | # Override the global default and scrape targets from this job every 5 seconds. 34 | scrape_interval: 5s 35 | 36 | static_configs: 37 | - targets: ["localhost:9090"] 38 | 39 | - job_name: "my-api" 40 | scrape_interval: 5s 41 | dns_sd_configs: 42 | - names: 43 | - "tasks.web-api" 44 | type: "A" 45 | port: 8008 46 | metrics_path: /metrics 47 | scheme: http 48 | static_configs: 49 | - targets: 50 | [ 51 | "web-api1:5000", 52 | "web-api2:5000", 53 | "192.168.1.114:5005", 54 | "192.168.1.114:5010", 55 | ] 56 | 57 | - job_name: "node-exporter" 58 | 59 | # Override the global default and scrape targets from this job every 5 seconds. 60 | scrape_interval: 5s 61 | 62 | dns_sd_configs: 63 | - names: 64 | - "tasks.node-exporter" 65 | type: "A" 66 | port: 9100 67 | metrics_path: /metrics 68 | scheme: http 69 | static_configs: 70 | - targets: ["node-exporter:9100"] # This is correct as it uses the container name and internal port 71 | -------------------------------------------------------------------------------- /docker/redis/redis.conf: -------------------------------------------------------------------------------- 1 | requirepass password -------------------------------------------------------------------------------- /docs/files/db_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmidHaqi/clean-web-api/fd7e5d8744e741f7a4d85c4bd8403edf7a3c8034/docs/files/db_diagram.png -------------------------------------------------------------------------------- /docs/files/grafana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmidHaqi/clean-web-api/fd7e5d8744e741f7a4d85c4bd8403edf7a3c8034/docs/files/grafana.png -------------------------------------------------------------------------------- /docs/files/kibana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmidHaqi/clean-web-api/fd7e5d8744e741f7a4d85c4bd8403edf7a3c8034/docs/files/kibana.png -------------------------------------------------------------------------------- /docs/files/swagger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmidHaqi/clean-web-api/fd7e5d8744e741f7a4d85c4bd8403edf7a3c8034/docs/files/swagger.png -------------------------------------------------------------------------------- /docs/files/system_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmidHaqi/clean-web-api/fd7e5d8744e741f7a4d85c4bd8403edf7a3c8034/docs/files/system_diagram.png -------------------------------------------------------------------------------- /src/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23-bookworm AS builder 2 | 3 | WORKDIR /app 4 | 5 | COPY go.* ./ 6 | ENV CGO_ENABLED=0 7 | RUN go env -w GO111MODULE=on 8 | RUN go env -w GOPROXY=https://goproxy.cn,direct 9 | RUN go mod download 10 | 11 | COPY . ./ 12 | 13 | RUN go build -v -o server ./cmd/main.go 14 | 15 | FROM debian:buster-slim 16 | RUN set -x && apt-get update && DEBIAN_FRONTEND=noninteractive \ 17 | rm -rf /var/lib/apt/lists/* 18 | 19 | COPY --from=builder /app/server /app/server 20 | COPY --from=builder /app/config/config-docker.yml /app/config/config-docker.yml 21 | COPY --from=builder /app/docs /app/docs 22 | 23 | ENV APP_ENV=docker 24 | ENV PORT=${Port} 25 | 26 | CMD [ "/app/server" ] -------------------------------------------------------------------------------- /src/api/dto/city.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import "github.com/omidhaqi/clean-web-api/usecase/dto" 4 | 5 | type CreateCityRequest struct { 6 | Name string `json:"name" binding:"required,alpha,min=3,max=20"` 7 | CountryId int `json:"countryId" binding:"required"` 8 | } 9 | 10 | type UpdateCityRequest struct { 11 | Name string `json:"name,omitempty" binding:"alpha,min=3,max=20"` 12 | CountryId int `json:"countryId,omitempty"` 13 | } 14 | type CityResponse struct { 15 | Id int `json:"id"` 16 | Name string `json:"name"` 17 | Country CountryResponse `json:"country,omitempty"` 18 | } 19 | 20 | func ToCityResponse(from dto.City) CityResponse { 21 | return CityResponse{ 22 | Id: from.Id, 23 | Name: from.Name, 24 | Country: ToCountryResponse(from.Country), 25 | } 26 | } 27 | 28 | func ToCreateCity(from CreateCityRequest) dto.CreateCity { 29 | return dto.CreateCity{ 30 | Name: from.Name, 31 | CountryId: from.CountryId, 32 | } 33 | } 34 | 35 | func ToUpdateCity(from UpdateCityRequest) dto.UpdateCity { 36 | return dto.UpdateCity{ 37 | Name: from.Name, 38 | CountryId: from.CountryId, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/api/dto/color.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import "github.com/omidhaqi/clean-web-api/usecase/dto" 4 | 5 | type CreateColorRequest struct { 6 | Name string `json:"name" binding:"alpha,min=3,max=15"` 7 | HexCode string `json:"hexCode" binding:"min=7,max=7"` 8 | } 9 | 10 | type UpdateColorRequest struct { 11 | Name string `json:"name,omitempty" binding:"alpha,min=3,max=15"` 12 | HexCode string `json:"hexCode,omitempty" binding:"min=7,max=7"` 13 | } 14 | 15 | type ColorResponse struct { 16 | Id int `json:"id"` 17 | Name string `json:"name,omitempty"` 18 | HexCode string `json:"hexCode,omitempty"` 19 | } 20 | 21 | func ToColorResponse(from dto.Color) ColorResponse { 22 | return ColorResponse{ 23 | Id: from.Id, 24 | Name: from.Name, 25 | HexCode: from.HexCode, 26 | } 27 | } 28 | 29 | func ToCreateColor(from CreateColorRequest) dto.CreateColor { 30 | return dto.CreateColor{ 31 | Name: from.Name, 32 | HexCode: from.HexCode, 33 | } 34 | } 35 | 36 | func ToUpdateColor(from CreateColorRequest) dto.UpdateColor { 37 | return dto.UpdateColor{ 38 | Name: from.Name, 39 | HexCode: from.HexCode, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/api/dto/company.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import "github.com/omidhaqi/clean-web-api/usecase/dto" 4 | 5 | type CreateCompanyRequest struct { 6 | Name string `json:"name" binding:"required,alpha,min=3,max=20"` 7 | CountryId int `json:"countryId" binding:"required"` 8 | } 9 | 10 | type UpdateCompanyRequest struct { 11 | Name string `json:"name,omitempty" binding:"alpha,min=3,max=20"` 12 | CountryId int `json:"countryId,omitempty"` 13 | } 14 | type CompanyResponse struct { 15 | Id int `json:"id"` 16 | Name string `json:"name"` 17 | Country CountryResponse `json:"country,omitempty"` 18 | } 19 | 20 | func ToCompanyResponse(from dto.Company) CompanyResponse { 21 | return CompanyResponse{ 22 | Id: from.Id, 23 | Name: from.Name, 24 | Country: ToCountryResponse(from.Country), 25 | } 26 | } 27 | 28 | func ToCreateCompany(from CreateCompanyRequest) dto.CreateCompany { 29 | return dto.CreateCompany{ 30 | Name: from.Name, 31 | CountryId: from.CountryId, 32 | } 33 | } 34 | 35 | func ToUpdateCompany(from UpdateCompanyRequest) dto.UpdateCompany { 36 | return dto.UpdateCompany{ 37 | Name: from.Name, 38 | CountryId: from.CountryId, 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/api/dto/country.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import "github.com/omidhaqi/clean-web-api/usecase/dto" 4 | 5 | type CreateUpdateCountryRequest struct { 6 | Name string `json:"name" binding:"required,alpha,min=3,max=20"` 7 | } 8 | 9 | type CountryResponse struct { 10 | Id int `json:"id"` 11 | Name string `json:"name"` 12 | Cities []CityResponse `json:"cities,omitempty"` 13 | Companies []CompanyResponse `json:"companies,omitempty"` 14 | } 15 | 16 | func ToCountryResponse(from dto.Country) CountryResponse { 17 | return CountryResponse{ 18 | Id: from.Id, 19 | Name: from.Name, 20 | } 21 | } 22 | 23 | func ToCreateUpdateCountry(from CreateUpdateCountryRequest) dto.Name { 24 | return dto.Name{ 25 | Name: from.Name, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/api/dto/file.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import ( 4 | "mime/multipart" 5 | 6 | "github.com/omidhaqi/clean-web-api/usecase/dto" 7 | ) 8 | 9 | type FileFormRequest struct { 10 | File *multipart.FileHeader `json:"file" form:"file" binding:"required" swaggerignore:"true"` 11 | } 12 | 13 | type UploadFileRequest struct { 14 | FileFormRequest 15 | Description string `json:"description" form:"description" binding:"required"` 16 | } 17 | 18 | type CreateFileRequest struct { 19 | Name string `json:"name"` 20 | Directory string `json:"directory"` 21 | Description string `json:"description"` 22 | MimeType string `json:"mimeType"` 23 | } 24 | 25 | type UpdateFileRequest struct { 26 | Description string `json:"description"` 27 | } 28 | 29 | type FileResponse struct { 30 | Id int `json:"id"` 31 | Name string `json:"name"` 32 | Directory string `json:"directory"` 33 | Description string `json:"description"` 34 | MimeType string `json:"mimeType"` 35 | } 36 | 37 | func ToFileResponse(from dto.File) FileResponse { 38 | return FileResponse{ 39 | Id: from.Id, 40 | Name: from.Name, 41 | Directory: from.Directory, 42 | Description: from.Description, 43 | MimeType: from.MimeType, 44 | } 45 | } 46 | 47 | func ToCreateFile(from CreateFileRequest) dto.CreateFile { 48 | return dto.CreateFile{ 49 | Name: from.Name, 50 | Directory: from.Directory, 51 | Description: from.Description, 52 | MimeType: from.MimeType, 53 | } 54 | } 55 | 56 | func ToUpdateFile(from UpdateFileRequest) dto.UpdateFile { 57 | return dto.UpdateFile{ 58 | Description: from.Description, 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/api/dto/property.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import "github.com/omidhaqi/clean-web-api/usecase/dto" 4 | 5 | type CreatePropertyCategoryRequest struct { 6 | Name string `json:"name" binding:"required,alpha,min=3,max=50"` 7 | Icon string `json:"icon" binding:"max=1000"` 8 | } 9 | 10 | type UpdatePropertyCategoryRequest struct { 11 | Name string `json:"name,omitempty"` 12 | Icon string `json:"icon,omitempty"` 13 | } 14 | 15 | type PropertyCategoryResponse struct { 16 | Id int `json:"id"` 17 | Name string `json:"name"` 18 | Icon string `json:"icon"` 19 | Properties []PropertyResponse `json:"properties,omitempty"` 20 | } 21 | 22 | type CreatePropertyRequest struct { 23 | Name string `json:"name" binding:"required,alpha,min=3,max=50"` 24 | CategoryId int `json:"categoryId" binding:"required"` 25 | Icon string `json:"icon" binding:"max=1000"` 26 | Description string `json:"description" binding:"max=1000"` 27 | DataType string `json:"dataType" binding:"max=15"` 28 | Unit string `json:"unit" binding:"max=15"` 29 | } 30 | 31 | type UpdatePropertyRequest struct { 32 | Name string `json:"name,omitempty"` 33 | CategoryId int `json:"categoryId,omitempty"` 34 | Icon string `json:"icon,omitempty" binding:"max=1000"` 35 | Description string `json:"description,omitempty" binding:"max=1000"` 36 | DataType string `json:"dataType,omitempty" binding:"max=15"` 37 | Unit string `json:"unit,omitempty" binding:"max=15"` 38 | } 39 | 40 | type PropertyResponse struct { 41 | Id int `json:"id"` 42 | Name string `json:"name"` 43 | Icon string `json:"icon"` 44 | Description string `json:"description"` 45 | DataType string `json:"dataType"` 46 | Unit string `json:"unit"` 47 | Category PropertyCategoryResponse `json:"category,omitempty"` 48 | } 49 | 50 | func ToPropertyResponse(from dto.Property) PropertyResponse { 51 | return PropertyResponse{ 52 | Id: from.Id, 53 | Name: from.Name, 54 | Icon: from.Icon, 55 | DataType: from.DataType, 56 | Unit: from.Unit, 57 | Category: ToPropertyCategoryResponse(from.Category), 58 | Description: from.Description, 59 | } 60 | } 61 | 62 | func ToCreateProperty(from CreatePropertyRequest) dto.CreateProperty { 63 | return dto.CreateProperty{ 64 | Name: from.Name, 65 | Icon: from.Icon, 66 | DataType: from.DataType, 67 | Unit: from.Unit, 68 | CategoryId: from.CategoryId, 69 | Description: from.Description, 70 | } 71 | } 72 | 73 | func ToUpdateProperty(from UpdatePropertyRequest) dto.UpdateProperty { 74 | return dto.UpdateProperty{ 75 | Name: from.Name, 76 | Icon: from.Icon, 77 | DataType: from.DataType, 78 | Unit: from.Unit, 79 | CategoryId: from.CategoryId, 80 | Description: from.Description, 81 | } 82 | } 83 | 84 | func ToPropertyCategoryResponse(from dto.PropertyCategory) PropertyCategoryResponse { 85 | properties := []PropertyResponse{} 86 | for _, item := range from.Properties { 87 | properties = append(properties, ToPropertyResponse(item)) 88 | } 89 | return PropertyCategoryResponse{ 90 | Id: from.Id, 91 | Name: from.Name, 92 | Icon: from.Icon, 93 | Properties: properties, 94 | } 95 | } 96 | 97 | func ToCreatePropertyCategory(from CreatePropertyCategoryRequest) dto.CreatePropertyCategory { 98 | return dto.CreatePropertyCategory{ 99 | Name: from.Name, 100 | Icon: from.Icon, 101 | } 102 | } 103 | 104 | func ToUpdatePropertyCategory(from UpdatePropertyCategoryRequest) dto.UpdatePropertyCategory { 105 | return dto.UpdatePropertyCategory{ 106 | Name: from.Name, 107 | Icon: from.Icon, 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/api/dto/user.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import usecase "github.com/omidhaqi/clean-web-api/usecase/dto" 4 | 5 | type GetOtpRequest struct { 6 | MobileNumber string `json:"mobileNumber" binding:"required,mobile,min=11,max=11"` 7 | } 8 | 9 | type TokenDetail struct { 10 | AccessToken string `json:"accessToken"` 11 | RefreshToken string `json:"refreshToken"` 12 | AccessTokenExpireTime int64 `json:"accessTokenExpireTime"` 13 | RefreshTokenExpireTime int64 `json:"refreshTokenExpireTime"` 14 | } 15 | 16 | type RegisterUserByUsernameRequest struct { 17 | FirstName string `json:"firstName" binding:"required,min=3"` 18 | LastName string `json:"lastName" binding:"required,min=6"` 19 | Username string `json:"username" binding:"required,min=5"` 20 | Email string `json:"email" binding:"min=6,email"` 21 | Password string `json:"password" binding:"required,password,min=6"` 22 | } 23 | 24 | type RegisterLoginByMobileRequest struct { 25 | MobileNumber string `json:"mobileNumber" binding:"required,mobile,min=11,max=11"` 26 | Otp string `json:"otp" binding:"required,min=6,max=6"` 27 | } 28 | 29 | type LoginByUsernameRequest struct { 30 | Username string `json:"username" binding:"required,min=5"` 31 | Password string `json:"password" binding:"required,min=6"` 32 | } 33 | 34 | func (from RegisterUserByUsernameRequest) ToRegisterUserByUsername() usecase.RegisterUserByUsername { 35 | return usecase.RegisterUserByUsername{ 36 | Username: from.Username, 37 | FirstName: from.FirstName, 38 | LastName: from.LastName, 39 | Email: from.Email, 40 | Password: from.Password, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/api/dto/year.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/omidhaqi/clean-web-api/usecase/dto" 7 | ) 8 | 9 | type CreatePersianYearRequest struct { 10 | PersianTitle string `json:"persianTitle" binding:"min=4,max=4"` 11 | Year int `json:"year"` 12 | StartAt time.Time `json:"startAt"` 13 | EndAt time.Time `json:"endAt"` 14 | } 15 | 16 | type UpdatePersianYearRequest struct { 17 | PersianTitle string `json:"persianTitle,omitempty" binding:"min=4,max=4"` 18 | Year int `json:"year,omitempty"` 19 | StartAt time.Time `json:"startAt,omitempty"` 20 | EndAt time.Time `json:"endAt,omitempty"` 21 | } 22 | 23 | type PersianYearResponse struct { 24 | Id int `json:"id"` 25 | PersianTitle string `json:"persianTitle,omitempty"` 26 | Year int `json:"year,omitempty"` 27 | StartAt time.Time `json:"startAt,omitempty"` 28 | EndAt time.Time `json:"endAt,omitempty"` 29 | } 30 | 31 | type PersianYearWithoutDateResponse struct { 32 | Id int `json:"id"` 33 | PersianTitle string `json:"persianTitle,omitempty"` 34 | Year int `json:"year,omitempty"` 35 | } 36 | 37 | func ToPersianYearResponse(from dto.PersianYear) PersianYearResponse { 38 | return PersianYearResponse{ 39 | Id: from.Id, 40 | PersianTitle: from.PersianTitle, 41 | Year: from.Year, 42 | StartAt: from.StartAt, 43 | EndAt: from.EndAt, 44 | } 45 | } 46 | 47 | func ToPersianYearWithoutDateResponse(from dto.PersianYearWithoutDate) PersianYearWithoutDateResponse { 48 | return PersianYearWithoutDateResponse{ 49 | Id: from.Id, 50 | PersianTitle: from.PersianTitle, 51 | Year: from.Year, 52 | } 53 | } 54 | 55 | func ToCreatePersianYear(from CreatePersianYearRequest) dto.CreatePersianYear { 56 | return dto.CreatePersianYear{ 57 | PersianTitle: from.PersianTitle, 58 | Year: from.Year, 59 | StartAt: from.StartAt, 60 | EndAt: from.EndAt, 61 | } 62 | } 63 | 64 | func ToUpdatePersianYear(from UpdatePersianYearRequest) dto.UpdatePersianYear { 65 | return dto.UpdatePersianYear{ 66 | PersianTitle: from.PersianTitle, 67 | Year: from.Year, 68 | StartAt: from.StartAt, 69 | EndAt: from.EndAt, 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/api/handler/car_model.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/dto" 6 | _ "github.com/omidhaqi/clean-web-api/api/helper" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | "github.com/omidhaqi/clean-web-api/dependency" 9 | _ "github.com/omidhaqi/clean-web-api/domain/filter" 10 | "github.com/omidhaqi/clean-web-api/usecase" 11 | ) 12 | 13 | type CarModelHandler struct { 14 | usecase *usecase.CarModelUsecase 15 | } 16 | 17 | func NewCarModelHandler(cfg *config.Config) *CarModelHandler { 18 | return &CarModelHandler{ 19 | usecase: usecase.NewCarModelUsecase(cfg, dependency.GetCarModelRepository(cfg)), 20 | } 21 | } 22 | 23 | // CreateCarModel godoc 24 | // @Summary Create a CarModel 25 | // @Description Create a CarModel 26 | // @Tags CarModels 27 | // @Accept json 28 | // @produces json 29 | // @Param Request body dto.CreateCarModelRequest true "Create a CarModel" 30 | // @Success 201 {object} helper.BaseHttpResponse{result=dto.CarModelResponse} "CarModel response" 31 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 32 | // @Router /v1/car-models/ [post] 33 | // @Security AuthBearer 34 | func (h *CarModelHandler) Create(c *gin.Context) { 35 | Create(c, dto.ToCreateCarModel, dto.ToCarModelResponse, h.usecase.Create) 36 | } 37 | 38 | // UpdateCarModel godoc 39 | // @Summary Update a CarModel 40 | // @Description Update a CarModel 41 | // @Tags CarModels 42 | // @Accept json 43 | // @produces json 44 | // @Param id path int true "Id" 45 | // @Param Request body dto.UpdateCarModelRequest true "Update a CarModel" 46 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CarModelResponse} "CarModel response" 47 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 48 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 49 | // @Router /v1/car-models/{id} [put] 50 | // @Security AuthBearer 51 | func (h *CarModelHandler) Update(c *gin.Context) { 52 | Update(c, dto.ToUpdateCarModel, dto.ToCarModelResponse, h.usecase.Update) 53 | } 54 | 55 | // DeleteCarModel godoc 56 | // @Summary Delete a CarModel 57 | // @Description Delete a CarModel 58 | // @Tags CarModels 59 | // @Accept json 60 | // @produces json 61 | // @Param id path int true "Id" 62 | // @Success 200 {object} helper.BaseHttpResponse "response" 63 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 64 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 65 | // @Router /v1/car-models/{id} [delete] 66 | // @Security AuthBearer 67 | func (h *CarModelHandler) Delete(c *gin.Context) { 68 | Delete(c, h.usecase.Delete) 69 | } 70 | 71 | // GetCarModel godoc 72 | // @Summary Get a CarModel 73 | // @Description Get a CarModel 74 | // @Tags CarModels 75 | // @Accept json 76 | // @produces json 77 | // @Param id path int true "Id" 78 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CarModelResponse} "CarModel response" 79 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 80 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 81 | // @Router /v1/car-models/{id} [get] 82 | // @Security AuthBearer 83 | func (h *CarModelHandler) GetById(c *gin.Context) { 84 | GetById(c, dto.ToCarModelResponse, h.usecase.GetById) 85 | } 86 | 87 | // GetCarModels godoc 88 | // @Summary Get CarModels 89 | // @Description Get CarModels 90 | // @Tags CarModels 91 | // @Accept json 92 | // @produces json 93 | // @Param Request body filter.PaginationInputWithFilter true "Request" 94 | // @Success 200 {object} helper.BaseHttpResponse{result=filter.PagedList[dto.CarModelResponse]} "CarModel response" 95 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 96 | // @Router /v1/car-models/get-by-filter [post] 97 | // @Security AuthBearer 98 | func (h *CarModelHandler) GetByFilter(c *gin.Context) { 99 | GetByFilter(c, dto.ToCarModelResponse, h.usecase.GetByFilter) 100 | } 101 | -------------------------------------------------------------------------------- /src/api/handler/car_model_color.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/dto" 6 | _ "github.com/omidhaqi/clean-web-api/api/helper" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | "github.com/omidhaqi/clean-web-api/dependency" 9 | _ "github.com/omidhaqi/clean-web-api/domain/filter" 10 | "github.com/omidhaqi/clean-web-api/usecase" 11 | ) 12 | 13 | type CarModelColorHandler struct { 14 | usecase *usecase.CarModelColorUsecase 15 | } 16 | 17 | func NewCarModelColorHandler(cfg *config.Config) *CarModelColorHandler { 18 | return &CarModelColorHandler{ 19 | usecase: usecase.NewCarModelColorUsecase(cfg, dependency.GetCarModelColorRepository(cfg)), 20 | } 21 | } 22 | 23 | // CreateCarModelColor godoc 24 | // @Summary Create a CarModelColor 25 | // @Description Create a CarModelColor 26 | // @Tags CarModelColors 27 | // @Accept json 28 | // @produces json 29 | // @Param Request body dto.CreateCarModelColorRequest true "Create a CarModelColor" 30 | // @Success 201 {object} helper.BaseHttpResponse{result=dto.CarModelColorResponse} "CarModelColor response" 31 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 32 | // @Router /v1/car-model-colors/ [post] 33 | // @Security AuthBearer 34 | func (h *CarModelColorHandler) Create(c *gin.Context) { 35 | Create(c, dto.ToCreateCarModelColor, dto.ToCarModelColorResponse, h.usecase.Create) 36 | } 37 | 38 | // UpdateCarModelColor godoc 39 | // @Summary Update a CarModelColor 40 | // @Description Update a CarModelColor 41 | // @Tags CarModelColors 42 | // @Accept json 43 | // @produces json 44 | // @Param id path int true "Id" 45 | // @Param Request body dto.UpdateCarModelColorRequest true "Update a CarModelColor" 46 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CarModelColorResponse} "CarModelColor response" 47 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 48 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 49 | // @Router /v1/car-model-colors/{id} [put] 50 | // @Security AuthBearer 51 | func (h *CarModelColorHandler) Update(c *gin.Context) { 52 | Update(c, dto.ToUpdateCarModelColor, dto.ToCarModelColorResponse, h.usecase.Update) 53 | } 54 | 55 | // DeleteCarModelColor godoc 56 | // @Summary Delete a CarModelColor 57 | // @Description Delete a CarModelColor 58 | // @Tags CarModelColors 59 | // @Accept json 60 | // @produces json 61 | // @Param id path int true "Id" 62 | // @Success 200 {object} helper.BaseHttpResponse "response" 63 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 64 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 65 | // @Router /v1/car-model-colors/{id} [delete] 66 | // @Security AuthBearer 67 | func (h *CarModelColorHandler) Delete(c *gin.Context) { 68 | Delete(c, h.usecase.Delete) 69 | } 70 | 71 | // GetCarModelColor godoc 72 | // @Summary Get a CarModelColor 73 | // @Description Get a CarModelColor 74 | // @Tags CarModelColors 75 | // @Accept json 76 | // @produces json 77 | // @Param id path int true "Id" 78 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CarModelColorResponse} "CarModelColor response" 79 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 80 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 81 | // @Router /v1/car-model-colors/{id} [get] 82 | // @Security AuthBearer 83 | func (h *CarModelColorHandler) GetById(c *gin.Context) { 84 | GetById(c, dto.ToCarModelColorResponse, h.usecase.GetById) 85 | } 86 | 87 | // GetCarModelColors godoc 88 | // @Summary Get CarModelColors 89 | // @Description Get CarModelColors 90 | // @Tags CarModelColors 91 | // @Accept json 92 | // @produces json 93 | // @Param Request body filter.PaginationInputWithFilter true "Request" 94 | // @Success 200 {object} helper.BaseHttpResponse{result=filter.PagedList[dto.CarModelColorResponse]} "CarModelColor response" 95 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 96 | // @Router /v1/car-model-colors/get-by-filter [post] 97 | // @Security AuthBearer 98 | func (h *CarModelColorHandler) GetByFilter(c *gin.Context) { 99 | GetByFilter(c, dto.ToCarModelColorResponse, h.usecase.GetByFilter) 100 | } 101 | -------------------------------------------------------------------------------- /src/api/handler/car_model_comment.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/dto" 6 | _ "github.com/omidhaqi/clean-web-api/api/helper" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | "github.com/omidhaqi/clean-web-api/dependency" 9 | _ "github.com/omidhaqi/clean-web-api/domain/filter" 10 | "github.com/omidhaqi/clean-web-api/usecase" 11 | ) 12 | 13 | type CarModelCommentHandler struct { 14 | usecase *usecase.CarModelCommentUsecase 15 | } 16 | 17 | func NewCarModelCommentHandler(cfg *config.Config) *CarModelCommentHandler { 18 | return &CarModelCommentHandler{ 19 | usecase: usecase.NewCarModelCommentUsecase(cfg, dependency.GetCarModelCommentRepository(cfg)), 20 | } 21 | } 22 | 23 | // CreateCarModelComment godoc 24 | // @Summary Create a CarModelComment 25 | // @Description Create a CarModelComment 26 | // @Tags CarModelComments 27 | // @Accept json 28 | // @produces json 29 | // @Param Request body dto.CreateCarModelCommentRequest true "Create a CarModelComment" 30 | // @Success 201 {object} helper.BaseHttpResponse{result=dto.CarModelCommentResponse} "CarModelComment response" 31 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 32 | // @Router /v1/car-model-comments/ [post] 33 | // @Security AuthBearer 34 | func (h *CarModelCommentHandler) Create(c *gin.Context) { 35 | Create(c, dto.ToCreateCarModelComment, dto.ToCarModelCommentResponse, h.usecase.Create) 36 | } 37 | 38 | // UpdateCarModelComment godoc 39 | // @Summary Update a CarModelComment 40 | // @Description Update a CarModelComment 41 | // @Tags CarModelComments 42 | // @Accept json 43 | // @produces json 44 | // @Param id path int true "Id" 45 | // @Param Request body dto.UpdateCarModelCommentRequest true "Update a CarModelComment" 46 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CarModelCommentResponse} "CarModelComment response" 47 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 48 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 49 | // @Router /v1/car-model-comments/{id} [put] 50 | // @Security AuthBearer 51 | func (h *CarModelCommentHandler) Update(c *gin.Context) { 52 | Update(c, dto.ToUpdateCarModelComment, dto.ToCarModelCommentResponse, h.usecase.Update) 53 | } 54 | 55 | // DeleteCarModelComment godoc 56 | // @Summary Delete a CarModelComment 57 | // @Description Delete a CarModelComment 58 | // @Tags CarModelComments 59 | // @Accept json 60 | // @produces json 61 | // @Param id path int true "Id" 62 | // @Success 200 {object} helper.BaseHttpResponse "response" 63 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 64 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 65 | // @Router /v1/car-model-comments/{id} [delete] 66 | // @Security AuthBearer 67 | func (h *CarModelCommentHandler) Delete(c *gin.Context) { 68 | Delete(c, h.usecase.Delete) 69 | } 70 | 71 | // GetCarModelComment godoc 72 | // @Summary Get a CarModelComment 73 | // @Description Get a CarModelComment 74 | // @Tags CarModelComments 75 | // @Accept json 76 | // @produces json 77 | // @Param id path int true "Id" 78 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CarModelCommentResponse} "CarModelComment response" 79 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 80 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 81 | // @Router /v1/car-model-comments/{id} [get] 82 | // @Security AuthBearer 83 | func (h *CarModelCommentHandler) GetById(c *gin.Context) { 84 | GetById(c, dto.ToCarModelCommentResponse, h.usecase.GetById) 85 | } 86 | 87 | // GetCarModelComments godoc 88 | // @Summary Get CarModelComments 89 | // @Description Get CarModelComments 90 | // @Tags CarModelComments 91 | // @Accept json 92 | // @produces json 93 | // @Param Request body filter.PaginationInputWithFilter true "Request" 94 | // @Success 200 {object} helper.BaseHttpResponse{result=filter.PagedList[dto.CarModelCommentResponse]} "CarModelComment response" 95 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 96 | // @Router /v1/car-model-comments/get-by-filter [post] 97 | // @Security AuthBearer 98 | func (h *CarModelCommentHandler) GetByFilter(c *gin.Context) { 99 | GetByFilter(c, dto.ToCarModelCommentResponse, h.usecase.GetByFilter) 100 | } 101 | -------------------------------------------------------------------------------- /src/api/handler/car_model_image.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/dto" 6 | _ "github.com/omidhaqi/clean-web-api/api/helper" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | "github.com/omidhaqi/clean-web-api/dependency" 9 | _ "github.com/omidhaqi/clean-web-api/domain/filter" 10 | "github.com/omidhaqi/clean-web-api/usecase" 11 | ) 12 | 13 | type CarModelImageHandler struct { 14 | usecase *usecase.CarModelImageUsecase 15 | } 16 | 17 | func NewCarModelImageHandler(cfg *config.Config) *CarModelImageHandler { 18 | return &CarModelImageHandler{ 19 | usecase: usecase.NewCarModelImageUsecase(cfg, dependency.GetCarModelImageRepository(cfg)), 20 | } 21 | } 22 | 23 | // CreateCarModelImage godoc 24 | // @Summary Create a CarModelImage 25 | // @Description Create a CarModelImage 26 | // @Tags CarModelImages 27 | // @Accept json 28 | // @produces json 29 | // @Param Request body dto.CreateCarModelImageRequest true "Create a CarModelImage" 30 | // @Success 201 {object} helper.BaseHttpResponse{result=dto.CarModelImageResponse} "CarModelImage response" 31 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 32 | // @Router /v1/car-model-images/ [post] 33 | // @Security AuthBearer 34 | func (h *CarModelImageHandler) Create(c *gin.Context) { 35 | Create(c, dto.ToCreateCarModelImage, dto.ToCarModelImageResponse, h.usecase.Create) 36 | } 37 | 38 | // UpdateCarModelImage godoc 39 | // @Summary Update a CarModelImage 40 | // @Description Update a CarModelImage 41 | // @Tags CarModelImages 42 | // @Accept json 43 | // @produces json 44 | // @Param id path int true "Id" 45 | // @Param Request body dto.UpdateCarModelImageRequest true "Update a CarModelImage" 46 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CarModelImageResponse} "CarModelImage response" 47 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 48 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 49 | // @Router /v1/car-model-images/{id} [put] 50 | // @Security AuthBearer 51 | func (h *CarModelImageHandler) Update(c *gin.Context) { 52 | Update(c, dto.ToUpdateCarModelImage, dto.ToCarModelImageResponse, h.usecase.Update) 53 | } 54 | 55 | // DeleteCarModelImage godoc 56 | // @Summary Delete a CarModelImage 57 | // @Description Delete a CarModelImage 58 | // @Tags CarModelImages 59 | // @Accept json 60 | // @produces json 61 | // @Param id path int true "Id" 62 | // @Success 200 {object} helper.BaseHttpResponse "response" 63 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 64 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 65 | // @Router /v1/car-model-images/{id} [delete] 66 | // @Security AuthBearer 67 | func (h *CarModelImageHandler) Delete(c *gin.Context) { 68 | Delete(c, h.usecase.Delete) 69 | } 70 | 71 | // GetCarModelImage godoc 72 | // @Summary Get a CarModelImage 73 | // @Description Get a CarModelImage 74 | // @Tags CarModelImages 75 | // @Accept json 76 | // @produces json 77 | // @Param id path int true "Id" 78 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CarModelImageResponse} "CarModelImage response" 79 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 80 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 81 | // @Router /v1/car-model-images/{id} [get] 82 | // @Security AuthBearer 83 | func (h *CarModelImageHandler) GetById(c *gin.Context) { 84 | GetById(c, dto.ToCarModelImageResponse, h.usecase.GetById) 85 | } 86 | 87 | // GetCarModelImages godoc 88 | // @Summary Get CarModelImages 89 | // @Description Get CarModelImages 90 | // @Tags CarModelImages 91 | // @Accept json 92 | // @produces json 93 | // @Param Request body filter.PaginationInputWithFilter true "Request" 94 | // @Success 200 {object} helper.BaseHttpResponse{result=filter.PagedList[dto.CarModelImageResponse]} "CarModelImage response" 95 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 96 | // @Router /v1/car-model-images/get-by-filter [post] 97 | // @Security AuthBearer 98 | func (h *CarModelImageHandler) GetByFilter(c *gin.Context) { 99 | GetByFilter(c, dto.ToCarModelImageResponse, h.usecase.GetByFilter) 100 | } 101 | -------------------------------------------------------------------------------- /src/api/handler/car_model_property.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/dto" 6 | _ "github.com/omidhaqi/clean-web-api/api/helper" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | "github.com/omidhaqi/clean-web-api/dependency" 9 | _ "github.com/omidhaqi/clean-web-api/domain/filter" 10 | "github.com/omidhaqi/clean-web-api/usecase" 11 | ) 12 | 13 | type CarModelPropertyHandler struct { 14 | usecase *usecase.CarModelPropertyUsecase 15 | } 16 | 17 | func NewCarModelPropertyHandler(cfg *config.Config) *CarModelPropertyHandler { 18 | return &CarModelPropertyHandler{ 19 | usecase: usecase.NewCarModelPropertyUsecase(cfg, dependency.GetCarModelPropertyRepository(cfg)), 20 | } 21 | } 22 | 23 | // CreateCarModelProperty godoc 24 | // @Summary Create a CarModelProperty 25 | // @Description Create a CarModelProperty 26 | // @Tags CarModelProperties 27 | // @Accept json 28 | // @produces json 29 | // @Param Request body dto.CreateCarModelPropertyRequest true "Create a CarModelProperty" 30 | // @Success 201 {object} helper.BaseHttpResponse{result=dto.CarModelPropertyResponse} "CarModelProperty response" 31 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 32 | // @Router /v1/car-model-properties/ [post] 33 | // @Security AuthBearer 34 | func (h *CarModelPropertyHandler) Create(c *gin.Context) { 35 | Create(c, dto.ToCreateCarModelProperty, dto.ToCarModelPropertyResponse, h.usecase.Create) 36 | } 37 | 38 | // UpdateCarModelProperty godoc 39 | // @Summary Update a CarModelProperty 40 | // @Description Update a CarModelProperty 41 | // @Tags CarModelProperties 42 | // @Accept json 43 | // @produces json 44 | // @Param id path int true "Id" 45 | // @Param Request body dto.UpdateCarModelPropertyRequest true "Update a CarModelProperty" 46 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CarModelPropertyResponse} "CarModelProperty response" 47 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 48 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 49 | // @Router /v1/car-model-properties/{id} [put] 50 | // @Security AuthBearer 51 | func (h *CarModelPropertyHandler) Update(c *gin.Context) { 52 | Update(c, dto.ToUpdateCarModelProperty, dto.ToCarModelPropertyResponse, h.usecase.Update) 53 | } 54 | 55 | // DeleteCarModelProperty godoc 56 | // @Summary Delete a CarModelProperty 57 | // @Description Delete a CarModelProperty 58 | // @Tags CarModelProperties 59 | // @Accept json 60 | // @produces json 61 | // @Param id path int true "Id" 62 | // @Success 200 {object} helper.BaseHttpResponse "response" 63 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 64 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 65 | // @Router /v1/car-model-properties/{id} [delete] 66 | // @Security AuthBearer 67 | func (h *CarModelPropertyHandler) Delete(c *gin.Context) { 68 | Delete(c, h.usecase.Delete) 69 | } 70 | 71 | // GetCarModelProperty godoc 72 | // @Summary Get a CarModelProperty 73 | // @Description Get a CarModelProperty 74 | // @Tags CarModelProperties 75 | // @Accept json 76 | // @produces json 77 | // @Param id path int true "Id" 78 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CarModelPropertyResponse} "CarModelProperty response" 79 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 80 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 81 | // @Router /v1/car-model-properties/{id} [get] 82 | // @Security AuthBearer 83 | func (h *CarModelPropertyHandler) GetById(c *gin.Context) { 84 | GetById(c, dto.ToCarModelPropertyResponse, h.usecase.GetById) 85 | } 86 | 87 | // GetCarModelProperties godoc 88 | // @Summary Get CarModelProperties 89 | // @Description Get CarModelProperties 90 | // @Tags CarModelProperties 91 | // @Accept json 92 | // @produces json 93 | // @Param Request body filter.PaginationInputWithFilter true "Request" 94 | // @Success 200 {object} helper.BaseHttpResponse{result=filter.PagedList[dto.CarModelPropertyResponse]} "CarModelProperty response" 95 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 96 | // @Router /v1/car-model-properties/get-by-filter [post] 97 | // @Security AuthBearer 98 | func (h *CarModelPropertyHandler) GetByFilter(c *gin.Context) { 99 | GetByFilter(c, dto.ToCarModelPropertyResponse, h.usecase.GetByFilter) 100 | } 101 | -------------------------------------------------------------------------------- /src/api/handler/car_model_year.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/dto" 6 | _ "github.com/omidhaqi/clean-web-api/api/helper" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | "github.com/omidhaqi/clean-web-api/dependency" 9 | _ "github.com/omidhaqi/clean-web-api/domain/filter" 10 | "github.com/omidhaqi/clean-web-api/usecase" 11 | ) 12 | 13 | type CarModelYearHandler struct { 14 | usecase *usecase.CarModelYearUsecase 15 | } 16 | 17 | func NewCarModelYearHandler(cfg *config.Config) *CarModelYearHandler { 18 | return &CarModelYearHandler{ 19 | usecase: usecase.NewCarModelYearUsecase(cfg, dependency.GetCarModelYearRepository(cfg)), 20 | } 21 | } 22 | 23 | // CreateCarModelYear godoc 24 | // @Summary Create a CarModelYear 25 | // @Description Create a CarModelYear 26 | // @Tags CarModelYears 27 | // @Accept json 28 | // @produces json 29 | // @Param Request body dto.CreateCarModelYearRequest true "Create a CarModelYear" 30 | // @Success 201 {object} helper.BaseHttpResponse{result=dto.CarModelYearResponse} "CarModelYear response" 31 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 32 | // @Router /v1/car-model-years/ [post] 33 | // @Security AuthBearer 34 | func (h *CarModelYearHandler) Create(c *gin.Context) { 35 | Create(c, dto.ToCreateCarModelYear, dto.ToCarModelYearResponse, h.usecase.Create) 36 | } 37 | 38 | // UpdateCarModelYear godoc 39 | // @Summary Update a CarModelYear 40 | // @Description Update a CarModelYear 41 | // @Tags CarModelYears 42 | // @Accept json 43 | // @produces json 44 | // @Param id path int true "Id" 45 | // @Param Request body dto.UpdateCarModelYearRequest true "Update a CarModelYear" 46 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CarModelYearResponse} "CarModelYear response" 47 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 48 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 49 | // @Router /v1/car-model-years/{id} [put] 50 | // @Security AuthBearer 51 | func (h *CarModelYearHandler) Update(c *gin.Context) { 52 | Update(c, dto.ToUpdateCarModelYear, dto.ToCarModelYearResponse, h.usecase.Update) 53 | } 54 | 55 | // DeleteCarModelYear godoc 56 | // @Summary Delete a CarModelYear 57 | // @Description Delete a CarModelYear 58 | // @Tags CarModelYears 59 | // @Accept json 60 | // @produces json 61 | // @Param id path int true "Id" 62 | // @Success 200 {object} helper.BaseHttpResponse "response" 63 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 64 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 65 | // @Router /v1/car-model-years/{id} [delete] 66 | // @Security AuthBearer 67 | func (h *CarModelYearHandler) Delete(c *gin.Context) { 68 | Delete(c, h.usecase.Delete) 69 | } 70 | 71 | // GetCarModelYear godoc 72 | // @Summary Get a CarModelYear 73 | // @Description Get a CarModelYear 74 | // @Tags CarModelYears 75 | // @Accept json 76 | // @produces json 77 | // @Param id path int true "Id" 78 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CarModelYearResponse} "CarModelYear response" 79 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 80 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 81 | // @Router /v1/car-model-years/{id} [get] 82 | // @Security AuthBearer 83 | func (h *CarModelYearHandler) GetById(c *gin.Context) { 84 | GetById(c, dto.ToCarModelYearResponse, h.usecase.GetById) 85 | } 86 | 87 | // GetCarModelYears godoc 88 | // @Summary Get CarModelYears 89 | // @Description Get CarModelYears 90 | // @Tags CarModelYears 91 | // @Accept json 92 | // @produces json 93 | // @Param Request body filter.PaginationInputWithFilter true "Request" 94 | // @Success 200 {object} helper.BaseHttpResponse{result=filter.PagedList[dto.CarModelYearResponse]} "CarModelYear response" 95 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 96 | // @Router /v1/car-model-years/get-by-filter [post] 97 | // @Security AuthBearer 98 | func (h *CarModelYearHandler) GetByFilter(c *gin.Context) { 99 | GetByFilter(c, dto.ToCarModelYearResponse, h.usecase.GetByFilter) 100 | } 101 | -------------------------------------------------------------------------------- /src/api/handler/car_type.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/dto" 6 | _ "github.com/omidhaqi/clean-web-api/api/helper" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | "github.com/omidhaqi/clean-web-api/dependency" 9 | _ "github.com/omidhaqi/clean-web-api/domain/filter" 10 | "github.com/omidhaqi/clean-web-api/usecase" 11 | ) 12 | 13 | type CarTypeHandler struct { 14 | usecase *usecase.CarTypeUsecase 15 | } 16 | 17 | func NewCarTypeHandler(cfg *config.Config) *CarTypeHandler { 18 | return &CarTypeHandler{ 19 | usecase: usecase.NewCarTypeUsecase(cfg, dependency.GetCarTypeRepository(cfg)), 20 | } 21 | } 22 | 23 | // CreateCarType godoc 24 | // @Summary Create a CarType 25 | // @Description Create a CarType 26 | // @Tags CarTypes 27 | // @Accept json 28 | // @produces json 29 | // @Param Request body dto.CreateCarTypeRequest true "Create a CarType" 30 | // @Success 201 {object} helper.BaseHttpResponse{result=dto.CarTypeResponse} "CarType response" 31 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 32 | // @Router /v1/car-types/ [post] 33 | // @Security AuthBearer 34 | func (h *CarTypeHandler) Create(c *gin.Context) { 35 | Create(c, dto.ToCreateCarType, dto.ToCarTypeResponse, h.usecase.Create) 36 | } 37 | 38 | // UpdateCarType godoc 39 | // @Summary Update a CarType 40 | // @Description Update a CarType 41 | // @Tags CarTypes 42 | // @Accept json 43 | // @produces json 44 | // @Param id path int true "Id" 45 | // @Param Request body dto.UpdateCarTypeRequest true "Update a CarType" 46 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CarTypeResponse} "CarType response" 47 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 48 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 49 | // @Router /v1/car-types/{id} [put] 50 | // @Security AuthBearer 51 | func (h *CarTypeHandler) Update(c *gin.Context) { 52 | Update(c, dto.ToUpdateCarType, dto.ToCarTypeResponse, h.usecase.Update) 53 | } 54 | 55 | // DeleteCarType godoc 56 | // @Summary Delete a CarType 57 | // @Description Delete a CarType 58 | // @Tags CarTypes 59 | // @Accept json 60 | // @produces json 61 | // @Param id path int true "Id" 62 | // @Success 200 {object} helper.BaseHttpResponse "response" 63 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 64 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 65 | // @Router /v1/car-types/{id} [delete] 66 | // @Security AuthBearer 67 | func (h *CarTypeHandler) Delete(c *gin.Context) { 68 | Delete(c, h.usecase.Delete) 69 | } 70 | 71 | // GetCarType godoc 72 | // @Summary Get a CarType 73 | // @Description Get a CarType 74 | // @Tags CarTypes 75 | // @Accept json 76 | // @produces json 77 | // @Param id path int true "Id" 78 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CarTypeResponse} "CarType response" 79 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 80 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 81 | // @Router /v1/car-types/{id} [get] 82 | // @Security AuthBearer 83 | func (h *CarTypeHandler) GetById(c *gin.Context) { 84 | GetById(c, dto.ToCarTypeResponse, h.usecase.GetById) 85 | } 86 | 87 | // GetCarTypes godoc 88 | // @Summary Get CarTypes 89 | // @Description Get CarTypes 90 | // @Tags CarTypes 91 | // @Accept json 92 | // @produces json 93 | // @Param Request body filter.PaginationInputWithFilter true "Request" 94 | // @Success 200 {object} helper.BaseHttpResponse{result=filter.PagedList[dto.CarTypeResponse]} "CarType response" 95 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 96 | // @Router /v1/car-types/get-by-filter [post] 97 | // @Security AuthBearer 98 | func (h *CarTypeHandler) GetByFilter(c *gin.Context) { 99 | GetByFilter(c, dto.ToCarTypeResponse, h.usecase.GetByFilter) 100 | } 101 | -------------------------------------------------------------------------------- /src/api/handler/city.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/dto" 6 | _ "github.com/omidhaqi/clean-web-api/api/helper" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | "github.com/omidhaqi/clean-web-api/dependency" 9 | _ "github.com/omidhaqi/clean-web-api/domain/filter" 10 | "github.com/omidhaqi/clean-web-api/usecase" 11 | ) 12 | 13 | type CityHandler struct { 14 | usecase *usecase.CityUsecase 15 | } 16 | 17 | func NewCityHandler(cfg *config.Config) *CityHandler { 18 | return &CityHandler{ 19 | usecase: usecase.NewCityUsecase(cfg, dependency.GetCityRepository(cfg)), 20 | } 21 | } 22 | 23 | // CreateCity godoc 24 | // @Summary Create a City 25 | // @Description Create a City 26 | // @Tags Cities 27 | // @Accept json 28 | // @produces json 29 | // @Param Request body dto.CreateCityRequest true "Create a City" 30 | // @Success 201 {object} helper.BaseHttpResponse{result=dto.CityResponse} "City response" 31 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 32 | // @Router /v1/cities/ [post] 33 | // @Security AuthBearer 34 | func (h *CityHandler) Create(c *gin.Context) { 35 | Create(c, dto.ToCreateCity, dto.ToCityResponse, h.usecase.Create) 36 | } 37 | 38 | // UpdateCity godoc 39 | // @Summary Update a City 40 | // @Description Update a City 41 | // @Tags Cities 42 | // @Accept json 43 | // @produces json 44 | // @Param id path int true "Id" 45 | // @Param Request body dto.UpdateCityRequest true "Update a City" 46 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CityResponse} "City response" 47 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 48 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 49 | // @Router /v1/cities/{id} [put] 50 | // @Security AuthBearer 51 | func (h *CityHandler) Update(c *gin.Context) { 52 | Update(c, dto.ToUpdateCity, dto.ToCityResponse, h.usecase.Update) 53 | } 54 | 55 | // DeleteCity godoc 56 | // @Summary Delete a City 57 | // @Description Delete a City 58 | // @Tags Cities 59 | // @Accept json 60 | // @produces json 61 | // @Param id path int true "Id" 62 | // @Success 200 {object} helper.BaseHttpResponse "response" 63 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 64 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 65 | // @Router /v1/cities/{id} [delete] 66 | // @Security AuthBearer 67 | func (h *CityHandler) Delete(c *gin.Context) { 68 | Delete(c, h.usecase.Delete) 69 | } 70 | 71 | // GetCity godoc 72 | // @Summary Get a City 73 | // @Description Get a City 74 | // @Tags Cities 75 | // @Accept json 76 | // @produces json 77 | // @Param id path int true "Id" 78 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CityResponse} "City response" 79 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 80 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 81 | // @Router /v1/cities/{id} [get] 82 | // @Security AuthBearer 83 | func (h *CityHandler) GetById(c *gin.Context) { 84 | GetById(c, dto.ToCityResponse, h.usecase.GetById) 85 | } 86 | 87 | // GetCities godoc 88 | // @Summary Get Cities 89 | // @Description Get Cities 90 | // @Tags Cities 91 | // @Accept json 92 | // @produces json 93 | // @Param Request body filter.PaginationInputWithFilter true "Request" 94 | // @Success 200 {object} helper.BaseHttpResponse{result=filter.PagedList[dto.CityResponse]} "City response" 95 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 96 | // @Router /v1/cities/get-by-filter [post] 97 | // @Security AuthBearer 98 | func (h *CityHandler) GetByFilter(c *gin.Context) { 99 | GetByFilter(c, dto.ToCityResponse, h.usecase.GetByFilter) 100 | } 101 | -------------------------------------------------------------------------------- /src/api/handler/color.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/dto" 6 | _ "github.com/omidhaqi/clean-web-api/api/helper" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | "github.com/omidhaqi/clean-web-api/dependency" 9 | _ "github.com/omidhaqi/clean-web-api/domain/filter" 10 | "github.com/omidhaqi/clean-web-api/usecase" 11 | ) 12 | 13 | type ColorHandler struct { 14 | usecase *usecase.ColorUsecase 15 | } 16 | 17 | func NewColorHandler(cfg *config.Config) *ColorHandler { 18 | return &ColorHandler{ 19 | usecase: usecase.NewColorUsecase(cfg, dependency.GetColorRepository(cfg)), 20 | } 21 | } 22 | 23 | // CreateColor godoc 24 | // @Summary Create a Color 25 | // @Description Create a Color 26 | // @Tags Colors 27 | // @Accept json 28 | // @produces json 29 | // @Param Request body dto.CreateColorRequest true "Create a Color" 30 | // @Success 201 {object} helper.BaseHttpResponse{result=dto.ColorResponse} "Color response" 31 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 32 | // @Router /v1/colors/ [post] 33 | // @Security AuthBearer 34 | func (h *ColorHandler) Create(c *gin.Context) { 35 | Create(c, dto.ToCreateColor, dto.ToColorResponse, h.usecase.Create) 36 | } 37 | 38 | // UpdateColor godoc 39 | // @Summary Update a Color 40 | // @Description Update a Color 41 | // @Tags Colors 42 | // @Accept json 43 | // @produces json 44 | // @Param id path int true "Id" 45 | // @Param Request body dto.UpdateColorRequest true "Update a Color" 46 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.ColorResponse} "Color response" 47 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 48 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 49 | // @Router /v1/colors/{id} [put] 50 | // @Security AuthBearer 51 | func (h *ColorHandler) Update(c *gin.Context) { 52 | Update(c, dto.ToUpdateColor, dto.ToColorResponse, h.usecase.Update) 53 | } 54 | 55 | // DeleteColor godoc 56 | // @Summary Delete a Color 57 | // @Description Delete a Color 58 | // @Tags Colors 59 | // @Accept json 60 | // @produces json 61 | // @Param id path int true "Id" 62 | // @Success 200 {object} helper.BaseHttpResponse "response" 63 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 64 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 65 | // @Router /v1/colors/{id} [delete] 66 | // @Security AuthBearer 67 | func (h *ColorHandler) Delete(c *gin.Context) { 68 | Delete(c, h.usecase.Delete) 69 | } 70 | 71 | // GetColor godoc 72 | // @Summary Get a Color 73 | // @Description Get a Color 74 | // @Tags Colors 75 | // @Accept json 76 | // @produces json 77 | // @Param id path int true "Id" 78 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.ColorResponse} "Color response" 79 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 80 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 81 | // @Router /v1/colors/{id} [get] 82 | // @Security AuthBearer 83 | func (h *ColorHandler) GetById(c *gin.Context) { 84 | GetById(c, dto.ToColorResponse, h.usecase.GetById) 85 | } 86 | 87 | // GetColors godoc 88 | // @Summary Get Colors 89 | // @Description Get Colors 90 | // @Tags Colors 91 | // @Accept json 92 | // @produces json 93 | // @Param Request body filter.PaginationInputWithFilter true "Request" 94 | // @Success 200 {object} helper.BaseHttpResponse{result=filter.PagedList[dto.ColorResponse]} "Color response" 95 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 96 | // @Router /v1/colors/get-by-filter [post] 97 | // @Security AuthBearer 98 | func (h *ColorHandler) GetByFilter(c *gin.Context) { 99 | GetByFilter(c, dto.ToColorResponse, h.usecase.GetByFilter) 100 | } 101 | -------------------------------------------------------------------------------- /src/api/handler/company.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/dto" 6 | _ "github.com/omidhaqi/clean-web-api/api/helper" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | "github.com/omidhaqi/clean-web-api/dependency" 9 | _ "github.com/omidhaqi/clean-web-api/domain/filter" 10 | "github.com/omidhaqi/clean-web-api/usecase" 11 | ) 12 | 13 | type CompanyHandler struct { 14 | usecase *usecase.CompanyUsecase 15 | } 16 | 17 | func NewCompanyHandler(cfg *config.Config) *CompanyHandler { 18 | return &CompanyHandler{ 19 | usecase: usecase.NewCompanyUsecase(cfg, dependency.GetCompanyRepository(cfg)), 20 | } 21 | } 22 | 23 | // CreateCompany godoc 24 | // @Summary Create a Company 25 | // @Description Create a Company 26 | // @Tags Companies 27 | // @Accept json 28 | // @produces json 29 | // @Param Request body dto.CreateCompanyRequest true "Create a Company" 30 | // @Success 201 {object} helper.BaseHttpResponse{result=dto.CompanyResponse} "Company response" 31 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 32 | // @Router /v1/companies/ [post] 33 | // @Security AuthBearer 34 | func (h *CompanyHandler) Create(c *gin.Context) { 35 | Create(c, dto.ToCreateCompany, dto.ToCompanyResponse, h.usecase.Create) 36 | } 37 | 38 | // UpdateCompany godoc 39 | // @Summary Update a Company 40 | // @Description Update a Company 41 | // @Tags Companies 42 | // @Accept json 43 | // @produces json 44 | // @Param id path int true "Id" 45 | // @Param Request body dto.UpdateCompanyRequest true "Update a Company" 46 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CompanyResponse} "Company response" 47 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 48 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 49 | // @Router /v1/companies/{id} [put] 50 | // @Security AuthBearer 51 | func (h *CompanyHandler) Update(c *gin.Context) { 52 | Update(c, dto.ToUpdateCompany, dto.ToCompanyResponse, h.usecase.Update) 53 | } 54 | 55 | // DeleteCompany godoc 56 | // @Summary Delete a Company 57 | // @Description Delete a Company 58 | // @Tags Companies 59 | // @Accept json 60 | // @produces json 61 | // @Param id path int true "Id" 62 | // @Success 200 {object} helper.BaseHttpResponse "response" 63 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 64 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 65 | // @Router /v1/companies/{id} [delete] 66 | // @Security AuthBearer 67 | func (h *CompanyHandler) Delete(c *gin.Context) { 68 | Delete(c, h.usecase.Delete) 69 | } 70 | 71 | // GetCompany godoc 72 | // @Summary Get a Company 73 | // @Description Get a Company 74 | // @Tags Companies 75 | // @Accept json 76 | // @produces json 77 | // @Param id path int true "Id" 78 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CompanyResponse} "Company response" 79 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 80 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 81 | // @Router /v1/companies/{id} [get] 82 | // @Security AuthBearer 83 | func (h *CompanyHandler) GetById(c *gin.Context) { 84 | GetById(c, dto.ToCompanyResponse, h.usecase.GetById) 85 | } 86 | 87 | // GetCompanies godoc 88 | // @Summary Get Companies 89 | // @Description Get Companies 90 | // @Tags Companies 91 | // @Accept json 92 | // @produces json 93 | // @Param Request body filter.PaginationInputWithFilter true "Request" 94 | // @Success 200 {object} helper.BaseHttpResponse{result=filter.PagedList[dto.CompanyResponse]} "Company response" 95 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 96 | // @Router /v1/companies/get-by-filter [post] 97 | // @Security AuthBearer 98 | func (h *CompanyHandler) GetByFilter(c *gin.Context) { 99 | GetByFilter(c, dto.ToCompanyResponse, h.usecase.GetByFilter) 100 | } 101 | -------------------------------------------------------------------------------- /src/api/handler/country.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/dto" 6 | _ "github.com/omidhaqi/clean-web-api/api/helper" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | "github.com/omidhaqi/clean-web-api/dependency" 9 | _ "github.com/omidhaqi/clean-web-api/domain/filter" 10 | "github.com/omidhaqi/clean-web-api/usecase" 11 | ) 12 | 13 | type CountryHandler struct { 14 | usecase *usecase.CountryUsecase 15 | } 16 | 17 | func NewCountryHandler(cfg *config.Config) *CountryHandler { 18 | return &CountryHandler{ 19 | usecase: usecase.NewCountryUsecase(cfg, dependency.GetCountryRepository(cfg))} 20 | } 21 | 22 | // CreateCountry godoc 23 | // @Summary Create a country 24 | // @Description Create a country 25 | // @Tags Countries 26 | // @Accept json 27 | // @produces json 28 | // @Param Request body dto.CreateUpdateCountryRequest true "Create a country" 29 | // @Success 201 {object} helper.BaseHttpResponse{result=dto.CountryResponse} "Country response" 30 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 31 | // @Router /v1/countries/ [post] 32 | // @Security AuthBearer 33 | func (h *CountryHandler) Create(c *gin.Context) { 34 | Create(c, dto.ToCreateUpdateCountry, dto.ToCountryResponse, h.usecase.Create) 35 | } 36 | 37 | // UpdateCountry godoc 38 | // @Summary Update a country 39 | // @Description Update a country 40 | // @Tags Countries 41 | // @Accept json 42 | // @produces json 43 | // @Param id path int true "Id" 44 | // @Param Request body dto.CreateUpdateCountryRequest true "Update a country" 45 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CountryResponse} "Country response" 46 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 47 | // @Router /v1/countries/{id} [put] 48 | // @Security AuthBearer 49 | func (h *CountryHandler) Update(c *gin.Context) { 50 | Update(c, dto.ToCreateUpdateCountry, dto.ToCountryResponse, h.usecase.Update) 51 | } 52 | 53 | // DeleteCountry godoc 54 | // @Summary Delete a country 55 | // @Description Delete a country 56 | // @Tags Countries 57 | // @Accept json 58 | // @produces json 59 | // @Param id path int true "Id" 60 | // @Success 200 {object} helper.BaseHttpResponse "response" 61 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 62 | // @Router /v1/countries/{id} [delete] 63 | // @Security AuthBearer 64 | func (h *CountryHandler) Delete(c *gin.Context) { 65 | Delete(c, h.usecase.Delete) 66 | } 67 | 68 | // GetCountry godoc 69 | // @Summary Get a country 70 | // @Description Get a country 71 | // @Tags Countries 72 | // @Accept json 73 | // @produces json 74 | // @Param id path int true "Id" 75 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.CountryResponse} "Country response" 76 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 77 | // @Router /v1/countries/{id} [get] 78 | // @Security AuthBearer 79 | func (h *CountryHandler) GetById(c *gin.Context) { 80 | GetById(c, dto.ToCountryResponse, h.usecase.GetById) 81 | } 82 | 83 | // GetCountries godoc 84 | // @Summary Get Countries 85 | // @Description Get Countries 86 | // @Tags Countries 87 | // @Accept json 88 | // @produces json 89 | // @Param Request body filter.PaginationInputWithFilter true "Request" 90 | // @Success 200 {object} helper.BaseHttpResponse{result=filter.PagedList[dto.CountryResponse]} "Country response" 91 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 92 | // @Router /v1/countries/get-by-filter [post] 93 | // @Security AuthBearer 94 | func (h *CountryHandler) GetByFilter(c *gin.Context) { 95 | GetByFilter(c, dto.ToCountryResponse, h.usecase.GetByFilter) 96 | } 97 | -------------------------------------------------------------------------------- /src/api/handler/gearbox.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/dto" 6 | _ "github.com/omidhaqi/clean-web-api/api/helper" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | "github.com/omidhaqi/clean-web-api/dependency" 9 | _ "github.com/omidhaqi/clean-web-api/domain/filter" 10 | "github.com/omidhaqi/clean-web-api/usecase" 11 | ) 12 | 13 | type GearboxHandler struct { 14 | usecase *usecase.GearboxUsecase 15 | } 16 | 17 | func NewGearboxHandler(cfg *config.Config) *GearboxHandler { 18 | return &GearboxHandler{ 19 | usecase: usecase.NewGearboxUsecase(cfg, dependency.GetGearboxRepository(cfg)), 20 | } 21 | } 22 | 23 | // CreateGearbox godoc 24 | // @Summary Create a Gearbox 25 | // @Description Create a Gearbox 26 | // @Tags Gearboxes 27 | // @Accept json 28 | // @produces json 29 | // @Param Request body dto.CreateGearboxRequest true "Create a Gearbox" 30 | // @Success 201 {object} helper.BaseHttpResponse{result=dto.GearboxResponse} "Gearbox response" 31 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 32 | // @Router /v1/gearboxes/ [post] 33 | // @Security AuthBearer 34 | func (h *GearboxHandler) Create(c *gin.Context) { 35 | Create(c, dto.ToCreateGearbox, dto.ToGearboxResponse, h.usecase.Create) 36 | } 37 | 38 | // UpdateGearbox godoc 39 | // @Summary Update a Gearbox 40 | // @Description Update a Gearbox 41 | // @Tags Gearboxes 42 | // @Accept json 43 | // @produces json 44 | // @Param id path int true "Id" 45 | // @Param Request body dto.UpdateGearboxRequest true "Update a Gearbox" 46 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.GearboxResponse} "Gearbox response" 47 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 48 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 49 | // @Router /v1/gearboxes/{id} [put] 50 | // @Security AuthBearer 51 | func (h *GearboxHandler) Update(c *gin.Context) { 52 | Update(c, dto.ToUpdateGearbox, dto.ToGearboxResponse, h.usecase.Update) 53 | } 54 | 55 | // DeleteGearbox godoc 56 | // @Summary Delete a Gearbox 57 | // @Description Delete a Gearbox 58 | // @Tags Gearboxes 59 | // @Accept json 60 | // @produces json 61 | // @Param id path int true "Id" 62 | // @Success 200 {object} helper.BaseHttpResponse "response" 63 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 64 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 65 | // @Router /v1/gearboxes/{id} [delete] 66 | // @Security AuthBearer 67 | func (h *GearboxHandler) Delete(c *gin.Context) { 68 | Delete(c, h.usecase.Delete) 69 | } 70 | 71 | // GetGearbox godoc 72 | // @Summary Get a Gearbox 73 | // @Description Get a Gearbox 74 | // @Tags Gearboxes 75 | // @Accept json 76 | // @produces json 77 | // @Param id path int true "Id" 78 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.GearboxResponse} "Gearbox response" 79 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 80 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 81 | // @Router /v1/gearboxes/{id} [get] 82 | // @Security AuthBearer 83 | func (h *GearboxHandler) GetById(c *gin.Context) { 84 | GetById(c, dto.ToGearboxResponse, h.usecase.GetById) 85 | } 86 | 87 | // GetGearboxes godoc 88 | // @Summary Get Gearboxes 89 | // @Description Get Gearboxes 90 | // @Tags Gearboxes 91 | // @Accept json 92 | // @produces json 93 | // @Param Request body filter.PaginationInputWithFilter true "Request" 94 | // @Success 200 {object} helper.BaseHttpResponse{result=filter.PagedList[dto.GearboxResponse]} "Gearbox response" 95 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 96 | // @Router /v1/gearboxes/get-by-filter [post] 97 | // @Security AuthBearer 98 | func (h *GearboxHandler) GetByFilter(c *gin.Context) { 99 | GetByFilter(c, dto.ToGearboxResponse, h.usecase.GetByFilter) 100 | } 101 | -------------------------------------------------------------------------------- /src/api/handler/health.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/omidhaqi/clean-web-api/api/helper" 8 | ) 9 | 10 | type HealthHandler struct { 11 | } 12 | 13 | func NewHealthHandler() *HealthHandler { 14 | return &HealthHandler{} 15 | } 16 | 17 | // HealthCheck godoc 18 | // @Summary Health Check 19 | // @Description Health Check 20 | // @Tags health 21 | // @Accept json 22 | // @Produce json 23 | // @Success 200 {object} helper.BaseHttpResponse "Success" 24 | // @Failure 400 {object} helper.BaseHttpResponse "Failed" 25 | // @Router /v1/health/ [get] 26 | func (h *HealthHandler) Health(c *gin.Context) { 27 | c.JSON(http.StatusOK, helper.GenerateBaseResponse("Working!", true, 0)) 28 | } 29 | -------------------------------------------------------------------------------- /src/api/handler/property.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/dto" 6 | _ "github.com/omidhaqi/clean-web-api/api/helper" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | "github.com/omidhaqi/clean-web-api/dependency" 9 | _ "github.com/omidhaqi/clean-web-api/domain/filter" 10 | "github.com/omidhaqi/clean-web-api/usecase" 11 | ) 12 | 13 | type PropertyHandler struct { 14 | usecase *usecase.PropertyUsecase 15 | } 16 | 17 | func NewPropertyHandler(cfg *config.Config) *PropertyHandler { 18 | return &PropertyHandler{ 19 | usecase: usecase.NewPropertyUsecase(cfg, dependency.GetPropertyRepository(cfg)), 20 | } 21 | } 22 | 23 | // CreateProperty godoc 24 | // @Summary Create a Property 25 | // @Description Create a Property 26 | // @Tags Properties 27 | // @Accept json 28 | // @produces json 29 | // @Param Request body dto.CreatePropertyRequest true "Create a Property" 30 | // @Success 201 {object} helper.BaseHttpResponse{result=dto.PropertyResponse} "Property response" 31 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 32 | // @Router /v1/properties/ [post] 33 | // @Security AuthBearer 34 | func (h *PropertyHandler) Create(c *gin.Context) { 35 | Create(c, dto.ToCreateProperty, dto.ToPropertyResponse, h.usecase.Create) 36 | } 37 | 38 | // UpdateProperty godoc 39 | // @Summary Update a Property 40 | // @Description Update a Property 41 | // @Tags Properties 42 | // @Accept json 43 | // @produces json 44 | // @Param id path int true "Id" 45 | // @Param Request body dto.UpdatePropertyRequest true "Update a Property" 46 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.PropertyResponse} "Property response" 47 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 48 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 49 | // @Router /v1/properties/{id} [put] 50 | // @Security AuthBearer 51 | func (h *PropertyHandler) Update(c *gin.Context) { 52 | Update(c, dto.ToUpdateProperty, dto.ToPropertyResponse, h.usecase.Update) 53 | } 54 | 55 | // DeleteProperty godoc 56 | // @Summary Delete a Property 57 | // @Description Delete a Property 58 | // @Tags Properties 59 | // @Accept json 60 | // @produces json 61 | // @Param id path int true "Id" 62 | // @Success 200 {object} helper.BaseHttpResponse "response" 63 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 64 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 65 | // @Router /v1/properties/{id} [delete] 66 | // @Security AuthBearer 67 | func (h *PropertyHandler) Delete(c *gin.Context) { 68 | Delete(c, h.usecase.Delete) 69 | } 70 | 71 | // GetProperty godoc 72 | // @Summary Get a Property 73 | // @Description Get a Property 74 | // @Tags Properties 75 | // @Accept json 76 | // @produces json 77 | // @Param id path int true "Id" 78 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.PropertyResponse} "Property response" 79 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 80 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 81 | // @Router /v1/properties/{id} [get] 82 | // @Security AuthBearer 83 | func (h *PropertyHandler) GetById(c *gin.Context) { 84 | GetById(c, dto.ToPropertyResponse, h.usecase.GetById) 85 | } 86 | 87 | // GetProperties godoc 88 | // @Summary Get Properties 89 | // @Description Get Properties 90 | // @Tags Properties 91 | // @Accept json 92 | // @produces json 93 | // @Param Request body filter.PaginationInputWithFilter true "Request" 94 | // @Success 200 {object} helper.BaseHttpResponse{result=filter.PagedList[dto.PropertyResponse]} "Property response" 95 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 96 | // @Router /v1/properties/get-by-filter [post] 97 | // @Security AuthBearer 98 | func (h *PropertyHandler) GetByFilter(c *gin.Context) { 99 | GetByFilter(c, dto.ToPropertyResponse, h.usecase.GetByFilter) 100 | } 101 | -------------------------------------------------------------------------------- /src/api/handler/property_category.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/dto" 6 | _ "github.com/omidhaqi/clean-web-api/api/helper" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | "github.com/omidhaqi/clean-web-api/dependency" 9 | _ "github.com/omidhaqi/clean-web-api/domain/filter" 10 | "github.com/omidhaqi/clean-web-api/usecase" 11 | ) 12 | 13 | type PropertyCategoryHandler struct { 14 | usecase *usecase.PropertyCategoryUsecase 15 | } 16 | 17 | func NewPropertyCategoryHandler(cfg *config.Config) *PropertyCategoryHandler { 18 | return &PropertyCategoryHandler{ 19 | usecase: usecase.NewPropertyCategoryUsecase(cfg, dependency.GetPropertyCategoryRepository(cfg)), 20 | } 21 | } 22 | 23 | // CreatePropertyCategory godoc 24 | // @Summary Create a PropertyCategory 25 | // @Description Create a PropertyCategory 26 | // @Tags PropertyCategories 27 | // @Accept json 28 | // @produces json 29 | // @Param Request body dto.CreatePropertyCategoryRequest true "Create a PropertyCategory" 30 | // @Success 201 {object} helper.BaseHttpResponse{result=dto.PropertyCategoryResponse} "PropertyCategory response" 31 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 32 | // @Router /v1/property-categories/ [post] 33 | // @Security AuthBearer 34 | func (h *PropertyCategoryHandler) Create(c *gin.Context) { 35 | Create(c, dto.ToCreatePropertyCategory, dto.ToPropertyCategoryResponse, h.usecase.Create) 36 | } 37 | 38 | // UpdatePropertyCategory godoc 39 | // @Summary Update a PropertyCategory 40 | // @Description Update a PropertyCategory 41 | // @Tags PropertyCategories 42 | // @Accept json 43 | // @produces json 44 | // @Param id path int true "Id" 45 | // @Param Request body dto.UpdatePropertyCategoryRequest true "Update a PropertyCategory" 46 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.PropertyCategoryResponse} "PropertyCategory response" 47 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 48 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 49 | // @Router /v1/property-categories/{id} [put] 50 | // @Security AuthBearer 51 | func (h *PropertyCategoryHandler) Update(c *gin.Context) { 52 | Update(c, dto.ToUpdatePropertyCategory, dto.ToPropertyCategoryResponse, h.usecase.Update) 53 | } 54 | 55 | // DeletePropertyCategory godoc 56 | // @Summary Delete a PropertyCategory 57 | // @Description Delete a PropertyCategory 58 | // @Tags PropertyCategories 59 | // @Accept json 60 | // @produces json 61 | // @Param id path int true "Id" 62 | // @Success 200 {object} helper.BaseHttpResponse "response" 63 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 64 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 65 | // @Router /v1/property-categories/{id} [delete] 66 | // @Security AuthBearer 67 | func (h *PropertyCategoryHandler) Delete(c *gin.Context) { 68 | Delete(c, h.usecase.Delete) 69 | } 70 | 71 | // GetPropertyCategory godoc 72 | // @Summary Get a PropertyCategory 73 | // @Description Get a PropertyCategory 74 | // @Tags PropertyCategories 75 | // @Accept json 76 | // @produces json 77 | // @Param id path int true "Id" 78 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.PropertyCategoryResponse} "PropertyCategory response" 79 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 80 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 81 | // @Router /v1/property-categories/{id} [get] 82 | // @Security AuthBearer 83 | func (h *PropertyCategoryHandler) GetById(c *gin.Context) { 84 | GetById(c, dto.ToPropertyCategoryResponse, h.usecase.GetById) 85 | } 86 | 87 | // GetPropertyCategories godoc 88 | // @Summary Get PropertyCategories 89 | // @Description Get PropertyCategories 90 | // @Tags PropertyCategories 91 | // @Accept json 92 | // @produces json 93 | // @Param Request body filter.PaginationInputWithFilter true "Request" 94 | // @Success 200 {object} helper.BaseHttpResponse{result=filter.PagedList[dto.PropertyCategoryResponse]} "PropertyCategory response" 95 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 96 | // @Router /v1/property-categories/get-by-filter [post] 97 | // @Security AuthBearer 98 | func (h *PropertyCategoryHandler) GetByFilter(c *gin.Context) { 99 | GetByFilter(c, dto.ToPropertyCategoryResponse, h.usecase.GetByFilter) 100 | } 101 | -------------------------------------------------------------------------------- /src/api/handler/year.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/dto" 6 | _ "github.com/omidhaqi/clean-web-api/api/helper" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | "github.com/omidhaqi/clean-web-api/dependency" 9 | _ "github.com/omidhaqi/clean-web-api/domain/filter" 10 | "github.com/omidhaqi/clean-web-api/usecase" 11 | ) 12 | 13 | type PersianYearHandler struct { 14 | usecase *usecase.PersianYearUsecase 15 | } 16 | 17 | func NewPersianYearHandler(cfg *config.Config) *PersianYearHandler { 18 | return &PersianYearHandler{ 19 | usecase: usecase.NewPersianYearUsecase(cfg, dependency.GetPersianYearRepository(cfg)), 20 | } 21 | } 22 | 23 | // CreatePersianYear godoc 24 | // @Summary Create a PersianYear 25 | // @Description Create a PersianYear 26 | // @Tags PersianYears 27 | // @Accept json 28 | // @produces json 29 | // @Param Request body dto.CreatePersianYearRequest true "Create a PersianYear" 30 | // @Success 201 {object} helper.BaseHttpResponse{result=dto.PersianYearResponse} "PersianYear response" 31 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 32 | // @Router /v1/years/ [post] 33 | // @Security AuthBearer 34 | func (h *PersianYearHandler) Create(c *gin.Context) { 35 | 36 | Create(c, dto.ToCreatePersianYear, dto.ToPersianYearResponse, h.usecase.Create) 37 | } 38 | 39 | // UpdatePersianYear godoc 40 | // @Summary Update a PersianYear 41 | // @Description Update a PersianYear 42 | // @Tags PersianYears 43 | // @Accept json 44 | // @produces json 45 | // @Param id path int true "Id" 46 | // @Param Request body dto.UpdatePersianYearRequest true "Update a PersianYear" 47 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.PersianYearResponse} "PersianYear response" 48 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 49 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 50 | // @Router /v1/years/{id} [put] 51 | // @Security AuthBearer 52 | func (h *PersianYearHandler) Update(c *gin.Context) { 53 | Update(c, dto.ToUpdatePersianYear, dto.ToPersianYearResponse, h.usecase.Update) 54 | } 55 | 56 | // DeletePersianYear godoc 57 | // @Summary Delete a PersianYear 58 | // @Description Delete a PersianYear 59 | // @Tags PersianYears 60 | // @Accept json 61 | // @produces json 62 | // @Param id path int true "Id" 63 | // @Success 200 {object} helper.BaseHttpResponse "response" 64 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 65 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 66 | // @Router /v1/years/{id} [delete] 67 | // @Security AuthBearer 68 | func (h *PersianYearHandler) Delete(c *gin.Context) { 69 | Delete(c, h.usecase.Delete) 70 | } 71 | 72 | // GetPersianYear godoc 73 | // @Summary Get a PersianYear 74 | // @Description Get a PersianYear 75 | // @Tags PersianYears 76 | // @Accept json 77 | // @produces json 78 | // @Param id path int true "Id" 79 | // @Success 200 {object} helper.BaseHttpResponse{result=dto.PersianYearResponse} "PersianYear response" 80 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 81 | // @Failure 404 {object} helper.BaseHttpResponse "Not found" 82 | // @Router /v1/years/{id} [get] 83 | // @Security AuthBearer 84 | func (h *PersianYearHandler) GetById(c *gin.Context) { 85 | GetById(c, dto.ToPersianYearResponse, h.usecase.GetById) 86 | } 87 | 88 | // GetPersianYears godoc 89 | // @Summary Get PersianYears 90 | // @Description Get PersianYears 91 | // @Tags PersianYears 92 | // @Accept json 93 | // @produces json 94 | // @Param Request body filter.PaginationInputWithFilter true "Request" 95 | // @Success 200 {object} helper.BaseHttpResponse{result=filter.PagedList[dto.PersianYearResponse]} "PersianYear response" 96 | // @Failure 400 {object} helper.BaseHttpResponse "Bad request" 97 | // @Router /v1/years/get-by-filter [post] 98 | // @Security AuthBearer 99 | func (h *PersianYearHandler) GetByFilter(c *gin.Context) { 100 | 101 | GetByFilter(c, dto.ToPersianYearResponse, h.usecase.GetByFilter) 102 | } 103 | -------------------------------------------------------------------------------- /src/api/helper/base_response.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import validation "github.com/omidhaqi/clean-web-api/api/validation" 4 | 5 | type BaseHttpResponse struct { 6 | Result any `json:"result"` 7 | Success bool `json:"success"` 8 | ResultCode ResultCode `json:"resultCode"` 9 | ValidationErrors *[]validation.ValidationError `json:"validationErrors"` 10 | Error any `json:"error"` 11 | } 12 | 13 | func GenerateBaseResponse(result any, success bool, resultCode ResultCode) *BaseHttpResponse { 14 | return &BaseHttpResponse{Result: result, 15 | Success: success, 16 | ResultCode: resultCode, 17 | } 18 | } 19 | 20 | func GenerateBaseResponseWithError(result any, success bool, resultCode ResultCode, err error) *BaseHttpResponse { 21 | return &BaseHttpResponse{Result: result, 22 | Success: success, 23 | ResultCode: resultCode, 24 | Error: err.Error(), 25 | } 26 | 27 | } 28 | 29 | func GenerateBaseResponseWithAnyError(result any, success bool, resultCode ResultCode, err any) *BaseHttpResponse { 30 | return &BaseHttpResponse{Result: result, 31 | Success: success, 32 | ResultCode: resultCode, 33 | Error: err, 34 | } 35 | } 36 | 37 | func GenerateBaseResponseWithValidationError(result any, success bool, resultCode ResultCode, err error) *BaseHttpResponse { 38 | return &BaseHttpResponse{Result: result, 39 | Success: success, 40 | ResultCode: resultCode, 41 | ValidationErrors: validation.GetValidationErrors(err), 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/api/helper/result_code.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | type ResultCode int 4 | 5 | const ( 6 | Success ResultCode = 0 7 | ValidationError ResultCode = 40001 8 | AuthError ResultCode = 40101 9 | ForbiddenError ResultCode = 40301 10 | NotFoundError ResultCode = 40401 11 | LimiterError ResultCode = 42901 12 | OtpLimiterError ResultCode = 42902 13 | CustomRecovery ResultCode = 50001 14 | InternalError ResultCode = 50002 15 | ) 16 | -------------------------------------------------------------------------------- /src/api/helper/status_code_mapping.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/omidhaqi/clean-web-api/pkg/service_errors" 7 | ) 8 | 9 | var StatusCodeMapping = map[string]int{ 10 | 11 | // OTP 12 | service_errors.OptExists: 409, 13 | service_errors.OtpUsed: 409, 14 | service_errors.OtpNotValid: 400, 15 | 16 | // User 17 | service_errors.EmailExists: 409, 18 | service_errors.UsernameExists: 409, 19 | service_errors.RecordNotFound: 404, 20 | service_errors.PermissionDenied: 403, 21 | } 22 | 23 | func TranslateErrorToStatusCode(err error) int { 24 | value, ok := StatusCodeMapping[err.Error()] 25 | if !ok { 26 | return http.StatusInternalServerError 27 | } 28 | return value 29 | } 30 | -------------------------------------------------------------------------------- /src/api/middleware/auth.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/golang-jwt/jwt" 10 | "github.com/omidhaqi/clean-web-api/api/helper" 11 | "github.com/omidhaqi/clean-web-api/config" 12 | constant "github.com/omidhaqi/clean-web-api/constant" 13 | "github.com/omidhaqi/clean-web-api/pkg/service_errors" 14 | "github.com/omidhaqi/clean-web-api/usecase" 15 | ) 16 | 17 | func Authentication(cfg *config.Config) gin.HandlerFunc { 18 | var tokenUsecase = usecase.NewTokenUsecase(cfg) 19 | 20 | return func(c *gin.Context) { 21 | var err error 22 | claimMap := map[string]interface{}{} 23 | auth := c.GetHeader(constant.AuthorizationHeaderKey) 24 | token := strings.Split(auth, " ") 25 | if auth == "" || len(token) < 2 { 26 | err = &service_errors.ServiceError{EndUserMessage: service_errors.TokenRequired} 27 | } else { 28 | claimMap, err = tokenUsecase.GetClaims(token[1]) 29 | if err != nil { 30 | switch err.(*jwt.ValidationError).Errors { 31 | case jwt.ValidationErrorExpired: 32 | err = &service_errors.ServiceError{EndUserMessage: service_errors.TokenExpired} 33 | default: 34 | err = &service_errors.ServiceError{EndUserMessage: service_errors.TokenInvalid} 35 | } 36 | } 37 | } 38 | if err != nil { 39 | c.AbortWithStatusJSON(http.StatusUnauthorized, helper.GenerateBaseResponseWithError( 40 | nil, false, helper.AuthError, err, 41 | )) 42 | return 43 | } 44 | 45 | c.Set(constant.UserIdKey, claimMap[constant.UserIdKey]) 46 | c.Set(constant.FirstNameKey, claimMap[constant.FirstNameKey]) 47 | c.Set(constant.LastNameKey, claimMap[constant.LastNameKey]) 48 | c.Set(constant.UsernameKey, claimMap[constant.UsernameKey]) 49 | c.Set(constant.EmailKey, claimMap[constant.EmailKey]) 50 | c.Set(constant.MobileNumberKey, claimMap[constant.MobileNumberKey]) 51 | c.Set(constant.RolesKey, claimMap[constant.RolesKey]) 52 | c.Set(constant.ExpireTimeKey, claimMap[constant.ExpireTimeKey]) 53 | 54 | c.Next() 55 | } 56 | } 57 | 58 | func Authorization(validRoles []string) gin.HandlerFunc { 59 | return func(c *gin.Context) { 60 | if len(c.Keys) == 0 { 61 | c.AbortWithStatusJSON(http.StatusForbidden, helper.GenerateBaseResponse(nil, false, helper.ForbiddenError)) 62 | return 63 | } 64 | rolesVal := c.Keys[constant.RolesKey] 65 | fmt.Println(rolesVal) 66 | if rolesVal == nil { 67 | c.AbortWithStatusJSON(http.StatusForbidden, helper.GenerateBaseResponse(nil, false, helper.ForbiddenError)) 68 | return 69 | } 70 | roles := rolesVal.([]interface{}) 71 | val := map[string]int{} 72 | for _, item := range roles { 73 | val[item.(string)] = 0 74 | } 75 | 76 | for _, item := range validRoles { 77 | if _, ok := val[item]; ok { 78 | c.Next() 79 | return 80 | } 81 | } 82 | c.AbortWithStatusJSON(http.StatusForbidden, helper.GenerateBaseResponse(nil, false, helper.ForbiddenError)) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/api/middleware/cors.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/config" 6 | ) 7 | 8 | func Cors(cfg *config.Config) gin.HandlerFunc { 9 | return func(c *gin.Context) { 10 | 11 | c.Writer.Header().Set("Access-Control-Allow-Origin", cfg.Cors.AllowOrigins) 12 | c.Header("Access-Control-Allow-Credentials", "true") 13 | c.Header("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") 14 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") 15 | c.Header("Access-Control-Max-Age", "21600") 16 | c.Set("content-type", "application/json") 17 | if c.Request.Method == "OPTIONS" { 18 | c.AbortWithStatus(204) 19 | return 20 | } 21 | 22 | c.Next() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/api/middleware/custom_recovery.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | "github.com/omidhaqi/clean-web-api/api/helper" 8 | ) 9 | 10 | func ErrorHandler(c *gin.Context, err any) { 11 | if err, ok := err.(error); ok { 12 | httpResponse := helper.GenerateBaseResponseWithError(nil, false, helper.CustomRecovery, err) 13 | c.AbortWithStatusJSON(http.StatusInternalServerError, httpResponse) 14 | return 15 | } 16 | httpResponse := helper.GenerateBaseResponseWithAnyError(nil, false, helper.CustomRecovery, err) 17 | c.AbortWithStatusJSON(http.StatusInternalServerError, httpResponse) 18 | } 19 | -------------------------------------------------------------------------------- /src/api/middleware/limiter.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/didip/tollbooth" 7 | "github.com/gin-gonic/gin" 8 | "github.com/omidhaqi/clean-web-api/api/helper" 9 | ) 10 | 11 | func LimitByRequest() gin.HandlerFunc { 12 | lmt := tollbooth.NewLimiter(1, nil) 13 | return func(c *gin.Context) { 14 | err := tollbooth.LimitByRequest(lmt, c.Writer, c.Request) 15 | if err != nil { 16 | c.AbortWithStatusJSON(http.StatusTooManyRequests, 17 | helper.GenerateBaseResponseWithError(nil, false, helper.LimiterError, err)) 18 | return 19 | } else { 20 | c.Next() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/api/middleware/logger.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "strings" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/omidhaqi/clean-web-api/config" 11 | "github.com/omidhaqi/clean-web-api/pkg/logging" 12 | ) 13 | 14 | type bodyLogWriter struct { 15 | gin.ResponseWriter 16 | body *bytes.Buffer 17 | } 18 | 19 | func (w bodyLogWriter) Write(b []byte) (int, error) { 20 | w.body.Write(b) 21 | return w.ResponseWriter.Write(b) 22 | } 23 | 24 | func (w bodyLogWriter) WriteString(s string) (int, error) { 25 | w.body.WriteString(s) 26 | return w.ResponseWriter.WriteString(s) 27 | } 28 | 29 | func DefaultStructuredLogger(cfg *config.Config) gin.HandlerFunc { 30 | logger := logging.NewLogger(cfg) 31 | return structuredLogger(logger) 32 | } 33 | 34 | func structuredLogger(logger logging.Logger) gin.HandlerFunc { 35 | return func(c *gin.Context) { 36 | if strings.Contains(c.FullPath(), "swagger") { 37 | c.Next() 38 | } else { 39 | blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer} 40 | start := time.Now() // start 41 | path := c.FullPath() 42 | raw := c.Request.URL.RawQuery 43 | 44 | bodyBytes, _ := io.ReadAll(c.Request.Body) 45 | c.Request.Body.Close() 46 | c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) 47 | 48 | c.Writer = blw 49 | c.Next() 50 | 51 | param := gin.LogFormatterParams{} 52 | param.TimeStamp = time.Now() // stop 53 | param.Latency = param.TimeStamp.Sub(start) 54 | param.ClientIP = c.ClientIP() 55 | param.Method = c.Request.Method 56 | param.StatusCode = c.Writer.Status() 57 | param.ErrorMessage = c.Errors.ByType(gin.ErrorTypePrivate).String() 58 | param.BodySize = c.Writer.Size() 59 | 60 | if raw != "" { 61 | path = path + "?" + raw 62 | } 63 | param.Path = path 64 | 65 | keys := map[logging.ExtraKey]interface{}{} 66 | keys[logging.Path] = param.Path 67 | keys[logging.ClientIp] = param.ClientIP 68 | keys[logging.Method] = param.Method 69 | keys[logging.Latency] = param.Latency 70 | keys[logging.StatusCode] = param.StatusCode 71 | keys[logging.ErrorMessage] = param.ErrorMessage 72 | keys[logging.BodySize] = param.BodySize 73 | keys[logging.RequestBody] = string(bodyBytes) 74 | keys[logging.ResponseBody] = blw.body.String() 75 | 76 | logger.Info(logging.RequestResponse, logging.Api, "", keys) 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/api/middleware/otp_limiter.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "errors" 5 | "net" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/gin-gonic/gin" 10 | "github.com/omidhaqi/clean-web-api/api/helper" 11 | "github.com/omidhaqi/clean-web-api/config" 12 | "github.com/omidhaqi/clean-web-api/pkg/limiter" 13 | "golang.org/x/time/rate" 14 | ) 15 | 16 | func OtpLimiter(cfg *config.Config) gin.HandlerFunc { 17 | var limiter = limiter.NewIPRateLimiter(rate.Every(cfg.Otp.Limiter*time.Second), 1) 18 | return func(c *gin.Context) { 19 | limiter := limiter.GetLimiter(getIP(c.Request.RemoteAddr)) 20 | if !limiter.Allow() { 21 | c.AbortWithStatusJSON(http.StatusTooManyRequests, helper.GenerateBaseResponseWithError(nil, false, helper.OtpLimiterError, errors.New("not allowed"))) 22 | c.Abort() 23 | } else { 24 | c.Next() 25 | } 26 | } 27 | } 28 | 29 | func getIP(remoteAddr string) string { 30 | ip, _, err := net.SplitHostPort(remoteAddr) 31 | if err != nil { 32 | return remoteAddr 33 | } 34 | return ip 35 | } 36 | -------------------------------------------------------------------------------- /src/api/middleware/prometheus.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "strconv" 5 | "time" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/omidhaqi/clean-web-api/pkg/metrics" 9 | ) 10 | 11 | func Prometheus() gin.HandlerFunc { 12 | return func(c *gin.Context) { 13 | start := time.Now() 14 | path := c.FullPath() 15 | method := c.Request.Method 16 | c.Next() 17 | status := c.Writer.Status() 18 | metrics.HttpDuration.WithLabelValues(path, method, strconv.Itoa(status)). 19 | Observe(float64(time.Since(start) / time.Millisecond)) 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/api/middleware/test_middleware.go: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func TestMiddleware() gin.HandlerFunc { 10 | return func(ctx *gin.Context) { 11 | apiKey := ctx.GetHeader("x-api-key") 12 | if apiKey == "1" { 13 | ctx.Next() 14 | } 15 | ctx.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ 16 | "result": "Api key is required", 17 | }) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/api/router/basic.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/handler" 6 | "github.com/omidhaqi/clean-web-api/config" 7 | ) 8 | 9 | const GetByFilterExp string = "/get-by-filter" 10 | 11 | func Country(r *gin.RouterGroup, cfg *config.Config) { 12 | h := handler.NewCountryHandler(cfg) 13 | 14 | r.POST("/", h.Create) 15 | r.PUT("/:id", h.Update) 16 | r.DELETE("/:id", h.Delete) 17 | r.GET("/:id", h.GetById) 18 | r.POST(GetByFilterExp, h.GetByFilter) 19 | } 20 | 21 | func City(r *gin.RouterGroup, cfg *config.Config) { 22 | h := handler.NewCityHandler(cfg) 23 | 24 | r.POST("/", h.Create) 25 | r.PUT("/:id", h.Update) 26 | r.DELETE("/:id", h.Delete) 27 | r.GET("/:id", h.GetById) 28 | r.POST(GetByFilterExp, h.GetByFilter) 29 | } 30 | 31 | func File(r *gin.RouterGroup, cfg *config.Config) { 32 | h := handler.NewFileHandler(cfg) 33 | 34 | r.POST("/", h.Create) 35 | r.PUT("/:id", h.Update) 36 | r.DELETE("/:id", h.Delete) 37 | r.GET("/:id", h.GetById) 38 | r.POST(GetByFilterExp, h.GetByFilter) 39 | } 40 | 41 | func Company(r *gin.RouterGroup, cfg *config.Config) { 42 | h := handler.NewCompanyHandler(cfg) 43 | 44 | r.POST("/", h.Create) 45 | r.PUT("/:id", h.Update) 46 | r.DELETE("/:id", h.Delete) 47 | r.GET("/:id", h.GetById) 48 | r.POST(GetByFilterExp, h.GetByFilter) 49 | } 50 | 51 | func Color(r *gin.RouterGroup, cfg *config.Config) { 52 | h := handler.NewColorHandler(cfg) 53 | 54 | r.POST("/", h.Create) 55 | r.PUT("/:id", h.Update) 56 | r.DELETE("/:id", h.Delete) 57 | r.GET("/:id", h.GetById) 58 | r.POST(GetByFilterExp, h.GetByFilter) 59 | } 60 | 61 | func Year(r *gin.RouterGroup, cfg *config.Config) { 62 | h := handler.NewPersianYearHandler(cfg) 63 | 64 | r.POST("/", h.Create) 65 | r.PUT("/:id", h.Update) 66 | r.DELETE("/:id", h.Delete) 67 | r.GET("/:id", h.GetById) 68 | r.POST(GetByFilterExp, h.GetByFilter) 69 | } 70 | -------------------------------------------------------------------------------- /src/api/router/cars.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/handler" 6 | "github.com/omidhaqi/clean-web-api/config" 7 | ) 8 | 9 | func CarType(r *gin.RouterGroup, cfg *config.Config) { 10 | h := handler.NewCarTypeHandler(cfg) 11 | 12 | r.POST("/", h.Create) 13 | r.PUT("/:id", h.Update) 14 | r.DELETE("/:id", h.Delete) 15 | r.GET("/:id", h.GetById) 16 | r.POST(GetByFilterExp, h.GetByFilter) 17 | } 18 | 19 | func Gearbox(r *gin.RouterGroup, cfg *config.Config) { 20 | h := handler.NewGearboxHandler(cfg) 21 | 22 | r.POST("/", h.Create) 23 | r.PUT("/:id", h.Update) 24 | r.DELETE("/:id", h.Delete) 25 | r.GET("/:id", h.GetById) 26 | r.POST(GetByFilterExp, h.GetByFilter) 27 | } 28 | 29 | func CarModel(r *gin.RouterGroup, cfg *config.Config) { 30 | h := handler.NewCarModelHandler(cfg) 31 | 32 | r.POST("/", h.Create) 33 | r.PUT("/:id", h.Update) 34 | r.DELETE("/:id", h.Delete) 35 | r.GET("/:id", h.GetById) 36 | r.POST(GetByFilterExp, h.GetByFilter) 37 | } 38 | 39 | func CarModelColor(r *gin.RouterGroup, cfg *config.Config) { 40 | h := handler.NewCarModelColorHandler(cfg) 41 | 42 | r.POST("/", h.Create) 43 | r.PUT("/:id", h.Update) 44 | r.DELETE("/:id", h.Delete) 45 | r.GET("/:id", h.GetById) 46 | r.POST(GetByFilterExp, h.GetByFilter) 47 | } 48 | 49 | func CarModelYear(r *gin.RouterGroup, cfg *config.Config) { 50 | h := handler.NewCarModelYearHandler(cfg) 51 | 52 | r.POST("/", h.Create) 53 | r.PUT("/:id", h.Update) 54 | r.DELETE("/:id", h.Delete) 55 | r.GET("/:id", h.GetById) 56 | r.POST(GetByFilterExp, h.GetByFilter) 57 | } 58 | 59 | func CarModelPriceHistory(r *gin.RouterGroup, cfg *config.Config) { 60 | h := handler.NewCarModelPriceHistoryHandler(cfg) 61 | 62 | r.POST("/", h.Create) 63 | r.PUT("/:id", h.Update) 64 | r.DELETE("/:id", h.Delete) 65 | r.GET("/:id", h.GetById) 66 | r.POST(GetByFilterExp, h.GetByFilter) 67 | } 68 | 69 | func CarModelImage(r *gin.RouterGroup, cfg *config.Config) { 70 | h := handler.NewCarModelImageHandler(cfg) 71 | 72 | r.POST("/", h.Create) 73 | r.PUT("/:id", h.Update) 74 | r.DELETE("/:id", h.Delete) 75 | r.GET("/:id", h.GetById) 76 | r.POST(GetByFilterExp, h.GetByFilter) 77 | } 78 | 79 | func CarModelProperty(r *gin.RouterGroup, cfg *config.Config) { 80 | h := handler.NewCarModelPropertyHandler(cfg) 81 | 82 | r.POST("/", h.Create) 83 | r.PUT("/:id", h.Update) 84 | r.DELETE("/:id", h.Delete) 85 | r.GET("/:id", h.GetById) 86 | r.POST(GetByFilterExp, h.GetByFilter) 87 | } 88 | 89 | func CarModelComment(r *gin.RouterGroup, cfg *config.Config) { 90 | h := handler.NewCarModelCommentHandler(cfg) 91 | 92 | r.POST("/", h.Create) 93 | r.PUT("/:id", h.Update) 94 | r.DELETE("/:id", h.Delete) 95 | r.GET("/:id", h.GetById) 96 | r.POST(GetByFilterExp, h.GetByFilter) 97 | } 98 | -------------------------------------------------------------------------------- /src/api/router/health.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/handler" 6 | ) 7 | 8 | func Health(r *gin.RouterGroup) { 9 | handler := handler.NewHealthHandler() 10 | 11 | r.GET("/", handler.Health) 12 | } 13 | -------------------------------------------------------------------------------- /src/api/router/property.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/handler" 6 | "github.com/omidhaqi/clean-web-api/config" 7 | ) 8 | 9 | func PropertyCategory(r *gin.RouterGroup, cfg *config.Config) { 10 | h := handler.NewPropertyCategoryHandler(cfg) 11 | 12 | r.POST("/", h.Create) 13 | r.PUT("/:id", h.Update) 14 | r.DELETE("/:id", h.Delete) 15 | r.GET("/:id", h.GetById) 16 | r.POST(GetByFilterExp, h.GetByFilter) 17 | } 18 | 19 | func Property(r *gin.RouterGroup, cfg *config.Config) { 20 | h := handler.NewPropertyHandler(cfg) 21 | 22 | r.POST("/", h.Create) 23 | r.PUT("/:id", h.Update) 24 | r.DELETE("/:id", h.Delete) 25 | r.GET("/:id", h.GetById) 26 | r.POST(GetByFilterExp, h.GetByFilter) 27 | } 28 | -------------------------------------------------------------------------------- /src/api/router/test_router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/handler" 6 | ) 7 | 8 | func TestRouter(r *gin.RouterGroup) { 9 | h := handler.NewTestHandler() 10 | 11 | r.GET("/", h.Test) 12 | r.GET("/users", h.Users) 13 | r.GET("/user/:id", h.UserById) 14 | r.GET("/user/get-user-by-username/:username", h.UserByUsername) 15 | r.GET("/user/:id/accounts", h.Accounts) 16 | r.POST("/add-user", h.AddUser) 17 | 18 | r.POST("/binder/header1", h.HeaderBinder1) 19 | r.POST("/binder/header2", h.HeaderBinder2) 20 | 21 | r.POST("/binder/query1", h.QueryBinder1) 22 | r.POST("/binder/query2", h.QueryBinder2) 23 | 24 | r.POST("/binder/uri/:id/:name", h.UriBinder) 25 | r.POST("/binder/body", h.BodyBinder) 26 | r.POST("/binder/form", h.FormBinder) 27 | r.POST("/binder/file", h.FileBinder) 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/api/router/users.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "github.com/omidhaqi/clean-web-api/api/handler" 6 | "github.com/omidhaqi/clean-web-api/api/middleware" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | ) 9 | 10 | func User(router *gin.RouterGroup, cfg *config.Config) { 11 | h := handler.NewUserHandler(cfg) 12 | 13 | router.POST("/send-otp", middleware.OtpLimiter(cfg), h.SendOtp) 14 | router.POST("/login-by-username", h.LoginByUsername) 15 | router.POST("/register-by-username", h.RegisterByUsername) 16 | router.POST("/login-by-mobile", h.RegisterLoginByMobileNumber) 17 | } 18 | -------------------------------------------------------------------------------- /src/api/validation/custom.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/go-playground/validator/v10" 7 | ) 8 | 9 | type ValidationError struct { 10 | Property string `json:"property"` 11 | Tag string `json:"tag"` 12 | Value string `json:"value"` 13 | Message string `json:"message"` 14 | } 15 | 16 | func GetValidationErrors(err error) *[]ValidationError { 17 | var validationErrors []ValidationError 18 | var ve validator.ValidationErrors 19 | if errors.As(err,&ve){ 20 | for _, err := range err.(validator.ValidationErrors){ 21 | var el ValidationError 22 | el.Property = err.Field() 23 | el.Tag = err.Tag() 24 | el.Value = err.Param() 25 | validationErrors = append(validationErrors, el) 26 | } 27 | return &validationErrors 28 | } 29 | return nil 30 | } -------------------------------------------------------------------------------- /src/api/validation/mobile.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "github.com/go-playground/validator/v10" 5 | "github.com/omidhaqi/clean-web-api/common" 6 | ) 7 | 8 | func IranianMobileNumberValidator(fld validator.FieldLevel) bool { 9 | 10 | value, ok := fld.Field().Interface().(string) 11 | if !ok { 12 | return false 13 | } 14 | 15 | return common.IranianMobileNumberValidate(value) 16 | } 17 | -------------------------------------------------------------------------------- /src/api/validation/password.go: -------------------------------------------------------------------------------- 1 | package validation 2 | 3 | import ( 4 | "github.com/go-playground/validator/v10" 5 | "github.com/omidhaqi/clean-web-api/common" 6 | ) 7 | 8 | func PasswordValidator(fld validator.FieldLevel) bool { 9 | value, ok := fld.Field().Interface().(string) 10 | if !ok { 11 | fld.Param() 12 | return false 13 | } 14 | 15 | return common.CheckPassword(value) 16 | } 17 | -------------------------------------------------------------------------------- /src/cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/omidhaqi/clean-web-api/api" 5 | "github.com/omidhaqi/clean-web-api/config" 6 | "github.com/omidhaqi/clean-web-api/infra/cache" 7 | database "github.com/omidhaqi/clean-web-api/infra/persistence/database" 8 | "github.com/omidhaqi/clean-web-api/infra/persistence/migration" 9 | "github.com/omidhaqi/clean-web-api/pkg/logging" 10 | ) 11 | 12 | // @securityDefinitions.apikey AuthBearer 13 | // @in header 14 | // @name Authorization 15 | func main() { 16 | 17 | cfg := config.GetConfig() 18 | logger := logging.NewLogger(cfg) 19 | 20 | err := cache.InitRedis(cfg) 21 | defer cache.CloseRedis() 22 | if err != nil { 23 | logger.Fatal(logging.Redis, logging.Startup, err.Error(), nil) 24 | } 25 | 26 | err = database.InitDb(cfg) 27 | defer database.CloseDb() 28 | if err != nil { 29 | logger.Fatal(logging.Postgres, logging.Startup, err.Error(), nil) 30 | } 31 | migration.Up1() 32 | 33 | api.InitServer(cfg) 34 | } 35 | -------------------------------------------------------------------------------- /src/common/mapper.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "encoding/json" 4 | 5 | // TODO: must write struct to struct mapper instead of this function 6 | func TypeConverter[T any](data any) (T, error) { 7 | var result T 8 | dataJson, err := json.Marshal(&data) 9 | if err != nil { 10 | return result, err 11 | } 12 | err = json.Unmarshal(dataJson, &result) 13 | if err != nil { 14 | return result, err 15 | } 16 | return result, nil 17 | } 18 | -------------------------------------------------------------------------------- /src/common/persian.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "log" 5 | "regexp" 6 | ) 7 | 8 | const iranianMobileNumberPattern string = `^09(1[0-9]|2[0-2]|3[0-9]|9[0-9])[0-9]{7}$` 9 | 10 | func IranianMobileNumberValidate(mobileNumber string) bool { 11 | res, err := regexp.MatchString(iranianMobileNumberPattern, mobileNumber) 12 | if err != nil { 13 | log.Print(err.Error()) 14 | } 15 | return res 16 | } 17 | -------------------------------------------------------------------------------- /src/common/strings.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | "time" 10 | "unicode" 11 | 12 | "github.com/omidhaqi/clean-web-api/config" 13 | ) 14 | 15 | var ( 16 | lowerCharSet = "abcdedfghijklmnopqrst" 17 | upperCharSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 18 | specialCharSet = "!@#$%&*" 19 | numberSet = "0123456789" 20 | allCharSet = lowerCharSet + upperCharSet + specialCharSet + numberSet 21 | ) 22 | 23 | var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") 24 | var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") 25 | 26 | func CheckPassword(password string) bool { 27 | cfg := config.GetConfig() 28 | if len(password) < cfg.Password.MinLength { 29 | return false 30 | } 31 | 32 | if cfg.Password.IncludeChars && !HasLetter(password) { 33 | return false 34 | } 35 | 36 | if cfg.Password.IncludeDigits && !HasDigits(password) { 37 | return false 38 | } 39 | 40 | if cfg.Password.IncludeLowercase && !HasLower(password) { 41 | return false 42 | } 43 | 44 | if cfg.Password.IncludeUppercase && !HasUpper(password) { 45 | return false 46 | } 47 | 48 | return true 49 | } 50 | 51 | func GeneratePassword() string { 52 | var password strings.Builder 53 | 54 | cfg := config.GetConfig() 55 | passwordLength := cfg.Password.MinLength + 2 56 | minSpecialChar := 2 57 | minNum := 3 58 | if !cfg.Password.IncludeDigits { 59 | minNum = 0 60 | } 61 | 62 | minUpperCase := 3 63 | if !cfg.Password.IncludeUppercase { 64 | minUpperCase = 0 65 | } 66 | 67 | minLowerCase := 3 68 | if !cfg.Password.IncludeLowercase { 69 | minLowerCase = 0 70 | } 71 | 72 | //Set special character 73 | for i := 0; i < minSpecialChar; i++ { 74 | random := rand.Intn(len(specialCharSet)) 75 | password.WriteString(string(specialCharSet[random])) 76 | } 77 | 78 | //Set numeric 79 | for i := 0; i < minNum; i++ { 80 | random := rand.Intn(len(numberSet)) 81 | password.WriteString(string(numberSet[random])) 82 | } 83 | 84 | //Set uppercase 85 | for i := 0; i < minUpperCase; i++ { 86 | random := rand.Intn(len(upperCharSet)) 87 | password.WriteString(string(upperCharSet[random])) 88 | } 89 | 90 | //Set lowercase 91 | for i := 0; i < minLowerCase; i++ { 92 | random := rand.Intn(len(lowerCharSet)) 93 | password.WriteString(string(lowerCharSet[random])) 94 | } 95 | 96 | remainingLength := passwordLength - minSpecialChar - minNum - minUpperCase 97 | for i := 0; i < remainingLength; i++ { 98 | random := rand.Intn(len(allCharSet)) 99 | password.WriteString(string(allCharSet[random])) 100 | } 101 | inRune := []rune(password.String()) 102 | rand.Shuffle(len(inRune), func(i, j int) { 103 | inRune[i], inRune[j] = inRune[j], inRune[i] 104 | }) 105 | return string(inRune) 106 | } 107 | 108 | func GenerateOtp() string { 109 | cfg := config.GetConfig() 110 | rand.Seed(time.Now().UnixNano()) 111 | min := int(math.Pow(10, float64(cfg.Otp.Digits-1))) // 10^d-1 100000 112 | max := int(math.Pow(10, float64(cfg.Otp.Digits)) - 1) // 999999 = 1000000 - 1 (10^d) -1 113 | 114 | var num = rand.Intn(max-min) + min 115 | return strconv.Itoa(num) 116 | } 117 | 118 | func HasUpper(s string) bool { 119 | for _, r := range s { 120 | if unicode.IsUpper(r) && unicode.IsLetter(r) { 121 | return true 122 | } 123 | } 124 | return false 125 | } 126 | 127 | func HasLower(s string) bool { 128 | for _, r := range s { 129 | if unicode.IsLower(r) && unicode.IsLetter(r) { 130 | return true 131 | } 132 | } 133 | return false 134 | } 135 | 136 | func HasLetter(s string) bool { 137 | for _, r := range s { 138 | if unicode.IsLetter(r) { 139 | return true 140 | } 141 | } 142 | return false 143 | } 144 | 145 | func HasDigits(s string) bool { 146 | for _, r := range s { 147 | if unicode.IsDigit(r) { 148 | return true 149 | } 150 | } 151 | return false 152 | } 153 | 154 | // To snake case : CountryId -> country_id 155 | func ToSnakeCase(str string) string { 156 | snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}") 157 | snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}") 158 | return strings.ToLower(snake) 159 | } 160 | -------------------------------------------------------------------------------- /src/config/config-development.yml: -------------------------------------------------------------------------------- 1 | server: 2 | internalPort: 5005 3 | externalPort: 5005 4 | runMode: debug 5 | logger: 6 | filePath: ../logs/ 7 | encoding: json 8 | level: debug 9 | logger: zap 10 | cors: 11 | allowOrigins: "*" 12 | postgres: 13 | host: localhost 14 | port: 5432 15 | user: postgres 16 | password: admin 17 | dbName: car_sale_db 18 | sslMode: disable 19 | maxIdleConns: 15 20 | maxOpenConns: 100 21 | connMaxLifetime: 5 22 | redis: 23 | host: localhost 24 | port: 6379 25 | password: password 26 | db: 0 27 | dialTimeout: 5 28 | readTimeout: 5 29 | writeTimeout: 5 30 | poolSize: 10 31 | poolTimeout: 15 32 | idleCheckFrequency: 500 33 | password: 34 | includeChars: true 35 | includeDigits: true 36 | minLength: 6 37 | maxLength: 64 38 | includeUppercase: true 39 | includeLowercase: true 40 | otp: 41 | expireTime: 120 42 | digits: 6 43 | limiter: 100 44 | jwt: 45 | secret: "mySecretKey" 46 | refreshSecret: "mySecretKey" 47 | accessTokenExpireDuration: 1440 48 | refreshTokenExpireDuration: 60 49 | -------------------------------------------------------------------------------- /src/config/config-docker.yml: -------------------------------------------------------------------------------- 1 | server: 2 | internalPort: 5000 3 | externalPort: 0 4 | runMode: debug 5 | logger: 6 | filePath: /app/logs/ 7 | encoding: json 8 | level: debug 9 | logger: zap 10 | cors: 11 | allowOrigins: "*" 12 | postgres: 13 | host: postgres_container 14 | port: 5432 15 | user: postgres 16 | password: admin 17 | dbName: car_sale_db 18 | sslMode: disable 19 | maxIdleConns: 15 20 | maxOpenConns: 100 21 | connMaxLifetime: 5 22 | redis: 23 | host: redis_container 24 | port: 6379 25 | password: password 26 | db: 0 27 | dialTimeout: 5 28 | readTimeout: 5 29 | writeTimeout: 5 30 | poolSize: 10 31 | poolTimeout: 15 32 | idleCheckFrequency: 500 33 | password: 34 | includeChars: true 35 | includeDigits: true 36 | minLength: 6 37 | maxLength: 64 38 | includeUppercase: true 39 | includeLowercase: true 40 | otp: 41 | expireTime: 120 42 | digits: 6 43 | limiter: 100 44 | jwt: 45 | secret: "mySecretKey" 46 | refreshSecret: "mySecretKey" 47 | accessTokenExpireDuration: 60 48 | refreshTokenExpireDuration: 60 49 | -------------------------------------------------------------------------------- /src/config/config-production.yml: -------------------------------------------------------------------------------- 1 | server: 2 | internalPort: 5010 3 | externalPort: 5010 4 | runMode: release 5 | logger: 6 | filePath: logs/ 7 | encoding: json 8 | level: debug 9 | logger: zap 10 | cors: 11 | allowOrigins: "*" 12 | postgres: 13 | host: localhost 14 | port: 5432 15 | user: postgres 16 | password: admin 17 | dbName: car_sale_db 18 | sslMode: disable 19 | maxIdleConns: 15 20 | maxOpenConns: 100 21 | connMaxLifetime: 5 22 | redis: 23 | host: localhost 24 | port: 6379 25 | password: password 26 | db: 0 27 | dialTimeout: 5 28 | readTimeout: 5 29 | writeTimeout: 5 30 | poolSize: 10 31 | poolTimeout: 15 32 | idleCheckFrequency: 500 33 | password: 34 | includeChars: true 35 | includeDigits: true 36 | minLength: 6 37 | maxLength: 64 38 | includeUppercase: true 39 | includeLowercase: true 40 | otp: 41 | expireTime: 120 42 | digits: 6 43 | limiter: 100 44 | jwt: 45 | secret: "mySecretKey" 46 | refreshSecret: "mySecretKey" 47 | accessTokenExpireDuration: 1440 48 | refreshTokenExpireDuration: 60 49 | -------------------------------------------------------------------------------- /src/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "os" 7 | "time" 8 | 9 | "github.com/spf13/viper" 10 | ) 11 | 12 | type Config struct { 13 | Server ServerConfig 14 | Postgres PostgresConfig 15 | Redis RedisConfig 16 | Password PasswordConfig 17 | Cors CorsConfig 18 | Logger LoggerConfig 19 | Otp OtpConfig 20 | JWT JWTConfig 21 | } 22 | 23 | type ServerConfig struct { 24 | InternalPort string 25 | ExternalPort string 26 | RunMode string 27 | } 28 | 29 | type LoggerConfig struct { 30 | FilePath string 31 | Encoding string 32 | Level string 33 | Logger string 34 | } 35 | 36 | type PostgresConfig struct { 37 | Host string 38 | Port string 39 | User string 40 | Password string 41 | DbName string 42 | SSLMode string 43 | MaxIdleConns int 44 | MaxOpenConns int 45 | ConnMaxLifetime time.Duration 46 | } 47 | 48 | type RedisConfig struct { 49 | Host string 50 | Port string 51 | Password string 52 | Db string 53 | DialTimeout time.Duration 54 | ReadTimeout time.Duration 55 | WriteTimeout time.Duration 56 | IdleCheckFrequency time.Duration 57 | PoolSize int 58 | PoolTimeout time.Duration 59 | } 60 | 61 | type PasswordConfig struct { 62 | IncludeChars bool 63 | IncludeDigits bool 64 | MinLength int 65 | MaxLength int 66 | IncludeUppercase bool 67 | IncludeLowercase bool 68 | } 69 | 70 | type CorsConfig struct { 71 | AllowOrigins string 72 | } 73 | 74 | type OtpConfig struct { 75 | ExpireTime time.Duration 76 | Digits int 77 | Limiter time.Duration 78 | } 79 | 80 | type JWTConfig struct { 81 | AccessTokenExpireDuration time.Duration 82 | RefreshTokenExpireDuration time.Duration 83 | Secret string 84 | RefreshSecret string 85 | } 86 | 87 | func GetConfig() *Config { 88 | cfgPath := getConfigPath(os.Getenv("APP_ENV")) 89 | v, err := LoadConfig(cfgPath, "yml") 90 | if err != nil { 91 | log.Fatalf("Error in load config %v", err) 92 | } 93 | 94 | cfg, err := ParseConfig(v) 95 | envPort := os.Getenv("PORT") 96 | if envPort != ""{ 97 | cfg.Server.ExternalPort = envPort 98 | log.Printf("Set external port from environment -> %s", cfg.Server.ExternalPort) 99 | }else{ 100 | cfg.Server.ExternalPort = cfg.Server.InternalPort 101 | log.Printf("Set external port from environment -> %s", cfg.Server.ExternalPort) 102 | } 103 | if err != nil { 104 | log.Fatalf("Error in parse config %v", err) 105 | } 106 | 107 | return cfg 108 | } 109 | 110 | func ParseConfig(v *viper.Viper) (*Config, error) { 111 | var cfg Config 112 | err := v.Unmarshal(&cfg) 113 | if err != nil { 114 | log.Printf("Unable to parse config: %v", err) 115 | return nil, err 116 | } 117 | return &cfg, nil 118 | } 119 | func LoadConfig(filename string, fileType string) (*viper.Viper, error) { 120 | v := viper.New() 121 | v.SetConfigType(fileType) 122 | v.SetConfigName(filename) 123 | v.AddConfigPath(".") 124 | v.AutomaticEnv() 125 | 126 | err := v.ReadInConfig() 127 | if err != nil { 128 | log.Printf("Unable to read config: %v", err) 129 | if _, ok := err.(viper.ConfigFileNotFoundError); ok { 130 | return nil, errors.New("config file not found") 131 | } 132 | return nil, err 133 | } 134 | return v, nil 135 | } 136 | 137 | func getConfigPath(env string) string { 138 | if env == "docker" { 139 | return "/app/config/config-docker" 140 | } else if env == "production" { 141 | return "/config/config-production" 142 | } else { 143 | return "../config/config-development" 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/constant/constanst.go: -------------------------------------------------------------------------------- 1 | package constant 2 | 3 | const ( 4 | // User 5 | AdminRoleName string = "admin" 6 | DefaultRoleName string = "default" 7 | DefaultUserName string = "admin" 8 | RedisOtpDefaultKey string = "otp" 9 | 10 | // Claims 11 | AuthorizationHeaderKey string = "Authorization" 12 | UserIdKey string = "UserId" 13 | FirstNameKey string = "FirstName" 14 | LastNameKey string = "LastName" 15 | UsernameKey string = "Username" 16 | EmailKey string = "Email" 17 | MobileNumberKey string = "MobileNumber" 18 | RolesKey string = "Roles" 19 | ExpireTimeKey string = "Exp" 20 | ) 21 | -------------------------------------------------------------------------------- /src/domain/filter/dynamic_filter.go: -------------------------------------------------------------------------------- 1 | package filter 2 | 3 | type Sort struct { 4 | ColId string `json:"colId"` 5 | Sort string `json:"sort"` 6 | } 7 | 8 | type Filter struct { 9 | // contains notContains equals notEqual startsWith lessThan lessThanOrEqual greaterThan greaterThanOrEqual inRange endsWith 10 | Type string `json:"type"` 11 | From string `json:"from"` 12 | To string `json:"to"` 13 | // text number 14 | FilterType string `json:"filterType"` 15 | } 16 | 17 | type DynamicFilter struct { 18 | Sort *[]Sort `json:"sort"` 19 | Filter map[string]Filter `json:"filter"` 20 | } 21 | -------------------------------------------------------------------------------- /src/domain/filter/pagination.go: -------------------------------------------------------------------------------- 1 | package filter 2 | 3 | import ( 4 | "math" 5 | 6 | "github.com/omidhaqi/clean-web-api/common" 7 | ) 8 | 9 | func NewPagedList[T any](items *[]T, count int64, pageNumber int, pageSize int64) *PagedList[T] { 10 | pl := &PagedList[T]{ 11 | PageNumber: pageNumber, 12 | PageSize: pageSize, 13 | TotalRows: count, 14 | Items: items, 15 | } 16 | pl.TotalPages = int(math.Ceil(float64(count) / float64(pageSize))) 17 | pl.HasNextPage = pl.PageNumber < pl.TotalPages 18 | pl.HasPreviousPage = pl.PageNumber > 1 19 | 20 | return pl 21 | } 22 | 23 | // Paginate 24 | func Paginate[TInput any, TOutput any](totalRows int64, items *[]TInput, pageNumber int, pageSize int64) (*PagedList[TOutput], error) { 25 | var rItems []TOutput 26 | 27 | rItems, err := common.TypeConverter[[]TOutput](items) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return NewPagedList(&rItems, totalRows, pageNumber, pageSize), err 32 | 33 | } 34 | 35 | type PagedList[T any] struct { 36 | PageNumber int `json:"pageNumber"` 37 | PageSize int64 `json:"pageSize"` 38 | TotalRows int64 `json:"totalRows"` 39 | TotalPages int `json:"totalPages"` 40 | HasPreviousPage bool `json:"hasPreviousPage"` 41 | HasNextPage bool `json:"hasNextPage"` 42 | Items *[]T `json:"items"` 43 | } 44 | 45 | type PaginationInput struct { 46 | PageSize int `json:"pageSize"` 47 | PageNumber int `json:"pageNumber"` 48 | } 49 | 50 | type PaginationInputWithFilter struct { 51 | PaginationInput 52 | DynamicFilter 53 | } 54 | 55 | func (p *PaginationInputWithFilter) GetOffset() int { 56 | // 2 , 10 => 11-20 57 | return (p.GetPageNumber() - 1) * p.GetPageSize() 58 | } 59 | 60 | func (p *PaginationInputWithFilter) GetPageSize() int { 61 | if p.PageSize == 0 { 62 | p.PageSize = 10 63 | } 64 | return p.PageSize 65 | } 66 | 67 | func (p *PaginationInputWithFilter) GetPageNumber() int { 68 | if p.PageNumber == 0 { 69 | p.PageNumber = 1 70 | } 71 | return p.PageNumber 72 | } 73 | -------------------------------------------------------------------------------- /src/domain/model/base.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type Country struct { 6 | BaseModel 7 | Name string `gorm:"size:15;type:string;not null;"` 8 | Cities []City 9 | Companies []Company 10 | } 11 | 12 | type City struct { 13 | BaseModel 14 | Name string `gorm:"size:10;type:string;not null;"` 15 | CountryId int 16 | Country Country `gorm:"foreignKey:CountryId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 17 | } 18 | 19 | type PersianYear struct { 20 | BaseModel 21 | PersianTitle string `gorm:"size:10;type:string;not null;unique"` 22 | Year int `gorm:"type:int;uniqueIndex;not null"` 23 | StartAt time.Time `gorm:"type:TIMESTAMP with time zone;not null;unique"` 24 | EndAt time.Time `gorm:"type:TIMESTAMP with time zone;not null;unique"` 25 | CarModelYears []CarModelYear 26 | } 27 | 28 | type Color struct { 29 | BaseModel 30 | Name string `gorm:"size:15;type:string;not null,unique"` 31 | HexCode string `gorm:"size:7;type:string;not null,unique"` 32 | CarModelColors []CarModelColor 33 | } 34 | 35 | type File struct { 36 | BaseModel 37 | Name string `gorm:"size:100;type:string;not null"` 38 | Directory string `gorm:"size:100;type:string;not null"` 39 | Description string `gorm:"size:500;type:string;not null"` 40 | MimeType string `gorm:"size:20;type:string;not null"` 41 | } 42 | -------------------------------------------------------------------------------- /src/domain/model/base_model.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "database/sql" 5 | "time" 6 | 7 | "gorm.io/gorm" 8 | ) 9 | 10 | type BaseModel struct { 11 | Id int `gorm:"primarykey"` 12 | 13 | CreatedAt time.Time `gorm:"type:TIMESTAMP with time zone;not null"` 14 | ModifiedAt sql.NullTime `gorm:"type:TIMESTAMP with time zone;null"` 15 | DeletedAt sql.NullTime `gorm:"type:TIMESTAMP with time zone;null"` 16 | 17 | CreatedBy int `gorm:"not null"` 18 | ModifiedBy *sql.NullInt64 `gorm:"null"` 19 | DeletedBy *sql.NullInt64 `gorm:"null"` 20 | } 21 | 22 | func (m *BaseModel) BeforeCreate(tx *gorm.DB) (err error) { 23 | value := tx.Statement.Context.Value("UserId") 24 | var userId = -1 25 | if value != nil { 26 | userId = int(value.(float64)) 27 | } 28 | m.CreatedAt = time.Now().UTC() 29 | m.CreatedBy = userId 30 | return 31 | } 32 | 33 | func (m *BaseModel) BeforeUpdate(tx *gorm.DB) (err error) { 34 | value := tx.Statement.Context.Value("UserId") 35 | var userId = &sql.NullInt64{Valid: false} 36 | if value != nil { 37 | userId = &sql.NullInt64{Valid: true, Int64: int64(value.(float64))} 38 | } 39 | m.ModifiedAt = sql.NullTime{Time: time.Now().UTC(), Valid: true} 40 | m.ModifiedBy = userId 41 | return 42 | } 43 | 44 | func (m *BaseModel) BeforeDelete(tx *gorm.DB) (err error) { 45 | value := tx.Statement.Context.Value("UserId") 46 | var userId = &sql.NullInt64{Valid: false} 47 | if value != nil { 48 | userId = &sql.NullInt64{Valid: true, Int64: int64(value.(float64))} 49 | } 50 | m.DeletedAt = sql.NullTime{Time: time.Now().UTC(), Valid: true} 51 | m.DeletedBy = userId 52 | return 53 | } 54 | -------------------------------------------------------------------------------- /src/domain/model/car.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type Gearbox struct { 6 | BaseModel 7 | Name string `gorm:"size:15;type:string;not null,unique;"` 8 | CarModels []CarModel 9 | } 10 | 11 | type CarType struct { 12 | BaseModel 13 | Name string `gorm:"size:15;type:string;not null,unique;"` 14 | CarModels []CarModel 15 | } 16 | 17 | type Company struct { 18 | BaseModel 19 | Name string `gorm:"size:15;type:string;not null,unique;"` 20 | Country Country `gorm:"foreignKey:CountryId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 21 | CountryId int 22 | CarModels []CarModel 23 | } 24 | 25 | type CarModel struct { 26 | BaseModel 27 | Name string `gorm:"size:15;type:string;not null,unique;"` 28 | Company Company `gorm:"foreignKey:CompanyId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 29 | CompanyId int 30 | CarType CarType `gorm:"foreignKey:CarTypeId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 31 | CarTypeId int 32 | Gearbox Gearbox `gorm:"foreignKey:GearboxId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 33 | GearboxId int 34 | CarModelColors []CarModelColor 35 | CarModelYears []CarModelYear 36 | CarModelProperties []CarModelProperty 37 | CarModelImages []CarModelImage 38 | CarModelComments []CarModelComment 39 | } 40 | 41 | type CarModelColor struct { 42 | BaseModel 43 | CarModel CarModel `gorm:"foreignKey:CarModelId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 44 | CarModelId int `gorm:"uniqueIndex:idx_CarModelId_ColorId"` 45 | Color Color `gorm:"foreignKey:ColorId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 46 | ColorId int `gorm:"uniqueIndex:idx_CarModelId_ColorId"` 47 | } 48 | 49 | type CarModelYear struct { 50 | BaseModel 51 | CarModel CarModel `gorm:"foreignKey:CarModelId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 52 | CarModelId int `gorm:"uniqueIndex:idx_CarModelId_PersianYearId"` 53 | PersianYear PersianYear `gorm:"foreignKey:PersianYearId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 54 | PersianYearId int `gorm:"uniqueIndex:idx_CarModelId_PersianYearId"` 55 | CarModelPriceHistories []CarModelPriceHistory 56 | } 57 | 58 | type CarModelImage struct { 59 | BaseModel 60 | CarModel CarModel `gorm:"foreignKey:CarModelId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 61 | CarModelId int `gorm:"uniqueIndex:idx_CarModelId_ImageId"` 62 | Image File `gorm:"foreignKey:ImageId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 63 | ImageId int `gorm:"uniqueIndex:idx_CarModelId_ImageId"` 64 | IsMainImage bool 65 | } 66 | 67 | type CarModelPriceHistory struct { 68 | BaseModel 69 | CarModelYear CarModelYear `gorm:"foreignKey:CarModelYearId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 70 | CarModelYearId int 71 | Price float64 `gorm:"type:decimal(10,2);not null"` 72 | PriceAt time.Time `gorm:"type:TIMESTAMP with time zone;not null"` 73 | } 74 | 75 | type CarModelProperty struct { 76 | BaseModel 77 | CarModel CarModel `gorm:"foreignKey:CarModelId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 78 | CarModelId int `gorm:"uniqueIndex:idx_CarModelId_PropertyId"` 79 | Property Property `gorm:"foreignKey:PropertyId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 80 | PropertyId int `gorm:"uniqueIndex:idx_CarModelId_PropertyId"` 81 | Value string `gorm:"size:1000,type:string;not null"` 82 | } 83 | 84 | type CarModelComment struct { 85 | BaseModel 86 | CarModel CarModel `gorm:"foreignKey:CarModelId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 87 | CarModelId int 88 | User User `gorm:"foreignKey:UserId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 89 | UserId int 90 | Message string `gorm:"size:500,type:string;not null"` 91 | } 92 | 93 | -------------------------------------------------------------------------------- /src/domain/model/property.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type PropertyCategory struct { 4 | BaseModel 5 | Name string `gorm:"size:50;type:string;not null,unique;"` 6 | Icon string `gorm:"size:1000;type:string;not null,unique;"` 7 | Properties []Property `gorm:"foreignKey:CategoryId"` 8 | } 9 | 10 | type Property struct { 11 | BaseModel 12 | Name string `gorm:"size:50;type:string;not null,unique;"` 13 | Icon string `gorm:"size:1000;type:string;not null,unique;"` 14 | Category PropertyCategory `gorm:"foreignKey:CategoryId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 15 | CategoryId int 16 | Description string `gorm:"size:1000;type:string;not null,unique;"` 17 | DataType string `gorm:"size:15;type:string;not null,unique;"` 18 | Unit string `gorm:"size:15;type:string;not null,unique;"` 19 | } 20 | -------------------------------------------------------------------------------- /src/domain/model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type User struct { 4 | BaseModel 5 | Username string `gorm:"type:string;size:20;not null;unique"` 6 | FirstName string `gorm:"type:string;size:15;null"` 7 | LastName string `gorm:"type:string;size:25;null"` 8 | MobileNumber string `gorm:"type:string;size:11;null;unique;default:null"` 9 | Email string `gorm:"type:string;size:64;null;unique;default:null"` 10 | Password string `gorm:"type:string;size:64;not null"` 11 | Enabled bool `gorm:"default:true"` 12 | UserRoles *[]UserRole 13 | } 14 | 15 | type Role struct { 16 | BaseModel 17 | Name string `gorm:"type:string;size:10;not null,unique"` 18 | UserRoles *[]UserRole 19 | } 20 | 21 | type UserRole struct { 22 | BaseModel 23 | User User `gorm:"foreignKey:UserId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 24 | Role Role `gorm:"foreignKey:RoleId;constraint:OnUpdate:NO ACTION;OnDelete:NO ACTION"` 25 | UserId int 26 | RoleId int 27 | } 28 | -------------------------------------------------------------------------------- /src/domain/repository/repository.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/domain/filter" 7 | "github.com/omidhaqi/clean-web-api/domain/model" 8 | ) 9 | 10 | type BaseRepository[TEntity any] interface { 11 | Create(ctx context.Context, entity TEntity) (TEntity, error) 12 | Update(ctx context.Context, id int, entity map[string]interface{}) (TEntity, error) 13 | Delete(ctx context.Context, id int) error 14 | GetById(ctx context.Context, id int) (TEntity, error) 15 | GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (int64, *[]TEntity, error) 16 | } 17 | type CountryRepository interface { 18 | BaseRepository[model.Country] 19 | } 20 | 21 | type CityRepository interface { 22 | BaseRepository[model.City] 23 | // Create(ctx context.Context, City model.City) (model.City, error) 24 | // Update(ctx context.Context, id int, City model.City) (model.City, error) 25 | // Delete(ctx context.Context, id int) error 26 | // GetById(ctx context.Context, id int) (model.City, error) 27 | // GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (int64, *[]model.City, error) 28 | } 29 | 30 | type PersianYearRepository interface { 31 | BaseRepository[model.PersianYear] 32 | } 33 | 34 | type ColorRepository interface { 35 | BaseRepository[model.Color] 36 | } 37 | 38 | type FileRepository interface { 39 | BaseRepository[model.File] 40 | } 41 | 42 | type GearboxRepository interface { 43 | BaseRepository[model.Gearbox] 44 | } 45 | 46 | type CarTypeRepository interface { 47 | BaseRepository[model.CarType] 48 | } 49 | 50 | type CompanyRepository interface { 51 | BaseRepository[model.Company] 52 | } 53 | 54 | type CarModelRepository interface { 55 | BaseRepository[model.CarModel] 56 | } 57 | 58 | type CarModelColorRepository interface { 59 | BaseRepository[model.CarModelColor] 60 | } 61 | 62 | type CarModelYearRepository interface { 63 | BaseRepository[model.CarModelYear] 64 | } 65 | 66 | type CarModelImageRepository interface { 67 | BaseRepository[model.CarModelImage] 68 | } 69 | 70 | type CarModelPriceHistoryRepository interface { 71 | BaseRepository[model.CarModelPriceHistory] 72 | } 73 | 74 | type CarModelPropertyRepository interface { 75 | BaseRepository[model.CarModelProperty] 76 | } 77 | 78 | type CarModelCommentRepository interface { 79 | BaseRepository[model.CarModelComment] 80 | } 81 | 82 | type PropertyCategoryRepository interface { 83 | BaseRepository[model.PropertyCategory] 84 | } 85 | 86 | type PropertyRepository interface { 87 | BaseRepository[model.Property] 88 | } 89 | 90 | type UserRepository interface { 91 | ExistsMobileNumber(ctx context.Context, mobileNumber string) (bool, error) 92 | ExistsUsername(ctx context.Context, username string) (bool, error) 93 | ExistsEmail(ctx context.Context, email string) (bool, error) 94 | FetchUserInfo(ctx context.Context, username string, password string) (model.User, error) 95 | GetDefaultRole(ctx context.Context) (roleId int, err error) 96 | CreateUser(ctx context.Context, u model.User) (model.User, error) 97 | } 98 | 99 | type RoleRepository interface { 100 | BaseRepository[model.Role] 101 | } 102 | -------------------------------------------------------------------------------- /src/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/omidhaqi/clean-web-api 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/didip/tollbooth v4.0.2+incompatible 7 | github.com/gin-gonic/gin v1.10.0 8 | github.com/go-playground/validator/v10 v10.20.0 9 | github.com/go-redis/redis/v7 v7.4.1 10 | github.com/prometheus/client_golang v1.22.0 11 | github.com/rs/zerolog v1.33.0 12 | github.com/spf13/viper v1.19.0 13 | github.com/swaggo/files v1.0.1 14 | github.com/swaggo/gin-swagger v1.6.0 15 | github.com/swaggo/swag v1.16.4 16 | go.uber.org/zap v1.21.0 17 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 18 | gorm.io/driver/postgres v1.5.9 19 | gorm.io/gorm v1.25.12 20 | ) 21 | 22 | require ( 23 | github.com/beorn7/perks v1.0.1 // indirect 24 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 25 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 26 | github.com/prometheus/client_model v0.6.1 // indirect 27 | github.com/prometheus/common v0.62.0 // indirect 28 | github.com/prometheus/procfs v0.15.1 // indirect 29 | ) 30 | 31 | require ( 32 | github.com/google/uuid v1.6.0 33 | github.com/onsi/ginkgo v1.16.5 // indirect 34 | github.com/onsi/gomega v1.18.1 // indirect 35 | ) 36 | 37 | require ( 38 | github.com/KyleBanks/depth v1.2.1 // indirect 39 | github.com/bytedance/sonic v1.11.6 // indirect 40 | github.com/bytedance/sonic/loader v0.1.1 // indirect 41 | github.com/cloudwego/base64x v0.1.4 // indirect 42 | github.com/cloudwego/iasm v0.2.0 // indirect 43 | github.com/fsnotify/fsnotify v1.7.0 // indirect 44 | github.com/gabriel-vasile/mimetype v1.4.3 // indirect 45 | github.com/gin-contrib/sse v0.1.0 // indirect 46 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 47 | github.com/go-openapi/jsonreference v0.21.0 // indirect 48 | github.com/go-openapi/spec v0.21.0 // indirect 49 | github.com/go-openapi/swag v0.23.0 // indirect 50 | github.com/go-playground/locales v0.14.1 // indirect 51 | github.com/go-playground/universal-translator v0.18.1 // indirect 52 | github.com/goccy/go-json v0.10.2 // indirect 53 | github.com/golang-jwt/jwt v3.2.2+incompatible 54 | github.com/hashicorp/hcl v1.0.0 // indirect 55 | github.com/jackc/pgpassfile v1.0.0 // indirect 56 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect 57 | github.com/jackc/pgx/v5 v5.5.5 // indirect 58 | github.com/jackc/puddle/v2 v2.2.1 // indirect 59 | github.com/jinzhu/inflection v1.0.0 // indirect 60 | github.com/jinzhu/now v1.1.5 // indirect 61 | github.com/josharian/intern v1.0.0 // indirect 62 | github.com/json-iterator/go v1.1.12 // indirect 63 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect 64 | github.com/leodido/go-urn v1.4.0 // indirect 65 | github.com/magiconair/properties v1.8.7 // indirect 66 | github.com/mailru/easyjson v0.7.7 // indirect 67 | github.com/mattn/go-colorable v0.1.13 // indirect 68 | github.com/mattn/go-isatty v0.0.20 // indirect 69 | github.com/mitchellh/mapstructure v1.5.0 // indirect 70 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 71 | github.com/modern-go/reflect2 v1.0.2 // indirect 72 | github.com/patrickmn/go-cache v2.1.0+incompatible // indirect 73 | github.com/pelletier/go-toml/v2 v2.2.2 // indirect 74 | github.com/pkg/errors v0.9.1 // indirect 75 | github.com/sagikazarmark/locafero v0.4.0 // indirect 76 | github.com/sagikazarmark/slog-shim v0.1.0 // indirect 77 | github.com/sourcegraph/conc v0.3.0 // indirect 78 | github.com/spf13/afero v1.11.0 // indirect 79 | github.com/spf13/cast v1.6.0 // indirect 80 | github.com/spf13/pflag v1.0.5 // indirect 81 | github.com/subosito/gotenv v1.6.0 // indirect 82 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 83 | github.com/ugorji/go/codec v1.2.12 // indirect 84 | go.uber.org/atomic v1.9.0 // indirect 85 | go.uber.org/multierr v1.9.0 // indirect 86 | golang.org/x/arch v0.8.0 // indirect 87 | golang.org/x/crypto v0.35.0 88 | golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect 89 | golang.org/x/net v0.33.0 // indirect 90 | golang.org/x/sync v0.11.0 // indirect 91 | golang.org/x/sys v0.30.0 // indirect 92 | golang.org/x/text v0.22.0 // indirect 93 | golang.org/x/time v0.5.0 94 | golang.org/x/tools v0.27.0 // indirect 95 | google.golang.org/protobuf v1.36.5 // indirect 96 | gopkg.in/ini.v1 v1.67.0 // indirect 97 | gopkg.in/yaml.v3 v3.0.1 // indirect 98 | ) 99 | -------------------------------------------------------------------------------- /src/infra/cache/redis.go: -------------------------------------------------------------------------------- 1 | package cache 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/go-redis/redis/v7" 9 | "github.com/omidhaqi/clean-web-api/config" 10 | ) 11 | 12 | var redisClient *redis.Client 13 | 14 | func InitRedis(cfg *config.Config) error { 15 | redisClient = redis.NewClient(&redis.Options{ 16 | Addr: fmt.Sprintf("%s:%s", cfg.Redis.Host, cfg.Redis.Port), 17 | Password: cfg.Redis.Password, 18 | DB: 0, 19 | DialTimeout: cfg.Redis.DialTimeout * time.Second, 20 | ReadTimeout: cfg.Redis.ReadTimeout * time.Second, 21 | WriteTimeout: cfg.Redis.WriteTimeout * time.Second, 22 | PoolSize: cfg.Redis.PoolSize, 23 | PoolTimeout: cfg.Redis.PoolTimeout, 24 | IdleTimeout: 500 * time.Millisecond, 25 | IdleCheckFrequency: cfg.Redis.IdleCheckFrequency * time.Millisecond, 26 | }) 27 | 28 | _, err := redisClient.Ping().Result() 29 | if err != nil { 30 | return err 31 | } 32 | return nil 33 | } 34 | 35 | func GetRedis() *redis.Client { 36 | return redisClient 37 | } 38 | 39 | func CloseRedis() { 40 | redisClient.Close() 41 | } 42 | 43 | func Set[T any](c *redis.Client, key string, value T, duration time.Duration) error { 44 | v, err := json.Marshal(value) 45 | if err != nil { 46 | return err 47 | } 48 | return c.Set(key, v, duration).Err() 49 | } 50 | 51 | func Get[T any](c *redis.Client, key string) (T, error) { 52 | var dest T = *new(T) 53 | v, err := c.Get(key).Result() 54 | if err != nil { 55 | return dest, err 56 | } 57 | err = json.Unmarshal([]byte(v), &dest) 58 | if err != nil { 59 | return dest, err 60 | } 61 | return dest, nil 62 | } 63 | -------------------------------------------------------------------------------- /src/infra/persistence/database/postgres.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "time" 7 | 8 | "github.com/omidhaqi/clean-web-api/config" 9 | "gorm.io/driver/postgres" 10 | "gorm.io/gorm" 11 | ) 12 | 13 | var dbClient *gorm.DB 14 | 15 | func InitDb(cfg *config.Config) error { 16 | var err error 17 | cnn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s TimeZone=Asia/Tehran", 18 | cfg.Postgres.Host, cfg.Postgres.Port, cfg.Postgres.User, cfg.Postgres.Password, 19 | cfg.Postgres.DbName, cfg.Postgres.SSLMode) 20 | 21 | dbClient, err = gorm.Open(postgres.Open(cnn), &gorm.Config{}) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | sqlDb, _ := dbClient.DB() 27 | err = sqlDb.Ping() 28 | if err != nil { 29 | return err 30 | } 31 | 32 | sqlDb.SetMaxIdleConns(cfg.Postgres.MaxIdleConns) 33 | sqlDb.SetMaxOpenConns(cfg.Postgres.MaxOpenConns) 34 | sqlDb.SetConnMaxLifetime(cfg.Postgres.ConnMaxLifetime * time.Minute) 35 | 36 | log.Println("Db connection established") 37 | return nil 38 | } 39 | 40 | func GetDb() *gorm.DB { 41 | return dbClient 42 | } 43 | 44 | func CloseDb() { 45 | con, _ := dbClient.DB() 46 | con.Close() 47 | } 48 | -------------------------------------------------------------------------------- /src/infra/persistence/database/query_builder.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | 8 | "github.com/omidhaqi/clean-web-api/common" 9 | "gorm.io/gorm" 10 | 11 | filter "github.com/omidhaqi/clean-web-api/domain/filter" 12 | ) 13 | 14 | type PreloadEntity struct { 15 | Entity string 16 | } 17 | 18 | // GenerateDynamicQuery 19 | func GenerateDynamicQuery[T any](filter *filter.DynamicFilter) string { 20 | t := new(T) 21 | typeT := reflect.TypeOf(*t) 22 | query := make([]string, 0) 23 | query = append(query, "deleted_by is null") 24 | if filter.Filter != nil { 25 | for name, filter := range filter.Filter { 26 | if fld, ok := typeT.FieldByName(name); ok { 27 | query = append(query, GenerateDynamicFilter(fld, filter)) 28 | } 29 | } 30 | } 31 | return strings.Join(query, " AND ") 32 | } 33 | 34 | func GenerateDynamicFilter(fld reflect.StructField, filter filter.Filter) string { 35 | conditionQuery := "" 36 | fld.Name = common.ToSnakeCase(fld.Name) 37 | switch filter.Type { 38 | case "contains": 39 | conditionQuery = fmt.Sprintf("%s ILike '%%%s%%'", fld.Name, filter.From) 40 | case "notContains": 41 | conditionQuery = fmt.Sprintf("%s not ILike '%%%s%%'", fld.Name, filter.From) 42 | case "startsWith": 43 | conditionQuery = fmt.Sprintf("%s ILike '%s%%'", fld.Name, filter.From) 44 | case "endsWith": 45 | conditionQuery = fmt.Sprintf("%s ILike '%%%s'", fld.Name, filter.From) 46 | case "equals": 47 | conditionQuery = fmt.Sprintf("%s = '%s'", fld.Name, filter.From) 48 | case "notEqual": 49 | conditionQuery = fmt.Sprintf("%s != '%s'", fld.Name, filter.From) 50 | case "lessThan": 51 | conditionQuery = fmt.Sprintf("%s < %s", fld.Name, filter.From) 52 | case "lessThanOrEqual": 53 | conditionQuery = fmt.Sprintf("%s <= %s", fld.Name, filter.From) 54 | case "greaterThan": 55 | conditionQuery = fmt.Sprintf("%s > %s", fld.Name, filter.From) 56 | case "greaterThanOrEqual": 57 | conditionQuery = fmt.Sprintf("%s >= %s", fld.Name, filter.From) 58 | case "inRange": 59 | if fld.Type.Kind() == reflect.String { 60 | conditionQuery = fmt.Sprintf("%s >= '%s%%' AND ", fld.Name, filter.From) 61 | conditionQuery += fmt.Sprintf("%s <= '%%%s'", fld.Name, filter.To) 62 | } else { 63 | conditionQuery = fmt.Sprintf("%s >= %s AND ", fld.Name, filter.From) 64 | conditionQuery += fmt.Sprintf("%s <= %s", fld.Name, filter.To) 65 | } 66 | } 67 | return conditionQuery 68 | } 69 | 70 | // generateDynamicSort 71 | func GenerateDynamicSort[T any](filter *filter.DynamicFilter) string { 72 | t := new(T) 73 | typeT := reflect.TypeOf(*t) 74 | sort := make([]string, 0) 75 | if filter.Sort != nil { 76 | for _, tp := range *filter.Sort { 77 | fld, ok := typeT.FieldByName(tp.ColId) 78 | if ok && (tp.Sort == "asc" || tp.Sort == "desc") { 79 | fld.Name = common.ToSnakeCase(fld.Name) 80 | sort = append(sort, fmt.Sprintf("%s %s", fld.Name, tp.Sort)) 81 | } 82 | } 83 | } 84 | return strings.Join(sort, ", ") 85 | } 86 | 87 | // Preload 88 | func Preload(db *gorm.DB, preloads []PreloadEntity) *gorm.DB { 89 | for _, item := range preloads { 90 | db = db.Preload(item.Entity) 91 | } 92 | return db 93 | } 94 | -------------------------------------------------------------------------------- /src/infra/persistence/repository/postgres_user_repository.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/constant" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | database "github.com/omidhaqi/clean-web-api/infra/persistence/database" 10 | "github.com/omidhaqi/clean-web-api/pkg/logging" 11 | "golang.org/x/crypto/bcrypt" 12 | "gorm.io/gorm" 13 | ) 14 | 15 | const userFilterExp string = "username = ?" 16 | const countFilterExp string = "count(*) > 0" 17 | 18 | type PostgresUserRepository struct { 19 | *BaseRepository[model.User] 20 | } 21 | 22 | func NewUserRepository(cfg *config.Config) *PostgresUserRepository { 23 | var preloads []database.PreloadEntity = []database.PreloadEntity{} 24 | return &PostgresUserRepository{BaseRepository: NewBaseRepository[model.User](cfg, preloads)} 25 | } 26 | 27 | func (r *PostgresUserRepository) CreateUser(ctx context.Context, u model.User) (model.User, error) { 28 | 29 | roleId, err := r.GetDefaultRole(ctx) 30 | if err != nil { 31 | r.logger.Error(logging.Postgres, logging.DefaultRoleNotFound, err.Error(), nil) 32 | return u, err 33 | } 34 | tx := r.database.WithContext(ctx).Begin() 35 | err = tx.Create(&u).Error 36 | if err != nil { 37 | tx.Rollback() 38 | r.logger.Error(logging.Postgres, logging.Rollback, err.Error(), nil) 39 | return u, err 40 | } 41 | err = tx.Create(&model.UserRole{RoleId: roleId, UserId: u.Id}).Error 42 | if err != nil { 43 | tx.Rollback() 44 | r.logger.Error(logging.Postgres, logging.Rollback, err.Error(), nil) 45 | return u, err 46 | } 47 | tx.Commit() 48 | return u, nil 49 | } 50 | 51 | func (r *PostgresUserRepository) FetchUserInfo(ctx context.Context, username string, password string) (model.User, error) { 52 | var user model.User 53 | err := r.database.WithContext(ctx). 54 | Model(&model.User{}). 55 | Where(userFilterExp, username). 56 | Preload("UserRoles", func(tx *gorm.DB) *gorm.DB { 57 | return tx.Preload("Role") 58 | }). 59 | Find(&user).Error 60 | 61 | if err != nil { 62 | return user, err 63 | } 64 | 65 | err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) 66 | if err != nil { 67 | return user, err 68 | } 69 | 70 | return user, nil 71 | } 72 | 73 | func (r *PostgresUserRepository) ExistsEmail(ctx context.Context, email string) (bool, error) { 74 | var exists bool 75 | if err := r.database.WithContext(ctx).Model(&model.User{}). 76 | Select(countFilterExp). 77 | Where("email = ?", email). 78 | Find(&exists). 79 | Error; err != nil { 80 | r.logger.Error(logging.Postgres, logging.Select, err.Error(), nil) 81 | return false, err 82 | } 83 | return exists, nil 84 | } 85 | 86 | func (r *PostgresUserRepository) ExistsUsername(ctx context.Context, username string) (bool, error) { 87 | var exists bool 88 | if err := r.database.WithContext(ctx).Model(&model.User{}). 89 | Select(countFilterExp). 90 | Where(userFilterExp, username). 91 | Find(&exists). 92 | Error; err != nil { 93 | r.logger.Error(logging.Postgres, logging.Select, err.Error(), nil) 94 | return false, err 95 | } 96 | return exists, nil 97 | } 98 | 99 | func (r *PostgresUserRepository) ExistsMobileNumber(ctx context.Context, mobileNumber string) (bool, error) { 100 | var exists bool 101 | if err := r.database.WithContext(ctx).Model(&model.User{}). 102 | Select(countFilterExp). 103 | Where("mobile_number = ?", mobileNumber). 104 | Find(&exists). 105 | Error; err != nil { 106 | r.logger.Error(logging.Postgres, logging.Select, err.Error(), nil) 107 | return false, err 108 | } 109 | return exists, nil 110 | } 111 | 112 | func (r *PostgresUserRepository) GetDefaultRole(ctx context.Context) (roleId int, err error) { 113 | 114 | if err = r.database.WithContext(ctx).Model(&model.Role{}). 115 | Select("id"). 116 | Where("name = ?", constant.DefaultRoleName). 117 | First(&roleId).Error; err != nil { 118 | return 0, err 119 | } 120 | return roleId, nil 121 | } 122 | -------------------------------------------------------------------------------- /src/pkg/limiter/ip_limiter.go: -------------------------------------------------------------------------------- 1 | package limiter 2 | 3 | import ( 4 | "sync" 5 | 6 | "golang.org/x/time/rate" 7 | ) 8 | 9 | // IPRateLimiter . 10 | type IPRateLimiter struct { 11 | ips map[string]*rate.Limiter 12 | mu *sync.RWMutex 13 | r rate.Limit 14 | b int 15 | } 16 | 17 | // NewIPRateLimiter . 18 | func NewIPRateLimiter(r rate.Limit, b int) *IPRateLimiter { 19 | i := &IPRateLimiter{ 20 | ips: make(map[string]*rate.Limiter), 21 | mu: &sync.RWMutex{}, 22 | r: r, 23 | b: b, 24 | } 25 | 26 | return i 27 | } 28 | 29 | // AddIP creates a new rate limiter and adds it to the ips map, 30 | // using the IP address as the key 31 | func (i *IPRateLimiter) AddIP(ip string) *rate.Limiter { 32 | i.mu.Lock() 33 | defer i.mu.Unlock() 34 | 35 | limiter := rate.NewLimiter(i.r, i.b) 36 | 37 | i.ips[ip] = limiter 38 | 39 | return limiter 40 | } 41 | 42 | // GetLimiter returns the rate limiter for the provided IP address if it exists. 43 | // Otherwise calls AddIP to add IP address to the map 44 | func (i *IPRateLimiter) GetLimiter(ip string) *rate.Limiter { 45 | i.mu.Lock() 46 | limiter, exists := i.ips[ip] 47 | 48 | if !exists { 49 | i.mu.Unlock() 50 | return i.AddIP(ip) 51 | } 52 | 53 | i.mu.Unlock() 54 | 55 | return limiter 56 | } 57 | -------------------------------------------------------------------------------- /src/pkg/logging/category.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | type Category string 4 | type SubCategory string 5 | type ExtraKey string 6 | 7 | const ( 8 | General Category = "General" 9 | IO Category = "IO" 10 | Internal Category = "Internal" 11 | Postgres Category = "Postgres" 12 | Redis Category = "Redis" 13 | Validation Category = "Validation" 14 | RequestResponse Category = "RequestResponse" 15 | Prometheus Category = "Prometheus" 16 | ) 17 | 18 | const ( 19 | // General 20 | Startup SubCategory = "Startup" 21 | ExternalService SubCategory = "ExternalService" 22 | 23 | // Postgres 24 | Migration SubCategory = "Migration" 25 | Select SubCategory = "Select" 26 | Rollback SubCategory = "Rollback" 27 | Update SubCategory = "Update" 28 | Delete SubCategory = "Delete" 29 | Insert SubCategory = "Insert" 30 | 31 | // Internal 32 | Api SubCategory = "Api" 33 | HashPassword SubCategory = "HashPassword" 34 | DefaultRoleNotFound SubCategory = "DefaultRoleNotFound" 35 | FailedToCreateUser SubCategory = "FailedToCreateUser" 36 | 37 | // Validation 38 | MobileValidation SubCategory = "MobileValidation" 39 | PasswordValidation SubCategory = "PasswordValidation" 40 | 41 | // IO 42 | RemoveFile SubCategory = "RemoveFile" 43 | ) 44 | 45 | const ( 46 | AppName ExtraKey = "AppName" 47 | LoggerName ExtraKey = "Logger" 48 | ClientIp ExtraKey = "ClientIp" 49 | HostIp ExtraKey = "HostIp" 50 | Method ExtraKey = "Method" 51 | StatusCode ExtraKey = "StatusCode" 52 | BodySize ExtraKey = "BodySize" 53 | Path ExtraKey = "Path" 54 | Latency ExtraKey = "Latency" 55 | RequestBody ExtraKey = "RequestBody" 56 | ResponseBody ExtraKey = "ResponseBody" 57 | ErrorMessage ExtraKey = "ErrorMessage" 58 | ) 59 | -------------------------------------------------------------------------------- /src/pkg/logging/log_helper.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | func logParamsToZapParams(keys map[ExtraKey]interface{}) []interface{} { 4 | params := make([]interface{}, 0, len(keys)) 5 | 6 | for k, v := range keys { 7 | params = append(params, string(k)) 8 | params = append(params, v) 9 | } 10 | 11 | return params 12 | } 13 | 14 | func logParamsToZeroParams(keys map[ExtraKey]interface{}) map[string]interface{} { 15 | params := map[string]interface{}{} 16 | 17 | for k, v := range keys { 18 | params[string(k)] = v 19 | } 20 | 21 | return params 22 | } 23 | -------------------------------------------------------------------------------- /src/pkg/logging/logger.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import "github.com/omidhaqi/clean-web-api/config" 4 | 5 | type Logger interface { 6 | Init() 7 | 8 | Debug(cat Category, sub SubCategory, msg string, extra map[ExtraKey]interface{}) 9 | Debugf(template string, args ...interface{}) 10 | 11 | Info(cat Category, sub SubCategory, msg string, extra map[ExtraKey]interface{}) 12 | Infof(template string, args ...interface{}) 13 | 14 | Warn(cat Category, sub SubCategory, msg string, extra map[ExtraKey]interface{}) 15 | Warnf(template string, args ...interface{}) 16 | 17 | Error(cat Category, sub SubCategory, msg string, extra map[ExtraKey]interface{}) 18 | Errorf(template string, args ...interface{}) 19 | 20 | Fatal(cat Category, sub SubCategory, msg string, extra map[ExtraKey]interface{}) 21 | Fatalf(template string, args ...interface{}) 22 | } 23 | 24 | func NewLogger(cfg *config.Config) Logger { 25 | if cfg.Logger.Logger == "zap" { 26 | return newZapLogger(cfg) 27 | } else if cfg.Logger.Logger == "zerolog" { 28 | return newZeroLogger(cfg) 29 | } 30 | panic("logger not supported") 31 | } 32 | -------------------------------------------------------------------------------- /src/pkg/logging/zap_logger.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/google/uuid" 8 | "github.com/omidhaqi/clean-web-api/config" 9 | "go.uber.org/zap" 10 | "go.uber.org/zap/zapcore" 11 | "gopkg.in/natefinch/lumberjack.v2" 12 | ) 13 | 14 | var zapSinLogger *zap.SugaredLogger 15 | 16 | type zapLogger struct { 17 | cfg *config.Config 18 | logger *zap.SugaredLogger 19 | } 20 | 21 | var zapLogLevelMapping = map[string]zapcore.Level{ 22 | "debug": zapcore.DebugLevel, 23 | "info": zapcore.InfoLevel, 24 | "warn": zapcore.WarnLevel, 25 | "error": zapcore.ErrorLevel, 26 | "fatal": zapcore.FatalLevel, 27 | } 28 | 29 | func newZapLogger(cfg *config.Config) *zapLogger { 30 | logger := &zapLogger{cfg: cfg} 31 | logger.Init() 32 | return logger 33 | } 34 | 35 | func (l *zapLogger) getLogLevel() zapcore.Level { 36 | level, exists := zapLogLevelMapping[l.cfg.Logger.Level] 37 | if !exists { 38 | return zapcore.DebugLevel 39 | } 40 | return level 41 | } 42 | 43 | func (l *zapLogger) Init() { 44 | once.Do(func() { 45 | fileName := fmt.Sprintf("%s%s-%s.%s", l.cfg.Logger.FilePath, time.Now().Format("2006-01-02"), uuid.New(), "log") 46 | w := zapcore.AddSync(&lumberjack.Logger{ 47 | Filename: fileName, 48 | MaxSize: 1, 49 | MaxAge: 20, 50 | LocalTime: true, 51 | MaxBackups: 5, 52 | Compress: true, 53 | }) 54 | 55 | config := zap.NewProductionEncoderConfig() 56 | config.EncodeTime = zapcore.ISO8601TimeEncoder 57 | 58 | core := zapcore.NewCore( 59 | zapcore.NewJSONEncoder(config), 60 | w, 61 | l.getLogLevel(), 62 | ) 63 | 64 | logger := zap.New(core, zap.AddCaller(), 65 | zap.AddCallerSkip(1), 66 | zap.AddStacktrace(zapcore.ErrorLevel), 67 | ).Sugar() 68 | 69 | zapSinLogger = logger.With("AppName", "MyApp", "LoggerName", "Zaplog") 70 | }) 71 | 72 | l.logger = zapSinLogger 73 | } 74 | 75 | func (l *zapLogger) Debug(cat Category, sub SubCategory, msg string, extra map[ExtraKey]interface{}) { 76 | params := prepareLogInfo(cat, sub, extra) 77 | 78 | l.logger.Debugw(msg, params...) 79 | } 80 | 81 | func (l *zapLogger) Debugf(template string, args ...interface{}) { 82 | l.logger.Debugf(template, args) 83 | } 84 | 85 | func (l *zapLogger) Info(cat Category, sub SubCategory, msg string, extra map[ExtraKey]interface{}) { 86 | params := prepareLogInfo(cat, sub, extra) 87 | l.logger.Infow(msg, params...) 88 | } 89 | 90 | func (l *zapLogger) Infof(template string, args ...interface{}) { 91 | l.logger.Infof(template, args) 92 | } 93 | 94 | func (l *zapLogger) Warn(cat Category, sub SubCategory, msg string, extra map[ExtraKey]interface{}) { 95 | params := prepareLogInfo(cat, sub, extra) 96 | l.logger.Warnw(msg, params...) 97 | } 98 | 99 | func (l *zapLogger) Warnf(template string, args ...interface{}) { 100 | l.logger.Warnf(template, args) 101 | } 102 | 103 | func (l *zapLogger) Error(cat Category, sub SubCategory, msg string, extra map[ExtraKey]interface{}) { 104 | params := prepareLogInfo(cat, sub, extra) 105 | l.logger.Errorw(msg, params...) 106 | } 107 | 108 | func (l *zapLogger) Errorf(template string, args ...interface{}) { 109 | l.logger.Errorf(template, args) 110 | } 111 | 112 | func (l *zapLogger) Fatal(cat Category, sub SubCategory, msg string, extra map[ExtraKey]interface{}) { 113 | params := prepareLogInfo(cat, sub, extra) 114 | l.logger.Fatalw(msg, params...) 115 | } 116 | 117 | func (l *zapLogger) Fatalf(template string, args ...interface{}) { 118 | l.logger.Fatalf(template, args) 119 | } 120 | 121 | func prepareLogInfo(cat Category, sub SubCategory, extra map[ExtraKey]interface{}) []interface{} { 122 | if extra == nil { 123 | extra = make(map[ExtraKey]interface{}) 124 | } 125 | extra["Category"] = cat 126 | extra["SubCategory"] = sub 127 | 128 | return logParamsToZapParams(extra) 129 | } 130 | -------------------------------------------------------------------------------- /src/pkg/logging/zero_logger.go: -------------------------------------------------------------------------------- 1 | package logging 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "sync" 7 | "time" 8 | 9 | "github.com/google/uuid" 10 | "github.com/omidhaqi/clean-web-api/config" 11 | "github.com/rs/zerolog" 12 | "github.com/rs/zerolog/pkgerrors" 13 | ) 14 | 15 | var once sync.Once 16 | var zeroSinLogger *zerolog.Logger 17 | 18 | type zeroLogger struct { 19 | cfg *config.Config 20 | logger *zerolog.Logger 21 | } 22 | 23 | var zeroLogLevelMapping = map[string]zerolog.Level{ 24 | "debug": zerolog.DebugLevel, 25 | "info": zerolog.InfoLevel, 26 | "warn": zerolog.WarnLevel, 27 | "error": zerolog.ErrorLevel, 28 | "fatal": zerolog.FatalLevel, 29 | } 30 | 31 | func newZeroLogger(cfg *config.Config) *zeroLogger { 32 | logger := &zeroLogger{cfg: cfg} 33 | logger.Init() 34 | return logger 35 | } 36 | 37 | func (l *zeroLogger) getLogLevel() zerolog.Level { 38 | level, exists := zeroLogLevelMapping[l.cfg.Logger.Level] 39 | if !exists { 40 | return zerolog.DebugLevel 41 | } 42 | return level 43 | } 44 | 45 | func (l *zeroLogger) Init() { 46 | once.Do(func() { 47 | 48 | zerolog.ErrorStackMarshaler = pkgerrors.MarshalStack 49 | fileName := fmt.Sprintf("%s%s-%s.%s", l.cfg.Logger.FilePath, time.Now().Format("2006-01-02"), uuid.New(), "log") 50 | 51 | file, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) 52 | if err != nil { 53 | panic("could not open log file") 54 | } 55 | 56 | var logger = zerolog.New(file). 57 | With(). 58 | Timestamp(). 59 | Str("AppName", "MyApp"). 60 | Str("LoggerName", "Zerolog"). 61 | Logger() 62 | zerolog.SetGlobalLevel(l.getLogLevel()) 63 | zeroSinLogger = &logger 64 | }) 65 | l.logger = zeroSinLogger 66 | } 67 | 68 | func (l *zeroLogger) Debug(cat Category, sub SubCategory, msg string, extra map[ExtraKey]interface{}) { 69 | 70 | l.logger. 71 | Debug(). 72 | Str("Category", string(cat)). 73 | Str("SubCategory", string(sub)). 74 | Fields(logParamsToZeroParams(extra)). 75 | Msg(msg) 76 | } 77 | 78 | func (l *zeroLogger) Debugf(template string, args ...interface{}) { 79 | l.logger. 80 | Debug(). 81 | Msgf(template, args...) 82 | } 83 | 84 | func (l *zeroLogger) Info(cat Category, sub SubCategory, msg string, extra map[ExtraKey]interface{}) { 85 | 86 | l.logger. 87 | Info(). 88 | Str("Category", string(cat)). 89 | Str("SubCategory", string(sub)). 90 | Fields(logParamsToZeroParams(extra)). 91 | Msg(msg) 92 | } 93 | 94 | func (l *zeroLogger) Infof(template string, args ...interface{}) { 95 | l.logger. 96 | Info(). 97 | Msgf(template, args...) 98 | } 99 | 100 | func (l *zeroLogger) Warn(cat Category, sub SubCategory, msg string, extra map[ExtraKey]interface{}) { 101 | 102 | l.logger. 103 | Warn(). 104 | Str("Category", string(cat)). 105 | Str("SubCategory", string(sub)). 106 | Fields(logParamsToZeroParams(extra)). 107 | Msg(msg) 108 | } 109 | 110 | func (l *zeroLogger) Warnf(template string, args ...interface{}) { 111 | l.logger. 112 | Warn(). 113 | Msgf(template, args...) 114 | } 115 | 116 | func (l *zeroLogger) Error(cat Category, sub SubCategory, msg string, extra map[ExtraKey]interface{}) { 117 | 118 | l.logger. 119 | Error(). 120 | Str("Category", string(cat)). 121 | Str("SubCategory", string(sub)). 122 | Fields(logParamsToZeroParams(extra)). 123 | Msg(msg) 124 | } 125 | 126 | func (l *zeroLogger) Errorf(template string, args ...interface{}) { 127 | l.logger. 128 | Error(). 129 | Msgf(template, args...) 130 | } 131 | 132 | func (l *zeroLogger) Fatal(cat Category, sub SubCategory, msg string, extra map[ExtraKey]interface{}) { 133 | 134 | l.logger. 135 | Fatal(). 136 | Str("Category", string(cat)). 137 | Str("SubCategory", string(sub)). 138 | Fields(logParamsToZeroParams(extra)). 139 | Msg(msg) 140 | } 141 | 142 | func (l *zeroLogger) Fatalf(template string, args ...interface{}) { 143 | l.logger. 144 | Fatal(). 145 | Msgf(template, args...) 146 | } 147 | -------------------------------------------------------------------------------- /src/pkg/metrics/counters.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import "github.com/prometheus/client_golang/prometheus" 4 | 5 | 6 | 7 | var DbCall = prometheus.NewCounterVec( 8 | prometheus.CounterOpts{ 9 | Name: "db_calls_total", 10 | Help: "Number of database calls", 11 | },[]string{"type_name","operation_name", "status"}, 12 | ) -------------------------------------------------------------------------------- /src/pkg/metrics/histograms.go: -------------------------------------------------------------------------------- 1 | package metrics 2 | 3 | import "github.com/prometheus/client_golang/prometheus" 4 | 5 | var HttpDuration = prometheus.NewHistogramVec( 6 | prometheus.HistogramOpts{ 7 | Name: "http_response_time", 8 | Help: "Duration of HTTP requests", 9 | Buckets: []float64{1, 2, 5, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000}, 10 | }, []string{"path", "method", "status_code"}) -------------------------------------------------------------------------------- /src/pkg/service_errors/error_code.go: -------------------------------------------------------------------------------- 1 | package service_errors 2 | 3 | const ( 4 | // Token 5 | UnExpectedError = "Expected error" 6 | ClaimsNotFound = "Claims not found" 7 | TokenRequired = "token required" 8 | TokenExpired = "token expired" 9 | TokenInvalid = "token invalid" 10 | 11 | // OTP 12 | OptExists = "Otp exists" 13 | OtpUsed = "Otp used" 14 | OtpNotValid = "Otp invalid" 15 | 16 | // User 17 | EmailExists = "Email exists" 18 | UsernameExists = "Username exists" 19 | PermissionDenied = "Permission denied" 20 | UsernameOrPasswordInvalid = "username or password invalid" 21 | 22 | // DB 23 | RecordNotFound = "record not found" 24 | ) 25 | -------------------------------------------------------------------------------- /src/pkg/service_errors/service_error.go: -------------------------------------------------------------------------------- 1 | package service_errors 2 | 3 | type ServiceError struct { 4 | EndUserMessage string `json:"endUserMessage"` 5 | TechnicalMessage string `json:"technicalMessage"` 6 | Err error 7 | } 8 | 9 | func (s *ServiceError) Error() string { 10 | return s.EndUserMessage 11 | } 12 | -------------------------------------------------------------------------------- /src/tests/integration/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmidHaqi/clean-web-api/fd7e5d8744e741f7a4d85c4bd8403edf7a3c8034/src/tests/integration/.gitkeep -------------------------------------------------------------------------------- /src/tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OmidHaqi/clean-web-api/fd7e5d8744e741f7a4d85c4bd8403edf7a3c8034/src/tests/unit/.gitkeep -------------------------------------------------------------------------------- /src/usecase/base_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/common" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | "github.com/omidhaqi/clean-web-api/domain/filter" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/pkg/logging" 11 | ) 12 | 13 | type BaseUsecase[TEntity any, TCreate any, TUpdate any, TResponse any] struct { 14 | logger logging.Logger 15 | repository repository.BaseRepository[TEntity] 16 | } 17 | 18 | func NewBaseUsecase[TEntity any, TCreate any, TUpdate any, TResponse any](cfg *config.Config, repository repository.BaseRepository[TEntity]) *BaseUsecase[TEntity, TCreate, TUpdate, TResponse] { 19 | logger := logging.NewLogger(cfg) 20 | return &BaseUsecase[TEntity, TCreate, TUpdate, TResponse]{ 21 | repository: repository, 22 | logger: logger, 23 | } 24 | } 25 | 26 | func (u *BaseUsecase[TEntity, TCreate, TUpdate, TResponse]) Create(ctx context.Context, req TCreate) (TResponse, error) { 27 | var response TResponse 28 | entity, _ := common.TypeConverter[TEntity](req) 29 | 30 | entity, err := u.repository.Create(ctx, entity) 31 | if err != nil { 32 | return response, err 33 | } 34 | 35 | response, _ = common.TypeConverter[TResponse](entity) 36 | return response, nil 37 | } 38 | 39 | func (u *BaseUsecase[TEntity, TCreate, TUpdate, TResponse]) Update(ctx context.Context, id int, req TUpdate) (TResponse, error) { 40 | var response TResponse 41 | updateMap, _ := common.TypeConverter[map[string]interface{}](req) 42 | 43 | entity, err := u.repository.Update(ctx, id, updateMap) 44 | if err != nil { 45 | return response, err 46 | } 47 | response, _ = common.TypeConverter[TResponse](entity) 48 | 49 | return response, nil 50 | } 51 | 52 | func (u *BaseUsecase[TEntity, TCreate, TUpdate, TResponse]) Delete(ctx context.Context, id int) error { 53 | 54 | return u.repository.Delete(ctx, id) 55 | } 56 | 57 | func (u *BaseUsecase[TEntity, TCreate, TUpdate, TResponse]) GetById(ctx context.Context, id int) (TResponse, error) { 58 | var response TResponse 59 | entity, err := u.repository.GetById(ctx, id) 60 | if err != nil { 61 | return response, err 62 | } 63 | return common.TypeConverter[TResponse](entity) 64 | } 65 | 66 | func (u *BaseUsecase[TEntity, TCreate, TUpdate, TResponse]) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[TResponse], error) { 67 | var response *filter.PagedList[TResponse] 68 | count, entities, err := u.repository.GetByFilter(ctx, req) 69 | if err != nil { 70 | return response, err 71 | } 72 | 73 | return filter.Paginate[TEntity, TResponse](count, entities, req.PageNumber, int64(req.PageSize)) 74 | } 75 | -------------------------------------------------------------------------------- /src/usecase/carModelColor_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/domain/filter" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/usecase/dto" 11 | ) 12 | 13 | type CarModelColorUsecase struct { 14 | base *BaseUsecase[model.CarModelColor, dto.CreateCarModelColor, dto.UpdateCarModelColor, dto.CarModelColor] 15 | } 16 | 17 | func NewCarModelColorUsecase(cfg *config.Config, repository repository.CarModelColorRepository) *CarModelColorUsecase { 18 | return &CarModelColorUsecase{ 19 | base: NewBaseUsecase[model.CarModelColor, dto.CreateCarModelColor, dto.UpdateCarModelColor, dto.CarModelColor](cfg, repository), 20 | } 21 | } 22 | 23 | // Create 24 | func (u *CarModelColorUsecase) Create(ctx context.Context, req dto.CreateCarModelColor) (dto.CarModelColor, error) { 25 | return u.base.Create(ctx, req) 26 | } 27 | 28 | // Update 29 | func (u *CarModelColorUsecase) Update(ctx context.Context, id int, req dto.UpdateCarModelColor) (dto.CarModelColor, error) { 30 | return u.base.Update(ctx, id, req) 31 | } 32 | 33 | // Delete 34 | func (u *CarModelColorUsecase) Delete(ctx context.Context, id int) error { 35 | return u.base.Delete(ctx, id) 36 | } 37 | 38 | // Get By Id 39 | func (u *CarModelColorUsecase) GetById(ctx context.Context, id int) (dto.CarModelColor, error) { 40 | return u.base.GetById(ctx, id) 41 | } 42 | 43 | // Get By Filter 44 | func (u *CarModelColorUsecase) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[dto.CarModelColor], error) { 45 | return u.base.GetByFilter(ctx, req) 46 | } -------------------------------------------------------------------------------- /src/usecase/carModelComment_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/domain/filter" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/usecase/dto" 11 | ) 12 | 13 | 14 | type CarModelCommentUsecase struct { 15 | base *BaseUsecase[model.CarModelComment, dto.CreateCarModelComment, dto.UpdateCarModelComment, dto.CarModelComment] 16 | } 17 | 18 | func NewCarModelCommentUsecase(cfg *config.Config, repository repository.CarModelCommentRepository) *CarModelCommentUsecase { 19 | return &CarModelCommentUsecase{ 20 | base: NewBaseUsecase[model.CarModelComment, dto.CreateCarModelComment, dto.UpdateCarModelComment, dto.CarModelComment](cfg, repository), 21 | } 22 | } 23 | 24 | // Create 25 | func (u *CarModelCommentUsecase) Create(ctx context.Context, req dto.CreateCarModelComment) (dto.CarModelComment, error) { 26 | return u.base.Create(ctx, req) 27 | } 28 | 29 | // Update 30 | func (u *CarModelCommentUsecase) Update(ctx context.Context, id int, req dto.UpdateCarModelComment) (dto.CarModelComment, error) { 31 | return u.base.Update(ctx, id, req) 32 | } 33 | 34 | // Delete 35 | func (u *CarModelCommentUsecase) Delete(ctx context.Context, id int) error { 36 | return u.base.Delete(ctx, id) 37 | } 38 | 39 | // Get By Id 40 | func (u *CarModelCommentUsecase) GetById(ctx context.Context, id int) (dto.CarModelComment, error) { 41 | return u.base.GetById(ctx, id) 42 | } 43 | 44 | // Get By Filter 45 | func (u *CarModelCommentUsecase) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[dto.CarModelComment], error) { 46 | return u.base.GetByFilter(ctx, req) 47 | } -------------------------------------------------------------------------------- /src/usecase/carModelImage_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/domain/filter" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/usecase/dto" 11 | ) 12 | 13 | 14 | type CarModelImageUsecase struct { 15 | base *BaseUsecase[model.CarModelImage, dto.CreateCarModelImage, dto.UpdateCarModelImage, dto.CarModelImage] 16 | } 17 | 18 | func NewCarModelImageUsecase(cfg *config.Config, repository repository.CarModelImageRepository) *CarModelImageUsecase { 19 | return &CarModelImageUsecase{ 20 | base: NewBaseUsecase[model.CarModelImage, dto.CreateCarModelImage, dto.UpdateCarModelImage, dto.CarModelImage](cfg, repository), 21 | } 22 | } 23 | 24 | // Create 25 | func (u *CarModelImageUsecase) Create(ctx context.Context, req dto.CreateCarModelImage) (dto.CarModelImage, error) { 26 | return u.base.Create(ctx, req) 27 | } 28 | 29 | // Update 30 | func (u *CarModelImageUsecase) Update(ctx context.Context, id int, req dto.UpdateCarModelImage) (dto.CarModelImage, error) { 31 | return u.base.Update(ctx, id, req) 32 | } 33 | 34 | // Delete 35 | func (u *CarModelImageUsecase) Delete(ctx context.Context, id int) error { 36 | return u.base.Delete(ctx, id) 37 | } 38 | 39 | // Get By Id 40 | func (u *CarModelImageUsecase) GetById(ctx context.Context, id int) (dto.CarModelImage, error) { 41 | return u.base.GetById(ctx, id) 42 | } 43 | 44 | // Get By Filter 45 | func (u *CarModelImageUsecase) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[dto.CarModelImage], error) { 46 | return u.base.GetByFilter(ctx, req) 47 | } -------------------------------------------------------------------------------- /src/usecase/carModelPriceHistory_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/domain/filter" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/usecase/dto" 11 | ) 12 | 13 | type CarModelPriceHistoryUsecase struct { 14 | base *BaseUsecase[model.CarModelPriceHistory, dto.CreateCarModelPriceHistory, dto.UpdateCarModelPriceHistory, dto.CarModelPriceHistory] 15 | } 16 | 17 | func NewCarModelPriceHistoryUsecase(cfg *config.Config, repository repository.CarModelPriceHistoryRepository) *CarModelPriceHistoryUsecase { 18 | return &CarModelPriceHistoryUsecase{ 19 | base: NewBaseUsecase[model.CarModelPriceHistory, dto.CreateCarModelPriceHistory, dto.UpdateCarModelPriceHistory, dto.CarModelPriceHistory](cfg, repository), 20 | } 21 | } 22 | 23 | // Create 24 | func (u *CarModelPriceHistoryUsecase) Create(ctx context.Context, req dto.CreateCarModelPriceHistory) (dto.CarModelPriceHistory, error) { 25 | return u.base.Create(ctx, req) 26 | } 27 | 28 | // Update 29 | func (u *CarModelPriceHistoryUsecase) Update(ctx context.Context, id int, req dto.UpdateCarModelPriceHistory) (dto.CarModelPriceHistory, error) { 30 | return u.base.Update(ctx, id, req) 31 | } 32 | 33 | // Delete 34 | func (u *CarModelPriceHistoryUsecase) Delete(ctx context.Context, id int) error { 35 | return u.base.Delete(ctx, id) 36 | } 37 | 38 | // Get By Id 39 | func (u *CarModelPriceHistoryUsecase) GetById(ctx context.Context, id int) (dto.CarModelPriceHistory, error) { 40 | return u.base.GetById(ctx, id) 41 | } 42 | 43 | // Get By Filter 44 | func (u *CarModelPriceHistoryUsecase) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[dto.CarModelPriceHistory], error) { 45 | return u.base.GetByFilter(ctx, req) 46 | } -------------------------------------------------------------------------------- /src/usecase/carModelProperty_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/domain/filter" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/usecase/dto" 11 | ) 12 | 13 | 14 | type CarModelPropertyUsecase struct { 15 | base *BaseUsecase[model.CarModelProperty, dto.CreateCarModelProperty, dto.UpdateCarModelProperty, dto.CarModelProperty] 16 | } 17 | 18 | func NewCarModelPropertyUsecase(cfg *config.Config, repository repository.CarModelPropertyRepository) *CarModelPropertyUsecase { 19 | return &CarModelPropertyUsecase{ 20 | base: NewBaseUsecase[model.CarModelProperty, dto.CreateCarModelProperty, dto.UpdateCarModelProperty, dto.CarModelProperty](cfg, repository), 21 | } 22 | } 23 | 24 | // Create 25 | func (u *CarModelPropertyUsecase) Create(ctx context.Context, req dto.CreateCarModelProperty) (dto.CarModelProperty, error) { 26 | return u.base.Create(ctx, req) 27 | } 28 | 29 | // Update 30 | func (u *CarModelPropertyUsecase) Update(ctx context.Context, id int, req dto.UpdateCarModelProperty) (dto.CarModelProperty, error) { 31 | return u.base.Update(ctx, id, req) 32 | } 33 | 34 | // Delete 35 | func (u *CarModelPropertyUsecase) Delete(ctx context.Context, id int) error { 36 | return u.base.Delete(ctx, id) 37 | } 38 | 39 | // Get By Id 40 | func (u *CarModelPropertyUsecase) GetById(ctx context.Context, id int) (dto.CarModelProperty, error) { 41 | return u.base.GetById(ctx, id) 42 | } 43 | 44 | // Get By Filter 45 | func (u *CarModelPropertyUsecase) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[dto.CarModelProperty], error) { 46 | return u.base.GetByFilter(ctx, req) 47 | } -------------------------------------------------------------------------------- /src/usecase/carModelYear_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/domain/filter" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/usecase/dto" 11 | ) 12 | 13 | type CarModelYearUsecase struct { 14 | base *BaseUsecase[model.CarModelYear, dto.CreateCarModelYear, dto.UpdateCarModelYear, dto.CarModelYear] 15 | } 16 | 17 | func NewCarModelYearUsecase(cfg *config.Config, repository repository.CarModelYearRepository) *CarModelYearUsecase { 18 | return &CarModelYearUsecase{ 19 | base: NewBaseUsecase[model.CarModelYear, dto.CreateCarModelYear, dto.UpdateCarModelYear, dto.CarModelYear](cfg, repository), 20 | } 21 | } 22 | 23 | // Create 24 | func (u *CarModelYearUsecase) Create(ctx context.Context, req dto.CreateCarModelYear) (dto.CarModelYear, error) { 25 | return u.base.Create(ctx, req) 26 | } 27 | 28 | // Update 29 | func (u *CarModelYearUsecase) Update(ctx context.Context, id int, req dto.UpdateCarModelYear) (dto.CarModelYear, error) { 30 | return u.base.Update(ctx, id, req) 31 | } 32 | 33 | // Delete 34 | func (u *CarModelYearUsecase) Delete(ctx context.Context, id int) error { 35 | return u.base.Delete(ctx, id) 36 | } 37 | 38 | // Get By Id 39 | func (u *CarModelYearUsecase) GetById(ctx context.Context, id int) (dto.CarModelYear, error) { 40 | return u.base.GetById(ctx, id) 41 | } 42 | 43 | // Get By Filter 44 | func (u *CarModelYearUsecase) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[dto.CarModelYear], error) { 45 | return u.base.GetByFilter(ctx, req) 46 | } -------------------------------------------------------------------------------- /src/usecase/carModel_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/domain/filter" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/usecase/dto" 11 | ) 12 | 13 | 14 | type CarModelUsecase struct { 15 | base *BaseUsecase[model.CarModel, dto.CreateCarModel, dto.UpdateCarModel, dto.CarModel] 16 | } 17 | 18 | func NewCarModelUsecase(cfg *config.Config, repository repository.CarModelRepository) *CarModelUsecase { 19 | return &CarModelUsecase{ 20 | base: NewBaseUsecase[model.CarModel, dto.CreateCarModel, dto.UpdateCarModel, dto.CarModel](cfg, repository), 21 | } 22 | } 23 | 24 | // Create 25 | func (u *CarModelUsecase) Create(ctx context.Context, req dto.CreateCarModel) (dto.CarModel, error) { 26 | return u.base.Create(ctx, req) 27 | } 28 | 29 | // Update 30 | func (u *CarModelUsecase) Update(ctx context.Context, id int, req dto.UpdateCarModel) (dto.CarModel, error) { 31 | return u.base.Update(ctx, id, req) 32 | } 33 | 34 | // Delete 35 | func (u *CarModelUsecase) Delete(ctx context.Context, id int) error { 36 | return u.base.Delete(ctx, id) 37 | } 38 | 39 | // Get By Id 40 | func (u *CarModelUsecase) GetById(ctx context.Context, id int) (dto.CarModel, error) { 41 | return u.base.GetById(ctx, id) 42 | } 43 | 44 | // Get By Filter 45 | func (u *CarModelUsecase) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[dto.CarModel], error) { 46 | return u.base.GetByFilter(ctx, req) 47 | } -------------------------------------------------------------------------------- /src/usecase/carType_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/domain/filter" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/usecase/dto" 11 | ) 12 | 13 | type CarTypeUsecase struct { 14 | base *BaseUsecase[model.CarType, dto.Name, dto.Name, dto.IdName] 15 | } 16 | 17 | func NewCarTypeUsecase(cfg *config.Config, repository repository.CarTypeRepository) *CarTypeUsecase { 18 | return &CarTypeUsecase{ 19 | base: NewBaseUsecase[model.CarType, dto.Name, dto.Name, dto.IdName](cfg, repository), 20 | } 21 | } 22 | 23 | // Create 24 | func (u *CarTypeUsecase) Create(ctx context.Context, req dto.Name) (dto.IdName, error) { 25 | return u.base.Create(ctx, req) 26 | } 27 | 28 | // Update 29 | func (u *CarTypeUsecase) Update(ctx context.Context, id int, req dto.Name) (dto.IdName, error) { 30 | return u.base.Update(ctx, id, req) 31 | } 32 | 33 | // Delete 34 | func (u *CarTypeUsecase) Delete(ctx context.Context, id int) error { 35 | return u.base.Delete(ctx, id) 36 | } 37 | 38 | // Get By Id 39 | func (u *CarTypeUsecase) GetById(ctx context.Context, id int) (dto.IdName, error) { 40 | return u.base.GetById(ctx, id) 41 | } 42 | 43 | // Get By Filter 44 | func (u *CarTypeUsecase) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[dto.IdName], error) { 45 | return u.base.GetByFilter(ctx, req) 46 | } -------------------------------------------------------------------------------- /src/usecase/city_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/domain/filter" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/usecase/dto" 11 | ) 12 | 13 | 14 | type CityUsecase struct { 15 | base *BaseUsecase[model.City, dto.CreateCity, dto.UpdateCity, dto.City] 16 | } 17 | 18 | func NewCityUsecase(cfg *config.Config, repository repository.CityRepository) *CityUsecase { 19 | return &CityUsecase{ 20 | base: NewBaseUsecase[model.City, dto.CreateCity, dto.UpdateCity, dto.City](cfg, repository), 21 | } 22 | } 23 | 24 | // Create 25 | func (u *CityUsecase) Create(ctx context.Context, req dto.CreateCity) (dto.City, error) { 26 | return u.base.Create(ctx, req) 27 | } 28 | 29 | // Update 30 | func (u *CityUsecase) Update(ctx context.Context, id int, req dto.UpdateCity) (dto.City, error) { 31 | return u.base.Update(ctx, id, req) 32 | } 33 | 34 | // Delete 35 | func (u *CityUsecase) Delete(ctx context.Context, id int) error { 36 | return u.base.Delete(ctx, id) 37 | } 38 | 39 | // Get By Id 40 | func (u *CityUsecase) GetById(ctx context.Context, id int) (dto.City, error) { 41 | return u.base.GetById(ctx, id) 42 | } 43 | 44 | // Get By Filter 45 | func (u *CityUsecase) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[dto.City], error) { 46 | return u.base.GetByFilter(ctx, req) 47 | } -------------------------------------------------------------------------------- /src/usecase/color_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/domain/filter" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/usecase/dto" 11 | ) 12 | 13 | type ColorUsecase struct { 14 | base *BaseUsecase[model.Color, dto.CreateColor, dto.UpdateColor, dto.Color] 15 | } 16 | 17 | func NewColorUsecase(cfg *config.Config, repository repository.ColorRepository) *ColorUsecase { 18 | return &ColorUsecase{ 19 | base: NewBaseUsecase[model.Color, dto.CreateColor, dto.UpdateColor, dto.Color](cfg, repository), 20 | } 21 | } 22 | 23 | // Create 24 | func (u *ColorUsecase) Create(ctx context.Context, req dto.CreateColor) (dto.Color, error) { 25 | return u.base.Create(ctx, req) 26 | } 27 | 28 | // Update 29 | func (u *ColorUsecase) Update(ctx context.Context, id int, req dto.UpdateColor) (dto.Color, error) { 30 | return u.base.Update(ctx, id, req) 31 | } 32 | 33 | // Delete 34 | func (u *ColorUsecase) Delete(ctx context.Context, id int) error { 35 | return u.base.Delete(ctx, id) 36 | } 37 | 38 | // Get By Id 39 | func (u *ColorUsecase) GetById(ctx context.Context, id int) (dto.Color, error) { 40 | return u.base.GetById(ctx, id) 41 | } 42 | 43 | // Get By Filter 44 | func (u *ColorUsecase) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[dto.Color], error) { 45 | return u.base.GetByFilter(ctx, req) 46 | } -------------------------------------------------------------------------------- /src/usecase/company_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/domain/filter" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/usecase/dto" 11 | ) 12 | 13 | type CompanyUsecase struct { 14 | base *BaseUsecase[model.Company, dto.CreateCompany, dto.UpdateCompany, dto.Company] 15 | } 16 | 17 | func NewCompanyUsecase(cfg *config.Config, repository repository.CompanyRepository) *CompanyUsecase { 18 | return &CompanyUsecase{ 19 | base: NewBaseUsecase[model.Company, dto.CreateCompany, dto.UpdateCompany, dto.Company](cfg, repository), 20 | } 21 | } 22 | 23 | // Create 24 | func (u *CompanyUsecase) Create(ctx context.Context, req dto.CreateCompany) (dto.Company, error) { 25 | return u.base.Create(ctx, req) 26 | } 27 | 28 | // Update 29 | func (u *CompanyUsecase) Update(ctx context.Context, id int, req dto.UpdateCompany) (dto.Company, error) { 30 | return u.base.Update(ctx, id, req) 31 | } 32 | 33 | // Delete 34 | func (u *CompanyUsecase) Delete(ctx context.Context, id int) error { 35 | return u.base.Delete(ctx, id) 36 | } 37 | 38 | // Get By Id 39 | func (u *CompanyUsecase) GetById(ctx context.Context, id int) (dto.Company, error) { 40 | return u.base.GetById(ctx, id) 41 | } 42 | 43 | // Get By Filter 44 | func (u *CompanyUsecase) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[dto.Company], error) { 45 | return u.base.GetByFilter(ctx, req) 46 | } -------------------------------------------------------------------------------- /src/usecase/country_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/domain/filter" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/usecase/dto" 11 | ) 12 | 13 | 14 | type CountryUsecase struct { 15 | base *BaseUsecase[model.Country, dto.Name, dto.Name, dto.Country] 16 | } 17 | 18 | func NewCountryUsecase(cfg *config.Config, repository repository.CountryRepository) *CountryUsecase { 19 | return &CountryUsecase{ 20 | base: NewBaseUsecase[model.Country, dto.Name, dto.Name, dto.Country](cfg, repository), 21 | } 22 | } 23 | 24 | // Create 25 | func (u *CountryUsecase) Create(ctx context.Context, req dto.Name) (dto.Country, error) { 26 | return u.base.Create(ctx, req) 27 | } 28 | 29 | // Update 30 | func (u *CountryUsecase) Update(ctx context.Context, id int, req dto.Name) (dto.Country, error) { 31 | return u.base.Update(ctx, id, req) 32 | } 33 | 34 | // Delete 35 | func (u *CountryUsecase) Delete(ctx context.Context, id int) error { 36 | return u.base.Delete(ctx, id) 37 | } 38 | 39 | // Get By Id 40 | func (u *CountryUsecase) GetById(ctx context.Context, id int) (dto.Country, error) { 41 | return u.base.GetById(ctx, id) 42 | } 43 | 44 | // Get By Filter 45 | func (u *CountryUsecase) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[dto.Country], error) { 46 | return u.base.GetByFilter(ctx, req) 47 | } -------------------------------------------------------------------------------- /src/usecase/dto/base.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | type IdName struct { 8 | Id int 9 | Name string 10 | } 11 | type Name struct { 12 | Name string 13 | } 14 | 15 | type Country struct { 16 | IdName 17 | Cities []City 18 | Companies []Company 19 | } 20 | 21 | type CreateCity struct { 22 | Name string 23 | CountryId int 24 | } 25 | 26 | type UpdateCity struct { 27 | Name string 28 | CountryId int 29 | } 30 | type City struct { 31 | IdName 32 | Country Country 33 | } 34 | 35 | type CreateFile struct { 36 | Name string 37 | Directory string 38 | Description string 39 | MimeType string 40 | } 41 | 42 | type UpdateFile struct { 43 | Description string 44 | } 45 | 46 | type File struct { 47 | IdName 48 | Directory string 49 | Description string 50 | MimeType string 51 | } 52 | 53 | type CreateCompany struct { 54 | Name string 55 | CountryId int 56 | } 57 | 58 | type UpdateCompany struct { 59 | Name string 60 | CountryId int 61 | } 62 | type Company struct { 63 | IdName 64 | Country Country 65 | } 66 | 67 | type CreateColor struct { 68 | Name string 69 | HexCode string 70 | } 71 | 72 | type UpdateColor struct { 73 | Name string 74 | HexCode string 75 | } 76 | 77 | type Color struct { 78 | IdName 79 | HexCode string 80 | } 81 | 82 | type CreatePersianYear struct { 83 | PersianTitle string 84 | Year int 85 | StartAt time.Time 86 | EndAt time.Time 87 | } 88 | 89 | type UpdatePersianYear struct { 90 | PersianTitle string 91 | Year int 92 | StartAt time.Time 93 | EndAt time.Time 94 | } 95 | 96 | type PersianYear struct { 97 | Id int 98 | PersianTitle string 99 | Year int 100 | StartAt time.Time 101 | EndAt time.Time 102 | } 103 | 104 | type PersianYearWithoutDate struct { 105 | Id int 106 | PersianTitle string 107 | Year int 108 | } 109 | -------------------------------------------------------------------------------- /src/usecase/dto/car.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import "time" 4 | 5 | type CreateCarModel struct { 6 | Name string 7 | CompanyId int 8 | CarTypeId int 9 | GearboxId int 10 | } 11 | 12 | type UpdateCarModel struct { 13 | Name string 14 | CompanyId int 15 | CarTypeId int 16 | GearboxId int 17 | } 18 | 19 | type CarModel struct { 20 | IdName 21 | CarType IdName 22 | Company Company 23 | Gearbox IdName 24 | CarModelColors []CarModelColor 25 | CarModelYears []CarModelYear 26 | CarModelImages []CarModelImage 27 | CarModelProperties []CarModelProperty 28 | CarModelComments []CarModelComment 29 | } 30 | 31 | type CreateCarModelColor struct { 32 | CarModelId int 33 | ColorId int 34 | } 35 | 36 | type UpdateCarModelColor struct { 37 | CarModelId int 38 | ColorId int 39 | } 40 | 41 | type CarModelColor struct { 42 | Id int 43 | Color Color 44 | } 45 | 46 | type CreateCarModelYear struct { 47 | CarModelId int 48 | PersianYearId int 49 | } 50 | 51 | type UpdateCarModelYear struct { 52 | CarModelId int 53 | PersianYearId int 54 | } 55 | 56 | type CarModelYear struct { 57 | Id int 58 | PersianYear PersianYearWithoutDate 59 | CarModelId int 60 | CarModelPriceHistories []CarModelPriceHistory 61 | } 62 | 63 | type CreateCarModelPriceHistory struct { 64 | CarModelYearId int 65 | PriceAt time.Time 66 | Price float64 67 | } 68 | 69 | type UpdateCarModelPriceHistory struct { 70 | PriceAt time.Time 71 | Price float64 72 | } 73 | 74 | type CarModelPriceHistory struct { 75 | Id int 76 | CarModelYearId int 77 | PriceAt time.Time 78 | Price float64 79 | } 80 | 81 | type CreateCarModelImage struct { 82 | CarModelId int 83 | ImageId int 84 | IsMainImage bool 85 | } 86 | 87 | type UpdateCarModelImage struct { 88 | IsMainImage bool 89 | } 90 | 91 | type CarModelImage struct { 92 | Id int 93 | CarModelId int 94 | Image File 95 | IsMainImage bool 96 | } 97 | 98 | type CreateCarModelProperty struct { 99 | CarModelId int 100 | PropertyId int 101 | Value string 102 | } 103 | 104 | type UpdateCarModelProperty struct { 105 | Value string 106 | } 107 | 108 | type CarModelProperty struct { 109 | Id int 110 | CarModelId int 111 | Property Property 112 | Value string 113 | } 114 | 115 | type CreateCarModelComment struct { 116 | CarModelId int 117 | UserId int 118 | Message string 119 | } 120 | 121 | type UpdateCarModelComment struct { 122 | Message string 123 | } 124 | 125 | type CarModelComment struct { 126 | Id int 127 | CarModelId int 128 | User User 129 | Message string 130 | } 131 | 132 | type User struct { 133 | Id int 134 | Username string 135 | FirstName string 136 | LastName string 137 | Email string 138 | } 139 | -------------------------------------------------------------------------------- /src/usecase/dto/property.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type CreatePropertyCategory struct { 4 | Name string 5 | Icon string 6 | } 7 | 8 | type UpdatePropertyCategory struct { 9 | Name string 10 | Icon string 11 | } 12 | 13 | type PropertyCategory struct { 14 | IdName 15 | Icon string 16 | Properties []Property 17 | } 18 | 19 | type CreateProperty struct { 20 | Name string 21 | CategoryId int 22 | Icon string 23 | Description string 24 | DataType string 25 | Unit string 26 | } 27 | 28 | type UpdateProperty struct { 29 | Name string 30 | CategoryId int 31 | Icon string 32 | Description string 33 | DataType string 34 | Unit string 35 | } 36 | 37 | type Property struct { 38 | IdName 39 | Icon string 40 | Description string 41 | DataType string 42 | Unit string 43 | Category PropertyCategory 44 | } 45 | -------------------------------------------------------------------------------- /src/usecase/dto/user.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | import model "github.com/omidhaqi/clean-web-api/domain/model" 4 | 5 | type TokenDetail struct { 6 | AccessToken string 7 | RefreshToken string 8 | AccessTokenExpireTime int64 9 | RefreshTokenExpireTime int64 10 | } 11 | 12 | type RegisterUserByUsername struct { 13 | FirstName string 14 | LastName string 15 | Username string 16 | Email string 17 | Password string 18 | } 19 | 20 | func ToUserModel(from RegisterUserByUsername) model.User { 21 | return model.User{Username: from.Username, 22 | FirstName: from.FirstName, 23 | LastName: from.LastName, 24 | Email: from.Email, 25 | } 26 | } 27 | 28 | type RegisterLoginByMobile struct { 29 | MobileNumber string 30 | Otp string 31 | } 32 | 33 | type LoginByUsername struct { 34 | Username string 35 | Password string 36 | } 37 | -------------------------------------------------------------------------------- /src/usecase/file_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/domain/filter" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/usecase/dto" 11 | ) 12 | 13 | 14 | type FileUsecase struct { 15 | base *BaseUsecase[model.File, dto.CreateFile, dto.UpdateFile, dto.File] 16 | } 17 | 18 | func NewFileUsecase(cfg *config.Config, repository repository.FileRepository) *FileUsecase { 19 | return &FileUsecase{ 20 | base: NewBaseUsecase[model.File, dto.CreateFile, dto.UpdateFile, dto.File](cfg, repository), 21 | } 22 | } 23 | 24 | // Create 25 | func (u *FileUsecase) Create(ctx context.Context, req dto.CreateFile) (dto.File, error) { 26 | return u.base.Create(ctx, req) 27 | } 28 | 29 | // Update 30 | func (u *FileUsecase) Update(ctx context.Context, id int, req dto.UpdateFile) (dto.File, error) { 31 | return u.base.Update(ctx, id, req) 32 | } 33 | 34 | // Delete 35 | func (u *FileUsecase) Delete(ctx context.Context, id int) error { 36 | return u.base.Delete(ctx, id) 37 | } 38 | 39 | // Get By Id 40 | func (u *FileUsecase) GetById(ctx context.Context, id int) (dto.File, error) { 41 | return u.base.GetById(ctx, id) 42 | } 43 | 44 | // Get By Filter 45 | func (u *FileUsecase) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[dto.File], error) { 46 | return u.base.GetByFilter(ctx, req) 47 | } -------------------------------------------------------------------------------- /src/usecase/gearbox_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/domain/filter" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/usecase/dto" 11 | ) 12 | type GearboxUsecase struct { 13 | base *BaseUsecase[model.Gearbox, dto.Name, dto.Name, dto.IdName] 14 | } 15 | 16 | func NewGearboxUsecase(cfg *config.Config, repository repository.GearboxRepository) *GearboxUsecase { 17 | return &GearboxUsecase{ 18 | base: NewBaseUsecase[model.Gearbox, dto.Name, dto.Name, dto.IdName](cfg, repository), 19 | } 20 | } 21 | 22 | // Create 23 | func (u *GearboxUsecase) Create(ctx context.Context, req dto.Name) (dto.IdName, error) { 24 | return u.base.Create(ctx, req) 25 | } 26 | 27 | // Update 28 | func (u *GearboxUsecase) Update(ctx context.Context, id int, req dto.Name) (dto.IdName, error) { 29 | return u.base.Update(ctx, id, req) 30 | } 31 | 32 | // Delete 33 | func (u *GearboxUsecase) Delete(ctx context.Context, id int) error { 34 | return u.base.Delete(ctx, id) 35 | } 36 | 37 | // Get By Id 38 | func (u *GearboxUsecase) GetById(ctx context.Context, id int) (dto.IdName, error) { 39 | return u.base.GetById(ctx, id) 40 | } 41 | 42 | // Get By Filter 43 | func (u *GearboxUsecase) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[dto.IdName], error) { 44 | return u.base.GetByFilter(ctx, req) 45 | } -------------------------------------------------------------------------------- /src/usecase/otp_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/go-redis/redis/v7" 8 | "github.com/omidhaqi/clean-web-api/common" 9 | "github.com/omidhaqi/clean-web-api/config" 10 | constant "github.com/omidhaqi/clean-web-api/constant" 11 | "github.com/omidhaqi/clean-web-api/infra/cache" 12 | "github.com/omidhaqi/clean-web-api/pkg/logging" 13 | "github.com/omidhaqi/clean-web-api/pkg/service_errors" 14 | ) 15 | type OtpUsecase struct { 16 | logger logging.Logger 17 | cfg *config.Config 18 | redisClient *redis.Client 19 | } 20 | 21 | type otpDto struct { 22 | Value string 23 | Used bool 24 | } 25 | 26 | func NewOtpUsecase(cfg *config.Config) *OtpUsecase { 27 | logger := logging.NewLogger(cfg) 28 | redis := cache.GetRedis() 29 | return &OtpUsecase{logger: logger, cfg: cfg, redisClient: redis} 30 | } 31 | 32 | func (u *OtpUsecase) SendOtp(mobileNumber string) error { 33 | otp := common.GenerateOtp() 34 | err := u.SetOtp(mobileNumber, otp) 35 | if err != nil { 36 | return err 37 | } 38 | return nil 39 | } 40 | 41 | func (u *OtpUsecase) SetOtp(mobileNumber string, otp string) error { 42 | key := fmt.Sprintf("%s:%s", constant.RedisOtpDefaultKey, mobileNumber) 43 | val := &otpDto{ 44 | Value: otp, 45 | Used: false, 46 | } 47 | 48 | res, err := cache.Get[otpDto](u.redisClient, key) 49 | if err == nil && !res.Used { 50 | return &service_errors.ServiceError{EndUserMessage: service_errors.OptExists} 51 | } else if err == nil && res.Used { 52 | return &service_errors.ServiceError{EndUserMessage: service_errors.OtpUsed} 53 | } 54 | err = cache.Set(u.redisClient, key, val, u.cfg.Otp.ExpireTime*time.Second) 55 | if err != nil { 56 | return err 57 | } 58 | return nil 59 | } 60 | 61 | func (u *OtpUsecase) ValidateOtp(mobileNumber string, otp string) error { 62 | key := fmt.Sprintf("%s:%s", constant.RedisOtpDefaultKey, mobileNumber) 63 | res, err := cache.Get[otpDto](u.redisClient, key) 64 | if err != nil { 65 | return err 66 | } else if res.Used { 67 | return &service_errors.ServiceError{EndUserMessage: service_errors.OtpUsed} 68 | } else if !res.Used && res.Value != otp { 69 | return &service_errors.ServiceError{EndUserMessage: service_errors.OtpNotValid} 70 | } else if !res.Used && res.Value == otp { 71 | res.Used = true 72 | err = cache.Set(u.redisClient, key, res, u.cfg.Otp.ExpireTime*time.Second) 73 | if err != nil { 74 | return err 75 | } 76 | } 77 | return nil 78 | } -------------------------------------------------------------------------------- /src/usecase/propertyCategory_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/domain/filter" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/usecase/dto" 11 | ) 12 | 13 | 14 | type PropertyCategoryUsecase struct { 15 | base *BaseUsecase[model.PropertyCategory, dto.CreatePropertyCategory, dto.UpdatePropertyCategory, dto.PropertyCategory] 16 | } 17 | 18 | func NewPropertyCategoryUsecase(cfg *config.Config, repository repository.PropertyCategoryRepository) *PropertyCategoryUsecase { 19 | return &PropertyCategoryUsecase{ 20 | base: NewBaseUsecase[model.PropertyCategory, dto.CreatePropertyCategory, dto.UpdatePropertyCategory, dto.PropertyCategory](cfg, repository), 21 | } 22 | } 23 | 24 | // Create 25 | func (u *PropertyCategoryUsecase) Create(ctx context.Context, req dto.CreatePropertyCategory) (dto.PropertyCategory, error) { 26 | return u.base.Create(ctx, req) 27 | } 28 | 29 | // Update 30 | func (u *PropertyCategoryUsecase) Update(ctx context.Context, id int, req dto.UpdatePropertyCategory) (dto.PropertyCategory, error) { 31 | return u.base.Update(ctx, id, req) 32 | } 33 | 34 | // Delete 35 | func (u *PropertyCategoryUsecase) Delete(ctx context.Context, id int) error { 36 | return u.base.Delete(ctx, id) 37 | } 38 | 39 | // Get By Id 40 | func (u *PropertyCategoryUsecase) GetById(ctx context.Context, id int) (dto.PropertyCategory, error) { 41 | return u.base.GetById(ctx, id) 42 | } 43 | 44 | // Get By Filter 45 | func (u *PropertyCategoryUsecase) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[dto.PropertyCategory], error) { 46 | return u.base.GetByFilter(ctx, req) 47 | } -------------------------------------------------------------------------------- /src/usecase/property_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/domain/filter" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/usecase/dto" 11 | ) 12 | 13 | type PropertyUsecase struct { 14 | base *BaseUsecase[model.Property, dto.CreateProperty, dto.UpdateProperty, dto.Property] 15 | } 16 | 17 | func NewPropertyUsecase(cfg *config.Config, repository repository.PropertyRepository) *PropertyUsecase { 18 | return &PropertyUsecase{ 19 | base: NewBaseUsecase[model.Property, dto.CreateProperty, dto.UpdateProperty, dto.Property](cfg, repository), 20 | } 21 | } 22 | 23 | // Create 24 | func (u *PropertyUsecase) Create(ctx context.Context, req dto.CreateProperty) (dto.Property, error) { 25 | return u.base.Create(ctx, req) 26 | } 27 | 28 | // Update 29 | func (u *PropertyUsecase) Update(ctx context.Context, id int, req dto.UpdateProperty) (dto.Property, error) { 30 | return u.base.Update(ctx, id, req) 31 | } 32 | 33 | // Delete 34 | func (u *PropertyUsecase) Delete(ctx context.Context, id int) error { 35 | return u.base.Delete(ctx, id) 36 | } 37 | 38 | // Get By Id 39 | func (u *PropertyUsecase) GetById(ctx context.Context, id int) (dto.Property, error) { 40 | return u.base.GetById(ctx, id) 41 | } 42 | 43 | // Get By Filter 44 | func (u *PropertyUsecase) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[dto.Property], error) { 45 | return u.base.GetByFilter(ctx, req) 46 | } -------------------------------------------------------------------------------- /src/usecase/token_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/golang-jwt/jwt" 7 | "github.com/omidhaqi/clean-web-api/config" 8 | "github.com/omidhaqi/clean-web-api/constant" 9 | "github.com/omidhaqi/clean-web-api/pkg/logging" 10 | "github.com/omidhaqi/clean-web-api/pkg/service_errors" 11 | dto "github.com/omidhaqi/clean-web-api/usecase/dto" 12 | ) 13 | 14 | 15 | type TokenUsecase struct { 16 | logger logging.Logger 17 | cfg *config.Config 18 | } 19 | 20 | type tokenDto struct { 21 | UserId int 22 | FirstName string 23 | LastName string 24 | Username string 25 | MobileNumber string 26 | Email string 27 | Roles []string 28 | } 29 | 30 | func NewTokenUsecase(cfg *config.Config) *TokenUsecase { 31 | logger := logging.NewLogger(cfg) 32 | return &TokenUsecase{ 33 | cfg: cfg, 34 | logger: logger, 35 | } 36 | } 37 | 38 | func (u *TokenUsecase) GenerateToken(token tokenDto) (*dto.TokenDetail, error) { 39 | td := &dto.TokenDetail{} 40 | td.AccessTokenExpireTime = time.Now().Add(u.cfg.JWT.AccessTokenExpireDuration * time.Minute).Unix() 41 | td.RefreshTokenExpireTime = time.Now().Add(u.cfg.JWT.RefreshTokenExpireDuration * time.Minute).Unix() 42 | 43 | atc := jwt.MapClaims{} 44 | 45 | atc[constant.UserIdKey] = token.UserId 46 | atc[constant.FirstNameKey] = token.FirstName 47 | atc[constant.LastNameKey] = token.LastName 48 | atc[constant.UsernameKey] = token.Username 49 | atc[constant.EmailKey] = token.Email 50 | atc[constant.MobileNumberKey] = token.MobileNumber 51 | atc[constant.RolesKey] = token.Roles 52 | atc[constant.ExpireTimeKey] = td.AccessTokenExpireTime 53 | 54 | at := jwt.NewWithClaims(jwt.SigningMethodHS256, atc) 55 | 56 | var err error 57 | td.AccessToken, err = at.SignedString([]byte(u.cfg.JWT.Secret)) 58 | 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | rtc := jwt.MapClaims{} 64 | 65 | rtc[constant.UserIdKey] = token.UserId 66 | rtc[constant.ExpireTimeKey] = td.RefreshTokenExpireTime 67 | 68 | rt := jwt.NewWithClaims(jwt.SigningMethodHS256, rtc) 69 | 70 | td.RefreshToken, err = rt.SignedString([]byte(u.cfg.JWT.RefreshSecret)) 71 | 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | return td, nil 77 | } 78 | 79 | func (u *TokenUsecase) VerifyToken(token string) (*jwt.Token, error) { 80 | at, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { 81 | _, ok := token.Method.(*jwt.SigningMethodHMAC) 82 | if !ok { 83 | return nil, &service_errors.ServiceError{EndUserMessage: service_errors.UnExpectedError} 84 | } 85 | return []byte(u.cfg.JWT.Secret), nil 86 | }) 87 | if err != nil { 88 | return nil, err 89 | } 90 | return at, nil 91 | } 92 | 93 | func (u *TokenUsecase) GetClaims(token string) (claimMap map[string]interface{}, err error) { 94 | claimMap = map[string]interface{}{} 95 | 96 | verifyToken, err := u.VerifyToken(token) 97 | if err != nil { 98 | return nil, err 99 | } 100 | claims, ok := verifyToken.Claims.(jwt.MapClaims) 101 | if ok && verifyToken.Valid { 102 | for k, v := range claims { 103 | claimMap[k] = v 104 | } 105 | return claimMap, nil 106 | } 107 | return nil, &service_errors.ServiceError{EndUserMessage: service_errors.ClaimsNotFound} 108 | } -------------------------------------------------------------------------------- /src/usecase/year_usecase.go: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/omidhaqi/clean-web-api/config" 7 | "github.com/omidhaqi/clean-web-api/domain/filter" 8 | model "github.com/omidhaqi/clean-web-api/domain/model" 9 | "github.com/omidhaqi/clean-web-api/domain/repository" 10 | "github.com/omidhaqi/clean-web-api/usecase/dto" 11 | ) 12 | 13 | 14 | type PersianYearUsecase struct { 15 | base *BaseUsecase[model.PersianYear, dto.CreatePersianYear, dto.UpdatePersianYear, dto.PersianYear] 16 | } 17 | 18 | func NewPersianYearUsecase(cfg *config.Config, repository repository.PersianYearRepository) *PersianYearUsecase { 19 | return &PersianYearUsecase{ 20 | base: NewBaseUsecase[model.PersianYear, dto.CreatePersianYear, dto.UpdatePersianYear, dto.PersianYear](cfg, repository), 21 | } 22 | } 23 | 24 | // Create 25 | func (u *PersianYearUsecase) Create(ctx context.Context, req dto.CreatePersianYear) (dto.PersianYear, error) { 26 | return u.base.Create(ctx, req) 27 | } 28 | 29 | // Update 30 | func (u *PersianYearUsecase) Update(ctx context.Context, id int, req dto.UpdatePersianYear) (dto.PersianYear, error) { 31 | return u.base.Update(ctx, id, req) 32 | } 33 | 34 | // Delete 35 | func (u *PersianYearUsecase) Delete(ctx context.Context, id int) error { 36 | return u.base.Delete(ctx, id) 37 | } 38 | 39 | // Get By Id 40 | func (u *PersianYearUsecase) GetById(ctx context.Context, id int) (dto.PersianYear, error) { 41 | return u.base.GetById(ctx, id) 42 | } 43 | 44 | // Get By Filter 45 | func (u *PersianYearUsecase) GetByFilter(ctx context.Context, req filter.PaginationInputWithFilter) (*filter.PagedList[dto.PersianYear], error) { 46 | return u.base.GetByFilter(ctx, req) 47 | } --------------------------------------------------------------------------------