├── Dockerfile ├── Makefile ├── README.md ├── docker-compose.yml ├── entrypoint.sh └── lrmi ├── .formatter.exs ├── .gitignore ├── README.md ├── config ├── config.exs ├── dev.exs ├── prod.exs └── test.exs ├── lib ├── lrmi.ex ├── lrmi │ ├── application.ex │ ├── helpers │ │ └── atomic_map_extension.ex │ ├── repo.ex │ └── resources │ │ ├── creative_work.ex │ │ └── resources.ex ├── lrmi_graphql │ ├── .DS_Store │ ├── helpers │ │ ├── .DS_Store │ │ └── absinthe │ │ │ └── data_paginator.ex │ ├── object_types │ │ ├── creative_work.ex │ │ ├── language.ex │ │ ├── person.ex │ │ └── publisher.ex │ ├── resolvers │ │ └── resources │ │ │ └── creative_work.ex │ └── schema.ex ├── lrmi_web.ex └── lrmi_web │ ├── channels │ └── user_socket.ex │ ├── endpoint.ex │ ├── gettext.ex │ └── views │ ├── error_helpers.ex │ └── error_view.ex ├── mix.exs ├── mix.lock ├── priv ├── gettext │ ├── en │ │ └── LC_MESSAGES │ │ │ └── errors.po │ └── errors.pot ├── repo │ ├── db_constraints.exs │ └── seeds.exs └── seed_data │ └── creative_work.json └── test ├── lrmi └── resources │ ├── creative_work_test.exs │ └── resources_test.exs ├── lrmi_graphql ├── object_types │ └── creative_work_test.exs └── queries │ └── creative_works_test.exs ├── lrmi_web └── views │ └── error_view_test.exs ├── support ├── absinthe_helpers.ex ├── conn_case.ex ├── data_case.ex ├── factories │ └── creative_work.ex └── factory.ex └── test_helper.exs /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM elixir:1.7-alpine 2 | 3 | RUN apk add --no-cache bash 4 | RUN apk add --update \ 5 | nodejs \ 6 | build-base 7 | 8 | EXPOSE 4000 9 | 10 | WORKDIR /usr/src/app 11 | 12 | RUN yes | mix local.hex 13 | 14 | RUN yes | mix archive.install hex phx_new 1.4.0 15 | 16 | COPY ./entrypoint.sh /etc/entrypoint.sh 17 | RUN chmod +x /etc/entrypoint.sh 18 | CMD ["/bin/sh"] 19 | ENTRYPOINT ["/etc/entrypoint.sh"] 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash # Use bash syntax 2 | 3 | mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) 4 | current_dir_name := $(notdir $(patsubst %/,%,$(dir $(mkfile_path)))) 5 | 6 | run: 7 | docker-compose run --service-ports --rm app 8 | 9 | destroy: 10 | docker-compose down 11 | docker volume rm ${current_dir_name}_elixir-mix 12 | docker volume rm ${current_dir_name}_mongodb-data 13 | docker rmi ${current_dir_name}_app 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is it? 2 | 3 | This project is a Graphlql API made with elixir, phoenix framework and mongodb on docker environment. The following tools are installed: 4 | * mix 5 | * hex 6 | * phx_new 7 | 8 | This project is a guide for anyone who tries to use mongodb with phoenix framework. I've created it above docker container because is more easy for me, but if you already have elixir environment in your local machine, feel free to run the project in your machine. Remember yourself, in this situation, you have to setup a mongodb server by on your own. 9 | 10 | The data sample was built on LRMI format (http://lrmi.dublincore.org/). I used this format because is pretty simple to explain the project and because I'm using in production at my job. 11 | 12 | In this Medium article https://medium.com/@pierreabreu/how-to-create-a-graphql-api-with-elixir-and-mongodb-f3a9e0602832 I explain deeply the project, design code and other things. 13 | 14 | # How to use it? 15 | 16 | After you've clone this project, follow the steps: 17 | - open a terminal session in your terminal tool (Iterm, Terminal, Putty) 18 | - go to the folder where you've cloned the project (Ex.: ```cd graphqlapi-with-elixir-and-mongo```) 19 | - type the command: ```make run``` 20 | 21 | This command will run the container and open a session inside the container. If all goes well, you will be in the path ```/usr/src/app```. In this path, type `cd lrmi` and after that type the following commands: 22 | * ```mix ecto.setup```: This command creates the database and seed with sample data 23 | * ```mix phx.server``` 24 | 25 | The server will run on `http://localhost:4000`. To test it, you can use https://insomnia.rest/. 26 | 27 | At last, test the queries: 28 | ```` 29 | query { 30 | creativeWorks(first: 2) { 31 | edges { 32 | node { 33 | name 34 | identifier { 35 | propertyId 36 | value 37 | } 38 | } 39 | cursor 40 | } 41 | pageInfo { 42 | startCursor 43 | endCursor 44 | hasNextPage 45 | hasPreviousPage 46 | } 47 | } 48 | } 49 | ```` 50 | 51 | ```` 52 | query { 53 | creativeWorks(first: 2, after: "5c8eaac3380b1ac5dee8531e") { 54 | edges { 55 | node { 56 | name 57 | identifier { 58 | propertyId 59 | value 60 | } 61 | } 62 | cursor 63 | } 64 | pageInfo { 65 | startCursor 66 | endCursor 67 | hasNextPage 68 | hasPreviousPage 69 | } 70 | } 71 | } 72 | ```` 73 | 74 | ``` 75 | query { 76 | creativeWorks(first: 2, identifier: {value: "112121", propertyId: "ContentHost-ContentID"}) { 77 | edges { 78 | node { 79 | name 80 | identifier { 81 | propertyId 82 | value 83 | } 84 | } 85 | cursor 86 | } 87 | pageInfo { 88 | startCursor 89 | endCursor 90 | hasNextPage 91 | hasPreviousPage 92 | } 93 | } 94 | } 95 | ``` 96 | 97 | When you have to finish your job, go to the terminal session where you've run `make run` and stop cowboy server pressing CTR+C twice. After that, type `exit` and at last `make down` 98 | 99 | ##### IMPORTANT! 100 | when you run ```make run``` at the first time, the docker image will be created. In the next times, docker compose will use the same image. 101 | 102 | # Automated Testing 103 | To run the test, go to the console when I've run `make run`, go to the LRMI project `cd lrmi` and type `mix test` 104 | 105 | I didn't create tests cases for all code, I've just created for more important areas of the project. 106 | 107 | # FAQ 108 | 109 | => **I don't want to use this project any more, is there a command to destroy the project?** 110 | **A:** Yes, open your terminal session, go to the project and type de command `make destroy` 111 | 112 | => **I'm having problems to up the container or run phoenixproject project. What can I do ?** 113 | **A:** create a [github issue](https://github.com/pierreabreup/graphqlapi-with-elixir-and-mongo/issues) . I promise will answer as soon as possible 114 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | app: 5 | build: . 6 | ports: 7 | - 4000:4000 8 | depends_on: 9 | - mongodb 10 | volumes: 11 | - '.:/usr/src/app' 12 | - elixir-mix:/root/.mix 13 | 14 | mongodb: 15 | image: mongo:latest 16 | environment: 17 | - MONGO_DATA_DIR=/data/db 18 | - MONGO_LOG_DIR=/dev/null 19 | - MONGODB_USER="root" 20 | - MONGODB_PASS="root" 21 | volumes: 22 | - mongodb-data:/data/db 23 | ports: 24 | - 27017:27017 25 | command: mongod --bind_ip_all --smallfiles --logpath=/dev/null # --quiet 26 | 27 | volumes: 28 | elixir-mix: 29 | mongodb-data: 30 | 31 | networks: 32 | local: 33 | 34 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | exec "$@" 6 | -------------------------------------------------------------------------------- /lrmi/.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 | -------------------------------------------------------------------------------- /lrmi/.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 | lrmi-*.tar 24 | 25 | # Since we are building assets from assets/, 26 | # we ignore priv/static. You may want to comment 27 | # this depending on your deployment strategy. 28 | /priv/static/ 29 | 30 | # Files matching config/*.secret.exs pattern contain sensitive 31 | # data and you should not commit them into version control. 32 | # 33 | # Alternatively, you may comment the line below and commit the 34 | # secrets files as long as you replace their contents by environment 35 | # variables. 36 | /config/*.secret.exs 37 | 38 | .DS_Store 39 | -------------------------------------------------------------------------------- /lrmi/README.md: -------------------------------------------------------------------------------- 1 | # Lrmi 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` 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: http://www.phoenixframework.org/ 16 | * Guides: https://hexdocs.pm/phoenix/overview.html 17 | * Docs: https://hexdocs.pm/phoenix 18 | * Mailing list: http://groups.google.com/group/phoenix-talk 19 | * Source: https://github.com/phoenixframework/phoenix 20 | -------------------------------------------------------------------------------- /lrmi/config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.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 | use Mix.Config 9 | 10 | config :lrmi, 11 | ecto_repos: [Lrmi.Repo] 12 | 13 | # Configures the endpoint 14 | config :lrmi, LrmiWeb.Endpoint, 15 | url: [host: "localhost"], 16 | secret_key_base: "k1x30yIssIIOD+gV18sN4WqKiPGQ+kYQE4mygmQ4j3X/UIBIOZn+GxD9HWAEXA4R", 17 | render_errors: [view: LrmiWeb.ErrorView, accepts: ~w(json)], 18 | pubsub: [name: Lrmi.PubSub, adapter: Phoenix.PubSub.PG2] 19 | 20 | # Configures Elixir's Logger 21 | config :logger, :console, 22 | format: "$time $metadata[$level] $message\n", 23 | metadata: [:request_id] 24 | 25 | # Use Jason for JSON parsing in Phoenix 26 | config :phoenix, :json_library, Jason 27 | 28 | config :absinthe, adapter: Absinthe.Adapter.LanguageConventions 29 | 30 | config :lrmi, :data_pagination, 31 | limit: 1000 32 | 33 | # Import environment specific config. This must remain at the bottom 34 | # of this file so it overrides the configuration defined above. 35 | import_config "#{Mix.env()}.exs" 36 | -------------------------------------------------------------------------------- /lrmi/config/dev.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # For development, we disable any cache and enable 4 | # debugging and code reloading. 5 | # 6 | # The watchers configuration can be used to run external 7 | # watchers to your application. For example, we use it 8 | # with webpack to recompile .js and .css sources. 9 | config :lrmi, LrmiWeb.Endpoint, 10 | http: [port: 4000], 11 | debug_errors: true, 12 | code_reloader: true, 13 | check_origin: false, 14 | watchers: [] 15 | 16 | # ## SSL Support 17 | # 18 | # In order to use HTTPS in development, a self-signed 19 | # certificate can be generated by running the following 20 | # Mix task: 21 | # 22 | # mix phx.gen.cert 23 | # 24 | # Note that this task requires Erlang/OTP 20 or later. 25 | # Run `mix help phx.gen.cert` for more information. 26 | # 27 | # The `http:` config above can be replaced with: 28 | # 29 | # https: [ 30 | # port: 4001, 31 | # cipher_suite: :strong, 32 | # keyfile: "priv/cert/selfsigned_key.pem", 33 | # certfile: "priv/cert/selfsigned.pem" 34 | # ], 35 | # 36 | # If desired, both `http:` and `https:` keys can be 37 | # configured to run both http and https servers on 38 | # different ports. 39 | 40 | # Do not include metadata nor timestamps in development logs 41 | config :logger, :console, format: "[$level] $message\n" 42 | 43 | # Set a higher stacktrace during development. Avoid configuring such 44 | # in production as building large stacktraces may be expensive. 45 | config :phoenix, :stacktrace_depth, 20 46 | 47 | # Initialize plugs at runtime for faster development compilation 48 | config :phoenix, :plug_init_mode, :runtime 49 | 50 | # Configure your database 51 | config :lrmi, :db_config, 52 | name: :mongo, 53 | database: "lrmi", 54 | seeds: ["mongodb:27017"], 55 | pool: DBConnection.Poolboy 56 | -------------------------------------------------------------------------------- /lrmi/config/prod.exs: -------------------------------------------------------------------------------- 1 | use Mix.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 :lrmi, LrmiWeb.Endpoint, 13 | http: [:inet6, port: System.get_env("PORT") || 4000], 14 | url: [host: "example.com", port: 80], 15 | cache_static_manifest: "priv/static/cache_manifest.json" 16 | 17 | # Do not print debug messages in production 18 | config :logger, level: :info 19 | 20 | # ## SSL Support 21 | # 22 | # To get SSL working, you will need to add the `https` key 23 | # to the previous section and set your `:url` port to 443: 24 | # 25 | # config :lrmi, LrmiWeb.Endpoint, 26 | # ... 27 | # url: [host: "example.com", port: 443], 28 | # https: [ 29 | # :inet6, 30 | # port: 443, 31 | # cipher_suite: :strong, 32 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), 33 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") 34 | # ] 35 | # 36 | # The `cipher_suite` is set to `:strong` to support only the 37 | # latest and more secure SSL ciphers. This means old browsers 38 | # and clients may not be supported. You can set it to 39 | # `:compatible` for wider support. 40 | # 41 | # `:keyfile` and `:certfile` expect an absolute path to the key 42 | # and cert in disk or a relative path inside priv, for example 43 | # "priv/ssl/server.key". For all supported SSL configuration 44 | # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 45 | # 46 | # We also recommend setting `force_ssl` in your endpoint, ensuring 47 | # no data is ever sent via http, always redirecting to https: 48 | # 49 | # config :lrmi, LrmiWeb.Endpoint, 50 | # force_ssl: [hsts: true] 51 | # 52 | # Check `Plug.SSL` for all available options in `force_ssl`. 53 | 54 | # ## Using releases (distillery) 55 | # 56 | # If you are doing OTP releases, you need to instruct Phoenix 57 | # to start the server for all endpoints: 58 | # 59 | # config :phoenix, :serve_endpoints, true 60 | # 61 | # Alternatively, you can configure exactly which server to 62 | # start per endpoint: 63 | # 64 | # config :lrmi, LrmiWeb.Endpoint, server: true 65 | # 66 | # Note you can't rely on `System.get_env/1` when using releases. 67 | # See the releases documentation accordingly. 68 | 69 | # Finally import the config/prod.secret.exs which should be versioned 70 | # separately. 71 | import_config "prod.secret.exs" 72 | -------------------------------------------------------------------------------- /lrmi/config/test.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # We don't run a server during test. If one is required, 4 | # you can enable the server option below. 5 | config :lrmi, LrmiWeb.Endpoint, 6 | http: [port: 4002], 7 | server: false 8 | 9 | # Print only warnings and errors during test 10 | config :logger, level: :warn 11 | 12 | config :lrmi, :db_config, 13 | name: :mongo, 14 | database: "lrmi_test", 15 | seeds: ["mongodb:27017"], 16 | pool: DBConnection.Poolboy 17 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi.ex: -------------------------------------------------------------------------------- 1 | defmodule Lrmi do 2 | @moduledoc """ 3 | Lrmi 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 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi/application.ex: -------------------------------------------------------------------------------- 1 | defmodule Lrmi.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 | def start(_type, _args) do 9 | # List all child processes to be supervised 10 | database_config = Application.get_env(:lrmi, :db_config) 11 | children = [ 12 | # Start the Ecto repository 13 | # Start the endpoint when the application starts 14 | LrmiWeb.Endpoint, 15 | %{ 16 | id: Mongo, 17 | start: { Mongo, :start_link, [database_config] } 18 | } 19 | ] 20 | 21 | # See https://hexdocs.pm/elixir/Supervisor.html 22 | # for other strategies and supported options 23 | opts = [strategy: :one_for_one, name: Lrmi.Supervisor] 24 | Supervisor.start_link(children, opts) 25 | end 26 | 27 | # Tell Phoenix to update the endpoint configuration 28 | # whenever the application is updated. 29 | def config_change(changed, _new, removed) do 30 | LrmiWeb.Endpoint.config_change(changed, removed) 31 | :ok 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi/helpers/atomic_map_extension.ex: -------------------------------------------------------------------------------- 1 | defmodule AtomicMap.Extension do 2 | import String, only: [first: 1, replace: 4, downcase: 1, upcase: 1] 3 | 4 | def keys_to_camel_case(value) when is_map(value) == false do 5 | value 6 | end 7 | 8 | def keys_to_camel_case(map) when is_map(map) do 9 | try do 10 | for {key, val} <- map, 11 | into: %{}, 12 | do: {camel_case(key), keys_to_camel_case(val)} 13 | rescue 14 | Protocol.UndefinedError -> map 15 | end 16 | end 17 | 18 | defp camel_case(key) when is_atom(key) do 19 | key = Atom.to_string(key) 20 | first_char = key |> first 21 | 22 | key 23 | |> Macro.camelize 24 | |> replace(upcase(first_char), downcase(first_char), global: false) 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi/repo.ex: -------------------------------------------------------------------------------- 1 | defmodule Lrmi.Repo do 2 | 3 | @mongo_query_limit Application.get_env(:content_proxy, :data_pagination)[:limit] 4 | @mongo_query_sort %{_id: 1} 5 | 6 | def insert(%Ecto.Changeset{} = changeset) do 7 | case Mongo.find_one_and_replace(:mongo, mongo_collection_name(changeset), %{_id: %{"$exists" => false}}, changeset.changes, [return_document: :after, upsert: true, pool: DBConnection.Poolboy]) do 8 | {:ok, content} -> 9 | {:ok, AtomicMap.convert(content, %{ignore: true})} 10 | {:error, error} -> 11 | {:error, error} 12 | end 13 | end 14 | 15 | def all(data_source, queryable) when is_atom(data_source) and is_map(queryable) do 16 | Mongo.find( 17 | :mongo, 18 | mongo_collection_name(data_source), 19 | queryable_to_mongo_query(queryable), 20 | limit: safe_limit(queryable), 21 | sort: Map.get(queryable, :sort, @mongo_query_sort), 22 | pool: DBConnection.Poolboy 23 | ) 24 | |> Enum.to_list 25 | |> AtomicMap.convert(%{ignore: true}) 26 | end 27 | 28 | defp mongo_collection_name(data_source) when is_atom(data_source) do 29 | struct(data_source) 30 | |> data_source.changeset(%{}) 31 | |> mongo_collection_name 32 | end 33 | 34 | defp mongo_collection_name(%Ecto.Changeset{} = changeset) do 35 | changeset.data.__meta__.source() 36 | end 37 | 38 | defp queryable_to_mongo_query(queryable) do 39 | mongo_query = 40 | queryable 41 | |> Map.drop([:first, :last, :before, :after, :limit, :offset, :sort]) 42 | |> AtomicMap.Extension.keys_to_camel_case 43 | |> format_is_property_id 44 | 45 | cond do 46 | Map.has_key?(queryable, :before) -> 47 | Map.put(mongo_query, "_id", %{"$lt": BSON.ObjectId.decode!(Map.get(queryable, :before))}) 48 | Map.has_key?(queryable, :after) -> 49 | Map.put(mongo_query, "_id", %{"$gt": BSON.ObjectId.decode!(Map.get(queryable, :after))}) 50 | true -> 51 | mongo_query 52 | end 53 | end 54 | 55 | defp format_is_property_id(mongo_query) do 56 | case Map.get(mongo_query, "identifier", {}) do 57 | {} -> 58 | mongo_query 59 | identifier -> 60 | mongo_query 61 | |> Map.drop(["identifier"]) 62 | |> Map.put("identifier.propertyID", Map.get(identifier,"propertyId")) 63 | |> Map.put("identifier.value", Map.get(identifier,"value")) 64 | 65 | end 66 | end 67 | 68 | defp safe_limit(queryable) do 69 | Map.get(queryable, :limit, @mongo_query_limit) 70 | |>( 71 | fn(limit) -> 72 | case limit do 73 | value when value > @mongo_query_limit -> 74 | @mongo_query_limit 75 | _ -> 76 | limit 77 | end 78 | end 79 | ).() 80 | end 81 | 82 | end 83 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi/resources/creative_work.ex: -------------------------------------------------------------------------------- 1 | defmodule Lrmi.Resources.CreativeWork do 2 | use Ecto.Schema 3 | import Ecto.Changeset 4 | alias Lrmi.Resources.CreativeWork 5 | 6 | 7 | schema "creative_works" do 8 | field :url, :string 9 | field :typeOf, :string 10 | field :name, :string 11 | field :description, :string 12 | field :educationalUse, :string 13 | field :interactivityType, :string 14 | field :learningResourceType, :string 15 | field :position, :integer 16 | field :dateCreated, :utc_datetime 17 | field :dateModified, :utc_datetime 18 | field :identifier, :map 19 | field :publisher, :map 20 | field :inLanguage, :map 21 | field :author, {:array, :map} 22 | field :additionalProperty, {:array, :map} 23 | field :educationalAlignment, {:array, :map} 24 | end 25 | 26 | def changeset(%CreativeWork{} = content, attrs) do 27 | content 28 | |> cast(attrs, [ 29 | :url, 30 | :name, 31 | :typeOf, 32 | :author, 33 | :position, 34 | :publisher, 35 | :inLanguage, 36 | :identifier, 37 | :description, 38 | :dateCreated, 39 | :dateModified, 40 | :educationalUse, 41 | :interactivityType, 42 | :additionalProperty, 43 | :educationalAlignment, 44 | :learningResourceType 45 | ]) 46 | |> validate_required([:typeOf, :name, :dateCreated, :dateModified, :identifier]) 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi/resources/resources.ex: -------------------------------------------------------------------------------- 1 | defmodule Lrmi.Resources do 2 | 3 | alias Lrmi.Repo 4 | alias Lrmi.Resources.CreativeWork 5 | 6 | 7 | def list_creative_works(%{} = query) do 8 | Repo.all(CreativeWork, query) 9 | end 10 | 11 | def create_creative_work(attrs \\ %{}) do 12 | changeset = CreativeWork.changeset(%CreativeWork{}, attrs) 13 | case changeset.valid? do 14 | true -> 15 | Repo.insert(changeset) 16 | false -> 17 | {:error, changeset} 18 | end 19 | end 20 | 21 | end 22 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi_graphql/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pierreabreup/graphqlapi-with-elixir-and-mongo/dd0cf52751e094c9622cc731b12dfa7cf8631522/lrmi/lib/lrmi_graphql/.DS_Store -------------------------------------------------------------------------------- /lrmi/lib/lrmi_graphql/helpers/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pierreabreup/graphqlapi-with-elixir-and-mongo/dd0cf52751e094c9622cc731b12dfa7cf8631522/lrmi/lib/lrmi_graphql/helpers/.DS_Store -------------------------------------------------------------------------------- /lrmi/lib/lrmi_graphql/helpers/absinthe/data_paginator.ex: -------------------------------------------------------------------------------- 1 | defmodule LrmiGraphql.Helpers.Absinthe.DataPaginator do 2 | 3 | def build_args(pagination_args) do 4 | pagination_args 5 | |> Map.put(:limit, limit(pagination_args) + 1) #increase 1 to detect if there is a next_page 6 | |> Map.put(:sort, sort(pagination_args)) 7 | |> Map.put(:offset, offset()) 8 | end 9 | 10 | def page_info(data_list, pagination_args) do 11 | limit = (pagination_args.limit - 1) #discount increased limit 12 | { 13 | Enum.take(data_list, limit), 14 | [ 15 | has_next_page: length(data_list) > limit, 16 | has_previous_page: Map.has_key?(pagination_args, :after) #THIS HAVE TO BE FIXED 17 | ] 18 | } 19 | end 20 | 21 | defp limit(pagination_args) do 22 | Map.get(pagination_args, :first, Map.get(pagination_args, :last)) 23 | end 24 | 25 | defp sort(pagination_args) do 26 | %{_id: Map.has_key?(pagination_args, :last) && - 1 || 1 } 27 | end 28 | 29 | defp offset() do 30 | 0 #FORCE TO 0 BECAUSE PAGINATION IS DONE BY CURSOR (object ID) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi_graphql/object_types/creative_work.ex: -------------------------------------------------------------------------------- 1 | defmodule LrmiGraphql.Schema.ObjectTypes.CreativeWork do 2 | use Absinthe.Schema.Notation 3 | use Absinthe.Relay.Schema, :modern 4 | 5 | import_types Absinthe.Type.Custom 6 | import_types LrmiGraphql.Schema.ObjectTypes.{ 7 | Person, 8 | Language, 9 | Publisher 10 | } 11 | 12 | alias LrmiGraphql.Resolvers 13 | 14 | 15 | object :creative_work do 16 | field :type_of, non_null(:string) 17 | field :name, non_null(:string) 18 | field :date_created, non_null(:datetime) 19 | field :date_modified, non_null(:datetime) 20 | field :learning_resource_type, non_null(:string) 21 | field :position, :integer 22 | field :description, :string 23 | field :educational_use, :string 24 | field :interactivity_type, :string 25 | field :url, :string 26 | field :author, list_of(:person) 27 | field :additional_property, list_of(:property_value) 28 | field :educational_alignment, list_of(:alignment_object) 29 | field :identifier, non_null(:property_value) 30 | field :publisher, :publisher 31 | field :in_language, :language 32 | end 33 | 34 | object :property_value do 35 | field :type_of, non_null(:string) 36 | field :property_id, non_null(:string) 37 | field :value, non_null(:string) 38 | end 39 | 40 | object :alignment_object do 41 | field :type_of, non_null(:string) 42 | field :alignment_type, non_null(:string) 43 | field :educational_framework, non_null(:string) 44 | field :target_name, non_null(:string) 45 | field :target_url, :string 46 | end 47 | 48 | input_object :identifier_input do 49 | field :property_id, non_null(:string) 50 | field :value, non_null(:string) 51 | end 52 | 53 | connection node_type: :creative_work 54 | 55 | object :creative_work_queries do 56 | connection field :creative_works, node_type: :creative_work do 57 | arg :type_of, :string 58 | arg :identifier, :identifier_input 59 | 60 | resolve fn 61 | args, _ -> 62 | pagination_limit = Application.get_env(:lrmi, :data_pagination)[:limit] 63 | cond do 64 | Map.fetch(args, :first) == :error && Map.fetch(args, :last) == :error -> 65 | {:error, "The argument \"first\" or the argument \"last\" must be present"} 66 | Map.get(args, :first, 0) > pagination_limit || Map.get(args, :last, 0) > pagination_limit -> 67 | {:error, "The first/last argument must be lower or equal than #{pagination_limit}" } 68 | true -> 69 | Resolvers.Resources.CreativeWork.paged_list(args) 70 | end 71 | 72 | end 73 | 74 | middleware &replace_relay_connection_cursors/2 75 | end 76 | end 77 | 78 | defp replace_relay_connection_cursors(resolution, _) do 79 | case resolution.value do 80 | nil -> 81 | resolution 82 | _ -> 83 | new_edges = edges_cursors_to_object_id(resolution.value.edges) 84 | page_info = page_info_cursors_to_object_id(new_edges, resolution.value.page_info) 85 | 86 | resolution_value = 87 | resolution.value 88 | |> Map.replace!(:edges, new_edges) 89 | |> Map.replace!(:page_info, page_info) 90 | 91 | 92 | %{resolution | value: resolution_value} 93 | end 94 | end 95 | 96 | defp edges_cursors_to_object_id(edges) do 97 | Enum.map( 98 | edges, 99 | fn edge -> %{edge | cursor: BSON.ObjectId.encode!(edge.node._id)} end 100 | ) 101 | end 102 | 103 | defp page_info_cursors_to_object_id(edges, page_info) when length(edges) == 0 do 104 | page_info 105 | end 106 | 107 | defp page_info_cursors_to_object_id(edges, page_info) do 108 | page_info 109 | |> Map.replace!(:start_cursor, Enum.at(edges, 0).cursor) 110 | |> Map.replace!(:end_cursor, Enum.at(edges, -1).cursor) 111 | end 112 | 113 | 114 | end 115 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi_graphql/object_types/language.ex: -------------------------------------------------------------------------------- 1 | defmodule LrmiGraphql.Schema.ObjectTypes.Language do 2 | use Absinthe.Schema.Notation 3 | 4 | object :language do 5 | field :typeOf, non_null(:string) 6 | field :name, non_null(:string) 7 | field :alternateName, non_null(:string) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi_graphql/object_types/person.ex: -------------------------------------------------------------------------------- 1 | defmodule LrmiGraphql.Schema.ObjectTypes.Person do 2 | use Absinthe.Schema.Notation 3 | 4 | object :person do 5 | field :type_of, non_null(:string) 6 | field :name, non_null(:string) 7 | field :url, :string 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi_graphql/object_types/publisher.ex: -------------------------------------------------------------------------------- 1 | defmodule LrmiGraphql.Schema.ObjectTypes.Publisher do 2 | use Absinthe.Schema.Notation 3 | 4 | object :publisher do 5 | field :type_of, non_null(:string) 6 | field :name, non_null(:string) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi_graphql/resolvers/resources/creative_work.ex: -------------------------------------------------------------------------------- 1 | defmodule LrmiGraphql.Resolvers.Resources.CreativeWork do 2 | alias Lrmi.Resources 3 | alias Absinthe.Relay 4 | alias LrmiGraphql.Helpers.Absinthe.DataPaginator 5 | 6 | def paged_list(args) do 7 | pagination_args = DataPaginator.build_args(args) 8 | contents = Resources.list_creative_works(pagination_args) 9 | {contents, page_info} = DataPaginator.page_info(contents, pagination_args) 10 | 11 | Relay.Connection.from_slice(contents, pagination_args.offset, page_info) 12 | end 13 | 14 | 15 | end 16 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi_graphql/schema.ex: -------------------------------------------------------------------------------- 1 | defmodule LrmiGraphql.Schema do 2 | use Absinthe.Schema 3 | 4 | import_types LrmiGraphql.Schema.ObjectTypes.CreativeWork 5 | 6 | 7 | query do 8 | import_fields :creative_work_queries 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi_web.ex: -------------------------------------------------------------------------------- 1 | defmodule LrmiWeb 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 LrmiWeb, :controller 9 | use LrmiWeb, :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: LrmiWeb 23 | 24 | import Plug.Conn 25 | import LrmiWeb.Gettext 26 | alias LrmiWeb.Router.Helpers, as: Routes 27 | end 28 | end 29 | 30 | def view do 31 | quote do 32 | use Phoenix.View, 33 | root: "lib/lrmi_web/templates", 34 | namespace: LrmiWeb 35 | 36 | # Import convenience functions from controllers 37 | import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1] 38 | 39 | import LrmiWeb.ErrorHelpers 40 | import LrmiWeb.Gettext 41 | alias LrmiWeb.Router.Helpers, as: Routes 42 | end 43 | end 44 | 45 | def router do 46 | quote do 47 | use Phoenix.Router 48 | import Plug.Conn 49 | import Phoenix.Controller 50 | end 51 | end 52 | 53 | def channel do 54 | quote do 55 | use Phoenix.Channel 56 | import LrmiWeb.Gettext 57 | end 58 | end 59 | 60 | @doc """ 61 | When used, dispatch to the appropriate controller/view/etc. 62 | """ 63 | defmacro __using__(which) when is_atom(which) do 64 | apply(__MODULE__, which, []) 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi_web/channels/user_socket.ex: -------------------------------------------------------------------------------- 1 | defmodule LrmiWeb.UserSocket do 2 | use Phoenix.Socket 3 | 4 | ## Channels 5 | # channel "room:*", LrmiWeb.RoomChannel 6 | 7 | # Socket params are passed from the client and can 8 | # be used to verify and authenticate a user. After 9 | # verification, you can put default assigns into 10 | # the socket that will be set for all channels, ie 11 | # 12 | # {:ok, assign(socket, :user_id, verified_user_id)} 13 | # 14 | # To deny connection, return `:error`. 15 | # 16 | # See `Phoenix.Token` documentation for examples in 17 | # performing token verification on connect. 18 | def connect(_params, socket, _connect_info) do 19 | {:ok, socket} 20 | end 21 | 22 | # Socket id's are topics that allow you to identify all sockets for a given user: 23 | # 24 | # def id(socket), do: "user_socket:#{socket.assigns.user_id}" 25 | # 26 | # Would allow you to broadcast a "disconnect" event and terminate 27 | # all active sockets and channels for a given user: 28 | # 29 | # LrmiWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) 30 | # 31 | # Returning `nil` makes this socket anonymous. 32 | def id(_socket), do: nil 33 | end 34 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi_web/endpoint.ex: -------------------------------------------------------------------------------- 1 | defmodule LrmiWeb.Endpoint do 2 | use Phoenix.Endpoint, otp_app: :lrmi 3 | 4 | 5 | # Code reloading can be explicitly enabled under the 6 | # :code_reloader configuration of your endpoint. 7 | if code_reloading? do 8 | plug Phoenix.CodeReloader 9 | end 10 | 11 | plug Plug.RequestId 12 | plug Plug.Logger 13 | 14 | plug Plug.Parsers, 15 | parsers: [:urlencoded, :multipart, :json], 16 | pass: ["*/*"], 17 | json_decoder: Jason 18 | 19 | plug Plug.MethodOverride 20 | plug Plug.Head 21 | 22 | # The session will be stored in the cookie and signed, 23 | # this means its contents can be read but not tampered with. 24 | # Set :encryption_salt if you would also like to encrypt it. 25 | plug Plug.Session, 26 | store: :cookie, 27 | key: "_lrmi_key", 28 | signing_salt: "T3cTH6S2" 29 | 30 | plug Absinthe.Plug, 31 | schema: LrmiGraphql.Schema, 32 | json_codec: Jason 33 | end 34 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi_web/gettext.ex: -------------------------------------------------------------------------------- 1 | defmodule LrmiWeb.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 LrmiWeb.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: :lrmi 24 | end 25 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi_web/views/error_helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule LrmiWeb.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(LrmiWeb.Gettext, "errors", msg, msg, count, opts) 29 | else 30 | Gettext.dgettext(LrmiWeb.Gettext, "errors", msg, opts) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lrmi/lib/lrmi_web/views/error_view.ex: -------------------------------------------------------------------------------- 1 | defmodule LrmiWeb.ErrorView do 2 | use LrmiWeb, :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 | -------------------------------------------------------------------------------- /lrmi/mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Lrmi.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :lrmi, 7 | version: "0.1.0", 8 | elixir: "~> 1.5", 9 | elixirc_paths: elixirc_paths(Mix.env()), 10 | compilers: [:phoenix, :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: {Lrmi.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.4.0"}, 37 | {:phoenix_ecto, "~> 4.0"}, 38 | {:poolboy, ">= 0.0.0"}, 39 | {:mongodb, "0.4.7"}, 40 | {:gettext, "~> 0.11"}, 41 | {:jason, "~> 1.0"}, 42 | {:plug_cowboy, "~> 2.0"}, 43 | {:absinthe, "~> 1.4"}, 44 | {:absinthe_plug, "~> 1.4"}, 45 | {:absinthe_relay, "~> 1.4"}, 46 | {:atomic_map, "~> 0.8"}, 47 | {:ex_machina, "~> 2.2", only: :test} 48 | ] 49 | end 50 | 51 | # Aliases are shortcuts or tasks specific to the current project. 52 | # For example, to create, migrate and run the seeds file at once: 53 | # 54 | # $ mix ecto.setup 55 | # 56 | # See the documentation for `Mix` for more info on aliases. 57 | defp aliases do 58 | [ 59 | "ecto.setup": ["run priv/repo/db_constraints.exs", "run priv/repo/seeds.exs"], 60 | seed: ["run priv/repo/seeds.exs"], 61 | test: ["test"] 62 | ] 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lrmi/mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "absinthe": {:hex, :absinthe, "1.4.16", "0933e4d9f12652b12115d5709c0293a1bf78a22578032e9ad0dad4efee6b9eb1", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, 3 | "absinthe_plug": {:hex, :absinthe_plug, "1.4.6", "ac5d2d3d02acf52fda0f151b294017ab06e2ed1c6c15334e06aac82c94e36e08", [:mix], [{:absinthe, "~> 1.4.11", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.2 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, 4 | "absinthe_relay": {:hex, :absinthe_relay, "1.4.6", "ec0e2288994b388556247cf9601245abec785cdf206d6e844f2992d29de21624", [:mix], [{:absinthe, "~> 1.4.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, 5 | "atomic_map": {:hex, :atomic_map, "0.9.3", "3c7f1302e0590164732d08ca999708efbb2cd768abf2911cf140280ce2dc499d", [:mix], [], "hexpm"}, 6 | "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, 7 | "cowboy": {:hex, :cowboy, "2.6.1", "f2e06f757c337b3b311f9437e6e072b678fcd71545a7b2865bdaa154d078593f", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, 8 | "cowlib": {:hex, :cowlib, "2.7.0", "3ef16e77562f9855a2605900cedb15c1462d76fb1be6a32fc3ae91973ee543d2", [:rebar3], [], "hexpm"}, 9 | "db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, 10 | "decimal": {:hex, :decimal, "1.7.0", "30d6b52c88541f9a66637359ddf85016df9eb266170d53105f02e4a67e00c5aa", [:mix], [], "hexpm"}, 11 | "ecto": {:hex, :ecto, "3.0.7", "44dda84ac6b17bbbdeb8ac5dfef08b7da253b37a453c34ab1a98de7f7e5fec7f", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"}, 12 | "ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"}, 13 | "gettext": {:hex, :gettext, "0.16.1", "e2130b25eebcbe02bb343b119a07ae2c7e28bd4b146c4a154da2ffb2b3507af2", [:mix], [], "hexpm"}, 14 | "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, 15 | "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, 16 | "mongodb": {:hex, :mongodb, "0.4.7", "7dd4e9f0dd7107fd3f0247f60b7acc21a6789ae0fafc698713b603a79dfee655", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, 17 | "phoenix": {:hex, :phoenix, "1.4.2", "3a1250f22010daeee265923bae02f10b5434b569b999c1b18100b5da05834d93", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"}, 18 | "phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, 19 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"}, 20 | "plug": {:hex, :plug, "1.7.2", "d7b7db7fbd755e8283b6c0a50be71ec0a3d67d9213d74422d9372effc8e87fd1", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"}, 21 | "plug_cowboy": {:hex, :plug_cowboy, "2.0.1", "d798f8ee5acc86b7d42dbe4450b8b0dadf665ce588236eb0a751a132417a980e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, 22 | "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, 23 | "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"}, 24 | "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, 25 | } 26 | -------------------------------------------------------------------------------- /lrmi/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 be %{count} character(s)" 54 | msgid_plural "should be %{count} character(s)" 55 | msgstr[0] "" 56 | msgstr[1] "" 57 | 58 | msgid "should have %{count} item(s)" 59 | msgid_plural "should have %{count} item(s)" 60 | msgstr[0] "" 61 | msgstr[1] "" 62 | 63 | msgid "should be at least %{count} character(s)" 64 | msgid_plural "should be at least %{count} character(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 most %{count} character(s)" 74 | msgid_plural "should be at most %{count} character(s)" 75 | msgstr[0] "" 76 | msgstr[1] "" 77 | 78 | msgid "should have at most %{count} item(s)" 79 | msgid_plural "should have at most %{count} item(s)" 80 | msgstr[0] "" 81 | msgstr[1] "" 82 | 83 | ## From Ecto.Changeset.validate_number/3 84 | msgid "must be less than %{number}" 85 | msgstr "" 86 | 87 | msgid "must be greater than %{number}" 88 | msgstr "" 89 | 90 | msgid "must be less than or equal to %{number}" 91 | msgstr "" 92 | 93 | msgid "must be greater than or equal to %{number}" 94 | msgstr "" 95 | 96 | msgid "must be equal to %{number}" 97 | msgstr "" 98 | -------------------------------------------------------------------------------- /lrmi/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 | -------------------------------------------------------------------------------- /lrmi/priv/repo/db_constraints.exs: -------------------------------------------------------------------------------- 1 | Mongo.command!(:mongo, [createIndexes: "creative_works", 2 | indexes: [ %{ key: %{ "identifier.propertyID": 1, "identifier.value": 1}, 3 | name: "identifier_unique_idx", 4 | unique: true} ] ], pool: DBConnection.Poolboy) 5 | 6 | Mix.shell.info("\nSET identifier FIELD AS UNIQUE !\n") 7 | -------------------------------------------------------------------------------- /lrmi/priv/repo/seeds.exs: -------------------------------------------------------------------------------- 1 | alias Lrmi.Resources 2 | 3 | seed_data = Application.app_dir(:lrmi, ["priv","seed_data","creative_work.json"]) 4 | |> File.read!() 5 | |> Jason.decode!() 6 | 7 | Enum.each seed_data, fn(content) -> 8 | case Resources.create_creative_work(content) do 9 | {:ok, saved_content} -> 10 | Mix.shell.info("DOCUMENT #{saved_content.name} INSERTED") 11 | {:error, error} -> 12 | Mix.shell.info("ERRROR ON #{content["name"]}") 13 | IO.inspect(error) 14 | end 15 | end 16 | 17 | Mix.shell.info("FINISH!") 18 | -------------------------------------------------------------------------------- /lrmi/priv/seed_data/creative_work.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "url": "http://my-content-host.test.com/content/112121", 4 | "typeOf": "CreativeWork", 5 | "dateCreated": "2017-08-24T13:58:33Z", 6 | "dateModified": "2019-01-02T12:22:07Z", 7 | "identifier": { 8 | "value": "112121", 9 | "typeOf": "PropertyValue", 10 | "propertyID": "ContentHost-ContentID" 11 | }, 12 | "educationalAlignment": [ 13 | { 14 | "typeOf": "AlignmentObject", 15 | "alignmentType": "educationLevel", 16 | "educationalFramework": "Diretrizes e Bases da Educação Nacional (LDB)", 17 | "targetName": "Elementary school 1", 18 | "targetURL": "http://portal.mec.gov.br/secretaria-de-educacao-basica" 19 | }, 20 | { 21 | "typeOf": "AlignmentObject", 22 | "alignmentType": "educationalSubject", 23 | "educationalFramework": "Diretrizes e Bases da Educação Nacional (LDB)", 24 | "targetName": "Mathematic", 25 | "targetURL": "http://portal.mec.gov.br/secretaria-de-educacao-basica" 26 | } 27 | ], 28 | "name": "My content about mathematic, elementary year 1", 29 | "interactivityType": "Expositive", 30 | "learningResourceType": "Didactic Material", 31 | "inLanguage": { 32 | "typeOf": "Language", 33 | "name": "Portuguese", 34 | "alternateName": "pt-br" 35 | }, 36 | "additionalProperty": [ 37 | { 38 | "typeOf": "PropertyValue", 39 | "propertyID": "Collection-Name", 40 | "value": "MyCollection" 41 | } 42 | ], 43 | "publisher": { 44 | "typeOf": "Organization", 45 | "name": "MyPublisher" 46 | }, 47 | "author": [ 48 | { 49 | "typeOf": "Person", 50 | "name": "Johon Smith", 51 | "url": "http://my-user-host.test.com/user/122" 52 | } 53 | ] 54 | }, 55 | { 56 | "url": "http://my-content-host.test.com/content/212121", 57 | "typeOf": "CreativeWork", 58 | "dateCreated": "2017-08-24T13:58:33Z", 59 | "dateModified": "2019-01-02T12:22:07Z", 60 | "identifier": { 61 | "value": "212121", 62 | "typeOf": "PropertyValue", 63 | "propertyID": "ContentHost-ContentID" 64 | }, 65 | "educationalAlignment": [ 66 | { 67 | "typeOf": "AlignmentObject", 68 | "alignmentType": "educationLevel", 69 | "educationalFramework": "Diretrizes e Bases da Educação Nacional (LDB)", 70 | "targetName": "Elementary school 2", 71 | "targetURL": "http://portal.mec.gov.br/secretaria-de-educacao-basica" 72 | }, 73 | { 74 | "typeOf": "AlignmentObject", 75 | "alignmentType": "educationalSubject", 76 | "educationalFramework": "Diretrizes e Bases da Educação Nacional (LDB)", 77 | "targetName": "History", 78 | "targetURL": "http://portal.mec.gov.br/secretaria-de-educacao-basica" 79 | } 80 | ], 81 | "name": "My content about History, elementary year 2", 82 | "interactivityType": "Expositive", 83 | "learningResourceType": "Didactic Material", 84 | "inLanguage": { 85 | "typeOf": "Language", 86 | "name": "Portuguese", 87 | "alternateName": "pt-br" 88 | }, 89 | "additionalProperty": [ 90 | { 91 | "typeOf": "PropertyValue", 92 | "propertyID": "Collection-Name", 93 | "value": "MyCollection" 94 | } 95 | ], 96 | "publisher": { 97 | "typeOf": "Organization", 98 | "name": "MyPublisher" 99 | }, 100 | "author": [ 101 | { 102 | "typeOf": "Person", 103 | "name": "Sara Smith", 104 | "url": "http://my-user-host.test.com/user/222" 105 | } 106 | ] 107 | } 108 | 109 | ] 110 | -------------------------------------------------------------------------------- /lrmi/test/lrmi/resources/creative_work_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Lrmi.Resources.CreativeWorkTest do 2 | use Lrmi.DataCase 3 | alias Lrmi.Resources.CreativeWork 4 | 5 | describe "Resources.CreativeWork" do 6 | test "typeOf is required" do 7 | changeset = CreativeWork.changeset(%CreativeWork{}, %{}) 8 | assert %{typeOf: ["can't be blank"]} = errors_on(changeset) 9 | end 10 | 11 | test "name is required" do 12 | changeset = CreativeWork.changeset(%CreativeWork{}, %{}) 13 | assert %{name: ["can't be blank"]} = errors_on(changeset) 14 | end 15 | 16 | test "dateCreated is required" do 17 | changeset = CreativeWork.changeset(%CreativeWork{}, %{}) 18 | assert %{dateCreated: ["can't be blank"]} = errors_on(changeset) 19 | end 20 | 21 | test "dateModified is required" do 22 | changeset = CreativeWork.changeset(%CreativeWork{}, %{}) 23 | assert %{dateModified: ["can't be blank"]} = errors_on(changeset) 24 | end 25 | 26 | test "identifier is required" do 27 | changeset = CreativeWork.changeset(%CreativeWork{}, %{}) 28 | assert %{identifier: ["can't be blank"]} = errors_on(changeset) 29 | end 30 | 31 | test "dateCreated must be ISO 8601 DateTime UTC" do 32 | content = build(:creative_work) 33 | |> Map.replace!(:dateCreated, "2019-01-23T18:00:38Z") 34 | 35 | changeset = CreativeWork.changeset(%CreativeWork{}, content) 36 | assert changeset.valid? 37 | end 38 | 39 | test "dateModified must be ISO 8601 DateTime UTC" do 40 | content = build(:creative_work) 41 | |> Map.replace!(:dateModified, "2019-01-23T18:00:38Z") 42 | 43 | changeset = CreativeWork.changeset(%CreativeWork{}, content) 44 | assert changeset.valid? 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lrmi/test/lrmi/resources/resources_test.exs: -------------------------------------------------------------------------------- 1 | defmodule Lrmi.ResourcesTest do 2 | use Lrmi.DataCase 3 | 4 | alias Lrmi.Resources 5 | 6 | describe "Resources" do 7 | 8 | def create_creative_work(attrs \\ %{}) do 9 | build(:creative_work, attrs) 10 | |> Resources.create_creative_work() 11 | end 12 | 13 | test "list_creative_works/1 with no args returns any content" do 14 | {_, content} = create_creative_work() 15 | 16 | assert Resources.list_creative_works(%{}) == [content] 17 | end 18 | 19 | test "list_creative_works/1 with 'typeOf' argument returns contents for specific typeOf" do 20 | attrs = %{typeOf: "chapter"} 21 | create_creative_work() 22 | {_, content} = create_creative_work(attrs) 23 | 24 | assert Resources.list_creative_works(attrs) == [content] 25 | end 26 | 27 | test "list_creative_works/1 with 'after' argument returns next content" do 28 | {_, content_one} = create_creative_work() 29 | {_, content_two} = create_creative_work() 30 | 31 | assert Resources.list_creative_works(%{after: BSON.ObjectId.encode!(content_one._id)}) == [content_two] 32 | end 33 | 34 | test "list_creative_works/1 with 'before' argument returns previous content" do 35 | {_, content_one} = create_creative_work() 36 | {_, content_two} = create_creative_work() 37 | 38 | assert Resources.list_creative_works(%{before: BSON.ObjectId.encode!(content_two._id)}) == [content_one] 39 | end 40 | 41 | test "create_creative_work/1 with valid data creates a content" do 42 | assert {:ok, %{} = content} = create_creative_work() 43 | end 44 | 45 | 46 | test "create_creative_work/1 with invalid data returns error changeset" do 47 | assert {:error, %Ecto.Changeset{}} = Resources.create_creative_work(%{}) 48 | end 49 | 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lrmi/test/lrmi_graphql/object_types/creative_work_test.exs: -------------------------------------------------------------------------------- 1 | defmodule LrmiGraphql.Schema.ObjectTypes.ContentTest do 2 | use ExUnit.Case 3 | 4 | alias LrmiGraphql.Schema.ObjectTypes.CreativeWork 5 | 6 | describe "Schema.ObjectTypes.CreativeWork" do 7 | test " __absinthe_types__ are valids" do 8 | 9 | types = %{ 10 | alignment_object: "AlignmentObject", 11 | creative_work: "CreativeWork", 12 | creative_work_connection: "CreativeWorkConnection", 13 | creative_work_edge: "CreativeWorkEdge", 14 | creative_work_queries: "CreativeWorkQueries", 15 | date: "Date", 16 | datetime: "DateTime", 17 | decimal: "Decimal", 18 | identifier_input: "IdentifierInput", 19 | language: "Language", 20 | naive_datetime: "NaiveDateTime", 21 | page_info: "PageInfo", 22 | person: "Person", 23 | property_value: "PropertyValue", 24 | publisher: "Publisher", 25 | time: "Time" 26 | } 27 | 28 | assert types == CreativeWork.__absinthe_types__() 29 | end 30 | 31 | test "creative work structure is valid" do 32 | types = [ 33 | additional_property: %Absinthe.Type.List{of_type: :property_value}, 34 | author: %Absinthe.Type.List{of_type: :person}, 35 | date_created: %Absinthe.Type.NonNull{of_type: :datetime}, 36 | date_modified: %Absinthe.Type.NonNull{of_type: :datetime}, 37 | description: :string, 38 | educational_alignment: %Absinthe.Type.List{of_type: :alignment_object}, 39 | educational_use: :string, 40 | identifier: %Absinthe.Type.NonNull{of_type: :property_value}, 41 | in_language: :language, 42 | interactivity_type: :string, 43 | learning_resource_type: %Absinthe.Type.NonNull{of_type: :string}, 44 | name: %Absinthe.Type.NonNull{of_type: :string}, 45 | position: :integer, 46 | publisher: :publisher, 47 | type_of: %Absinthe.Type.NonNull{of_type: :string}, 48 | url: :string 49 | ] 50 | 51 | 52 | assert types == Enum.map(CreativeWork.__absinthe_type__(:creative_work).fields, fn {k, v} -> {k, v.type} end) 53 | end 54 | 55 | test "alignment_object structure is valid" do 56 | types = [ 57 | alignment_type: %Absinthe.Type.NonNull{of_type: :string}, 58 | educational_framework: %Absinthe.Type.NonNull{of_type: :string}, 59 | target_name: %Absinthe.Type.NonNull{of_type: :string}, 60 | target_url: :string, 61 | type_of: %Absinthe.Type.NonNull{of_type: :string} 62 | ] 63 | 64 | assert types == Enum.map(CreativeWork.__absinthe_type__(:alignment_object).fields, fn {k, v} -> {k, v.type} end) 65 | end 66 | 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lrmi/test/lrmi_graphql/queries/creative_works_test.exs: -------------------------------------------------------------------------------- 1 | defmodule LrmiGraphql.Queries.CreativeWorksTest do 2 | use Lrmi.DataCase 3 | use LrmiWeb.ConnCase 4 | 5 | alias Lrmi.Resources 6 | alias LrmiGraphql.AbsintheHelpers 7 | 8 | describe "Queries.CreativeWorks" do 9 | 10 | def create_creative_work(attrs \\ %{}) do 11 | build(:creative_work, attrs) 12 | |> Resources.create_creative_work() 13 | end 14 | 15 | def graphql_request_creative_works(connection, query) do 16 | connection.conn 17 | |> post("/", AbsintheHelpers.query_skeleton(query, "creativeWorks")) 18 | end 19 | 20 | test "show error message when 'first' is out of range", connection do 21 | query = """ 22 | { 23 | creativeWorks(first: 2000) { 24 | edges{ 25 | node { 26 | name 27 | } 28 | } 29 | } 30 | } 31 | """ 32 | 33 | res = graphql_request_creative_works(connection, query) 34 | assert hd(json_response(res, 200)["errors"])["message"] == "The first/last argument must be lower or equal than 1000" 35 | end 36 | 37 | test "show error message when 'last' is out of range", connection do 38 | query = """ 39 | { 40 | creativeWorks(first: 2000) { 41 | edges{ 42 | node { 43 | name 44 | } 45 | } 46 | } 47 | } 48 | """ 49 | 50 | res = graphql_request_creative_works(connection, query) 51 | assert hd(json_response(res, 200)["errors"])["message"] == "The first/last argument must be lower or equal than 1000" 52 | end 53 | 54 | test "show error message when there is no 'first' and 'last' arguments", connection do 55 | query = """ 56 | { 57 | creativeWorks { 58 | edges{ 59 | node { 60 | name 61 | } 62 | } 63 | } 64 | } 65 | """ 66 | 67 | res = graphql_request_creative_works(connection, query) 68 | assert hd(json_response(res, 200)["errors"])["message"] == "The argument \"first\" or the argument \"last\" must be present" 69 | end 70 | 71 | test "show 'first:1' content", connection do 72 | {_, content} = create_creative_work() 73 | create_creative_work() 74 | 75 | query = """ 76 | { 77 | creativeWorks(first: 1) { 78 | edges{ 79 | node { 80 | name 81 | } 82 | } 83 | } 84 | } 85 | """ 86 | 87 | res = graphql_request_creative_works(connection, query) 88 | first_content = hd(json_response(res, 200)["data"]["creativeWorks"]["edges"]) 89 | 90 | assert length(json_response(res, 200)["data"]["creativeWorks"]["edges"]) == 1 && first_content["node"]["name"] == content.name 91 | end 92 | 93 | test "show 'last:1' content", connection do 94 | create_creative_work() 95 | {_, content} = create_creative_work() 96 | 97 | 98 | query = """ 99 | { 100 | creativeWorks(last: 1) { 101 | edges{ 102 | node { 103 | name 104 | } 105 | } 106 | } 107 | } 108 | """ 109 | 110 | res = graphql_request_creative_works(connection, query) 111 | 112 | last_content = hd(json_response(res, 200)["data"]["creativeWorks"]["edges"]) 113 | 114 | assert length(json_response(res, 200)["data"]["creativeWorks"]["edges"]) == 1 && last_content["node"]["name"] == content.name 115 | end 116 | 117 | test "cursor is a String of size 24", connection do 118 | create_creative_work() 119 | 120 | query = """ 121 | { 122 | creativeWorks(last: 1) { 123 | edges{ 124 | cursor 125 | } 126 | } 127 | } 128 | """ 129 | 130 | res = graphql_request_creative_works(connection, query) 131 | assert String.length(hd(json_response(res, 200)["data"]["creativeWorks"]["edges"])["cursor"]) == 24 132 | 133 | end 134 | 135 | end 136 | end 137 | -------------------------------------------------------------------------------- /lrmi/test/lrmi_web/views/error_view_test.exs: -------------------------------------------------------------------------------- 1 | defmodule LrmiWeb.ErrorViewTest do 2 | use LrmiWeb.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(LrmiWeb.ErrorView, "404.json", []) == %{errors: %{detail: "Not Found"}} 9 | end 10 | 11 | test "renders 500.json" do 12 | assert render(LrmiWeb.ErrorView, "500.json", []) == 13 | %{errors: %{detail: "Internal Server Error"}} 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lrmi/test/support/absinthe_helpers.ex: -------------------------------------------------------------------------------- 1 | defmodule LrmiGraphql.AbsintheHelpers do 2 | def query_skeleton(query, query_name) do 3 | %{ 4 | "operationName" => "#{query_name}", 5 | "query" => "query #{query_name} #{query}", 6 | "variables" => "{}" 7 | } 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lrmi/test/support/conn_case.ex: -------------------------------------------------------------------------------- 1 | defmodule LrmiWeb.ConnCase do 2 | 3 | use ExUnit.CaseTemplate 4 | 5 | using do 6 | quote do 7 | # Import conveniences for testing with connections 8 | use Phoenix.ConnTest 9 | alias LrmiWeb.Router.Helpers, as: Routes 10 | 11 | # The default endpoint for testing 12 | @endpoint LrmiWeb.Endpoint 13 | end 14 | end 15 | 16 | setup do 17 | conn = Phoenix.ConnTest.build_conn() 18 | |> Plug.Conn.put_req_header("content-type", "application/json") 19 | 20 | {:ok, %{conn: conn}} 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /lrmi/test/support/data_case.ex: -------------------------------------------------------------------------------- 1 | defmodule Lrmi.DataCase do 2 | 3 | use ExUnit.CaseTemplate 4 | 5 | using do 6 | quote do 7 | alias Lrmi.Repo 8 | 9 | import Ecto 10 | import Ecto.Changeset 11 | import Lrmi.DataCase 12 | import Lrmi.Factory 13 | end 14 | end 15 | 16 | setup tags do 17 | 18 | Mongo.delete_many(:mongo, "creative_works", %{}, pool: DBConnection.Poolboy) 19 | {:ok, []} 20 | end 21 | 22 | @doc """ 23 | A helper that transforms changeset errors into a map of messages. 24 | 25 | assert {:error, changeset} = Accounts.create_user(%{password: "short"}) 26 | assert "password is too short" in errors_on(changeset).password 27 | assert %{password: ["password is too short"]} = errors_on(changeset) 28 | 29 | """ 30 | def errors_on(changeset) do 31 | Ecto.Changeset.traverse_errors(changeset, fn {message, opts} -> 32 | Enum.reduce(opts, message, fn {key, value}, acc -> 33 | String.replace(acc, "%{#{key}}", to_string(value)) 34 | end) 35 | end) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lrmi/test/support/factories/creative_work.ex: -------------------------------------------------------------------------------- 1 | defmodule Lrmi.CreativeWorkFactory do 2 | defmacro __using__(_opts) do 3 | quote do 4 | def creative_work_factory do 5 | %{ 6 | typeOf: "Book", 7 | name: sequence("My content about mathematic, elementary year 1"), 8 | dateCreated: "2018-03-24T15:30:00Z", 9 | dateModified: "2018-04-01T09:10:00Z", 10 | author: [ 11 | %{ 12 | typeOf: "Person", 13 | name: "Johon Smith", 14 | url: "http://my-user-host.test.com/user/122" 15 | } 16 | ], 17 | additionalProperty: [ 18 | %{ 19 | typeOf: "PropertyValue", 20 | propertyID: "Collection-Name", 21 | value: "MyCollection" 22 | } 23 | ], 24 | identifier: %{ 25 | typeOf: "PropertyValue", 26 | propertyID: "ContentHost-ContentID", 27 | value: sequence("112121") 28 | }, 29 | publisher: %{ 30 | typeOf: "Organization", 31 | name: "MyPublisher" 32 | }, 33 | inLanguage: %{ 34 | typeOf: "Language", 35 | name: "Portuguese", 36 | alternateName: "pt-br" 37 | }, 38 | educationalUse: "Professional Support", 39 | interactivityType: "Expositive", 40 | learningResourceType: "Didactic Material", 41 | educationalAlignment: [ 42 | %{ 43 | typeOf: "AlignmentObject", 44 | alignmentType: "educationLevel", 45 | educationalFramework: "Diretrizes e Bases da Educação Nacional (LDB)", 46 | targetName: "Elementary school 1", 47 | targetURL: "http://portal.mec.gov.br/secretaria-de-educacao-basica" 48 | }, 49 | %{ 50 | typeOf: "AlignmentObject", 51 | alignmentType: "educationalSubject", 52 | educationalFramework: "Diretrizes e Bases da Educação Nacional (LDB)", 53 | targetName: "Mathematic", 54 | targetURL: "http://portal.mec.gov.br/secretaria-de-educacao-basica" 55 | }, 56 | ], 57 | url: "http://my-content-host.test.com/content/112121" 58 | } 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lrmi/test/support/factory.ex: -------------------------------------------------------------------------------- 1 | defmodule Lrmi.Factory do 2 | use ExMachina 3 | use Lrmi.CreativeWorkFactory 4 | end 5 | -------------------------------------------------------------------------------- /lrmi/test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------