license: MIT

deploy: brunch build --production

watch: brunch watch --stdin

repo_name: bye/ass

license: mit


code:
# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
# User-agent: *
# Disallow: /


code:
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
#
# This configuration file is loaded before any dependency and
# is restricted to this project.
use Mix.Config

# General application configuration
config :qtfile,
  ecto_repos: [Qtfile.Repo],
  token_secret_key_base: "REPLACE THIS"

# Configures the endpoint
config :qtfile, QtfileWeb.Endpoint,
  url: [host: "localhost"],
  secret_key_base: "REPLACE THIS",
  render_errors: [view: QtfileWeb.ErrorView, accepts: ~w(html json)],
  pubsub: [name: Qtfile.PubSub,
           adapter: Phoenix.PubSub.PG2]

# Configures Elixir's Logger
config :logger, :console,
  format: "$time $metadata[$level] $message\n",
  metadata: [:request_id]

config :arc,
  storage: Arc.Storage.Local,
  storage_dir: "uploads/rooms"

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env}.exs"


code:
use Mix.Config

# For development, we disable any cache and enable
# debugging and code reloading.
#
# The watchers configuration can be used to run external
# watchers to your application. For example, we use it
# with brunch.io to recompile .js and .css sources.
config :qtfile, QtfileWeb.Endpoint,
  http: [port: 4000],
  debug_errors: true,
  code_reloader: true,
  check_origin: false,
  watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin",
                    cd: Path.expand("../assets", __DIR__)]]

# ## SSL Support
#
# In order to use HTTPS in development, a self-signed
# certificate can be generated by running the following
# command from your terminal:
#
#     openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" -keyout priv/server.key -out priv/server.pem
#
# The `http:` config above can be replaced with:
#
#     https: [port: 4000, keyfile: "priv/server.key", certfile: "priv/server.pem"],
#
# If desired, both `http:` and `https:` keys can be
# configured to run both http and https servers on
# different ports.

# Watch static and templates for browser reloading.
config :qtfile, QtfileWeb.Endpoint,
  live_reload: [
    patterns: [
      ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
      ~r{priv/gettext/.*(po)$},
      ~r{lib/qtfile_web/views/.*(ex)$},
      ~r{lib/qtfile_web/templates/.*(eex)$}
    ]
  ]

# Do not include metadata nor timestamps in development logs
config :logger, :console, format: "[$level] $message\n"

# Set a higher stacktrace during development. Avoid configuring such
# in production as building large stacktraces may be expensive.
config :phoenix, :stacktrace_depth, 20

# Configure your database
config :qtfile, Qtfile.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: "postgres",
  password: "postgres",
  database: "qtfile_dev",
  hostname: "localhost",
  pool_size: 10


code:
use Mix.Config

# For production, we often load configuration from external
# sources, such as your system environment. For this reason,
# you won't find the :http configuration below, but set inside
# QtfileWeb.Endpoint.init/2 when load_from_system_env is
# true. Any dynamic configuration should be done there.
#
# Don't forget to configure the url host to something meaningful,
# Phoenix uses this information when generating URLs.
#
# Finally, we also include the path to a cache manifest
# containing the digested version of static files. This
# manifest is generated by the mix phx.digest task
# which you typically run after static files are built.
config :qtfile, QtfileWeb.Endpoint,
  load_from_system_env: true,
  url: [host: "example.com", port: 80],
  cache_static_manifest: "priv/static/cache_manifest.json"

# Do not print debug messages in production
config :logger, level: :info

# ## SSL Support
#
# To get SSL working, you will need to add the `https` key
# to the previous section and set your `:url` port to 443:
#
#     config :qtfile, QtfileWeb.Endpoint,
#       ...
#       url: [host: "example.com", port: 443],
#       https: [:inet6,
#               port: 443,
#               keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
#               certfile: System.get_env("SOME_APP_SSL_CERT_PATH")]
#
# Where those two env variables return an absolute path to
# the key and cert in disk or a relative path inside priv,
# for example "priv/ssl/server.key".
#
# We also recommend setting `force_ssl`, ensuring no data is
# ever sent via http, always redirecting to https:
#
#     config :qtfile, QtfileWeb.Endpoint,
#       force_ssl: [hsts: true]
#
# Check `Plug.SSL` for all available options in `force_ssl`.

# ## Using releases
#
# If you are doing OTP releases, you need to instruct Phoenix
# to start the server for all endpoints:
#
#     config :phoenix, :serve_endpoints, true
#
# Alternatively, you can configure exactly which server to
# start per endpoint:
#
#     config :qtfile, QtfileWeb.Endpoint, server: true
#

# Finally import the config/prod.secret.exs
# which should be versioned separately.
import_config "prod.secret.exs"


code:
use Mix.Config

# In this file, we keep production configuration that
# you'll likely want to automate and keep away from
# your version control system.
#
# You should document the content of this
# file or create a script for recreating it, since it's
# kept out of version control and might be hard to recover
# or recreate for your teammates (or yourself later on).
config :qtfile, QtfileWeb.Endpoint,
  secret_key_base: ""

# Configure your database
config :qtfile, Qtfile.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: "postgres",
  password: "postgres",
  database: "qtfile_prod",
  pool_size: 15


code:
use Mix.Config

# We don't run a server during test. If one is required,
# you can enable the server option below.
config :qtfile, QtfileWeb.Endpoint,
  http: [port: 4001],
  server: false

# Print only warnings and errors during test
config :logger, level: :warn

# Configure your database
config :qtfile, Qtfile.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: "postgres",
  password: "postgres",
  database: "qtfile_test",
  hostname: "localhost",
  pool: Ecto.Adapters.SQL.Sandbox


code:
defmodule Qtfile do
  @moduledoc """
  Qtfile keeps the contexts that define your domain
  and business logic.

  Contexts are also responsible for managing your data, regardless
  if it comes from the database, an external API or others.
  """
end


