diff --git a/apps/dashboard/components/api-keys/list.tsx b/apps/dashboard/components/api-keys/list.tsx index c28bbd94f5..224fbd592f 100644 --- a/apps/dashboard/components/api-keys/list.tsx +++ b/apps/dashboard/components/api-keys/list.tsx @@ -11,13 +11,13 @@ import RevokeKey from "./revoke" import { apiKeys } from "@/services/graphql/queries/api-keys" import { authOptions } from "@/app/api/auth/[...nextauth]/route" -const formatDate = (dateStr: string): string => { +const formatDate = (timestamp: string): string => { const options: Intl.DateTimeFormatOptions = { year: "numeric", month: "long", day: "2-digit", } - return new Date(dateStr).toLocaleDateString(undefined, options) + return new Date(parseInt(timestamp) * 1000).toLocaleDateString(undefined, options) } const ApiKeysList = async () => { diff --git a/apps/dashboard/services/graphql/generated.ts b/apps/dashboard/services/graphql/generated.ts index d0faccc9fd..9fb623d4f3 100644 --- a/apps/dashboard/services/graphql/generated.ts +++ b/apps/dashboard/services/graphql/generated.ts @@ -212,6 +212,7 @@ export type ApiKey = { }; export type ApiKeyCreateInput = { + readonly expireInDays?: InputMaybe; readonly name: Scalars['String']['input']; }; diff --git a/core/api-keys/src/graphql/convert.rs b/core/api-keys/src/graphql/convert.rs index ec11eb7d7d..43f42435b4 100644 --- a/core/api-keys/src/graphql/convert.rs +++ b/core/api-keys/src/graphql/convert.rs @@ -17,9 +17,9 @@ impl From for ApiKey { name: key.name, revoked: key.revoked, expired: key.expired, - last_used_at: key.last_used_at, - created_at: key.created_at, - expires_at: key.expires_at, + last_used_at: key.last_used_at.map(Into::into), + created_at: key.created_at.into(), + expires_at: key.expires_at.into(), } } } diff --git a/core/api-keys/src/graphql/schema.rs b/core/api-keys/src/graphql/schema.rs index dcb3f07c45..60ca5935c1 100644 --- a/core/api-keys/src/graphql/schema.rs +++ b/core/api-keys/src/graphql/schema.rs @@ -1,5 +1,5 @@ use async_graphql::*; -use chrono::{DateTime, Utc}; +use chrono::{DateTime, TimeZone, Utc}; use crate::{app::ApiKeysApp, identity::IdentityApiKeyId}; @@ -7,6 +7,42 @@ pub struct AuthSubject { pub id: String, } +#[derive(Clone, Copy)] +pub struct Timestamp(DateTime); + +impl From> for Timestamp { + fn from(dt: DateTime) -> Self { + Timestamp(dt) + } +} + +impl Into> for Timestamp { + fn into(self) -> DateTime { + self.0 + } +} + +#[Scalar(name = "Timestamp")] +impl ScalarType for Timestamp { + fn parse(value: async_graphql::Value) -> async_graphql::InputValueResult { + let epoch = match &value { + async_graphql::Value::Number(n) => n + .as_i64() + .ok_or_else(|| async_graphql::InputValueError::expected_type(value)), + _ => Err(async_graphql::InputValueError::expected_type(value)), + }?; + + Utc.timestamp_opt(epoch, 0) + .single() + .map(Timestamp) + .ok_or_else(|| async_graphql::InputValueError::custom("Invalid timestamp")) + } + + fn to_value(&self) -> async_graphql::Value { + async_graphql::Value::Number(self.0.timestamp().into()) + } +} + pub struct Query; #[Object] @@ -21,11 +57,11 @@ impl Query { pub(super) struct ApiKey { pub id: ID, pub name: String, - pub created_at: DateTime, + pub created_at: Timestamp, pub revoked: bool, pub expired: bool, - pub last_used_at: Option>, - pub expires_at: DateTime, + pub last_used_at: Option, + pub expires_at: Timestamp, } #[derive(SimpleObject)] diff --git a/core/api-keys/subgraph/schema.graphql b/core/api-keys/subgraph/schema.graphql index b55aa23cf4..e575dd9394 100644 --- a/core/api-keys/subgraph/schema.graphql +++ b/core/api-keys/subgraph/schema.graphql @@ -1,11 +1,11 @@ type ApiKey { id: ID! name: String! - createdAt: DateTime! + createdAt: Timestamp! revoked: Boolean! expired: Boolean! - lastUsedAt: DateTime - expiresAt: DateTime! + lastUsedAt: Timestamp + expiresAt: Timestamp! } input ApiKeyCreateInput { @@ -23,13 +23,6 @@ input ApiKeyRevokeInput { } -""" -Implement the DateTime scalar - -The input/output is a string in RFC3339 format. -""" -scalar DateTime - @@ -40,6 +33,8 @@ type Mutation { +scalar Timestamp + extend type User @key(fields: "id") { id: ID! @external apiKeys: [ApiKey!]! diff --git a/core/api/dev/apollo-federation/supergraph.graphql b/core/api/dev/apollo-federation/supergraph.graphql index 7a459a2bf8..2309908466 100644 --- a/core/api/dev/apollo-federation/supergraph.graphql +++ b/core/api/dev/apollo-federation/supergraph.graphql @@ -160,11 +160,11 @@ type ApiKey { id: ID! name: String! - createdAt: DateTime! + createdAt: Timestamp! revoked: Boolean! expired: Boolean! - lastUsedAt: DateTime - expiresAt: DateTime! + lastUsedAt: Timestamp + expiresAt: Timestamp! } input ApiKeyCreateInput @@ -398,14 +398,6 @@ type Currency symbol: String! } -""" -Implement the DateTime scalar - -The input/output is a string in RFC3339 format. -""" -scalar DateTime - @join__type(graph: API_KEYS) - type DepositFeesInformation @join__type(graph: PUBLIC) { @@ -1537,6 +1529,7 @@ type SuccessPayload Timestamp field, serialized as Unix time (the number of seconds since the Unix epoch) """ scalar Timestamp + @join__type(graph: API_KEYS) @join__type(graph: PUBLIC) """A time-based one-time password""" diff --git a/dev/config/apollo-federation/supergraph.graphql b/dev/config/apollo-federation/supergraph.graphql index 7a459a2bf8..2309908466 100644 --- a/dev/config/apollo-federation/supergraph.graphql +++ b/dev/config/apollo-federation/supergraph.graphql @@ -160,11 +160,11 @@ type ApiKey { id: ID! name: String! - createdAt: DateTime! + createdAt: Timestamp! revoked: Boolean! expired: Boolean! - lastUsedAt: DateTime - expiresAt: DateTime! + lastUsedAt: Timestamp + expiresAt: Timestamp! } input ApiKeyCreateInput @@ -398,14 +398,6 @@ type Currency symbol: String! } -""" -Implement the DateTime scalar - -The input/output is a string in RFC3339 format. -""" -scalar DateTime - @join__type(graph: API_KEYS) - type DepositFeesInformation @join__type(graph: PUBLIC) { @@ -1537,6 +1529,7 @@ type SuccessPayload Timestamp field, serialized as Unix time (the number of seconds since the Unix epoch) """ scalar Timestamp + @join__type(graph: API_KEYS) @join__type(graph: PUBLIC) """A time-based one-time password"""