├── .gitignore ├── frontend ├── src │ ├── components │ │ ├── Footer.js │ │ ├── Navbar.js │ │ └── MovieCard.js │ ├── setupTests.js │ ├── App.test.js │ ├── index.js │ ├── reportWebVitals.js │ ├── search.svg │ ├── App.js │ └── App.css ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── .gitignore └── package.json ├── backend └── starflix │ ├── test │ ├── test_helper.exs │ ├── starflix_web │ │ └── views │ │ │ └── error_view_test.exs │ └── support │ │ ├── conn_case.ex │ │ └── data_case.ex │ ├── priv │ ├── repo │ │ ├── migrations │ │ │ └── .formatter.exs │ │ └── seeds.exs │ └── gettext │ │ ├── errors.pot │ │ └── en │ │ └── LC_MESSAGES │ │ └── errors.po │ ├── lib │ ├── starflix │ │ ├── mailer.ex │ │ ├── repo.ex │ │ └── application.ex │ ├── starflix.ex │ ├── starflix_web │ │ ├── router.ex │ │ ├── views │ │ │ ├── error_view.ex │ │ │ └── error_helpers.ex │ │ ├── gettext.ex │ │ ├── endpoint.ex │ │ └── telemetry.ex │ └── starflix_web.ex │ ├── .formatter.exs │ ├── .gitignore │ ├── README.md │ ├── config │ ├── test.exs │ ├── config.exs │ ├── prod.exs │ ├── dev.exs │ └── runtime.exs │ ├── mix.exs │ └── mix.lock └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /frontend/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | // Footer here -------------------------------------------------------------------------------- /frontend/src/components/Navbar.js: -------------------------------------------------------------------------------- 1 | //navbar here -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZainAsif767/Starflix/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZainAsif767/Starflix/HEAD/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZainAsif767/Starflix/HEAD/frontend/public/logo512.png -------------------------------------------------------------------------------- /backend/starflix/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | Ecto.Adapters.SQL.Sandbox.mode(Starflix.Repo, :manual) 3 | -------------------------------------------------------------------------------- /backend/starflix/priv/repo/migrations/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:ecto_sql], 3 | inputs: ["*.exs"] 4 | ] 5 | -------------------------------------------------------------------------------- /backend/starflix/lib/starflix/mailer.ex: -------------------------------------------------------------------------------- 1 | defmodule Starflix.Mailer do 2 | use Swoosh.Mailer, otp_app: :starflix 3 | end 4 | -------------------------------------------------------------------------------- /backend/starflix/lib/starflix/repo.ex: -------------------------------------------------------------------------------- 1 | defmodule Starflix.Repo do 2 | use Ecto.Repo, 3 | otp_app: :starflix, 4 | adapter: Ecto.Adapters.Postgres 5 | end 6 | -------------------------------------------------------------------------------- /backend/starflix/.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:ecto, :phoenix], 3 | inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs}"], 4 | subdirectories: ["priv/*/migrations"] 5 | ] 6 | -------------------------------------------------------------------------------- /frontend/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // // allows you to do things like: 3 | // // expect(element).toHaveTextContent(/react/i) 4 | // // learn more: https://github.com/testing-library/jest-dom 5 | // import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | // import { render, screen } from '@testing-library/react'; 2 | // import App from './App'; 3 | 4 | // test('renders learn react link', () => { 5 | // render(); 6 | // const linkElement = screen.getByText(/learn react/i); 7 | // expect(linkElement).toBeInTheDocument(); 8 | // }); 9 | -------------------------------------------------------------------------------- /backend/starflix/lib/starflix.ex: -------------------------------------------------------------------------------- 1 | defmodule Starflix do 2 | @moduledoc """ 3 | Starflix keeps the contexts that define your domain 4 | and business logic. 5 | 6 | Contexts are also responsible for managing your data, regardless 7 | if it comes from the database, an external API or others. 8 | """ 9 | end 10 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './App'; 4 | import reportWebVitals from './reportWebVitals'; 5 | 6 | const root = ReactDOM.createRoot(document.getElementById('root')); 7 | root.render( 8 | 9 | 10 | 11 | ); 12 | 13 | reportWebVitals(); 14 | -------------------------------------------------------------------------------- /backend/starflix/priv/repo/seeds.exs: -------------------------------------------------------------------------------- 1 | # Script for populating the database. You can run it as: 2 | # 3 | # mix run priv/repo/seeds.exs 4 | # 5 | # Inside the script, you can read and write to any of your 6 | # repositories directly: 7 | # 8 | # Starflix.Repo.insert!(%Starflix.SomeSchema{}) 9 | # 10 | # We recommend using the bang functions (`insert!`, `update!` 11 | # and so on) as they will fail if something goes wrong. 12 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /frontend/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /frontend/src/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /backend/starflix/test/starflix_web/views/error_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule StarflixWeb.ErrorViewTest do 2 | use StarflixWeb.ConnCase, async: true 3 | 4 | # Bring render/3 and render_to_string/3 for testing custom views 5 | import Phoenix.View 6 | 7 | test "renders 404.json" do 8 | assert render(StarflixWeb.ErrorView, "404.json", []) == %{errors: %{detail: "Not Found"}} 9 | end 10 | 11 | test "renders 500.json" do 12 | assert render(StarflixWeb.ErrorView, "500.json", []) == 13 | %{errors: %{detail: "Internal Server Error"}} 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Starflix is a basic movie landing page, having search functionality, a cool card component, and API from movieDB. 2 | 3 | To run this project: 4 | ``` 5 | git clone 6 | cd frontend 7 | npm install 8 | npm start 9 | ``` 10 | The project is not completely developed. However, the front end is almost done and the back end needs working... 11 | 12 | ## A Video overview of this Project. 13 | 14 | https://github.com/ZainAsif767/Starflix/assets/98703238/3b6fe59c-2802-4051-92d0-e92a3c17ceff 15 | 16 | https://github.com/ZainAsif767/Starflix/assets/98703238/20a1564f-4679-45c6-aeb8-a92fea12fd8f 17 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /backend/starflix/lib/starflix_web/router.ex: -------------------------------------------------------------------------------- 1 | defmodule StarflixWeb.Router do 2 | use StarflixWeb, :router 3 | 4 | pipeline :api do 5 | plug :accepts, ["json"] 6 | end 7 | 8 | scope "/api", StarflixWeb do 9 | pipe_through :api 10 | end 11 | 12 | # Enables the Swoosh mailbox preview in development. 13 | # 14 | # Note that preview only shows emails that were sent by the same 15 | # node running the Phoenix server. 16 | if Mix.env() == :dev do 17 | scope "/dev" do 18 | pipe_through [:fetch_session, :protect_from_forgery] 19 | 20 | forward "/mailbox", Plug.Swoosh.MailboxPreview 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /backend/starflix/lib/starflix_web/views/error_view.ex: -------------------------------------------------------------------------------- 1 | defmodule StarflixWeb.ErrorView do 2 | use StarflixWeb, :view 3 | 4 | # If you want to customize a particular status code 5 | # for a certain format, you may uncomment below. 6 | # def render("500.json", _assigns) do 7 | # %{errors: %{detail: "Internal Server Error"}} 8 | # end 9 | 10 | # By default, Phoenix returns the status message from 11 | # the template name. For example, "404.json" becomes 12 | # "Not Found". 13 | def template_not_found(template, _assigns) do 14 | %{errors: %{detail: Phoenix.Controller.status_message_from_template(template)}} 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /frontend/src/components/MovieCard.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function MovieCard(props) { 4 | return ( 5 |
6 |
7 |