code:
defmodule Qtfile.Accounts do
  @moduledoc """
  The Accounts context.
  """

  import Ecto.Query, warn: false
  alias Qtfile.Repo

  alias Qtfile.Accounts.User
  alias Qtfile.SingleToken

  @doc """
  Returns the list of users.

  ## Examples

      iex> list_users()
      [%User{}, ...]

  """
  def list_users do
    Repo.all(User)
  end

  @doc """
  Gets a single user.

  Raises `Ecto.NoResultsError` if the User does not exist.

  ## Examples

      iex> get_user!(123)
      %User{}

      iex> get_user!(456)
      ** (Ecto.NoResultsError)

  """
  def get_user!(id), do: Repo.get!(User, id)

  def get_user_by_username(username) do
    query = from u in User,
      select: u,
      where: u.username == ^username

    Repo.one(query)
  end

  @doc """
  Creates a user.

  ## Examples

      iex> create_user(%{field: value})
      {:ok, %User{}}

      iex> create_user(%{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def create_user(attrs \\ %{}) do
    IO.inspect attrs
    case SingleToken.verify_token(Map.get(attrs, "token", "")) do
      true ->
        # attrs = Map.put(attrs, "password", Bcrypt.hash_pwd_salt(Map.get(attrs, "password")))
        attrs =
        if Mix.env == :prod do
          Map.put(attrs, "password", Bcrypt.hash_pwd_salt(Map.get(attrs, "password")))
        else
          Map.put(attrs, "password", Map.get(attrs, "password"))
        end

        attrs = Map.put(attrs, "role", "user")
        attrs = Map.put(attrs, "status", "active")

        changeset = %User{}
        |> User.changeset(attrs)
        # |> Ecto.Changeset.cast_assoc(:credential, with: &Credential.changeset/2)
        # |> Ecto.Changeset.cast_assoc(:token, with: &TokenHash.changeset/2)

        Repo.insert(changeset)
      false ->
        {:error, :invalid_token}
    end

  end

  @doc """
  Updates a user.

  ## Examples

      iex> update_user(user, %{field: new_value})
      {:ok, %User{}}

      iex> update_user(user, %{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def update_user(%User{} = user, attrs) do
    user
    |> User.changeset(attrs)
    |> Repo.update()
  end

  @doc """
  Deletes a User.

  ## Examples

      iex> delete_user(user)
      {:ok, %User{}}

      iex> delete_user(user)
      {:error, %Ecto.Changeset{}}

  """
  def delete_user(%User{} = user) do
    Repo.delete(user)
  end

  @doc """
  Returns an `%Ecto.Changeset{}` for tracking user changes.

  ## Examples

      iex> change_user(user)
      %Ecto.Changeset{source: %User{}}

  """
  def change_user(%User{} = user) do
    User.changeset(user, %{})
  end

  def authenticate_by_username_password(username, password) do
    username = String.downcase(username)

    query =
      from u in User,
      select: %{hashed_password: u.password, user: u},
      where: fragment("lower(?)", u.username) == type(^username, :string)

    case Repo.one(query) do
      %{hashed_password: hashed_password, user: user} ->
        authenticate_by_username_password_helper(%{hashed_password: hashed_password, user: user}, password)
      nil ->
        {:error, :unauthorized}
    end
  end

  defp authenticate_by_username_password_helper(%{hashed_password: hashed_password, user: user}, password) do
    if Mix.env == :prod do
      cond do
        Bcrypt.verify_pass(password, hashed_password) ->
          {:ok, user}
        true ->
          {:error, :unauthorized}
      end
    else
      cond do
        password == hashed_password ->
          {:ok, user}
        true ->
          {:error, :unauthorized}
      end
    end
  end
end


code:
defmodule Qtfile.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset
  alias Qtfile.Accounts.User

  schema "users" do
    field :name, :string
    field :password, :string
    field :username, :string
    # Available statuses:
    # active - not banned
    # banned - banned
    field :status, :string, default: "active"
    # Available roles:
    # admin - runs the site
    # mod - moderates the site
    # user - regular user, no special privs
    field :role, :string, default: "user"
    has_many :files, Qtfile.Files.File, on_delete: :delete_all

    timestamps()
  end

  @doc false
  def changeset(%User{} = user, attrs) do
    user
    |> cast(attrs, [:name, :username, :password, :status, :role])
    |> validate_required([:name, :username, :password, :status, :role])
    |> unique_constraint(:username)
  end
end


code:
defmodule Qtfile.Application do
  use Application

  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  def start(_type, _args) do
    import Supervisor.Spec

    # Define workers and child supervisors to be supervised
    children = [
      # Start the Ecto repository
      supervisor(Qtfile.Repo, []),
      # Start the endpoint when the application starts
      supervisor(QtfileWeb.Endpoint, []),
      # Start your own worker by calling: Qtfile.Worker.start_link(arg1, arg2, arg3)
      # worker(Qtfile.Worker, [arg1, arg2, arg3]),
    ]

    # See https://hexdocs.pm/elixir/Supervisor.html
    # for other strategies and supported options
    opts = [strategy: :one_for_one, name: Qtfile.Supervisor]
    Supervisor.start_link(children, opts)
  end

  # Tell Phoenix to update the endpoint configuration
  # whenever the application is updated.
  def config_change(changed, _new, removed) do
    QtfileWeb.Endpoint.config_change(changed, removed)
    :ok
  end
end


code:
defmodule Qtfile.Files.File do
  use Ecto.Schema
  import Ecto.Changeset
  alias Qtfile.Files.File

  schema "files" do
    field :filename, :string
    field :mime_type, :string
    field :uuid, :string
    belongs_to :rooms, Qtfile.Rooms.Room
    field :hash, :string
    field :size, :integer
    belongs_to :users, Qtfile.Accounts.User
    field :ip_address, :string
    field :expiration_date, :utc_datetime

    timestamps()
  end

  @doc false
  def changeset(%File{} = file, attrs) do
    file
    |> cast(attrs, [:uuid, :filename, :mime_type, :hash, :size, :ip_address, :expiration_date])
    |> put_assoc(:users, attrs.uploader)
    |> put_assoc(:rooms, attrs.room)
    |> validate_required([:uuid, :filename, :hash, :size, :ip_address, :expiration_date])
    |> unique_constraint(:uuid)
  end
end


code:
defmodule Qtfile.Files do
  @moduledoc """
  The Files context.
  """

  import Ecto.Query, warn: false
  require Logger
  alias Qtfile.Repo

  alias Qtfile.Files.File

  @doc """
  Returns the list of files.

  ## Examples

      iex> list_files()
      [%File{}, ...]

  """
  def list_files do
    Repo.all(File)
  end

  @doc """
  Gets a single file.

  Raises `Ecto.NoResultsError` if the File does not exist.

  ## Examples

      iex> get_file!(123)
      %File{}

      iex> get_file!(456)
      ** (Ecto.NoResultsError)

  """
  def get_file!(id), do: Repo.get!(File, id)

  def get_files_by_room_id(room_id) do
    query = from f in File,
      join: r in assoc(f, :rooms),
      where: r.room_id == ^room_id,
      join: u in assoc(f, :users),
      select: %{
        uploader: u.name,
        room_id: r.room_id,
        filename: f.filename,
        mime_type: f.mime_type,
        uuid: f.uuid,
        hash: f.hash,
        size: f.size,
        expiration_date: f.expiration_date
      },
      order_by: [asc: :expiration_date]

    query
    |> Repo.all
  end

  def get_file_by_uuid(uuid) do
    query = from f in File,
      where: f.uuid == ^uuid,
      preload: :users,
      preload: :rooms,
      select: f

    query
    |> Repo.one
  end

  @doc """
  Creates a file.

  ## Examples

      iex> create_file(%{field: value})
      {:ok, %File{}}

      iex> create_file(%{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def create_file(attrs \\ %{}) do
    %File{}
    |> File.changeset(attrs)
    |> Repo.insert()
  end

  def add_file(uuid, filename, room, hash, size, uploader, ip_address, expiration_date, mime_type) do
    result = create_file(%{uuid: uuid, filename: filename, mime_type: mime_type, room: room, hash: hash, size: size, uploader: uploader, ip_address: ip_address, expiration_date: expiration_date})
    case result do
      {:ok, _} ->
        QtfileWeb.RoomChannel.broadcast_new_files([%{filename: filename, hash: hash, uuid: uuid, size: size, uploader: uploader.name, expiration_date: expiration_date}], room.room_id)
        :ok
      {:error, changeset} ->
        Logger.error("failed to add file to db")
        Logger.error(inspect(changeset))
        :error
    end
  end

  @doc """
  Updates a file.

  ## Examples

      iex> update_file(file, %{field: new_value})
      {:ok, %File{}}

      iex> update_file(file, %{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def update_file(%File{} = file, attrs) do
    file
    |> File.changeset(attrs)
    |> Repo.update()
  end

  @doc """
  Deletes a File.

  ## Examples

      iex> delete_file(file)
      {:ok, %File{}}

      iex> delete_file(file)
      {:error, %Ecto.Changeset{}}

  """
  def delete_file(%File{} = file) do
    absolute_path = get_absolute_path(file)
    Repo.delete(file)
    Elixir.File.rm(absolute_path)
  end

  @doc """
  Returns an `%Ecto.Changeset{}` for tracking file changes.

  ## Examples

      iex> change_file(file)
      %Ecto.Changeset{source: %File{}}

  """
  def change_file(%File{} = file) do
    File.changeset(file, %{})
  end

  def get_absolute_path(file) do
    path = Application.get_env(:arc, :storage_dir, "uploads/rooms")
    path <> "/" <> file.rooms.room_id <> "/" <> file.uuid <> "-original" <> Path.extname(file.filename)
  end
end


code:
defmodule Qtfile.Repo do
  use Ecto.Repo, otp_app: :qtfile

  @doc """
  Dynamically loads the repository url from the
  DATABASE_URL environment variable.
  """
  def init(_, opts) do
    {:ok, Keyword.put(opts, :url, System.get_env("DATABASE_URL"))}
  end
end


code:
defmodule Qtfile.Rooms.Room do
  use Ecto.Schema
  import Ecto.Changeset
  alias Qtfile.Rooms.Room

  schema "rooms" do
    field :disabled, :boolean, default: false
    # Seconds
    # 259200 is 72 hours
    field :file_ttl, :integer, default: 259200
    has_many :files, Qtfile.Files.File, on_delete: :delete_all
    field :motd, :string
    field :owner, :string
    field :room_id, :string
    field :room_name, :string, default: "New Room"

    timestamps()
  end

  def default_room_name, do: "New Room"
  def default_files, do: []
  def default_motd, do: ""
  def default_file_ttl, do: 259200
  def default_disabled, do: false

  @doc false
  def changeset(%Room{} = room, attrs) do
    room
    |> cast(attrs, [:room_id, :room_name, :owner, :motd, :disabled, :file_ttl])
    |> validate_required([:room_id, :room_name, :owner, :disabled, :file_ttl])
    |> unique_constraint(:room_id)
  end
end


code:
defmodule Qtfile.Rooms do
  @moduledoc """
  The Rooms context.
  """

  import Ecto.Query, warn: false
  alias Qtfile.Repo

  alias Qtfile.Rooms.Room

  def generate_room_id() do
    generate_room_id(6)
  end

  def generate_room_id(length) do
    :crypto.strong_rand_bytes(length) |> Base.url_encode64(padding: false) |> String.slice(0, length)
  end

  @doc """
  Returns the list of rooms.

  ## Examples

      iex> list_rooms()
      [%Room{}, ...]

  """
  def list_rooms do
    Repo.all(Room)
  end

  def room_exists?(room_id) do
    not (get_room_by_room_id!(room_id) == nil)
  end

  @doc """
  Gets a single room.

  Raises `Ecto.NoResultsError` if the Room does not exist.

  ## Examples

      iex> get_room!(123)
      %Room{}

      iex> get_room!(456)
      ** (Ecto.NoResultsError)

  """
  def get_room!(id), do: Repo.get!(Room, id)

  def get_room_by_room_id!(room_id) do
    query = from r in Room,
      select: r,
      where: r.room_id == ^room_id

    Repo.one(query)
  end

  def get_files(room_id) do
    query = from r in Room,
      where: r.room_id == ^room_id,
      preload: [:files]
    Repo.one(query).files
  end

  @doc """
  Creates a room.

  ## Examples

      iex> create_room(%{field: value})
      {:ok, %Room{}}

      iex> create_room(%{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """

  def create_room(room_id, owner) do
    alias Qtfile.Rooms.Room

    create_room(
      %{
        "room_id" => room_id,
        "owner" => owner,
        "room_name" => Room.default_room_name(),
        "disabled" => Room.default_disabled(),
        "file_ttl" => Room.default_file_ttl(),
        "files" => Room.default_files(),
        "motd" => Room.default_motd(),
      }
    )
  end

  def create_room(attrs \\ %{}) do
    %Room{}
    |> Room.changeset(attrs)
    |> Repo.insert()
  end

  @doc """
  Updates a room.

  ## Examples

      iex> update_room(room, %{field: new_value})
      {:ok, %Room{}}

      iex> update_room(room, %{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def update_room(%Room{} = room, attrs) do
    room
    |> Room.changeset(attrs)
    |> Repo.update()
  end

  @doc """
  Deletes a Room.

  ## Examples

      iex> delete_room(room)
      {:ok, %Room{}}

      iex> delete_room(room)
      {:error, %Ecto.Changeset{}}

  """
  def delete_room(%Room{} = room) do
    Repo.delete(room)
  end

  @doc """
  Returns an `%Ecto.Changeset{}` for tracking room changes.

  ## Examples

      iex> change_room(room)
      %Ecto.Changeset{source: %Room{}}

  """
  def change_room(%Room{} = room) do
    Room.changeset(room, %{})
  end
end


code:
defmodule Qtfile.SingleToken do
  @moduledoc """
  The SingleToken context.
  """

  import Ecto.Query, warn: false
  alias Qtfile.Repo

  alias Qtfile.SingleToken.Token

  def generate_token() do
    generate_token(48)
  end

  def generate_token(length) do
    :crypto.strong_rand_bytes(length) |> Base.url_encode64(padding: false)
  end

  def verify_token(token) do
    query = from t in Token,
      select: t.token,
      where: t.token == ^token

    result = Repo.all(query) |> Enum.count

    result > 0
  end

  @doc """
  Returns the list of tokens.

  ## Examples

      iex> list_tokens()
      [%Token{}, ...]

  """
  def list_tokens do
    Repo.all(Token)
  end

  @doc """
  Gets a single token.

  Raises `Ecto.NoResultsError` if the Token does not exist.

  ## Examples

      iex> get_token!(123)
      %Token{}

      iex> get_token!(456)
      ** (Ecto.NoResultsError)

  """
  def get_token!(id), do: Repo.get!(Token, id)

  def get_token() do
    get_token(1)
  end

  def get_token(limit) do
    # query = from token in Token,
    #   select: token.token

    # stream = Repo.stream(query, [{:max_rows, 1}])

    # Repo.transaction(fn() ->
    #   Enum.to_list(stream)
    # end)

    query = from t in Token,
      select: %{token: t.token, id: t.id},
      limit: ^limit

    Repo.all(query)
  end

  @doc """
  Creates a token.

  ## Examples

      iex> create_token(%{field: value})
      {:ok, %Token{}}

      iex> create_token(%{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def create_token(attrs \\ %{}) do
    changeset = %{token: generate_token()}

    %Token{}
    |> Token.changeset(changeset)
    |> Repo.insert()

    Map.get(changeset, :token)
  end

  @doc """
  Updates a token.

  ## Examples

      iex> update_token(token, %{field: new_value})
      {:ok, %Token{}}

      iex> update_token(token, %{field: bad_value})
      {:error, %Ecto.Changeset{}}

  """
  def update_token(%Token{} = token, attrs) do
    token
    |> Token.changeset(attrs)
    |> Repo.update()
  end

  @doc """
  Deletes a Token.

  ## Examples

      iex> delete_token(token)
      {:ok, %Token{}}

      iex> delete_token(token)
      {:error, %Ecto.Changeset{}}

  """
  def delete_token(%Token{} = token) do
    Repo.delete(token)
  end

  def delete_token_by_hash(token) do
    query = from t in Token,
      select: %{token: t.token, id: t.id},
      where: t.token == ^token

    case Repo.all(query) do
      [%{token: token, id: id}] ->
        delete_token(%Token{token: token, id: id})
      _ ->
        {:error, "lmao"}
    end
  end

  @doc """
  Returns an `%Ecto.Changeset{}` for tracking token changes.

  ## Examples

      iex> change_token(token)
      %Ecto.Changeset{source: %Token{}}

  """
  def change_token(%Token{} = token) do
    Token.changeset(token, %{})
  end
end


code:
defmodule Qtfile.SingleToken.Token do
  use Ecto.Schema
  import Ecto.Changeset
  alias Qtfile.SingleToken.Token

  schema "tokens" do
    field :token, :string

    timestamps()
  end

  @doc false
  def changeset(%Token{} = token, attrs) do
    token
    |> cast(attrs, [:token])
    |> validate_required([:token])
    |> unique_constraint(:token)
  end
end


code:
defmodule Qtfile.Util do
  def hash(hash_type, file_path) do
    File.stream!(file_path, [], 2048)
    |> Enum.reduce(:crypto.hash_init(hash_type), fn(line, acc) ->
      :crypto.hash_update(acc,line)
    end)
    |> :crypto.hash_final
    |> Base.encode16(case: :lower)
  end

  def get_ip_address(%{remote_ip: {one, two, three, four}}) do
    "#{one}.#{two}.#{three}.#{four}"
  end
end


code:
defmodule QtfileWeb do
  @moduledoc """
  The entrypoint for defining your web interface, such
  as controllers, views, channels and so on.

  This can be used in your application as:

      use QtfileWeb, :controller
      use QtfileWeb, :view

  The definitions below will be executed for every view,
  controller, etc, so keep them short and clean, focused
  on imports, uses and aliases.

  Do NOT define functions inside the quoted expressions
  below. Instead, define any helper function in modules
  and import those modules here.
  """

  def controller do
    quote do
      use Phoenix.Controller, namespace: QtfileWeb
      import Plug.Conn
      import QtfileWeb.Router.Helpers
      import QtfileWeb.Gettext
    end
  end

  def view do
    quote do
      use Phoenix.View, root: "lib/qtfile_web/templates",
                        namespace: QtfileWeb

      # Import convenience functions from controllers
      import Phoenix.Controller, only: [get_flash: 2, view_module: 1]

      # Use all HTML functionality (forms, tags, etc)
      use Phoenix.HTML

      import QtfileWeb.Router.Helpers
      import QtfileWeb.ErrorHelpers
      import QtfileWeb.Gettext
    end
  end

  def router do
    quote do
      use Phoenix.Router
      import Plug.Conn
      import Phoenix.Controller
    end
  end

  def channel do
    quote do
      use Phoenix.Channel
      import QtfileWeb.Gettext
    end
  end

  @doc """
  When used, dispatch to the appropriate controller/view/etc.
  """
  defmacro __using__(which) when is_atom(which) do
    apply(__MODULE__, which, [])
  end
end


code:
defmodule QtfileWeb.RoomChannel do
  use Phoenix.Channel

  def join("room:" <> room_id, message, socket) do
    if Qtfile.Rooms.room_exists?(room_id) do
      send(self(), {:role, socket.assigns[:user].role})
      send(self(), {:after_join, room_id})
      {:ok, socket}
    else
      {:error, %{reason: "Room does not exist"}}
    end
  end

  def join(_topic, _message, socket) do
    {:error, %{reason: "Unavailable"}}
  end

  # def handle_in("broadcast_notification", %{"title" => title, "body" => body}, socket) do
  #   IO.inspect title
  #   IO.inspect body
  #   broadcast!(socket, "notification", %{title: title, body: body})
  #   {:noreply, socket}
  # end

  def handle_info({:after_join, room_id}, socket) do
    #:timer.apply_interval(300, __MODULE__, :increment, [socket])
    files = Qtfile.Files.get_files_by_room_id(room_id)
    push(socket, "files", %{body: files})
    {:noreply, socket}
  end

  def handle_info({:role, role}, socket) do
    push(socket, "role", %{body: role})
    {:noreply, socket}
  end

  def handle_info({:deleted, room_id, file_uuid}, socket) do
    QtfileWeb.Endpoint.broadcast_from!(self(), "room:" <> room_id, "deleted", %{body: file_uuid})
    {:noreply, socket}
  end

  def handle_in("delete", %{"files" => files}, socket) do
    results = Enum.map(files, fn(uuid) ->
      file = Qtfile.Files.get_file_by_uuid(uuid)
      if file != nil do
        Qtfile.Files.delete_file(file)
        broadcast_deleted_file(file)
        :ok
      else
        :error
      end
    end)
    {:reply, {:ok, %{results: results}}, socket}
  end

  def broadcast_new_files(files, room_id) do
    QtfileWeb.Endpoint.broadcast_from!(self(), "room:" <> room_id, "files", %{body: files})
  end

  def broadcast_deleted_file(file) do
    send(self(), {:deleted, file.rooms.room_id, file.uuid})
  end

  def increment(socket) do
    # number = Channelstest.Incrementer.increment()
    # IO.inspect number
    number = 1

    push(socket, "update", %{body: number})
  end
end


code:
defmodule QtfileWeb.UserSocket do
  use Phoenix.Socket

  ## Channels
  channel "room:*", QtfileWeb.RoomChannel

  ## Transports
  transport :websocket, Phoenix.Transports.WebSocket
  # transport :longpoll, Phoenix.Transports.LongPoll

  # Socket params are passed from the client and can
  # be used to verify and authenticate a user. After
  # verification, you can put default assigns into
  # the socket that will be set for all channels, ie
  #
  #     {:ok, assign(socket, :user_id, verified_user_id)}
  #
  # To deny connection, return `:error`.
  #
  # See `Phoenix.Token` documentation for examples in
  # performing token verification on connect.
  def connect(%{"token" => token}, socket) do
    case Phoenix.Token.verify(socket, Application.get_env(:qtfile, :token_secret_key_base), token, max_age: 31557600) do
      {:ok, user_id} ->
        socket = assign(socket, :user, Qtfile.Accounts.get_user!(user_id))
        {:ok, socket}
      {:error, _} ->
        :error
    end
  end

  def connect(_params, _socket) do
    :error
  end

  # Socket id's are topics that allow you to identify all sockets for a given user:
  #
  #     def id(socket), do: "user_socket:#{socket.assigns.user_id}"
  #
  # Would allow you to broadcast a "disconnect" event and terminate
  # all active sockets and channels for a given user:
  #
  #     QtfileWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
  #
  # Returning `nil` makes this socket anonymous.
  def id(_socket), do: nil
end


code:
defmodule QtfileWeb.FileController do
  use QtfileWeb, :controller
  @image_extensions ~w(.jpg .jpeg .png)
  @nice_mime_types ~w(text audio video image)

  def upload(conn, %{"file" => file} = params) when not is_list(file) do
    upload(conn, %{params | "file" => [file]})
  end

  def upload(conn, %{"room_id" => room_id, "file" => files} = params) do
    if Qtfile.Rooms.room_exists?(room_id) do
      upload_room_exists(conn, params)
    else
      json conn, %{success: false, error: "room does not exist", preventRetry: true}
    end
  end

  def upload(conn, _) do
    json conn, %{success: false, error: "failed to provide room id in request", preventRetry: true}
  end

  defp upload_room_exists(conn, %{"mime_type" => mime_type, "room_id" => room_id, "file" => files}) do
    response =
      Enum.map(files, fn(file) ->
        %{filename: filename} = file
        room = Qtfile.Rooms.get_room_by_room_id!(room_id)
        uuid = Ecto.UUID.generate()
        scope = %{room_id: room_id, uuid: uuid}

        cond do
          check_extension(filename, @image_extensions) == true ->
            Qtfile.ImageFile.store({file, scope})
            |> store_in_db(conn, uuid, room, mime_type, Qtfile.ImageFile.storage_dir(:original, {file, room}))

          true ->
            Qtfile.GenericFile.store({file, scope})
            |> store_in_db(conn, uuid, room, mime_type, Qtfile.GenericFile.storage_dir(:original, {file, room}))
        end
      end) |> hd()

    # conn
    # |> redirect(to: room_path(conn, :index, room_id))
    # json conn, %{success: true}
    json conn, response
  end

  def download(conn, %{"uuid" => uuid, "realfilename" => _realfilename}) do
    file = Qtfile.Files.get_file_by_uuid(uuid)
    path = Application.get_env(:arc, :storage_dir, "uploads/rooms")

    if file != nil do
      absolute_path = Qtfile.Files.get_absolute_path(file)

      mime_type = file.mime_type

      nice_file =
        case mime_type do
          nil -> false
          _ ->
            case String.split(mime_type, "/") do
              [type, _] -> Enum.member?(@nice_mime_types, type)
              _ -> false
            end
        end

      if nice_file do
        conn
        |> put_resp_content_type(mime_type)
        |> send_file(200, absolute_path)
      else
        conn
        |> put_resp_content_type("application/octet-stream")
        |> send_download({:file, absolute_path})
      end

    else
      conn
      |> put_status(404)
      |> text("file not found!")
    end
  end

  def download_no_filename(conn, %{"uuid" => uuid}) do
    download(conn, %{"uuid" => uuid, "realfilename" => ""})
  end

  def check_extension(filename, extensions) do
    file_extension = filename |> Path.extname |> String.downcase
    Enum.member?(extensions, file_extension)
  end

  defp store_in_db({:ok, filename}, conn, uuid, room, mime_type, path) do
    file_path = Path.absname(path <> "/" <> uuid <> "-original" <> Path.extname(filename))
    file_size =
      case File.stat(file_path) do
        {:ok, %{size: size}} ->
          size
        _ ->
          0
      end

    uploader_id = get_session(conn, :user_id)
    uploader = Qtfile.Accounts.get_user!(uploader_id)
    ip_address = Qtfile.Util.get_ip_address(conn)
    %{file_ttl: file_ttl} = room
    expiration_date = DateTime.from_unix!(DateTime.to_unix(DateTime.utc_now()) + file_ttl)

    hash = Qtfile.Util.hash(:sha, file_path)
    Qtfile.Files.add_file(uuid, filename, room, hash, file_size, uploader, ip_address, expiration_date, mime_type)

    %{success: true}
  end

  defp store_in_db(_ok_filename_tuple, _conn, _uuid, _room, _upload_date, _mime_type, _path) do
    %{success: false, error: "failed to upload file", preventRetry: true}
  end
end


code:
defmodule QtfileWeb.LoginController do
  use QtfileWeb, :controller

  def index(conn, _params) do
    render(conn, "index.html")
  end
end


code:
defmodule QtfileWeb.PageController do
  use QtfileWeb, :controller

  def index(conn, _params) do
    render conn, "index.html"
  end
end


code:
defmodule QtfileWeb.RoomController do
  use QtfileWeb, :controller

  def index(conn, %{"room_id" => room_id}) do
    if Qtfile.Rooms.room_exists?(room_id) do
      conn
      |> assign(:room_id, room_id)
      |> render "room.html"
    else
      text conn, "FAILED TO FUIND ROM FUCK OF"
    end
  end

  def create_room(conn, _params) do
    user = Qtfile.Accounts.get_user!(get_session(conn, :user_id))
    room_id = Qtfile.Rooms.generate_room_id()

    Qtfile.Rooms.create_room(room_id, user.name)

    redirect(conn, to: room_path(conn, :index, room_id))
  end

  def not_found(conn, params) do
    IO.inspect params
    text conn, "fuck off"
  end
end


code:
defmodule QtfileWeb.RoomsController do
  use QtfileWeb, :controller

  def index(conn, _params) do
    render(conn, "index.html")
  end
end


code:
defmodule QtfileWeb.UserController do
  use QtfileWeb, :controller
  alias Qtfile.Accounts
  alias Qtfile.Accounts.User

  def login(conn, %{"user" => %{"username" => username, "password" => password}}) do
    case Accounts.authenticate_by_username_password(username, password) do
      {:ok, user} ->
        conn
        |> put_session(:user_id, user.id)
        |> configure_session(renew: true)
        |> redirect(to: "/")
      {:error, :unauthorized} ->
        conn
        |> put_flash(:error, "Wrong username or password")
        |> redirect(to: user_path(conn, :login_page))
        |> halt()
    end
  end

  def register(conn, %{"user" => user_params}) do
    case Accounts.create_user(user_params) do
      {:error, :invalid_token} ->
        IO.inspect "FIRST ERROR"
        conn
        |> put_flash(:error, "Invalid registration token")
        |> redirect(to: user_path(conn, :register_page))
        |> halt()
      {:error, %Ecto.Changeset{} = changeset} ->
        IO.inspect changeset, label: "SECOND ERROR"
        conn
        |> put_flash(:error, "failed to register an account")
        |> redirect(to: "/")
        |> halt()
      {:ok, user} ->
        Qtfile.SingleToken.delete_token_by_hash(Map.get(user_params, "token", ""))

        conn
        |> put_session(:user_id, user.id)
        |> configure_session(renew: true)
        |> redirect(to: "/")
    end
  end

  def logout(conn, _params) do
    conn
    |> configure_session(drop: true)
    |> redirect(to: "/")
    |> halt()
  end

  def login_page(conn, _params) do
    render(conn, "login.html")
  end

  def register_page(conn, _params) do
    render(conn, "register.html")
  end
end


code:
defmodule QtfileWeb.Endpoint do
  use Phoenix.Endpoint, otp_app: :qtfile

  socket "/socket", QtfileWeb.UserSocket

  # Serve at "/" the static files from "priv/static" directory.
  #
  # You should set gzip to true if you are running phoenix.digest
  # when deploying your static files in production.
  plug Plug.Static,
    at: "/", from: :qtfile, gzip: false,
    only: ~w(css fonts images js favicon.ico robots.txt)

  # Code reloading can be explicitly enabled under the
  # :code_reloader configuration of your endpoint.
  if code_reloading? do
    socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
    plug Phoenix.LiveReloader
    plug Phoenix.CodeReloader
  end

  plug Plug.RequestId
  plug Plug.Logger

  plug Plug.Parsers,
    parsers: [:urlencoded, :multipart, :json],
    pass: ["*/*"],
    json_decoder: Poison,
    length: 4_000_000_000 # 4GB

  plug Plug.MethodOverride
  plug Plug.Head

  # The session will be stored in the cookie and signed,
  # this means its contents can be read but not tampered with.
  # Set :encryption_salt if you would also like to encrypt it.
  plug Plug.Session,
    store: :cookie,
    key: "_qtfile_key",
    signing_salt: "lCKOtTOh",
    max_age: 31557600,
    encryption_salt: (Application.get_env(:qtfile, QtfileWeb.Endpoint) |> Keyword.get(:secret_key_base))

  plug QtfileWeb.Router

  @doc """
  Callback invoked for dynamically configuring the endpoint.

  It receives the endpoint configuration and checks if
  configuration should be loaded from the system environment.
  """
  def init(_key, config) do
    if config[:load_from_system_env] do
      port = System.get_env("PORT") || raise "expected the PORT environment variable to be set"
      {:ok, Keyword.put(config, :http, [:inet6, port: port])}
    else
      {:ok, config}
    end
  end
end


code:
defmodule QtfileWeb.Gettext do
  @moduledoc """
  A module providing Internationalization with a gettext-based API.

  By using [Gettext](https://hexdocs.pm/gettext),
  your module gains a set of macros for translations, for example:

      import QtfileWeb.Gettext

      # Simple translation
      gettext "Here is the string to translate"

      # Plural translation
      ngettext "Here is the string to translate",
               "Here are the strings to translate",
               3

      # Domain-based translation
      dgettext "errors", "Here is the error message to translate"

  See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
  """
  use Gettext, otp_app: :qtfile
end


code:
defmodule QtfileWeb.Router do
  use QtfileWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
    plug :fetch_session
    plug :fetch_flash
  end

  scope "/", QtfileWeb do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
    get "/login", UserController, :login_page
    get "/register", UserController, :register_page

    scope "/" do
      pipe_through :logged_in?

      get "/new", RoomController, :create_room
      get "/rooms", RoomsController, :index
      get "/get/:uuid/:realfilename", FileController, :download
      get "/get/:uuid/", FileController, :download_no_filename
    end
  end

  scope "/r", QtfileWeb do
    pipe_through [:browser, :logged_in?]

    get "/", RoomController, :not_found
    get "/:room_id", RoomController, :index
  end

  scope "/api", QtfileWeb do
    pipe_through :api

    post "/login", UserController, :login
    post "/register", UserController, :register
    get "/logout", UserController, :logout

    scope "/" do
      pipe_through :logged_in?

      post "/upload", FileController, :upload
    end

    scope "/mod" do
      pipe_through [:logged_in?, :is_mod?]
    end
  end

  # Other scopes may use custom stacks.
  # scope "/api", QtfileWeb do
  #   pipe_through :api
  # end

  defp logged_in?(conn, _) do
    case get_session(conn, :user_id) do
      nil ->
        conn
        |> send_resp(:forbidden, "Not logged in")
        |> halt()
      user_id ->
        conn
    end
  end

  defp is_mod?(conn, _) do
    user = Qtfile.Accounts.get_user!(get_session(conn, :user_id))
    unless user.role == "mod" or user.role == "admin" do
      conn
      |> send_resp(:forbidden, "Insufficient privileges")
      |> halt()
    else
      conn
    end
  end
end


code:
<% user_id = Plug.Conn.get_session(@conn, :user_id) %>
<% logged_in? = user_id != nil %>

<!DOCTYPE html>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">
    <%= if logged_in? do %>
        <%= tag :meta, name: "channel_token", content: Phoenix.Token.sign(@conn, Application.get_env(:qtfile, :token_secret_key_base), user_id, max_age: 31557600), id: "channel_token" %>
    <% end %>

    <title><%= title() %></title>
    <link rel="stylesheet" href="<%= static_path(@conn, "/css/app.css") %>">
    <link rel="shortcut icon" type="image/png" href="/images/favicon.png" />

      <%= render QtfileWeb.SharedView, "header.html", Map.put(assigns, :header_elements,
          [
              %{text: "rooms", url: "/rooms", condition: fn -> logged_in? end},
              %{text: "new room", url: "/new", condition: fn -> logged_in? end},
              %{text: "profile", url: "/profile", condition: fn -> logged_in? end},
              %{text: "logout", url: "/api/logout", condition: fn -> logged_in? end},

              %{text: "login", url: "/login", condition: fn -> not logged_in? end},
              %{text: "register", url: "/register", condition: fn -> not logged_in? end},
          ]) %>

        <%= render @view_module, @view_template, assigns %>

<%= get_flash(@conn, :info) %>

<%= get_flash(@conn, :error) %>


code:
    upload a file!

Upload

Loading

    <template v-cloak v-if="mod">

        delete files!

Delete

    </template>

      <input v-model="filter" placeholder="filter files">

<template id="file-list-template">

    <template v-for="(upload, index) in uploads">

    </template>
    <template v-if="filter == ''">
      <template v-for="(file, index) in files">

      </template>
    </template>
    <template v-else>
      <template v-for="(file, index) in filteredFiles">

      </template>
    </template>

</template>

<template id="upload-template">

    {{ name }} - {{ progress }}%

</template>

<template id="file-template">

    {{ filename }}
    {{ uploader }}
    {{ formattedExpirationDate }}
    <template v-if="mod">

        <input type="checkbox" v-model="markedForDeletion">
        <template v-if="markedForDeletion">
          Unmark
        </template>
        <template v-else>
          Delete
        </template>

    </template>

</template>


code:
    id: <a href="<%= room_path(QtfileWeb.Endpoint, :index, @room_id) %>" class="room-id"><%= @room_id %>
    |
    name: <%= @room_name %>
    |
    owner: <%= @room_owner %>


code:
         qtfile
        <%= for %{text: text, url: url, condition: condition} <- @header_elements do %>
            <%= if condition.() do %>
                | <a class="nav-text" href="<%= url %>"><%= text %>
            <% end %>
        <%= end %>

    <%= if Mix.env == :dev do %>

THIS IS A DEV VERSION OF THE SITE. PLEASE DO NOT USE. PASSWORDS ARE SAVED IN PLAINTEXT IN DEV ENVIRONMENT

    <%= end %>


code:
<%= form_for @conn, user_path(@conn, :login), [method: :post, as: :user], fn(f) -> %>

        <%= text_input f, :username, placeholder: "username" %>

        <%= password_input f, :password, placeholder: "password" %>

        <%= submit "login" %>

<% end %>


code:
<%= form_for @conn, user_path(@conn, :register), [method: :post, as: :user], fn(f) -> %>

register now

        <%= text_input f, :name, id: "user_name", placeholder: "name" %> this is your display name

        <%= text_input f, :username, id: "user_username", placeholder: "username" %> this is what you use to login. other people do not see this

        <%= password_input f, :password, id: "user_password", placeholder: "password" %> use a secure password or get bent

        <%= password_input f, :token, id: "user_token", placeholder: "token" %> get a registration token from the site administrator

    <%= submit "register" %>

<% end %>


code:
defmodule Qtfile.GenericFile do
  use Arc.Definition
  use Arc.Ecto.Definition
  import Ecto
  def __storage, do: Arc.Storage.Local

  # Include ecto support (requires package arc_ecto installed):

  @versions [:original]

  # To add a thumbnail version:
  # @versions [:original, :thumb]

  # Whitelist file extensions:
  # def validate({file, _}) do
  #   ~w(.jpg .jpeg .gif .png) |> Enum.member?(Path.extname(file.file_name))
  # end

  # Define a thumbnail transformation:
  # def transform(:thumb, _) do
  #   {:convert, "-strip -thumbnail 250x250^ -gravity center -extent 250x250 -format png", :png}
  # end

  # Override the persisted filenames:
  def filename(version, {file, scope}) do
    # version
    "#{scope.uuid}-#{version}"
  end

  # Override the storage directory:
  # def storage_dir(version, {file, scope}) do
  #   "uploads/user/avatars/#{scope.id}"
  # end

  def storage_dir(version, {file, scope}) do
    "uploads/rooms/#{scope.room_id}"
  end

  # Provide a default URL if there hasn't been a file uploaded
  # def default_url(version, scope) do
  #   "/images/avatars/default_#{version}.png"
  # end

  # Specify custom headers for s3 objects
  # Available options are [:cache_control, :content_disposition,
  #    :content_encoding, :content_length, :content_type,
  #    :expect, :expires, :storage_class, :website_redirect_location]
  #
  # def s3_object_headers(version, {file, scope}) do
  #   [content_type: Plug.MIME.path(file.file_name)]
  # end
end


code:
defmodule Qtfile.ImageFile do
  use Arc.Definition
  use Arc.Ecto.Definition
  import Ecto
  def __storage, do: Arc.Storage.Local

  # Include ecto support (requires package arc_ecto installed):

  # @versions [:original]

  # To add a thumbnail version:
  @versions [:original, :thumb]

  # Whitelist file extensions:
  # def validate({file, _}) do
  #   ~w(.jpg .jpeg .gif .png) |> Enum.member?(Path.extname(file.file_name))
  # end

  # Define a thumbnail transformation:
  def transform(:thumb, _) do
    # windows is garbage jesus christ just purge it please
    {os, _} = :os.type()

    if os === :unix do
      {:convert, "-strip -limit area 10MB -limit disk 100MB -limit width 3840 -limit height 2160 -thumbnail 250x250^ -gravity center -extent 250x250 -format png", :png}
    else
      :noaction
    end
  end

  # Override the persisted filenames:
  def filename(version, {file, scope}) do
    # version
    "#{scope.uuid}-#{version}"
  end

  # Override the storage directory:
  def storage_dir(version, {file, scope}) do
    "uploads/rooms/#{scope.room_id}"
  end

  # Provide a default URL if there hasn't been a file uploaded
  # def default_url(version, scope) do
  #   "/images/avatars/default_#{version}.png"
  # end

  # Specify custom headers for s3 objects
  # Available options are [:cache_control, :content_disposition,
  #    :content_encoding, :content_length, :content_type,
  #    :expect, :expires, :storage_class, :website_redirect_location]
  #
  # def s3_object_headers(version, {file, scope}) do
  #   [content_type: Plug.MIME.path(file.file_name)]
  # end
end


code:
defmodule QtfileWeb.ErrorHelpers do
  @moduledoc """
  Conveniences for translating and building error messages.
  """

  use Phoenix.HTML

  @doc """
  Generates tag for inlined form input errors.
  """
  def error_tag(form, field) do
    Enum.map(Keyword.get_values(form.errors, field), fn (error) ->
      content_tag :span, translate_error(error), class: "help-block"
    end)
  end

  @doc """
  Translates an error message using gettext.
  """
  def translate_error({msg, opts}) do
    # Because error messages were defined within Ecto, we must
    # call the Gettext module passing our Gettext backend. We
    # also use the "errors" domain as translations are placed
    # in the errors.po file.
    # Ecto will pass the :count keyword if the error message is
    # meant to be pluralized.
    # On your own code and templates, depending on whether you
    # need the message to be pluralized or not, this could be
    # written simply as:
    #
    #     dngettext "errors", "1 file", "%{count} files", count
    #     dgettext "errors", "is invalid"
    #
    if count = opts[:count] do
      Gettext.dngettext(QtfileWeb.Gettext, "errors", msg, msg, count, opts)
    else
      Gettext.dgettext(QtfileWeb.Gettext, "errors", msg, opts)
    end
  end
end


code:
defmodule QtfileWeb.ErrorView do
  use QtfileWeb, :view

  def render("404.html", _assigns) do
    "Page not found"
  end

  def render("500.html", _assigns) do
    "Internal server error"
  end

  # In case no render clause matches or no
  # template is found, let's render it as 500
  def template_not_found(_template, assigns) do
    render "500.html", assigns
  end
end


code:
defmodule QtfileWeb.FileView do
  use QtfileWeb, :view
end


code:
defmodule QtfileWeb.LayoutView do
  use QtfileWeb, :view

  def title do
    "qtfile - Realtime temporary filesharing!"
  end
end


code:
defmodule QtfileWeb.PageView do
  use QtfileWeb, :view
end


code:
defmodule QtfileWeb.RoomView do
  use QtfileWeb, :view
end


code:
defmodule QtfileWeb.RoomsView do
  use QtfileWeb, :view

  def print_all_rooms do
    rooms = Qtfile.Rooms.list_rooms()

    for %{room_id: room_id, room_name: room_name, owner: owner} <- rooms do
      render(__MODULE__, "room_line.html", room_id: room_id, room_name: room_name, room_owner: owner)
    end
  end
end


code:
defmodule QtfileWeb.SharedView do
  use QtfileWeb, :view
end


code:
defmodule QtfileWeb.UserView do
  use QtfileWeb, :view
end


code:
defmodule Qtfile.Mixfile do
  use Mix.Project

  def project do
    [
      app: :qtfile,
      version: "0.0.1",
      elixir: "~> 1.4",
      elixirc_paths: elixirc_paths(Mix.env),
      compilers: [:phoenix, :gettext] ++ Mix.compilers,
      start_permanent: Mix.env == :prod,
      aliases: aliases(),
      deps: deps()
    ]
  end

  # Configuration for the OTP application.
  #
  # Type `mix help compile.app` for more information.
  def application do
    [
      mod: {Qtfile.Application, []},
      extra_applications: [:logger, :runtime_tools, :arc_ecto]
    ]
  end

  # Specifies which paths to compile per environment.
  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(_),     do: ["lib"]

  # Specifies your project dependencies.
  #
  # Type `mix help deps` for examples and options.
  defp deps do
    [
      {:phoenix, "~> 1.3.0"},
      {:phoenix_pubsub, "~> 1.0"},
      {:phoenix_ecto, "~> 3.2"},
      {:postgrex, ">= 0.0.0"},
      {:phoenix_html, "~> 2.10"},
      {:phoenix_live_reload, "~> 1.0", only: :dev},
      {:gettext, "~> 0.11"},
      {:cowboy, "~> 1.0"},
      {:arc, "~> 0.8.0"},
      {:arc_ecto, "~> 0.7.0"},
      {:uuid, "~> 1.1"},
      {:comeonin, "~> 4.0", only: :prod},
      {:bcrypt_elixir, "~> 1.0", only: :prod},
    ]
  end

  # Aliases are shortcuts or tasks specific to the current project.
  # For example, to create, migrate and run the seeds file at once:
  #
  #     $ mix ecto.setup
  #
  # See the documentation for `Mix` for more info on aliases.
  defp aliases do
    [
      "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
      "ecto.reset": ["ecto.drop", "ecto.setup"],
      "test": ["ecto.create --quiet", "ecto.migrate", "test"]
    ]
  end
end


code:
%{"arc": {:hex, :arc, "0.8.0", "bb7cf8ea50f30f9c2bb357270c074def42a7ec6a3b4605be731cc5faf8fde6fd", [:mix], [{:ex_aws, "~> 1.1", [hex: :ex_aws, optional: true]}, {:httpoison, "~> 0.11", [hex: :httpoison, optional: false]}, {:poison, "~> 2.2 or ~> 3.1", [hex: :poison, optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, optional: true]}]},
  "arc_ecto": {:hex, :arc_ecto, "0.7.0", "428ed7ea4ed0be8996d8fc6e9f651d3e26fe9c9063f26cbf6731c865369845d3", [:mix], [{:arc, "~> 0.8.0", [hex: :arc, optional: false]}, {:ecto, "~> 2.0", [hex: :ecto, optional: false]}]},
  "bcrypt_elixir": {:hex, :bcrypt_elixir, "1.0.5", "cca70b5c8d9a98a0151c2d2796c728719c9c4b3f8bd2de015758ef577ee5141e", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, optional: false]}]},
  "certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], []},
  "comeonin": {:hex, :comeonin, "4.0.3", "4e257dcb748ed1ca2651b7ba24fdbd1bd24efd12482accf8079141e3fda23a10", [:mix], [{:argon2_elixir, "~> 1.2", [hex: :argon2_elixir, optional: true]}, {:bcrypt_elixir, "~> 0.12.1 or ~> 1.0", [hex: :bcrypt_elixir, optional: true]}, {:pbkdf2_elixir, "~> 0.12", [hex: :pbkdf2_elixir, optional: true]}]},
  "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], []},
  "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, optional: false]}]},
  "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], []},
  "db_connection": {:hex, :db_connection, "1.1.2", "2865c2a4bae0714e2213a0ce60a1b12d76a6efba0c51fbda59c9ab8d1accc7a8", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, optional: true]}]},
  "decimal": {:hex, :decimal, "1.4.1", "ad9e501edf7322f122f7fc151cce7c2a0c9ada96f2b0155b8a09a795c2029770", [:mix], []},
  "ecto": {:hex, :ecto, "2.2.6", "3fd1067661d6d64851a0d4db9acd9e884c00d2d1aa41cc09da687226cf894661", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, optional: true]}]},
  "elixir_make": {:hex, :elixir_make, "0.4.0", "992f38fabe705bb45821a728f20914c554b276838433349d4f2341f7a687cddf", [:mix], []},
  "file_system": {:hex, :file_system, "0.2.2", "7f1e9de4746f4eb8a4ca8f2fbab582d84a4e40fa394cce7bfcb068b988625b06", [:mix], []},
  "gettext": {:hex, :gettext, "0.13.1", "5e0daf4e7636d771c4c71ad5f3f53ba09a9ae5c250e1ab9c42ba9edccc476263", [:mix], []},
  "hackney": {:hex, :hackney, "1.10.1", "c38d0ca52ea80254936a32c45bb7eb414e7a96a521b4ce76d00a69753b157f21", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, optional: false]}, {:idna, "5.1.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]},
  "httpoison": {:hex, :httpoison, "0.13.0", "bfaf44d9f133a6599886720f3937a7699466d23bb0cd7a88b6ba011f53c6f562", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, optional: false]}]},
  "idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, optional: false]}]},
  "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
  "mime": {:hex, :mime, "1.1.0", "01c1d6f4083d8aa5c7b8c246ade95139620ef8effb009edde934e0ec3b28090a", [:mix], []},
  "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []},
  "phoenix": {:hex, :phoenix, "1.3.0", "1c01124caa1b4a7af46f2050ff11b267baa3edb441b45dbf243e979cd4c5891b", [:mix], [{:cowboy, "~> 1.0", [hex: :cowboy, optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, optional: false]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, optional: false]}]},
  "phoenix_ecto": {:hex, :phoenix_ecto, "3.3.0", "702f6e164512853d29f9d20763493f2b3bcfcb44f118af2bc37bb95d0801b480", [:mix], [{:ecto, "~> 2.1", [hex: :ecto, optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, optional: true]}, {:plug, "~> 1.0", [hex: :plug, optional: false]}]},
  "phoenix_html": {:hex, :phoenix_html, "2.10.5", "4f9df6b0fb7422a9440a73182a566cb9cbe0e3ffe8884ef9337ccf284fc1ef0a", [:mix], [{:plug, "~> 1.0", [hex: :plug, optional: false]}]},
  "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.1.3", "1d178429fc8950b12457d09c6afec247bfe1fcb6f36209e18fbb0221bdfe4d41", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2 or ~> 1.3", [hex: :phoenix, optional: false]}]},
  "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.0.2", "bfa7fd52788b5eaa09cb51ff9fcad1d9edfeb68251add458523f839392f034c1", [:mix], []},
  "plug": {:hex, :plug, "1.4.3", "236d77ce7bf3e3a2668dc0d32a9b6f1f9b1f05361019946aae49874904be4aed", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1", [hex: :cowboy, optional: true]}, {:mime, "~> 1.0", [hex: :mime, optional: false]}]},
  "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []},
  "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], []},
  "postgrex": {:hex, :postgrex, "0.13.3", "c277cfb2a9c5034d445a722494c13359e361d344ef6f25d604c2353185682bfc", [:mix], [{:connection, "~> 1.0", [hex: :connection, optional: false]}, {:db_connection, "~> 1.1", [hex: :db_connection, optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, optional: false]}]},
  "ranch": {:hex, :ranch, "1.3.2", "e4965a144dc9fbe70e5c077c65e73c57165416a901bd02ea899cfd95aa890986", [:rebar3], []},
  "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []},
  "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], []},
  "uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], []}}


code:
## `msgid`s in this file come from POT (.pot) files.
##
## Do not add, change, or remove `msgid`s manually here as
## they're tied to the ones in the corresponding POT file
## (with the same domain).
##
## Use `mix gettext.extract --merge` or `mix gettext.merge`
## to merge POT files into PO files.
msgid ""
msgstr ""
"Language: en\n"

## From Ecto.Changeset.cast/4
msgid "can't be blank"
msgstr ""

## From Ecto.Changeset.unique_constraint/3
msgid "has already been taken"
msgstr ""

## From Ecto.Changeset.put_change/3
msgid "is invalid"
msgstr ""

## From Ecto.Changeset.validate_acceptance/3
msgid "must be accepted"
msgstr ""

## From Ecto.Changeset.validate_format/3
msgid "has invalid format"
msgstr ""

## From Ecto.Changeset.validate_subset/3
msgid "has an invalid entry"
msgstr ""

## From Ecto.Changeset.validate_exclusion/3
msgid "is reserved"
msgstr ""

## From Ecto.Changeset.validate_confirmation/3
msgid "does not match confirmation"
msgstr ""

## From Ecto.Changeset.no_assoc_constraint/3
msgid "is still associated with this entry"
msgstr ""

msgid "are still associated with this entry"
msgstr ""

## From Ecto.Changeset.validate_length/3
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] ""
msgstr[1] ""

msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] ""
msgstr[1] ""

msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] ""
msgstr[1] ""

msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] ""
msgstr[1] ""

msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] ""
msgstr[1] ""

msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] ""
msgstr[1] ""

## From Ecto.Changeset.validate_number/3
msgid "must be less than %{number}"
msgstr ""

msgid "must be greater than %{number}"
msgstr ""

msgid "must be less than or equal to %{number}"
msgstr ""

msgid "must be greater than or equal to %{number}"
msgstr ""

msgid "must be equal to %{number}"
msgstr ""


code:
## This file is a PO Template file.
##
## `msgid`s here are often extracted from source code.
## Add new translations manually only if they're dynamic
## translations that can't be statically extracted.
##
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here as no
## effect: edit them in PO (`.po`) files instead.

## From Ecto.Changeset.cast/4
msgid "can't be blank"
msgstr ""

## From Ecto.Changeset.unique_constraint/3
msgid "has already been taken"
msgstr ""

## From Ecto.Changeset.put_change/3
msgid "is invalid"
msgstr ""

## From Ecto.Changeset.validate_acceptance/3
msgid "must be accepted"
msgstr ""

## From Ecto.Changeset.validate_format/3
msgid "has invalid format"
msgstr ""

## From Ecto.Changeset.validate_subset/3
msgid "has an invalid entry"
msgstr ""

