Skip to content

Commit 9fec009

Browse files
committed
feat: adding R2 video source, chapters, & captions in preparation for Cloudflare R2 testing
1 parent 66edad6 commit 9fec009

20 files changed

+582
-8
lines changed

app/actions/posts/destroy_post.ts

+3
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ export default class DestroyPost {
2424
await post.related('collections').detach()
2525
await post.related('taxonomies').detach()
2626

27+
await post.related('chapters').query().delete()
28+
await post.related('captions').query().delete()
29+
2730
await this.#destroyComments(post, trx)
2831
await this.#destroyHistory(post)
2932
await this.#removeFromWatchlists(post)

app/actions/posts/get_post.ts

+3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ export default class GetPost {
66
.where({ id })
77
.preload('thumbnails', (query) => query.orderBy('sort_order'))
88
.preload('covers', (query) => query.orderBy('sort_order'))
9+
.preload('chapters', (query) => query.orderBy('sort_order'))
10+
.preload('captions', (query) => query.orderBy('sort_order'))
911
.preload('taxonomies')
12+
1013
.firstOrFail()
1114
}
1215
}

app/actions/posts/store_post.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import db from '@adonisjs/lucid/services/db'
77
import { Infer } from '@vinejs/vine/types'
88
import SyncPostAsset from './sync_post_assets.js'
99
import SyncTaxonomies from '#actions/taxonomies/sync_taxonomies'
10+
import SyncCaptions from './sync_captions.js'
11+
import SyncChapters from './sync_chapters.js'
1012

1113
type Data = Infer<typeof postValidator>
1214

