1
0

Implement users in API

This commit is contained in:
Jai A
2020-09-28 10:48:15 -07:00
parent cd28a75c86
commit 05235f8385
10 changed files with 188 additions and 22 deletions

View File

@@ -0,0 +1,8 @@
ALTER TABLE users
ADD COLUMN github_id bigint NOT NULL default 0,
ADD COLUMN username varchar(255) NOT NULL default 'username',
ADD COLUMN name varchar(255) NOT NULL default 'John Doe',
ADD COLUMN email varchar(255) NULL default 'johndoe@modrinth.com',
ADD COLUMN avatar_url varchar(500) NOT NULL default '...',
ADD COLUMN bio varchar(160) NOT NULL default 'I make mods!',
ADD COLUMN created timestamptz default CURRENT_TIMESTAMP NOT NULL

View File

@@ -77,9 +77,16 @@ generate_ids!(
pub generate_state_id,
StateId,
8,
"SELECT EXISTS(SELECT 1 FROM mods WHERE id=$1)",
"SELECT EXISTS(SELECT 1 FROM states WHERE id=$1)",
StateId
);
generate_ids!(
pub generate_user_id,
UserId,
8,
"SELECT EXISTS(SELECT 1 FROM users WHERE id=$1)",
UserId
);
#[derive(Copy, Clone, Debug, Type)]
#[sqlx(transparent)]

View File

@@ -8,6 +8,7 @@ pub mod ids;
pub mod mod_item;
pub mod team_item;
pub mod version_item;
pub mod user_item;
pub use ids::*;
pub use mod_item::Mod;
@@ -16,6 +17,7 @@ pub use team_item::TeamMember;
pub use version_item::FileHash;
pub use version_item::Version;
pub use version_item::VersionFile;
pub use user_item::User;
#[derive(Error, Debug)]
pub enum DatabaseError {

View File

@@ -0,0 +1,115 @@
use super::ids::UserId;
pub struct User {
pub id: UserId,
pub github_id: UserId,
pub username: String,
pub name: String,
pub email: Option<String>,
pub avatar_url: String,
pub bio: String,
pub created: chrono::DateTime<chrono::Utc>,
}
impl User {
pub async fn insert(
&self,
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
) -> Result<(), sqlx::error::Error> {
sqlx::query!(
"
INSERT INTO users (
id, github_id, username, name, email,
avatar_url, bio, created
)
VALUES (
$1, $2, $3, $4, $5,
$6, $7, $8
)
",
self.id as UserId,
self.github_id as UserId,
&self.username,
&self.name,
self.email.as_ref(),
&self.avatar_url,
&self.bio,
self.created,
)
.execute(&mut *transaction)
.await?;
Ok(())
}
pub async fn get<'a, 'b, E>(
id: UserId,
executor: E,
) -> Result<Option<Self>, sqlx::error::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
let result = sqlx::query!(
"
SELECT u.github_id, u.name, u.email,
u.avatar_url, u.username, u.bio,
u.created
FROM users u
WHERE u.id = $1
",
id as UserId,
)
.fetch_optional(executor)
.await?;
if let Some(row) = result {
Ok(Some(User {
id,
github_id: UserId(row.github_id),
name: row.name,
email: row.email,
avatar_url: row.avatar_url,
username: row.username,
bio: row.bio,
created: row.created,
}))
} else {
Ok(None)
}
}
pub async fn get_from_github_id<'a, 'b, E>(
github_id: UserId,
executor: E,
) -> Result<Option<Self>, sqlx::error::Error>
where
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
{
let result = sqlx::query!(
"
SELECT u.id, u.name, u.email,
u.avatar_url, u.username, u.bio,
u.created
FROM users u
WHERE u.github_id = $1
",
github_id as UserId,
)
.fetch_optional(executor)
.await?;
if let Some(row) = result {
Ok(Some(User {
id: UserId(row.id),
github_id,
name: row.name,
email: row.email,
avatar_url: row.avatar_url,
username: row.username,
bio: row.bio,
created: row.created,
}))
} else {
Ok(None)
}
}
}

View File

@@ -1,7 +1,8 @@
use thiserror::Error;
pub use super::mods::{ModId, VersionId};
pub use super::teams::{TeamId, UserId};
pub use super::teams::TeamId;
pub use super::users::UserId;
/// Generates a random 64 bit integer that is exactly `n` characters
/// long when encoded as base62.

View File

@@ -2,3 +2,4 @@ pub mod error;
pub mod ids;
pub mod mods;
pub mod teams;
pub mod users;

View File

@@ -1,13 +1,8 @@
use super::ids::Base62Id;
use serde::{Deserialize, Serialize};
use crate::models::users::UserId;
//TODO Implement Item for teams
/// The ID of a specific user, encoded as base62 for usage in the API
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(from = "Base62Id")]
#[serde(into = "Base62Id")]
pub struct UserId(pub u64);
/// The ID of a team
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(from = "Base62Id")]