## From Ecto.Changeset.validate_exclusion/3
msgid "is reserved"
msgstr ""

## From Ecto.Changeset.validate_confirmation/3
msgid "does not match confirmation"
msgstr ""

## From Ecto.Changeset.no_assoc_constraint/3
msgid "is still associated with this entry"
msgstr ""

msgid "are still associated with this entry"
msgstr ""

## From Ecto.Changeset.validate_length/3
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] ""
msgstr[1] ""

msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] ""
msgstr[1] ""

msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] ""
msgstr[1] ""

msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] ""
msgstr[1] ""

msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] ""
msgstr[1] ""

msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] ""
msgstr[1] ""

## From Ecto.Changeset.validate_number/3
msgid "must be less than %{number}"
msgstr ""

msgid "must be greater than %{number}"
msgstr ""

msgid "must be less than or equal to %{number}"
msgstr ""

msgid "must be greater than or equal to %{number}"
msgstr ""

msgid "must be equal to %{number}"
msgstr ""


code:
defmodule Qtfile.Repo.Migrations.CreateUsers do
  use Ecto.Migration

  def change do
    create table(:users) do
      add :name, :string
      add :username, :string
      add :password, :string
      add :status, :string, default: "active"
      add :role, :string, default: "user"

      timestamps()
    end

    create unique_index(:users, [:username])
  end
