Back again as I plug away on my Ash project!
This is a question about conventions when it comes to structuring actions inside of resources that need to interact with each other. I know how all the logic for these actions need to be coded. I want to make sure I’m structuring this code in a sane manner that follows conventions for how Ash may typically be used.
I’m building out a system that has an Accounts
domain, which contains a User
resource and a ProviderIdentity
resource. The ProviderIdentity
resource stores information on third-party authentication identities, such as Google OAuth, which is associated with the User
resource. The User
resource has many provider identities and the ProviderIdentity
belongs to the User
resource. For standard authentication, the password hash and email are stored on the User
resource itself.
There are two main scenarios that can occur with this particular set up:
- The user is signing up for the website for the first time using Google OAuth as the method to create their account. In this scenario, a
User
would need to be created using the information provided by Google (name, email, etc.) and then aProviderIdentity
created and associated with it. - The user has already created an account using email and password, but they have now decided to log in with Google. Assuming they have verified their email address in our system, we can associate the
ProviderIdentity
with theUser
if the emails match. In this scenario, I wouldn’t want the information coming from Google (name, etc.) to overwrite anything already existing on theUser
resource. - The third scenario, which is a bit of an odd one, which is if they have already created an account using Google but haven’t set a password in their settings. If they try to log in using email and password, the
User
resource does exist that has this email address, but there is no password associated with it so it doesn’t allow the log in to occur. The same would be if they tried to sign up for a new account using that email. The account already exists. The only way to realtistically allow them to set a password would be to email them a password reset link. I think it would be too risky to allow them to set a password through the settings page without first confirming they have control of the email address associated with it, which is why I’d send the link and not allow it through the normal settings page.
Anyway, that was a long winded explanation to get to the actual question: where should all of this logic live? This too also has a few options:
- It can all live within actions. If this is the case, I assume there’s a way to do checks to see if things like the
User
already exists in acreate
action in theProviderIdentity
resource and then branch depending on if it does or does not exist to either create theUser
or to just simply create theProviderIdentity
. - I have actions on both resources that are exposed through the domain and then handle all the logic that ties it together in another module, such as a controller or liveview. Using this method I could do
Accounts.get_user_by_email
to see if theUser
exists. If not, create the user withAccounts.create_user
and then once that is done call intoAccounts.create_provider_identity
. These calls would all happen within a different module, not within the resources themselves. - So other method? A hybrid of both approaches? Something even better and more clean?
Which of these would be a best practices approach to structuring the application?
My only other question not related to structuring would be, if I wanted to have all of this happen within a transaction, is there anything special I need to do for any/all of these methods?