You've already forked AstralRinth
forked from didirus/AstralRinth
Implement users in API
This commit is contained in:
8
migrations/20200928170310_create-users.sql
Normal file
8
migrations/20200928170310_create-users.sql
Normal 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
|
||||
@@ -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)]
|
||||
|
||||
@@ -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 {
|
||||
|
||||
115
src/database/models/user_item.rs
Normal file
115
src/database/models/user_item.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -2,3 +2,4 @@ pub mod error;
|
||||
pub mod ids;
|
||||
pub mod mods;
|
||||
pub mod teams;
|
||||
pub mod users;
|
||||
|
||||
@@ -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
19
src/models/users.rs
Normal 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>,
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user