end


code:
defmodule Qtfile.Repo.Migrations.CreateTokens do
  use Ecto.Migration

  def change do
    create table(:tokens) do
      add :token, :string

      timestamps()
    end

    create unique_index(:tokens, [:token])
  end
end


code:
defmodule Qtfile.Repo.Migrations.CreateRooms do
  use Ecto.Migration

  def change do
    create table(:rooms) do
      add :room_id, :string
      add :room_name, :string
      add :owner, :string
      add :files, {:array, :map}
      add :motd, :string
      add :disabled, :boolean, default: false, null: false
      add :file_ttl, :integer

      timestamps()
    end

    create unique_index(:rooms, [:room_id])
  end
end


code:
defmodule Qtfile.Repo.Migrations.CreateFiles do
  use Ecto.Migration

  def change do
    create table(:files) do
      add :uuid, :string
      add :filename, :string
      add :mime_type, :string
      add :rooms_id, references(:rooms)
      add :hash, :string
      add :size, :integer
      add :users_id, references(:users)
      add :ip_address, :string
      add :expiration_date, :utc_datetime

      timestamps()
    end

    create unique_index(:files, [:uuid])
    create index(:files, [:expiration_date])
    create index(:files, [:users_id])
    create index(:files, [:rooms_id])
  end
end


