[
  {
    "path": ".adonisrc.json",
    "content": "{\n  \"typescript\": true,\n  \"commands\": [\n    \"./commands\",\n    \"@adonisjs/core/build/commands/index.js\",\n    \"@adonisjs/repl/build/commands\",\n    \"@adonisjs/lucid/build/commands\"\n  ],\n  \"exceptionHandlerNamespace\": \"App/Exceptions/Handler\",\n  \"aliases\": {\n    \"App\": \"app\",\n    \"Config\": \"config\",\n    \"Database\": \"database\",\n    \"Contracts\": \"contracts\"\n  },\n  \"preloads\": [\n    \"./start/routes\",\n    \"./start/kernel\"\n  ],\n  \"providers\": [\n    \"./providers/AppProvider\",\n    \"@adonisjs/core\",\n    \"@adonisjs/lucid\",\n    \"@adonisjs/ally\",\n    \"@adonisjs/auth\"\n  ],\n  \"aceProviders\": [\n    \"@adonisjs/repl\"\n  ],\n  \"tests\": {\n    \"suites\": [\n      {\n        \"name\": \"functional\",\n        \"files\": [\n          \"tests/functional/**/*.spec(.ts|.js)\"\n        ],\n        \"timeout\": 60000\n      }\n    ]\n  },\n  \"testProviders\": [\n    \"@japa/preset-adonis/TestsProvider\"\n  ]\n}"
  },
  {
    "path": ".dockerignore",
    "content": "node_modules\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.json]\ninsert_final_newline = false\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build\n\non:\n  push:\n    branches:\n      - main\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: ${{ github.repository }}\n\njobs:\n  build-and-push-image:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: read\n      packages: write\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v3\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v1\n\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v1\n\n      - name: Login to GitHub Packages\n        uses: docker/login-action@v1\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Prepare metadata\n        id: meta\n        uses: docker/metadata-action@v3\n        with:\n          images: ghcr.io/${{ env.IMAGE_NAME }}\n          tags: |\n            type=ref,event=branch\n            type=ref,event=pr\n            type=ref,event=tag\n\n      - name: Build and push\n        uses: docker/build-push-action@v2\n        with:\n          build-args: |\n            APP_RELEASE=${{ github.sha }}\n          push: ${{ github.event_name != 'pull_request' }}\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}\n\n      - name: Deploy the new image\n        uses: appleboy/ssh-action@master\n        with:\n          host: ${{ secrets.HOST }}\n          username: ${{ secrets.USERNAME }}\n          key: ${{ secrets.KEY }}\n          script: |\n            cd ~/stacks\n            docker stack deploy -c boumboum-back.yml boumboum-back --with-registry-auth\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\nbuild\ncoverage\n.vscode\n.DS_STORE\n.env\ntmp\n"
  },
  {
    "path": "Dockerfile",
    "content": "FROM node:20-alpine3.18 as base\n\nRUN apk --no-cache add curl\n\n# All deps stage\nFROM base as deps\nWORKDIR /app\nADD package.json package-lock.json ./\nRUN npm ci\n\n# Production only deps stage\nFROM base as production-deps\nWORKDIR /app\nADD package.json package-lock.json ./\nRUN npm ci --omit=dev\nRUN wget https://gobinaries.com/tj/node-prune --output-document - | /bin/sh && node-prune\n\n# Build stage\nFROM base as build\nWORKDIR /app\nCOPY --from=deps /app/node_modules /app/node_modules\nADD . .\nRUN node ace build --production --ignore-ts-errors\n\n# Production stage\nFROM base\nENV NODE_ENV=production\nWORKDIR /app\nCOPY --from=production-deps /app/node_modules /app/node_modules\nCOPY --from=build /app/build /app\nEXPOSE 8080\nCMD [\"node\", \"./server.js\"]\n"
  },
  {
    "path": "README.md",
    "content": "# Music Match API with AdonisJS\n\nAn innovative application that connects people based on their musical preferences, drawing inspiration from Tinder.\n\n## User Flow\n\n### User Registration/Login\n- Users sign up or log in using their Spotify credentials.\n\n### Profile Creation\n- Users complete their profiles, including basic information (name, date of birth, brief description, gender preference, and profile picture).\n- Users select and customize their top 4 favorite tracks from Spotify, retrieved using the Spotify API.\n\n### Matchmaking\n- Users are presented with potential matches based on their gender preference.\n\n### Interaction\n- Users see their matches.\n- For mutual matches, they can view the email of the match.\n\n## API Requirements\n\nWe seek a skilled Node.js developer to create this API using the AdonisJS framework. The chosen database engine can be PostgreSQL or MySQL based on your expertise and recommendation.\n\n## Required Endpoints\n\n1. **User Registration/Login**\n   - Implement OAuth2 authentication with Spotify to allow users to register or log in.\n\n2. **Logout Endpoint**\n   - Provide a secure endpoint for users to log out of their accounts.\n\n3. **Profile Management**\n   - Create endpoints for users to complete their profiles, including basic information and their preferred Spotify tracks.\n\n4. **Matching Endpoint**\n   - Develop an endpoint to retrieve and match users based on their gender preference.\n\n5. **Spotify Integration**\n   - Implement an endpoint to search for songs using the Spotify API.\n\n6. **Match History**\n   - Develop an endpoint to list all matches, indicating whether the match is mutual. Include relevant user data for each match.\n\n## Additional Spotify Data\n\nUpon user registration, it's crucial to save the user's Spotify preferences for future use, including:\n- User's top 20 artists\n- User's top 20 tracks\n- Artists the user follows\n\n## Technical Details\n\n- **Framework:** AdonisJS\n- **Database:** MySQL\n- **Authentication:** OAuth2 with Spotify\n\n## Getting Started\n\n1. **Install Dependencies**\n    ```bash\n    npm install\n    ```\n\n2. **Configure Database Connection**\n    - Set up the database connection details in your `.env` file.\n\n3. **Configure Spotify API**\n    - Add your Spotify client_id and client_secret in your `.env` file.\n\n4. **Run Migrations and Seed Data**\n    ```bash\n    node ace migration:run\n    node ace db:seed\n    ```\n\n5. **Start the Application**\n    ```bash\n    npm run dev\n    ```\n\n6. **Access the API at**\n   - [http://localhost:3333/api](http://localhost:3333/api)\n\n## API Documentation\n\n- For the Login API (http://localhost:3333/api/signin), open it in a browser as the request will be redirected to Spotify for authentication. This API won't work in Postman.\n- All other APIs are provided in the Postman collection.\n"
  },
  {
    "path": "ace",
    "content": "/*\n|--------------------------------------------------------------------------\n| Ace Commands\n|--------------------------------------------------------------------------\n|\n| This file is the entry point for running ace commands.\n|\n*/\n\nrequire('reflect-metadata')\nrequire('source-map-support').install({ handleUncaughtExceptions: false })\n\nconst { Ignitor } = require('@adonisjs/core/build/standalone')\nnew Ignitor(__dirname)\n  .ace()\n  .handle(process.argv.slice(2))\n"
  },
  {
    "path": "ace-manifest.json",
    "content": "{\n  \"commands\": {\n    \"dump:rcfile\": {\n      \"settings\": {},\n      \"commandPath\": \"@adonisjs/core/build/commands/DumpRc\",\n      \"commandName\": \"dump:rcfile\",\n      \"description\": \"Dump contents of .adonisrc.json file along with defaults\",\n      \"args\": [],\n      \"aliases\": [],\n      \"flags\": []\n    },\n    \"list:routes\": {\n      \"settings\": {\n        \"loadApp\": true,\n        \"stayAlive\": true\n      },\n      \"commandPath\": \"@adonisjs/core/build/commands/ListRoutes/index\",\n      \"commandName\": \"list:routes\",\n      \"description\": \"List application routes\",\n      \"args\": [],\n      \"aliases\": [],\n      \"flags\": [\n        {\n          \"name\": \"verbose\",\n          \"propertyName\": \"verbose\",\n          \"type\": \"boolean\",\n          \"description\": \"Display more information\"\n        },\n        {\n          \"name\": \"reverse\",\n          \"propertyName\": \"reverse\",\n          \"type\": \"boolean\",\n          \"alias\": \"r\",\n          \"description\": \"Reverse routes display\"\n        },\n        {\n          \"name\": \"methods\",\n          \"propertyName\": \"methodsFilter\",\n          \"type\": \"array\",\n          \"alias\": \"m\",\n          \"description\": \"Filter routes by method\"\n        },\n        {\n          \"name\": \"patterns\",\n          \"propertyName\": \"patternsFilter\",\n          \"type\": \"array\",\n          \"alias\": \"p\",\n          \"description\": \"Filter routes by the route pattern\"\n        },\n        {\n          \"name\": \"names\",\n          \"propertyName\": \"namesFilter\",\n          \"type\": \"array\",\n          \"alias\": \"n\",\n          \"description\": \"Filter routes by route name\"\n        },\n        {\n          \"name\": \"json\",\n          \"propertyName\": \"json\",\n          \"type\": \"boolean\",\n          \"description\": \"Output as JSON\"\n        },\n        {\n          \"name\": \"table\",\n          \"propertyName\": \"table\",\n          \"type\": \"boolean\",\n          \"description\": \"Output as Table\"\n        },\n        {\n          \"name\": \"max-width\",\n          \"propertyName\": \"maxWidth\",\n          \"type\": \"number\",\n          \"description\": \"Specify maximum rendering width. Ignored for JSON Output\"\n        }\n      ]\n    },\n    \"generate:key\": {\n      \"settings\": {},\n      \"commandPath\": \"@adonisjs/core/build/commands/GenerateKey\",\n      \"commandName\": \"generate:key\",\n      \"description\": \"Generate a new APP_KEY secret\",\n      \"args\": [],\n      \"aliases\": [],\n      \"flags\": []\n    },\n    \"repl\": {\n      \"settings\": {\n        \"loadApp\": true,\n        \"environment\": \"repl\",\n        \"stayAlive\": true\n      },\n      \"commandPath\": \"@adonisjs/repl/build/commands/AdonisRepl\",\n      \"commandName\": \"repl\",\n      \"description\": \"Start a new REPL session\",\n      \"args\": [],\n      \"aliases\": [],\n      \"flags\": []\n    },\n    \"db:seed\": {\n      \"settings\": {\n        \"loadApp\": true\n      },\n      \"commandPath\": \"@adonisjs/lucid/build/commands/DbSeed\",\n      \"commandName\": \"db:seed\",\n      \"description\": \"Execute database seeders\",\n      \"args\": [],\n      \"aliases\": [],\n      \"flags\": [\n        {\n          \"name\": \"connection\",\n          \"propertyName\": \"connection\",\n          \"type\": \"string\",\n          \"description\": \"Define a custom database connection for the seeders\",\n          \"alias\": \"c\"\n        },\n        {\n          \"name\": \"interactive\",\n          \"propertyName\": \"interactive\",\n          \"type\": \"boolean\",\n          \"description\": \"Run seeders in interactive mode\",\n          \"alias\": \"i\"\n        },\n        {\n          \"name\": \"files\",\n          \"propertyName\": \"files\",\n          \"type\": \"array\",\n          \"description\": \"Define a custom set of seeders files names to run\",\n          \"alias\": \"f\"\n        },\n        {\n          \"name\": \"compact-output\",\n          \"propertyName\": \"compactOutput\",\n          \"type\": \"boolean\",\n          \"description\": \"A compact single-line output\"\n        }\n      ]\n    },\n    \"db:wipe\": {\n      \"settings\": {\n        \"loadApp\": true\n      },\n      \"commandPath\": \"@adonisjs/lucid/build/commands/DbWipe\",\n      \"commandName\": \"db:wipe\",\n      \"description\": \"Drop all tables, views and types in database\",\n      \"args\": [],\n      \"aliases\": [],\n      \"flags\": [\n        {\n          \"name\": \"connection\",\n          \"propertyName\": \"connection\",\n          \"type\": \"string\",\n          \"description\": \"Define a custom database connection\",\n          \"alias\": \"c\"\n        },\n        {\n          \"name\": \"drop-views\",\n          \"propertyName\": \"dropViews\",\n          \"type\": \"boolean\",\n          \"description\": \"Drop all views\"\n        },\n        {\n          \"name\": \"drop-types\",\n          \"propertyName\": \"dropTypes\",\n          \"type\": \"boolean\",\n          \"description\": \"Drop all custom types (Postgres only)\"\n        },\n        {\n          \"name\": \"force\",\n          \"propertyName\": \"force\",\n          \"type\": \"boolean\",\n          \"description\": \"Explicitly force command to run in production\"\n        }\n      ]\n    },\n    \"db:truncate\": {\n      \"settings\": {\n        \"loadApp\": true\n      },\n      \"commandPath\": \"@adonisjs/lucid/build/commands/DbTruncate\",\n      \"commandName\": \"db:truncate\",\n      \"description\": \"Truncate all tables in database\",\n      \"args\": [],\n      \"aliases\": [],\n      \"flags\": [\n        {\n          \"name\": \"connection\",\n          \"propertyName\": \"connection\",\n          \"type\": \"string\",\n          \"description\": \"Define a custom database connection\",\n          \"alias\": \"c\"\n        },\n        {\n          \"name\": \"force\",\n          \"propertyName\": \"force\",\n          \"type\": \"boolean\",\n          \"description\": \"Explicitly force command to run in production\"\n        }\n      ]\n    },\n    \"make:model\": {\n      \"settings\": {\n        \"loadApp\": true\n      },\n      \"commandPath\": \"@adonisjs/lucid/build/commands/MakeModel\",\n      \"commandName\": \"make:model\",\n      \"description\": \"Make a new Lucid model\",\n      \"args\": [\n        {\n          \"type\": \"string\",\n          \"propertyName\": \"name\",\n          \"name\": \"name\",\n          \"required\": true,\n          \"description\": \"Name of the model class\"\n        }\n      ],\n      \"aliases\": [],\n      \"flags\": [\n        {\n          \"name\": \"migration\",\n          \"propertyName\": \"migration\",\n          \"type\": \"boolean\",\n          \"alias\": \"m\",\n          \"description\": \"Generate the migration for the model\"\n        },\n        {\n          \"name\": \"controller\",\n          \"propertyName\": \"controller\",\n          \"type\": \"boolean\",\n          \"alias\": \"c\",\n          \"description\": \"Generate the controller for the model\"\n        },\n        {\n          \"name\": \"factory\",\n          \"propertyName\": \"factory\",\n          \"type\": \"boolean\",\n          \"alias\": \"f\",\n          \"description\": \"Generate a factory for the model\"\n        }\n      ]\n    },\n    \"make:migration\": {\n      \"settings\": {\n        \"loadApp\": true\n      },\n      \"commandPath\": \"@adonisjs/lucid/build/commands/MakeMigration\",\n      \"commandName\": \"make:migration\",\n      \"description\": \"Make a new migration file\",\n      \"args\": [\n        {\n          \"type\": \"string\",\n          \"propertyName\": \"name\",\n          \"name\": \"name\",\n          \"required\": true,\n          \"description\": \"Name of the migration file\"\n        }\n      ],\n      \"aliases\": [],\n      \"flags\": [\n        {\n          \"name\": \"connection\",\n          \"propertyName\": \"connection\",\n          \"type\": \"string\",\n          \"description\": \"The connection flag is used to lookup the directory for the migration file\"\n        },\n        {\n          \"name\": \"folder\",\n          \"propertyName\": \"folder\",\n          \"type\": \"string\",\n          \"description\": \"Pre-select a migration directory\"\n        },\n        {\n          \"name\": \"create\",\n          \"propertyName\": \"create\",\n          \"type\": \"string\",\n          \"description\": \"Define the table name for creating a new table\"\n        },\n        {\n          \"name\": \"table\",\n          \"propertyName\": \"table\",\n          \"type\": \"string\",\n          \"description\": \"Define the table name for altering an existing table\"\n        }\n      ]\n    },\n    \"make:seeder\": {\n      \"settings\": {},\n      \"commandPath\": \"@adonisjs/lucid/build/commands/MakeSeeder\",\n      \"commandName\": \"make:seeder\",\n      \"description\": \"Make a new Seeder file\",\n      \"args\": [\n        {\n          \"type\": \"string\",\n          \"propertyName\": \"name\",\n          \"name\": \"name\",\n          \"required\": true,\n          \"description\": \"Name of the seeder class\"\n        }\n      ],\n      \"aliases\": [],\n      \"flags\": []\n    },\n    \"make:factory\": {\n      \"settings\": {},\n      \"commandPath\": \"@adonisjs/lucid/build/commands/MakeFactory\",\n      \"commandName\": \"make:factory\",\n      \"description\": \"Make a new factory\",\n      \"args\": [\n        {\n          \"type\": \"string\",\n          \"propertyName\": \"model\",\n          \"name\": \"model\",\n          \"required\": true,\n          \"description\": \"The name of the model\"\n        }\n      ],\n      \"aliases\": [],\n      \"flags\": [\n        {\n          \"name\": \"model-path\",\n          \"propertyName\": \"modelPath\",\n          \"type\": \"string\",\n          \"description\": \"The path to the model\"\n        },\n        {\n          \"name\": \"exact\",\n          \"propertyName\": \"exact\",\n          \"type\": \"boolean\",\n          \"description\": \"Create the factory with the exact name as provided\",\n          \"alias\": \"e\"\n        }\n      ]\n    },\n    \"migration:run\": {\n      \"settings\": {\n        \"loadApp\": true\n      },\n      \"commandPath\": \"@adonisjs/lucid/build/commands/Migration/Run\",\n      \"commandName\": \"migration:run\",\n      \"description\": \"Migrate database by running pending migrations\",\n      \"args\": [],\n      \"aliases\": [],\n      \"flags\": [\n        {\n          \"name\": \"connection\",\n          \"propertyName\": \"connection\",\n          \"type\": \"string\",\n          \"description\": \"Define a custom database connection\",\n          \"alias\": \"c\"\n        },\n        {\n          \"name\": \"force\",\n          \"propertyName\": \"force\",\n          \"type\": \"boolean\",\n          \"description\": \"Explicitly force to run migrations in production\"\n        },\n        {\n          \"name\": \"dry-run\",\n          \"propertyName\": \"dryRun\",\n          \"type\": \"boolean\",\n          \"description\": \"Do not run actual queries. Instead view the SQL output\"\n        },\n        {\n          \"name\": \"compact-output\",\n          \"propertyName\": \"compactOutput\",\n          \"type\": \"boolean\",\n          \"description\": \"A compact single-line output\"\n        },\n        {\n          \"name\": \"disable-locks\",\n          \"propertyName\": \"disableLocks\",\n          \"type\": \"boolean\",\n          \"description\": \"Disable locks acquired to run migrations safely\"\n        }\n      ]\n    },\n    \"migration:rollback\": {\n      \"settings\": {\n        \"loadApp\": true\n      },\n      \"commandPath\": \"@adonisjs/lucid/build/commands/Migration/Rollback\",\n      \"commandName\": \"migration:rollback\",\n      \"description\": \"Rollback migrations to a specific batch number\",\n      \"args\": [],\n      \"aliases\": [],\n      \"flags\": [\n        {\n          \"name\": \"connection\",\n          \"propertyName\": \"connection\",\n          \"type\": \"string\",\n          \"description\": \"Define a custom database connection\",\n          \"alias\": \"c\"\n        },\n        {\n          \"name\": \"force\",\n          \"propertyName\": \"force\",\n          \"type\": \"boolean\",\n          \"description\": \"Explictly force to run migrations in production\"\n        },\n        {\n          \"name\": \"dry-run\",\n          \"propertyName\": \"dryRun\",\n          \"type\": \"boolean\",\n          \"description\": \"Do not run actual queries. Instead view the SQL output\"\n        },\n        {\n          \"name\": \"batch\",\n          \"propertyName\": \"batch\",\n          \"type\": \"number\",\n          \"description\": \"Define custom batch number for rollback. Use 0 to rollback to initial state\"\n        },\n        {\n          \"name\": \"compact-output\",\n          \"propertyName\": \"compactOutput\",\n          \"type\": \"boolean\",\n          \"description\": \"A compact single-line output\"\n        },\n        {\n          \"name\": \"disable-locks\",\n          \"propertyName\": \"disableLocks\",\n          \"type\": \"boolean\",\n          \"description\": \"Disable locks acquired to run migrations safely\"\n        }\n      ]\n    },\n    \"migration:status\": {\n      \"settings\": {\n        \"loadApp\": true\n      },\n      \"commandPath\": \"@adonisjs/lucid/build/commands/Migration/Status\",\n      \"commandName\": \"migration:status\",\n      \"description\": \"View migrations status\",\n      \"args\": [],\n      \"aliases\": [],\n      \"flags\": [\n        {\n          \"name\": \"connection\",\n          \"propertyName\": \"connection\",\n          \"type\": \"string\",\n          \"description\": \"Define a custom database connection\",\n          \"alias\": \"c\"\n        }\n      ]\n    },\n    \"migration:reset\": {\n      \"settings\": {\n        \"loadApp\": true\n      },\n      \"commandPath\": \"@adonisjs/lucid/build/commands/Migration/Reset\",\n      \"commandName\": \"migration:reset\",\n      \"description\": \"Rollback all migrations\",\n      \"args\": [],\n      \"aliases\": [],\n      \"flags\": [\n        {\n          \"name\": \"connection\",\n          \"propertyName\": \"connection\",\n          \"type\": \"string\",\n          \"description\": \"Define a custom database connection\",\n          \"alias\": \"c\"\n        },\n        {\n          \"name\": \"force\",\n          \"propertyName\": \"force\",\n          \"type\": \"boolean\",\n          \"description\": \"Explicitly force command to run in production\"\n        },\n        {\n          \"name\": \"dry-run\",\n          \"propertyName\": \"dryRun\",\n          \"type\": \"boolean\",\n          \"description\": \"Do not run actual queries. Instead view the SQL output\"\n        },\n        {\n          \"name\": \"disable-locks\",\n          \"propertyName\": \"disableLocks\",\n          \"type\": \"boolean\",\n          \"description\": \"Disable locks acquired to run migrations safely\"\n        }\n      ]\n    },\n    \"migration:refresh\": {\n      \"settings\": {\n        \"loadApp\": true\n      },\n      \"commandPath\": \"@adonisjs/lucid/build/commands/Migration/Refresh\",\n      \"commandName\": \"migration:refresh\",\n      \"description\": \"Rollback and migrate database\",\n      \"args\": [],\n      \"aliases\": [],\n      \"flags\": [\n        {\n          \"name\": \"connection\",\n          \"propertyName\": \"connection\",\n          \"type\": \"string\",\n          \"description\": \"Define a custom database connection\",\n          \"alias\": \"c\"\n        },\n        {\n          \"name\": \"force\",\n          \"propertyName\": \"force\",\n          \"type\": \"boolean\",\n          \"description\": \"Explicitly force command to run in production\"\n        },\n        {\n          \"name\": \"dry-run\",\n          \"propertyName\": \"dryRun\",\n          \"type\": \"boolean\",\n          \"description\": \"Do not run actual queries. Instead view the SQL output\"\n        },\n        {\n          \"name\": \"seed\",\n          \"propertyName\": \"seed\",\n          \"type\": \"boolean\",\n          \"description\": \"Run seeders\"\n        },\n        {\n          \"name\": \"disable-locks\",\n          \"propertyName\": \"disableLocks\",\n          \"type\": \"boolean\",\n          \"description\": \"Disable locks acquired to run migrations safely\"\n        }\n      ]\n    },\n    \"migration:fresh\": {\n      \"settings\": {\n        \"loadApp\": true\n      },\n      \"commandPath\": \"@adonisjs/lucid/build/commands/Migration/Fresh\",\n      \"commandName\": \"migration:fresh\",\n      \"description\": \"Drop all tables and re-migrate the database\",\n      \"args\": [],\n      \"aliases\": [],\n      \"flags\": [\n        {\n          \"name\": \"connection\",\n          \"propertyName\": \"connection\",\n          \"type\": \"string\",\n          \"description\": \"Define a custom database connection\",\n          \"alias\": \"c\"\n        },\n        {\n          \"name\": \"force\",\n          \"propertyName\": \"force\",\n          \"type\": \"boolean\",\n          \"description\": \"Explicitly force command to run in production\"\n        },\n        {\n          \"name\": \"seed\",\n          \"propertyName\": \"seed\",\n          \"type\": \"boolean\",\n          \"description\": \"Run seeders\"\n        },\n        {\n          \"name\": \"drop-views\",\n          \"propertyName\": \"dropViews\",\n          \"type\": \"boolean\",\n          \"description\": \"Drop all views\"\n        },\n        {\n          \"name\": \"drop-types\",\n          \"propertyName\": \"dropTypes\",\n          \"type\": \"boolean\",\n          \"description\": \"Drop all custom types (Postgres only)\"\n        },\n        {\n          \"name\": \"disable-locks\",\n          \"propertyName\": \"disableLocks\",\n          \"type\": \"boolean\",\n          \"description\": \"Disable locks acquired to run migrations safely\"\n        }\n      ]\n    }\n  },\n  \"aliases\": {}\n}\n"
  },
  {
    "path": "app/Controllers/Http/GendersController.ts",
    "content": "import type { HttpContextContract } from \"@ioc:Adonis/Core/HttpContext\";\n\nimport Gender from \"App/Models/Gender\";\n\nexport default class GendersController {\n  public async index({ response }: HttpContextContract) {\n    try {\n      const genders = await Gender.query();\n      return response.json({\n        status: true,\n        data: genders,\n        message: \"Successfully fetched genders\",\n      });\n    } catch (err) {\n      return response.json({\n        status: false,\n        message: \"Something went wrong.\",\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "app/Controllers/Http/MatchesController.ts",
    "content": "import type { HttpContextContract } from \"@ioc:Adonis/Core/HttpContext\";\nimport { schema } from '@ioc:Adonis/Core/Validator'\n\nimport User from \"App/Models/User\";\nimport Match from \"App/Models/Match\";\nimport CreateMatchValidator from \"App/Validators/CreateMatchValidator\";\n\nexport default class MatchesController {\n  //retrive list of user's based on there gender preference\n  public async get({ response, auth }: HttpContextContract) {\n    try {\n      const userId = auth.user?.id;\n      if (!userId) {\n        return response.json({\n          message: \"kindly login\",\n        });\n      }\n\n      const currentUser = await User.query()\n        .where(\"id\", userId)\n        .preload(\"profile\")\n        .first();\n      const profile = currentUser?.profile;\n\n      if (!profile?.preferedGenderId) return response.json(\"Profile not exist\");\n\n      const users = await User.query()\n        .whereNot(\"id\", userId)\n        .preload(\"profile\")\n        .with(\"profile\", (q) => {\n          q.where(\"prefered_gender_id\", profile.preferedGenderId);\n        });\n\n      const mappedUsers = users?.map((u) => {\n        return {\n          id: u.id,\n          name: u.name,\n          avatar: u?.profile?.avatar,\n        };\n      });\n      return response.json({\n        data: mappedUsers,\n      });\n    } catch (err) {\n      console.log(\"errrrr\", err);\n    }\n  }\n\n  public async mutualMatch({ request, response, auth }: HttpContextContract) {\n    try {\n      const authId = auth.user?.id;\n      if (!authId) {\n        response.status(401);\n        return;\n      }\n\n      const payload = await request.validate(CreateMatchValidator);\n      const { userId } = payload;\n      if(authId == userId) {\n        return response.json({\n          message: \"Cannot mark youself as match.\"\n        })\n      }\n\n      const userExist = await User.query().where(\"id\", userId).first();\n      if(!userExist) {\n        return response.json({\n          message: \"User not found.\"\n        })\n      }\n\n      const matchExist = await Match.query()\n        .where(\"matcher_user_id\", userId)\n        .where(\"matched_user_id\", authId)\n        .first();\n\n      if (matchExist) {\n        await Match.query().where(\"id\", matchExist.id).update({\n          mutual_match: 1,\n          match_date: new Date(),\n        });\n\n        const matchedUser = await User.query()\n          .where(\"id\", userId)\n          .select(\"name\", \"email\")\n          // .preload(\"profile\")\n          .first();\n\n        const userData = {\n          name: matchedUser?.name,\n          email: matchedUser?.email,\n        };\n\n        return response.json({\n          message: \"It's a mutual match\",\n          data: userData,\n        });\n      }\n\n      const newMatch = new Match();\n      newMatch.matcherUserId = authId;\n      newMatch.matchedUserId = userId;\n      await newMatch.save();\n\n      return response.json({\n        message: \"Match has been marked.\",\n      });\n    } catch (err) {\n      return response.json({\n        message: \"Something went wrong.\",\n        errors: err?.messages?.errors,\n      });\n    }\n  }\n  public async history({ response, auth }: HttpContextContract) {\n    try {\n      const authId = auth.user?.id;\n      if (!authId) {\n        return response.json({\n          message: \"kindly login\",\n        });\n      }\n\n      const matchHistory = await Match.query()\n        .where(\"matcher_user_id\", authId)\n        .orWhere(\"matched_user_id\", authId)\n        .where(\"mutual_match\", 1);\n\n      const userIds = matchHistory?.map((history: Match) => {\n        return history.matcherUserId == authId\n          ? String(history.matchedUserId)\n          : String(history.matcherUserId);\n      });\n\n      const users = await User.query()\n        .whereIn(\"id\", userIds)\n        .select(\"name\", \"email\")\n\n      return response.json({\n        data: users,\n        message: \"Mutual match history.\",\n      });\n    } catch (err) {\n      return response.json({\n        message: \"Something went wrong.\",\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "app/Controllers/Http/ProfilesController.ts",
    "content": "import type { HttpContextContract } from \"@ioc:Adonis/Core/HttpContext\";\n\nimport Profile from \"App/Models/Profile\";\nimport SpotifyService from \"App/Services/SpotifyService\";\nimport CreateProfileValidator from \"App/Validators/CreateProfileValidator\";\n\nexport default class ProfilesController {\n  public async get({ response, auth }: HttpContextContract) {\n    try {\n      const userId = auth.user?.id;\n      if (!userId) {\n        response.status(401);\n        return;\n      }\n\n      const profile = await Profile.query().where(\"user_id\", userId).first();\n      return response.json({\n        data: profile,\n      });\n    } catch (err) {\n      return response.json({\n        status: false,\n        message: \"Something went wrong.\",\n      });\n    }\n  }\n  public async store({ request, response, auth }: HttpContextContract) {\n    try {\n      const userId = auth.user?.id;\n      if (!userId) {\n        response.status(401);\n        return;\n      }\n      const payload = await request.validate(CreateProfileValidator);\n      const { dateOfBirth, description, preferedGenderId, trackIds } =\n      payload;\n\n      const fileName = `${new Date().getTime()}.${payload.avatar.subtype}`;\n      await payload.avatar.moveToDisk(\"./\", {\n        name: fileName,\n      });\n\n      //save top 4 selected tracks by user\n      const favoriteTracks = await SpotifyService.updateFavorityTrack(\n        userId,\n        trackIds\n      );\n      if (!favoriteTracks?.status)\n        throw Error(\"unable to update favorite tracks\");\n\n      //Profile saved\n      let profile;\n      profile = await Profile.query().where(\"user_id\", userId).first();\n      profile = profile ? profile : new Profile();\n      profile.dateOfBirth = new Date(dateOfBirth);\n      profile.description = description;\n      profile.avatar = `/uploads/${fileName}`;\n      profile.preferedGenderId = preferedGenderId;\n      profile.userId = userId;\n      await profile.save();\n\n      return response.json({\n        status: true,\n        message: \"Profile Successfully Created\",\n        data: profile,\n      });\n    } catch (err) {\n      return response.json({\n        status: false,\n        message: \"Unable to create profile\",\n        errors: err?.messages?.errors,\n      });\n    }\n  }\n}\n"
  },
  {
    "path": "app/Controllers/Http/SpotifyController.ts",
    "content": "import type { HttpContextContract } from \"@ioc:Adonis/Core/HttpContext\";\nimport SpotifyService from \"App/Services/SpotifyService\";\n\nexport default class SpotifiesController {\n  public async artists({ response, auth }: HttpContextContract) {\n    try {\n      const userId = auth.user?.id;\n      const topArtists = await SpotifyService.getArtists(userId);\n      const savedArtists = await SpotifyService.saveArtists(userId, topArtists);\n      return response.json(savedArtists);\n    } catch (err) {\n      console.log(\"err\", err);\n    }\n  }\n\n  public async tracks({ response, auth }: HttpContextContract) {\n    try {\n      const userId = auth.user?.id;\n      const topTracks = await SpotifyService.getTracks(userId);\n      const mappdTracks = topTracks?.map((track) => {\n        return {\n          // image: track?.preview_url,\n          // uri: track.uri,\n          popularity: track.popularity,\n          name: track.name,\n          trackId: track.id,\n          album: track?.albun?.name,\n          // artists: artists,\n        };\n      });\n      // const savedTracks = await SpotifyService.saveTracks(userId, topTracks);\n\n      return response.json(mappdTracks);\n    } catch (err) {\n      console.log(\"err\", err);\n    }\n  }\n\n  public async trackByName({request, response, auth}: HttpContextContract) {\n    try {\n      const userId = auth.user?.id;\n      const { name } = request.qs()\n\n      const tracks = await SpotifyService.getTracksByName(userId, name)\n      \n      const mappedTracks = tracks?.map((track) => {\n        return  {\n          uri: track.uri,\n          popularity: track.popularity,\n          name: track.name,\n          trackId: track.id,\n          album: track?.album?.name,\n        }\n      }) \n\n      return response.json({\n        status: true,\n        data: mappedTracks\n      })\n    } catch(err) {\n      return response.json({\n        status: false,\n        message: \"Error fetching track's\"\n      })\n    }\n  }\n  \n}\n"
  },
  {
    "path": "app/Controllers/Http/UsersController.ts",
    "content": "import type { HttpContextContract } from \"@ioc:Adonis/Core/HttpContext\";\nimport Artist from \"App/Models/Artist\";\nimport SocialToken from \"App/Models/SocialToken\";\nimport Track from \"App/Models/Track\";\nimport User from \"App/Models/User\";\nimport SpotifyService from \"App/Services/SpotifyService\";\n\nexport default class UsersController {\n  public async redirect({ ally }: HttpContextContract) {\n    return ally.use(\"spotify\").stateless().redirect();\n  }\n\n  public async handleCallback({ ally, auth, response }: HttpContextContract) {\n    try {\n      const spotify = ally.use(\"spotify\").stateless();\n\n      /**\n       * User has explicitly denied the login request\n       */\n      if (spotify.accessDenied()) {\n        return \"Access was denied\";\n      }\n\n      /**\n       * Unable to verify the CSRF state\n       */\n      if (spotify.stateMisMatch()) {\n        return \"Request expired. try again\";\n      }\n\n      /**\n       * There was an unknown error during the redirect\n       */\n      if (spotify.hasError()) {\n        return spotify.getError();\n      }\n\n      /**\n       * Managing error states here\n       */\n\n      const user = await spotify.user();\n\n      const { token } = user;\n\n      const findUser = {\n        email: user.email as string,\n      };\n\n      const userDetails = {\n        name: user.name as string,\n        email: user.email as string,\n        provider: \"spotify\",\n        access_token: token.token as any,\n      };\n\n      const newUser = await User.firstOrCreate(findUser, userDetails);\n\n      if (!newUser) {\n        return response.json({\n          status: false,\n          message: \"Something went wrong.\",\n        });\n      }\n\n      /* Save Social Token */\n      let socialToken = await SocialToken.query()\n        .where(\"user_id\", newUser.id)\n        .first();\n\n      socialToken = socialToken ? socialToken : new SocialToken();\n      socialToken.user_id = newUser.id;\n      socialToken.token = token.token;\n      socialToken.refreshToken = token.refreshToken;\n      socialToken.type = token.type;\n      socialToken.expiresAt = token.expiresAt?.toString();\n\n      await socialToken.save();\n      /* Save Social Token */\n\n      //save top 20 tracks from spotify service\n      const trackExist = await Track.query().where(\"user_id\", newUser.id);\n      if (!trackExist?.length) {\n        const topTracks = await SpotifyService.getTracks(newUser.id);\n        const topTracksSaved = await SpotifyService.saveTracks(\n          newUser.id,\n          topTracks\n        );\n        if (!topTracksSaved?.status) throw Error(\"Unable to save top tracks\");\n      }\n\n      //save top 20 artists from spotify service\n\n      const artists = await Artist.query().where(\"user_id\", newUser.id);\n      if (!artists?.length) {\n        const topArtists = await SpotifyService.getArtists(newUser.id);\n        const topArtistsSaved = await SpotifyService.saveArtists(\n          newUser.id,\n          topArtists\n        );\n        if (!topArtistsSaved?.status)\n          throw Error(\"Unable to save artists tracks\");\n      }\n\n      // Generate API token\n\n      const userToken = await auth.use(\"api\").generate(newUser, {\n        expiresIn: \"90 mins\",\n      });\n\n      response.json({ /* newUser, */ userToken /* , socialToken */ });\n    } catch (err) {\n      response.json({\n        status: false,\n        message: \"Something went wrong.\",\n      });\n    }\n  }\n\n  public async logout({ auth, response }: HttpContextContract) {\n    await auth.use(\"api\").revoke();\n    return response.json({\n      revoked: true,\n    });\n  }\n}\n"
  },
  {
    "path": "app/Exceptions/Handler.ts",
    "content": "/*\n|--------------------------------------------------------------------------\n| Http Exception Handler\n|--------------------------------------------------------------------------\n|\n| AdonisJs will forward all exceptions occurred during an HTTP request to\n| the following class. You can learn more about exception handling by\n| reading docs.\n|\n| The exception handler extends a base `HttpExceptionHandler` which is not\n| mandatory, however it can do lot of heavy lifting to handle the errors\n| properly.\n|\n*/\n\nimport Logger from '@ioc:Adonis/Core/Logger'\nimport HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler'\n\nexport default class ExceptionHandler extends HttpExceptionHandler {\n  constructor () {\n    super(Logger)\n  }\n}\n"
  },
  {
    "path": "app/Middleware/Auth.ts",
    "content": "import { AuthenticationException } from '@adonisjs/auth/build/standalone'\nimport type { GuardsList } from '@ioc:Adonis/Addons/Auth'\nimport type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'\n\n/**\n * Auth middleware is meant to restrict un-authenticated access to a given route\n * or a group of routes.\n *\n * You must register this middleware inside `start/kernel.ts` file under the list\n * of named middleware.\n */\nexport default class AuthMiddleware {\n  /**\n   * The URL to redirect to when request is Unauthorized\n   */\n  protected redirectTo = '/login'\n\n  /**\n   * Authenticates the current HTTP request against a custom set of defined\n   * guards.\n   *\n   * The authentication loop stops as soon as the user is authenticated using any\n   * of the mentioned guards and that guard will be used by the rest of the code\n   * during the current request.\n   */\n  protected async authenticate(auth: HttpContextContract['auth'], guards: (keyof GuardsList)[]) {\n    /**\n     * Hold reference to the guard last attempted within the for loop. We pass\n     * the reference of the guard to the \"AuthenticationException\", so that\n     * it can decide the correct response behavior based upon the guard\n     * driver\n     */\n    let guardLastAttempted: string | undefined\n\n    for (let guard of guards) {\n      guardLastAttempted = guard\n\n      if (await auth.use(guard).check()) {\n        /**\n         * Instruct auth to use the given guard as the default guard for\n         * the rest of the request, since the user authenticated\n         * succeeded here\n         */\n        auth.defaultGuard = guard\n        return true\n      }\n    }\n\n    /**\n     * Unable to authenticate using any guard\n     */\n    throw new AuthenticationException(\n      'Unauthorized access',\n      'E_UNAUTHORIZED_ACCESS',\n      guardLastAttempted,\n      this.redirectTo,\n    )\n  }\n\n  /**\n   * Handle request\n   */\n  public async handle (\n    { auth }: HttpContextContract,\n    next: () => Promise<void>,\n    customGuards: (keyof GuardsList)[]\n  ) {\n    /**\n     * Uses the user defined guards or the default guard mentioned in\n     * the config file\n     */\n    const guards = customGuards.length ? customGuards : [auth.name]\n    await this.authenticate(auth, guards)\n    await next()\n  }\n}\n"
  },
  {
    "path": "app/Middleware/SilentAuth.ts",
    "content": "import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'\n\n/**\n * Silent auth middleware can be used as a global middleware to silent check\n * if the user is logged-in or not.\n *\n * The request continues as usual, even when the user is not logged-in.\n */\nexport default class SilentAuthMiddleware {\n  /**\n   * Handle request\n   */\n  public async handle({ auth }: HttpContextContract, next: () => Promise<void>) {\n    /**\n     * Check if user is logged-in or not. If yes, then `ctx.auth.user` will be\n     * set to the instance of the currently logged in user.\n     */\n    await auth.check()\n    await next()\n  }\n}\n"
  },
  {
    "path": "app/Models/ApiToken.ts",
    "content": "import { DateTime } from 'luxon'\nimport { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'\n\nexport default class ApiToken extends BaseModel {\n  @column({ isPrimary: true })\n  public id: number\n\n  @column.dateTime({ autoCreate: true })\n  public createdAt: DateTime\n\n  @column.dateTime({ autoCreate: true, autoUpdate: true })\n  public updatedAt: DateTime\n\n  @column()\n  public token: String\n}\n"
  },
  {
    "path": "app/Models/Artist.ts",
    "content": "import { DateTime } from 'luxon'\nimport { BaseModel, HasMany, column, hasMany } from '@ioc:Adonis/Lucid/Orm'\nimport Genre from './Genre'\n\nexport default class Artist extends BaseModel {\n  @column({ isPrimary: true })\n  public id: number\n\n  @column.dateTime({ autoCreate: true })\n  public createdAt: DateTime\n\n  @column.dateTime({ autoCreate: true, autoUpdate: true })\n  public updatedAt: DateTime\n\n  @column()\n  public userId: String\n\n  @column()\n  public name: String\n\n  @column()\n  public type: String\n  \n  @column()\n  public popularity: String\n  \n  @column()\n  public uri: String\n  \n  @column()\n  public spotifyArtistId: String\n  \n  @column()\n  public artistImage: String\n\n\n  @hasMany(() => Genre)\n  public genres: HasMany<typeof Genre>\n\n}\n"
  },
  {
    "path": "app/Models/Gender.ts",
    "content": "import { DateTime } from 'luxon'\nimport { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'\n\nexport default class Gender extends BaseModel {\n  @column({ isPrimary: true })\n  public id: number\n\n  @column()\n  public name: string\n\n  @column.dateTime({ autoCreate: true })\n  public createdAt: DateTime\n\n  @column.dateTime({ autoCreate: true, autoUpdate: true })\n  public updatedAt: DateTime\n}\n"
  },
  {
    "path": "app/Models/Genre.ts",
    "content": "import { DateTime } from 'luxon'\nimport { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'\n\nexport default class Genre extends BaseModel {\n  @column({ isPrimary: true })\n  public id: number\n\n  @column.dateTime({ autoCreate: true })\n  public createdAt: DateTime\n\n  @column.dateTime({ autoCreate: true, autoUpdate: true })\n  public updatedAt: DateTime\n\n  @column()\n  public name: String\n\n  @column()\n  public artistId: Number\n}\n"
  },
  {
    "path": "app/Models/Match.ts",
    "content": "import { DateTime } from 'luxon'\nimport { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'\n\nexport default class Match extends BaseModel {\n  @column({ isPrimary: true })\n  public id: number\n\n  @column.dateTime({ autoCreate: true })\n  public createdAt: DateTime\n\n  @column.dateTime({ autoCreate: true, autoUpdate: true })\n  public updatedAt: DateTime\n\n  @column()\n  public matcherUserId: Number\n\n  @column()\n  public matchedUserId: Number\n\n  @column()\n  public mutualMatch: Boolean\n\n  @column()\n  public matchDate: Date\n}\n"
  },
  {
    "path": "app/Models/Profile.ts",
    "content": "import { DateTime } from 'luxon'\nimport { BaseModel, HasOne, column, hasOne } from '@ioc:Adonis/Lucid/Orm'\nimport Gender from './Gender'\n\nexport default class Profile extends BaseModel {\n  @column({ isPrimary: true })\n  public id: number\n\n  @column.dateTime({ autoCreate: true })\n  public createdAt: DateTime\n\n  @column.dateTime({ autoCreate: true, autoUpdate: true })\n  public updatedAt: DateTime\n\n  @column()\n  public dateOfBirth: Date\n  \n  @column()\n  public description: String\n  \n  @column()\n  public avatar: String\n\n  @column()\n  public preferedGenderId: Number\n\n  @column()\n  public userId: Number\n\n  @hasOne(() => Gender)\n  public preferedGender: HasOne<typeof Gender>\n\n}\n"
  },
  {
    "path": "app/Models/SocialToken.ts",
    "content": "import { DateTime } from 'luxon'\nimport { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'\n\nexport default class SocialToken extends BaseModel {\n  @column({ isPrimary: true })\n  public id: number\n\n  @column()\n  public user_id: number\n\n  @column()\n  public token: string\n\n  @column()\n  public type: string\n\n  @column()\n  public refreshToken: string\n\n  @column()\n  public expiresAt: String\n\n  @column.dateTime({ autoCreate: true })\n  public createdAt: DateTime\n\n  @column.dateTime({ autoCreate: true, autoUpdate: true })\n  public updatedAt: DateTime\n}\n"
  },
  {
    "path": "app/Models/Track.ts",
    "content": "import { DateTime } from 'luxon'\nimport { BaseModel, HasMany, column, hasMany } from '@ioc:Adonis/Lucid/Orm'\nimport Artist from './Artist'\n\nexport default class Track extends BaseModel {\n  @column({ isPrimary: true })\n  public id: number\n\n  @column.dateTime({ autoCreate: true })\n  public createdAt: DateTime\n\n  @column.dateTime({ autoCreate: true, autoUpdate: true })\n  public updatedAt: DateTime\n\n  @column()\n  public userId: String\n\n  @column()\n  public uri: String\n  \n  @column()\n  public popularity: String\n\n  @column()\n  public name: String\n\n  \n  @column()\n  public tractImage: String\n  \n  @column()\n  public trackId: String\n  \n  @column()\n  public album: String\n\n\n  @column()\n  public favorite: Number\n\n\n  @hasMany(() => Artist)\n  public artists: HasMany<typeof Artist>\n\n\n}\n"
  },
  {
    "path": "app/Models/User.ts",
    "content": "import { DateTime } from 'luxon'\nimport Hash from '@ioc:Adonis/Core/Hash'\nimport { column, beforeSave, BaseModel, hasMany, HasMany, hasOne, HasOne } from '@ioc:Adonis/Lucid/Orm'\nimport Track from './Track'\nimport Artist from './Artist'\nimport Profile from './Profile'\n\nexport default class User extends BaseModel {\n  @column({ isPrimary: true })\n  public id: number\n\n  @column()\n  public name: string\n\n  @column()\n  public provider: string\n\n  @column()\n  public access_token: string\n\n  @column()\n  public email: string\n\n  @column({ serializeAs: null })\n  public password: string\n\n  @column()\n  public rememberMeToken: string | null\n\n  @column.dateTime({ autoCreate: true })\n  public createdAt: DateTime\n\n  @column.dateTime({ autoCreate: true, autoUpdate: true })\n  public updatedAt: DateTime\n\n  // @beforeSave()\n  // public static async hashPassword (user: User) {\n  //   if (user.$dirty.password) {\n  //     user.password = await Hash.make(user.password)\n  //   }\n  // }\n\n  @hasOne(() => Profile)\n  public profile: HasOne<typeof Profile>\n\n  @hasMany(() => Track)\n  public tracks: HasMany<typeof Track>\n\n  @hasMany(() => Artist)\n  public artists: HasMany<typeof Artist>\n\n}\n"
  },
  {
    "path": "app/Services/SpotifyService.ts",
    "content": "import Env from \"@ioc:Adonis/Core/Env\";\n\nimport Artist from \"App/Models/Artist\";\nimport Genre from \"App/Models/Genre\";\nimport SocialToken from \"App/Models/SocialToken\";\nimport Track from \"App/Models/Track\";\nimport Axios from \"axios\";\n\nexport default class SpotifyService {\n  public static async getArtists(userId) {\n    const SPOTIFY_URL = Env.get(\"SPOTIFY_URL\");\n    const social = await SocialToken.query().where(\"user_id\", userId).first();\n\n    const resp = await Axios.get(`${SPOTIFY_URL}/me/top/artists`, {\n      headers: {\n        Authorization: `Bearer ${social?.token}`,\n      },\n    });\n\n    return resp?.data?.items;\n  }\n\n  public static async getTracks(userId) {\n    const SPOTIFY_URL = Env.get(\"SPOTIFY_URL\");\n    const social = await SocialToken.query().where(\"user_id\", userId).first();\n    const resp = await Axios.get(\n      `${SPOTIFY_URL}/me/top/tracks?time_range=medium_term&limit=5`,\n      {\n        headers: {\n          Authorization: `Bearer ${social?.token}`,\n        },\n      }\n    );\n    return resp?.data?.items;\n  }\n\n  public static async saveArtists(userId, artists) {\n    try {\n      for (let artist of artists) {\n        //store artists\n        const newArtist = new Artist();\n        newArtist.userId = userId;\n        newArtist.name = artist?.name;\n        newArtist.type = artist?.type;\n        newArtist.popularity = artist?.popularity;\n        newArtist.uri = artist?.uri;\n        newArtist.spotifyArtistId = artist?.id;\n        newArtist.artistImage = artist?.images ? artist?.images[0]?.url : null;\n\n        artist.genres = artist.genres?.map((name) => {\n          let genre = new Genre();\n          genre.name = name;\n          return genre;\n        });\n\n        //store artists genres\n        await newArtist.related(\"genres\").saveMany(artist.genres);\n      }\n      return {\n        status: true,\n      };\n    } catch (err) {\n      console.log(\"FAILIURE\");\n      console.log(\"err\", err);\n      return {\n        status: false,\n      };\n    }\n  }\n\n  public static async saveTracks(userId, tracks) {\n    try {\n      for (let track of tracks) {\n        //store tracks\n        const newTrack = new Track();\n        newTrack.userId = userId;\n        newTrack.uri = track.uri;\n        newTrack.popularity = track.popularity;\n        newTrack.name = track.name;\n        newTrack.trackId = track.id;\n        newTrack.album = track?.album?.name;\n        await newTrack.save();\n      }\n      return {\n        status: true,\n      };\n    } catch (err) {\n      return {\n        status: false,\n      };\n    }\n  }\n\n  public static async getTracksByIds(userId, trackIds) {\n    const SPOTIFY_URL = Env.get(\"SPOTIFY_URL\");\n\n    const commaSeparatedIds = trackIds.join(\",\");\n    const social = await SocialToken.query().where(\"user_id\", userId).first();\n    const resp = await Axios.get(\n      `${SPOTIFY_URL}/tracks?ids=${commaSeparatedIds}`,\n      {\n        headers: {\n          Authorization: `Bearer ${social?.token}`,\n        },\n      }\n    );\n    return resp?.data?.tracks;\n  }\n\n  public static getTracksData(tracks) {\n    const mappdTracks = tracks?.map((track) => {\n      return {\n        popularity: track.popularity,\n        name: track.name,\n        trackId: track.id,\n        album: track?.albun?.name,\n      };\n    });\n\n    return mappdTracks;\n  }\n\n  public static async updateFavorityTrack(userId, trackIds) {\n    try {\n      const markFavorite = await Track.query()\n        .where(\"user_id\", userId)\n        .whereIn(\"track_id\", trackIds)\n        .update({\n          favorite: 1,\n        });\n\n      return {\n        status: true,\n        data: markFavorite,\n      };\n    } catch (err) {\n      return {\n        status: false,\n      };\n    }\n  }\n\n  public static async getTracksByName(userId, name) {\n    const SPOTIFY_URL = Env.get(\"SPOTIFY_URL\");\n    const social = await SocialToken.query().where(\"user_id\", userId).first();\n    const resp = await Axios.get(\n      `${SPOTIFY_URL}/search?q=track:${name}&type=track`,\n      {\n        headers: {\n          Authorization: `Bearer ${social?.token}`,\n        },\n      }\n    );\n    return resp?.data?.tracks?.items;\n  }\n}\n"
  },
  {
    "path": "app/Validators/CreateMatchValidator.ts",
    "content": "import { schema } from '@ioc:Adonis/Core/Validator'\nimport { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'\n\nexport default class CreateMatchValidator {\n  constructor(protected ctx: HttpContextContract) {}\n\n  /*\n   * Define schema to validate the \"shape\", \"type\", \"formatting\" and \"integrity\" of data.\n   *\n   * For example:\n   * 1. The username must be of data type string. But then also, it should\n   *    not contain special characters or numbers.\n   *    ```\n   *     schema.string({}, [ rules.alpha() ])\n   *    ```\n   *\n   * 2. The email must be of data type string, formatted as a valid\n   *    email. But also, not used by any other user.\n   *    ```\n   *     schema.string({}, [\n   *       rules.email(),\n   *       rules.unique({ table: 'users', column: 'email' }),\n   *     ])\n   *    ```\n   */\n\n  public schema = schema.create({\n    userId: schema.number(),\n  })\n\n  /**\n   * Custom messages for validation failures. You can make use of dot notation `(.)`\n   * for targeting nested fields and array expressions `(*)` for targeting all\n   * children of an array. For example:\n   *\n   * {\n   *   'profile.username.required': 'Username is required',\n   *   'scores.*.number': 'Define scores as valid numbers'\n   * }\n   *\n   */\n  public messages = {\n    required: 'The {{ field }} is required.',\n    // 'username.unique': 'Username not available'\n  }\n}\n"
  },
  {
    "path": "app/Validators/CreateProfileValidator.ts",
    "content": "import { schema } from '@ioc:Adonis/Core/Validator'\nimport { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'\n\nexport default class CreateProfileValidator {\n  constructor(protected ctx: HttpContextContract) {}\n\n  /*\n   * Define schema to validate the \"shape\", \"type\", \"formatting\" and \"integrity\" of data.\n   *\n   * For example:\n   * 1. The username must be of data type string. But then also, it should\n   *    not contain special characters or numbers.\n   *    ```\n   *     schema.string({}, [ rules.alpha() ])\n   *    ```\n   *\n   * 2. The email must be of data type string, formatted as a valid\n   *    email. But also, not used by any other user.\n   *    ```\n   *     schema.string({}, [\n   *       rules.email(),\n   *       rules.unique({ table: 'users', column: 'email' }),\n   *     ])\n   *    ```\n   */\n\n  public schema = schema.create({\n    dateOfBirth: schema.string(),\n    description: schema.string.nullableAndOptional(),\n    preferedGenderId: schema.number(),\n    trackIds: schema.array().members(schema.string()),\n    avatar: schema.file({\n      size: \"5mb\",\n      extnames: [\"jpg\", \"png\", \"jpeg\"],\n    }),\n  })\n\n  /**\n   * Custom messages for validation failures. You can make use of dot notation `(.)`\n   * for targeting nested fields and array expressions `(*)` for targeting all\n   * children of an array. For example:\n   *\n   * {\n   *   'profile.username.required': 'Username is required',\n   *   'scores.*.number': 'Define scores as valid numbers'\n   * }\n   *\n   */\n  public messages = {\n    required: 'The {{ field }} is required.',\n    // 'username.unique': 'Username not available'\n  }\n}\n"
  },
  {
    "path": "commands/index.ts",
    "content": "import { listDirectoryFiles } from '@adonisjs/core/build/standalone'\nimport Application from '@ioc:Adonis/Core/Application'\n\n/*\n|--------------------------------------------------------------------------\n| Exporting an array of commands\n|--------------------------------------------------------------------------\n|\n| Instead of manually exporting each file from this directory, we use the\n| helper `listDirectoryFiles` to recursively collect and export an array\n| of filenames.\n|\n| Couple of things to note:\n|\n| 1. The file path must be relative from the project root and not this directory.\n| 2. We must ignore this file to avoid getting into an infinite loop\n|\n*/\nexport default listDirectoryFiles(__dirname, Application.appRoot, ['./commands/index'])\n"
  },
  {
    "path": "compose.yml",
    "content": "services:\n  db:\n    image: mysql:8.0\n    platform: linux/x86_64\n    ports:\n      - 3306:3306\n    volumes:\n      - mysql-data:/var/lib/mysql\n    environment:\n      - MYSQL_ROOT_PASSWORD=\n      - MYSQL_ALLOW_EMPTY_PASSWORD=true\n\nvolumes:\n  mysql-data:\n"
  },
  {
    "path": "config/ally.ts",
    "content": "/**\n * Config source: https://git.io/JOdi5\n *\n * Feel free to let us know via PR, if you find something broken in this config\n * file.\n */\n\nimport Env from '@ioc:Adonis/Core/Env'\nimport { AllyConfig } from '@ioc:Adonis/Addons/Ally'\n\n/*\n|--------------------------------------------------------------------------\n| Ally Config\n|--------------------------------------------------------------------------\n|\n| The `AllyConfig` relies on the `SocialProviders` interface which is\n| defined inside `contracts/ally.ts` file.\n|\n*/\nconst allyConfig: AllyConfig = {\n  /*\n  |--------------------------------------------------------------------------\n  | Spotify driver\n  |--------------------------------------------------------------------------\n  */\n  spotify: {\n    driver: 'spotify',\n    clientId: Env.get('SPOTIFY_CLIENT_ID'),\n    clientSecret: Env.get('SPOTIFY_CLIENT_SECRET'),\n    callbackUrl: Env.get('SPOTIFY_CALLBACK_URL', 'http://localhost:3333/api/signin-callback'),\n    scopes: ['user-read-email', 'user-top-read', 'user-follow-read'],\n    showDialog: false\n  },\n}\n\nexport default allyConfig\n"
  },
  {
    "path": "config/app.ts",
    "content": "/**\n * Config source: https://git.io/JfefZ\n *\n * Feel free to let us know via PR, if you find something broken in this config\n * file.\n */\n\nimport proxyAddr from 'proxy-addr'\nimport Env from '@ioc:Adonis/Core/Env'\nimport type { ServerConfig } from '@ioc:Adonis/Core/Server'\nimport type { LoggerConfig } from '@ioc:Adonis/Core/Logger'\nimport type { ProfilerConfig } from '@ioc:Adonis/Core/Profiler'\nimport type { ValidatorConfig } from '@ioc:Adonis/Core/Validator'\n\n/*\n|--------------------------------------------------------------------------\n| Application secret key\n|--------------------------------------------------------------------------\n|\n| The secret to encrypt and sign different values in your application.\n| Make sure to keep the `APP_KEY` as an environment variable and secure.\n|\n| Note: Changing the application key for an existing app will make all\n| the cookies invalid and also the existing encrypted data will not\n| be decrypted.\n|\n*/\nexport const appKey: string = Env.get('APP_KEY')\n\n/*\n|--------------------------------------------------------------------------\n| Http server configuration\n|--------------------------------------------------------------------------\n|\n| The configuration for the HTTP(s) server. Make sure to go through all\n| the config properties to make keep server secure.\n|\n*/\nexport const http: ServerConfig = {\n  /*\n  |--------------------------------------------------------------------------\n  | Allow method spoofing\n  |--------------------------------------------------------------------------\n  |\n  | Method spoofing enables defining custom HTTP methods using a query string\n  | `_method`. This is usually required when you are making traditional\n  | form requests and wants to use HTTP verbs like `PUT`, `DELETE` and\n  | so on.\n  |\n  */\n  allowMethodSpoofing: false,\n\n  /*\n  |--------------------------------------------------------------------------\n  | Subdomain offset\n  |--------------------------------------------------------------------------\n  */\n  subdomainOffset: 2,\n\n  /*\n  |--------------------------------------------------------------------------\n  | Request Ids\n  |--------------------------------------------------------------------------\n  |\n  | Setting this value to `true` will generate a unique request id for each\n  | HTTP request and set it as `x-request-id` header.\n  |\n  */\n  generateRequestId: false,\n\n  /*\n  |--------------------------------------------------------------------------\n  | Trusting proxy servers\n  |--------------------------------------------------------------------------\n  |\n  | Define the proxy servers that AdonisJs must trust for reading `X-Forwarded`\n  | headers.\n  |\n  */\n  trustProxy: proxyAddr.compile('loopback'),\n\n  /*\n  |--------------------------------------------------------------------------\n  | Generating Etag\n  |--------------------------------------------------------------------------\n  |\n  | Whether or not to generate an etag for every response.\n  |\n  */\n  etag: false,\n\n  /*\n  |--------------------------------------------------------------------------\n  | JSONP Callback\n  |--------------------------------------------------------------------------\n  */\n  jsonpCallbackName: 'callback',\n\n  /*\n  |--------------------------------------------------------------------------\n  | Cookie settings\n  |--------------------------------------------------------------------------\n  */\n  cookie: {\n    domain: '',\n    path: '/',\n    maxAge: '2h',\n    httpOnly: true,\n    secure: false,\n    sameSite: false,\n  },\n\n  /*\n  |--------------------------------------------------------------------------\n  | Force Content Negotiation\n  |--------------------------------------------------------------------------\n  |\n  | The internals of the framework relies on the content negotiation to\n  | detect the best possible response type for a given HTTP request.\n  |\n  | However, it is a very common these days that API servers always wants to\n  | make response in JSON regardless of the existence of the `Accept` header.\n  |\n  | By setting `forceContentNegotiationTo = 'application/json'`, you negotiate\n  | with the server in advance to always return JSON without relying on the\n  | client to set the header explicitly.\n  |\n  */\n  forceContentNegotiationTo: 'application/json',\n}\n\n/*\n|--------------------------------------------------------------------------\n| Logger\n|--------------------------------------------------------------------------\n*/\nexport const logger: LoggerConfig = {\n  /*\n  |--------------------------------------------------------------------------\n  | Application name\n  |--------------------------------------------------------------------------\n  |\n  | The name of the application you want to add to the log. It is recommended\n  | to always have app name in every log line.\n  |\n  | The `APP_NAME` environment variable is automatically set by AdonisJS by\n  | reading the `name` property from the `package.json` file.\n  |\n  */\n  name: Env.get('APP_NAME'),\n\n  /*\n  |--------------------------------------------------------------------------\n  | Toggle logger\n  |--------------------------------------------------------------------------\n  |\n  | Enable or disable logger application wide\n  |\n  */\n  enabled: true,\n\n  /*\n  |--------------------------------------------------------------------------\n  | Logging level\n  |--------------------------------------------------------------------------\n  |\n  | The level from which you want the logger to flush logs. It is recommended\n  | to make use of the environment variable, so that you can define log levels\n  | at deployment level and not code level.\n  |\n  */\n  level: Env.get('LOG_LEVEL', 'info'),\n\n  /*\n  |--------------------------------------------------------------------------\n  | Pretty print\n  |--------------------------------------------------------------------------\n  |\n  | It is highly advised NOT to use `prettyPrint` in production, since it\n  | can have huge impact on performance.\n  |\n  */\n  prettyPrint: Env.get('NODE_ENV') === 'development',\n}\n\n/*\n|--------------------------------------------------------------------------\n| Profiler\n|--------------------------------------------------------------------------\n*/\nexport const profiler: ProfilerConfig = {\n  /*\n  |--------------------------------------------------------------------------\n  | Toggle profiler\n  |--------------------------------------------------------------------------\n  |\n  | Enable or disable profiler\n  |\n  */\n  enabled: true,\n\n  /*\n  |--------------------------------------------------------------------------\n  | Blacklist actions/row labels\n  |--------------------------------------------------------------------------\n  |\n  | Define an array of actions or row labels that you want to disable from\n  | getting profiled.\n  |\n  */\n  blacklist: [],\n\n  /*\n  |--------------------------------------------------------------------------\n  | Whitelist actions/row labels\n  |--------------------------------------------------------------------------\n  |\n  | Define an array of actions or row labels that you want to whitelist for\n  | the profiler. When whitelist is defined, then `blacklist` is ignored.\n  |\n  */\n  whitelist: [],\n}\n\n/*\n|--------------------------------------------------------------------------\n| Validator\n|--------------------------------------------------------------------------\n|\n| Configure the global configuration for the validator. Here's the reference\n| to the default config https://git.io/JT0WE\n|\n*/\nexport const validator: ValidatorConfig = {\n}\n"
  },
  {
    "path": "config/auth.ts",
    "content": "/**\n * Config source: https://git.io/JY0mp\n *\n * Feel free to let us know via PR, if you find something broken in this config\n * file.\n */\n\nimport type { AuthConfig } from '@ioc:Adonis/Addons/Auth'\n\n/*\n|--------------------------------------------------------------------------\n| Authentication Mapping\n|--------------------------------------------------------------------------\n|\n| List of available authentication mapping. You must first define them\n| inside the `contracts/auth.ts` file before mentioning them here.\n|\n*/\nconst authConfig: AuthConfig = {\n  guard: 'api',\n  guards: {\n    /*\n    |--------------------------------------------------------------------------\n    | OAT Guard\n    |--------------------------------------------------------------------------\n    |\n    | OAT (Opaque access tokens) guard uses database backed tokens to authenticate\n    | HTTP request. This guard DOES NOT rely on sessions or cookies and uses\n    | Authorization header value for authentication.\n    |\n    | Use this guard to authenticate mobile apps or web clients that cannot rely\n    | on cookies/sessions.\n    |\n    */\n    api: {\n      driver: 'oat',\n\n      /*\n      |--------------------------------------------------------------------------\n      | Tokens provider\n      |--------------------------------------------------------------------------\n      |\n      | Uses SQL database for managing tokens. Use the \"database\" driver, when\n      | tokens are the secondary mode of authentication.\n      | For example: The Github personal tokens\n      |\n      | The foreignKey column is used to make the relationship between the user\n      | and the token. You are free to use any column name here.\n      |\n      */\n      tokenProvider: {\n        type: 'api',\n        driver: 'database',\n        table: 'api_tokens',\n        foreignKey: 'user_id',\n      },\n\n      provider: {\n        /*\n        |--------------------------------------------------------------------------\n        | Driver\n        |--------------------------------------------------------------------------\n        |\n        | Name of the driver\n        |\n        */\n        driver: 'lucid',\n\n        /*\n        |--------------------------------------------------------------------------\n        | Identifier key\n        |--------------------------------------------------------------------------\n        |\n        | The identifier key is the unique key on the model. In most cases specifying\n        | the primary key is the right choice.\n        |\n        */\n        identifierKey: 'id',\n\n        /*\n        |--------------------------------------------------------------------------\n        | Uids\n        |--------------------------------------------------------------------------\n        |\n        | Uids are used to search a user against one of the mentioned columns. During\n        | login, the auth module will search the user mentioned value against one\n        | of the mentioned columns to find their user record.\n        |\n        */\n        uids: ['email'],\n\n        /*\n        |--------------------------------------------------------------------------\n        | Model\n        |--------------------------------------------------------------------------\n        |\n        | The model to use for fetching or finding users. The model is imported\n        | lazily since the config files are read way earlier in the lifecycle\n        | of booting the app and the models may not be in a usable state at\n        | that time.\n        |\n        */\n        model: () => import('App/Models/User'),\n      },\n    },\n  },\n}\n\nexport default authConfig\n"
  },
  {
    "path": "config/bodyparser.ts",
    "content": "/**\n * Config source: https://git.io/Jfefn\n *\n * Feel free to let us know via PR, if you find something broken in this config\n * file.\n */\n\nimport type { BodyParserConfig } from '@ioc:Adonis/Core/BodyParser'\n\nconst bodyParserConfig: BodyParserConfig = {\n  /*\n  |--------------------------------------------------------------------------\n  | White listed methods\n  |--------------------------------------------------------------------------\n  |\n  | HTTP methods for which body parsing must be performed. It is a good practice\n  | to avoid body parsing for `GET` requests.\n  |\n  */\n  whitelistedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'],\n\n  /*\n  |--------------------------------------------------------------------------\n  | JSON parser settings\n  |--------------------------------------------------------------------------\n  |\n  | The settings for the JSON parser. The types defines the request content\n  | types which gets processed by the JSON parser.\n  |\n  */\n  json: {\n    encoding: 'utf-8',\n    limit: '1mb',\n    strict: true,\n    types: [\n      'application/json',\n      'application/json-patch+json',\n      'application/vnd.api+json',\n      'application/csp-report',\n    ],\n  },\n\n  /*\n  |--------------------------------------------------------------------------\n  | Form parser settings\n  |--------------------------------------------------------------------------\n  |\n  | The settings for the `application/x-www-form-urlencoded` parser. The types\n  | defines the request content types which gets processed by the form parser.\n  |\n  */\n  form: {\n    encoding: 'utf-8',\n    limit: '1mb',\n    queryString: {},\n\n    /*\n    |--------------------------------------------------------------------------\n    | Convert empty strings to null\n    |--------------------------------------------------------------------------\n    |\n    | Convert empty form fields to null. HTML forms results in field string\n    | value when the field is left blank. This option normalizes all the blank\n    | field values to \"null\"\n    |\n    */\n    convertEmptyStringsToNull: true,\n\n    types: [\n      'application/x-www-form-urlencoded',\n    ],\n  },\n\n  /*\n  |--------------------------------------------------------------------------\n  | Raw body parser settings\n  |--------------------------------------------------------------------------\n  |\n  | Raw body just reads the request body stream as a plain text, which you\n  | can process by hand. This must be used when request body type is not\n  | supported by the body parser.\n  |\n  */\n  raw: {\n    encoding: 'utf-8',\n    limit: '1mb',\n    queryString: {},\n    types: [\n      'text/*',\n    ],\n  },\n\n  /*\n  |--------------------------------------------------------------------------\n  | Multipart parser settings\n  |--------------------------------------------------------------------------\n  |\n  | The settings for the `multipart/form-data` parser. The types defines the\n  | request content types which gets processed by the form parser.\n  |\n  */\n  multipart: {\n    /*\n    |--------------------------------------------------------------------------\n    | Auto process\n    |--------------------------------------------------------------------------\n    |\n    | The auto process option will process uploaded files and writes them to\n    | the `tmp` folder. You can turn it off and then manually use the stream\n    | to pipe stream to a different destination.\n    |\n    | It is recommended to keep `autoProcess=true`. Unless you are processing bigger\n    | file sizes.\n    |\n    */\n    autoProcess: true,\n\n    /*\n    |--------------------------------------------------------------------------\n    | Files to be processed manually\n    |--------------------------------------------------------------------------\n    |\n    | You can turn off `autoProcess` for certain routes by defining\n    | routes inside the following array.\n    |\n    | NOTE: Make sure the route pattern starts with a leading slash.\n    |\n    | Correct\n    | ```js\n    | /projects/:id/file\n    | ```\n    |\n    | Incorrect\n    | ```js\n    | projects/:id/file\n    | ```\n    */\n    processManually: [],\n\n    /*\n    |--------------------------------------------------------------------------\n    | Temporary file name\n    |--------------------------------------------------------------------------\n    |\n    | When auto processing is on. We will use this method to compute the temporary\n    | file name. AdonisJs will compute a unique `tmpPath` for you automatically,\n    | However, you can also define your own custom method.\n    |\n    */\n    // tmpFileName () {\n    // },\n\n    /*\n    |--------------------------------------------------------------------------\n    | Encoding\n    |--------------------------------------------------------------------------\n    |\n    | Request body encoding\n    |\n    */\n    encoding: 'utf-8',\n\n    /*\n    |--------------------------------------------------------------------------\n    | Convert empty strings to null\n    |--------------------------------------------------------------------------\n    |\n    | Convert empty form fields to null. HTML forms results in field string\n    | value when the field is left blank. This option normalizes all the blank\n    | field values to \"null\"\n    |\n    */\n    convertEmptyStringsToNull: true,\n\n    /*\n    |--------------------------------------------------------------------------\n    | Max Fields\n    |--------------------------------------------------------------------------\n    |\n    | The maximum number of fields allowed in the request body. The field includes\n    | text inputs and files both.\n    |\n    */\n    maxFields: 1000,\n\n    /*\n    |--------------------------------------------------------------------------\n    | Request body limit\n    |--------------------------------------------------------------------------\n    |\n    | The total limit to the multipart body. This includes all request files\n    | and fields data.\n    |\n    */\n    limit: '20mb',\n\n    /*\n    |--------------------------------------------------------------------------\n    | Types\n    |--------------------------------------------------------------------------\n    |\n    | The types that will be considered and parsed as multipart body.\n    |\n    */\n    types: [\n      'multipart/form-data',\n    ],\n  },\n}\n\nexport default bodyParserConfig\n"
  },
  {
    "path": "config/cors.ts",
    "content": "/**\n * Config source: https://git.io/JfefC\n *\n * Feel free to let us know via PR, if you find something broken in this config\n * file.\n */\n\nimport type { CorsConfig } from '@ioc:Adonis/Core/Cors'\n\nconst corsConfig: CorsConfig = {\n  /*\n  |--------------------------------------------------------------------------\n  | Enabled\n  |--------------------------------------------------------------------------\n  |\n  | A boolean to enable or disable CORS integration from your AdonisJs\n  | application.\n  |\n  | Setting the value to `true` will enable the CORS for all HTTP request. However,\n  | you can define a function to enable/disable it on per request basis as well.\n  |\n  */\n  enabled: false,\n\n  // You can also use a function that return true or false.\n  // enabled: (request) => request.url().startsWith('/api')\n\n  /*\n  |--------------------------------------------------------------------------\n  | Origin\n  |--------------------------------------------------------------------------\n  |\n  | Set a list of origins to be allowed for `Access-Control-Allow-Origin`.\n  | The value can be one of the following:\n  |\n  | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin\n  |\n  | Boolean (true)    - Allow current request origin.\n  | Boolean (false)   - Disallow all.\n  | String            - Comma separated list of allowed origins.\n  | Array             - An array of allowed origins.\n  | String (*)        - A wildcard (*) to allow all request origins.\n  | Function          - Receives the current origin string and should return\n  |                     one of the above values.\n  |\n  */\n  origin: true,\n\n  /*\n  |--------------------------------------------------------------------------\n  | Methods\n  |--------------------------------------------------------------------------\n  |\n  | An array of allowed HTTP methods for CORS. The `Access-Control-Request-Method`\n  | is checked against the following list.\n  |\n  | Following is the list of default methods. Feel free to add more.\n  */\n  methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'],\n\n  /*\n  |--------------------------------------------------------------------------\n  | Headers\n  |--------------------------------------------------------------------------\n  |\n  | List of headers to be allowed for `Access-Control-Allow-Headers` header.\n  | The value can be one of the following:\n  |\n  | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers\n  |\n  | Boolean(true)     - Allow all headers mentioned in `Access-Control-Request-Headers`.\n  | Boolean(false)    - Disallow all headers.\n  | String            - Comma separated list of allowed headers.\n  | Array             - An array of allowed headers.\n  | Function          - Receives the current header and should return one of the above values.\n  |\n  */\n  headers: true,\n\n  /*\n  |--------------------------------------------------------------------------\n  | Expose Headers\n  |--------------------------------------------------------------------------\n  |\n  | A list of headers to be exposed by setting `Access-Control-Expose-Headers`.\n  | header. By default following 6 simple response headers are exposed.\n  |\n  | Cache-Control\n  | Content-Language\n  | Content-Type\n  | Expires\n  | Last-Modified\n  | Pragma\n  |\n  | In order to add more headers, simply define them inside the following array.\n  |\n  | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers\n  |\n  */\n  exposeHeaders: [\n    'cache-control',\n    'content-language',\n    'content-type',\n    'expires',\n    'last-modified',\n    'pragma',\n  ],\n\n  /*\n  |--------------------------------------------------------------------------\n  | Credentials\n  |--------------------------------------------------------------------------\n  |\n  | Toggle `Access-Control-Allow-Credentials` header. If value is set to `true`,\n  | then header will be set, otherwise not.\n  |\n  | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials\n  |\n  */\n  credentials: true,\n\n  /*\n  |--------------------------------------------------------------------------\n  | MaxAge\n  |--------------------------------------------------------------------------\n  |\n  | Define `Access-Control-Max-Age` header in seconds.\n  | https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age\n  |\n  */\n  maxAge: 90,\n}\n\nexport default corsConfig\n"
  },
  {
    "path": "config/database.ts",
    "content": "/**\n * Config source: https://git.io/JesV9\n *\n * Feel free to let us know via PR, if you find something broken in this config\n * file.\n */\n\nimport Env from '@ioc:Adonis/Core/Env'\nimport type { DatabaseConfig } from '@ioc:Adonis/Lucid/Database'\n\nconst databaseConfig: DatabaseConfig = {\n  /*\n  |--------------------------------------------------------------------------\n  | Connection\n  |--------------------------------------------------------------------------\n  |\n  | The primary connection for making database queries across the application\n  | You can use any key from the `connections` object defined in this same\n  | file.\n  |\n  */\n  connection: Env.get('DB_CONNECTION'),\n\n  connections: {\n    /*\n    |--------------------------------------------------------------------------\n    | MySQL config\n    |--------------------------------------------------------------------------\n    |\n    | Configuration for MySQL database. Make sure to install the driver\n    | from npm when using this connection\n    |\n    | npm i mysql2\n    |\n    */\n    mysql: {\n      client: 'mysql2',\n      connection: {\n        host: Env.get('MYSQL_HOST'),\n        port: Env.get('MYSQL_PORT'),\n        user: Env.get('MYSQL_USER'),\n        password: Env.get('MYSQL_PASSWORD', ''),\n        database: Env.get('MYSQL_DB_NAME'),\n      },\n      migrations: {\n        naturalSort: true,\n      },\n      healthCheck: false,\n      debug: false,\n    },\n\n  }\n}\n\nexport default databaseConfig\n"
  },
  {
    "path": "config/drive.ts",
    "content": "/**\n * Config source: https://git.io/JBt3o\n *\n * Feel free to let us know via PR, if you find something broken in this config\n * file.\n */\n\nimport Env from '@ioc:Adonis/Core/Env'\nimport { driveConfig } from '@adonisjs/core/build/config'\nimport Application from '@ioc:Adonis/Core/Application'\n\n/*\n|--------------------------------------------------------------------------\n| Drive Config\n|--------------------------------------------------------------------------\n|\n| The `DriveConfig` relies on the `DisksList` interface which is\n| defined inside the `contracts` directory.\n|\n*/\nexport default driveConfig({\n  /*\n  |--------------------------------------------------------------------------\n  | Default disk\n  |--------------------------------------------------------------------------\n  |\n  | The default disk to use for managing file uploads. The value is driven by\n  | the `DRIVE_DISK` environment variable.\n  |\n  */\n  disk: Env.get('DRIVE_DISK'),\n\n  disks: {\n    /*\n    |--------------------------------------------------------------------------\n    | Local\n    |--------------------------------------------------------------------------\n    |\n    | Uses the local file system to manage files. Make sure to turn off serving\n    | files when not using this disk.\n    |\n    */\n    local: {\n      driver: 'local',\n      visibility: 'public',\n\n      /*\n      |--------------------------------------------------------------------------\n      | Storage root - Local driver only\n      |--------------------------------------------------------------------------\n      |\n      | Define an absolute path to the storage directory from where to read the\n      | files.\n      |\n      */\n      root: Application.tmpPath('uploads'),\n\n      /*\n      |--------------------------------------------------------------------------\n      | Serve files - Local driver only\n      |--------------------------------------------------------------------------\n      |\n      | When this is set to true, AdonisJS will configure a files server to serve\n      | files from the disk root. This is done to mimic the behavior of cloud\n      | storage services that has inbuilt capabilities to serve files.\n      |\n      */\n      serveFiles: true,\n\n      /*\n      |--------------------------------------------------------------------------\n      | Base path - Local driver only\n      |--------------------------------------------------------------------------\n      |\n      | Base path is always required when \"serveFiles = true\". Also make sure\n      | the `basePath` is unique across all the disks using \"local\" driver and\n      | you are not registering routes with this prefix.\n      |\n      */\n      basePath: '/uploads',\n    },\n\n    /*\n    |--------------------------------------------------------------------------\n    | S3 Driver\n    |--------------------------------------------------------------------------\n    |\n    | Uses the S3 cloud storage to manage files. Make sure to install the s3\n    | drive separately when using it.\n    |\n    |**************************************************************************\n    | npm i @adonisjs/drive-s3\n    |**************************************************************************\n    |\n    */\n    // s3: {\n    //   driver: 's3',\n    //   visibility: 'public',\n    //   key: Env.get('S3_KEY'),\n    //   secret: Env.get('S3_SECRET'),\n    //   region: Env.get('S3_REGION'),\n    //   bucket: Env.get('S3_BUCKET'),\n    //   endpoint: Env.get('S3_ENDPOINT'),\n    //\n    //  // For minio to work\n    //  // forcePathStyle: true,\n    // },\n\n    /*\n    |--------------------------------------------------------------------------\n    | GCS Driver\n    |--------------------------------------------------------------------------\n    |\n    | Uses the Google cloud storage to manage files. Make sure to install the GCS\n    | drive separately when using it.\n    |\n    |**************************************************************************\n    | npm i @adonisjs/drive-gcs\n    |**************************************************************************\n    |\n    */\n    // gcs: {\n    //   driver: 'gcs',\n    //   visibility: 'public',\n    //   keyFilename: Env.get('GCS_KEY_FILENAME'),\n    //   bucket: Env.get('GCS_BUCKET'),\n\n      /*\n      |--------------------------------------------------------------------------\n      | Uniform ACL - Google cloud storage only\n      |--------------------------------------------------------------------------\n      |\n      | When using the Uniform ACL on the bucket, the \"visibility\" option is\n      | ignored. Since, the files ACL is managed by the google bucket policies\n      | directly.\n      |\n      |**************************************************************************\n      | Learn more: https://cloud.google.com/storage/docs/uniform-bucket-level-access\n      |**************************************************************************\n      |\n      | The following option just informs drive whether your bucket is using uniform\n      | ACL or not. The actual setting needs to be toggled within the Google cloud\n      | console.\n      |\n      */\n    //   usingUniformAcl: false,\n    // },\n  },\n})\n"
  },
  {
    "path": "config/hash.ts",
    "content": "/**\n * Config source: https://git.io/JfefW\n *\n * Feel free to let us know via PR, if you find something broken in this config\n * file.\n */\n\nimport Env from '@ioc:Adonis/Core/Env'\nimport { hashConfig } from '@adonisjs/core/build/config'\n\n/*\n|--------------------------------------------------------------------------\n| Hash Config\n|--------------------------------------------------------------------------\n|\n| The `HashConfig` relies on the `HashList` interface which is\n| defined inside `contracts` directory.\n|\n*/\nexport default hashConfig({\n  /*\n  |--------------------------------------------------------------------------\n  | Default hasher\n  |--------------------------------------------------------------------------\n  |\n  | By default we make use of the argon hasher to hash values. However, feel\n  | free to change the default value\n  |\n  */\n  default: Env.get('HASH_DRIVER', 'scrypt'),\n\n  list: {\n    /*\n    |--------------------------------------------------------------------------\n    | scrypt\n    |--------------------------------------------------------------------------\n    |\n    | Scrypt mapping uses the Node.js inbuilt crypto module for creating\n    | hashes.\n    |\n    | We are using the default configuration recommended within the Node.js\n    | documentation.\n    | https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback\n    |\n    */\n    scrypt: {\n      driver: 'scrypt',\n      cost: 16384,\n      blockSize: 8,\n      parallelization: 1,\n      saltSize: 16,\n      keyLength: 64,\n      maxMemory: 32 * 1024 * 1024,\n    },\n\n    /*\n    |--------------------------------------------------------------------------\n    | Argon\n    |--------------------------------------------------------------------------\n    |\n    | Argon mapping uses the `argon2` driver to hash values.\n    |\n    | Make sure you install the underlying dependency for this driver to work.\n    | https://www.npmjs.com/package/phc-argon2.\n    |\n    | npm install phc-argon2\n    |\n    */\n    argon: {\n      driver: 'argon2',\n      variant: 'id',\n      iterations: 3,\n      memory: 4096,\n      parallelism: 1,\n      saltSize: 16,\n    },\n\n    /*\n    |--------------------------------------------------------------------------\n    | Bcrypt\n    |--------------------------------------------------------------------------\n    |\n    | Bcrypt mapping uses the `bcrypt` driver to hash values.\n    |\n    | Make sure you install the underlying dependency for this driver to work.\n    | https://www.npmjs.com/package/phc-bcrypt.\n    |\n    | npm install phc-bcrypt\n    |\n    */\n    bcrypt: {\n      driver: 'bcrypt',\n      rounds: 10,\n    },\n  },\n})\n"
  },
  {
    "path": "contracts/ally.ts",
    "content": "/**\n * Contract source: https://git.io/JOdiQ\n *\n * Feel free to let us know via PR, if you find something broken in this contract\n * file.\n */\n\ndeclare module '@ioc:Adonis/Addons/Ally' {\n\tinterface SocialProviders {\n    spotify: {\n      config: SpotifyDriverConfig\n      implementation: SpotifyDriverContract\n    }\n\t}\n}\n"
  },
  {
    "path": "contracts/auth.ts",
    "content": "/**\n * Contract source: https://git.io/JOdz5\n *\n * Feel free to let us know via PR, if you find something broken in this\n * file.\n */\n\nimport User from 'App/Models/User'\n\ndeclare module '@ioc:Adonis/Addons/Auth' {\n  /*\n  |--------------------------------------------------------------------------\n  | Providers\n  |--------------------------------------------------------------------------\n  |\n  | The providers are used to fetch users. The Auth module comes pre-bundled\n  | with two providers that are `Lucid` and `Database`. Both uses database\n  | to fetch user details.\n  |\n  | You can also create and register your own custom providers.\n  |\n  */\n  interface ProvidersList {\n    /*\n    |--------------------------------------------------------------------------\n    | User Provider\n    |--------------------------------------------------------------------------\n    |\n    | The following provider uses Lucid models as a driver for fetching user\n    | details from the database for authentication.\n    |\n    | You can create multiple providers using the same underlying driver with\n    | different Lucid models.\n    |\n    */\n    user: {\n      implementation: LucidProviderContract<typeof User>\n      config: LucidProviderConfig<typeof User>\n    }\n  }\n\n  /*\n  |--------------------------------------------------------------------------\n  | Guards\n  |--------------------------------------------------------------------------\n  |\n  | The guards are used for authenticating users using different drivers.\n  | The auth module comes with 3 different guards.\n  |\n  | - SessionGuardContract\n  | - BasicAuthGuardContract\n  | - OATGuardContract ( Opaque access token )\n  |\n  | Every guard needs a provider for looking up users from the database.\n  |\n  */\n  interface GuardsList {\n    /*\n    |--------------------------------------------------------------------------\n    | OAT Guard\n    |--------------------------------------------------------------------------\n    |\n    | OAT, stands for (Opaque access tokens) guard uses database backed tokens\n    | to authenticate requests.\n    |\n    */\n    api: {\n      implementation: OATGuardContract<'user', 'api'>\n      config: OATGuardConfig<'user'>\n      client: OATClientContract<'user'>\n    }\n  }\n}\n"
  },
  {
    "path": "contracts/drive.ts",
    "content": "/**\n * Contract source: https://git.io/JBt3I\n *\n * Feel free to let us know via PR, if you find something broken in this contract\n * file.\n */\n\nimport type { InferDisksFromConfig } from '@adonisjs/core/build/config'\nimport type driveConfig from '../config/drive'\n\ndeclare module '@ioc:Adonis/Core/Drive' {\n  interface DisksList extends InferDisksFromConfig<typeof driveConfig> {}\n}\n"
  },
  {
    "path": "contracts/env.ts",
    "content": "/**\n * Contract source: https://git.io/JTm6U\n *\n * Feel free to let us know via PR, if you find something broken in this contract\n * file.\n */\n\ndeclare module '@ioc:Adonis/Core/Env' {\n  /*\n  |--------------------------------------------------------------------------\n  | Getting types for validated environment variables\n  |--------------------------------------------------------------------------\n  |\n  | The `default` export from the \"../env.ts\" file exports types for the\n  | validated environment variables. Here we merge them with the `EnvTypes`\n  | interface so that you can enjoy intellisense when using the \"Env\"\n  | module.\n  |\n  */\n\n  type CustomTypes = typeof import('../env').default\n  interface EnvTypes extends CustomTypes {\n  }\n}\n"
  },
  {
    "path": "contracts/events.ts",
    "content": "/**\n * Contract source: https://git.io/JfefG\n *\n * Feel free to let us know via PR, if you find something broken in this contract\n * file.\n */\n\ndeclare module '@ioc:Adonis/Core/Event' {\n  /*\n  |--------------------------------------------------------------------------\n  | Define typed events\n  |--------------------------------------------------------------------------\n  |\n  | You can define types for events inside the following interface and\n  | AdonisJS will make sure that all listeners and emit calls adheres\n  | to the defined types.\n  |\n  | For example:\n  |\n  | interface EventsList {\n  |   'new:user': UserModel\n  | }\n  |\n  | Now calling `Event.emit('new:user')` will statically ensure that passed value is\n  | an instance of the the UserModel only.\n  |\n  */\n  interface EventsList {\n    //\n  }\n}\n"
  },
  {
    "path": "contracts/hash.ts",
    "content": "/**\n * Contract source: https://git.io/Jfefs\n *\n * Feel free to let us know via PR, if you find something broken in this contract\n * file.\n */\n\nimport type { InferListFromConfig } from '@adonisjs/core/build/config'\nimport type hashConfig from '../config/hash'\n\ndeclare module '@ioc:Adonis/Core/Hash' {\n  interface HashersList extends InferListFromConfig<typeof hashConfig> {}\n}\n"
  },
  {
    "path": "contracts/tests.ts",
    "content": "/**\n * Contract source: https://bit.ly/3DP1ypf\n *\n * Feel free to let us know via PR, if you find something broken in this contract\n * file.\n */\n\nimport '@japa/runner'\n\ndeclare module '@japa/runner' {\n  interface TestContext {\n    // Extend context\n  }\n\n  interface Test<TestData> {\n    // Extend test\n  }\n}\n"
  },
  {
    "path": "database/factories/index.ts",
    "content": "// import Factory from '@ioc:Adonis/Lucid/Factory'\n"
  },
  {
    "path": "database/migrations/1698432448829_users.ts",
    "content": "import BaseSchema from '@ioc:Adonis/Lucid/Schema'\n\nexport default class extends BaseSchema {\n  protected tableName = 'users'\n\n  public async up() {\n    this.schema.createTable(this.tableName, (table) => {\n      table.increments('id').primary()\n      table.string('name').notNullable()\n      table.string('email', 255).notNullable().unique()\n      // table.string('password', 180).notNullable()\n      table.string('remember_me_token').nullable()\n      table.string('access_token')\n      table.string('provider')\n\n      /**\n       * Uses timestampz for PostgreSQL and DATETIME2 for MSSQL\n       */\n      table.timestamp('created_at', { useTz: true }).notNullable()\n      table.timestamp('updated_at', { useTz: true }).notNullable()\n    })\n  }\n\n  public async down() {\n    this.schema.dropTable(this.tableName)\n  }\n}\n"
  },
  {
    "path": "database/migrations/1698432448832_api_tokens.ts",
    "content": "import BaseSchema from '@ioc:Adonis/Lucid/Schema'\n\nexport default class extends BaseSchema {\n  protected tableName = 'api_tokens'\n\n  public async up() {\n    this.schema.createTable(this.tableName, (table) => {\n      table.increments('id').primary()\n      table.integer('user_id').unsigned()\n      table.string('name').notNullable()\n      table.string('type').notNullable()\n      table.string('token', 64).notNullable().unique()\n\n      /**\n       * Uses timestampz for PostgreSQL and DATETIME2 for MSSQL\n       */\n      table.timestamp('expires_at', { useTz: true }).nullable()\n      table.timestamp('created_at', { useTz: true }).notNullable()\n    })\n  }\n\n  public async down() {\n    this.schema.dropTable(this.tableName)\n  }\n}\n"
  },
  {
    "path": "database/migrations/1698440823891_social_tokens.ts",
    "content": "import BaseSchema from '@ioc:Adonis/Lucid/Schema'\n\nexport default class extends BaseSchema {\n  protected tableName = 'social_tokens'\n\n  public async up () {\n    this.schema.createTable(this.tableName, (table) => {\n      table.increments('id')\n      table.integer('user_id').unsigned()\n      table.text('token').notNullable()\n      table.text('refresh_token').notNullable()\n      table.dateTime('expires_at').notNullable()\n      table.string('type', 50).notNullable()\n\n      /**\n       * Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL\n       */\n      table.timestamp('created_at', { useTz: true })\n      table.timestamp('updated_at', { useTz: true })\n    })\n  }\n\n  public async down () {\n    this.schema.dropTable(this.tableName)\n  }\n}\n"
  },
  {
    "path": "database/migrations/1698491899562_genders.ts",
    "content": "import BaseSchema from '@ioc:Adonis/Lucid/Schema'\n\nexport default class extends BaseSchema {\n  protected tableName = 'genders'\n\n  public async up () {\n    this.schema.createTable(this.tableName, (table) => {\n      table.increments('id')\n\n      table.string('name', 255)\n      table.tinyint('status').defaultTo(1)\n\n      /**\n       * Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL\n       */\n      table.timestamp('created_at', { useTz: true })\n      table.timestamp('updated_at', { useTz: true })\n    })\n  }\n\n  public async down () {\n    this.schema.dropTable(this.tableName)\n  }\n}\n"
  },
  {
    "path": "database/migrations/1698491899563_profiles.ts",
    "content": "import BaseSchema from '@ioc:Adonis/Lucid/Schema'\n\nexport default class extends BaseSchema {\n  protected tableName = 'profiles'\n\n  public async up () {\n    this.schema.createTable(this.tableName, (table) => {\n      table.increments('id')\n\n      table.date('date_of_birth')\n      table.text('description')\n      table.string('avatar')\n      table.integer('prefered_gender_id')\n        .unsigned()\n\n\n      table.integer('user_id')\n        .unsigned()\n\n\n\n      /**\n       * Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL\n       */\n      table.timestamp('created_at', { useTz: true })\n      table.timestamp('updated_at', { useTz: true })\n    })\n  }\n\n  public async down () {\n    this.schema.dropTable(this.tableName)\n  }\n}\n"
  },
  {
    "path": "database/migrations/1698528332152_artists.ts",
    "content": "import BaseSchema from '@ioc:Adonis/Lucid/Schema'\n\nexport default class extends BaseSchema {\n  protected tableName = 'artists'\n\n  public async up () {\n    this.schema.createTable(this.tableName, (table) => {\n      table.increments('id')\n\n      table.integer('user_id')\n        .unsigned()\n\n\n      /* user => track || null */\n      table.string('type') \n \n      table.string('name')\n      table.string('popularity')\n      table.string('followers')\n      table.string('uri')\n      table.string('spotify_artist_id')\n      table.string('artist_image')\n\n      /**\n       * Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL\n       */\n      table.timestamp('created_at', { useTz: true })\n      table.timestamp('updated_at', { useTz: true })\n    })\n  }\n\n  public async down () {\n    this.schema.dropTable(this.tableName)\n  }\n}\n"
  },
  {
    "path": "database/migrations/1698570713652_genres.ts",
    "content": "import BaseSchema from '@ioc:Adonis/Lucid/Schema'\n// \"genres\": [\n//   \"pakistani indie\",\n//   \"pakistani pop\",\n//   \"urdu hip hop\"\n// ],\nexport default class extends BaseSchema {\n  protected tableName = 'genres'\n\n  public async up () {\n    this.schema.createTable(this.tableName, (table) => {\n      table.increments('id')\n      table.integer('artist_id')\n        .unsigned()\n\n\n      table.string('name')\n\n      /**\n       * Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL\n       */\n      table.timestamp('created_at', { useTz: true })\n      table.timestamp('updated_at', { useTz: true })\n    })\n  }\n\n  public async down () {\n    this.schema.dropTable(this.tableName)\n  }\n}\n"
  },
  {
    "path": "database/migrations/1698571837102_tracks.ts",
    "content": "import BaseSchema from \"@ioc:Adonis/Lucid/Schema\";\n\nexport default class extends BaseSchema {\n  protected tableName = \"tracks\";\n\n  public async up() {\n    this.schema.createTable(this.tableName, (table) => {\n      table.increments(\"id\");\n\n      table.integer(\"user_id\").unsigned();\n\n      table.string(\"name\");\n      table.string(\"uri\");\n      table.string(\"popularity\");\n      table.string(\"tract_image\");\n      table.string(\"track_id\");\n      table.string(\"album\");\n      table.tinyint('favorite').defaultTo(0)\n      \n      /**\n       * Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL\n       */\n      table.timestamp(\"created_at\", { useTz: true });\n      table.timestamp(\"updated_at\", { useTz: true });\n    });\n  }\n\n  public async down() {\n    this.schema.dropTable(this.tableName);\n  }\n}\n"
  },
  {
    "path": "database/migrations/1698942906705_matches.ts",
    "content": "import BaseSchema from '@ioc:Adonis/Lucid/Schema'\n\nexport default class extends BaseSchema {\n  protected tableName = 'matches'\n\n  public async up () {\n    this.schema.createTable(this.tableName, (table) => {\n      table.increments('id')\n\n      table.integer('matcher_user_id')\n      table.integer('matched_user_id')\n      table.date('match_date')\n\n      table.tinyint(\"mutual_match\").defaultTo(0)\n\n      /**\n       * Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL\n       */\n      table.timestamp('created_at', { useTz: true })\n      table.timestamp('updated_at', { useTz: true })\n    })\n  }\n\n  public async down () {\n    this.schema.dropTable(this.tableName)\n  }\n}\n"
  },
  {
    "path": "database/seeders/Gender.ts",
    "content": "import BaseSeeder from '@ioc:Adonis/Lucid/Seeder'\nimport Gender from 'App/Models/Gender'\n\nexport default class extends BaseSeeder {\n  public async run () {\n    await Gender.createMany([\n      {\n        name: 'Male',\n      },\n      {\n        name: 'Female',\n      },\n      {\n        name: 'Other',\n      },\n    ])\n  }\n}\n"
  },
  {
    "path": "env.ts",
    "content": "/*\n|--------------------------------------------------------------------------\n| Validating Environment Variables\n|--------------------------------------------------------------------------\n|\n| In this file we define the rules for validating environment variables.\n| By performing validation we ensure that your application is running in\n| a stable environment with correct configuration values.\n|\n| This file is read automatically by the framework during the boot lifecycle\n| and hence do not rename or move this file to a different location.\n|\n*/\n\nimport Env from '@ioc:Adonis/Core/Env'\n\nexport default Env.rules({\n\tHOST: Env.schema.string({ format: 'host' }),\n\tPORT: Env.schema.number(),\n\tAPP_KEY: Env.schema.string(),\n\tAPP_NAME: Env.schema.string(),\n  DRIVE_DISK: Env.schema.enum(['local'] as const),\n\tNODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const),\n\tDB_CONNECTION: Env.schema.string(),\n\tMYSQL_HOST: Env.schema.string({ format: 'host' }),\n\tMYSQL_PORT: Env.schema.number(),\n\tMYSQL_USER: Env.schema.string(),\n\tMYSQL_PASSWORD: Env.schema.string.optional(),\n\tMYSQL_DB_NAME: Env.schema.string(),\n\tSPOTIFY_CLIENT_ID: Env.schema.string(),\n\tSPOTIFY_CLIENT_SECRET: Env.schema.string(),\n  SPOTIFY_CALLBACK_URL: Env.schema.string.optional(),\n\tSESSION_DRIVER: Env.schema.string()\n})\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"musical-matching\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"node ace serve --watch\",\n    \"build\": \"node ace build --production\",\n    \"start\": \"node server.js\",\n    \"test\": \"node ace test\"\n  },\n  \"devDependencies\": {\n    \"@adonisjs/assembler\": \"^5.9.6\",\n    \"@japa/preset-adonis\": \"^1.2.0\",\n    \"@japa/runner\": \"^2.5.1\",\n    \"@types/proxy-addr\": \"^2.0.2\",\n    \"@types/source-map-support\": \"^0.5.9\",\n    \"adonis-preset-ts\": \"^2.1.0\",\n    \"pino-pretty\": \"^10.2.3\",\n    \"typescript\": \"~4.6\",\n    \"youch\": \"^3.3.2\",\n    \"youch-terminal\": \"^2.2.3\"\n  },\n  \"dependencies\": {\n    \"@adonisjs/ally\": \"^4.1.5\",\n    \"@adonisjs/auth\": \"^8.2.3\",\n    \"@adonisjs/core\": \"^5.9.0\",\n    \"@adonisjs/lucid\": \"^18.4.2\",\n    \"@adonisjs/repl\": \"^3.1.11\",\n    \"axios\": \"^1.6.0\",\n    \"luxon\": \"^3.4.3\",\n    \"mysql2\": \"^3.6.2\",\n    \"proxy-addr\": \"^2.0.7\",\n    \"reflect-metadata\": \"^0.1.13\",\n    \"source-map-support\": \"^0.5.21\"\n  }\n}\n"
  },
  {
    "path": "providers/AppProvider.ts",
    "content": "import type { ApplicationContract } from '@ioc:Adonis/Core/Application'\n\nexport default class AppProvider {\n  constructor (protected app: ApplicationContract) {\n  }\n\n  public register () {\n    // Register your own bindings\n  }\n\n  public async boot () {\n    // IoC container is ready\n  }\n\n  public async ready () {\n    // App is ready\n  }\n\n  public async shutdown () {\n    // Cleanup, since app is going down\n  }\n}\n"
  },
  {
    "path": "server.ts",
    "content": "/*\n|--------------------------------------------------------------------------\n| AdonisJs Server\n|--------------------------------------------------------------------------\n|\n| The contents in this file is meant to bootstrap the AdonisJs application\n| and start the HTTP server to accept incoming connections. You must avoid\n| making this file dirty and instead make use of `lifecycle hooks` provided\n| by AdonisJs service providers for custom code.\n|\n*/\n\nimport 'reflect-metadata'\nimport sourceMapSupport from 'source-map-support'\nimport { Ignitor } from '@adonisjs/core/build/standalone'\n\nsourceMapSupport.install({ handleUncaughtExceptions: false })\n\nnew Ignitor(__dirname)\n  .httpServer()\n  .start()\n"
  },
  {
    "path": "start/kernel.ts",
    "content": "/*\n|--------------------------------------------------------------------------\n| Application middleware\n|--------------------------------------------------------------------------\n|\n| This file is used to define middleware for HTTP requests. You can register\n| middleware as a `closure` or an IoC container binding. The bindings are\n| preferred, since they keep this file clean.\n|\n*/\n\nimport Server from '@ioc:Adonis/Core/Server'\n\n/*\n|--------------------------------------------------------------------------\n| Global middleware\n|--------------------------------------------------------------------------\n|\n| An array of global middleware, that will be executed in the order they\n| are defined for every HTTP requests.\n|\n*/\nServer.middleware.register([\n  () => import('@ioc:Adonis/Core/BodyParser'),\n])\n\n/*\n|--------------------------------------------------------------------------\n| Named middleware\n|--------------------------------------------------------------------------\n|\n| Named middleware are defined as key-value pair. The value is the namespace\n| or middleware function and key is the alias. Later you can use these\n| alias on individual routes. For example:\n|\n| { auth: () => import('App/Middleware/Auth') }\n|\n| and then use it as follows\n|\n| Route.get('dashboard', 'UserController.dashboard').middleware('auth')\n|\n*/\nServer.middleware.registerNamed({\n  auth: () => import('App/Middleware/Auth')\n})\n"
  },
  {
    "path": "start/routes.ts",
    "content": "/*\n|--------------------------------------------------------------------------\n| Routes\n|--------------------------------------------------------------------------\n|\n| This file is dedicated for defining HTTP routes. A single file is enough\n| for majority of projects, however you can define routes in different\n| files and just make sure to import them inside this file. For example\n|\n| Define routes in following two files\n| ├── start/routes/cart.ts\n| ├── start/routes/customer.ts\n|\n| and then import them inside `start/routes.ts` as follows\n|\n| import './routes/cart'\n| import './routes/customer'\n|\n*/\n\nimport Route from \"@ioc:Adonis/Core/Route\";\n\nRoute.get('health', ({ response }) => response.noContent())\n\nRoute.group(() => {\n  Route.get(\"/\", async ({ response }) => {\n    return response.json(\"Working.\");\n  });\n\n  // SIGN IN ROUTES\n  Route.get(\"/signin\", \"UsersController.redirect\");\n\n  //OAuth CALLBACK\n  Route.get(\"/signin-callback\", \"UsersController.handleCallback\");\n\n  Route.post(\"/logout\", \"UsersController.logout\");\n\n  Route.group(() => {\n    Route.get(\"/\", \"ProfilesController.get\");\n    Route.post(\"/\", \"ProfilesController.store\");\n  })\n  .prefix(\"profile\")\n  .middleware(\"auth:api\");\n\n  Route.group(() => {\n    Route.get(\"/\", \"GendersController.index\");\n  })\n  .prefix(\"genders\")\n  .middleware(\"auth:api\");\n\n  Route.group(() => {\n    Route.get(\"/artists\", \"SpotifyController.artists\");\n    Route.get(\"/tracks\", \"SpotifyController.tracks\");\n    Route.get(\"/track-by-name\", \"SpotifyController.trackByName\");\n  })\n  .prefix(\"spotify\")\n  .middleware(\"auth:api\");\n\n  Route.group(() => {\n    /* potential matches */\n    Route.get(\"/\", \"MatchesController.get\");\n\n    /* mark match */\n    Route.post(\"/mutual\", \"MatchesController.mutualMatch\");\n\n    /* get mutual match history */\n    Route.get(\"/history\", \"MatchesController.history\");\n  })\n  .prefix(\"matches\")\n  .middleware(\"auth:api\");\n\n}).prefix(\"api\");\n"
  },
  {
    "path": "test.ts",
    "content": "/*\n|--------------------------------------------------------------------------\n| Tests\n|--------------------------------------------------------------------------\n|\n| The contents in this file boots the AdonisJS application and configures\n| the Japa tests runner.\n|\n| For the most part you will never edit this file. The configuration\n| for the tests can be controlled via \".adonisrc.json\" and\n| \"tests/bootstrap.ts\" files.\n|\n*/\n\nprocess.env.NODE_ENV = 'test'\n\nimport 'reflect-metadata'\nimport sourceMapSupport from 'source-map-support'\nimport { Ignitor } from '@adonisjs/core/build/standalone'\nimport { configure, processCliArgs, run, RunnerHooksHandler } from '@japa/runner'\n\nsourceMapSupport.install({ handleUncaughtExceptions: false })\n\nconst kernel = new Ignitor(__dirname).kernel('test')\n\nkernel\n  .boot()\n  .then(() => import('./tests/bootstrap'))\n  .then(({ runnerHooks, ...config }) => {\n    const app: RunnerHooksHandler[] = [() => kernel.start()]\n\n    configure({\n      ...kernel.application.rcFile.tests,\n      ...processCliArgs(process.argv.slice(2)),\n      ...config,\n      ...{\n        importer: (filePath) => import(filePath),\n        setup: app.concat(runnerHooks.setup),\n        teardown: runnerHooks.teardown,\n      },\n      cwd: kernel.application.appRoot\n    })\n\n    run()\n  })\n"
  },
  {
    "path": "tests/bootstrap.ts",
    "content": "/**\n * File source: https://bit.ly/3ukaHTz\n *\n * Feel free to let us know via PR, if you find something broken in this contract\n * file.\n */\n\nimport type { Config } from '@japa/runner'\nimport TestUtils from '@ioc:Adonis/Core/TestUtils'\nimport { assert, runFailedTests, specReporter, apiClient } from '@japa/preset-adonis'\n\n/*\n|--------------------------------------------------------------------------\n| Japa Plugins\n|--------------------------------------------------------------------------\n|\n| Japa plugins allows you to add additional features to Japa. By default\n| we register the assertion plugin.\n|\n| Feel free to remove existing plugins or add more.\n|\n*/\nexport const plugins: Required<Config>['plugins'] = [assert(), runFailedTests(), apiClient()]\n\n/*\n|--------------------------------------------------------------------------\n| Japa Reporters\n|--------------------------------------------------------------------------\n|\n| Japa reporters displays/saves the progress of tests as they are executed.\n| By default, we register the spec reporter to show a detailed report\n| of tests on the terminal.\n|\n*/\nexport const reporters: Required<Config>['reporters'] = [specReporter()]\n\n/*\n|--------------------------------------------------------------------------\n| Runner hooks\n|--------------------------------------------------------------------------\n|\n| Runner hooks are executed after booting the AdonisJS app and\n| before the test files are imported.\n|\n| You can perform actions like starting the HTTP server or running migrations\n| within the runner hooks\n|\n*/\nexport const runnerHooks: Pick<Required<Config>, 'setup' | 'teardown'> = {\n  setup: [() => TestUtils.ace().loadCommands()],\n  teardown: [],\n}\n\n/*\n|--------------------------------------------------------------------------\n| Configure individual suites\n|--------------------------------------------------------------------------\n|\n| The configureSuite method gets called for every test suite registered\n| within \".adonisrc.json\" file.\n|\n| You can use this method to configure suites. For example: Only start\n| the HTTP server when it is a functional suite.\n*/\nexport const configureSuite: Required<Config>['configureSuite'] = (suite) => {\n  if (suite.name === 'functional') {\n    suite.setup(() => TestUtils.httpServer().start())\n  }\n}\n"
  },
  {
    "path": "tests/functional/hello_world.spec.ts",
    "content": "import { test } from '@japa/runner'\n\ntest('display welcome page', async ({ client }) => {\n  const response = await client.get('/')\n\n  response.assertStatus(200)\n  response.assertBodyContains({ hello: 'world' })\n})\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"extends\": \"adonis-preset-ts/tsconfig.json\",\n  \"include\": [\n    \"**/*\"\n  ],\n  \"exclude\": [\n    \"node_modules\",\n    \"build\"\n  ],\n  \"compilerOptions\": {\n    \"outDir\": \"build\",\n    \"rootDir\": \"./\",\n    \"sourceMap\": true,\n    \"paths\": {\n      \"App/*\": [\n        \"./app/*\"\n      ],\n      \"Config/*\": [\n        \"./config/*\"\n      ],\n      \"Contracts/*\": [\n        \"./contracts/*\"\n      ],\n      \"Database/*\": [\n        \"./database/*\"\n      ]\n    },\n    \"types\": [\n      \"@adonisjs/core\",\n      \"@adonisjs/repl\",\n      \"@japa/preset-adonis/build/adonis-typings\",\n      \"@adonisjs/lucid\",\n      \"@adonisjs/ally\",\n      \"@adonisjs/auth\"\n    ]\n  }\n}"
  }
]