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

🚧 Restructure book clubs #461

Merged
merged 14 commits into from
Oct 5, 2024
Merged
86 changes: 57 additions & 29 deletions apps/server/src/routers/api/v1/book_club.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@
chrono::{Duration, Utc},
or, Direction,
};
use serde::Deserialize;
use serde::{Deserialize, Serialize};
use serde_qs::axum::QsQuery;
use serde_with::skip_serializing_none;
use specta::Type;
use stump_core::{
db::entity::{
macros::{
book_club_member_and_schedule_include, book_club_with_books_include,
book_club_with_schedule,
book_club_member_and_schedule_include, book_club_member_with_user,
book_club_with_books_include, book_club_with_schedule,
},
BookClub, BookClubBook, BookClubInvitation, BookClubMember, BookClubMemberRole,
BookClubMemberRoleSpec, BookClubSchedule, User, UserPermission,
BookClub, BookClubBook, BookClubExternalBook, BookClubInvitation, BookClubMember,
BookClubMemberRole, BookClubMemberRoleSpec, BookClubSchedule, User,
UserPermission,
},
prisma::{
book_club, book_club_book, book_club_invitation, book_club_member,
Expand All @@ -44,6 +46,8 @@
// be an error, but it would definitely be a warning for admins/creator
// TODO: users that are members but have the feature revoked need some reconcilation...

// TODO: adjust the instrumentation once ret(err) is supported: https://github.com/tokio-rs/tracing/pull/2970

pub(crate) fn mount(app_state: AppState) -> Router<AppState> {
Router::new()
.route("/book-clubs", get(get_book_clubs).post(create_book_club))
Expand Down Expand Up @@ -152,7 +156,7 @@
)
}

#[derive(Deserialize, Type, ToSchema)]
#[derive(Serialize, Deserialize, Debug, Type, ToSchema)]

Check warning on line 159 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L159

Added line #L159 was not covered by tests
pub struct GetBookClubsParams {
#[serde(default)]
all: bool,
Expand All @@ -168,6 +172,7 @@
(status = 500, description = "Internal server error")
)
)]
#[tracing::instrument(err, skip(ctx, req))]

Check warning on line 175 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L175

Added line #L175 was not covered by tests
async fn get_book_clubs(
State(ctx): State<AppState>,
QsQuery(params): QsQuery<GetBookClubsParams>,
Expand Down Expand Up @@ -196,15 +201,17 @@
Ok(Json(book_clubs.into_iter().map(BookClub::from).collect()))
}

#[derive(Deserialize, Type, ToSchema)]
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Type, ToSchema)]

Check warning on line 205 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L205

Added line #L205 was not covered by tests
pub struct CreateBookClub {
pub name: String,
#[serde(default)]
pub is_private: bool,
#[specta(optional)]
pub member_role_spec: Option<BookClubMemberRoleSpec>,

#[serde(default)]
pub creator_hide_progress: bool,
#[specta(optional)]
pub creator_display_name: Option<String>,
}

Expand All @@ -218,6 +225,7 @@
(status = 500, description = "Internal server error")
)
)]
#[tracing::instrument(err, skip(ctx, req))]

Check warning on line 228 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L228

Added line #L228 was not covered by tests
async fn create_book_club(
State(ctx): State<AppState>,
Extension(req): Extension<RequestContext>,
Expand Down Expand Up @@ -276,6 +284,7 @@
(status = 500, description = "Internal server error")
)
)]
#[tracing::instrument(err, skip(ctx, req))]

Check warning on line 287 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L287

Added line #L287 was not covered by tests
async fn get_book_club(
State(ctx): State<AppState>,
Path(id): Path<String>,
Expand All @@ -294,6 +303,7 @@
.find_first(where_params)
.include(book_club_with_books_include::include(
book_club_member_access_for_user(viewer),
vec![], // TODO: access control for books and/or future schedule restrictions
))
.exec()
.await?
Expand All @@ -302,7 +312,8 @@
Ok(Json(BookClub::from(book_club)))
}

#[derive(Deserialize, Type, ToSchema)]
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Type, ToSchema)]