code:
# Script for populating the database. You can run it as:
#
#     mix run priv/repo/seeds.exs
#
# Inside the script, you can read and write to any of your
# repositories directly:
#
#     Qtfile.Repo.insert!(%Qtfile.SomeSchema{})
#
# We recommend using the bang functions (`insert!`, `update!`
# and so on) as they will fail if something goes wrong.


code:
defmodule Qtfile.AccountsTest do
  use Qtfile.DataCase

  alias Qtfile.Accounts

  describe "users" do
    alias Qtfile.Accounts.User

    @valid_attrs %{name: "some name", password: "some password", username: "some username"}
    @update_attrs %{name: "some updated name", password: "some updated password", username: "some updated username"}
    @invalid_attrs %{name: nil, password: nil, username: nil}

    def user_fixture(attrs \\ %{}) do
      {:ok, user} =
        attrs
        |> Enum.into(@valid_attrs)
        |> Accounts.create_user()

      user
    end

    test "list_users/0 returns all users" do
      user = user_fixture()
      assert Accounts.list_users() == [user]
    end

    test "get_user!/1 returns the user with given id" do
      user = user_fixture()
      assert Accounts.get_user!(user.id) == user
    end

    test "create_user/1 with valid data creates a user" do
      assert {:ok, %User{} = user} = Accounts.create_user(@valid_attrs)
      assert user.name == "some name"
      assert user.password == "some password"
      assert user.username == "some username"
    end

    test "create_user/1 with invalid data returns error changeset" do
      assert {:error, %Ecto.Changeset{}} = Accounts.create_user(@invalid_attrs)
    end

    test "update_user/2 with valid data updates the user" do
      user = user_fixture()
      assert {:ok, user} = Accounts.update_user(user, @update_attrs)
      assert %User{} = user
      assert user.name == "some updated name"
      assert user.password == "some updated password"
      assert user.username == "some updated username"
    end

    test "update_user/2 with invalid data returns error changeset" do
      user = user_fixture()
      assert {:error, %Ecto.Changeset{}} = Accounts.update_user(user, @invalid_attrs)
      assert user == Accounts.get_user!(user.id)
    end

    test "delete_user/1 deletes the user" do
      user = user_fixture()
      assert {:ok, %User{}} = Accounts.delete_user(user)
      assert_raise Ecto.NoResultsError, fn -> Accounts.get_user!(user.id) end
    end

    test "change_user/1 returns a user changeset" do
      user = user_fixture()
      assert %Ecto.Changeset{} = Accounts.change_user(user)
    end
  end
end


code:
defmodule Qtfile.FilesTest do
  use Qtfile.DataCase

  alias Qtfile.Files

  describe "files" do
    alias Qtfile.Files.File

    @valid_attrs %{filename: "some filename", uuid: "some uuid"}
    @update_attrs %{filename: "some updated filename", uuid: "some updated uuid"}
    @invalid_attrs %{filename: nil, uuid: nil}

    def file_fixture(attrs \\ %{}) do
      {:ok, file} =
        attrs
        |> Enum.into(@valid_attrs)
        |> Files.create_file()

      file
    end

    test "list_files/0 returns all files" do
      file = file_fixture()
      assert Files.list_files() == [file]
    end

    test "get_file!/1 returns the file with given id" do
      file = file_fixture()
      assert Files.get_file!(file.id) == file
    end

    test "create_file/1 with valid data creates a file" do
      assert {:ok, %File{} = file} = Files.create_file(@valid_attrs)
      assert file.filename == "some filename"
      assert file.uuid == "some uuid"
    end

    test "create_file/1 with invalid data returns error changeset" do
      assert {:error, %Ecto.Changeset{}} = Files.create_file(@invalid_attrs)
    end

    test "update_file/2 with valid data updates the file" do
      file = file_fixture()
      assert {:ok, file} = Files.update_file(file, @update_attrs)
      assert %File{} = file
      assert file.filename == "some updated filename"
      assert file.uuid == "some updated uuid"
    end

    test "update_file/2 with invalid data returns error changeset" do
      file = file_fixture()
      assert {:error, %Ecto.Changeset{}} = Files.update_file(file, @invalid_attrs)
      assert file == Files.get_file!(file.id)
    end

    test "delete_file/1 deletes the file" do
      file = file_fixture()
      assert {:ok, %File{}} = Files.delete_file(file)
      assert_raise Ecto.NoResultsError, fn -> Files.get_file!(file.id) end
    end

    test "change_file/1 returns a file changeset" do
      file = file_fixture()
      assert %Ecto.Changeset{} = Files.change_file(file)
    end
  end
end


