# It has alternatives

A website that helps people to find everything's alternative source.

> It utilize invitation mechanism to make sure the core information created by platform creator and early members won't get changed by new comers.

### The data structure

```protobuf
syntax = "proto3";
package it_has_alternatives;

service it_has_alternatives {
    // just try to put traefik outside the docker, then handle 80 port directly
    // then do auth only for requests_url that starts with 'it_has_alternatives'
    // for others, forward them to another traefik instance

    // we only allow get_special_jwt() and is_jwt_ok() and search_alternatives() and get_an_object() function not include the jwt token
    // others, in traefik logic, have to pass a jwt_check api, for example '/is_user', if it returns 200, then pass, if it returns 500, then refuse
    rpc get_special_jwt (Get_Special_JWT_Request) returns (Get_Special_JWT_Response);
    rpc is_jwt_ok (is_JWT_ok_Request) returns (is_JWT_ok_Response);

    rpc get_invitation_code (Get_invitation_code_request) returns (Get_invitation_code_response);

    rpc search_alternatives (Search_Alternative_Request) returns (Search_Alternative_Response);
    rpc get_an_object (Get_an_object_Request) returns (Get_an_object_Response);
    rpc add_alternative (Add_Object_Request) returns (Add_Object_Response);
    rpc update_alternative (Update_Object_Request) returns (Update_Object_Response);
    rpc delete_alternative (Delete_Object_Request) returns (Delete_Object_Response);
}

enum Sort_By {
    like = 0;
    dislike = 1;
}

message A_User {
    string id = 1;
    int64 create_time_in_10_numbers_timestamp_format = 2;
    string email = 3;
    string password = 4;
    string jwt = 5;
    int64 level = 6;
    string parent_email = 7;
    repeated string children_email_list = 8;
    int64 invitation_counting = 9;
    repeated string invitation_code_list = 10;
}

message An_Object {
    string id = 1;
    string name = 2;
    string description = 3;
    int64 level = 4;
    int64 likes = 5; 
    int64 dislikes = 6; 
    repeated string alternative_id_list = 7;
}

message Get_Special_JWT_Request {
    string email = 1;
    string password = 2; // it needs to have a strong password
    string invitation_code = 3;
}

message Get_Special_JWT_Response {
    string error = 1;
    string encrypted_jwt = 2;
}

message is_JWT_ok_Request {
    string jwt = 1;
}

message is_JWT_ok_Response {
    string error = 1;
    bool ok = 2;
    bool is_admin = 3;
}

message Get_invitation_code_request {

}

message Get_invitation_code_response {
    string error = 1;
    string invitation_code = 2;
}

message Search_Alternative_Request {
    string key_words = 1;
    string keywords_of_name = 2;
    string keywords_of_description = 3;
    Sort_By sort_by = 4;
    int64 page_size = 5;
    int64 page_number = 6;
}

message Search_Alternative_Response {
    optional string error = 1;
    repeated An_Object alternative_object_list = 2;
}

message Get_an_object_Request {
    string id = 1;
    string name = 2;
}

message Get_an_object_Response {
    optional string error = 1;
    An_Object an_object = 2;
}

message Add_Object_Request {
    An_Object an_object = 1;
}

message Add_Object_Response {
    optional string error = 1;
    bool success = 2;
}

message Update_Object_Request {
    An_Object an_object = 1;
}

message Update_Object_Response {
    optional string error = 1;
    bool success = 2;
}

message Delete_Object_Request {
    An_Object an_object = 1;
}

message Delete_Object_Response {
    optional string error = 1;
    bool success = 2;
}
```

## The docker-compose file

```yaml
#docker-compose.gateway.yaml
version: "3.9"

services:
  traefik:
    image: "traefik:v3.0.0-beta2"
    network_mode: "host"        
    command:
      - "--accesslog=true"
      - "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--providers.file.directory=/traefik_dynamic_configs"
      - "--providers.file.watch=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      # - "--entrypoints.websecure.address=:443"
      # - "--entrypoints.smtp.address=:25"
    ports:
      - "80:80"
      # - "443:443"
      # - "25:25"
      #- "8081:8080"
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./configs/traefik_dynamic_configs:/traefik_dynamic_configs" # <== Volume for dynamic conf file
    restart: unless-stopped
```

```yaml
#docker-compose.database.yaml
version: "3.9"

services:
  my_mongo:
    image: mongo:6.0.4-jammy
    network_mode: "host"        
    # expose:
    #   - "27017:27017"
    ports:
      - '27017:27017'
    environment:
      - MONGO_INITDB_ROOT_USERNAME=yingshaoxo
      - MONGO_INITDB_ROOT_PASSWORD=yingshaoxo
    volumes:
      - "./configs/mongodb_data:/data/db"
    restart: always
```