Check warning on line 316 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L316

Added line #L316 was not covered by tests
pub struct UpdateBookClub {
pub name: Option<String>,
pub description: Option<String>,
Expand All @@ -321,6 +332,7 @@
(status = 500, description = "Internal server error")
)
)]
#[tracing::instrument(err, skip(ctx, req))]

Check warning on line 335 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L335

Added line #L335 was not covered by tests
async fn update_book_club(
State(ctx): State<AppState>,
Path(id): Path<String>,
Expand Down Expand Up @@ -370,14 +382,15 @@
Ok(Json(BookClub::from(updated_book_club)))
}

#[derive(Deserialize, Type, ToSchema)]
#[derive(Serialize, Deserialize, Debug, Type, ToSchema)]

Check warning on line 385 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L385

Added line #L385 was not covered by tests
pub struct UpdateBookClubSchedule {}

async fn get_book_club_invitations() -> APIResult<Json<Vec<BookClubInvitation>>> {
Err(APIError::NotImplemented)
}

#[derive(Deserialize, Type, ToSchema)]
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Type, ToSchema)]

Check warning on line 393 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L393

Added line #L393 was not covered by tests
pub struct CreateBookClubInvitation {
pub user_id: String,
pub role: Option<BookClubMemberRole>,
Expand Down Expand Up @@ -450,6 +463,7 @@
(status = 500, description = "Internal server error")
)
)]
#[tracing::instrument(err, skip(ctx, req))]

Check warning on line 466 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L466

Added line #L466 was not covered by tests
async fn get_book_club_members(
State(ctx): State<AppState>,
Path(id): Path<String>,
Expand All @@ -469,6 +483,7 @@
let book_club_members = client
.book_club_member()
.find_many(where_params)
.include(book_club_member_with_user::include())
.exec()
.await?;

Expand All @@ -480,7 +495,7 @@
))
}

#[derive(Deserialize, Type, ToSchema, Default)]
#[derive(Serialize, Deserialize, Debug, Type, ToSchema, Default)]

Check warning on line 498 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L498

Added line #L498 was not covered by tests
pub struct CreateBookClubMember {
pub user_id: String,
pub display_name: Option<String>,
Expand All @@ -507,7 +522,7 @@
Ok(BookClubMember::from(created_member))
}

#[derive(Deserialize, Type, ToSchema)]
#[derive(Serialize, Deserialize, Debug, Type, ToSchema)]

Check warning on line 525 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L525

Added line #L525 was not covered by tests
pub struct BookClubInvitationAnswer {
pub accept: bool,
pub member_details: Option<CreateBookClubMember>,
Expand All @@ -523,6 +538,7 @@
(status = 500, description = "Internal server error")
)
)]
#[tracing::instrument(err, skip(ctx, req))]

Check warning on line 541 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L541

Added line #L541 was not covered by tests
async fn respond_to_book_club_invitation(
State(ctx): State<AppState>,
Path((id, invitation_id)): Path<(String, String)>,
Expand Down Expand Up @@ -581,9 +597,10 @@
(status = 500, description = "Internal server error")
)
)]
#[tracing::instrument(err, skip(ctx, req))]

Check warning on line 600 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L600

Added line #L600 was not covered by tests
async fn create_book_club_member_handler(
State(ctx): State<AppState>,
Path(id): Path<String>,
State(ctx): State<AppState>,
Extension(req): Extension<RequestContext>,
Json(payload): Json<CreateBookClubMember>,
) -> APIResult<Json<BookClubMember>> {
Expand All @@ -603,6 +620,7 @@
(status = 500, description = "Internal server error")
)
)]
#[tracing::instrument(err, skip(ctx, req))]

Check warning on line 623 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L623

Added line #L623 was not covered by tests
async fn get_book_club_member(
State(ctx): State<AppState>,
Path((id, member_id)): Path<(String, String)>,
Expand Down Expand Up @@ -630,7 +648,8 @@
Ok(Json(BookClubMember::from(book_club_member)))
}

#[derive(Deserialize, Type, ToSchema)]
#[derive(Serialize, Deserialize, Debug, Type, ToSchema)]

