diff --git a/README.md b/README.md
index ab4ed3b98e51..3c659de34e49 100644
--- a/README.md
+++ b/README.md
@@ -95,7 +95,13 @@ brew install protobuf
apt-get install protobuf-compiler libopenblas-dev
```
-3. Now, you can build Tabby by running the command `cargo build`.
+3. Install useful tools:
+```bash
+# For Ubuntu
+sudo apt install make sqlite3 graphviz
+```
+
+4. Now, you can build Tabby by running the command `cargo build`.
### Start Hacking!
... and don't forget to submit a [Pull Request](https://github.com/TabbyML/tabby/compare)
diff --git a/ee/tabby-db/migrations/0030_user-name.down.sql b/ee/tabby-db/migrations/0030_user-name.down.sql
new file mode 100644
index 000000000000..1592aac27486
--- /dev/null
+++ b/ee/tabby-db/migrations/0030_user-name.down.sql
@@ -0,0 +1 @@
+ALTER TABLE users DROP COLUMN name;
diff --git a/ee/tabby-db/migrations/0030_user-name.up.sql b/ee/tabby-db/migrations/0030_user-name.up.sql
new file mode 100644
index 000000000000..6bc54f4d339d
--- /dev/null
+++ b/ee/tabby-db/migrations/0030_user-name.up.sql
@@ -0,0 +1 @@
+ALTER TABLE users ADD COLUMN name VARCHAR(255);
diff --git a/ee/tabby-db/schema.sqlite b/ee/tabby-db/schema.sqlite
index 4d5fbbb52d80..66f4681b8d7b 100644
Binary files a/ee/tabby-db/schema.sqlite and b/ee/tabby-db/schema.sqlite differ
diff --git a/ee/tabby-db/schema/schema.sql b/ee/tabby-db/schema/schema.sql
index cd1904ffcd34..61ace098e575 100644
--- a/ee/tabby-db/schema/schema.sql
+++ b/ee/tabby-db/schema/schema.sql
@@ -24,6 +24,7 @@ CREATE TABLE users(
active BOOLEAN NOT NULL DEFAULT 1,
password_encrypted VARCHAR(128),
avatar BLOB DEFAULT NULL,
+ name VARCHAR(255),
CONSTRAINT `idx_email` UNIQUE(`email`)
CONSTRAINT `idx_auth_token` UNIQUE(`auth_token`)
);
diff --git a/ee/tabby-db/schema/schema.svg b/ee/tabby-db/schema/schema.svg
index 37fb06b985e1..b554dd2aa1ba 100644
--- a/ee/tabby-db/schema/schema.svg
+++ b/ee/tabby-db/schema/schema.svg
@@ -392,6 +392,10 @@
avatar
+
+
+
+name
diff --git a/ee/tabby-db/src/users.rs b/ee/tabby-db/src/users.rs
index e31960110b54..3a02cbdb99e8 100644
--- a/ee/tabby-db/src/users.rs
+++ b/ee/tabby-db/src/users.rs
@@ -14,6 +14,7 @@ pub struct UserDAO {
pub id: i64,
pub email: String,
+ pub name: Option,
pub password_encrypted: Option,
pub is_admin: bool,
@@ -28,7 +29,7 @@ macro_rules! select {
($str:literal $(,)? $($val:expr),*) => {
query_as!(
UserDAO,
- r#"SELECT id as "id!", email, password_encrypted, is_admin, created_at as "created_at!", updated_at as "updated_at!", auth_token, active FROM users WHERE "# + $str,
+ r#"SELECT id as "id!", email, name, password_encrypted, is_admin, created_at as "created_at!", updated_at as "updated_at!", auth_token, active FROM users WHERE "# + $str,
$($val),*
)
}
@@ -123,6 +124,7 @@ impl DbConn {
[
"id"!,
"email",
+ "name",
"password_encrypted",
"is_admin",
"created_at"!,
@@ -256,6 +258,13 @@ impl DbConn {
})
.await
}
+
+ pub async fn update_user_name(&self, id: i64, name: String) -> Result<()> {
+ query!("UPDATE users SET name = ? WHERE id = ?;", name, id)
+ .execute(&self.pool)
+ .await?;
+ Ok(())
+ }
}
fn generate_auth_token() -> String {
@@ -292,6 +301,20 @@ mod tests {
assert!(conn.update_user_active(id, false).await.is_err());
}
+ #[tokio::test]
+ async fn test_update_user_name() {
+ let conn = DbConn::new_in_memory().await.unwrap();
+ let id = create_user(&conn).await;
+
+ let user = conn.get_user(id).await.unwrap().unwrap();
+ assert_eq!(user.name, None);
+
+ conn.update_user_name(id, "test".into()).await.unwrap();
+
+ let user = conn.get_user(id).await.unwrap().unwrap();
+ assert_eq!(user.name, Some("test".into()));
+ }
+
#[tokio::test]
async fn test_get_user_by_email() {
let conn = DbConn::new_in_memory().await.unwrap();
diff --git a/ee/tabby-schema/graphql/schema.graphql b/ee/tabby-schema/graphql/schema.graphql
index a5e237cd893b..821ef202a7bc 100644
--- a/ee/tabby-schema/graphql/schema.graphql
+++ b/ee/tabby-schema/graphql/schema.graphql
@@ -322,6 +322,7 @@ type Mutation {
updateUserActive(id: ID!, active: Boolean!): Boolean!
updateUserRole(id: ID!, isAdmin: Boolean!): Boolean!
uploadUserAvatarBase64(id: ID!, avatarBase64: String): Boolean!
+ updateUserName(id: ID!, name: String!): Boolean!
register(email: String!, password1: String!, password2: String!, invitationCode: String): RegisterResponse!
tokenAuth(email: String!, password: String!): TokenAuthResponse!
verifyToken(token: String!): Boolean!
@@ -445,6 +446,7 @@ type TokenAuthResponse {
type User {
id: ID!
email: String!
+ name: String!
isAdmin: Boolean!
isOwner: Boolean!
authToken: String!
diff --git a/ee/tabby-schema/src/dao.rs b/ee/tabby-schema/src/dao.rs
index eaefdfe96154..93fa9b283156 100644
--- a/ee/tabby-schema/src/dao.rs
+++ b/ee/tabby-schema/src/dao.rs
@@ -52,6 +52,7 @@ impl From for auth::User {
auth::User {
id: val.id.as_id(),
email: val.email,
+ name: val.name.unwrap_or_default(),
is_owner,
is_admin: val.is_admin,
auth_token: val.auth_token,
diff --git a/ee/tabby-schema/src/schema/auth.rs b/ee/tabby-schema/src/schema/auth.rs
index d827dbec5a0d..6abc06e2adff 100644
--- a/ee/tabby-schema/src/schema/auth.rs
+++ b/ee/tabby-schema/src/schema/auth.rs
@@ -175,6 +175,7 @@ impl JWTPayload {
pub struct User {
pub id: juniper::ID,
pub email: String,
+ pub name: String,
pub is_admin: bool,
pub is_owner: bool,
pub auth_token: String,
@@ -278,6 +279,22 @@ pub struct PasswordChangeInput {
pub new_password2: String,
}
+#[derive(Validate)]
+pub struct UpdateUserNameInput {
+ #[validate(length(min = 2, code = "name", message = "Name must be at least 2 characters"))]
+ #[validate(length(
+ max = 20,
+ code = "name",
+ message = "Name must be at most 20 characters"
+ ))]
+ #[validate(regex(
+ code = "name",
+ path = "crate::schema::constants::USERNAME_REGEX",
+ message = "Invalid name, name may contain numbers or special characters which are not supported"
+ ))]
+ pub name: String,
+}
+
#[derive(Debug, Serialize, Deserialize, GraphQLObject)]
#[graphql(context = Context)]
pub struct Invitation {
@@ -406,6 +423,7 @@ pub trait AuthenticationService: Send + Sync {
async fn update_user_role(&self, id: &ID, is_admin: bool) -> Result<()>;
async fn update_user_avatar(&self, id: &ID, avatar: Option>) -> Result<()>;
async fn get_user_avatar(&self, id: &ID) -> Result