Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add database of shared links #1098

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .tool-versions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't commit this as this is a local development setup, thanks!

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
postgres 14.10
elixir 1.13.3
erlang 25.3.2
111 changes: 111 additions & 0 deletions lib/cadet/shared_programs.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
defmodule Cadet.SharedPrograms do
@moduledoc """
The SharedPrograms context.
"""

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

alias Cadet.SharedPrograms.SharedProgram

@doc """
Returns the list of shared_programs.

## Examples

iex> list_shared_programs()
[%SharedProgram{}, ...]

"""
def list_shared_programs do
Repo.all(SharedProgram)
end

@doc """
Gets a single shared_program.

Raises `Ecto.NoResultsError` if the Shared program does not exist.

## Examples

iex> get_shared_program!(123)
%SharedProgram{}

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

"""
def get_shared_program!(id), do: Repo.get!(SharedProgram, id)

def get_shared_program_by_uuid!(uuid) do
case Repo.get_by(SharedProgram, uuid: uuid) do
nil -> raise "SharedProgram not found for UUID #{uuid}"
shared_program -> shared_program
end
end

@doc """
Creates a shared_program.

## Examples

iex> create_shared_program(%{field: value})
{:ok, %SharedProgram{}}

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

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

@doc """
Updates a shared_program.

## Examples

iex> update_shared_program(shared_program, %{field: new_value})
{:ok, %SharedProgram{}}

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

"""
def update_shared_program(%SharedProgram{} = shared_program, attrs) do
shared_program
|> SharedProgram.changeset(attrs)
|> Repo.update()
end

@doc """
Deletes a shared_program.

## Examples

iex> delete_shared_program(shared_program)
{:ok, %SharedProgram{}}

iex> delete_shared_program(shared_program)
{:error, %Ecto.Changeset{}}

"""
def delete_shared_program(%SharedProgram{} = shared_program) do
Repo.delete(shared_program)
end

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

## Examples

iex> change_shared_program(shared_program)
%Ecto.Changeset{data: %SharedProgram{}}

"""
def change_shared_program(%SharedProgram{} = shared_program, attrs \\ %{}) do
SharedProgram.changeset(shared_program, attrs)
end
end
36 changes: 36 additions & 0 deletions lib/cadet/shared_programs/shared_program.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
defmodule Cadet.SharedPrograms.SharedProgram do
@moduledoc """
Contains methods for storing frontend programs to database with uuid.
"""
use Ecto.Schema
import Ecto.Changeset

schema "shared_programs" do
field(:data, :map)
field(:uuid, Ecto.UUID)

timestamps()
end

