{"action":"created","comment":{"url":"https://api.github.com/repos/azavea/raster-foundry/pulls/comments/77678334","id":77678334,"diff_hunk":"@@ -2,40 +2,65 @@ package com.azavea.rf.user\n \n import scala.concurrent.{Future, ExecutionContext}\n import scala.util.{Try, Success, Failure}\n+import scala.language.postfixOps\n import java.sql.Timestamp\n-import slick.lifted.{ColumnOrdered, TableQuery, Query}\n+import slick.lifted.Query\n import com.lonelyplanet.akka.http.extensions.{PageRequest, Order}\n-import de.choffmeister.auth.common.JsonWebToken\n \n-import com.azavea.rf.datamodel.latest.schema.tables.{Users, Organizations}\n-import com.azavea.rf.datamodel.latest.schema.tables.UsersRow\n+import com.azavea.rf.datamodel.latest.schema.tables.{Users, Organizations, UsersToOrganizations}\n+import com.azavea.rf.datamodel.latest.schema.tables.{UsersRow, UsersToOrganizationsRow}\n import com.azavea.rf.utils.{Database,PaginatedResponse}\n \n+object Roles extends Enumeration {\n+  val Uploader = Role(\"uploader\")\n+  val Viewer = Role(\"viewer\")\n+  val Admin = Role(\"admin\")\n+\n+  def Role(name: String): Value with Matching =\n+    new Val(nextId, name) with Matching\n+\n+  def unapply(s: String): Option[Value] =\n+    values.find(s == _.toString)\n+\n+  trait Matching {\n+    def unapply(s: String): Boolean =\n+      (s == toString)\n+  }\n+}\n \n /**\n-  * Instantiated from a POST request to create a user\n+  * Instantiated from a POST request to create a user. Typically used with the default public org.\n   */\n-case class UsersRowCreate(authId: String, organizationId: java.util.UUID) {\n-  def toUsersRow(): UsersRow = {\n+case class UsersRowCreate(id: String, organizationId: java.util.UUID, role: Option[String] = Some(\"uploader\")) {\n+  def toUsersOrgTuple(): (UsersRow, UsersToOrganizationsRow)= {\n     val now = new Timestamp((new java.util.Date()).getTime())\n     val newUUID = java.util.UUID.randomUUID\n-    UsersRow(\n-      id=newUUID,\n-      authId=authId,\n-      organizationId=organizationId\n+    val user = UsersRow(\n+      id=id\n     )\n+    val defaultedRole = role getOrElse { \"null\" } match {\n+      case Roles(role) => role\n+      case s: String => throw new Exception( \"\\\"\" + s + \"\\\" is not a valid user Role\")\n+    }\n+\n+    val userToOrg = UsersToOrganizationsRow(\n+      userId=id,\n+      organizationId=organizationId,\n+      defaultedRole.toString(),\n+      now,\n+      now\n+    )\n+    (user, userToOrg)\n   }\n }\n \n+case class OrganizationWithRole(id: java.util.UUID, name: String, role: String)\n ","path":"app-server/app/src/main/scala/user/User.scala","position":68,"original_position":68,"commit_id":"baa6ff6f19c947a6feb9fc22e162e061e139f264","original_commit_id":"baa6ff6f19c947a6feb9fc22e162e061e139f264","user":{"login":"notthatbreezy","id":898060,"avatar_url":"https://avatars.githubusercontent.com/u/898060?v=3","gravatar_id":"","url":"https://api.github.com/users/notthatbreezy","html_url":"https://github.com/notthatbreezy","followers_url":"https://api.github.com/users/notthatbreezy/followers","following_url":"https://api.github.com/users/notthatbreezy/following{/other_user}","gists_url":"https://api.github.com/users/notthatbreezy/gists{/gist_id}","starred_url":"https://api.github.com/users/notthatbreezy/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/notthatbreezy/subscriptions","organizations_url":"https://api.github.com/users/notthatbreezy/orgs","repos_url":"https://api.github.com/users/notthatbreezy/repos","events_url":"https://api.github.com/users/notthatbreezy/events{/privacy}","received_events_url":"https://api.github.com/users/notthatbreezy/received_events","type":"User","site_admin":false},"body":"I think we've been settling on two lines between top-level objects/classes and one line inside classes/objects separating functions","created_at":"2016-09-06T17:15:20Z","updated_at":"2016-09-06T17:15:20Z","html_url":"https://github.com/azavea/raster-foundry/pull/443#discussion_r77678334","pull_request_url":"https://api.github.com/repos/azavea/raster-foundry/pulls/443","_links":{"self":{"href":"https://api.github.com/repos/azavea/raster-foundry/pulls/comments/77678334"},"html":{"href":"https://github.com/azavea/raster-foundry/pull/443#discussion_r77678334"},"pull_request":{"href":"https://api.github.com/repos/azavea/raster-foundry/pulls/443"}}},"pull_request":{"url":"https://api.github.com/repos/azavea/raster-foundry/pulls/443","id":83869881,"html_url":"https://github.com/azavea/raster-foundry/pull/443","diff_url":"https://github.com/azavea/raster-foundry/pull/443.diff","patch_url":"https://github.com/azavea/raster-foundry/pull/443.patch","issue_url":"https://api.github.com/repos/azavea/raster-foundry/issues/443","number":443,"state":"open","locked":false,"title":"Create many to many user-org relationship w/ metadata","user":{"login":"Lknechtli","id":4392704,"avatar_url":"https://avatars.githubusercontent.com/u/4392704?v=3","gravatar_id":"","url":"https://api.github.com/users/Lknechtli","html_url":"https://github.com/Lknechtli","followers_url":"https://api.github.com/users/Lknechtli/followers","following_url":"https://api.github.com/users/Lknechtli/following{/other_user}","gists_url":"https://api.github.com/users/Lknechtli/gists{/gist_id}","starred_url":"https://api.github.com/users/Lknechtli/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Lknechtli/subscriptions","organizations_url":"https://api.github.com/users/Lknechtli/orgs","repos_url":"https://api.github.com/users/Lknechtli/repos","events_url":"https://api.github.com/users/Lknechtli/events{/privacy}","received_events_url":"https://api.github.com/users/Lknechtli/received_events","type":"User","site_admin":false},"body":"User table only contains auth0 ids now  \r\n\r\nUsersToOrganizations table contains metadata about the relationship between users and orgs (just role for now)  \r\n\r\nRoles table contains a list of roles - just a string as primary key for now. We can put actual actions on this table if we want, or in the API based on the role. I think I'd rather have the API use specific permissions which are matched to the users' roles, rather than implicitly defining the role permissions ad-hock in code.\r\n\r\nAPI responses use a marshaller to nest relevant organization information wherever users are returned\r\n\r\n### Testing\r\nTo avoid polluting auth0 with many temporary users, I recommend using existing users. You can impersonate another user and log into raster foundry by clicking the \"Sign in as User\" button:\r\n![image](https://cloud.githubusercontent.com/assets/4392704/18277453/8e494f38-741c-11e6-8899-bcb494017251.png)\r\nthen selecting localhost:9091 as the callback url and clicking the blue button next to Client Side app\r\n![image](https://cloud.githubusercontent.com/assets/4392704/18277494/b35a423c-741c-11e6-83a5-d1a15c203ac1.png)  \r\n\r\nYou can then open dev console and copy the jwt token for use with the API  \r\n#### Create user automatically using a valid jwt token\r\nUsing a new auth0 users (or after deleting it from the database), create a new user by accessing the API using an auth0 jwt in the header.  This should create a new user as usual.  The api response should look something like this if you queried `/users/`:\r\n```JSON\r\n{\r\n  \"count\": 4,\r\n  \"pageSize\": 30,\r\n  \"hasPrevious\": false,\r\n  \"page\": 0,\r\n  \"results\": [{\r\n    \"id\": \"auth0|57b744b09da7f9936f14fd5c\",\r\n    \"organizations\": [{\r\n      \"id\": \"dfac6307-b5ef-43f7-beda-b9f208bb7726\",\r\n      \"name\": \"Public\",\r\n      \"role\": \"admin\"\r\n    }]\r\n  }, {\r\n    \"id\": \"default\",\r\n    \"organizations\": [{\r\n      \"id\": \"dfac6307-b5ef-43f7-beda-b9f208bb7726\",\r\n      \"name\": \"Public\",\r\n      \"role\": \"user\"\r\n    }]\r\n  }, {\r\n    \"id\": \"auth0|57b744b09da7f9936f14fd5b\",\r\n    \"organizations\": [{\r\n      \"id\": \"dfac6307-b5ef-43f7-beda-b9f208bb7726\",\r\n      \"name\": \"Public\",\r\n      \"role\": \"admin\"\r\n    }]\r\n  }, {\r\n    \"id\": \"auth0|57b214b49594971c3ec3a32f\",\r\n    \"organizations\": [{\r\n      \"id\": \"dfac6307-b5ef-43f7-beda-b9f208bb7726\",\r\n      \"name\": \"Public\",\r\n      \"role\": \"user\"\r\n    }]\r\n  }],\r\n  \"hasNext\": false\r\n}\r\n```  \r\n#### Create a new user manually\r\nCreate a new user manually by creating a new auth0 user / deleting it from the database and making a POST request to `/users/`.  Required fields are `id` and `organizationId`, with an optional `role` field\r\n```JSON\r\n{\r\n  \"id\": \"auth0|57b744b09da7f9936f14fd5b\",\r\n  \"organizationId\": \"dfac6307-b5ef-43f7-beda-b9f208bb7726\",\r\n  \"role\": \"admin\"\r\n}\r\n```\r\nThe response should look something like this: \r\n```JSON\r\n{\r\n    \"id\": \"auth0|57b744b09da7f9936f14fd5b\",\r\n    \"organizations\": [{\r\n      \"id\": \"dfac6307-b5ef-43f7-beda-b9f208bb7726\",\r\n      \"name\": \"Public\",\r\n      \"role\": \"admin\"\r\n    }]\r\n  }\r\n```\r\nValid roles are `user` and `admin`, stored in the `Roles` table of the database.  There is also an enumeration in Users.scala, but we currently don't check against it.\r\nAn invalid value for `role` will result in a 500 error, until we add error handling directives to the API\r\n","created_at":"2016-09-02T21:56:21Z","updated_at":"2016-09-06T17:15:20Z","closed_at":null,"merged_at":null,"merge_commit_sha":"96aa562913387ff487ce1660b8ace3639e218c03","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/azavea/raster-foundry/pulls/443/commits","review_comments_url":"https://api.github.com/repos/azavea/raster-foundry/pulls/443/comments","review_comment_url":"https://api.github.com/repos/azavea/raster-foundry/pulls/comments{/number}","comments_url":"https://api.github.com/repos/azavea/raster-foundry/issues/443/comments","statuses_url":"https://api.github.com/repos/azavea/raster-foundry/statuses/baa6ff6f19c947a6feb9fc22e162e061e139f264","head":{"label":"azavea:feature/user-org-relationship-changes","ref":"feature/user-org-relationship-changes","sha":"baa6ff6f19c947a6feb9fc22e162e061e139f264","user":{"login":"azavea","id":595231,"avatar_url":"https://avatars.githubusercontent.com/u/595231?v=3","gravatar_id":"","url":"https://api.github.com/users/azavea","html_url":"https://github.com/azavea","followers_url":"https://api.github.com/users/azavea/followers","following_url":"https://api.github.com/users/azavea/following{/other_user}","gists_url":"https://api.github.com/users/azavea/gists{/gist_id}","starred_url":"https://api.github.com/users/azavea/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/azavea/subscriptions","organizations_url":"https://api.github.com/users/azavea/orgs","repos_url":"https://api.github.com/users/azavea/repos","events_url":"https://api.github.com/users/azavea/events{/privacy}","received_events_url":"https://api.github.com/users/azavea/received_events","type":"Organization","site_admin":false},"repo":{"id":36464140,"name":"raster-foundry","full_name":"azavea/raster-foundry","owner":{"login":"azavea","id":595231,"avatar_url":"https://avatars.githubusercontent.com/u/595231?v=3","gravatar_id":"","url":"https://api.github.com/users/azavea","html_url":"https://github.com/azavea","followers_url":"https://api.github.com/users/azavea/followers","following_url":"https://api.github.com/users/azavea/following{/other_user}","gists_url":"https://api.github.com/users/azavea/gists{/gist_id}","starred_url":"https://api.github.com/users/azavea/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/azavea/subscriptions","organizations_url":"https://api.github.com/users/azavea/orgs","repos_url":"https://api.github.com/users/azavea/repos","events_url":"https://api.github.com/users/azavea/events{/privacy}","received_events_url":"https://api.github.com/users/azavea/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/azavea/raster-foundry","description":"The Raster Foundry web application.","fork":false,"url":"https://api.github.com/repos/azavea/raster-foundry","forks_url":"https://api.github.com/repos/azavea/raster-foundry/forks","keys_url":"https://api.github.com/repos/azavea/raster-foundry/keys{/key_id}","collaborators_url":"https://api.github.com/repos/azavea/raster-foundry/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/azavea/raster-foundry/teams","hooks_url":"https://api.github.com/repos/azavea/raster-foundry/hooks","issue_events_url":"https://api.github.com/repos/azavea/raster-foundry/issues/events{/number}","events_url":"https://api.github.com/repos/azavea/raster-foundry/events","assignees_url":"https://api.github.com/repos/azavea/raster-foundry/assignees{/user}","branches_url":"https://api.github.com/repos/azavea/raster-foundry/branches{/branch}","tags_url":"https://api.github.com/repos/azavea/raster-foundry/tags","blobs_url":"https://api.github.com/repos/azavea/raster-foundry/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/azavea/raster-foundry/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/azavea/raster-foundry/git/refs{/sha}","trees_url":"https://api.github.com/repos/azavea/raster-foundry/git/trees{/sha}","statuses_url":"https://api.github.com/repos/azavea/raster-foundry/statuses/{sha}","languages_url":"https://api.github.com/repos/azavea/raster-foundry/languages","stargazers_url":"https://api.github.com/repos/azavea/raster-foundry/stargazers","contributors_url":"https://api.github.com/repos/azavea/raster-foundry/contributors","subscribers_url":"https://api.github.com/repos/azavea/raster-foundry/subscribers","subscription_url":"https://api.github.com/repos/azavea/raster-foundry/subscription","commits_url":"https://api.github.com/repos/azavea/raster-foundry/commits{/sha}","git_commits_url":"https://api.github.com/repos/azavea/raster-foundry/git/commits{/sha}","comments_url":"https://api.github.com/repos/azavea/raster-foundry/comments{/number}","issue_comment_url":"https://api.github.com/repos/azavea/raster-foundry/issues/comments{/number}","contents_url":"https://api.github.com/repos/azavea/raster-foundry/contents/{+path}","compare_url":"https://api.github.com/repos/azavea/raster-foundry/compare/{base}...{head}","merges_url":"https://api.github.com/repos/azavea/raster-foundry/merges","archive_url":"https://api.github.com/repos/azavea/raster-foundry/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/azavea/raster-foundry/downloads","issues_url":"https://api.github.com/repos/azavea/raster-foundry/issues{/number}","pulls_url":"https://api.github.com/repos/azavea/raster-foundry/pulls{/number}","milestones_url":"https://api.github.com/repos/azavea/raster-foundry/milestones{/number}","notifications_url":"https://api.github.com/repos/azavea/raster-foundry/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/azavea/raster-foundry/labels{/name}","releases_url":"https://api.github.com/repos/azavea/raster-foundry/releases{/id}","deployments_url":"https://api.github.com/repos/azavea/raster-foundry/deployments","created_at":"2015-05-28T20:25:45Z","updated_at":"2016-07-18T16:12:22Z","pushed_at":"2016-09-06T15:41:58Z","git_url":"git://github.com/azavea/raster-foundry.git","ssh_url":"git@github.com:azavea/raster-foundry.git","clone_url":"https://github.com/azavea/raster-foundry.git","svn_url":"https://github.com/azavea/raster-foundry","homepage":"","size":13972,"stargazers_count":5,"watchers_count":5,"language":"HTML","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":8,"mirror_url":null,"open_issues_count":22,"forks":8,"open_issues":22,"watchers":5,"default_branch":"develop"}},"base":{"label":"azavea:develop","ref":"develop","sha":"b77c6b1ad067e3444a9472f493450bb8bdd4016e","user":{"login":"azavea","id":595231,"avatar_url":"https://avatars.githubusercontent.com/u/595231?v=3","gravatar_id":"","url":"https://api.github.com/users/azavea","html_url":"https://github.com/azavea","followers_url":"https://api.github.com/users/azavea/followers","following_url":"https://api.github.com/users/azavea/following{/other_user}","gists_url":"https://api.github.com/users/azavea/gists{/gist_id}","starred_url":"https://api.github.com/users/azavea/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/azavea/subscriptions","organizations_url":"https://api.github.com/users/azavea/orgs","repos_url":"https://api.github.com/users/azavea/repos","events_url":"https://api.github.com/users/azavea/events{/privacy}","received_events_url":"https://api.github.com/users/azavea/received_events","type":"Organization","site_admin":false},"repo":{"id":36464140,"name":"raster-foundry","full_name":"azavea/raster-foundry","owner":{"login":"azavea","id":595231,"avatar_url":"https://avatars.githubusercontent.com/u/595231?v=3","gravatar_id":"","url":"https://api.github.com/users/azavea","html_url":"https://github.com/azavea","followers_url":"https://api.github.com/users/azavea/followers","following_url":"https://api.github.com/users/azavea/following{/other_user}","gists_url":"https://api.github.com/users/azavea/gists{/gist_id}","starred_url":"https://api.github.com/users/azavea/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/azavea/subscriptions","organizations_url":"https://api.github.com/users/azavea/orgs","repos_url":"https://api.github.com/users/azavea/repos","events_url":"https://api.github.com/users/azavea/events{/privacy}","received_events_url":"https://api.github.com/users/azavea/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/azavea/raster-foundry","description":"The Raster Foundry web application.","fork":false,"url":"https://api.github.com/repos/azavea/raster-foundry","forks_url":"https://api.github.com/repos/azavea/raster-foundry/forks","keys_url":"https://api.github.com/repos/azavea/raster-foundry/keys{/key_id}","collaborators_url":"https://api.github.com/repos/azavea/raster-foundry/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/azavea/raster-foundry/teams","hooks_url":"https://api.github.com/repos/azavea/raster-foundry/hooks","issue_events_url":"https://api.github.com/repos/azavea/raster-foundry/issues/events{/number}","events_url":"https://api.github.com/repos/azavea/raster-foundry/events","assignees_url":"https://api.github.com/repos/azavea/raster-foundry/assignees{/user}","branches_url":"https://api.github.com/repos/azavea/raster-foundry/branches{/branch}","tags_url":"https://api.github.com/repos/azavea/raster-foundry/tags","blobs_url":"https://api.github.com/repos/azavea/raster-foundry/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/azavea/raster-foundry/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/azavea/raster-foundry/git/refs{/sha}","trees_url":"https://api.github.com/repos/azavea/raster-foundry/git/trees{/sha}","statuses_url":"https://api.github.com/repos/azavea/raster-foundry/statuses/{sha}","languages_url":"https://api.github.com/repos/azavea/raster-foundry/languages","stargazers_url":"https://api.github.com/repos/azavea/raster-foundry/stargazers","contributors_url":"https://api.github.com/repos/azavea/raster-foundry/contributors","subscribers_url":"https://api.github.com/repos/azavea/raster-foundry/subscribers","subscription_url":"https://api.github.com/repos/azavea/raster-foundry/subscription","commits_url":"https://api.github.com/repos/azavea/raster-foundry/commits{/sha}","git_commits_url":"https://api.github.com/repos/azavea/raster-foundry/git/commits{/sha}","comments_url":"https://api.github.com/repos/azavea/raster-foundry/comments{/number}","issue_comment_url":"https://api.github.com/repos/azavea/raster-foundry/issues/comments{/number}","contents_url":"https://api.github.com/repos/azavea/raster-foundry/contents/{+path}","compare_url":"https://api.github.com/repos/azavea/raster-foundry/compare/{base}...{head}","merges_url":"https://api.github.com/repos/azavea/raster-foundry/merges","archive_url":"https://api.github.com/repos/azavea/raster-foundry/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/azavea/raster-foundry/downloads","issues_url":"https://api.github.com/repos/azavea/raster-foundry/issues{/number}","pulls_url":"https://api.github.com/repos/azavea/raster-foundry/pulls{/number}","milestones_url":"https://api.github.com/repos/azavea/raster-foundry/milestones{/number}","notifications_url":"https://api.github.com/repos/azavea/raster-foundry/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/azavea/raster-foundry/labels{/name}","releases_url":"https://api.github.com/repos/azavea/raster-foundry/releases{/id}","deployments_url":"https://api.github.com/repos/azavea/raster-foundry/deployments","created_at":"2015-05-28T20:25:45Z","updated_at":"2016-07-18T16:12:22Z","pushed_at":"2016-09-06T15:41:58Z","git_url":"git://github.com/azavea/raster-foundry.git","ssh_url":"git@github.com:azavea/raster-foundry.git","clone_url":"https://github.com/azavea/raster-foundry.git","svn_url":"https://github.com/azavea/raster-foundry","homepage":"","size":13972,"stargazers_count":5,"watchers_count":5,"language":"HTML","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":8,"mirror_url":null,"open_issues_count":22,"forks":8,"open_issues":22,"watchers":5,"default_branch":"develop"}},"_links":{"self":{"href":"https://api.github.com/repos/azavea/raster-foundry/pulls/443"},"html":{"href":"https://github.com/azavea/raster-foundry/pull/443"},"issue":{"href":"https://api.github.com/repos/azavea/raster-foundry/issues/443"},"comments":{"href":"https://api.github.com/repos/azavea/raster-foundry/issues/443/comments"},"review_comments":{"href":"https://api.github.com/repos/azavea/raster-foundry/pulls/443/comments"},"review_comment":{"href":"https://api.github.com/repos/azavea/raster-foundry/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/azavea/raster-foundry/pulls/443/commits"},"statuses":{"href":"https://api.github.com/repos/azavea/raster-foundry/statuses/baa6ff6f19c947a6feb9fc22e162e061e139f264"}}}}
{"actor":{"display_login":"notthatbreezy"}}