Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[!WIP] Implement Collection APIs #54

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
37 changes: 11 additions & 26 deletions server/migrations/20240604111833_collections.sql
Original file line number Diff line number Diff line change
Expand Up @@ -18,40 +18,25 @@ SELECT trigger_updated_at('collections');
-- And creating an index on `user_id` to make it easier to find all collections for a given user
CREATE INDEX collections_user_id ON collections (user_id);

-- Creating a join table for collections and sources
CREATE TABLE collection_sources
-- Creating a join table for collections and items
CREATE TABLE collection_items
(
collection_source_id uuid primary key default uuid_generate_v1mc(),
collection_item_id uuid primary key default uuid_generate_v1mc(),
collection_id uuid not null references collections (collection_id),
source_id uuid not null references sources (source_id),
item_id uuid not null,
item_type integer not null,

created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),

unique (collection_id, source_id)
unique (collection_id, item_id)
);

-- And applying our `updated_at` trigger is as easy as this.
SELECT trigger_updated_at('collection_sources');
SELECT trigger_updated_at('collection_items');

-- And creating an index on `collection_id` and `source_id` to make it easier to find all sources for a given collection
CREATE INDEX collection_sources_collection_id ON collection_sources (collection_id);
CREATE INDEX collection_sources_source_id ON collection_sources (source_id);
-- And creating an index on `collection_id` and `item_id` to make it easier to find all items for a given collection
CREATE INDEX collection_items_collection_id ON collection_items (collection_id);
CREATE INDEX collection_items_collection_id_item_type ON collection_items (collection_id, item_type);

-- And creating an join table for collections and searches
CREATE TABLE collection_searches
(
collection_search_id uuid primary key default uuid_generate_v1mc(),
collection_id uuid not null references collections (collection_id),
search_id uuid not null references searches (search_id),
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),

unique (collection_id, search_id)
);

-- And applying our `updated_at` trigger is as easy as this.
SELECT trigger_updated_at('collection_searches');

-- And creating an index on `collection_id` and `search_id` to make it easier to find all searches for a given collection
CREATE INDEX collection_searches_collection_id ON collection_searches (collection_id);
CREATE INDEX collection_searches_search_id ON collection_searches (search_id);
75 changes: 75 additions & 0 deletions server/src/collections/api_models.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
use crate::collections::data_models;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;