code:
defmodule Qtfile.RoomsTest do
  use Qtfile.DataCase

  alias Qtfile.Rooms

  describe "rooms" do
    alias Qtfile.Rooms.Room

    @valid_attrs %{disabled: true, file_ttl: 42, files: "some files", motd: "some motd", owner: "some owner", room_id: "some room_id", room_name: "some room_name"}
    @update_attrs %{disabled: false, file_ttl: 43, files: "some updated files", motd: "some updated motd", owner: "some updated owner", room_id: "some updated room_id", room_name: "some updated room_name"}
    @invalid_attrs %{disabled: nil, file_ttl: nil, files: nil, motd: nil, owner: nil, room_id: nil, room_name: nil}

    def room_fixture(attrs \\ %{}) do
      {:ok, room} =
        attrs
        |> Enum.into(@valid_attrs)
        |> Rooms.create_room()

      room
    end

    test "list_rooms/0 returns all rooms" do
      room = room_fixture()
      assert Rooms.list_rooms() == [room]
    end

    test "get_room!/1 returns the room with given id" do
      room = room_fixture()
      assert Rooms.get_room!(room.id) == room
    end

    test "create_room/1 with valid data creates a room" do
      assert {:ok, %Room{} = room} = Rooms.create_room(@valid_attrs)
      assert room.disabled == true
      assert room.file_ttl == 42
      assert room.files == "some files"
      assert room.motd == "some motd"
      assert room.owner == "some owner"
      assert room.room_id == "some room_id"
      assert room.room_name == "some room_name"
    end

    test "create_room/1 with invalid data returns error changeset" do
      assert {:error, %Ecto.Changeset{}} = Rooms.create_room(@invalid_attrs)
    end

    test "update_room/2 with valid data updates the room" do
      room = room_fixture()
      assert {:ok, room} = Rooms.update_room(room, @update_attrs)
      assert %Room{} = room
      assert room.disabled == false
      assert room.file_ttl == 43
      assert room.files == "some updated files"
      assert room.motd == "some updated motd"
      assert room.owner == "some updated owner"
      assert room.room_id == "some updated room_id"
      assert room.room_name == "some updated room_name"
    end

    test "update_room/2 with invalid data returns error changeset" do
      room = room_fixture()
      assert {:error, %Ecto.Changeset{}} = Rooms.update_room(room, @invalid_attrs)
      assert room == Rooms.get_room!(room.id)
    end

    test "delete_room/1 deletes the room" do
      room = room_fixture()
      assert {:ok, %Room{}} = Rooms.delete_room(room)
      assert_raise Ecto.NoResultsError, fn -> Rooms.get_room!(room.id) end
    end

    test "change_room/1 returns a room changeset" do
      room = room_fixture()
      assert %Ecto.Changeset{} = Rooms.change_room(room)
    end
  end
end


code:
defmodule Qtfile.SingleTokenTest do
  use Qtfile.DataCase

  alias Qtfile.SingleToken

  describe "tokens" do
    alias Qtfile.SingleToken.Token

    @valid_attrs %{token: "some token"}
    @update_attrs %{token: "some updated token"}
    @invalid_attrs %{token: nil}

    def token_fixture(attrs \\ %{}) do
      {:ok, token} =
        attrs
        |> Enum.into(@valid_attrs)
        |> SingleToken.create_token()

      token
    end

    test "list_tokens/0 returns all tokens" do
      token = token_fixture()
      assert SingleToken.list_tokens() == [token]
    end

    test "get_token!/1 returns the token with given id" do
      token = token_fixture()
      assert SingleToken.get_token!(token.id) == token
    end

    test "create_token/1 with valid data creates a token" do
      assert {:ok, %Token{} = token} = SingleToken.create_token(@valid_attrs)
      assert token.token == "some token"
    end

    test "create_token/1 with invalid data returns error changeset" do
      assert {:error, %Ecto.Changeset{}} = SingleToken.create_token(@invalid_attrs)
    end

    test "update_token/2 with valid data updates the token" do
      token = token_fixture()
      assert {:ok, token} = SingleToken.update_token(token, @update_attrs)
      assert %Token{} = token
      assert token.token == "some updated token"
    end

    test "update_token/2 with invalid data returns error changeset" do
      token = token_fixture()
      assert {:error, %Ecto.Changeset{}} = SingleToken.update_token(token, @invalid_attrs)
      assert token == SingleToken.get_token!(token.id)
    end

    test "delete_token/1 deletes the token" do
      token = token_fixture()
      assert {:ok, %Token{}} = SingleToken.delete_token(token)
      assert_raise Ecto.NoResultsError, fn -> SingleToken.get_token!(token.id) end
    end

    test "change_token/1 returns a token changeset" do
      token = token_fixture()
      assert %Ecto.Changeset{} = SingleToken.change_token(token)
    end
  end
end


code:
defmodule QtfileWeb.PageControllerTest do
  use QtfileWeb.ConnCase

  test "GET /", %{conn: conn} do
    conn = get conn, "/"
    assert html_response(conn, 200) =~ "Welcome to Phoenix!"
  end
end


code:
defmodule QtfileWeb.ErrorViewTest do
  use QtfileWeb.ConnCase, async: true

  # Bring render/3 and render_to_string/3 for testing custom views
  import Phoenix.View

  test "renders 404.html" do
    assert render_to_string(QtfileWeb.ErrorView, "404.html", []) ==
           "Page not found"
  end

  test "render 500.html" do
    assert render_to_string(QtfileWeb.ErrorView, "500.html", []) ==
           "Internal server error"
  end

  test "render any other" do
    assert render_to_string(QtfileWeb.ErrorView, "505.html", []) ==
           "Internal server error"
  end
end


code:
defmodule QtfileWeb.LayoutViewTest do
  use QtfileWeb.ConnCase, async: true
end


code:
defmodule QtfileWeb.PageViewTest do
  use QtfileWeb.ConnCase, async: true
end


code:
defmodule QtfileWeb.ChannelCase do
  @moduledoc """
  This module defines the test case to be used by
  channel tests.

  Such tests rely on `Phoenix.ChannelTest` and also
  import other functionality to make it easier
  to build common datastructures and query the data layer.

  Finally, if the test case interacts with the database,
  it cannot be async. For this reason, every test runs
  inside a transaction which is reset at the beginning
  of the test unless the test case is marked as async.
  """

  use ExUnit.CaseTemplate

  using do
    quote do
      # Import conveniences for testing with channels
      use Phoenix.ChannelTest

      # The default endpoint for testing
      @endpoint QtfileWeb.Endpoint
    end
  end

  setup tags do
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Qtfile.Repo)
    unless tags[:async] do
      Ecto.Adapters.SQL.Sandbox.mode(Qtfile.Repo, {:shared, self()})
    end
    :ok
  end

end


code:
defmodule QtfileWeb.ConnCase do
  @moduledoc """
  This module defines the test case to be used by
  tests that require setting up a connection.

  Such tests rely on `Phoenix.ConnTest` and also
  import other functionality to make it easier
  to build common datastructures and query the data layer.

  Finally, if the test case interacts with the database,
  it cannot be async. For this reason, every test runs
  inside a transaction which is reset at the beginning
  of the test unless the test case is marked as async.
  """

  use ExUnit.CaseTemplate

  using do
    quote do
      # Import conveniences for testing with connections
      use Phoenix.ConnTest
      import QtfileWeb.Router.Helpers

      # The default endpoint for testing
      @endpoint QtfileWeb.Endpoint
    end
  end

  setup tags do
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Qtfile.Repo)
    unless tags[:async] do
      Ecto.Adapters.SQL.Sandbox.mode(Qtfile.Repo, {:shared, self()})
    end
    {:ok, conn: Phoenix.ConnTest.build_conn()}
  end

end


code:
defmodule Qtfile.DataCase do
  @moduledoc """
  This module defines the setup for tests requiring
  access to the application's data layer.

  You may define functions here to be used as helpers in
  your tests.

  Finally, if the test case interacts with the database,
  it cannot be async. For this reason, every test runs
  inside a transaction which is reset at the beginning
  of the test unless the test case is marked as async.
  """

  use ExUnit.CaseTemplate

  using do
    quote do
      alias Qtfile.Repo

      import Ecto
      import Ecto.Changeset
      import Ecto.Query
      import Qtfile.DataCase
    end
  end

  setup tags do
    :ok = Ecto.Adapters.SQL.Sandbox.checkout(Qtfile.Repo)

    unless tags[:async] do
      Ecto.Adapters.SQL.Sandbox.mode(Qtfile.Repo, {:shared, self()})
    end

    :ok
  end

  @doc """
  A helper that transform changeset errors to a map of messages.

      assert {:error, changeset} = Accounts.create_user(%{password: "short"})
      assert "password is too short" in errors_on(changeset).password
      assert %{password: ["password is too short"]} = errors_on(changeset)

  """
  def errors_on(changeset) do
    Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
      Enum.reduce(opts, message, fn {key, value}, acc ->
        String.replace(acc, "%{#{key}}", to_string(value))
      end)
    end)
  end
end


code:
ExUnit.start()

Ecto.Adapters.SQL.Sandbox.mode(Qtfile.Repo, :manual)


code:
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto

###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs     diff=csharp

###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln       merge=binary
#*.csproj    merge=binary
#*.vbproj    merge=binary
#*.vcxproj   merge=binary
#*.vcproj    merge=binary
#*.dbproj    merge=binary
#*.fsproj    merge=binary
#*.lsproj    merge=binary
#*.wixproj   merge=binary
#*.modelproj merge=binary
#*.sqlproj   merge=binary
#*.wwaproj   merge=binary

###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg   binary
#*.png   binary
#*.gif   binary

###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc   diff=astextplain
#*.DOC   diff=astextplain
#*.docx  diff=astextplain
#*.DOCX  diff=astextplain
#*.dot   diff=astextplain
#*.DOT   diff=astextplain
#*.pdf   diff=astextplain
#*.PDF   diff=astextplain
#*.rtf   diff=astextplain
#*.RTF   diff=astextplain

path: .gitattributes


code:
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.

# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
*.csproj.user

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs

# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/

# Visual Studio 2015 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/

# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*

# NUNIT
*.VisualState.xml
TestResult.xml

# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c

# DNX
project.lock.json
project.fragment.lock.json
artifacts/

*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc

# Chutzpah Test files
_Chutzpah*

# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb

# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap

# TFS 2012 Local Workspace
$tf/

# Guidance Automation Toolkit
*.gpState

# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user

# JustCode is a .NET coding add-in
.JustCode

# TeamCity is a build add-in
_TeamCity*

# DotCover is a Code Coverage Tool
*.dotCover

# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*

# MightyMoose
*.mm.*
AutoTest.Net/

# Web workbench (sass)
.sass-cache/

# Installshield output folder
[Ee]xpress/

# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html

# Click-Once directory
publish/

# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted
#*.pubxml
*.publishproj

# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/

# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files
*.nuget.props
*.nuget.targets

# Microsoft Azure Build Output
csx/
*.build.csdef

# Microsoft Azure Emulator
ecf/
rcf/

# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt

# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/

# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs

# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/

# RIA/Silverlight projects
Generated_Code/

# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm

# SQL Server files
*.mdf
*.ldf

# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings

# Microsoft Fakes
FakesAssemblies/

# GhostDoc plugin setting file
*.GhostDoc.xml

# Node.js Tools for Visual Studio
.ntvs_analysis.dat

# Visual Studio 6 build log
*.plg

# Visual Studio 6 workspace options file
*.opt

# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions

# Paket dependency manager
.paket/paket.exe
paket-files/

# FAKE - F# Make
.fake/

# JetBrains Rider
.idea/
*.sln.iml

# CodeRush
.cr/

# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
/Archived
/MyTo-Do.txt
/LL_post.txt
/creditsnew.txt
/Assemblies/UnityEngine.UI.dll
/Assemblies/UnityEngine.dll
/Assemblies/TextMeshPro-1.0.55.56.0b11.dll
/Assemblies/NVorbis.dll
/Assemblies/NAudio.dll
/Assemblies/HugsLib.dll
/Assemblies/Assembly-CSharp.dll
/Assemblies/Assembly-CSharp-firstpass.dll
/Assemblies/0Harmony.dll
/RimJobWorld.Main.sln
*.csproj
/.gitignore

path: .gitignore


code:
<?xml version="1.0" encoding="utf-8"?>

