core: type workspace and secret timestamps
This commit is contained in:
+8
-8
@@ -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"
|
||||
}
|
||||
+8
-8
@@ -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"
|
||||
}
|
||||
+6
-6
@@ -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"
|
||||
}
|
||||
+11
-11
@@ -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"
|
||||
}
|
||||
+8
-8
@@ -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"
|
||||
}
|
||||
+11
-11
@@ -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"
|
||||
}
|
||||
+8
-8
@@ -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"
|
||||
}
|
||||
+5
-5
@@ -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"
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>,
|
||||
}
|
||||
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user