@@ -20,7 +22,8 @@ export default class StorePost {
2022
if (!data.stateId) data.stateId = States.DRAFT
2123
if (!data.postTypeId) data.postTypeId = PostTypes.LESSON
2224

23-
const { thumbnail, publishAtDate, publishAtTime, taxonomyIds, ...store } = data
25+
const { thumbnail, publishAtDate, publishAtTime, taxonomyIds, captions, chapters, ...store } =
26+
data
2427

2528
return db.transaction(async (trx) => {
2629
const post = await Post.create(store, { client: trx })
@@ -29,6 +32,8 @@ export default class StorePost {
2932

3033
await SyncTaxonomies.handle({ resource: post, ids: taxonomyIds })
3134
await SyncPostAsset.handle({ post, asset: thumbnail }, trx)
35+
await SyncCaptions.handle({ post, captions }, trx)
36+
await SyncChapters.handle({ post, chapters }, trx)
3237

3338
return post
3439
})

app/actions/posts/sync_captions.ts

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import Post from '#models/post'
2+
import { postValidator } from '#validators/post'
3+
import { TransactionClientContract } from '@adonisjs/lucid/types/database'
4+
import { Infer } from '@vinejs/vine/types'
5+
6+
type Params = {
7+
post: Post
8+
captions: Infer<typeof postValidator>['captions']
9+
}
10+
11+
export default class SyncCaptions {
12+
static async handle({ post, captions }: Params, trx: TransactionClientContract) {
13+
if (!captions?.length) {
14+
await post.related('captions').query().delete()
15+
return
16+
}
17+
18+
const promises = captions.map(async (caption, index) => {
19+
if (!caption.id) {
20+
return post.related('captions').create(caption, { client: trx })
21+
}
22+
23+
const row = await post.related('captions').query().where('id', caption.id).firstOrFail()
24+
25+
row.useTransaction(trx)
26+
row.merge({
27+
sortOrder: index,
28+
...caption,
29+
})
30+
31+
return row.save()
32+
})
33+
34+
await Promise.all(promises)
35+
}
36+
}

app/actions/posts/sync_chapters.ts

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import Post from '#models/post'
2+
import { postValidator } from '#validators/post'
3+
import { TransactionClientContract } from '@adonisjs/lucid/types/database'
4+
import { Infer } from '@vinejs/vine/types'
5+
6+
type Params = {
7+
post: Post
8+
chapters: Infer<typeof postValidator>['chapters']
9+
}
10+
11+
export default class SyncChapters {
12+
static async handle({ post, chapters }: Params, trx: TransactionClientContract) {
13+
if (!chapters?.length) {
14+
await post.related('chapters').query().delete()
15+
return
16+
}
17+
18+
const promises = chapters.map(async (chapter, index) => {
19+
if (!chapter.id) {
20+
return post.related('chapters').create(chapter, { client: trx })
21+
}
22+
23+
const row = await post.related('chapters').query().where('id', chapter.id).firstOrFail()
24+
25+
row.useTransaction(trx)
26+
row.merge({
27+
sortOrder: index,
28+
...chapter,
29+
})
30+
31+
return row.save()
32+
})
33+
34+
await Promise.all(promises)
35+
}
36+
}

app/actions/posts/update_post.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import db from '@adonisjs/lucid/services/db'
66
import { Infer } from '@vinejs/vine/types'
77
import SyncPostAsset from './sync_post_assets.js'
88
import SyncTaxonomies from '#actions/taxonomies/sync_taxonomies'
9+
import SyncCaptions from './sync_captions.js'
10+
import SyncChapters from './sync_chapters.js'
911

1012
type Data = Infer<typeof postValidator>
1113

@@ -25,7 +27,8 @@ export default class UpdatePost {
2527
if (!data.stateId) data.stateId = States.DRAFT
2628
if (!data.postTypeId) data.postTypeId = PostTypes.LESSON
2729

28-
const { thumbnail, publishAtDate, publishAtTime, taxonomyIds, ...update } = data
30+
const { thumbnail, publishAtDate, publishAtTime, taxonomyIds, captions, chapters, ...update } =
31+
data
2932

3033
post.merge(update)
3134

@@ -39,6 +42,8 @@ export default class UpdatePost {
3942
await post.save()
4043
await SyncTaxonomies.handle({ resource: post, ids: taxonomyIds })
4144
await SyncPostAsset.handle({ post, asset: thumbnail }, trx)
45+
await SyncCaptions.handle({ post, captions }, trx)
46+
await SyncChapters.handle({ post, chapters }, trx)
4247

4348
return post
4449
})

app/dtos/post.ts

+6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import VideoTypes from '#enums/video_types'
1313
import States from '#enums/states'
1414
import PaywallTypes from '#enums/paywall_types'
1515
import PostTypes from '#enums/post_types'
16+
import PostChapterDto from './post_chapter.js'
17+
import PostCaptionDto from './post_caption.js'
1618

1719
export default class PostDto extends BaseModelDto {
1820
declare id: number
@@ -69,6 +71,8 @@ export default class PostDto extends BaseModelDto {
6971
declare viewHistory: HistoryDto[]
7072
declare progressionHistory: ProgressDto[]
7173
declare watchlist: WatchlistDto[]
74+
declare chapters: PostChapterDto[]
75+
declare captions: PostCaptionDto[]
7276

7377
declare publishAtISO: string | null
7478
declare publishAtDisplay: string
@@ -146,6 +150,8 @@ export default class PostDto extends BaseModelDto {
146150
this.viewHistory = HistoryDto.fromArray(post.viewHistory)
147151
this.progressionHistory = ProgressDto.fromArray(post.progressionHistory)
148152
this.watchlist = WatchlistDto.fromArray(post.watchlist)
153+
this.chapters = PostChapterDto.fromArray(post.chapters)
154+
this.captions = PostCaptionDto.fromArray(post.captions)
149155

150156
this.publishAtISO = post.publishAtISO
151157
this.publishAtDisplay = post.publishAtDisplay

app/dtos/post_caption.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { BaseModelDto } from '@adocasts.com/dto/base'
2+
import PostCaption from '#models/post_caption'
3+
import CaptionTypes from '#enums/caption_types'
4+
import CaptionLanguages from '#enums/caption_languages'
5+
import PostDto from '#dtos/post'
6+
7+
export default class PostCaptionDto extends BaseModelDto {
8+
declare id: number
9+
declare postId: number
10+
declare type: CaptionTypes
11+
declare label: string
12+
declare language: CaptionLanguages
13+
declare filename: string
14+
declare sortOrder: number
15+
declare createdAt: string
16+
declare updatedAt: string
17+
declare post: PostDto | null
18+
19+
constructor(postCaption?: PostCaption) {
20+
super()
21+
22+
if (!postCaption) return
23+
this.id = postCaption.id
24+
this.postId = postCaption.postId
25+
this.type = postCaption.type
26+
this.label = postCaption.label
27+
this.language = postCaption.language
28+
this.filename = postCaption.filename
29+
this.sortOrder = postCaption.sortOrder
30+
this.createdAt = postCaption.createdAt.toISO()!
31+
this.updatedAt = postCaption.updatedAt.toISO()!
32+
this.post = postCaption.post && new PostDto(postCaption.post)
33+
}
34+
}

app/dtos/post_chapter.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { BaseModelDto } from '@adocasts.com/dto/base'
2+
import PostChapter from '#models/post_chapter'
3+
import PostDto from '#dtos/post'
4+
5+
export default class PostChapterDto extends BaseModelDto {
6+
declare id: number
7+
declare postId: number
8+
declare start: string
9+
declare end: string
10+
declare text: string
11+
declare sortOrder: number
12+
declare createdAt: string
13+
declare updatedAt: string
14+
declare post: PostDto | null
15+
declare startSeconds: number
16+
declare endSeconds: number
17+
18+
constructor(postChapter?: PostChapter) {
19+
super()
20+
21+
if (!postChapter) return
22+
this.id = postChapter.id
23+
this.postId = postChapter.postId
24+
this.start = postChapter.start
25+
this.end = postChapter.end
26+
this.text = postChapter.text
27+
this.sortOrder = postChapter.sortOrder
28+
this.createdAt = postChapter.createdAt.toISO()!
29+
this.updatedAt = postChapter.updatedAt.toISO()!
30+
this.post = postChapter.post && new PostDto(postChapter.post)
31+
this.startSeconds = postChapter.startSeconds
32+
this.endSeconds = postChapter.endSeconds
33+
}
34+
}

app/dtos/post_form.ts

+36
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ import VideoTypes from '#enums/video_types'
55
import States from '#enums/states'
66
import PaywallTypes from '#enums/paywall_types'
77
import PostTypes from '#enums/post_types'
8+
import CaptionTypes from '#enums/caption_types'
9+
import CaptionLanguages from '#enums/caption_languages'
10+
11+
type Caption = {
12+
id?: number
13+
type: CaptionTypes
14+
language: CaptionLanguages
15+
label: string
16+
}
17+
18+
type Chapter = {
19+
id?: number
20+
start: string
21+
end: string
22+
text: string
23+
}
824

925
export default class PostFormDto extends BaseModelDto {
1026
declare id: number
@@ -38,6 +54,8 @@ export default class PostFormDto extends BaseModelDto {
3854
declare thumbnail: AssetDto | null
3955
declare cover: AssetDto | null
4056
declare taxonomyIds: number[]
57+
declare captions: Caption[] | null
58+
declare chapters: Chapter[] | null
4159

4260
constructor(post?: Post) {
4361
super()
@@ -74,5 +92,23 @@ export default class PostFormDto extends BaseModelDto {
7492
this.thumbnail = post.thumbnails.length ? new AssetDto(post.thumbnails[0]) : null
7593
this.cover = post.covers.length ? new AssetDto(post.covers[0]) : null
7694
this.taxonomyIds = post.taxonomies?.map((row) => row.id) ?? []
95+
96+
if (post.captions?.length) {
97+
this.captions = post.captions.map((row) => ({
98+
id: row.id,
99+
type: row.type,
100+
language: row.language,
101+
label: row.label,
102+
}))
103+
}
104+
105+
if (post.chapters?.length) {
106+
this.chapters = post.chapters.map((row) => ({
107+
id: row.id,
108+
start: row.start,
109+
end: row.end,
110+
text: row.text,
111+
}))
112+
}
77113
}
78114
}

app/enums/caption_languages.ts

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
enum CaptionLanguages {
2+
ENGLISH = 'en',
3+
SPANISH = 'es',
4+
FRENCH = 'fr',
5+
GERMAN = 'de',
6+
PORTUGUESE = 'pt',
7+
}
8+
9+
export const CaptionLanguageDesc: Record<CaptionLanguages, string> = {
10+
[CaptionLanguages.ENGLISH]: 'English',
11+
[CaptionLanguages.SPANISH]: 'Spanish',
12+
[CaptionLanguages.FRENCH]: 'French',
13+
[CaptionLanguages.GERMAN]: 'German',
14+
[CaptionLanguages.PORTUGUESE]: 'Portuguese',
15+
}
16+
17+
export default CaptionLanguages

app/enums/caption_types.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
enum CaptionTypes {
2+
SRT = 'srt',
3+
VTT = 'vtt',
4+
}
5+
6+
export const CaptionTypeDesc: Record<CaptionTypes, string> = {
7+
[CaptionTypes.SRT]: 'SRT',
8+
[CaptionTypes.VTT]: 'VTT',
9+
}
10+
11+
export default CaptionTypes

app/models/post.ts

+8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import SlugService from '#services/slug_service'
2727
import router from '@adonisjs/core/services/router'
2828
import Progress from './progress.js'
2929
import PostTypes from '#enums/post_types'
30+
import PostCaption from './post_caption.js'
31+
import PostChapter from './post_chapter.js'
3032

3133
export default class Post extends AppBaseModel {
3234
serializeExtras = true
@@ -164,6 +166,12 @@ export default class Post extends AppBaseModel {
164166
@hasMany(() => Comment)
165167
declare comments: HasMany<typeof Comment>
166168

169+
@hasMany(() => PostCaption)
170+
declare captions: HasMany<typeof PostCaption>
171+
172+
@hasMany(() => PostChapter)
173+
declare chapters: HasMany<typeof PostChapter>
174+
167175
@manyToMany(() => User, {
168176
pivotTable: 'author_posts',
169177
pivotColumns: ['author_type_id'],

0 commit comments

Comments
 (0)