feat: add course cover positioning
All checks were successful
CI / Validate frontend and API (push) Successful in 37s
CI / Build and publish Docker images (push) Successful in 18s

This commit is contained in:
Cauê Faleiros
2026-09-04 14:42:04 -03:00
parent ad860074fd
commit 7a01be2b59
10 changed files with 120 additions and 18 deletions

View File

@@ -0,0 +1,5 @@
alter table courses
add column cover_image_position_x real not null default 50
check (cover_image_position_x >= 0 and cover_image_position_x <= 100),
add column cover_image_position_y real not null default 50
check (cover_image_position_y >= 0 and cover_image_position_y <= 100);

View File

@@ -22,6 +22,8 @@ export const courseSelect = (publicOnly = false, includeUnreadyMedia = false) =>
select value #>> '{}' from platform_settings where key = 'media.defaultCoverImageUrl'
)) as "coverImageUrl",
c.cover_image_zoom as "coverImageZoom",
c.cover_image_position_x as "coverImagePositionX",
c.cover_image_position_y as "coverImagePositionY",
c.status,
c.published_at as "publishedAt",
jsonb_build_object(

View File

@@ -46,6 +46,8 @@ const courseSchema = z.object({
category: z.string().trim().min(1).max(120),
coverImageUrl: coverImageUrlSchema.nullable().optional(),
coverImageZoom: z.number().min(1).max(2.5).default(1),
coverImagePositionX: z.number().min(0).max(100).default(50),
coverImagePositionY: z.number().min(0).max(100).default(50),
status: z.enum(['draft', 'published']).default('draft'),
lessons: z.array(lessonSchema).min(1),
assets: z.array(assetSchema).default([]),
@@ -214,10 +216,10 @@ export const manageCourseRoutes: FastifyPluginAsync = async (app) => {
await client.query('begin');
const slug = await uniqueSlug(client, input.slug, input.title);
const course = await client.query<{ id: string }>(
`insert into courses (slug, title, description, category, cover_image_url, cover_image_zoom, status, instructor_id, published_at)
values ($1, $2, $3, $4, $5, $6, $7::course_status, $8, case when $7::course_status = 'published'::course_status then now() else null end)
`insert into courses (slug, title, description, category, cover_image_url, cover_image_zoom, cover_image_position_x, cover_image_position_y, status, instructor_id, published_at)
values ($1, $2, $3, $4, $5, $6, $7, $8, $9::course_status, $10, case when $9::course_status = 'published'::course_status then now() else null end)
returning id`,
[slug, input.title, input.description, input.category, input.coverImageUrl ?? null, input.coverImageZoom, input.status, request.user.id],
[slug, input.title, input.description, input.category, input.coverImageUrl ?? null, input.coverImageZoom, input.coverImagePositionX, input.coverImagePositionY, input.status, request.user.id],
);
await replaceCourseContents(client, course.rows[0].id, input);
await client.query('commit');
@@ -246,10 +248,10 @@ export const manageCourseRoutes: FastifyPluginAsync = async (app) => {
const slug = await uniqueSlug(client, input.slug, input.title, courseId);
await client.query(
`update courses
set slug = $2, title = $3, description = $4, category = $5, cover_image_url = $6, cover_image_zoom = $7,
status = $8::course_status, published_at = case when $8::course_status = 'published'::course_status then coalesce(published_at, now()) else null end
set slug = $2, title = $3, description = $4, category = $5, cover_image_url = $6, cover_image_zoom = $7, cover_image_position_x = $8, cover_image_position_y = $9,
status = $10::course_status, published_at = case when $10::course_status = 'published'::course_status then coalesce(published_at, now()) else null end
where id = $1`,
[courseId, slug, input.title, input.description, input.category, input.coverImageUrl ?? null, input.coverImageZoom, input.status],
[courseId, slug, input.title, input.description, input.category, input.coverImageUrl ?? null, input.coverImageZoom, input.coverImagePositionX, input.coverImagePositionY, input.status],
);
await replaceCourseContents(client, courseId, input);
await client.query('commit');
@@ -281,8 +283,8 @@ export const manageCourseRoutes: FastifyPluginAsync = async (app) => {
const client = await pool.connect();
try {
await client.query('begin');
const original = await client.query<{ title: string; description: string; category: string; cover_image_url: string | null; cover_image_zoom: number; instructor_id: string }>(
`select title, description, category, cover_image_url, cover_image_zoom, instructor_id from courses where id = $1`, [courseId],
const original = await client.query<{ title: string; description: string; category: string; cover_image_url: string | null; cover_image_zoom: number; cover_image_position_x: number; cover_image_position_y: number; instructor_id: string }>(
`select title, description, category, cover_image_url, cover_image_zoom, cover_image_position_x, cover_image_position_y, instructor_id from courses where id = $1`, [courseId],
);
if (!original.rows[0]) {
await client.query('rollback');
@@ -292,9 +294,9 @@ export const manageCourseRoutes: FastifyPluginAsync = async (app) => {
const title = `${source.title} (cópia)`;
const slug = await uniqueSlug(client, undefined, title);
const copiedCourse = await client.query<{ id: string }>(
`insert into courses (slug, title, description, category, cover_image_url, cover_image_zoom, status, instructor_id)
values ($1, $2, $3, $4, $5, $6, 'draft', $7) returning id`,
[slug, title, source.description, source.category, source.cover_image_url, source.cover_image_zoom, source.instructor_id],
`insert into courses (slug, title, description, category, cover_image_url, cover_image_zoom, cover_image_position_x, cover_image_position_y, status, instructor_id)
values ($1, $2, $3, $4, $5, $6, $7, $8, 'draft', $9) returning id`,
[slug, title, source.description, source.category, source.cover_image_url, source.cover_image_zoom, source.cover_image_position_x, source.cover_image_position_y, source.instructor_id],
);
const newCourseId = copiedCourse.rows[0].id;
await client.query(