You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2025-07-29 22:01:14 +03:00
GraphQL schema documentation
This commit is contained in:
@ -1,13 +1,38 @@
|
||||
type Authentication {
|
||||
"""
|
||||
An authentication records when a user enter their credential in a browser
|
||||
session.
|
||||
"""
|
||||
type Authentication implements CreationEvent & Node {
|
||||
"""
|
||||
ID of the object.
|
||||
"""
|
||||
id: ID!
|
||||
"""
|
||||
When the object was created.
|
||||
"""
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
|
||||
type BrowserSession {
|
||||
"""
|
||||
A browser session represents a logged in user in a browser.
|
||||
"""
|
||||
type BrowserSession implements Node & CreationEvent {
|
||||
"""
|
||||
ID of the object.
|
||||
"""
|
||||
id: ID!
|
||||
"""
|
||||
The user logged in this session.
|
||||
"""
|
||||
user: User!
|
||||
"""
|
||||
The most recent authentication of this session.
|
||||
"""
|
||||
lastAuthentication: Authentication
|
||||
"""
|
||||
When the object was created.
|
||||
"""
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
@ -40,20 +65,62 @@ type BrowserSessionEdge {
|
||||
node: BrowserSession!
|
||||
}
|
||||
|
||||
type CompatSession {
|
||||
"""
|
||||
A compat session represents a client session which used the legacy Matrix
|
||||
login API.
|
||||
"""
|
||||
type CompatSession implements Node & CreationEvent {
|
||||
"""
|
||||
ID of the object.
|
||||
"""
|
||||
id: ID!
|
||||
"""
|
||||
The user authorized for this session.
|
||||
"""
|
||||
user: User!
|
||||
"""
|
||||
The Matrix Device ID of this session.
|
||||
"""
|
||||
deviceId: String!
|
||||
"""
|
||||
When the object was created.
|
||||
"""
|
||||
createdAt: DateTime!
|
||||
"""
|
||||
When the session ended.
|
||||
"""
|
||||
finishedAt: DateTime
|
||||
}
|
||||
|
||||
type CompatSsoLogin {
|
||||
"""
|
||||
A compat SSO login represents a login done through the legacy Matrix login
|
||||
API, via the `m.login.sso` login method.
|
||||
"""
|
||||
type CompatSsoLogin implements Node {
|
||||
"""
|
||||
ID of the object.
|
||||
"""
|
||||
id: ID!
|
||||
"""
|
||||
When the object was created.
|
||||
"""
|
||||
createdAt: DateTime!
|
||||
"""
|
||||
The redirect URI used during the login.
|
||||
"""
|
||||
redirectUri: Url!
|
||||
"""
|
||||
When the login was fulfilled, and the user was redirected back to the
|
||||
client.
|
||||
"""
|
||||
fulfilledAt: DateTime
|
||||
"""
|
||||
When the client exchanged the login token sent during the redirection.
|
||||
"""
|
||||
exchangedAt: DateTime
|
||||
"""
|
||||
The compat session which was started by this login.
|
||||
"""
|
||||
session: CompatSession
|
||||
}
|
||||
|
||||
@ -86,6 +153,13 @@ type CompatSsoLoginEdge {
|
||||
node: CompatSsoLogin!
|
||||
}
|
||||
|
||||
interface CreationEvent {
|
||||
"""
|
||||
When the object was created.
|
||||
"""
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
"""
|
||||
Implement the DateTime<Utc> scalar
|
||||
|
||||
@ -96,21 +170,71 @@ scalar DateTime
|
||||
|
||||
|
||||
|
||||
type Oauth2Client {
|
||||
interface Node {
|
||||
"""
|
||||
ID of the object.
|
||||
"""
|
||||
id: ID!
|
||||
}
|
||||
|
||||
"""
|
||||
An OAuth 2.0 client
|
||||
"""
|
||||
type Oauth2Client implements Node {
|
||||
"""
|
||||
ID of the object.
|
||||
"""
|
||||
id: ID!
|
||||
"""
|
||||
OAuth 2.0 client ID
|
||||
"""
|
||||
clientId: String!
|
||||
"""
|
||||
Client name advertised by the client.
|
||||
"""
|
||||
clientName: String
|
||||
"""
|
||||
Client URI advertised by the client.
|
||||
"""
|
||||
clientUri: Url
|
||||
"""
|
||||
Terms of services URI advertised by the client.
|
||||
"""
|
||||
tosUri: Url
|
||||
"""
|
||||
Privacy policy URI advertised by the client.
|
||||
"""
|
||||
policyUri: Url
|
||||
"""
|
||||
List of redirect URIs used for authorization grants by the client.
|
||||
"""
|
||||
redirectUris: [Url!]!
|
||||
}
|
||||
|
||||
type Oauth2Session {
|
||||
"""
|
||||
An OAuth 2.0 session represents a client session which used the OAuth APIs
|
||||
to login.
|
||||
"""
|
||||
type Oauth2Session implements Node {
|
||||
"""
|
||||
ID of the object.
|
||||
"""
|
||||
id: ID!
|
||||
"""
|
||||
OAuth 2.0 client used by this session.
|
||||
"""
|
||||
client: Oauth2Client!
|
||||
"""
|
||||
Scope granted for this session.
|
||||
"""
|
||||
scope: String!
|
||||
"""
|
||||
The browser session which started this OAuth 2.0 session.
|
||||
"""
|
||||
browserSession: BrowserSession!
|
||||
"""
|
||||
User authorized for this session.
|
||||
"""
|
||||
user: User!
|
||||
}
|
||||
|
||||
@ -165,7 +289,10 @@ type PageInfo {
|
||||
endCursor: String
|
||||
}
|
||||
|
||||
type Query {
|
||||
"""
|
||||
The query root of the GraphQL interface.
|
||||
"""
|
||||
type RootQuery {
|
||||
"""
|
||||
Get the current logged in browser session
|
||||
"""
|
||||
@ -182,20 +309,60 @@ URL is a String implementing the [URL Standard](http://url.spec.whatwg.org/)
|
||||
"""
|
||||
scalar Url
|
||||
|
||||
type User {
|
||||
"""
|
||||
A user is an individual's account.
|
||||
"""
|
||||
type User implements Node {
|
||||
"""
|
||||
ID of the object.
|
||||
"""
|
||||
id: ID!
|
||||
"""
|
||||
Username chosen by the user.
|
||||
"""
|
||||
username: String!
|
||||
"""
|
||||
Primary email address of the user.
|
||||
"""
|
||||
primaryEmail: UserEmail
|
||||
"""
|
||||
Get the list of compatibility SSO logins, chronologically sorted
|
||||
"""
|
||||
compatSsoLogins(after: String, before: String, first: Int, last: Int): CompatSsoLoginConnection!
|
||||
"""
|
||||
Get the list of active browser sessions, chronologically sorted
|
||||
"""
|
||||
browserSessions(after: String, before: String, first: Int, last: Int): BrowserSessionConnection!
|
||||
"""
|
||||
Get the list of emails, chronologically sorted
|
||||
"""
|
||||
emails(after: String, before: String, first: Int, last: Int): UserEmailConnection!
|
||||
"""
|
||||
Get the list of OAuth 2.0 sessions, chronologically sorted
|
||||
"""
|
||||
oauth2Sessions(after: String, before: String, first: Int, last: Int): Oauth2SessionConnection!
|
||||
}
|
||||
|
||||
type UserEmail {
|
||||
"""
|
||||
A user email address
|
||||
"""
|
||||
type UserEmail implements CreationEvent & Node {
|
||||
"""
|
||||
ID of the object.
|
||||
"""
|
||||
id: ID!
|
||||
"""
|
||||
Email address
|
||||
"""
|
||||
email: String!
|
||||
"""
|
||||
When the object was created.
|
||||
"""
|
||||
createdAt: DateTime!
|
||||
"""
|
||||
When the email address was confirmed. Is `null` if the email was never
|
||||
verified by the user.
|
||||
"""
|
||||
confirmedAt: DateTime
|
||||
}
|
||||
|
||||
@ -212,6 +379,9 @@ type UserEmailConnection {
|
||||
A list of nodes.
|
||||
"""
|
||||
nodes: [UserEmail!]!
|
||||
"""
|
||||
Identifies the total count of items in the connection.
|
||||
"""
|
||||
totalCount: Int!
|
||||
}
|
||||
|
||||
@ -230,6 +400,6 @@ type UserEmailEdge {
|
||||
}
|
||||
|
||||
schema {
|
||||
query: Query
|
||||
query: RootQuery
|
||||
}
|
||||
|
||||
|
@ -22,36 +22,40 @@
|
||||
#![warn(clippy::pedantic)]
|
||||
#![allow(clippy::module_name_repetitions, clippy::missing_errors_doc)]
|
||||
|
||||
use async_graphql::{Context, EmptyMutation, EmptySubscription};
|
||||
use async_graphql::{Context, Description, EmptyMutation, EmptySubscription};
|
||||
use mas_axum_utils::SessionInfo;
|
||||
use model::CreationEvent;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use self::model::{BrowserSession, User};
|
||||
use self::model::{BrowserSession, Node, User};
|
||||
|
||||
mod model;
|
||||
|
||||
pub type Schema = async_graphql::Schema<Query, EmptyMutation, EmptySubscription>;
|
||||
pub type SchemaBuilder = async_graphql::SchemaBuilder<Query, EmptyMutation, EmptySubscription>;
|
||||
pub type Schema = async_graphql::Schema<RootQuery, EmptyMutation, EmptySubscription>;
|
||||
pub type SchemaBuilder = async_graphql::SchemaBuilder<RootQuery, EmptyMutation, EmptySubscription>;
|
||||
|
||||
#[must_use]
|
||||
pub fn schema_builder() -> SchemaBuilder {
|
||||
async_graphql::Schema::build(Query::new(), EmptyMutation, EmptySubscription)
|
||||
async_graphql::Schema::build(RootQuery::new(), EmptyMutation, EmptySubscription)
|
||||
.register_output_type::<Node>()
|
||||
.register_output_type::<CreationEvent>()
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Query {
|
||||
/// The query root of the GraphQL interface.
|
||||
#[derive(Default, Description)]
|
||||
pub struct RootQuery {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
impl Query {
|
||||
impl RootQuery {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_graphql::Object]
|
||||
impl Query {
|
||||
#[async_graphql::Object(use_type_description)]
|
||||
impl RootQuery {
|
||||
/// Get the current logged in browser session
|
||||
async fn current_browser_session(
|
||||
&self,
|
||||
|
@ -12,12 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use async_graphql::{Object, ID};
|
||||
use async_graphql::{Description, Object, ID};
|
||||
use chrono::{DateTime, Utc};
|
||||
use mas_storage::PostgresqlBackend;
|
||||
|
||||
use super::User;
|
||||
|
||||
/// A browser session represents a logged in user in a browser.
|
||||
#[derive(Description)]
|
||||
pub struct BrowserSession(pub mas_data_model::BrowserSession<PostgresqlBackend>);
|
||||
|
||||
impl From<mas_data_model::BrowserSession<PostgresqlBackend>> for BrowserSession {
|
||||
@ -26,34 +28,43 @@ impl From<mas_data_model::BrowserSession<PostgresqlBackend>> for BrowserSession
|
||||
}
|
||||
}
|
||||
|
||||
#[Object]
|
||||
#[Object(use_type_description)]
|
||||
impl BrowserSession {
|
||||
async fn id(&self) -> ID {
|
||||
/// ID of the object.
|
||||
pub async fn id(&self) -> ID {
|
||||
ID(self.0.data.to_string())
|
||||
}
|
||||
|
||||
/// The user logged in this session.
|
||||
async fn user(&self) -> User {
|
||||
User(self.0.user.clone())
|
||||
}
|
||||
|
||||
/// The most recent authentication of this session.
|
||||
async fn last_authentication(&self) -> Option<Authentication> {
|
||||
self.0.last_authentication.clone().map(Authentication)
|
||||
}
|
||||
|
||||
async fn created_at(&self) -> DateTime<Utc> {
|
||||
/// When the object was created.
|
||||
pub async fn created_at(&self) -> DateTime<Utc> {
|
||||
self.0.created_at
|
||||
}
|
||||
}
|
||||
|
||||
/// An authentication records when a user enter their credential in a browser
|
||||
/// session.
|
||||
#[derive(Description)]
|
||||
pub struct Authentication(pub mas_data_model::Authentication<PostgresqlBackend>);
|
||||
|
||||
#[Object]
|
||||
#[Object(use_type_description)]
|
||||
impl Authentication {
|
||||
async fn id(&self) -> ID {
|
||||
/// ID of the object.
|
||||
pub async fn id(&self) -> ID {
|
||||
ID(self.0.data.to_string())
|
||||
}
|
||||
|
||||
async fn created_at(&self) -> DateTime<Utc> {
|
||||
/// When the object was created.
|
||||
pub async fn created_at(&self) -> DateTime<Utc> {
|
||||
self.0.created_at
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use async_graphql::{Object, ID};
|
||||
use async_graphql::{Description, Object, ID};
|
||||
use chrono::{DateTime, Utc};
|
||||
use mas_data_model::CompatSsoLoginState;
|
||||
use mas_storage::PostgresqlBackend;
|
||||
@ -20,47 +20,63 @@ use url::Url;
|
||||
|
||||
use super::User;
|
||||
|
||||
/// A compat session represents a client session which used the legacy Matrix
|
||||
/// login API.
|
||||
#[derive(Description)]
|
||||
pub struct CompatSession(pub mas_data_model::CompatSession<PostgresqlBackend>);
|
||||
|
||||
#[Object]
|
||||
#[Object(use_type_description)]
|
||||
impl CompatSession {
|
||||
async fn id(&self) -> ID {
|
||||
/// ID of the object.
|
||||
pub async fn id(&self) -> ID {
|
||||
ID(self.0.data.to_string())
|
||||
}
|
||||
|
||||
/// The user authorized for this session.
|
||||
async fn user(&self) -> User {
|
||||
User(self.0.user.clone())
|
||||
}
|
||||
|
||||
/// The Matrix Device ID of this session.
|
||||
async fn device_id(&self) -> &str {
|
||||
self.0.device.as_str()
|
||||
}
|
||||
|
||||
async fn created_at(&self) -> DateTime<Utc> {
|
||||
/// When the object was created.
|
||||
pub async fn created_at(&self) -> DateTime<Utc> {
|
||||
self.0.created_at
|
||||
}
|
||||
|
||||
async fn finished_at(&self) -> Option<DateTime<Utc>> {
|
||||
/// When the session ended.
|
||||
pub async fn finished_at(&self) -> Option<DateTime<Utc>> {
|
||||
self.0.finished_at
|
||||
}
|
||||
}
|
||||
|
||||
/// A compat SSO login represents a login done through the legacy Matrix login
|
||||
/// API, via the `m.login.sso` login method.
|
||||
#[derive(Description)]
|
||||
pub struct CompatSsoLogin(pub mas_data_model::CompatSsoLogin<PostgresqlBackend>);
|
||||
|
||||
#[Object]
|
||||
#[Object(use_type_description)]
|
||||
impl CompatSsoLogin {
|
||||
async fn id(&self) -> ID {
|
||||
/// ID of the object.
|
||||
pub async fn id(&self) -> ID {
|
||||
ID(self.0.data.to_string())
|
||||
}
|
||||
|
||||
async fn created_at(&self) -> DateTime<Utc> {
|
||||
/// When the object was created.
|
||||
pub async fn created_at(&self) -> DateTime<Utc> {
|
||||
self.0.created_at
|
||||
}
|
||||
|
||||
/// The redirect URI used during the login.
|
||||
async fn redirect_uri(&self) -> &Url {
|
||||
&self.0.redirect_uri
|
||||
}
|
||||
|
||||
/// When the login was fulfilled, and the user was redirected back to the
|
||||
/// client.
|
||||
async fn fulfilled_at(&self) -> Option<DateTime<Utc>> {
|
||||
match &self.0.state {
|
||||
CompatSsoLoginState::Pending => None,
|
||||
@ -69,6 +85,7 @@ impl CompatSsoLogin {
|
||||
}
|
||||
}
|
||||
|
||||
/// When the client exchanged the login token sent during the redirection.
|
||||
async fn exchanged_at(&self) -> Option<DateTime<Utc>> {
|
||||
match &self.0.state {
|
||||
CompatSsoLoginState::Pending | CompatSsoLoginState::Fulfilled { .. } => None,
|
||||
@ -76,6 +93,7 @@ impl CompatSsoLogin {
|
||||
}
|
||||
}
|
||||
|
||||
/// The compat session which was started by this login.
|
||||
async fn session(&self) -> Option<CompatSession> {
|
||||
match &self.0.state {
|
||||
CompatSsoLoginState::Pending => None,
|
||||
|
@ -12,6 +12,9 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use async_graphql::{Interface, ID};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
mod browser_sessions;
|
||||
mod compat_sessions;
|
||||
mod cursor;
|
||||
@ -20,7 +23,34 @@ mod users;
|
||||
|
||||
pub use self::{
|
||||
browser_sessions::{Authentication, BrowserSession},
|
||||
compat_sessions::{CompatSession, CompatSsoLogin},
|
||||
cursor::{Cursor, NodeCursor, NodeType},
|
||||
oauth::{OAuth2Client, OAuth2Consent, OAuth2Session},
|
||||
users::{User, UserEmail},
|
||||
};
|
||||
|
||||
#[derive(Interface)]
|
||||
#[graphql(field(name = "id", desc = "ID of the object.", type = "ID"))]
|
||||
pub enum Node {
|
||||
Authentication(Box<Authentication>),
|
||||
BrowserSession(Box<BrowserSession>),
|
||||
CompatSession(Box<CompatSession>),
|
||||
CompatSsoLogin(Box<CompatSsoLogin>),
|
||||
OAuth2Client(Box<OAuth2Client>),
|
||||
OAuth2Session(Box<OAuth2Session>),
|
||||
User(Box<User>),
|
||||
UserEmail(Box<UserEmail>),
|
||||
}
|
||||
|
||||
#[derive(Interface)]
|
||||
#[graphql(field(
|
||||
name = "created_at",
|
||||
desc = "When the object was created.",
|
||||
type = "DateTime<Utc>"
|
||||
))]
|
||||
pub enum CreationEvent {
|
||||
Authentication(Box<Authentication>),
|
||||
CompatSession(Box<CompatSession>),
|
||||
BrowserSession(Box<BrowserSession>),
|
||||
UserEmail(Box<UserEmail>),
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use async_graphql::{Context, Object, ID};
|
||||
use async_graphql::{Context, Description, Object, ID};
|
||||
use mas_storage::{oauth2::client::lookup_client, PostgresqlBackend};
|
||||
use oauth2_types::scope::Scope;
|
||||
use sqlx::PgPool;
|
||||
@ -21,75 +21,97 @@ use url::Url;
|
||||
|
||||
use super::{BrowserSession, User};
|
||||
|
||||
/// An OAuth 2.0 session represents a client session which used the OAuth APIs
|
||||
/// to login.
|
||||
#[derive(Description)]
|
||||
pub struct OAuth2Session(pub mas_data_model::Session<PostgresqlBackend>);
|
||||
|
||||
#[Object]
|
||||
#[Object(use_type_description)]
|
||||
impl OAuth2Session {
|
||||
/// ID of the object.
|
||||
pub async fn id(&self) -> ID {
|
||||
ID(self.0.data.to_string())
|
||||
}
|
||||
|
||||
/// OAuth 2.0 client used by this session.
|
||||
pub async fn client(&self) -> OAuth2Client {
|
||||
OAuth2Client(self.0.client.clone())
|
||||
}
|
||||
|
||||
/// Scope granted for this session.
|
||||
pub async fn scope(&self) -> String {
|
||||
self.0.scope.to_string()
|
||||
}
|
||||
|
||||
/// The browser session which started this OAuth 2.0 session.
|
||||
pub async fn browser_session(&self) -> BrowserSession {
|
||||
BrowserSession(self.0.browser_session.clone())
|
||||
}
|
||||
|
||||
/// User authorized for this session.
|
||||
pub async fn user(&self) -> User {
|
||||
User(self.0.browser_session.user.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// An OAuth 2.0 client
|
||||
#[derive(Description)]
|
||||
pub struct OAuth2Client(pub mas_data_model::Client<PostgresqlBackend>);
|
||||
|
||||
#[Object]
|
||||
#[Object(use_type_description)]
|
||||
impl OAuth2Client {
|
||||
/// ID of the object.
|
||||
pub async fn id(&self) -> ID {
|
||||
ID(self.0.data.to_string())
|
||||
}
|
||||
|
||||
/// OAuth 2.0 client ID
|
||||
pub async fn client_id(&self) -> &str {
|
||||
&self.0.client_id
|
||||
}
|
||||
|
||||
/// Client name advertised by the client.
|
||||
pub async fn client_name(&self) -> Option<&str> {
|
||||
self.0.client_name.as_deref()
|
||||
}
|
||||
|
||||
/// Client URI advertised by the client.
|
||||
pub async fn client_uri(&self) -> Option<&Url> {
|
||||
self.0.client_uri.as_ref()
|
||||
}
|
||||
|
||||
/// Terms of services URI advertised by the client.
|
||||
pub async fn tos_uri(&self) -> Option<&Url> {
|
||||
self.0.tos_uri.as_ref()
|
||||
}
|
||||
|
||||
/// Privacy policy URI advertised by the client.
|
||||
pub async fn policy_uri(&self) -> Option<&Url> {
|
||||
self.0.policy_uri.as_ref()
|
||||
}
|
||||
|
||||
/// List of redirect URIs used for authorization grants by the client.
|
||||
pub async fn redirect_uris(&self) -> &[Url] {
|
||||
&self.0.redirect_uris
|
||||
}
|
||||
}
|
||||
|
||||
/// An OAuth 2.0 consent represents the scope a user consented to grant to a
|
||||
/// client.
|
||||
#[derive(Description)]
|
||||
pub struct OAuth2Consent {
|
||||
scope: Scope,
|
||||
client_id: Ulid,
|
||||
}
|
||||
|
||||
#[Object]
|
||||
#[Object(use_type_description)]
|
||||
impl OAuth2Consent {
|
||||
/// Scope consented by the user for this client.
|
||||
pub async fn scope(&self) -> String {
|
||||
self.scope.to_string()
|
||||
}
|
||||
|
||||
/// OAuth 2.0 client for which the user granted access.
|
||||
pub async fn client(&self, ctx: &Context<'_>) -> Result<OAuth2Client, async_graphql::Error> {
|
||||
let mut conn = ctx.data::<PgPool>()?.acquire().await?;
|
||||
let client = lookup_client(&mut conn, self.client_id).await?;
|
||||
|
@ -14,7 +14,7 @@
|
||||
|
||||
use async_graphql::{
|
||||
connection::{query, Connection, Edge, OpaqueCursor},
|
||||
Context, Object, ID,
|
||||
Context, Description, Object, ID,
|
||||
};
|
||||
use chrono::{DateTime, Utc};
|
||||
use mas_storage::PostgresqlBackend;
|
||||
@ -24,6 +24,8 @@ use super::{
|
||||
compat_sessions::CompatSsoLogin, BrowserSession, Cursor, NodeCursor, NodeType, OAuth2Session,
|
||||
};
|
||||
|
||||
#[derive(Description)]
|
||||
/// A user is an individual's account.
|
||||
pub struct User(pub mas_data_model::User<PostgresqlBackend>);
|
||||
|
||||
impl From<mas_data_model::User<PostgresqlBackend>> for User {
|
||||
@ -38,27 +40,34 @@ impl From<mas_data_model::BrowserSession<PostgresqlBackend>> for User {
|
||||
}
|
||||
}
|
||||
|
||||
#[Object]
|
||||
#[Object(use_type_description)]
|
||||
impl User {
|
||||
async fn id(&self) -> ID {
|
||||
/// ID of the object.
|
||||
pub async fn id(&self) -> ID {
|
||||
ID(self.0.data.to_string())
|
||||
}
|
||||
|
||||
/// Username chosen by the user.
|
||||
async fn username(&self) -> &str {
|
||||
&self.0.username
|
||||
}
|
||||
|
||||
/// Primary email address of the user.
|
||||
async fn primary_email(&self) -> Option<UserEmail> {
|
||||
self.0.primary_email.clone().map(UserEmail)
|
||||
}
|
||||
|
||||
/// Get the list of compatibility SSO logins, chronologically sorted
|
||||
async fn compat_sso_logins(
|
||||
&self,
|
||||
ctx: &Context<'_>,
|
||||
|
||||
#[graphql(desc = "Returns the elements in the list that come after the cursor.")]
|
||||
after: Option<String>,
|
||||
#[graphql(desc = "Returns the elements in the list that come before the cursor.")]
|
||||
before: Option<String>,
|
||||
first: Option<i32>,
|
||||
last: Option<i32>,
|
||||
#[graphql(desc = "Returns the first *n* elements from the list.")] first: Option<i32>,
|
||||
#[graphql(desc = "Returns the last *n* elements from the list.")] last: Option<i32>,
|
||||
) -> Result<Connection<Cursor, CompatSsoLogin>, async_graphql::Error> {
|
||||
let database = ctx.data::<PgPool>()?;
|
||||
|
||||
@ -96,13 +105,17 @@ impl User {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the list of active browser sessions, chronologically sorted
|
||||
async fn browser_sessions(
|
||||
&self,
|
||||
ctx: &Context<'_>,
|
||||
|
||||
#[graphql(desc = "Returns the elements in the list that come after the cursor.")]
|
||||
after: Option<String>,
|
||||
#[graphql(desc = "Returns the elements in the list that come before the cursor.")]
|
||||
before: Option<String>,
|
||||
first: Option<i32>,
|
||||
last: Option<i32>,
|
||||
#[graphql(desc = "Returns the first *n* elements from the list.")] first: Option<i32>,
|
||||
#[graphql(desc = "Returns the last *n* elements from the list.")] last: Option<i32>,
|
||||
) -> Result<Connection<Cursor, BrowserSession>, async_graphql::Error> {
|
||||
let database = ctx.data::<PgPool>()?;
|
||||
|
||||
@ -140,13 +153,17 @@ impl User {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the list of emails, chronologically sorted
|
||||
async fn emails(
|
||||
&self,
|
||||
ctx: &Context<'_>,
|
||||
|
||||
#[graphql(desc = "Returns the elements in the list that come after the cursor.")]
|
||||
after: Option<String>,
|
||||
#[graphql(desc = "Returns the elements in the list that come before the cursor.")]
|
||||
before: Option<String>,
|
||||
first: Option<i32>,
|
||||
last: Option<i32>,
|
||||
#[graphql(desc = "Returns the first *n* elements from the list.")] first: Option<i32>,
|
||||
#[graphql(desc = "Returns the last *n* elements from the list.")] last: Option<i32>,
|
||||
) -> Result<Connection<Cursor, UserEmail, UserEmailsPagination>, async_graphql::Error> {
|
||||
let database = ctx.data::<PgPool>()?;
|
||||
|
||||
@ -188,13 +205,17 @@ impl User {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Get the list of OAuth 2.0 sessions, chronologically sorted
|
||||
async fn oauth2_sessions(
|
||||
&self,
|
||||
ctx: &Context<'_>,
|
||||
|
||||
#[graphql(desc = "Returns the elements in the list that come after the cursor.")]
|
||||
after: Option<String>,
|
||||
#[graphql(desc = "Returns the elements in the list that come before the cursor.")]
|
||||
before: Option<String>,
|
||||
first: Option<i32>,
|
||||
last: Option<i32>,
|
||||
#[graphql(desc = "Returns the first *n* elements from the list.")] first: Option<i32>,
|
||||
#[graphql(desc = "Returns the last *n* elements from the list.")] last: Option<i32>,
|
||||
) -> Result<Connection<Cursor, OAuth2Session>, async_graphql::Error> {
|
||||
let database = ctx.data::<PgPool>()?;
|
||||
|
||||
@ -233,22 +254,29 @@ impl User {
|
||||
}
|
||||
}
|
||||
|
||||
/// A user email address
|
||||
#[derive(Description)]
|
||||
pub struct UserEmail(mas_data_model::UserEmail<PostgresqlBackend>);
|
||||
|
||||
#[Object]
|
||||
#[Object(use_type_description)]
|
||||
impl UserEmail {
|
||||
async fn id(&self) -> ID {
|
||||
/// ID of the object.
|
||||
pub async fn id(&self) -> ID {
|
||||
ID(self.0.data.to_string())
|
||||
}
|
||||
|
||||
/// Email address
|
||||
async fn email(&self) -> &str {
|
||||
&self.0.email
|
||||
}
|
||||
|
||||
async fn created_at(&self) -> DateTime<Utc> {
|
||||
/// When the object was created.
|
||||
pub async fn created_at(&self) -> DateTime<Utc> {
|
||||
self.0.created_at
|
||||
}
|
||||
|
||||
/// When the email address was confirmed. Is `null` if the email was never
|
||||
/// verified by the user.
|
||||
async fn confirmed_at(&self) -> Option<DateTime<Utc>> {
|
||||
self.0.confirmed_at
|
||||
}
|
||||
@ -258,6 +286,7 @@ pub struct UserEmailsPagination(mas_data_model::User<PostgresqlBackend>);
|
||||
|
||||
#[Object]
|
||||
impl UserEmailsPagination {
|
||||
/// Identifies the total count of items in the connection.
|
||||
async fn total_count(&self, ctx: &Context<'_>) -> Result<i64, async_graphql::Error> {
|
||||
let mut conn = ctx.data::<PgPool>()?.acquire().await?;
|
||||
let count = mas_storage::user::count_user_emails(&mut conn, &self.0).await?;
|
||||
|
@ -13,9 +13,16 @@
|
||||
// limitations under the License.
|
||||
|
||||
use sqlx::{Database, QueryBuilder};
|
||||
use thiserror::Error;
|
||||
use ulid::Ulid;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[error("Either 'first' or 'last' must be specified")]
|
||||
pub struct InvalidPagination;
|
||||
|
||||
/// Add cursor-based pagination to a query, as used in paginated GraphQL
|
||||
/// connections
|
||||
pub fn generate_pagination<'a, DB>(
|
||||
query: &mut QueryBuilder<'a, DB>,
|
||||
id_field: &'static str,
|
||||
@ -23,7 +30,7 @@ pub fn generate_pagination<'a, DB>(
|
||||
after: Option<Ulid>,
|
||||
first: Option<usize>,
|
||||
last: Option<usize>,
|
||||
) -> Result<(), anyhow::Error>
|
||||
) -> Result<(), InvalidPagination>
|
||||
where
|
||||
DB: Database,
|
||||
Uuid: sqlx::Type<DB> + sqlx::Encode<'a, DB>,
|
||||
@ -69,20 +76,21 @@ where
|
||||
.push(" DESC LIMIT ")
|
||||
.push_bind((count + 1) as i64);
|
||||
} else {
|
||||
anyhow::bail!("Either 'first' or 'last' must be specified");
|
||||
return Err(InvalidPagination);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Process a page returned by a paginated query
|
||||
pub fn process_page<T>(
|
||||
mut page: Vec<T>,
|
||||
first: Option<usize>,
|
||||
last: Option<usize>,
|
||||
) -> Result<(bool, bool, Vec<T>), anyhow::Error> {
|
||||
) -> Result<(bool, bool, Vec<T>), InvalidPagination> {
|
||||
let limit = match (first, last) {
|
||||
(Some(count), _) | (_, Some(count)) => count,
|
||||
_ => anyhow::bail!("Either 'first' or 'last' must be specified"),
|
||||
_ => return Err(InvalidPagination),
|
||||
};
|
||||
|
||||
let is_full = page.len() == (limit + 1);
|
||||
|
Reference in New Issue
Block a user