core: type workspace and secret timestamps

This commit is contained in:
a.tolmachev
2026-04-13 05:59:11 +00:00
parent 03cb109b40
commit c736b4f5a8
17 changed files with 243 additions and 139 deletions
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "select\n id,\n slug,\n display_name,\n status,\n settings_json,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\"\n from workspaces\n where id = $1",
"query": "select\n id,\n slug,\n display_name,\n status,\n settings_json,\n created_at as \"created_at!: time::OffsetDateTime\",\n updated_at as \"updated_at!: time::OffsetDateTime\"\n from workspaces\n where id = $1",
"describe": {
"columns": [
{
@@ -30,13 +30,13 @@
},
{
"ordinal": 5,
"name": "created_at!",
"type_info": "Text"
"name": "created_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
},
{
"ordinal": 6,
"name": "updated_at!",
"type_info": "Text"
"name": "updated_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
}
],
"parameters": {
@@ -50,9 +50,9 @@
false,
false,
false,
null,
null
false,
false
]
},
"hash": "925b63209407fe86b78bf5458b1a17253db129a7a13a6c7bc4db4f439244009c"
"hash": "56cf7fc3b89517095c52a30c091a040415a5f6eef589e9a264b9b1fd687353b7"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "select\n w.id,\n w.slug,\n w.display_name,\n w.status,\n w.settings_json,\n to_char(w.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(w.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",\n m.role\n from memberships m\n join workspaces w on w.id = m.workspace_id\n where m.user_id = $1\n order by w.slug asc",
"query": "select\n w.id,\n w.slug,\n w.display_name,\n w.status,\n w.settings_json,\n w.created_at as \"created_at!: time::OffsetDateTime\",\n w.updated_at as \"updated_at!: time::OffsetDateTime\",\n m.role\n from memberships m\n join workspaces w on w.id = m.workspace_id\n where m.user_id = $1\n order by w.slug asc",
"describe": {
"columns": [
{
@@ -30,13 +30,13 @@
},
{
"ordinal": 5,
"name": "created_at!",
"type_info": "Text"
"name": "created_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
},
{
"ordinal": 6,
"name": "updated_at!",
"type_info": "Text"
"name": "updated_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
},
{
"ordinal": 7,
@@ -55,10 +55,10 @@
false,
false,
false,
null,
null,
false,
false,
false
]
},
"hash": "4206303b16bfbe2abc436d00ad5da3358246a78c0bb283321c9441e7eea68467"
"hash": "92bd2268efce8ba515b1d502287dea62bdfae0ba66100589ead3f986244b4cbb"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "select\n m.workspace_id,\n m.user_id,\n m.role,\n to_char(m.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n u.email,\n u.display_name,\n u.status,\n u.created_at as \"user_created_at!: OffsetDateTime\"\n from memberships m\n join users u on u.id = m.user_id\n where m.workspace_id = $1\n order by u.email asc",
"query": "select\n m.workspace_id,\n m.user_id,\n m.role,\n m.created_at as \"created_at!: time::OffsetDateTime\",\n u.email,\n u.display_name,\n u.status,\n u.created_at as \"user_created_at!: time::OffsetDateTime\"\n from memberships m\n join users u on u.id = m.user_id\n where m.workspace_id = $1\n order by u.email asc",
"describe": {
"columns": [
{
@@ -20,8 +20,8 @@
},
{
"ordinal": 3,
"name": "created_at!",
"type_info": "Text"
"name": "created_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
},
{
"ordinal": 4,
@@ -40,7 +40,7 @@
},
{
"ordinal": 7,
"name": "user_created_at!: OffsetDateTime",
"name": "user_created_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
}
],
@@ -53,12 +53,12 @@
false,
false,
false,
null,
false,
false,
false,
false,
false
]
},
"hash": "ee22d5c528a095d0223aa0cb8a30b6ebce6498d375efc7b7a6227c5de1d24823"
"hash": "c3002576245c677598f401b5db517f9c01c9e5cbc1eea5511005d572a0776edc"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "select\n id,\n workspace_id,\n name,\n kind,\n status,\n current_version,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",\n to_char(last_used_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_used_at\n from secrets\n where workspace_id = $1 and id = $2",
"query": "select\n id,\n workspace_id,\n name,\n kind,\n status,\n current_version,\n created_at as \"created_at!: time::OffsetDateTime\",\n updated_at as \"updated_at!: time::OffsetDateTime\",\n last_used_at as \"last_used_at: time::OffsetDateTime\"\n from secrets\n where workspace_id = $1 and id = $2",
"describe": {
"columns": [
{
@@ -35,18 +35,18 @@
},
{
"ordinal": 6,
"name": "created_at!",
"type_info": "Text"
"name": "created_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
},
{
"ordinal": 7,
"name": "updated_at!",
"type_info": "Text"
"name": "updated_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
},
{
"ordinal": 8,
"name": "last_used_at",
"type_info": "Text"
"name": "last_used_at: time::OffsetDateTime",
"type_info": "Timestamptz"
}
],
"parameters": {
@@ -62,10 +62,10 @@
false,
false,
false,
null,
null,
null
false,
false,
true
]
},
"hash": "4fcd20a68487da32614c2d7ad6152e35c0251e45ed744b3c4883e7ab8dd64747"
"hash": "d5b882fd05f2db2e766207064bd46ad2ffe665efc5f11252136bee340a3a4a89"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "select\n id,\n slug,\n display_name,\n status,\n settings_json,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\"\n from workspaces\n order by slug asc",
"query": "select\n id,\n slug,\n display_name,\n status,\n settings_json,\n created_at as \"created_at!: time::OffsetDateTime\",\n updated_at as \"updated_at!: time::OffsetDateTime\"\n from workspaces\n order by slug asc",
"describe": {
"columns": [
{
@@ -30,13 +30,13 @@
},
{
"ordinal": 5,
"name": "created_at!",
"type_info": "Text"
"name": "created_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
},
{
"ordinal": 6,
"name": "updated_at!",
"type_info": "Text"
"name": "updated_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
}
],
"parameters": {
@@ -48,9 +48,9 @@
false,
false,
false,
null,
null
false,
false
]
},
"hash": "0701c93547a515170803136ed093c06df78d08e83c9f2eee241204820fd20f5c"
"hash": "d6cd575c05dabaa09177985375c59ce42857433dd11580659481c562f9d9ab3f"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "select\n id,\n workspace_id,\n name,\n kind,\n status,\n current_version,\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",\n to_char(last_used_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_used_at\n from secrets\n where workspace_id = $1\n order by name asc",
"query": "select\n id,\n workspace_id,\n name,\n kind,\n status,\n current_version,\n created_at as \"created_at!: time::OffsetDateTime\",\n updated_at as \"updated_at!: time::OffsetDateTime\",\n last_used_at as \"last_used_at: time::OffsetDateTime\"\n from secrets\n where workspace_id = $1\n order by name asc",
"describe": {
"columns": [
{
@@ -35,18 +35,18 @@
},
{
"ordinal": 6,
"name": "created_at!",
"type_info": "Text"
"name": "created_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
},
{
"ordinal": 7,
"name": "updated_at!",
"type_info": "Text"
"name": "updated_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
},
{
"ordinal": 8,
"name": "last_used_at",
"type_info": "Text"
"name": "last_used_at: time::OffsetDateTime",
"type_info": "Timestamptz"
}
],
"parameters": {
@@ -61,10 +61,10 @@
false,
false,
false,
null,
null,
null
false,
false,
true
]
},
"hash": "4b6bd8192eea43df8d50504e86e9517becbccf7462aa3008c356f7ef93cd73b4"
"hash": "dcde0691b13dbab00364f4142b95a2785c0a612ff739e341e28d0831b7170f6b"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "select\n id,\n workspace_id,\n email,\n role,\n status,\n token_hash,\n to_char(expires_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"expires_at!\",\n to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\"\n from invitation_tokens\n where workspace_id = $1\n order by created_at desc",
"query": "select\n id,\n workspace_id,\n email,\n role,\n status,\n token_hash,\n expires_at as \"expires_at!: time::OffsetDateTime\",\n created_at as \"created_at!: time::OffsetDateTime\"\n from invitation_tokens\n where workspace_id = $1\n order by created_at desc",
"describe": {
"columns": [
{
@@ -35,13 +35,13 @@
},
{
"ordinal": 6,
"name": "expires_at!",
"type_info": "Text"
"name": "expires_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
},
{
"ordinal": 7,
"name": "created_at!",
"type_info": "Text"
"name": "created_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
}
],
"parameters": {
@@ -56,9 +56,9 @@
false,
false,
false,
null,
null
false,
false
]
},
"hash": "b69d55c4b0352cac007496828e2d08369749bac52d2076c3ba679c4aaa466fc9"
"hash": "e9fc9fdf8ef885ab247d903dbde0a2f07bc89aec46cdbe1e856700fa57674f41"
}
@@ -1,6 +1,6 @@
{
"db_name": "PostgreSQL",
"query": "select\n sv.secret_id,\n sv.version,\n sv.ciphertext,\n sv.key_version,\n to_char(sv.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",\n sv.created_by\n from secrets s\n join secret_versions sv\n on sv.secret_id = s.id and sv.version = s.current_version\n where s.workspace_id = $1 and s.id = $2",
"query": "select\n sv.secret_id,\n sv.version,\n sv.ciphertext,\n sv.key_version,\n sv.created_at as \"created_at!: time::OffsetDateTime\",\n sv.created_by\n from secrets s\n join secret_versions sv\n on sv.secret_id = s.id and sv.version = s.current_version\n where s.workspace_id = $1 and s.id = $2",
"describe": {
"columns": [
{
@@ -25,8 +25,8 @@
},
{
"ordinal": 4,
"name": "created_at!",
"type_info": "Text"
"name": "created_at!: time::OffsetDateTime",
"type_info": "Timestamptz"
},
{
"ordinal": 5,
@@ -45,9 +45,9 @@
false,
false,
false,
null,
false,
true
]
},
"hash": "d50ada14acca676613ea3312e92c73a85fd24606e88c9b07fc33535690b540d1"
"hash": "fb0bec06d2a3dbbf455947346eea5022c7ddac2ffe2a1969b2c6a2ed72c3730a"
}
+17 -18
View File
@@ -870,14 +870,14 @@ impl AdminService {
user_id: &crank_core::UserId,
payload: WorkspacePayload,
) -> Result<WorkspaceRecord, ApiError> {
let now = now_string()?;
let now = OffsetDateTime::now_utc();
let workspace = Workspace {
id: WorkspaceId::new(new_prefixed_id("ws")),
slug: payload.slug,
display_name: payload.display_name,
status: WorkspaceStatus::Active,
settings: payload.settings,
created_at: now.clone(),
created_at: now,
updated_at: now,
};
@@ -1034,7 +1034,7 @@ impl AdminService {
status: payload.status.unwrap_or(existing.status),
settings: payload.settings.unwrap_or(existing.settings),
created_at: existing.created_at,
updated_at: now_string()?,
updated_at: OffsetDateTime::now_utc(),
};
self.registry
@@ -1183,10 +1183,10 @@ impl AdminService {
status: InvitationStatus::Pending,
token_hash: hash_access_secret(&invite_token),
expires_at: match payload.expires_at {
Some(expires_at) => expires_at,
Some(expires_at) => parse_timestamp(&expires_at)?,
None => default_invitation_expiry()?,
},
created_at: now_string()?,
created_at: OffsetDateTime::now_utc(),
},
};
@@ -2362,9 +2362,7 @@ impl AdminService {
auth_profile: &AuthProfile,
) -> Result<ResolvedAuth, RuntimeError> {
let mut secrets = BTreeMap::new();
let used_at = now_string().map_err(|error| RuntimeError::SecretCrypto {
details: error.to_string(),
})?;
let used_at = OffsetDateTime::now_utc();
for secret_id in auth_profile.config.secret_ids() {
let secret = self
@@ -2451,7 +2449,7 @@ impl AdminService {
self.ensure_workspace_exists(workspace_id).await?;
validate_secret_payload(&payload)?;
let now = now_string()?;
let now = OffsetDateTime::now_utc();
let secret = Secret {
id: SecretId::new(new_prefixed_id("secret")),
workspace_id: workspace_id.clone(),
@@ -2459,7 +2457,7 @@ impl AdminService {
kind: payload.kind,
status: SecretStatus::Active,
current_version: 1,
created_at: now.clone(),
created_at: now,
updated_at: now,
last_used_at: None,
};
@@ -2494,7 +2492,7 @@ impl AdminService {
return Err(ApiError::validation("secret value must not be null"));
}
let now = now_string()?;
let now = OffsetDateTime::now_utc();
let ciphertext = self
.secret_crypto
.encrypt(&payload.value)
@@ -4615,6 +4613,11 @@ fn now_string() -> Result<String, ApiError> {
.map_err(|error| ApiError::internal(error.to_string()))
}
fn parse_timestamp(value: &str) -> Result<OffsetDateTime, ApiError> {
OffsetDateTime::parse(value, &Rfc3339)
.map_err(|_| ApiError::validation("timestamp must be RFC 3339"))
}
fn format_timestamp(timestamp: OffsetDateTime) -> String {
timestamp
.format(&Rfc3339)
@@ -4642,9 +4645,7 @@ async fn resolve_runtime_auth_for_task(
})?;
let mut secrets = BTreeMap::new();
let used_at = now_string().map_err(|error| RuntimeError::SecretCrypto {
details: error.to_string(),
})?;
let used_at = OffsetDateTime::now_utc();
for secret_id in auth_profile.config.secret_ids() {
let secret = registry
@@ -4682,12 +4683,10 @@ async fn resolve_runtime_auth_for_task(
ResolvedAuth::from_profile(&auth_profile, &secrets).map(Some)
}
fn default_invitation_expiry() -> Result<String, ApiError> {
fn default_invitation_expiry() -> Result<OffsetDateTime, ApiError> {
OffsetDateTime::now_utc()
.checked_add(time::Duration::days(7))
.ok_or_else(|| ApiError::internal("failed to compute invitation expiry"))?
.format(&Rfc3339)
.map_err(|error| ApiError::internal(error.to_string()))
.ok_or_else(|| ApiError::internal("failed to compute invitation expiry"))
}
fn runtime_error_code(error: &RuntimeError) -> &'static str {
+1 -5
View File
@@ -560,7 +560,7 @@ async fn resolve_auth_profile(
auth_profile: &AuthProfile,
) -> Result<ResolvedAuth, RuntimeError> {
let mut secrets = BTreeMap::new();
let used_at = now_rfc3339();
let used_at = OffsetDateTime::now_utc();
for secret_id in auth_profile.config.secret_ids() {
let secret = registry
@@ -1918,10 +1918,6 @@ fn format_rfc3339(timestamp: OffsetDateTime) -> String {
.unwrap_or_else(|_| "1970-01-01T00:00:00Z".to_owned())
}
fn now_rfc3339() -> String {
format_rfc3339(OffsetDateTime::now_utc())
}
fn add_millis(timestamp: OffsetDateTime, millis: u64) -> OffsetDateTime {
let delta = time::Duration::milliseconds(i64::try_from(millis).unwrap_or(i64::MAX));
+6 -3
View File
@@ -58,7 +58,8 @@ pub struct Membership {
pub workspace_id: WorkspaceId,
pub user_id: UserId,
pub role: MembershipRole,
pub created_at: String,
#[serde(with = "time::serde::rfc3339")]
pub created_at: OffsetDateTime,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -69,8 +70,10 @@ pub struct InvitationToken {
pub role: MembershipRole,
pub status: InvitationStatus,
pub token_hash: String,
pub expires_at: String,
pub created_at: String,
#[serde(with = "time::serde::rfc3339")]
pub expires_at: OffsetDateTime,
#[serde(with = "time::serde::rfc3339")]
pub created_at: OffsetDateTime,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+58 -4
View File
@@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use crate::ids::{SecretId, UserId, WorkspaceId};
@@ -26,9 +27,12 @@ pub struct Secret {
pub kind: SecretKind,
pub status: SecretStatus,
pub current_version: u32,
pub created_at: String,
pub updated_at: String,
pub last_used_at: Option<String>,
#[serde(with = "time::serde::rfc3339")]
pub created_at: OffsetDateTime,
#[serde(with = "time::serde::rfc3339")]
pub updated_at: OffsetDateTime,
#[serde(with = "time::serde::rfc3339::option")]
pub last_used_at: Option<OffsetDateTime>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -37,6 +41,56 @@ pub struct SecretVersion {
pub version: u32,
pub ciphertext: String,
pub key_version: String,
pub created_at: String,
#[serde(with = "time::serde::rfc3339")]
pub created_at: OffsetDateTime,
pub created_by: Option<UserId>,
}
#[cfg(test)]
mod tests {
use serde_json::json;
use time::{OffsetDateTime, format_description::well_known::Rfc3339};
use super::{Secret, SecretKind, SecretStatus, SecretVersion};
use crate::ids::{SecretId, UserId, WorkspaceId};
#[test]
fn secret_serializes_timestamps_as_rfc3339() {
let secret = Secret {
id: SecretId::new("secret_01"),
workspace_id: WorkspaceId::new("ws_01"),
name: "primary".to_owned(),
kind: SecretKind::Token,
status: SecretStatus::Active,
current_version: 2,
created_at: OffsetDateTime::parse("2026-03-25T12:00:00Z", &Rfc3339).unwrap(),
updated_at: OffsetDateTime::parse("2026-03-25T12:05:00Z", &Rfc3339).unwrap(),
last_used_at: Some(OffsetDateTime::parse("2026-03-25T12:07:00Z", &Rfc3339).unwrap()),
};
let value = serde_json::to_value(&secret).unwrap();
assert_eq!(value["created_at"], json!("2026-03-25T12:00:00Z"));
assert_eq!(value["updated_at"], json!("2026-03-25T12:05:00Z"));
assert_eq!(value["last_used_at"], json!("2026-03-25T12:07:00Z"));
}
#[test]
fn secret_version_deserializes_created_at_from_rfc3339() {
let version: SecretVersion = serde_json::from_value(json!({
"secret_id": "secret_01",
"version": 2,
"ciphertext": "opaque",
"key_version": "v2",
"created_at": "2026-03-25T12:07:00Z",
"created_by": "user_01"
}))
.unwrap();
assert_eq!(
version.created_at,
OffsetDateTime::parse("2026-03-25T12:07:00Z", &Rfc3339).unwrap()
);
assert_eq!(version.created_by, Some(UserId::new("user_01")));
}
}
+55 -2
View File
@@ -1,5 +1,6 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use time::OffsetDateTime;
use crate::ids::WorkspaceId;
@@ -17,6 +18,58 @@ pub struct Workspace {
pub display_name: String,
pub status: WorkspaceStatus,
pub settings: Value,
pub created_at: String,
pub updated_at: String,
#[serde(with = "time::serde::rfc3339")]
pub created_at: OffsetDateTime,
#[serde(with = "time::serde::rfc3339")]
pub updated_at: OffsetDateTime,
}
#[cfg(test)]
mod tests {
use serde_json::json;
use time::{OffsetDateTime, format_description::well_known::Rfc3339};
use super::{Workspace, WorkspaceStatus};
use crate::ids::WorkspaceId;
#[test]
fn workspace_serializes_timestamps_as_rfc3339() {
let workspace = Workspace {
id: WorkspaceId::new("ws_01"),
slug: "primary".to_owned(),
display_name: "Primary".to_owned(),
status: WorkspaceStatus::Active,
settings: json!({"region":"eu"}),
created_at: OffsetDateTime::parse("2026-03-25T12:00:00Z", &Rfc3339).unwrap(),
updated_at: OffsetDateTime::parse("2026-03-25T12:05:00Z", &Rfc3339).unwrap(),
};
let value = serde_json::to_value(&workspace).unwrap();
assert_eq!(value["created_at"], json!("2026-03-25T12:00:00Z"));
assert_eq!(value["updated_at"], json!("2026-03-25T12:05:00Z"));
}
#[test]
fn workspace_deserializes_timestamps_from_rfc3339() {
let workspace: Workspace = serde_json::from_value(json!({
"id": "ws_01",
"slug": "primary",
"display_name": "Primary",
"status": "active",
"settings": {"region":"eu"},
"created_at": "2026-03-25T12:00:00Z",
"updated_at": "2026-03-25T12:05:00Z"
}))
.unwrap();
assert_eq!(
workspace.created_at,
OffsetDateTime::parse("2026-03-25T12:00:00Z", &Rfc3339).unwrap()
);
assert_eq!(
workspace.updated_at,
OffsetDateTime::parse("2026-03-25T12:05:00Z", &Rfc3339).unwrap()
);
}
}
+3 -3
View File
@@ -61,7 +61,7 @@ pub struct MembershipRecord {
pub workspace_id: WorkspaceId,
pub user: User,
pub role: MembershipRole,
pub created_at: String,
pub created_at: OffsetDateTime,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@@ -448,8 +448,8 @@ pub struct RotateSecretRequest<'a> {
pub secret_id: &'a SecretId,
pub ciphertext: &'a str,
pub key_version: &'a str,
pub created_at: &'a str,
pub updated_at: &'a str,
pub created_at: &'a OffsetDateTime,
pub updated_at: &'a OffsetDateTime,
pub created_by: Option<&'a crank_core::UserId>,
}
+10 -10
View File
@@ -1478,8 +1478,8 @@ mod tests {
display_name: "Extra Workspace".to_owned(),
status: crank_core::WorkspaceStatus::Active,
settings: json!({"region":"eu"}),
created_at: "2026-03-25T12:00:00Z".to_owned(),
updated_at: "2026-03-25T12:00:00Z".to_owned(),
created_at: timestamp("2026-03-25T12:00:00Z"),
updated_at: timestamp("2026-03-25T12:00:00Z"),
};
let mut user = User {
id: UserId::new("user_extra_01"),
@@ -1544,8 +1544,8 @@ mod tests {
display_name: "Profile Workspace".to_owned(),
status: crank_core::WorkspaceStatus::Active,
settings: json!({}),
created_at: "2026-03-25T12:00:00Z".to_owned(),
updated_at: "2026-03-25T12:00:00Z".to_owned(),
created_at: timestamp("2026-03-25T12:00:00Z"),
updated_at: timestamp("2026-03-25T12:00:00Z"),
};
let other_workspace = Workspace {
id: WorkspaceId::new("ws_profile_02"),
@@ -1553,8 +1553,8 @@ mod tests {
display_name: "Other Workspace".to_owned(),
status: crank_core::WorkspaceStatus::Active,
settings: json!({}),
created_at: "2026-03-25T12:01:00Z".to_owned(),
updated_at: "2026-03-25T12:01:00Z".to_owned(),
created_at: timestamp("2026-03-25T12:01:00Z"),
updated_at: timestamp("2026-03-25T12:01:00Z"),
};
let user_id = registry
.upsert_bootstrap_user("profile@example.com", "Owner", "hashed-password")
@@ -1611,8 +1611,8 @@ mod tests {
display_name: "Session Workspace".to_owned(),
status: crank_core::WorkspaceStatus::Active,
settings: json!({}),
created_at: "2026-03-25T12:00:00Z".to_owned(),
updated_at: "2026-03-25T12:00:00Z".to_owned(),
created_at: timestamp("2026-03-25T12:00:00Z"),
updated_at: timestamp("2026-03-25T12:00:00Z"),
};
let user_id = registry
.upsert_bootstrap_user("session@example.com", "Owner", "hashed-password")
@@ -1666,8 +1666,8 @@ mod tests {
display_name: "Keys Workspace".to_owned(),
status: crank_core::WorkspaceStatus::Active,
settings: json!({}),
created_at: "2026-03-25T12:00:00Z".to_owned(),
updated_at: "2026-03-25T12:00:00Z".to_owned(),
created_at: timestamp("2026-03-25T12:00:00Z"),
updated_at: timestamp("2026-03-25T12:00:00Z"),
};
let api_key = PlatformApiKey {
id: PlatformApiKeyId::new("key_01"),
+13 -13
View File
@@ -13,9 +13,9 @@ impl PostgresRegistry {
kind,
status,
current_version,
to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",
to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",
to_char(last_used_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_used_at
created_at as \"created_at!: time::OffsetDateTime\",
updated_at as \"updated_at!: time::OffsetDateTime\",
last_used_at as \"last_used_at: time::OffsetDateTime\"
from secrets
where workspace_id = $1
order by name asc",
@@ -56,9 +56,9 @@ impl PostgresRegistry {
kind,
status,
current_version,
to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",
to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",
to_char(last_used_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as last_used_at
created_at as \"created_at!: time::OffsetDateTime\",
updated_at as \"updated_at!: time::OffsetDateTime\",
last_used_at as \"last_used_at: time::OffsetDateTime\"
from secrets
where workspace_id = $1 and id = $2",
workspace_id.as_str(),
@@ -96,7 +96,7 @@ impl PostgresRegistry {
sv.version,
sv.ciphertext,
sv.key_version,
to_char(sv.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",
sv.created_at as \"created_at!: time::OffsetDateTime\",
sv.created_by
from secrets s
join secret_versions sv
@@ -149,9 +149,9 @@ impl PostgresRegistry {
.bind(serialize_enum_text(&request.secret.kind, "kind")?)
.bind(serialize_enum_text(&request.secret.status, "status")?)
.bind(to_db_version(request.secret.current_version))
.bind(request.secret.last_used_at.as_deref())
.bind(&request.secret.created_at)
.bind(&request.secret.updated_at)
.bind(request.secret.last_used_at)
.bind(request.secret.created_at)
.bind(request.secret.updated_at)
.execute(&mut *tx)
.await;
@@ -173,7 +173,7 @@ impl PostgresRegistry {
.bind(to_db_version(request.secret.current_version))
.bind(request.ciphertext)
.bind(request.key_version)
.bind(&request.secret.created_at)
.bind(request.secret.created_at)
.bind(request.created_by.map(|value| value.as_str()))
.execute(&mut *tx)
.await?;
@@ -247,7 +247,7 @@ impl PostgresRegistry {
version: next_version,
ciphertext: request.ciphertext.to_owned(),
key_version: request.key_version.to_owned(),
created_at: request.created_at.to_owned(),
created_at: *request.created_at,
created_by: request.created_by.cloned(),
},
})
@@ -280,7 +280,7 @@ impl PostgresRegistry {
&self,
workspace_id: &WorkspaceId,
secret_id: &SecretId,
used_at: &str,
used_at: &OffsetDateTime,
) -> Result<(), RegistryError> {
let result = sqlx::query(
"update secrets
+15 -16
View File
@@ -1,5 +1,4 @@
use super::*;
use time::OffsetDateTime;
impl PostgresRegistry {
pub async fn list_workspaces(&self) -> Result<Vec<WorkspaceRecord>, RegistryError> {
@@ -10,8 +9,8 @@ impl PostgresRegistry {
display_name,
status,
settings_json,
to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",
to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\"
created_at as \"created_at!: time::OffsetDateTime\",
updated_at as \"updated_at!: time::OffsetDateTime\"
from workspaces
order by slug asc",
)
@@ -46,8 +45,8 @@ impl PostgresRegistry {
w.display_name,
w.status,
w.settings_json,
to_char(w.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",
to_char(w.updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\",
w.created_at as \"created_at!: time::OffsetDateTime\",
w.updated_at as \"updated_at!: time::OffsetDateTime\",
m.role
from memberships m
join workspaces w on w.id = m.workspace_id
@@ -85,11 +84,11 @@ impl PostgresRegistry {
m.workspace_id,
m.user_id,
m.role,
to_char(m.created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",
m.created_at as \"created_at!: time::OffsetDateTime\",
u.email,
u.display_name,
u.status,
u.created_at as \"user_created_at!: OffsetDateTime\"
u.created_at as \"user_created_at!: time::OffsetDateTime\"
from memberships m
join users u on u.id = m.user_id
where m.workspace_id = $1
@@ -180,8 +179,8 @@ impl PostgresRegistry {
role,
status,
token_hash,
to_char(expires_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"expires_at!\",
to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\"
expires_at as \"expires_at!: time::OffsetDateTime\",
created_at as \"created_at!: time::OffsetDateTime\"
from invitation_tokens
where workspace_id = $1
order by created_at desc",
@@ -232,8 +231,8 @@ impl PostgresRegistry {
.bind(serialize_enum_text(&request.invitation.role, "role")?)
.bind(serialize_enum_text(&request.invitation.status, "status")?)
.bind(&request.invitation.token_hash)
.bind(&request.invitation.expires_at)
.bind(&request.invitation.created_at)
.bind(request.invitation.expires_at)
.bind(request.invitation.created_at)
.execute(&self.pool)
.await?;
@@ -287,8 +286,8 @@ impl PostgresRegistry {
display_name,
status,
settings_json,
to_char(created_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"created_at!\",
to_char(updated_at at time zone 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS\"Z\"') as \"updated_at!\"
created_at as \"created_at!: time::OffsetDateTime\",
updated_at as \"updated_at!: time::OffsetDateTime\"
from workspaces
where id = $1",
workspace_id.as_str(),
@@ -334,8 +333,8 @@ impl PostgresRegistry {
.bind(&request.workspace.display_name)
.bind(serialize_enum_text(&request.workspace.status, "status")?)
.bind(Json(request.workspace.settings.clone()))
.bind(&request.workspace.created_at)
.bind(&request.workspace.updated_at)
.bind(request.workspace.created_at)
.bind(request.workspace.updated_at)
.execute(&self.pool)
.await;
@@ -369,7 +368,7 @@ impl PostgresRegistry {
.bind(&request.workspace.display_name)
.bind(serialize_enum_text(&request.workspace.status, "status")?)
.bind(Json(request.workspace.settings.clone()))
.bind(&request.workspace.updated_at)
.bind(request.workspace.updated_at)
.bind(request.workspace.id.as_str())
.execute(&self.pool)
.await;