Check warning on line 651 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L651

Added line #L651 was not covered by tests
#[skip_serializing_none]
pub struct UpdateBookClubMember {
pub display_name: Option<String>,
pub private_membership: Option<bool>,
Expand All @@ -646,6 +665,7 @@
(status = 500, description = "Internal server error")
)
)]
#[tracing::instrument(err, skip(ctx, req))]

Check warning on line 668 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L668

Added line #L668 was not covered by tests
async fn update_book_club_member(
State(ctx): State<AppState>,
Path((_id, member_id)): Path<(String, String)>,
Expand Down Expand Up @@ -717,17 +737,13 @@
/// - A book that is not stored in the database
///
/// This provides some flexibility for book clubs to add books that perhaps are not on the server
#[derive(Deserialize, Type, ToSchema)]
#[derive(Serialize, Deserialize, Debug, Type, ToSchema)]

Check warning on line 740 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L740

Added line #L740 was not covered by tests
#[serde(untagged)]
pub enum CreateBookClubScheduleBookOption {
/// A book that is stored in the database
Stored { id: String },
/// A book that is not stored in the database
External {
title: String,
author: String,
url: Option<String>,
},
External(BookClubExternalBook),
}

impl CreateBookClubScheduleBookOption {
Expand All @@ -740,24 +756,32 @@
book_club_book::book_entity_id::set(Some(id)),
]
},
CreateBookClubScheduleBookOption::External { title, author, url } => vec![
CreateBookClubScheduleBookOption::External(BookClubExternalBook {
title,
author,
url,
image_url,
}) => vec![

Check warning on line 764 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L760-L764

Added lines #L760 - L764 were not covered by tests
book_club_book::title::set(Some(title)),
book_club_book::author::set(Some(author)),
book_club_book::url::set(url),
book_club_book::image_url::set(image_url),

Check warning on line 768 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L768

Added line #L768 was not covered by tests
],
}
}
}

#[derive(Deserialize, Type, ToSchema)]
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Type, ToSchema)]

Check warning on line 775 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L775

Added line #L775 was not covered by tests
pub struct CreateBookClubScheduleBook {
pub book: CreateBookClubScheduleBookOption,
pub start_at: Option<String>,
pub end_at: Option<String>,
pub discussion_duration_days: Option<i32>,
}

#[derive(Deserialize, Type, ToSchema)]
#[skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Type, ToSchema)]

Check warning on line 784 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L784

Added line #L784 was not covered by tests
pub struct CreateBookClubSchedule {
pub default_interval_days: Option<i32>,
pub books: Vec<CreateBookClubScheduleBook>,
Expand All @@ -777,9 +801,10 @@
(status = 500, description = "Internal server error")
)
)]
#[tracing::instrument(err, skip(ctx, req))]

Check warning on line 804 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L804

Added line #L804 was not covered by tests
async fn create_book_club_schedule(
State(ctx): State<AppState>,
Path(id): Path<String>,
State(ctx): State<AppState>,
Extension(req): Extension<RequestContext>,
Json(payload): Json<CreateBookClubSchedule>,
) -> APIResult<Json<BookClubSchedule>> {
Expand Down Expand Up @@ -882,9 +907,10 @@
(status = 500, description = "Internal server error")
)
)]
#[tracing::instrument(err, skip(ctx, req))]

Check warning on line 910 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L910

Added line #L910 was not covered by tests
async fn get_book_club_schedule(
State(ctx): State<AppState>,
Path(id): Path<String>,
State(ctx): State<AppState>,
Extension(req): Extension<RequestContext>,
) -> APIResult<Json<BookClubSchedule>> {
let client = &ctx.db;
Expand Down Expand Up @@ -914,14 +940,15 @@
Ok(Json(BookClubSchedule::from(book_club_schedule)))
}

#[derive(Deserialize, Type, ToSchema)]
#[derive(Serialize, Deserialize, Debug, Type, ToSchema)]

Check warning on line 943 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L943

