Add support for multiple emails for profile.

This commit is contained in:
Andrey Antukh 2020-01-23 17:50:00 +01:00
parent 282b170147
commit 3e8b570c6b
3 changed files with 79 additions and 33 deletions

View file

@ -15,6 +15,37 @@ CREATE TABLE users (
is_demo boolean NOT NULL DEFAULT false is_demo boolean NOT NULL DEFAULT false
); );
CREATE UNIQUE INDEX users__username__idx
ON users (username)
WHERE deleted_at IS null;
CREATE UNIQUE INDEX users__email__idx
ON users (email)
WHERE deleted_at IS null;
CREATE INDEX users__is_demo
ON users (is_demo)
WHERE deleted_at IS null
AND is_demo IS true;
--- Table used for register all used emails by the user
CREATE TABLE IF NOT EXISTS user_emails (
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
verified_at timestamptz NULL DEFAULT NULL,
email text NOT NULL,
is_main boolean NOT NULL DEFAULT false,
is_verified boolean NOT NULL DEFAULT false
);
CREATE INDEX user_emails__user_id__idx
ON user_emails (user_id);
--- Table for user key value attributes
CREATE TABLE IF NOT EXISTS user_attrs ( CREATE TABLE IF NOT EXISTS user_attrs (
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
@ -27,6 +58,8 @@ CREATE TABLE IF NOT EXISTS user_attrs (
PRIMARY KEY (key, user_id) PRIMARY KEY (key, user_id)
); );
--- Table for store verification tokens
CREATE TABLE IF NOT EXISTS tokens ( CREATE TABLE IF NOT EXISTS tokens (
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token text NOT NULL, token text NOT NULL,
@ -37,6 +70,8 @@ CREATE TABLE IF NOT EXISTS tokens (
PRIMARY KEY (token, user_id) PRIMARY KEY (token, user_id)
); );
--- Table for store user sessions.
CREATE TABLE IF NOT EXISTS sessions ( CREATE TABLE IF NOT EXISTS sessions (
id uuid PRIMARY KEY DEFAULT uuid_generate_v4(), id uuid PRIMARY KEY DEFAULT uuid_generate_v4(),
@ -47,6 +82,9 @@ CREATE TABLE IF NOT EXISTS sessions (
user_agent text NULL user_agent text NULL
); );
CREATE INDEX sessions__user_id__idx
ON sessions (user_id);
-- Insert a placeholder system user. -- Insert a placeholder system user.
INSERT INTO users (id, fullname, username, email, photo, password) INSERT INTO users (id, fullname, username, email, photo, password)
@ -57,18 +95,7 @@ VALUES ('00000000-0000-0000-0000-000000000000'::uuid,
'', '',
'!'); '!');
CREATE UNIQUE INDEX users__username__idx --- Triggers
ON users (username)
WHERE deleted_at IS null;
CREATE UNIQUE INDEX users__email__idx
ON users (email)
WHERE deleted_at IS null;
CREATE INDEX users__is_demo
ON users(is_demo)
WHERE deleted_at IS null
AND is_demo IS true;
CREATE TRIGGER users__modified_at__tgr CREATE TRIGGER users__modified_at__tgr
BEFORE UPDATE ON users BEFORE UPDATE ON users

View file

@ -32,10 +32,14 @@
[uxbox.util.uuid :as uuid] [uxbox.util.uuid :as uuid]
[vertx.core :as vc])) [vertx.core :as vc]))
(def sql:create-demo-user (def sql:insert-user
"insert into users (id, fullname, username, email, password, photo, is_demo) "insert into users (id, fullname, username, email, password, photo, is_demo)
values ($1, $2, $3, $4, $5, '', true) returning *") values ($1, $2, $3, $4, $5, '', true) returning *")
(def sql:insert-email
"insert into user_emails (user_id, email, is_main)
values ($1, $2, true)")
(sm/defmutation ::create-demo-profile (sm/defmutation ::create-demo-profile
[params] [params]
(let [id (uuid/next) (let [id (uuid/next)
@ -47,6 +51,7 @@
(sodi.util/bytes->b64s)) (sodi.util/bytes->b64s))
password' (sodi.pwhash/derive password)] password' (sodi.pwhash/derive password)]
(db/with-atomic [conn db/pool] (db/with-atomic [conn db/pool]
(db/query-one conn [sql:create-demo-user id fullname username email password']) (db/query-one conn [sql:insert-user id fullname username email password'])
(db/query-one conn [sql:insert-email id email])
{:username username {:username username
:password password}))) :password password})))

View file

@ -35,7 +35,7 @@
(s/def ::email ::us/email) (s/def ::email ::us/email)
(s/def ::fullname ::us/string) (s/def ::fullname ::us/string)
(s/def ::metadata any?) (s/def ::lang ::us/string)
(s/def ::old-password ::us/string) (s/def ::old-password ::us/string)
(s/def ::password ::us/string) (s/def ::password ::us/string)
(s/def ::path ::us/string) (s/def ::path ::us/string)
@ -82,6 +82,15 @@
(-> (retrieve-user db/pool username) (-> (retrieve-user db/pool username)
(p/then' check-user)))) (p/then' check-user))))
;; --- Mutation: Add additional email
;; TODO
;; --- Mutation: Mark email as main email
;; TODO
;; --- Mutation: Verify email (or maybe query?)
;; TODO
;; --- Mutation: Update Profile (own) ;; --- Mutation: Update Profile (own)
(defn- check-username-and-email! (defn- check-username-and-email!
@ -109,23 +118,22 @@
(def sql:update-profile (def sql:update-profile
"update users "update users
set username = $2, set username = $2,
email = $3, fullname = $3,
fullname = $4, lang = $4
metadata = $5
where id = $1 where id = $1
and deleted_at is null and deleted_at is null
returning *") returning *")
(defn- update-profile (defn- update-profile
[conn {:keys [id username email fullname metadata] :as params}] [conn {:keys [id username fullname lang] :as params}]
(let [sqlv [sql:update-profile id username (let [sqlv [sql:update-profile
email fullname (blob/encode metadata)]] id username fullname lang]]
(-> (db/query-one conn sqlv) (-> (db/query-one conn sqlv)
(p/then' su/raise-not-found-if-nil) (p/then' su/raise-not-found-if-nil)
(p/then' profile/strip-private-attrs)))) (p/then' profile/strip-private-attrs))))
(s/def ::update-profile (s/def ::update-profile
(s/keys :req-un [::id ::username ::email ::fullname ::metadata])) (s/keys :req-un [::id ::username ::fullname ::lang]))
(sm/defmutation ::update-profile (sm/defmutation ::update-profile
[params] [params]
@ -213,10 +221,14 @@
;; --- Mutation: Register Profile ;; --- Mutation: Register Profile
(def sql:create-user (def sql:insert-user
"insert into users (id, fullname, username, email, password, photo) "insert into users (id, fullname, username, email, password, photo)
values ($1, $2, $3, $4, $5, '') returning *") values ($1, $2, $3, $4, $5, '') returning *")
(def sql:insert-email
"insert into user_emails (user_id, email, is_main)
values ($1, $2, true)")
(defn- check-profile-existence! (defn- check-profile-existence!
[conn {:keys [username email] :as params}] [conn {:keys [username email] :as params}]
(let [sql "select exists (let [sql "select exists
@ -237,22 +249,24 @@
[conn {:keys [id username fullname email password] :as params}] [conn {:keys [id username fullname email password] :as params}]
(let [id (or id (uuid/next)) (let [id (or id (uuid/next))
password (sodi.pwhash/derive password) password (sodi.pwhash/derive password)
sqlv [sql:create-user sqlv1 [sql:insert-user id
id fullname username
fullname email password]
username sqlv2 [sql:insert-email id email]]
email (p/let [profile (db/query-one conn sqlv1)]
password]] (db/query-one conn sqlv2)
(db/query-one conn sqlv))) profile)))
(defn register-profile (defn register-profile
[conn params] [conn params]
(-> (create-profile conn params) (-> (create-profile conn params)
(p/then' profile/strip-private-attrs) (p/then' profile/strip-private-attrs)
(p/then (fn [profile] (p/then (fn [profile]
(-> (emails/send! emails/register {:to (:email params) ;; TODO: send a correct link for email verification
:name (:fullname params)}) (p/let [data {:to (:email params)
(p/then' (constantly profile))))))) :name (:fullname params)}]
(emails/send! emails/register data)
profile)))))
(s/def ::register-profile (s/def ::register-profile
(s/keys :req-un [::username ::email ::password ::fullname])) (s/keys :req-un [::username ::email ::password ::fullname]))