#[derive(Serialize, Deserialize, Debug)]
pub struct CreateCollectionRequest {
pub name: String,
pub description: Option<String>,
pub category: data_models::CategoryType,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct GetOneCollectionRequest {
pub collection_id: uuid::Uuid,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct GetCollectionsRequest {
pub limit: Option<u8>,
pub offset: Option<u8>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct GetCollectionsResponse {
pub collections:Vec<data_models::Collection>
}

#[derive(Serialize, Deserialize, Debug)]
pub struct UpdateCollectionRequest {
pub collection_id: uuid::Uuid,
pub name: Option<String>,
pub description: Option<String>,
pub category: Option<data_models::CategoryType>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct DeleteCollectionRequest {
pub collection_id: uuid::Uuid,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct CollectionItem {
pub item_id: uuid::Uuid,
pub item_type:data_models::CollectionItemType,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct AddItemsToCollectionRequest {
pub collection_id: uuid::Uuid,
pub items: Vec<CollectionItem>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct GetItemsFromCollectionRequest {
pub collection_id: uuid::Uuid,
pub item_type: data_models::CollectionItemType,
pub limit: Option<u8>,
pub offset: Option<u8>,
}

#[derive(Serialize, Deserialize, Debug)]
pub struct GetItemsFromCollectionResponse {
pub items: Vec<data_models::CollectionItem>
}

#[derive(Serialize, Deserialize, Debug)]
pub struct DeleteItemsFromCollectionRequest {
pub collection_id: uuid::Uuid,
pub items: Vec<uuid::Uuid>,
}





63 changes: 63 additions & 0 deletions server/src/collections/data_models.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use crate::custom_types::DateTime;
use serde::{Deserialize, Serialize};
use serde_json;
use sqlx::FromRow;
use std::fmt::Debug;

#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
pub enum CategoryType {
CategoryA,
CategoryB,
CategoryC,
}

impl From<i32> for CategoryType {
fn from(value: i32) -> Self {
match value {
0 => CategoryType::CategoryA,
1 => CategoryType::CategoryB,
_ => CategoryType::CategoryC,
}
}
}

#[derive(FromRow, Serialize, Deserialize, Clone, Debug)]
pub struct Collection {
pub collection_id: uuid::Uuid,
pub user_id: uuid::Uuid,
pub name: String,
pub description: Option<String>,
pub category: CategoryType,

pub context: Option<serde_json::Value>,
pub metadata: Option<serde_json::Value>,

pub created_at: DateTime,
pub updated_at: DateTime,
}

#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
pub enum CollectionItemType {
Search,
Source,
}

impl From<i32> for CollectionItemType {
fn from(value: i32) -> Self {
match value {
0 => CollectionItemType::Search,
_ => CollectionItemType::Source,
}
}
}

#[derive(FromRow, Serialize, Deserialize, Clone, Debug)]
pub struct CollectionItem {
pub collection_item_id: uuid::Uuid,
pub collection_id: uuid::Uuid,
pub item_id: uuid::Uuid,
pub item_type: CollectionItemType,

pub created_at: DateTime,
pub updated_at: DateTime,
}
9 changes: 9 additions & 0 deletions server/src/collections/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pub use api_models::*;
pub use data_models::*;
pub use routes::*;
pub use services::*;

pub mod api_models;
pub mod data_models;
pub mod routes;
pub mod services;
126 changes: 126 additions & 0 deletions server/src/collections/routes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use crate::collections::{api_models, services, data_models};
use crate::startup::AppState;
use crate::users::User;
use axum::extract::{Query, State};
use axum::http::StatusCode;
use axum::response::IntoResponse;
use axum::routing::{delete, get, patch, post, put};
use axum::{Json, Router};
use sqlx::PgPool;

#[tracing::instrument(level = "debug", skip_all, ret, err(Debug))]
async fn create_collection_handler(
State(pool): State<PgPool>,
user: User,
Json(create_collections_request): Json<api_models::CreateCollectionRequest>,
) -> crate::Result<impl IntoResponse> {
let user_id = user.user_id;

let collection = services::insert_new_collection(&pool, &user_id, &create_collections_request).await?;

Ok((StatusCode::OK, Json(collection)))
}


#[tracing::instrument(level = "debug", skip_all, ret, err(Debug))]
async fn get_one_collection_handler(
State(pool): State<PgPool>,
user: User,
Query(get_one_collection_request): Query<api_models::GetOneCollectionRequest>,
) -> crate::Result<impl IntoResponse> {
let user_id = user.user_id;

let collection = services::get_one_collection(&pool, &user_id, &get_one_collection_request).await?;

Ok((StatusCode::OK, Json(collection)))
}

#[tracing::instrument(level = "debug", skip_all, ret, err(Debug))]
async fn update_collection_handler(
State(pool): State<PgPool>,
user: User,
Json(update_collection_request): Json<api_models::UpdateCollectionRequest>,
) -> crate::Result<impl IntoResponse> {
let user_id = user.user_id;

let collection = services::update_collection(&pool, &user_id, &update_collection_request).await?;

Ok((StatusCode::OK, Json(collection)))
}

#[tracing::instrument(level = "debug", skip_all, ret, err(Debug))]
async fn delete_collection_handler(
State(pool): State<PgPool>,
user: User,
Json(delete_collection_request): Json<api_models::DeleteCollectionRequest>,
) -> crate::Result<impl IntoResponse> {
let user_id = user.user_id;

services::delete_collection(&pool, &user_id, &delete_collection_request).await?;

Ok((StatusCode::OK, ()))
}

#[tracing::instrument(level = "debug", skip_all, ret, err(Debug))]
async fn get_collections_handler(
State(pool): State<PgPool>,
user: User,
Query(get_collections_request): Query<api_models::GetCollectionsRequest>,
) -> crate::Result<impl IntoResponse> {
let user_id = user.user_id;

let collections = services::get_collections(&pool, &user_id, &get_collections_request).await?;

Ok((StatusCode::OK, Json(collections)))
}

#[tracing::instrument(level = "debug", skip_all, ret, err(Debug))]
async fn add_items_to_collection_handler(
State(pool): State<PgPool>,
user: User,
Json(add_items_to_collection_request): Json<api_models::AddItemsToCollectionRequest>,
) -> crate::Result<impl IntoResponse> {
let user_id = user.user_id;

services::add_items_to_collection(&pool, &user_id, &add_items_to_collection_request).await?;

Ok((StatusCode::OK, ()))
}

#[tracing::instrument(level = "debug", skip_all, ret, err(Debug))]
async fn get_items_from_collection_handler(
State(pool): State<PgPool>,
user: User,
Query(get_items_from_collection_request): Query<api_models::GetItemsFromCollectionRequest>,
) -> crate::Result<impl IntoResponse> {
let user_id = user.user_id;

let items = services::get_items_from_collection(&pool, &user_id, &get_items_from_collection_request).await?;

Ok((StatusCode::OK, Json(items)))
}

#[tracing::instrument(level = "debug", skip_all, ret, err(Debug))]
async fn delete_items_from_collection_handler(
State(pool): State<PgPool>,
user: User,
Json(delete_items_from_collection_request): Json<api_models::DeleteItemsFromCollectionRequest>,
) -> crate::Result<impl IntoResponse> {
let user_id = user.user_id;

services::delete_items_from_collection(&pool, &user_id, &delete_items_from_collection_request).await?;

Ok((StatusCode::OK, ()))
}

pub fn routes() -> Router<AppState> {
Router::new()
.route("/", post(create_collection_handler))
.route("/", get(get_one_collection_handler))
.route("/", patch(update_collection_handler))
.route("/", delete(delete_collection_handler))
.route("/all", get(get_collections_handler))
.route("/items", put(add_items_to_collection_handler))
.route("/items", get(get_items_from_collection_handler))
.route("/items", delete(delete_items_from_collection_handler))
}
Loading