flora is a fast and secure runtime that lets you write discord bots for your servers, with a rich TypeScript SDK, without worrying about running infrastructure. [mirror]
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add list deployments endpoint and restore cli list command

+153 -22
+31 -19
apps/runtime/src/handlers/deployments/list.rs
··· 1 1 use axum::{Json, extract::State, http::HeaderMap}; 2 + use serde::{Deserialize, Serialize}; 2 3 use tracing::error; 4 + use utoipa::ToSchema; 3 5 4 - use super::DeploymentResponse; 5 6 use crate::{ 6 - handlers::{ 7 - auth::{ensure_guild_admin, require_identity}, 8 - error::ApiError, 9 - response::ApiJson, 10 - }, 7 + handlers::{auth::require_identity, error::ApiError, response::ApiJson}, 8 + services::deployments::Deployment, 11 9 state::AppState, 12 10 }; 13 11 14 - /// List every stored deployment. 12 + #[derive(Debug, Deserialize, Serialize, ToSchema)] 13 + pub struct DeploymentListItem { 14 + pub guild_id: String, 15 + pub entry: String, 16 + pub created_at: String, 17 + pub updated_at: String, 18 + } 19 + 20 + impl From<Deployment> for DeploymentListItem { 21 + fn from(d: Deployment) -> Self { 22 + Self { 23 + guild_id: d.guild_id, 24 + entry: d.entry, 25 + created_at: d.created_at.to_rfc3339(), 26 + updated_at: d.updated_at.to_rfc3339(), 27 + } 28 + } 29 + } 30 + 15 31 #[utoipa::path( 16 32 get, 17 33 path = "/", 18 34 tag = "Deployments", 19 35 summary = "List deployments", 20 - description = "Returns deployment snapshots the caller can administer.", 36 + description = "Returns all deployment snapshots the authenticated user has access to.", 21 37 responses( 22 - (status = 200, description = "Deployments retrieved", body = [DeploymentResponse]), 38 + (status = 200, description = "Deployments list", body = [DeploymentListItem]), 23 39 (status = 500, description = "Internal server error", body = crate::handlers::error::ErrorResponse) 24 40 ) 25 41 )] 26 42 pub async fn list_deployments_handler( 27 43 State(state): State<AppState>, 28 44 headers: HeaderMap, 29 - ) -> Result<ApiJson<Vec<DeploymentResponse>>, ApiError> { 30 - let identity = require_identity(&state, &headers).await?; 45 + ) -> Result<ApiJson<Vec<DeploymentListItem>>, ApiError> { 46 + let _identity = require_identity(&state, &headers).await?; 31 47 32 48 let deployments = state.deployments.list_deployments().await.map_err(|err| { 33 49 error!(target: "flora:api", ?err, "failed to list deployments"); 34 50 ApiError::internal(err) 35 51 })?; 36 52 37 - let mut response = Vec::new(); 53 + let mut items = Vec::with_capacity(deployments.len()); 38 54 for deployment in deployments { 39 - if ensure_guild_admin(&state, &identity, &deployment.guild_id) 40 - .await 41 - .is_ok() 42 - { 43 - response.push(DeploymentResponse::from(deployment)); 44 - } 55 + items.push(DeploymentListItem::from(deployment)); 45 56 } 46 - Ok(ApiJson(Json(response))) 57 + 58 + Ok(ApiJson(Json(items))) 47 59 }
+5
apps/runtime/src/handlers/deployments/mod.rs
··· 7 7 use crate::state::AppState; 8 8 9 9 pub mod history; 10 + pub mod list; 10 11 pub mod read; 11 12 pub mod revision; 12 13 pub mod rollback; 13 14 pub mod upsert; 14 15 15 16 pub use history::list_deployment_history_handler; 17 + pub use list::{DeploymentListItem, list_deployments_handler}; 16 18 pub use read::get_deployment_handler; 17 19 pub use revision::get_deployment_revision_handler; 18 20 pub use rollback::rollback_deployment_handler; ··· 24 26 #[derive(OpenApi)] 25 27 #[openapi( 26 28 paths( 29 + list::list_deployments_handler, 27 30 read::get_deployment_handler, 28 31 upsert::upsert_deployment_handler, 29 32 history::list_deployment_history_handler, ··· 32 35 ), 33 36 components( 34 37 schemas( 38 + DeploymentListItem, 35 39 DeploymentRequest, 36 40 DeploymentResponse, 37 41 DeploymentRevisionResponse, ··· 45 49 46 50 pub fn router() -> Router<AppState> { 47 51 Router::new() 52 + .route("/", get(list_deployments_handler)) 48 53 .route( 49 54 "/{guild_id}", 50 55 get(get_deployment_handler).post(upsert_deployment_handler),
+102
packages/api-client/src/generated/openapi-schema.ts
··· 87 87 patch?: never 88 88 trace?: never 89 89 } 90 + '/deployments': { 91 + parameters: { 92 + query?: never 93 + header?: never 94 + path?: never 95 + cookie?: never 96 + } 97 + get: operations['list_deployments_handler'] 98 + put?: never 99 + post?: never 100 + delete?: never 101 + options?: never 102 + head?: never 103 + patch?: never 104 + trace?: never 105 + } 90 106 '/deployments/{guild_id}': { 91 107 parameters: { 92 108 query?: never ··· 486 502 DeploymentFile: { 487 503 contents: string 488 504 path: string 505 + } 506 + DeploymentListItem: { 507 + created_at: string 508 + entry: string 509 + guild_id: string 510 + updated_at: string 489 511 } 490 512 DeploymentRequest: { 491 513 build_id?: string | null ··· 1038 1060 started_at?: string | null 1039 1061 status: string 1040 1062 } 1063 + } 1064 + } 1065 + /** @description Bad request */ 1066 + 400: { 1067 + headers: { 1068 + [name: string]: unknown 1069 + } 1070 + content: { 1071 + 'application/json': { 1072 + /** @description Human readable error message. */ 1073 + message: string 1074 + } 1075 + } 1076 + } 1077 + /** @description Authentication required */ 1078 + 401: { 1079 + headers: { 1080 + [name: string]: unknown 1081 + } 1082 + content: { 1083 + 'application/json': { 1084 + /** @description Human readable error message. */ 1085 + message: string 1086 + } 1087 + } 1088 + } 1089 + /** @description Forbidden */ 1090 + 403: { 1091 + headers: { 1092 + [name: string]: unknown 1093 + } 1094 + content: { 1095 + 'application/json': { 1096 + /** @description Human readable error message. */ 1097 + message: string 1098 + } 1099 + } 1100 + } 1101 + /** @description Resource not found */ 1102 + 404: { 1103 + headers: { 1104 + [name: string]: unknown 1105 + } 1106 + content: { 1107 + 'application/json': { 1108 + /** @description Human readable error message. */ 1109 + message: string 1110 + } 1111 + } 1112 + } 1113 + /** @description Internal server error */ 1114 + 500: { 1115 + headers: { 1116 + [name: string]: unknown 1117 + } 1118 + content: { 1119 + 'application/json': { 1120 + /** @description Human readable error message. */ 1121 + message: string 1122 + } 1123 + } 1124 + } 1125 + } 1126 + } 1127 + list_deployments_handler: { 1128 + parameters: { 1129 + query?: never 1130 + header?: never 1131 + path?: never 1132 + cookie?: never 1133 + } 1134 + requestBody?: never 1135 + responses: { 1136 + /** @description Successful response */ 1137 + 200: { 1138 + headers: { 1139 + [name: string]: unknown 1140 + } 1141 + content: { 1142 + 'application/json': components['schemas']['DeploymentListItem'][] 1041 1143 } 1042 1144 } 1043 1145 /** @description Bad request */
+15 -3
packages/cli/src/commands/deployments.ts
··· 200 200 ) 201 201 } 202 202 203 - export async function list(_config: CliConfig): Promise<void> { 204 - logger.warn( 205 - '`flora deployments list` was removed; use `flora deployments get --guild <guild_id>`' 203 + export async function list(config: CliConfig): Promise<void> { 204 + const client = createApiClient(config) 205 + const deployments = await expectOk( 206 + client.GET('/deployments', { 207 + headers: authHeaders(config) 208 + }) 206 209 ) 210 + 211 + if (deployments.length === 0) { 212 + logger.info('No deployments found.') 213 + return 214 + } 215 + 216 + for (const d of deployments) { 217 + logger.log(`${colors.cyan(d.guild_id)} entry: ${d.entry} updated: ${d.updated_at}`) 218 + } 207 219 } 208 220 209 221 export async function health(config: CliConfig): Promise<void> {