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

MVP for user-contributed document audio #435

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
66 changes: 60 additions & 6 deletions graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,6 @@ type AnnotatedDoc {
"""
isReference: Boolean!
"""
The audio recording resource for this entire document
"""
audioRecording: AudioSlice
"""
Arbitrary number used for manually ordering documents in a collection.
For collections without manual ordering, use zero here.
"""
Expand Down Expand Up @@ -73,6 +69,22 @@ type AnnotatedDoc {
Collection chapters that contain this document.
"""
chapters: [CollectionChapter!]
"""
The audio for this document that was ingested from GoogleSheets, if there is any.
"""
ingestedAudioTrack: AudioSlice
"""
A slices of audio associated with this word in the context of a document.
This audio has been selected by an editor from contributions, or is the
same as the ingested audio track, if one is available.
"""
editedAudio: [AudioSlice!]!
"""
Audio for this word that has been recorded by community members. Will be
empty if user does not have access to uncurated contributions.
TODO! User guard for contributors only
"""
userContributedAudio: [AudioSlice!]!
}

"""
Expand Down Expand Up @@ -196,6 +208,21 @@ Element within a spreadsheet before being transformed into a full document.
"""
union AnnotatedSeg = AnnotatedForm | LineBreak

"""
Request to attach user-recorded audio to a document
"""
input AttachAudioToDocumentInput {
"""
Document to bind audio to
"""
documentId: UUID!
"""
A URL to a Cloudfront-proxied user-recorded reading of a document.
A new resource will be created to represent the recording if one does not exist already
"""
contributorAudioUrl: String!
}

"""
Request to attach user-recorded audio to a word
"""
Expand Down Expand Up @@ -436,7 +463,25 @@ type ContributorDetails {
}

"""
Request to update if a piece of audio should be included in an edited collection
Request to update if a piece of document audio should be included in an edited collection
"""
input CurateDocumentAudioInput {
"""
Document audio is attached to
"""
documentId: UUID!
"""
Audio to include/exclude
"""
audioSliceId: UUID!
"""
New value
"""
includeInEditedCollection: Boolean!
}

"""
Request to update if a piece of word audio should be included in an edited collection
"""
input CurateWordAudioInput {
"""
Expand Down Expand Up @@ -845,14 +890,23 @@ type Mutation {
"""
removeBookmark(documentId: UUID!): AnnotatedDoc!
"""
Decide if a piece audio should be included in edited collection
Decide if a piece of word audio should be included in edited collection
"""
curateWordAudio(input: CurateWordAudioInput!): AnnotatedForm!
"""
Decide if a piece of document audio should be included in edited collection
"""
curateDocumentAudio(input: CurateDocumentAudioInput!): AnnotatedDoc!
"""
Attach audio that has already been uploaded to S3 to a particular word
Assumes user requesting mutation recoreded the audio
"""
attachAudioToWord(input: AttachAudioToWordInput!): AnnotatedForm!
"""
Attach audio that has already been uploaded to S3 to a particular document
Assumes user requesting mutation recorded the audio
"""
attachAudioToDocument(input: AttachAudioToDocumentInput!): AnnotatedDoc!
updateDocumentMetadata(document: DocumentMetadataUpdate!): UUID!
}

Expand Down
59 changes: 56 additions & 3 deletions graphql/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
use dailp::{
auth::{AuthGuard, GroupGuard, UserGroup, UserInfo},
comment::{CommentParent, CommentUpdate, DeleteCommentInput, PostCommentInput},
slugify_ltree, AnnotatedForm, AttachAudioToWordInput, CollectionChapter, CurateWordAudioInput,
slugify_ltree, AnnotatedForm, AttachAudioToDocumentInput, AttachAudioToWordInput,
CollectionChapter, CurateDocumentAudioInput, CurateWordAudioInput,
DeleteContributorAttribution, DocumentMetadataUpdate, DocumentParagraph,
UpdateContributorAttribution, Uuid,
};
Expand Down Expand Up @@ -580,7 +581,7 @@ impl Mutation {
.ok_or_else(|| anyhow::format_err!("Failed to load document"))?)
}