<ModMetaData>
	<name>RimJobWorld</name>
	<author>Ed86</author>
	<url>https://gitgud.io/Ed86/rjw</url>
	<supportedVersions>

1.0

	</supportedVersions>
	<description>
M for Mature

Load mod at bottom of mod list:
	Core
	HugsLib
	++Mods
	RimJobWorld
	RJW_Ex

Support dev: https://www.patreon.com/Ed86
Forum: https://www.loverslab.com/files/file/7257-rimjobworld/
Discord: https://discord.gg/CXwHhv8
	</description>
</ModMetaData>


code:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Manifest>
	<identifier>RimJobWorld</identifier>
	<version>3.2.2</version>
	<dependencies>

HugsLib

	</dependencies>
	<incompatibleWith />
	<loadAfter>

HugsLib

	</loadAfter>
	<suggests>
	</suggests>
	<manifestUri>https://gitgud.io/Ed86/rjw/raw/master/About/Manifest.xml</manifestUri>
	<downloadUri>https://gitgud.io/Ed86/rjw</downloadUri>
</Manifest>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>
	<BodyPartDef>
		<defName>Genitals</defName>
		genitals
		<hitPoints>12</hitPoints>
		<frostbiteVulnerability>0.01</frostbiteVulnerability>
		<skinCovered>true</skinCovered>
		<solid>false</solid>
		<canSuggestAmputation>false</canSuggestAmputation>
		<bleedRate>1.0</bleedRate>
		<tags>

RJW_FertilitySource

		</tags>
	</BodyPartDef>

	<BodyPartDef>
		<defName>Chest</defName>
		chest
		<hitPoints>24</hitPoints>
		<frostbiteVulnerability>0.01</frostbiteVulnerability>
		<skinCovered>true</skinCovered>
		<solid>false</solid>
		<canSuggestAmputation>false</canSuggestAmputation>
		<bleedRate>1.0</bleedRate>
	</BodyPartDef>

	<BodyPartDef>
		<defName>Anus</defName>
		anus
		<hitPoints>8</hitPoints>
		<frostbiteVulnerability>0.01</frostbiteVulnerability>
		<skinCovered>true</skinCovered>
		<solid>false</solid>
		<canSuggestAmputation>false</canSuggestAmputation>
		<bleedRate>1.0</bleedRate>
	</BodyPartDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>
	<BodyPartDef>
		<defName>MechGenitals</defName>
		mechanoid implanter
		<hitPoints>20</hitPoints>
		<skinCovered>true</skinCovered>
		<solid>true</solid>
		<canSuggestAmputation>false</canSuggestAmputation>
		<bleedRate>0</bleedRate>
		<frostbiteVulnerability>0.0</frostbiteVulnerability>
		<tags>

RJW_FertilitySource

		</tags>
	</BodyPartDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>
	<BodyPartTagDef>
		<defName>RJW_FertilitySource</defName>
		<label>fertility_source</label>
		<description>Neccessary for reproduction</description>
		<vital>false</vital>
	</BodyPartTagDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8"?>

<Defs>
	<ConceptDef>
		<defName>WhoreBeds</defName>
		Whore beds
		<priority>50</priority>
		<needsOpportunity>True</needsOpportunity>
		<helpText>Whores prefer to work in beds.\n\nThere are specific whore beds you can build for them.</helpText>
		<highlightTags>

MainTab-Architect-Closed

DesignationCategoryButton-Furniture-Closed

Designator-Build-WhoreBed

		</highlightTags>
	</ConceptDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>
<Defs>
	<DamageDef>
		<defName>ViralDamage</defName>
		<workerClass>DamageWorker_AddInjury</workerClass>
		<label>viral damage</label>
		<externalViolence>false</externalViolence>
		<deathMessage>{0} has died of a disease.</deathMessage>
		<hediff>VirusPerma</hediff>
		<hediffSkin>VirusPerma</hediffSkin>
		<hediffSolid>VirusPerma</hediffSolid>
		<makesBlood>false</makesBlood>
		<harmAllLayersUntilOutside>false</harmAllLayersUntilOutside>
	</DamageDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>
	<ThingDef ParentName="MakeableDrugBase">
		<defName>Aphrodisiac</defName>
		Aphrodisiac drug
		<description>A smokable drug made from humpshroom.</description>
		<graphicData>
			<texPath>Things/Item/Drug/Penoxycyline</texPath>
			<graphicClass>Graphic_StackCount</graphicClass>
		</graphicData>
		<rotatable>false</rotatable>
		<statBases>
			<WorkToMake>450</WorkToMake>
			<MarketValue>50</MarketValue>
			<Mass>0.05</Mass>
			<DeteriorationRate>6</DeteriorationRate>
			<Flammability>1.3</Flammability>
		</statBases>
		<techLevel>Medieval</techLevel>
		<ingestible>
			<foodType>Plant, Processed</foodType>
			<joyKind>Chemical</joyKind>
			<joy>0.80</joy>
			<baseIngestTicks>720</baseIngestTicks>
			<nurseable>true</nurseable>
			<drugCategory>Social</drugCategory>
			<ingestSound>Ingest_Smoke</ingestSound>
			<ingestEffect>Smoke_Joint</ingestEffect>
			<ingestEffectEat>EatVegetarian</ingestEffectEat>
			<ingestHoldOffsetStanding>
				<northDefault>
					<offset>(0.27,0,0.08)</offset>
					<behind>true</behind>
				</northDefault>
				<east>
					<offset>(0.45,0,0.08)</offset>
				</east>
				<south>
					<offset>(0.27,0,0.08)</offset>
				</south>
				<west>
					<offset>(-0.50,0,0.08)</offset>
					<flip>true</flip>
				</west>
			</ingestHoldOffsetStanding>
			<ingestHoldUsesTable>false</ingestHoldUsesTable>
			<ingestCommandString>Smoke {0}</ingestCommandString>
			<ingestReportString>Smoking {0}.</ingestReportString>
			<ingestReportStringEat>Consuming {0}.</ingestReportStringEat>
			<useEatingSpeedStat>false</useEatingSpeedStat>
			<outcomeDoers>

					<hediffDef>HumpShroomEffect</hediffDef>
					<severity>0.6</severity>
					<toleranceChemical>HumpShroom</toleranceChemical>

					<hediffDef>HumpShroomTolerance</hediffDef>
					<severity>0.042</severity>
					<divideByBodySize>true</divideByBodySize>

					<need>Sex</need>
					<offset>-0.5</offset>

			</outcomeDoers>
		</ingestible>
		<recipeMaker>
			<recipeUsers>

CraftingSpot

DrugLab

			</recipeUsers>
			<workSpeedStat>DrugCookingSpeed</workSpeedStat>
			<workSkill>Cooking</workSkill>
		</recipeMaker>
		<costList>
			<HumpShroom>4</HumpShroom>
		</costList>
		<comps>

				<chemical>HumpShroom</chemical>
				<addictiveness>0.030</addictiveness>
				<minToleranceToAddict>0.15</minToleranceToAddict>
				<existingAddictionSeverityOffset>0.1</existingAddictionSeverityOffset>
				<needLevelOffset>1</needLevelOffset>
				<listOrder>30</listOrder>

		</comps>
	</ThingDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>

	<ThingDef ParentName="MakeableDrugPillBase">
		<defName>RJW_Contraceptive</defName>
		Contraception pills
		<description>Deliver severe hit to fertility for one quadrum.</description>
		<graphicData>
			<texPath>Things/Item/Contraceptive</texPath>
			<graphicClass>Graphic_StackCount</graphicClass>
		</graphicData>
		<rotatable>false</rotatable>
		<statBases>
			<WorkToMake>120</WorkToMake>
			<MarketValue>15</MarketValue>
			<Mass>0.05</Mass>
		</statBases>
		<techLevel>Industrial</techLevel>
		<ingestible>
			<drugCategory>Medical</drugCategory>
			<outcomeDoers>

					<hediffDef>RJW_Contraceptive</hediffDef>
					<severity>1.0</severity>

			</outcomeDoers>
		</ingestible>
		<recipeMaker>
			<researchPrerequisite>DrugProduction</researchPrerequisite>
			<recipeUsers>

DrugLab

			</recipeUsers>
		</recipeMaker>
		<costList>
			<MedicineHerbal>1</MedicineHerbal>
		</costList>
		<comps>

			<addictiveness>0</addictiveness>
			<listOrder>1000</listOrder>
				<overdoseSeverityOffset>
					<min>0.08</min>
					<max>0.14</max>
				</overdoseSeverityOffset>

		</comps>
	</ThingDef>

	<HediffDef>
		<defName>RJW_Contraceptive</defName>
		<hediffClass>HediffWithComps</hediffClass>
		contraceptive
		<defaultLabelColor>(0.75, 0.75, 1.0)</defaultLabelColor>
		<isBad>false</isBad>
		<comps>

				<disappearsAfterTicks>
					<min>900000</min>
					<max>900000</max>
				</disappearsAfterTicks>

		</comps>
		<stages>

				Contracepted
				<capMods>

						<capacity>Reproduction</capacity>
						<setMax>0.1</setMax>

				</capMods>

		</stages>
	</HediffDef>
</Defs>


code:
<?xml version="1.0" encoding="utf-8" ?>

<Defs>

	<ThingDef ParentName="MakeableDrugPillBase">
		<defName>RJW_FertPill</defName>
		Hyperfertility pills
		<description>Enhances fertility for 48 hours. Also works as a mild aphrodisiac.</description>
		<graphicData>
			<texPath>Things/Item/FertPill</texPath>
			<graphicClass>Graphic_StackCount</graphicClass>
		</graphicData>
		<rotatable>false</rotatable>
		<statBases>
			<WorkToMake>120</WorkToMake>
			<MarketValue>15</MarketValue>
			<Mass>0.05</Mass>
		</statBases>
		<techLevel>Industrial</techLevel>
		<ingestible>
			<drugCategory>Medical</drugCategory>
			<outcomeDoers>

					<hediffDef>RJW_FertPill</hediffDef>
					<severity>1.0</severity>

					<need>Sex</need>
					<offset>-0.25</offset>

			</outcomeDoers>
		</ingestible>
		<recipeMaker>
			<researchPrerequisite>DrugProduction</researchPrerequisite>
			<recipeUsers>

DrugLab

			</recipeUsers>
		</recipeMaker>
		<costList>
			<MedicineHerbal>1</MedicineHerbal>
			<HumpShroom>1</HumpShroom>
		</costList>
		<comps>

			<addictiveness>0</addictiveness>
			<listOrder>1000</listOrder>
				<overdoseSeverityOffset>
					<min>0.08</min>
					<max>0.14</max>
				</overdoseSeverityOffset>

		</comps>
	</ThingDef>

	<HediffDef>
		<defName>RJW_FertPill</defName>
		<hediffClass>HediffWithComps</hediffClass>
		fertpill
		<defaultLabelColor>(0.75, 0.75, 1.0)</defaultLabelColor>
		<isBad>false</isBad>
		<comps>

				<disappearsAfterTicks>
					<min>120000</min>
					<max>120000</max>
				</disappearsAfterTicks>

		</comps>
		<stages>

				Hyperfertility
				<capMods>

						<capacity>Reproduction</capacity>
						<offset>4</offset>

				</capMods>

		</stages>
	</HediffDef>
</Defs>