Added line #L943 was not covered by tests
pub struct AddBooksToBookClubSchedule {
pub books: Vec<CreateBookClubScheduleBook>,
}

#[tracing::instrument(err, skip(ctx, req))]

Check warning on line 948 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L948

Added line #L948 was not covered by tests
async fn add_books_to_book_club_schedule(
State(ctx): State<AppState>,
Path(id): Path<String>,
State(ctx): State<AppState>,
Extension(req): Extension<RequestContext>,
Json(payload): Json<AddBooksToBookClubSchedule>,
) -> APIResult<Json<Vec<BookClubBook>>> {
Expand Down Expand Up @@ -1048,9 +1075,10 @@
(status = 500, description = "Internal server error")
)
)]
#[tracing::instrument(err, skip(ctx, req))]

Check warning on line 1078 in apps/server/src/routers/api/v1/book_club.rs

View check run for this annotation

Codecov / codecov/patch

apps/server/src/routers/api/v1/book_club.rs#L1078

Added line #L1078 was not covered by tests
async fn get_book_club_current_book(
State(ctx): State<AppState>,
Path(id): Path<String>,
State(ctx): State<AppState>,
Extension(req): Extension<RequestContext>,
) -> APIResult<Json<BookClubBook>> {
let client = &ctx.db;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
Warnings:

- You are about to drop the `book_club_chat_boards` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `book_club_chat_message_likes` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `book_club_chat_messages` table. If the table is not empty, all the data it contains will be lost.

*/
-- AlterTable
ALTER TABLE "book_club_books" ADD COLUMN "image_url" TEXT;

-- AlterTable
ALTER TABLE "book_club_member_favorite_books" ADD COLUMN "image_url" TEXT;

-- DropTable
PRAGMA foreign_keys=off;
DROP TABLE "book_club_chat_boards";
PRAGMA foreign_keys=on;

-- DropTable
PRAGMA foreign_keys=off;
DROP TABLE "book_club_chat_message_likes";
PRAGMA foreign_keys=on;

-- DropTable
PRAGMA foreign_keys=off;
DROP TABLE "book_club_chat_messages";
PRAGMA foreign_keys=on;

-- CreateTable
CREATE TABLE "book_club_discussions" (
"id" TEXT NOT NULL PRIMARY KEY,
"is_locked" BOOLEAN NOT NULL DEFAULT false,
"book_club_book_id" TEXT NOT NULL,
CONSTRAINT "book_club_discussions_book_club_book_id_fkey" FOREIGN KEY ("book_club_book_id") REFERENCES "book_club_books" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

-- CreateTable
CREATE TABLE "book_club_discussion_messages" (
"id" TEXT NOT NULL PRIMARY KEY,
"content" TEXT NOT NULL,
"timestamp" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"is_top_message" BOOLEAN NOT NULL DEFAULT true,
"deleted_at" DATETIME,
"parent_message_id" TEXT,
"discussion_id" TEXT NOT NULL,
"member_id" TEXT,
CONSTRAINT "book_club_discussion_messages_parent_message_id_fkey" FOREIGN KEY ("parent_message_id") REFERENCES "book_club_discussion_messages" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "book_club_discussion_messages_discussion_id_fkey" FOREIGN KEY ("discussion_id") REFERENCES "book_club_discussions" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "book_club_discussion_messages_member_id_fkey" FOREIGN KEY ("member_id") REFERENCES "book_club_members" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

-- CreateTable
CREATE TABLE "book_club_discussion_message_likes" (
"id" TEXT NOT NULL PRIMARY KEY,
"timestamp" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"liked_by_id" TEXT NOT NULL,
"message_id" TEXT NOT NULL,
CONSTRAINT "book_club_discussion_message_likes_liked_by_id_fkey" FOREIGN KEY ("liked_by_id") REFERENCES "book_club_members" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "book_club_discussion_message_likes_message_id_fkey" FOREIGN KEY ("message_id") REFERENCES "book_club_discussion_messages" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);

-- CreateIndex
CREATE UNIQUE INDEX "book_club_discussions_book_club_book_id_key" ON "book_club_discussions"("book_club_book_id");
Loading
Loading