/// Decide if a piece audio should be included in edited collection
/// Decide if a piece of word audio should be included in edited collection
#[graphql(guard = "GroupGuard::new(UserGroup::Editors)")]
async fn curate_word_audio(
&self,
Expand All @@ -594,7 +595,7 @@ impl Mutation {
let word_id = context
.data::<DataLoader<Database>>()?
.loader()
.update_audio_visibility(
.update_word_audio_visibility(
&input.word_id,
&input.audio_slice_id,
input.include_in_edited_collection,
Expand All @@ -608,6 +609,35 @@ impl Mutation {
.await?)
}

/// Decide if a piece of document audio should be included in edited collection
#[graphql(guard = "GroupGuard::new(UserGroup::Editors)")]
async fn curate_document_audio(
&self,
context: &Context<'_>,
input: CurateDocumentAudioInput,
) -> FieldResult<dailp::AnnotatedDoc> {
let user = context
.data_opt::<UserInfo>()
.ok_or_else(|| anyhow::format_err!("User is not signed in"))?;
let document_id = context
.data::<DataLoader<Database>>()?
.loader()
.update_document_audio_visibility(
&input.document_id,
&input.audio_slice_id,
input.include_in_edited_collection,
&user.id,
)
.await?;
Ok(context
.data::<DataLoader<Database>>()?
.load_one(dailp::DocumentId(
document_id.ok_or_else(|| anyhow::format_err!("Document not found"))?,
))
.await?
.ok_or_else(|| anyhow::format_err!("Document not found"))?)
}

/// Attach audio that has already been uploaded to S3 to a particular word
/// Assumes user requesting mutation recoreded the audio
#[graphql(guard = "GroupGuard::new(UserGroup::Contributors)")]
Expand All @@ -632,6 +662,29 @@ impl Mutation {
.await?)
}

/// Attach audio that has already been uploaded to S3 to a particular document
/// Assumes user requesting mutation recorded the audio
#[graphql(guard = "GroupGuard::new(UserGroup::Contributors)")]
async fn attach_audio_to_document(
&self,
context: &Context<'_>,
input: AttachAudioToDocumentInput,
) -> FieldResult<dailp::AnnotatedDoc> {
let user = context
.data_opt::<UserInfo>()
.ok_or_else(|| anyhow::format_err!("User is not signed in"))?;
let _media_slice_id = context
.data::<DataLoader<Database>>()?
.loader()
.attach_audio_to_document(&input, &user.id)
.await?;
Ok(context
.data::<DataLoader<Database>>()?
.load_one(dailp::DocumentId(input.document_id))
.await?
.ok_or_else(|| anyhow::format_err!("Document not found"))?)
}

#[graphql(guard = "GroupGuard::new(UserGroup::Editors)")]
async fn update_document_metadata(
&self,
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions types/queries/attach_audio_to_document.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-- Binds: user_id, resource_url, start, end, document_id

with upserted_audio_resource as (
insert into media_resource (url, recorded_at, recorded_by)
select $2::text, now(), $1
-- we do this no-op update to ensure an id is returned
on conflict (url) do update set url=excluded.url
returning id
),

inserted_audio_slice as (
insert into media_slice (resource_id, time_range)
select upserted_audio_resource.id, int8range($3, $4)
from upserted_audio_resource
returning id
)

insert into document_user_media (document_id, media_slice_id)
select $5, inserted_audio_slice.id
from inserted_audio_slice
join document on document.id = $5
on conflict (media_slice_id, document_id) do nothing -- document already associated
returning media_slice_id
18 changes: 18 additions & 0 deletions types/queries/document_contributor_audio.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
select
media_slice.id as "id",
media_slice.time_range as "range?",
media_resource.url as "resource_url",
document_user_media.include_in_edited_collection as "include_in_edited_collection",
media_resource.recorded_at as "recorded_at?",
contributor.id as "recorded_by?",
contributor.display_name as "recorded_by_name?",
editor.id as "edited_by?",
editor.display_name as "edited_by_name?"
from document_user_media
left join media_slice on media_slice.id = document_user_media.media_slice_id
left join media_resource on media_resource.id = media_slice.resource_id
left join dailp_user contributor on contributor.id = media_resource.recorded_by
left join dailp_user editor on editor.id = document_user_media.edited_by
where
document_id = $1
order by media_resource.recorded_at desc
28 changes: 28 additions & 0 deletions types/queries/update_document_audio_visibility.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
-- Binds: document_id, slice_id, include_in_edited_collection, editor_id

with document_update as (
-- update ingested audio, if the slice_id was ingested audio
update document
set include_audio_in_edited_collection=$3,
audio_edited_by=$4
where document.id = $1
-- if the slice_id given doesn't match, we won't update
and document.audio_slice_id = $2
returning document.id as document_id
),
document_user_media_update as (
-- update user contributed audio, if the slice id is user contributed audio tied to the document
update document_user_media
set include_in_edited_collection=$3,
edited_by=$4
where document_id = $1
and media_slice_id = $2
returning document_id
)

select distinct t.document_id
from (
select document_id from document_update
union
select document_id from document_user_media_update
) as t
Loading