From 195e021da4817fa4cca3a602ce0fa32e82cd34c4 Mon Sep 17 00:00:00 2001 From: fede Date: Tue, 6 Jan 2026 14:02:17 -0300 Subject: [PATCH 01/12] =?UTF-8?q?a=C3=B1adido=20tama=C3=B1o=20de=20imagen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/post/img/[idpost]/+server.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/routes/post/img/[idpost]/+server.ts b/src/routes/post/img/[idpost]/+server.ts index 6f88bab..debec56 100644 --- a/src/routes/post/img/[idpost]/+server.ts +++ b/src/routes/post/img/[idpost]/+server.ts @@ -212,7 +212,8 @@ export const GET: RequestHandler = async ({ params, fetch, request }) => { return new Response(new Uint8Array(pngBuffer), { headers: { 'Content-Type': 'image/png', - 'Cache-Control': 'public, max-age=31536000, immutable' + 'Cache-Control': 'public, max-age=31536000, immutable', + 'Content-Length': pngBuffer.length.toString() } }); }; From d98e06ca31b897ea4d152cc5626ef5f4ed40b3b6 Mon Sep 17 00:00:00 2001 From: fede Date: Tue, 6 Jan 2026 14:03:11 -0300 Subject: [PATCH 02/12] =?UTF-8?q?a=C3=B1adida=20logica=20a=20la=20imagen?= =?UTF-8?q?=20que=20se=20muestra?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/post/[idpost]/+page.svelte | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/routes/post/[idpost]/+page.svelte b/src/routes/post/[idpost]/+page.svelte index 7fbd4dd..647e1c4 100644 --- a/src/routes/post/[idpost]/+page.svelte +++ b/src/routes/post/[idpost]/+page.svelte @@ -65,7 +65,11 @@ : ''} - + {#if data.post?.imageUrl} + + {:else} + + {/if}
From d3a9bb74058d0bb0d383031b0e979e028a7f806f Mon Sep 17 00:00:00 2001 From: fede Date: Wed, 7 Jan 2026 16:05:42 -0300 Subject: [PATCH 03/12] hecha functionalidad para seguir usuarios desde los posts --- src/lib/components/BotonSeguir.svelte | 96 ++++++++++++++++++++++ src/lib/components/PostCard.svelte | 3 +- src/lib/hooks/esSeguido.ts | 26 ++++++ src/lib/hooks/seguirUsuario.ts | 21 +++++ src/lib/stores/cacheSeguidos.svelte.js | 105 +++++++++++++++++++++++++ src/routes/+page.svelte | 2 +- 6 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 src/lib/components/BotonSeguir.svelte create mode 100644 src/lib/hooks/esSeguido.ts create mode 100644 src/lib/hooks/seguirUsuario.ts create mode 100644 src/lib/stores/cacheSeguidos.svelte.js diff --git a/src/lib/components/BotonSeguir.svelte b/src/lib/components/BotonSeguir.svelte new file mode 100644 index 0000000..0e31a04 --- /dev/null +++ b/src/lib/components/BotonSeguir.svelte @@ -0,0 +1,96 @@ + + +{#if mensajeError} + +{/if} + +{#if post.authorName !== $sesionStore?.username && post.authorName !== '[deleted]'} + + + + + + {#if seguido == true} + Dejar de seguir + {:else} + Seguir + {/if} + + +{/if} diff --git a/src/lib/components/PostCard.svelte b/src/lib/components/PostCard.svelte index 4ab90ca..a97fa1b 100644 --- a/src/lib/components/PostCard.svelte +++ b/src/lib/components/PostCard.svelte @@ -32,6 +32,7 @@ import { likePost } from '@/hooks/likePost'; import { goto } from '$app/navigation'; import { resolve } from '$app/paths'; + import BotonSeguir from './BotonSeguir.svelte'; interface postProp { post: Post; @@ -43,7 +44,6 @@ let cargandoBorrar = $state(false); let mensajeError = $state(''); - let cargandoEditar = $state(false); let cargandoLike = $state(false); let errorLike = $state(false); @@ -123,6 +123,7 @@ @{post.authorName}
+ {#if post.authorName === $sesionStore?.username} diff --git a/src/lib/hooks/esSeguido.ts b/src/lib/hooks/esSeguido.ts new file mode 100644 index 0000000..ea2b51d --- /dev/null +++ b/src/lib/hooks/esSeguido.ts @@ -0,0 +1,26 @@ +import { sesionStore } from '@/stores/usuario'; +import { get } from 'svelte/store'; +import type { Post } from '../../types'; +import { apiBase } from '@/stores/url'; + +export async function esSeguido(post: Post) { + if (!get(sesionStore)?.accessToken || post.authorName === '[deleted]') return; + + const id = post.authorId; + try { + const response = await fetch(`${get(apiBase)}/api/users/${id}/is-following`, { + headers: { + Authorization: `Bearer ${get(sesionStore)?.accessToken}` + } + }); + + if (response.ok) { + const data = await response.json(); + return data; + } else { + return false; + } + } catch { + return null; + } +} diff --git a/src/lib/hooks/seguirUsuario.ts b/src/lib/hooks/seguirUsuario.ts new file mode 100644 index 0000000..378bfd8 --- /dev/null +++ b/src/lib/hooks/seguirUsuario.ts @@ -0,0 +1,21 @@ +import { apiBase } from '@/stores/url'; +import { sesionStore } from '@/stores/usuario'; +import { get } from 'svelte/store'; + +export async function seguirUsuario(idusuario: string, toggle: Boolean = false) { + try { + const req = await fetch(`${get(apiBase)}/api/users/${idusuario}/follow`, { + method: !toggle ? 'POST' : 'DELETE', + headers: { + Authorization: `Bearer ${get(sesionStore)?.accessToken}` + } + }); + if (req.ok) { + return true; + } else { + return false; + } + } catch { + return null; + } +} diff --git a/src/lib/stores/cacheSeguidos.svelte.js b/src/lib/stores/cacheSeguidos.svelte.js new file mode 100644 index 0000000..279429a --- /dev/null +++ b/src/lib/stores/cacheSeguidos.svelte.js @@ -0,0 +1,105 @@ +import { browser } from '$app/environment'; +import { writable } from 'svelte/store'; + +class FollowCache { + constructor() { + if (browser) { + this.loadFromStorage(); + } + } + + /** @type {Map} */ + #cache = new Map(); + + /** @type {import('svelte/store').Writable>} */ + store = writable(this.#cache); + + /** + * @param {string} userId + * @returns {boolean | undefined} + */ + get(userId) { + return this.#cache.get(userId); + } + + /** + * @param {string} userId + * @param {boolean} isFollowed + */ + set(userId, isFollowed) { + this.#cache.set(userId, isFollowed); + this.store.set(this.#cache); + this.saveToStorage(); + + if (browser) { + window.dispatchEvent( + new CustomEvent('followCacheUpdated', { + detail: { userId, isFollowed } + }) + ); + } + } + + /** + * @param {string} userId + * @returns {boolean} + */ + has(userId) { + return this.#cache.has(userId); + } + + /** + * @param {string} userId + */ + delete(userId) { + this.#cache.delete(userId); + this.store.set(this.#cache); + this.saveToStorage(); + + if (browser) { + window.dispatchEvent( + new CustomEvent('followCacheUpdated', { + detail: { userId, isFollowed: false } + }) + ); + } + } + + clear() { + this.#cache.clear(); + this.store.set(this.#cache); + this.saveToStorage(); + + if (browser) { + window.dispatchEvent( + new CustomEvent('followCacheUpdated', { + detail: { clearAll: true } + }) + ); + } + } + + saveToStorage() { + if (browser) { + const data = Object.fromEntries(this.#cache); + sessionStorage.setItem('follow-cache', JSON.stringify(data)); + } + } + + loadFromStorage() { + if (browser) { + try { + const stored = sessionStorage.getItem('follow-cache'); + if (stored) { + const data = JSON.parse(stored); + this.#cache = new Map(Object.entries(data)); + this.store.set(this.#cache); + } + } catch (error) { + console.error('Error cargando desde sesion:', error); + } + } + } +} + +export const cacheSeguidos = new FollowCache(); diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 1e1533d..eec2ce0 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -38,7 +38,7 @@ - + From 680ff35a2d489cda12210061d869674498981120 Mon Sep 17 00:00:00 2001 From: fede Date: Wed, 7 Jan 2026 16:42:24 -0300 Subject: [PATCH 04/12] =?UTF-8?q?a=C3=B1adido=20soporte=20de=20seguir=20us?= =?UTF-8?q?uarios=20en=20pagina=20de=20perfil=20y=20search?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/components/BotonSeguir.svelte | 8 ++++---- src/lib/components/UserCard.svelte | 3 ++- src/routes/[perfil]/+page.svelte | 4 ++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/lib/components/BotonSeguir.svelte b/src/lib/components/BotonSeguir.svelte index 0e31a04..f2c4c6c 100644 --- a/src/lib/components/BotonSeguir.svelte +++ b/src/lib/components/BotonSeguir.svelte @@ -15,7 +15,8 @@ import DialogContent from './ui/dialog/dialog-content.svelte'; import Dialog from './ui/dialog/dialog.svelte'; - let { post }: { post: Post } = $props(); + let { post }: { post: Omit, 'authorId'> & { authorId: string } } = $props(); + let seguido: Boolean | null = $state(null); if (typeof window !== 'undefined') { @@ -56,10 +57,9 @@ + + {#if data.seguidores.length === 0} +
+

No hay seguidores para mostrar.

+
+ {:else} +
+ {#each data.seguidores as follower (follower.id)} +
+ +
+ {/each} +
+ {/if} + + diff --git a/src/routes/[perfil]/seguidores/+page.ts b/src/routes/[perfil]/seguidores/+page.ts new file mode 100644 index 0000000..e9f0f8d --- /dev/null +++ b/src/routes/[perfil]/seguidores/+page.ts @@ -0,0 +1,20 @@ +import { obtenerSeguidoresPorUsuario } from '@/hooks/obtenerSeguidoresPorUsuario'; +import { obtenerUsuarioPorUsername } from '@/hooks/obtenerUsuario'; +import { error } from '@sveltejs/kit'; +import type { UserResponseDto, UsersResponseDto } from '../../../types'; + +export async function load({ params, fetch }) { + const usuario: UserResponseDto | null = await obtenerUsuarioPorUsername(params.perfil, fetch); + if (!usuario) error(404, 'No se encontro el usuario, ' + params.perfil); + + const seguidoresResponse: UsersResponseDto | null = await obtenerSeguidoresPorUsuario( + usuario.id, + 100, + fetch + ); + + return { + usuario, + seguidores: seguidoresResponse?.response || [] + }; +} diff --git a/src/routes/[perfil]/seguidos/+page.svelte b/src/routes/[perfil]/seguidos/+page.svelte new file mode 100644 index 0000000..f8501c1 --- /dev/null +++ b/src/routes/[perfil]/seguidos/+page.svelte @@ -0,0 +1,42 @@ + + +
+
+
+

+ Seguidos de @{data.usuario.username} +

+ +
+ {#if data.seguidos.length === 0} +
+

No hay seguidos para mostrar.

+
+ {:else} +
+ {#each data.seguidos as follower (follower.id)} +
+ +
+ {/each} +
+ {/if} +
+
diff --git a/src/routes/[perfil]/seguidos/+page.ts b/src/routes/[perfil]/seguidos/+page.ts new file mode 100644 index 0000000..4e0e506 --- /dev/null +++ b/src/routes/[perfil]/seguidos/+page.ts @@ -0,0 +1,20 @@ +import { obtenerUsuarioPorUsername } from '@/hooks/obtenerUsuario'; +import { error } from '@sveltejs/kit'; +import type { UserResponseDto, UsersResponseDto } from '../../../types'; +import { obtenerSeguidosPorUsuario } from '@/hooks/obtenerSeguidosPorUsuario'; + +export async function load({ params, fetch }) { + const usuario: UserResponseDto | null = await obtenerUsuarioPorUsername(params.perfil, fetch); + if (!usuario) error(404, 'No se encontro el usuario, ' + params.perfil); + + const seguidosResponse: UsersResponseDto | null = await obtenerSeguidosPorUsuario( + usuario.id, + 100, + fetch + ); + + return { + usuario, + seguidos: seguidosResponse?.response || [] + }; +}