feat: let students customize public profiles
This commit is contained in:
@@ -72,6 +72,7 @@ async function assertVideoAccess(videoId: string, user: { id: string; role: stri
|
||||
|
||||
export const mediaRoutes: FastifyPluginAsync = async (app) => {
|
||||
const manageAccess = { preHandler: app.requireRoles(['instructor', 'admin']) };
|
||||
const coverAccess = { preHandler: app.authenticate };
|
||||
|
||||
app.get('/bunny/config', manageAccess, async () => ({
|
||||
data: {
|
||||
@@ -80,14 +81,14 @@ export const mediaRoutes: FastifyPluginAsync = async (app) => {
|
||||
},
|
||||
}));
|
||||
|
||||
app.get('/covers/config', manageAccess, async () => ({
|
||||
app.get('/covers/config', coverAccess, async () => ({
|
||||
data: {
|
||||
configured: isBunnyStorageConfigured(),
|
||||
maxUploadBytes: config.BUNNY_COVER_MAX_UPLOAD_MB * 1024 * 1024,
|
||||
},
|
||||
}));
|
||||
|
||||
app.post('/covers', manageAccess, async (request, reply) => {
|
||||
app.post('/covers', coverAccess, async (request, reply) => {
|
||||
const contentType = request.headers['content-type']?.split(';')[0]?.toLowerCase();
|
||||
const body = request.body;
|
||||
if (!contentType || !Buffer.isBuffer(body)) return reply.code(400).send({ error: 'Envie um arquivo de imagem.' });
|
||||
|
||||
@@ -4,6 +4,7 @@ import { recordAudit } from '../audit.js';
|
||||
import { pool } from '../db/pool.js';
|
||||
import { courseSelect } from './courses.js';
|
||||
|
||||
const profileParamsSchema = z.object({ profileId: z.string().uuid() });
|
||||
const instructorParamsSchema = z.object({ instructorId: z.string().uuid() });
|
||||
|
||||
const optionalUrl = z.union([z.string().url().max(500), z.literal(''), z.null()]).optional()
|
||||
@@ -68,25 +69,23 @@ export const profileRoutes: FastifyPluginAsync = async (app) => {
|
||||
|
||||
app.patch('/me', { preHandler: app.authenticate }, async (request, reply) => {
|
||||
const input = profileUpdateSchema.parse(request.body);
|
||||
const isInstructor = request.user.role === 'instructor' || request.user.role === 'admin';
|
||||
const result = await pool.query<ProfileRow>(
|
||||
`update users set
|
||||
display_name = $2,
|
||||
avatar_image_url = case when $3::boolean then $4 else avatar_image_url end,
|
||||
profile_headline = case when $3::boolean then $5 else profile_headline end,
|
||||
profile_bio = case when $3::boolean then $6 else profile_bio end,
|
||||
website_url = case when $3::boolean then $7 else website_url end,
|
||||
linkedin_url = case when $3::boolean then $8 else linkedin_url end,
|
||||
instagram_url = case when $3::boolean then $9 else instagram_url end,
|
||||
youtube_url = case when $3::boolean then $10 else youtube_url end,
|
||||
profile_is_public = case when $3::boolean then coalesce($11, profile_is_public) else profile_is_public end,
|
||||
avatar_image_url = $3,
|
||||
profile_headline = $4,
|
||||
profile_bio = $5,
|
||||
website_url = $6,
|
||||
linkedin_url = $7,
|
||||
instagram_url = $8,
|
||||
youtube_url = $9,
|
||||
profile_is_public = coalesce($10, profile_is_public),
|
||||
updated_at = now()
|
||||
where id = $1
|
||||
returning ${profileColumns}, email`,
|
||||
[
|
||||
request.user.id,
|
||||
input.name,
|
||||
isInstructor,
|
||||
input.avatarImageUrl,
|
||||
input.headline,
|
||||
input.bio,
|
||||
@@ -103,21 +102,30 @@ export const profileRoutes: FastifyPluginAsync = async (app) => {
|
||||
return { data: profile };
|
||||
});
|
||||
|
||||
app.get('/instructors/:instructorId', async (request, reply) => {
|
||||
const { instructorId } = instructorParamsSchema.parse(request.params);
|
||||
const getPublicProfile = async (profileId: string, reply: { code: (status: number) => { send: (payload: object) => unknown } }, instructorOnly = false) => {
|
||||
const result = await pool.query<ProfileRow>(
|
||||
`select ${profileColumns}
|
||||
from users
|
||||
where id = $1 and profile_is_public and role in ('instructor', 'admin')`,
|
||||
[instructorId],
|
||||
where id = $1 and profile_is_public${instructorOnly ? " and role in ('instructor', 'admin')" : ''}`,
|
||||
[profileId],
|
||||
);
|
||||
const profile = result.rows[0];
|
||||
if (!profile) return reply.code(404).send({ error: 'Perfil de instrutor não encontrado.' });
|
||||
if (!profile) return reply.code(404).send({ error: instructorOnly ? 'Perfil de instrutor não encontrado.' : 'Perfil não encontrado ou não está público.' });
|
||||
|
||||
const courses = await pool.query(
|
||||
`${courseSelect()} where c.instructor_id = $1 and c.status = 'published' order by c.published_at desc`,
|
||||
[instructorId],
|
||||
);
|
||||
const canPublishCourses = profile.role === 'instructor' || profile.role === 'admin';
|
||||
const courses = canPublishCourses
|
||||
? await pool.query(`${courseSelect()} where c.instructor_id = $1 and c.status = 'published' order by c.published_at desc`, [profileId])
|
||||
: { rows: [] };
|
||||
return { data: { ...profile, courses: courses.rows.map(withoutProtectedMedia) } };
|
||||
};
|
||||
|
||||
app.get('/:profileId', async (request, reply) => {
|
||||
const { profileId } = profileParamsSchema.parse(request.params);
|
||||
return getPublicProfile(profileId, reply);
|
||||
});
|
||||
|
||||
app.get('/instructors/:instructorId', async (request, reply) => {
|
||||
const { instructorId } = instructorParamsSchema.parse(request.params);
|
||||
return getPublicProfile(instructorId, reply, true);
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user