Hi, elixir noob-ish person here haveing problems with sending data to a join table as part of our create secret function in elixir (elixir setup as an api service).
I have three tables,
- user table
- secret table
- secret_user join table
The schemas are set as such
user schema
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "users" do
field :username, :string
field :first_name, :string
field :last_name, :string
field :passphrase, :string
field :email, :string
field :phone_num, :integer
belongs_to(:org, KzyBeApi.Orgs.Org, foreign_key: :org_id, on_replace: :delete)
many_to_many(:secrets, KzyBeApi.Secrets.Secret, join_through: KzyBeApi.Secrets_users.Secret_user)
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs,[
:username,
:first_name,
:last_name,
:passphrase,
:email,
:phone_num
])
|> unique_constraint(:username)
|> unique_constraint(:email)
|> validate_required([:email, :passphrase])
|> validate_format(:email, ~r/@/, message: "Email format not valid")
|> validate_length(:email, max: 160)
|> put_password_hash()
end
secret schema
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "secrets" do
field :secret_name, :string
field :external_login, :string # username for credentials
field :passphrase, :string # passphrase for credentials
field :file_name, :string
field :file_receiver, :string
field :created_date, :string
field :created_by, :string # user_id table joiner foreign_key
field :provider, :string # 1Pass, AWS/ASM, LastPass, etc.
field :s3_path, :string
field :token, :string
field :file_location, :string
field :updated_by, :string
field :secret_id, :string
field :user_id, :string
many_to_many :users, KzyBeApi.Users.User, join_through: KzyBeApi.Secrets_users.Secret_user
many_to_many :secrets_users, KzyBeApi.Secrets_users.Secret_user, join_through: KzyBeApi.Secrets_users.Secret_user
timestamps()
end
@doc false
def changeset(secret, attrs) do
secret
|> cast(attrs, [
:secret_name,
:external_login,
:passphrase,
:file_name,
:file_receiver,
:created_date,
:created_by,
:provider,
:s3_path,
:token,
:file_location,
:updated_by,
])
|> validate_required(:secret_name)
end
end
secrets_users join schema
@primary_key {:id, :binary_id, autogenerate: true}
schema "secrets_users" do
belongs_to :secret, KzyBeApi.Secrets.Secret, foreign_key: :secret_id, on_replace: :delete
belongs_to :user, KzyBeApi.Users.User, foreign_key: :user_id, on_replace: :delete
timestamps()
end
@doc false
def changeset(secret_user, attrs) do
secret_user
|> cast(attrs, [
:user_id,
:secret_id,
])
# |> validate_required([:user_id, :secret_id])
end
end
I have a build_assoc in the secrets_users.ex context file like so
def create_secret_user(secret, attrs \\ %{}) do
secret
|> Ecto.build_assoc(:secrets_users)
|> Secret_user.changeset(attrs)
|> Repo.insert()
end
Finally, I have a create function in the secret_controller.ex file as such
def create(conn, %{"secret" => secret_params}) do
conn = Guardian.Plug.VerifyHeader.call(conn, realm: "Bearer")
conn = Guardian.Plug.LoadResource.call(conn, allow_blank: true)
conn = fetch_user_id_from_token(conn)
changeset = Secret.changeset(%Secret{}, secret_params)
case Repo.insert(changeset) do
{:ok, %Secret{} = secret} ->
map = %{:secret_id => secret.id, :user_id => conn.assigns[:user_id]}
Secrets_users.create_secret_user(secret, %{secret_id: map[:secret_id], user_id: map[:user_id]})
{:ok, secret_with_assoc} ->
conn
|> put_status(:created)
|> render(:show, secret: secret_with_assoc)
{:error, _changeset} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{errors: "Failed to create the join record"})
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> json(%{errors: changeset.errors})
end
end
defp fetch_user_id_from_token(conn) do
case Guardian.Plug.current_resource(conn) do
nil -> conn
resource -> assign(conn, :user_id, resource.id)
end
end
This works as far as being able to make the api call and seeing the secret table get filled out
# SELECT * FROM secrets;
id | secret_name | external_login | passphrase | token | file_name | file_receiver | s3_path | inserted_at | updated_at
--------------------------------------+-------------+----------------+------------+-------+-----------+---------------+---------+---------------------+---------------------
85342b3f-9bac-4fff-b489-93c5cad246c4 | secret name | somename | testpass1 | | | | | 2023-06-03 21:16:21 | 2023-06-03 21:16:21
But I do not see the data hitting the join table
# SELECT * FROM secrets_users;
id | user_id | secret_id | inserted_at | updated_at
----+---------+-----------+-------------+------------
(0 rows)
and I get this api response from the call as a response
{
"errors": {
"secret_id": [
"is invalid"
],
"user_id": [
"is invalid"
]
}
}
In the termial running mix phx.server for my api I see no errors and can confirm the values for user_id and secret_id thats suppose to get sent to the secrets_users join table is correct
warning: unused alias Secret_user
lib/kzy_be_api_web/controllers/secret_controller.ex:5
[info] POST /api/secret/create
[debug] Processing with KzyBeApiWeb.SecretController.create/2
Parameters: %{"secret" => %{"external_login" => "somename", "passphrase" => "testpass1", "secret_name" => "secret name"}}
Pipelines: [:api, :auth]
[debug] QUERY OK source="guardian_tokens" db=4.2ms idle=1363.7ms
SELECT g0."jti", g0."typ", g0."aud", g0."iss", g0."sub", g0."exp", g0."jwt", g0."claims", g0."inserted_at", g0."updated_at" FROM "guardian_tokens" AS g0 WHERE ((g0."jti" = $1) AND (g0."aud" = $2)) ["723d6ea0-f29c-497c-a15b-b96d45c40e4a", "kzy_be_api"]
↳ Guardian.DB.on_verify/2, at: lib/guardian/db.ex:134
[debug] QUERY OK source="users" db=3.1ms idle=1368.0ms
SELECT u0."id", u0."username", u0."first_name", u0."last_name", u0."passphrase", u0."email", u0."phone_num", u0."org_id", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) ["e9763d23-63b0-4f3a-a1a4-1442ac5b8beb"]
↳ KzyBeApiWeb.Auth.Guardian.resource_from_claims/1, at: lib/kzy_be_api_web/auth/guardian.ex:20
[debug] QUERY OK source="users" db=3.9ms idle=1371.2ms
SELECT u0."id", u0."username", u0."first_name", u0."last_name", u0."passphrase", u0."email", u0."phone_num", u0."org_id", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) ["e9763d23-63b0-4f3a-a1a4-1442ac5b8beb"]
↳ KzyBeApiWeb.Auth.SetUser.call/2, at: lib/kzy_be_api_web/auth/set_user.ex:18
[debug] QUERY OK source="users" db=0.9ms idle=1374.7ms
SELECT u0."id", u0."username", u0."first_name", u0."last_name", u0."passphrase", u0."email", u0."phone_num", u0."org_id", u0."inserted_at", u0."updated_at" FROM "users" AS u0 WHERE (u0."id" = $1) ["e9763d23-63b0-4f3a-a1a4-1442ac5b8beb"]
↳ KzyBeApiWeb.Auth.Guardian.resource_from_claims/1, at: lib/kzy_be_api_web/auth/guardian.ex:20
[debug] QUERY OK db=2.4ms queue=1.4ms idle=1375.8ms
INSERT INTO "secrets" ("external_login","passphrase","secret_name","inserted_at","updated_at","id") VALUES ($1,$2,$3,$4,$5,$6) ["somename", "testpass1", "secret name", ~N[2023-06-03 21:28:46], ~N[2023-06-03 21:28:46], "189da04e-bbcf-4310-9460-241d6b182276"]
↳ KzyBeApiWeb.SecretController.create/2, at: lib/kzy_be_api_web/controllers/secret_controller.ex:24
Hello
189da04e-bbcf-4310-9460-241d6b182276
e9763d23-63b0-4f3a-a1a4-1442ac5b8beb
[info] Sent 422 in 26ms
I am lost at this point as to how to fix this or what my oversight is. ANY help is appreciated.