Compare commits

..

1 commit

Author SHA1 Message Date
6e38eaddad
feat: Add user resource w/database as storage
Some checks failed
ci/woodpecker/pr/ci Pipeline failed
2025-01-12 14:21:09 +01:00
7 changed files with 70 additions and 150 deletions

View file

@ -11,7 +11,7 @@ type ControllerApiDescription = ApiDescription<Arc<ControllerContext>>;
pub fn api() -> Result<ControllerApiDescription> {
let mut api = ControllerApiDescription::new();
user::register_api(&mut api)?;
api.register(user::get_user_by_id)?;
api.register(version::version)?;
Ok(api)
}

View file

@ -3,3 +3,4 @@ pub mod context;
mod user;
mod version;

58
controller/src/user.rs Normal file
View file

@ -0,0 +1,58 @@
use clap::error::RichFormatter;
use dropshot::{endpoint, HttpError, HttpResponseOk, Path, RequestContext};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use trace_request::trace_request;
use uuid::Uuid;
use std::sync::Arc;
use crate::context::ControllerContext;
/// User
#[derive(Serialize, JsonSchema)]
struct User {
id: Uuid,
name: String,
}
#[allow(dead_code)]
#[derive(Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
struct UsersPathParams {
user_id: Uuid,
}
/// Fetch user info.
#[endpoint {
method = GET,
path = "/users/{userId}",
tags = [ "user" ],
}]
#[trace_request]
pub(crate) async fn get_user_by_id(
rqctx: RequestContext<Arc<ControllerContext>>,
params: Path<UsersPathParams>,
) -> Result<HttpResponseOk<User>, HttpError> {
let id = params.into_inner().user_id;
tracing::debug!(id = id.to_string(), "Getting user by id");
let pg = rqctx.context().pg_pool.to_owned();
let rec = sqlx::query!(r#"SELECT * FROM users WHERE id = $1"#, id)
.fetch_one(&pg)
.await
.map_err(|e| match e {
sqlx::Error::RowNotFound => {
HttpError::for_not_found(None, format!("User not found by id: {:?}", id))
}
err => HttpError::for_internal_error(format!("Error: {}", err)),
})?;
let user = User {
id: rec.id,
name: rec.name,
};
Ok(HttpResponseOk(user))
}

View file

@ -1,126 +0,0 @@
use dropshot::{
endpoint, EmptyScanParams, HttpError, HttpResponseOk, PaginationParams, Path, Query,
RequestContext, ResultsPage, WhichPage,
};
use dropshot::{ApiDescription, ApiDescriptionRegisterError};
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use trace_request::trace_request;
use uuid::Uuid;
use std::sync::Arc;
use super::User;
use crate::context::ControllerContext;
#[derive(Deserialize, JsonSchema, Serialize)]
#[serde(rename_all = "camelCase")]
struct UsersPathParams {
user_id: Uuid,
}
#[derive(Deserialize, JsonSchema, Serialize)]
#[serde(rename_all = "camelCase")]
struct UserPage {
user_id: Uuid,
}
pub fn register_api(
api: &mut ApiDescription<Arc<ControllerContext>>,
) -> Result<(), ApiDescriptionRegisterError> {
api.register(get_user_by_id)?;
api.register(list_users)
}
/// Fetch user info.
#[endpoint {
method = GET,
path = "/users/{userId}",
tags = [ "user" ],
}]
#[trace_request]
async fn get_user_by_id(
rqctx: RequestContext<Arc<ControllerContext>>,
params: Path<UsersPathParams>,
) -> Result<HttpResponseOk<User>, HttpError> {
let id = params.into_inner().user_id;
tracing::debug!(id = id.to_string(), "Getting user by id");
let pg = rqctx.context().pg_pool.to_owned();
let rec = sqlx::query!(r#"SELECT * FROM users WHERE id = $1"#, id)
.fetch_one(&pg)
.await
.map_err(|e| match e {
sqlx::Error::RowNotFound => {
HttpError::for_not_found(None, format!("User not found by id: {:?}", id))
}
err => HttpError::for_internal_error(format!("Error: {}", err)),
})?;
let user = User {
id: rec.id,
name: rec.name,
};
Ok(HttpResponseOk(user))
}
/// List users
#[endpoint {
method = GET,
path = "/users",
tags = [ "user" ],
}]
#[trace_request]
async fn list_users(
rqctx: RequestContext<Arc<ControllerContext>>,
query: Query<PaginationParams<EmptyScanParams, UserPage>>,
) -> Result<HttpResponseOk<ResultsPage<User>>, HttpError> {
let pag_params = query.into_inner();
let limit = rqctx.page_limit(&pag_params)?.get() as usize;
let pg = rqctx.context().pg_pool.to_owned();
let users = match &pag_params.page {
WhichPage::First(..) => {
tracing::debug!("Listing users. First page");
sqlx::query!(r#"SELECT * FROM users ORDER BY id LIMIT $1"#, limit as i64)
.fetch_all(&pg)
.await
.map_err(|e| HttpError::for_internal_error(format!("Error: {}", e)))?
.into_iter()
.map(|rec| User {
id: rec.id,
name: rec.name,
})
.collect()
}
WhichPage::Next(UserPage { user_id: last_seen }) => {
tracing::debug!(
last_seen = last_seen.to_string(),
"Listing users. Next page"
);
sqlx::query!(
r#"SELECT * FROM users WHERE id > $1 ORDER BY id LIMIT $2"#,
last_seen,
limit as i64
)
.fetch_all(&pg)
.await
.map_err(|e| HttpError::for_internal_error(format!("Error: {}", e)))?
.into_iter()
.map(|rec| User {
id: rec.id,
name: rec.name,
})
.collect()
}
};
Ok(HttpResponseOk(ResultsPage::new(
users,
&EmptyScanParams {},
|u: &User, _| UserPage { user_id: u.id },
)?))
}

View file

@ -1,14 +0,0 @@
use schemars::JsonSchema;
use serde::Serialize;
use uuid::Uuid;
mod api;
pub use self::api::register_api;
/// User
#[derive(Serialize, JsonSchema)]
struct User {
id: Uuid,
name: String,
}

View file

@ -96,18 +96,20 @@ pub fn init_tracing(otel_endpoint: Option<&String>, log_stderr: bool) -> Result<
None => None,
};
let metrics_layer = meter_provider
.as_ref()
.map(|p| MetricsLayer::new(p.to_owned()));
let metrics_layer = match meter_provider {
Some(ref p) => Some(MetricsLayer::new(p.to_owned())),
None => None,
};
let tracer_provider = match otel_endpoint {
Some(endpoint) => Some(init_tracer_provider(endpoint, resource)?),
None => None,
};
let trace_layer = tracer_provider
.as_ref()
.map(|p| OpenTelemetryLayer::new(p.tracer("tracing-otel-subscriber")));
let trace_layer = match tracer_provider {
Some(ref p) => Some(OpenTelemetryLayer::new(p.tracer("tracing-otel-subscriber"))),
None => None,
};
opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());

View file

@ -11,7 +11,7 @@ run-controller $RUST_LOG="debug,h2=info,hyper_util=info,tower=info":
# Run controller local development
[group('controller')]
dev-controller: dev-controller-db-migrate
dev-controller:
watchexec --clear --restart --stop-signal INT --debounce 300ms -- just run-controller
# Run agent
@ -76,7 +76,6 @@ dev-postgres:
--volume patagia-postgres:/var/lib/postgresql/data \
--volume "${XDG_RUNTIME_DIR}/patagia-postgres:/var/run/postgresql" \
docker.io/postgres:17
sleep 0.3
# Clean up PostgreSQL data
[group('controller')]
@ -91,7 +90,7 @@ dev-postgres-psql:
[group('controller')]
[working-directory: 'controller']
dev-controller-db-migrate: dev-postgres
dev-controller-db-migrate:
cargo sqlx migrate run
[group('controller')]