@doc false
def changeset(shared_program, attrs) do
shared_program
Comment on lines +8 to +17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing definitions for required and optional fields (take a look at the other models in the codebase to see how they're implemented).

|> cast(attrs, [:data])
|> generate_uuid_if_nil()
|> validate_required([:uuid])
end

defp generate_uuid_if_nil(changeset) do
if get_change(changeset, :uuid) do
changeset
else
put_change(changeset, :uuid, Ecto.UUID.generate())
end
end

defimpl String.Chars, for: Cadet.SharedPrograms.SharedProgram do
def to_string(%Cadet.SharedPrograms.SharedProgram{uuid: uuid}) do
"SharedProgram with UUID: #{uuid}"
end
end
end
25 changes: 25 additions & 0 deletions lib/cadet_web/controllers/changeset_json.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule CadetWeb.ChangesetJSON do
@doc """
Renders changeset errors.
"""
def error(%{changeset: changeset}) do
# When encoded, the changeset returns its errors
# as a JSON object. So we just pass it forward.
%{errors: Ecto.Changeset.traverse_errors(changeset, &translate_error/1)}
end

defp translate_error({msg, opts}) do
# You can make use of gettext to translate error messages by
# uncommenting and adjusting the following code:

# if count = opts[:count] do
# Gettext.dngettext(CadetWeb.Gettext, "errors", msg, msg, count, opts)
# else
# Gettext.dgettext(CadetWeb.Gettext, "errors", msg, opts)
# end

Enum.reduce(opts, msg, fn {key, value}, acc ->
String.replace(acc, "%{#{key}}", fn _ -> to_string(value) end)
end)
end
end
24 changes: 24 additions & 0 deletions lib/cadet_web/controllers/fallback_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
defmodule CadetWeb.FallbackController do
@moduledoc """
Translates controller action results into valid `Plug.Conn` responses.

See `Phoenix.Controller.action_fallback/1` for more details.
"""
use CadetWeb, :controller

# This clause handles errors returned by Ecto's insert/update/delete.
def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
conn
|> put_status(:unprocessable_entity)
|> put_view(json: CadetWeb.ChangesetJSON)
|> render(:error, changeset: changeset)
end

# This clause is an example of how to handle resources that cannot be found.
def call(conn, {:error, :not_found}) do
conn
|> put_status(:not_found)
|> put_view(html: CadetWeb.ErrorHTML, json: CadetWeb.ErrorJSON)
|> render(:"404")
end
end
85 changes: 85 additions & 0 deletions lib/cadet_web/controllers/shared_program_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
defmodule CadetWeb.SharedProgramController do
use CadetWeb, :controller

alias Cadet.SharedPrograms
alias Cadet.SharedPrograms.SharedProgram

action_fallback(CadetWeb.FallbackController)

def index(conn, _params) do
shared_programs = SharedPrograms.list_shared_programs()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should not expose this. Very dangerous and breaks privacy.

render(conn, :index, shared_programs: shared_programs)
end

def create(conn, %{"shared_program" => shared_program_params}) do
with {:ok, %SharedProgram{uuid: uuid} = shared_program} <-
SharedPrograms.create_shared_program(shared_program_params) do
# change to current server of source academy
# server = "http://localhost:8000"
# url =
Comment on lines +17 to +19
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for?

conn
|> put_status(:created)
|> put_resp_header("location", ~s"/api/shared_programs/#{shared_program}")
# |> render(:show, shared_program: shared_program)
|> render("show.json", %{uuid: uuid})

# case ping_url_shortener(uuid) do
# {:ok, shortened_url} ->
# conn
# |> put_status(:created)
# |> put_resp_header("location", ~s"/api/shared_programs/#{shared_program}")
# # |> render(:show, shared_program: shared_program)
# |> render("show.json", %{uuid: uuid, shortened_url: shortened_url})
# {:error, reason} ->
# conn
# |> put_status(:internal_server_error)
# |> json(%{error: "Failed to shorten URL: #{reason}"})
# end
Comment on lines +26 to +37
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented code should either be explained with a comment or removed if unused.

end
end

def show(conn, %{"id" => uuid}) do
shared_program = SharedPrograms.get_shared_program_by_uuid!(uuid)
render(conn, :show, shared_program: shared_program)
end

def update(conn, %{"id" => id, "shared_program" => shared_program_params}) do
shared_program = SharedPrograms.get_shared_program!(id)

with {:ok, %SharedProgram{} = shared_program} <-
SharedPrograms.update_shared_program(shared_program, shared_program_params) do
render(conn, :show, shared_program: shared_program)
end
end

def delete(conn, %{"id" => id}) do
shared_program = SharedPrograms.get_shared_program!(id)

with {:ok, %SharedProgram{}} <- SharedPrograms.delete_shared_program(shared_program) do
send_resp(conn, :no_content, "")
end
end

defp ping_url_shortener(uuid) do
url = "https://localhost:8000/yourls-api.php"

params = %{
signature: "5eef899abd",
action: "shorturl",
format: "json",
keyword: uuid,
url: "http://localhost:8000/playground#uuid=#{uuid}"
}

case HTTPoison.post(url, {:form, params}) do
{:ok, %{status_code: 200, body: body}} ->
{:ok, body}

{:error, %HTTPoison.Error{reason: reason}} ->
{:error, reason}

other ->
{:error, "Unexpected response: #{inspect(other)}"}
end
end
Comment on lines +63 to +84
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove hardcoding.

end
25 changes: 25 additions & 0 deletions lib/cadet_web/controllers/shared_program_json.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule CadetWeb.SharedProgramJSON do
alias Cadet.SharedPrograms.SharedProgram

@doc """
Renders a list of shared_programs.
"""
def index(%{shared_programs: shared_programs}) do
%{data: for(shared_program <- shared_programs, do: data(shared_program))}
end

@doc """
Renders a single shared_program.
"""
def show(%{shared_program: shared_program}) do
%{data: data(shared_program)}
end

defp data(%SharedProgram{} = shared_program) do
%{
id: shared_program.id,
uuid: shared_program.uuid,
data: shared_program.data
}
end
end
6 changes: 6 additions & 0 deletions lib/cadet_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,12 @@ defmodule CadetWeb.Router do
# pipe_through :api
# end

scope "/v2", CadetWeb do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't create a separate scope is possible, since this endpoint is grouped as part of the other routes.

pipe_through(:api)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should pipe through the proper authentication middleware like the other endpoints.


resources("/shared_programs", SharedProgramController, only: [:index, :show, :create, :delete])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we supporting :index and :delete? Only create and read operations are meant to be supported right?

end

def swagger_info do
%{
info: %{
Expand Down
7 changes: 7 additions & 0 deletions lib/cadet_web/templates/shared_program/show.json.eex
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<%= Jason.encode!(%{
id: @shared_program.id,
uuid: @shared_program.uuid,
data: @shared_program.data,
inserted_at: @shared_program.inserted_at,
updated_at: @shared_program.updated_at
}) %>
21 changes: 21 additions & 0 deletions lib/cadet_web/views/shared_program_view.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
defmodule CadetWeb.SharedProgramView do
use CadetWeb, :view

def render("index.json", %{shared_programs: shared_programs}) do
%{
data: Enum.map(shared_programs, &render_shared_program/1)
}
end

def render("show.json", %{uuid: uuid}) do
%{uuid: uuid}
end

defp render_shared_program(shared_program) do
%{
uuid: shared_program.uuid,
json: shared_program.data
# Add other attributes as needed
}
end
end
3 changes: 2 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ defmodule Cadet.Mixfile do
# The following are indirect dependencies, but we need to override the
# versions due to conflicts
{:jsx, "~> 3.1", override: true},
{:xml_builder, "~> 2.1", override: true}
{:xml_builder, "~> 2.1", override: true},
{:uuid, "~> 1.1"}
]
end

Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@
"timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
"tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
"websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"},
"xml_builder": {:hex, :xml_builder, "2.2.0", "cc5f1eeefcfcde6e90a9b77fb6c490a20bc1b856a7010ce6396f6da9719cbbab", [:mix], [], "hexpm", "9d66d52fb917565d358166a4314078d39ef04d552904de96f8e73f68f64a62c9"},
Expand Down
12 changes: 12 additions & 0 deletions priv/repo/migrations/20240303150357_create_shared_programs.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
defmodule Cadet.Repo.Migrations.CreateSharedPrograms do
use Ecto.Migration

def change do
create table(:shared_programs) do
add(:uuid, :uuid)
add(:data, :map)
Comment on lines +6 to +7
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add constraints and/or indices to this table (e.g. non-null, unique, primary key, etc.)?


timestamps()
end
end
end