19
src/models/users.rs Normal file
View File

@@ -0,0 +1,19 @@
use super::ids::Base62Id;
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(from = "Base62Id")]
#[serde(into = "Base62Id")]
pub struct UserId(pub u64);
#[derive(Serialize, Deserialize)]
pub struct User {
pub id: UserId,
pub github_id: UserId,
pub username: String,
pub name: String,
pub email: Option<String>,
pub avatar_url: String,
pub bio: String,
pub created: chrono::DateTime<chrono::Utc>,
}

View File

@@ -5,8 +5,7 @@ use actix_web::{get, HttpResponse};
use actix_web::http::StatusCode;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use serde_json::Value;
use crate::database::models::generate_state_id;
use crate::database::models::{generate_state_id, User, UserId};
use sqlx::postgres::PgPool;
use crate::models::ids::base62_impl::{to_base62, parse_base62};
use chrono::Utc;
@@ -30,7 +29,7 @@ pub enum AuthorizationError {
DatabaseError(#[from] crate::database::models::DatabaseError),
#[error("Error while parsing JSON: {0}")]
SerDeError(#[from] serde_json::Error),
#[error("Error while communicating to GitHub OAuth2")]
#[error("Error while communicating to GitHub OAuth2: {0}")]
GithubError(#[from] reqwest::Error),
#[error("Invalid Authentication credentials")]
InvalidCredentialsError,
@@ -84,14 +83,13 @@ pub struct AccessToken {
pub token_type: String,
}
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Debug)]
pub struct GitHubUser {
pub login: String,
pub id: usize,
pub node_id: String,
pub id: u64,
pub avatar_url: String,
pub gravatar_id: String,
pub url: String,
pub name: String,
pub email: Option<String>,
pub bio: String,
}
@@ -118,7 +116,7 @@ pub async fn init(Query(info): Query<AuthorizationInit>, client: Data<PgPool>) -
let client_id = dotenv::var("GITHUB_CLIENT_ID")?;
let url = format!("https://github.com/login/oauth/authorize?client_id={}&state={}&scope={}", client_id, to_base62(state.0 as u64), "%20repo%20read%3Aorg%20read%3Auser%20user%3Aemail");
Ok(HttpResponse::PermanentRedirect()
Ok(HttpResponse::TemporaryRedirect()
.header("Location", &*url)
.json(AuthorizationInit {
url,
@@ -143,7 +141,6 @@ pub async fn auth_callback(Query(info): Query<Authorization>, client: Data<PgPoo
let now = Utc::now();
let duration = result.expires.signed_duration_since(now);
info!("{:?}", duration.num_seconds());
if duration.num_seconds() < 0 {
return Err(AuthorizationError::InvalidCredentialsError);
}
@@ -176,7 +173,7 @@ pub async fn auth_callback(Query(info): Query<Authorization>, client: Data<PgPoo
.json()
.await?;
let user : Value = client
let user : GitHubUser = client
.get("https://api.github.com/user")
.header(reqwest::header::USER_AGENT, "Modrinth")
.header(reqwest::header::AUTHORIZATION, format!("token {}", token.access_token))
@@ -185,11 +182,32 @@ pub async fn auth_callback(Query(info): Query<Authorization>, client: Data<PgPoo
.json()
.await?;
let user_result = User::get_from_github_id(UserId(user.id as i64), &mut *transaction).await?;
match user_result{
Some(x) => {
info!("{:?}", x.id)
}
None => {
let user_id = crate::database::models::generate_user_id(&mut transaction).await?.into();
User {
id: user_id,
github_id: UserId(user.id as i64),
username: user.login,
name: user.name,
email: user.email,
avatar_url: user.avatar_url,
bio: user.bio,
created: Utc::now()
}.insert(&mut transaction).await?;
}
}
transaction.commit().await?;
let redirect_url = format!("{}?url={}", result.url, token.access_token);
let redirect_url = format!("{}?code={}", result.url, token.access_token);
Ok(HttpResponse::PermanentRedirect()
Ok(HttpResponse::TemporaryRedirect()
.header("Location", &*redirect_url)
.json(AuthorizationInit {
url: redirect_url,

View File

@@ -14,7 +14,7 @@ pub mod indexing;
#[derive(Error, Debug)]
pub enum SearchError {
#[error("Error while connecting to the MeiliSearch database")]
#[error("Error while connecting to the MeiliSearch database: {0}")]
IndexDBError(#[from] meilisearch_sdk::errors::Error),
#[error("Error while serializing or deserializing JSON: {0}")]
SerDeError(#[from] serde_json::Error),