{props.movie.Year}

8 |
9 |
10 | {props.movie.Title} 11 |
12 |
13 | {props.movie.Type} 14 |

{props.movie.Title}

15 | 16 |
17 |
18 | ) 19 | } -------------------------------------------------------------------------------- /backend/starflix/.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | starflix-*.tar 24 | 25 | -------------------------------------------------------------------------------- /backend/starflix/README.md: -------------------------------------------------------------------------------- 1 | # Starflix 2 | 3 | To start your Phoenix server: 4 | 5 | * Install dependencies with `mix deps.get` 6 | * Create and migrate your database with `mix ecto.setup` 7 | * Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server` 8 | 9 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser. 10 | 11 | Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html). 12 | 13 | ## Learn more 14 | 15 | * Official website: https://www.phoenixframework.org/ 16 | * Guides: https://hexdocs.pm/phoenix/overview.html 17 | * Docs: https://hexdocs.pm/phoenix 18 | * Forum: https://elixirforum.com/c/phoenix-forum 19 | * Source: https://github.com/phoenixframework/phoenix 20 | -------------------------------------------------------------------------------- /backend/starflix/lib/starflix_web/gettext.ex: -------------------------------------------------------------------------------- 1 | defmodule StarflixWeb.Gettext do 2 | @moduledoc """ 3 | A module providing Internationalization with a gettext-based API. 4 | 5 | By using [Gettext](https://hexdocs.pm/gettext), 6 | your module gains a set of macros for translations, for example: 7 | 8 | import StarflixWeb.Gettext 9 | 10 | # Simple translation 11 | gettext("Here is the string to translate") 12 | 13 | # Plural translation 14 | ngettext("Here is the string to translate", 15 | "Here are the strings to translate", 16 | 3) 17 | 18 | # Domain-based translation 19 | dgettext("errors", "Here is the error message to translate") 20 | 21 | See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage. 22 | """ 23 | use Gettext, otp_app: :starflix 24 | end 25 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Starflix Global 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zainflix", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "react": "^18.2.0", 10 | "react-dom": "^18.2.0", 11 | "react-scripts": "5.0.1", 12 | "web-vitals": "^2.1.4" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": [ 22 | "react-app", 23 | "react-app/jest" 24 | ] 25 | }, 26 | "browserslist": { 27 | "production": [ 28 | ">0.2%", 29 | "not dead", 30 | "not op_mini all" 31 | ], 32 | "development": [ 33 | "last 1 chrome version", 34 | "last 1 firefox version", 35 | "last 1 safari version" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /backend/starflix/config/test.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Configure your database 4 | # 5 | # The MIX_TEST_PARTITION environment variable can be used 6 | # to provide built-in test partitioning in CI environment. 7 | # Run `mix help test` for more information. 8 | config :starflix, Starflix.Repo, 9 | username: "postgres", 10 | password: "postgres", 11 | hostname: "localhost", 12 | database: "starflix_test#{System.get_env("MIX_TEST_PARTITION")}", 13 | pool: Ecto.Adapters.SQL.Sandbox, 14 | pool_size: 10 15 | 16 | # We don't run a server during test. If one is required, 17 | # you can enable the server option below. 18 | config :starflix, StarflixWeb.Endpoint, 19 | http: [ip: {127, 0, 0, 1}, port: 4002], 20 | secret_key_base: "s4i9GbwI7DU3fxFWXiXT6TwcDvYdLQSqQCe/JwKQoFhrAxmxrw/2mZK7Qc1gc9Mf", 21 | server: false 22 | 23 | # In test we don't send emails. 24 | config :starflix, Starflix.Mailer, adapter: Swoosh.Adapters.Test 25 | 26 | # Print only warnings and errors during test 27 | config :logger, level: :warn 28 | 29 | # Initialize plugs at runtime for faster test compilation 30 | config :phoenix, :plug_init_mode, :runtime 31 | -------------------------------------------------------------------------------- /backend/starflix/lib/starflix/application.ex: -------------------------------------------------------------------------------- 1 | defmodule Starflix.Application do 2 | # See https://hexdocs.pm/elixir/Application.html 3 | # for more information on OTP Applications 4 | @moduledoc false 5 | 6 | use Application 7 | 8 | @impl true 9 | def start(_type, _args) do 10 | children = [ 11 | # Start the Ecto repository 12 | Starflix.Repo, 13 | # Start the Telemetry supervisor 14 | StarflixWeb.Telemetry, 15 | # Start the PubSub system 16 | {Phoenix.PubSub, name: Starflix.PubSub}, 17 | # Start the Endpoint (http/https) 18 | StarflixWeb.Endpoint 19 | # Start a worker by calling: Starflix.Worker.start_link(arg) 20 | # {Starflix.Worker, arg} 21 | ] 22 | 23 | # See https://hexdocs.pm/elixir/Supervisor.html 24 | # for other strategies and supported options 25 | opts = [strategy: :one_for_one, name: Starflix.Supervisor] 26 | Supervisor.start_link(children, opts) 27 | end 28 | 29 | # Tell Phoenix to update the endpoint configuration 30 | # whenever the application is updated. 31 | @impl true 32 | def config_change(changed, _new, removed) do 33 | StarflixWeb.Endpoint.config_change(changed, removed) 34 | :ok 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /backend/starflix/test/support/conn_case.ex: -------------------------------------------------------------------------------- 1 | defmodule StarflixWeb.ConnCase do 2 | @moduledoc """ 3 | This module defines the test case to be used by 4 | tests that require setting up a connection. 5 | 6 | Such tests rely on `Phoenix.ConnTest` and also 7 | import other functionality to make it easier 8 | to build common data structures and query the data layer. 9 | 10 | Finally, if the test case interacts with the database, 11 | we enable the SQL sandbox, so changes done to the database 12 | are reverted at the end of every test. If you are using 13 | PostgreSQL, you can even run database tests asynchronously 14 | by setting `use StarflixWeb.ConnCase, async: true`, although 15 | this option is not recommended for other databases. 16 | """ 17 | 18 | use ExUnit.CaseTemplate 19 | 20 | using do 21 | quote do 22 | # Import conveniences for testing with connections 23 | import Plug.Conn 24 | import Phoenix.ConnTest 25 | import StarflixWeb.ConnCase 26 | 27 | alias StarflixWeb.Router.Helpers, as: Routes 28 | 29 | # The default endpoint for testing 30 | @endpoint StarflixWeb.Endpoint 31 | end 32 | end 33 | 34 | setup tags do 35 | Starflix.DataCase.setup_sandbox(tags) 36 | {:ok, conn: Phoenix.ConnTest.build_conn()} 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /backend/starflix/lib/starflix_web/views/error_helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule StarflixWeb.ErrorHelpers do 2 | @moduledoc """ 3 | Conveniences for translating and building error messages. 4 | """ 5 | 6 | @doc """ 7 | Translates an error message using gettext. 8 | """ 9 | def translate_error({msg, opts}) do 10 | # When using gettext, we typically pass the strings we want 11 | # to translate as a static argument: 12 | # 13 | # # Translate "is invalid" in the "errors" domain 14 | # dgettext("errors", "is invalid") 15 | # 16 | # # Translate the number of files with plural rules 17 | # dngettext("errors", "1 file", "%{count} files", count) 18 | # 19 | # Because the error messages we show in our forms and APIs 20 | # are defined inside Ecto, we need to translate them dynamically. 21 | # This requires us to call the Gettext module passing our gettext 22 | # backend as first argument. 23 | # 24 | # Note we use the "errors" domain, which means translations 25 | # should be written to the errors.po file. The :count option is 26 | # set by Ecto and indicates we should also apply plural rules. 27 | if count = opts[:count] do 28 | Gettext.dngettext(StarflixWeb.Gettext, "errors", msg, msg, count, opts) 29 | else 30 | Gettext.dgettext(StarflixWeb.Gettext, "errors", msg, opts) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /backend/starflix/lib/starflix_web/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule StarflixWeb.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :starflix 3 | 4 | # The session will be stored in the cookie and signed, 5 | # this means its contents can be read but not tampered with. 6 | # Set :encryption_salt if you would also like to encrypt it. 7 | @session_options [ 8 | store: :cookie, 9 | key: "_starflix_key", 10 | signing_salt: "eSg0lq9k" 11 | ] 12 | 13 | # socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]] 14 | 15 | # Serve at "/" the static files from "priv/static" directory. 16 | # 17 | # You should set gzip to true if you are running phx.digest 18 | # when deploying your static files in production. 19 | plug Plug.Static, 20 | at: "/", 21 | from: :starflix, 22 | gzip: false, 23 | only: ~w(assets fonts images favicon.ico robots.txt) 24 | 25 | # Code reloading can be explicitly enabled under the 26 | # :code_reloader configuration of your endpoint. 27 | if code_reloading? do 28 | plug Phoenix.CodeReloader 29 | plug Phoenix.Ecto.CheckRepoStatus, otp_app: :starflix 30 | end 31 | 32 | plug Plug.RequestId 33 | plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint] 34 | 35 | plug Plug.Parsers, 36 | parsers: [:urlencoded, :multipart, :json], 37 | pass: ["*/*"], 38 | json_decoder: Phoenix.json_library() 39 | 40 | plug Plug.MethodOverride 41 | plug Plug.Head 42 | plug Plug.Session, @session_options 43 | plug StarflixWeb.Router 44 | end 45 | -------------------------------------------------------------------------------- /backend/starflix/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Config module. 3 | # 4 | # This configuration file is loaded before any dependency and 5 | # is restricted to this project. 6 | 7 | # General application configuration 8 | import Config 9 | 10 | config :starflix, 11 | ecto_repos: [Starflix.Repo] 12 | 13 | # Configures the endpoint 14 | config :starflix, StarflixWeb.Endpoint, 15 | url: [host: "localhost"], 16 | render_errors: [view: StarflixWeb.ErrorView, accepts: ~w(json), layout: false], 17 | pubsub_server: Starflix.PubSub, 18 | live_view: [signing_salt: "av/hzTtd"] 19 | 20 | # Configures the mailer 21 | # 22 | # By default it uses the "Local" adapter which stores the emails 23 | # locally. You can see the emails in your browser, at "/dev/mailbox". 24 | # 25 | # For production it's recommended to configure a different adapter 26 | # at the `config/runtime.exs`. 27 | config :starflix, Starflix.Mailer, adapter: Swoosh.Adapters.Local 28 | 29 | # Swoosh API client is needed for adapters other than SMTP. 30 | config :swoosh, :api_client, false 31 | 32 | # Configures Elixir's Logger 33 | config :logger, :console, 34 | format: "$time $metadata[$level] $message\n", 35 | metadata: [:request_id] 36 | 37 | # Use Jason for JSON parsing in Phoenix 38 | config :phoenix, :json_library, Jason 39 | 40 | # Import environment specific config. This must remain at the bottom 41 | # of this file so it overrides the configuration defined above. 42 | import_config "#{config_env()}.exs" 43 | -------------------------------------------------------------------------------- /backend/starflix/test/support/data_case.ex: -------------------------------------------------------------------------------- 1 | defmodule Starflix.DataCase do 2 | @moduledoc """ 3 | This module defines the setup for tests requiring 4 | access to the application's data layer. 5 | 6 | You may define functions here to be used as helpers in 7 | your tests. 8 | 9 | Finally, if the test case interacts with the database, 10 | we enable the SQL sandbox, so changes done to the database 11 | are reverted at the end of every test. If you are using 12 | PostgreSQL, you can even run database tests asynchronously 13 | by setting `use Starflix.DataCase, async: true`, although 14 | this option is not recommended for other databases. 15 | """ 16 | 17 | use ExUnit.CaseTemplate 18 | 19 | using do 20 | quote do 21 | alias Starflix.Repo 22 | 23 | import Ecto 24 | import Ecto.Changeset 25 | import Ecto.Query 26 | import Starflix.DataCase 27 | end 28 | end 29 | 30 | setup tags do 31 | Starflix.DataCase.setup_sandbox(tags) 32 | :ok 33 | end 34 | 35 | @doc """ 36 | Sets up the sandbox based on the test tags. 37 | """ 38 | def setup_sandbox(tags) do 39 | pid = Ecto.Adapters.SQL.Sandbox.start_owner!(Starflix.Repo, shared: not tags[:async]) 40 | on_exit(fn -> Ecto.Adapters.SQL.Sandbox.stop_owner(pid) end) 41 | end 42 | 43 | @doc """ 44 | A helper that transforms changeset errors into a map of messages. 45 | 46 | assert {:error, changeset} = Accounts.create_user(%{password: "short"}) 47 | assert "password is too short" in errors_on(changeset).password 48 | assert %{password: ["password is too short"]} = errors_on(changeset) 49 | 50 | """ 51 | def errors_on(changeset) do 52 | Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> 53 | Regex.replace(~r"%{(\w+)}", message, fn _, key -> 54 | opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string() 55 | end) 56 | end) 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /backend/starflix/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Starflix.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :starflix, 7 | version: "0.1.0", 8 | elixir: "~> 1.12", 9 | elixirc_paths: elixirc_paths(Mix.env()), 10 | compilers: [:gettext] ++ Mix.compilers(), 11 | start_permanent: Mix.env() == :prod, 12 | aliases: aliases(), 13 | deps: deps() 14 | ] 15 | end 16 | 17 | # Configuration for the OTP application. 18 | # 19 | # Type `mix help compile.app` for more information. 20 | def application do 21 | [ 22 | mod: {Starflix.Application, []}, 23 | extra_applications: [:logger, :runtime_tools] 24 | ] 25 | end 26 | 27 | # Specifies which paths to compile per environment. 28 | defp elixirc_paths(:test), do: ["lib", "test/support"] 29 | defp elixirc_paths(_), do: ["lib"] 30 | 31 | # Specifies your project dependencies. 32 | # 33 | # Type `mix help deps` for examples and options. 34 | defp deps do 35 | [ 36 | {:phoenix, "~> 1.6.15"}, 37 | {:phoenix_ecto, "~> 4.4"}, 38 | {:ecto_sql, "~> 3.6"}, 39 | {:postgrex, ">= 0.0.0"}, 40 | {:swoosh, "~> 1.3"}, 41 | {:telemetry_metrics, "~> 0.6"}, 42 | {:telemetry_poller, "~> 1.0"}, 43 | {:gettext, "~> 0.18"}, 44 | {:jason, "~> 1.2"}, 45 | {:plug_cowboy, "~> 2.5"} 46 | ] 47 | end 48 | 49 | # Aliases are shortcuts or tasks specific to the current project. 50 | # For example, to install project dependencies and perform other setup tasks, run: 51 | # 52 | # $ mix setup 53 | # 54 | # See the documentation for `Mix` for more info on aliases. 55 | defp aliases do 56 | [ 57 | setup: ["deps.get", "ecto.setup"], 58 | "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], 59 | "ecto.reset": ["ecto.drop", "ecto.setup"], 60 | test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"] 61 | ] 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /backend/starflix/config/prod.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # For production, don't forget to configure the url host 4 | # to something meaningful, Phoenix uses this information 5 | # when generating URLs. 6 | # 7 | # Note we also include the path to a cache manifest 8 | # containing the digested version of static files. This 9 | # manifest is generated by the `mix phx.digest` task, 10 | # which you should run after static files are built and 11 | # before starting your production server. 12 | config :starflix, StarflixWeb.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json" 13 | 14 | # Do not print debug messages in production 15 | config :logger, level: :info 16 | 17 | # ## SSL Support 18 | # 19 | # To get SSL working, you will need to add the `https` key 20 | # to the previous section and set your `:url` port to 443: 21 | # 22 | # config :starflix, StarflixWeb.Endpoint, 23 | # ..., 24 | # url: [host: "example.com", port: 443], 25 | # https: [ 26 | # ..., 27 | # port: 443, 28 | # cipher_suite: :strong, 29 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), 30 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") 31 | # ] 32 | # 33 | # The `cipher_suite` is set to `:strong` to support only the 34 | # latest and more secure SSL ciphers. This means old browsers 35 | # and clients may not be supported. You can set it to 36 | # `:compatible` for wider support. 37 | # 38 | # `:keyfile` and `:certfile` expect an absolute path to the key 39 | # and cert in disk or a relative path inside priv, for example 40 | # "priv/ssl/server.key". For all supported SSL configuration 41 | # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 42 | # 43 | # We also recommend setting `force_ssl` in your endpoint, ensuring 44 | # no data is ever sent via http, always redirecting to https: 45 | # 46 | # config :starflix, StarflixWeb.Endpoint, 47 | # force_ssl: [hsts: true] 48 | # 49 | # Check `Plug.SSL` for all available options in `force_ssl`. 50 | -------------------------------------------------------------------------------- /backend/starflix/lib/starflix_web.ex: -------------------------------------------------------------------------------- 1 | defmodule StarflixWeb do 2 | @moduledoc """ 3 | The entrypoint for defining your web interface, such 4 | as controllers, views, channels and so on. 5 | 6 | This can be used in your application as: 7 | 8 | use StarflixWeb, :controller 9 | use StarflixWeb, :view 10 | 11 | The definitions below will be executed for every view, 12 | controller, etc, so keep them short and clean, focused 13 | on imports, uses and aliases. 14 | 15 | Do NOT define functions inside the quoted expressions 16 | below. Instead, define any helper function in modules 17 | and import those modules here. 18 | """ 19 | 20 | def controller do 21 | quote do 22 | use Phoenix.Controller, namespace: StarflixWeb 23 | 24 | import Plug.Conn 25 | import StarflixWeb.Gettext 26 | alias StarflixWeb.Router.Helpers, as: Routes 27 | end 28 | end 29 | 30 | def view do 31 | quote do 32 | use Phoenix.View, 33 | root: "lib/starflix_web/templates", 34 | namespace: StarflixWeb 35 | 36 | # Import convenience functions from controllers 37 | import Phoenix.Controller, 38 | only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1] 39 | 40 | # Include shared imports and aliases for views 41 | unquote(view_helpers()) 42 | end 43 | end 44 | 45 | def router do 46 | quote do 47 | use Phoenix.Router 48 | 49 | import Plug.Conn 50 | import Phoenix.Controller 51 | end 52 | end 53 | 54 | def channel do 55 | quote do 56 | use Phoenix.Channel 57 | import StarflixWeb.Gettext 58 | end 59 | end 60 | 61 | defp view_helpers do 62 | quote do 63 | # Import basic rendering functionality (render, render_layout, etc) 64 | import Phoenix.View 65 | 66 | import StarflixWeb.ErrorHelpers 67 | import StarflixWeb.Gettext 68 | alias StarflixWeb.Router.Helpers, as: Routes 69 | end 70 | end 71 | 72 | @doc """ 73 | When used, dispatch to the appropriate controller/view/etc. 74 | """ 75 | defmacro __using__(which) when is_atom(which) do 76 | apply(__MODULE__, which, []) 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /backend/starflix/config/dev.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # Configure your database 4 | config :starflix, Starflix.Repo, 5 | username: "postgres", 6 | password: "postgres", 7 | hostname: "localhost", 8 | database: "starflix_dev", 9 | stacktrace: true, 10 | show_sensitive_data_on_connection_error: true, 11 | pool_size: 10 12 | 13 | # For development, we disable any cache and enable 14 | # debugging and code reloading. 15 | # 16 | # The watchers configuration can be used to run external 17 | # watchers to your application. For example, we use it 18 | # with esbuild to bundle .js and .css sources. 19 | config :starflix, StarflixWeb.Endpoint, 20 | # Binding to loopback ipv4 address prevents access from other machines. 21 | # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. 22 | http: [ip: {127, 0, 0, 1}, port: 4000], 23 | check_origin: false, 24 | code_reloader: true, 25 | debug_errors: true, 26 | secret_key_base: "BKKDkhKd9C9Oyl2evTHljORX52qMsRq23eMlF/m2CHn38TjE+ZQLrjzGJlilgczX", 27 | watchers: [] 28 | 29 | # ## SSL Support 30 | # 31 | # In order to use HTTPS in development, a self-signed 32 | # certificate can be generated by running the following 33 | # Mix task: 34 | # 35 | # mix phx.gen.cert 36 | # 37 | # Note that this task requires Erlang/OTP 20 or later. 38 | # Run `mix help phx.gen.cert` for more information. 39 | # 40 | # The `http:` config above can be replaced with: 41 | # 42 | # https: [ 43 | # port: 4001, 44 | # cipher_suite: :strong, 45 | # keyfile: "priv/cert/selfsigned_key.pem", 46 | # certfile: "priv/cert/selfsigned.pem" 47 | # ], 48 | # 49 | # If desired, both `http:` and `https:` keys can be 50 | # configured to run both http and https servers on 51 | # different ports. 52 | 53 | # Do not include metadata nor timestamps in development logs 54 | config :logger, :console, format: "[$level] $message\n" 55 | 56 | # Set a higher stacktrace during development. Avoid configuring such 57 | # in production as building large stacktraces may be expensive. 58 | config :phoenix, :stacktrace_depth, 20 59 | 60 | # Initialize plugs at runtime for faster development compilation 61 | config :phoenix, :plug_init_mode, :runtime 62 | -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './App.css'; 3 | import { useState, useEffect } from 'react'; 4 | import MovieCard from './components/MovieCard'; 5 | import SearchIcon from './search.svg' 6 | 7 | export default function App() { 8 | const [movies, setMovies] = useState([]) 9 | const [searchTerm, setSearchTerm] = useState('') 10 | 11 | const apiURL = "http://www.omdbapi.com?apikey=2b1c8c2f" // Api called and saved 12 | 13 | // Just to understand what props to pass from API 👇 14 | // const movie = { 15 | // "Poster": "https://m.media-amazon.com/images/M/MV5BN2I4OTllM2MtMWVhNC00MjkzLWJlMDUtN2FhMGQ2ZGVjMjllXkEyXkFqcGdeQXVyMTEyNzgwMDUw._V1_SX300.jpg", 16 | // "Title": "Batman v Superman: Dawn of Justice (Ultimate Edition)", 17 | // "Type": "movie", 18 | // "Year": "2016", 19 | // "imdbID": "tt18689424" 20 | // } 21 | 22 | // Fetching movies from API and setting data 23 | const searchMovies = async (title) => { 24 | const response = await fetch(`${apiURL}&s=${title}`) 25 | const data = await response.json() 26 | setMovies(data.Search) 27 | } 28 | 29 | useEffect(() => { 30 | searchMovies('Batman') 31 | }, []) 32 | // mapping the movies array👇 33 | const movieElements = movies.map(movie => ( 34 | )) 35 | 36 | // what to display if movie searched is found or not 37 | const showMovie = movies?.length > 0 38 | ? ( 39 |
40 | {movieElements} 41 |
42 | ) : ( 43 |
44 |

No Movies Found !

45 |
46 | ) 47 | function handleChange(event) { 48 | setSearchTerm(event.target.value) 49 | } 50 | function handleClick() { 51 | searchMovies(searchTerm) 52 | } 53 | function handleSubmit(event) { 54 | if (event.key === 'Enter') { 55 | handleClick() 56 | } 57 | } 58 | return ( 59 |
60 |
61 |

STARFLIX

62 |
63 |
64 | 70 | Search 75 |
76 | {showMovie} 77 |
78 |

End

79 |
80 | ) 81 | } 82 | -------------------------------------------------------------------------------- /backend/starflix/lib/starflix_web/telemetry.ex: -------------------------------------------------------------------------------- 1 | defmodule StarflixWeb.Telemetry do 2 | use Supervisor 3 | import Telemetry.Metrics 4 | 5 | def start_link(arg) do 6 | Supervisor.start_link(__MODULE__, arg, name: __MODULE__) 7 | end 8 | 9 | @impl true 10 | def init(_arg) do 11 | children = [ 12 | # Telemetry poller will execute the given period measurements 13 | # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics 14 | {:telemetry_poller, measurements: periodic_measurements(), period: 10_000} 15 | # Add reporters as children of your supervision tree. 16 | # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()} 17 | ] 18 | 19 | Supervisor.init(children, strategy: :one_for_one) 20 | end 21 | 22 | def metrics do 23 | [ 24 | # Phoenix Metrics 25 | summary("phoenix.endpoint.stop.duration", 26 | unit: {:native, :millisecond} 27 | ), 28 | summary("phoenix.router_dispatch.stop.duration", 29 | tags: [:route], 30 | unit: {:native, :millisecond} 31 | ), 32 | 33 | # Database Metrics 34 | summary("starflix.repo.query.total_time", 35 | unit: {:native, :millisecond}, 36 | description: "The sum of the other measurements" 37 | ), 38 | summary("starflix.repo.query.decode_time", 39 | unit: {:native, :millisecond}, 40 | description: "The time spent decoding the data received from the database" 41 | ), 42 | summary("starflix.repo.query.query_time", 43 | unit: {:native, :millisecond}, 44 | description: "The time spent executing the query" 45 | ), 46 | summary("starflix.repo.query.queue_time", 47 | unit: {:native, :millisecond}, 48 | description: "The time spent waiting for a database connection" 49 | ), 50 | summary("starflix.repo.query.idle_time", 51 | unit: {:native, :millisecond}, 52 | description: 53 | "The time the connection spent waiting before being checked out for the query" 54 | ), 55 | 56 | # VM Metrics 57 | summary("vm.memory.total", unit: {:byte, :kilobyte}), 58 | summary("vm.total_run_queue_lengths.total"), 59 | summary("vm.total_run_queue_lengths.cpu"), 60 | summary("vm.total_run_queue_lengths.io") 61 | ] 62 | end 63 | 64 | defp periodic_measurements do 65 | [ 66 | # A module, function and arguments to be invoked periodically. 67 | # This function must call :telemetry.execute/3 and a metric must be added above. 68 | # {StarflixWeb, :count_users, []} 69 | ] 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /backend/starflix/priv/gettext/errors.pot: -------------------------------------------------------------------------------- 1 | ## This is a PO Template file. 2 | ## 3 | ## `msgid`s here are often extracted from source code. 4 | ## Add new translations manually only if they're dynamic 5 | ## translations that can't be statically extracted. 6 | ## 7 | ## Run `mix gettext.extract` to bring this file up to 8 | ## date. Leave `msgstr`s empty as changing them here has no 9 | ## effect: edit them in PO (`.po`) files instead. 10 | 11 | ## From Ecto.Changeset.cast/4 12 | msgid "can't be blank" 13 | msgstr "" 14 | 15 | ## From Ecto.Changeset.unique_constraint/3 16 | msgid "has already been taken" 17 | msgstr "" 18 | 19 | ## From Ecto.Changeset.put_change/3 20 | msgid "is invalid" 21 | msgstr "" 22 | 23 | ## From Ecto.Changeset.validate_acceptance/3 24 | msgid "must be accepted" 25 | msgstr "" 26 | 27 | ## From Ecto.Changeset.validate_format/3 28 | msgid "has invalid format" 29 | msgstr "" 30 | 31 | ## From Ecto.Changeset.validate_subset/3 32 | msgid "has an invalid entry" 33 | msgstr "" 34 | 35 | ## From Ecto.Changeset.validate_exclusion/3 36 | msgid "is reserved" 37 | msgstr "" 38 | 39 | ## From Ecto.Changeset.validate_confirmation/3 40 | msgid "does not match confirmation" 41 | msgstr "" 42 | 43 | ## From Ecto.Changeset.no_assoc_constraint/3 44 | msgid "is still associated with this entry" 45 | msgstr "" 46 | 47 | msgid "are still associated with this entry" 48 | msgstr "" 49 | 50 | ## From Ecto.Changeset.validate_length/3 51 | msgid "should be %{count} character(s)" 52 | msgid_plural "should be %{count} character(s)" 53 | msgstr[0] "" 54 | msgstr[1] "" 55 | 56 | msgid "should have %{count} item(s)" 57 | msgid_plural "should have %{count} item(s)" 58 | msgstr[0] "" 59 | msgstr[1] "" 60 | 61 | msgid "should be at least %{count} character(s)" 62 | msgid_plural "should be at least %{count} character(s)" 63 | msgstr[0] "" 64 | msgstr[1] "" 65 | 66 | msgid "should have at least %{count} item(s)" 67 | msgid_plural "should have at least %{count} item(s)" 68 | msgstr[0] "" 69 | msgstr[1] "" 70 | 71 | msgid "should be at most %{count} character(s)" 72 | msgid_plural "should be at most %{count} character(s)" 73 | msgstr[0] "" 74 | msgstr[1] "" 75 | 76 | msgid "should have at most %{count} item(s)" 77 | msgid_plural "should have at most %{count} item(s)" 78 | msgstr[0] "" 79 | msgstr[1] "" 80 | 81 | ## From Ecto.Changeset.validate_number/3 82 | msgid "must be less than %{number}" 83 | msgstr "" 84 | 85 | msgid "must be greater than %{number}" 86 | msgstr "" 87 | 88 | msgid "must be less than or equal to %{number}" 89 | msgstr "" 90 | 91 | msgid "must be greater than or equal to %{number}" 92 | msgstr "" 93 | 94 | msgid "must be equal to %{number}" 95 | msgstr "" 96 | -------------------------------------------------------------------------------- /backend/starflix/priv/gettext/en/LC_MESSAGES/errors.po: -------------------------------------------------------------------------------- 1 | ## `msgid`s in this file come from POT (.pot) files. 2 | ## 3 | ## Do not add, change, or remove `msgid`s manually here as 4 | ## they're tied to the ones in the corresponding POT file 5 | ## (with the same domain). 6 | ## 7 | ## Use `mix gettext.extract --merge` or `mix gettext.merge` 8 | ## to merge POT files into PO files. 9 | msgid "" 10 | msgstr "" 11 | "Language: en\n" 12 | 13 | ## From Ecto.Changeset.cast/4 14 | msgid "can't be blank" 15 | msgstr "" 16 | 17 | ## From Ecto.Changeset.unique_constraint/3 18 | msgid "has already been taken" 19 | msgstr "" 20 | 21 | ## From Ecto.Changeset.put_change/3 22 | msgid "is invalid" 23 | msgstr "" 24 | 25 | ## From Ecto.Changeset.validate_acceptance/3 26 | msgid "must be accepted" 27 | msgstr "" 28 | 29 | ## From Ecto.Changeset.validate_format/3 30 | msgid "has invalid format" 31 | msgstr "" 32 | 33 | ## From Ecto.Changeset.validate_subset/3 34 | msgid "has an invalid entry" 35 | msgstr "" 36 | 37 | ## From Ecto.Changeset.validate_exclusion/3 38 | msgid "is reserved" 39 | msgstr "" 40 | 41 | ## From Ecto.Changeset.validate_confirmation/3 42 | msgid "does not match confirmation" 43 | msgstr "" 44 | 45 | ## From Ecto.Changeset.no_assoc_constraint/3 46 | msgid "is still associated with this entry" 47 | msgstr "" 48 | 49 | msgid "are still associated with this entry" 50 | msgstr "" 51 | 52 | ## From Ecto.Changeset.validate_length/3 53 | msgid "should have %{count} item(s)" 54 | msgid_plural "should have %{count} item(s)" 55 | msgstr[0] "" 56 | msgstr[1] "" 57 | 58 | msgid "should be %{count} character(s)" 59 | msgid_plural "should be %{count} character(s)" 60 | msgstr[0] "" 61 | msgstr[1] "" 62 | 63 | msgid "should be %{count} byte(s)" 64 | msgid_plural "should be %{count} byte(s)" 65 | msgstr[0] "" 66 | msgstr[1] "" 67 | 68 | msgid "should have at least %{count} item(s)" 69 | msgid_plural "should have at least %{count} item(s)" 70 | msgstr[0] "" 71 | msgstr[1] "" 72 | 73 | msgid "should be at least %{count} character(s)" 74 | msgid_plural "should be at least %{count} character(s)" 75 | msgstr[0] "" 76 | msgstr[1] "" 77 | 78 | msgid "should be at least %{count} byte(s)" 79 | msgid_plural "should be at least %{count} byte(s)" 80 | msgstr[0] "" 81 | msgstr[1] "" 82 | 83 | msgid "should have at most %{count} item(s)" 84 | msgid_plural "should have at most %{count} item(s)" 85 | msgstr[0] "" 86 | msgstr[1] "" 87 | 88 | msgid "should be at most %{count} character(s)" 89 | msgid_plural "should be at most %{count} character(s)" 90 | msgstr[0] "" 91 | msgstr[1] "" 92 | 93 | msgid "should be at most %{count} byte(s)" 94 | msgid_plural "should be at most %{count} byte(s)" 95 | msgstr[0] "" 96 | msgstr[1] "" 97 | 98 | ## From Ecto.Changeset.validate_number/3 99 | msgid "must be less than %{number}" 100 | msgstr "" 101 | 102 | msgid "must be greater than %{number}" 103 | msgstr "" 104 | 105 | msgid "must be less than or equal to %{number}" 106 | msgstr "" 107 | 108 | msgid "must be greater than or equal to %{number}" 109 | msgstr "" 110 | 111 | msgid "must be equal to %{number}" 112 | msgstr "" 113 | -------------------------------------------------------------------------------- /backend/starflix/config/runtime.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # config/runtime.exs is executed for all environments, including 4 | # during releases. It is executed after compilation and before the 5 | # system starts, so it is typically used to load production configuration 6 | # and secrets from environment variables or elsewhere. Do not define 7 | # any compile-time configuration in here, as it won't be applied. 8 | # The block below contains prod specific runtime configuration. 9 | 10 | # ## Using releases 11 | # 12 | # If you use `mix release`, you need to explicitly enable the server 13 | # by passing the PHX_SERVER=true when you start it: 14 | # 15 | # PHX_SERVER=true bin/starflix start 16 | # 17 | # Alternatively, you can use `mix phx.gen.release` to generate a `bin/server` 18 | # script that automatically sets the env var above. 19 | if System.get_env("PHX_SERVER") do 20 | config :starflix, StarflixWeb.Endpoint, server: true 21 | end 22 | 23 | if config_env() == :prod do 24 | database_url = 25 | System.get_env("DATABASE_URL") || 26 | raise """ 27 | environment variable DATABASE_URL is missing. 28 | For example: ecto://USER:PASS@HOST/DATABASE 29 | """ 30 | 31 | maybe_ipv6 = if System.get_env("ECTO_IPV6"), do: [:inet6], else: [] 32 | 33 | config :starflix, Starflix.Repo, 34 | # ssl: true, 35 | url: database_url, 36 | pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"), 37 | socket_options: maybe_ipv6 38 | 39 | # The secret key base is used to sign/encrypt cookies and other secrets. 40 | # A default value is used in config/dev.exs and config/test.exs but you 41 | # want to use a different value for prod and you most likely don't want 42 | # to check this value into version control, so we use an environment 43 | # variable instead. 44 | secret_key_base = 45 | System.get_env("SECRET_KEY_BASE") || 46 | raise """ 47 | environment variable SECRET_KEY_BASE is missing. 48 | You can generate one by calling: mix phx.gen.secret 49 | """ 50 | 51 | host = System.get_env("PHX_HOST") || "example.com" 52 | port = String.to_integer(System.get_env("PORT") || "4000") 53 | 54 | config :starflix, StarflixWeb.Endpoint, 55 | url: [host: host, port: 443, scheme: "https"], 56 | http: [ 57 | # Enable IPv6 and bind on all interfaces. 58 | # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. 59 | # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html 60 | # for details about using IPv6 vs IPv4 and loopback vs public addresses. 61 | ip: {0, 0, 0, 0, 0, 0, 0, 0}, 62 | port: port 63 | ], 64 | secret_key_base: secret_key_base 65 | 66 | # ## Configuring the mailer 67 | # 68 | # In production you need to configure the mailer to use a different adapter. 69 | # Also, you may need to configure the Swoosh API client of your choice if you 70 | # are not using SMTP. Here is an example of the configuration: 71 | # 72 | # config :starflix, Starflix.Mailer, 73 | # adapter: Swoosh.Adapters.Mailgun, 74 | # api_key: System.get_env("MAILGUN_API_KEY"), 75 | # domain: System.get_env("MAILGUN_DOMAIN") 76 | # 77 | # For this example you need include a HTTP client required by Swoosh API client. 78 | # Swoosh supports Hackney and Finch out of the box: 79 | # 80 | # config :swoosh, :api_client, Swoosh.ApiClient.Hackney 81 | # 82 | # See https://hexdocs.pm/swoosh/Swoosh.html#module-installation for details. 83 | end 84 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Roboto+Slab:100,300,400,700"); 2 | @import url("https://fonts.googleapis.com/css?family=Raleway:300,300i,400,400i,500,500i,600,600i,700,700i,800,800i,900,900i"); 3 | @import url('https://fonts.googleapis.com/css?family=Roboto:700,900'); 4 | 5 | * { 6 | margin: 0; 7 | border: 0; 8 | box-sizing: border-box; 9 | } 10 | 11 | :root { 12 | --font-roboto: "Roboto Slab", serif; 13 | --font-raleway: "Raleway", sans-serif; 14 | } 15 | 16 | body { 17 | font-family: var(--font-roboto); 18 | background-color: #212426; 19 | } 20 | 21 | .app { 22 | padding: 4rem; 23 | 24 | display: flex; 25 | justify-content: center; 26 | align-items: center; 27 | flex-direction: column; 28 | } 29 | 30 | 31 | h1 { 32 | text-align: center; 33 | font-family: 'Roboto'; 34 | font-size: 12vmin; 35 | font-weight: 700; 36 | animation: netflix_style 3.5s normal; 37 | outline: none; 38 | white-space: nowrap; 39 | color: #e90418; 40 | } 41 | 42 | @keyframes netflix_style { 43 | 0% { 44 | text-shadow: makelongshadow(100, 1); 45 | color: #f3f3f3; 46 | transform: scale(1.5, 1.5); 47 | } 48 | 49 | 10% { 50 | text-shadow: makelongshadow(100, 1.5); 51 | color: #f3f3f3; 52 | transform: scale(1.5, 1.5); 53 | } 54 | 55 | 15% { 56 | color: #f3f3f3; 57 | } 58 | 59 | 20% { 60 | color: #e90418; 61 | text-shadow: none; 62 | transform: scale(1.1, 1.1); 63 | } 64 | 65 | 75% { 66 | opacity: 1; 67 | } 68 | 69 | 80% { 70 | opacity: 0; 71 | color: #e90418; 72 | transform: scale(0.85, 0.9); 73 | } 74 | 75 | 100% { 76 | opacity: 0; 77 | } 78 | } 79 | 80 | .search { 81 | width: 70%; 82 | margin: 4rem 0 2rem; 83 | 84 | display: flex; 85 | align-items: center; 86 | justify-content: center; 87 | 88 | padding: 1.5rem 1.75rem; 89 | border-radius: 3rem; 90 | background: #1f2123; 91 | -webkit-box-shadow: 5px 5px 7px #1c1d1f, -5px -5px 7px #222527; 92 | box-shadow: 5px 5px 7px #1c1d1f, -5px -5px 7px #222527; 93 | } 94 | 95 | .search input { 96 | flex: 1; 97 | border: none; 98 | font-size: 1.5rem; 99 | font-family: var(--font-raleway); 100 | font-weight: 500; 101 | padding-right: 1rem; 102 | 103 | outline: none; 104 | color: #a1a1a1; 105 | background: #1f2123; 106 | } 107 | 108 | .search img { 109 | width: 35px; 110 | height: 35px; 111 | cursor: pointer; 112 | } 113 | 114 | .empty { 115 | width: 100%; 116 | margin-top: 3rem; 117 | 118 | display: flex; 119 | justify-content: center; 120 | align-items: center; 121 | } 122 | 123 | .empty h2 { 124 | font-size: 1.25rem; 125 | color: #f9d3b4; 126 | font-family: var(--font-raleway); 127 | } 128 | 129 | .container { 130 | width: 100%; 131 | margin-top: 3rem; 132 | 133 | display: flex; 134 | justify-content: center; 135 | align-items: center; 136 | flex-wrap: wrap; 137 | } 138 | 139 | .movie { 140 | width: 310px; 141 | height: 460px; 142 | margin: 1.5rem; 143 | 144 | position: relative; 145 | border-radius: 12px; 146 | overflow: hidden; 147 | border: none; 148 | 149 | transition: all 0.4s cubic-bezier(0.175, 0.885, 0, 1); 150 | box-shadow: 0px 13px 10px -7px rgba(0, 0, 0, 0.1); 151 | } 152 | 153 | .movie div:nth-of-type(1) { 154 | position: absolute; 155 | padding: 16px; 156 | width: 100%; 157 | opacity: 0; 158 | top: 0; 159 | color: #f9d3b4; 160 | } 161 | 162 | .movie:hover { 163 | box-shadow: 0px 30px 18px -8px rgba(0, 0, 0, 0.1); 164 | transform: scale(1.05, 1.05); 165 | cursor: pointer; 166 | } 167 | 168 | .movie div:nth-of-type(2) { 169 | width: 100%; 170 | height: 100%; 171 | } 172 | 173 | .movie div:nth-of-type(2) img { 174 | height: 100%; 175 | width: 100%; 176 | } 177 | 178 | .movie div:nth-of-type(3) { 179 | z-index: 2; 180 | background-color: #343739; 181 | padding: 16px 24px 24px 24px; 182 | 183 | position: absolute; 184 | bottom: 0; 185 | right: 0; 186 | left: 0; 187 | } 188 | 189 | .movie div:nth-of-type(3) span { 190 | font-family: "Raleway", sans-serif; 191 | text-transform: uppercase; 192 | font-size: 13px; 193 | letter-spacing: 2px; 194 | font-weight: 500; 195 | color: #f0f0f0; 196 | } 197 | 198 | .movie div:nth-of-type(3) h3 { 199 | margin-top: 5px; 200 | font-family: "Roboto Slab", serif; 201 | color: #f9d3b4; 202 | } 203 | 204 | .movie:hover div:nth-of-type(2) { 205 | height: 100%; 206 | opacity: 0.6; 207 | } 208 | 209 | .movie:hover div:nth-of-type(3) { 210 | background-color: transparent; 211 | } 212 | 213 | .movie:hover div:nth-of-type(1) { 214 | opacity: 1; 215 | } 216 | 217 | 218 | 219 | @media screen and (max-width: 600px) { 220 | .app { 221 | padding: 4rem 2rem; 222 | } 223 | 224 | .search { 225 | padding: 1rem 1.75rem; 226 | width: 100%; 227 | } 228 | 229 | .search input { 230 | font-size: 1rem; 231 | } 232 | 233 | .search img { 234 | width: 20px; 235 | height: 20px; 236 | } 237 | } 238 | 239 | @media screen and (max-width: 400px) { 240 | .app { 241 | padding: 4rem 1rem; 242 | } 243 | 244 | h1 { 245 | font-size: 2rem; 246 | } 247 | 248 | .container { 249 | margin-top: 2rem; 250 | } 251 | 252 | .movie { 253 | width: "100%"; 254 | margin: 1rem; 255 | } 256 | } -------------------------------------------------------------------------------- /backend/starflix/mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "castore": {:hex, :castore, "1.0.0", "c25cd0794c054ebe6908a86820c8b92b5695814479ec95eeff35192720b71eec", [:mix], [], "hexpm", "577d0e855983a97ca1dfa33cbb8a3b6ece6767397ffb4861514343b078fc284b"}, 3 | "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, 4 | "cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"}, 5 | "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, 6 | "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"}, 7 | "db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"}, 8 | "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, 9 | "ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"}, 10 | "ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"}, 11 | "expo": {:hex, :expo, "0.3.0", "13127c1d5f653b2927f2616a4c9ace5ae372efd67c7c2693b87fd0fdc30c6feb", [:mix], [], "hexpm", "fb3cd4bf012a77bc1608915497dae2ff684a06f0fa633c7afa90c4d72b881823"}, 12 | "gettext": {:hex, :gettext, "0.22.0", "a25d71ec21b1848957d9207b81fd61cb25161688d282d58bdafef74c2270bdc4", [:mix], [{:expo, "~> 0.3.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "cb0675141576f73720c8e49b4f0fd3f2c69f0cd8c218202724d4aebab8c70ace"}, 13 | "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, 14 | "mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"}, 15 | "phoenix": {:hex, :phoenix, "1.6.15", "0a1d96bbc10747fd83525370d691953cdb6f3ccbac61aa01b4acb012474b047d", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d70ab9fbf6b394755ea88b644d34d79d8b146e490973151f248cacd122d20672"}, 16 | "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"}, 17 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, 18 | "phoenix_template": {:hex, :phoenix_template, "1.0.0", "c57bc5044f25f007dc86ab21895688c098a9f846a8dda6bc40e2d0ddc146e38f", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "1b066f99a26fd22064c12b2600a9a6e56700f591bf7b20b418054ea38b4d4357"}, 19 | "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, 20 | "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, 21 | "plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, 22 | "plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"}, 23 | "postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"}, 24 | "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, 25 | "swoosh": {:hex, :swoosh, "1.9.1", "0a5d7bf9954eb41d7e55525bc0940379982b090abbaef67cd8e1fd2ed7f8ca1a", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76dffff3ffcab80f249d5937a592eaef7cc49ac6f4cdd27e622868326ed6371e"}, 26 | "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, 27 | "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, 28 | "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, 29 | } 30 | --------------------------------------------------------------------------------