```yaml
#docker-compose.service.yaml
version: "3.9"

services:
  it_has_alternatives:
    network_mode: "host"        
    build: 
      context: ./backend_service
      dockerfile: ./Dockerfile
    image: yingshaoxo/it_has_alternatives
    # expose:
    #   - "5550"
    #   - "5551"
    #   - "5552"
    #   - "5553"
    ports:
      - "5550:5550"
      - "5551:5551"
      - "5552:5552"
      - "5553:5553"
    environment:
      - ADMIN_EMAIL=yingshaoxo@gmail.com
      - MONGO_DB_URL=mongodb://yingshaoxo:yingshaoxo@127.0.0.1:27017/
    restart: unless-stopped
```

## The traefik configuration

```yaml
## in /etc/hosts
# 127.0.0.1 alternatives.domain.local user_alternatives.domain.local admin_alternatives.domain.local

# tcp:
#   routers:
#     next_traefik_router:
#       entryPoints:
#         - web
#       # rule: "HostSNI(`*`)"
#       # rule: "!HostSNI(`alternatives.ai-tools-online.xyz`)"
#       rule: "HostSNI(`auth.weloveparty.ai-tools-online.xyz`)"
#       service: next_traefik_service
#       tls:
#          passthrough: true
#   services:
#     next_traefik_service:
#       loadBalancer:
#         servers:
#           - address: 144.202.109.163:810
#           # - address: 127.0.0.1:810

http:
  routers:
    next_traefik_router:
      entryPoints:
        - web
        - websecure
      # rule: "!Host(`alternatives.ai-tools-online.xyz`)"
      rule: "HostRegexp(`(.*)weloveparty(.*)`)"
      service: next_traefik_service
    its_alternative_router_for_visitor_operations:
      entryPoints:
        - web
        - websecure
      # rule: "(Host(`alternatives.ai-tools-online.xyz`) || Host(`alternatives.domain.local`)) && PathPrefix(`/it_has_alternatives/`)"
      rule: "Host(`alternatives.ai-tools-online.xyz`) || Host(`alternatives.domain.local`)"
      middlewares:
        - cros_allow_all
      service: backend_service_for_visitor
    its_alternative_router_for_users_operations:
      entryPoints:
        - web
        - websecure
      rule: "Host(`user_alternatives.ai-tools-online.xyz`) || Host(`user_alternatives.domain.local`)"
      middlewares:
        - cros_allow_all
        - my_jwt_auth
      service: backend_service_for_user
    its_alternative_router_for_admin_operations:
      entryPoints:
        - web
        - websecure
      rule: "Host(`admin_alternatives.ai-tools-online.xyz`) || Host(`admin_alternatives.domain.local`)"
      middlewares:
        - cros_allow_all
        - my_admin_jwt_auth
      service: backend_service_for_admin

  middlewares:
    cros_allow_all:
      headers:
        accessControlAllowMethods: "*"
        accessControlAllowHeaders: "*"
        accessControlAllowOriginList: "*"
        accessControlMaxAge: 100
        addVaryHeader: true
    my_grpcweb:
      grpcWeb:
        allowOrigins:
          - "*"
    my_jwt_auth:
        forwardAuth:
          # address: "http://it_has_alternatives:5550/v1/jwt_auth_gateway/"
          address: "http://127.0.0.1:5550/v1/jwt_auth_gateway/"
          # address: http://host.docker.internal:5550/v1/jwt_auth_gateway/
          authResponseHeaders:
              - "email" # jwt_auth_gateway will put this header to the original request
    my_admin_jwt_auth:
        forwardAuth:
          # address: "http://it_has_alternatives:5550/v1/admin_jwt_auth_gateway/"
          address: "http://127.0.0.1:5550/v1/admin_jwt_auth_gateway/"
          # address: http://host.docker.internal:5550/v1/admin_jwt_auth_gateway/
          authResponseHeaders:
              - "email" # jwt_auth_gateway will put this header to the original request

  services:
    next_traefik_service:
      loadBalancer:
        servers:
          - url: http://127.0.0.1:810
          # - url: http://host.docker.internal:810
    backend_service_for_visitor:
      loadBalancer:
        servers:
          # - url: http://it_has_alternatives:5551
          - url: http://127.0.0.1:5551
          # - url: http://host.docker.internal:5551
        passHostHeader: true
    backend_service_for_user:
      loadBalancer:
        servers:
          # - url: http://it_has_alternatives:5552
          - url: http://127.0.0.1:5552
          # - url: http://host.docker.internal:5552
        passHostHeader: true
    backend_service_for_admin:
      loadBalancer:
        servers:
          # - url: http://it_has_alternatives:5553
          - url: http://127.0.0.1:5553
          # - url: http://host.docker.internal:5553
        passHostHeader: true
```
