feat: agregar funcionalidad para obtener organizaciones públicas y actualizar el sitemap con rutas dinámicas

This commit is contained in:
2026-09-16 10:44:54 -03:00
parent 60eb940d3e
commit eff1988d66
6 changed files with 175 additions and 1 deletions
@@ -72,6 +72,8 @@ export class CompaniesAdapterMongoose implements ICompaniesAdapter {
showPublicProfessionals: { type: Boolean, required: false, default: true }, showPublicProfessionals: { type: Boolean, required: false, default: true },
}); });
this.schema.index({ published: 1, banned: 1 });
this.companyList = model<ICompanyDocument>("Company", this.schema); this.companyList = model<ICompanyDocument>("Company", this.schema);
} }
+59
View File
@@ -1,5 +1,6 @@
import { isNull } from "../../helpers/IsNull"; import { isNull } from "../../helpers/IsNull";
import CompaniesList from "../Companies/Companies"; import CompaniesList from "../Companies/Companies";
import { CompaniesAdapterMongoose } from "../Companies/Companies.Adapter.Mongoose";
import ServiceList from "../Services/Service"; import ServiceList from "../Services/Service";
import { import {
PublicServicesResults, PublicServicesResults,
@@ -15,6 +16,24 @@ import { BotEventParams, BotView } from "../WapServer/WapServer.Interface";
import WapServerList from "../WapServer/WapServer"; import WapServerList from "../WapServer/WapServer";
import { COMPANY_PUBLISHED_STATUS } from "../Companies/Companies.Interface"; import { COMPANY_PUBLISHED_STATUS } from "../Companies/Companies.Interface";
export type PublicOrganizationsParams = {
page: number;
limit: number;
};
export type PublicOrganizationListItem = {
slug: string;
name: string;
description: string;
creationDate: Date;
};
export type PublicOrganizationsResult = {
data: PublicOrganizationListItem[];
page: number;
pages: number;
};
export type HomeView = { export type HomeView = {
education: PublicServicesResults; education: PublicServicesResults;
health: PublicServicesResults; health: PublicServicesResults;
@@ -39,6 +58,7 @@ export type PublicOrganizationViewParams = {
export type PublicOrganizationView = { export type PublicOrganizationView = {
id: string; id: string;
slug: string;
headerFile: string; headerFile: string;
headerColor: string; headerColor: string;
headerFontColor: string; headerFontColor: string;
@@ -143,6 +163,7 @@ class Views {
return { return {
id: isNull<string>(company.id, ""), id: isNull<string>(company.id, ""),
slug: isNull<string>(company.slug, ""),
name: isNull<string>(company.name, ""), name: isNull<string>(company.name, ""),
description: isNull<string>(company.description, ""), description: isNull<string>(company.description, ""),
categoryId: isNull<number>(company.categoryId, 50000), categoryId: isNull<number>(company.categoryId, 50000),
@@ -249,6 +270,44 @@ class Views {
}; };
} }
public async publicOrganizations(
data: PublicOrganizationsParams
): Promise<PublicOrganizationsResult> {
const { page, limit } = data;
const filter = {
published: COMPANY_PUBLISHED_STATUS.PUBLISHED,
banned: { $ne: true },
};
const companyList = (CompaniesList.companies as CompaniesAdapterMongoose).companyList;
const count = await companyList
.countDocuments(filter)
.exec();
const skip = (page - 1) * limit;
const results = (await companyList
.find(filter)
.select("slug name description creationDate")
.sort({ creationDate: 1 })
.skip(skip)
.limit(limit)
.lean()
.exec()) as any[];
return {
data: results.map((company) => ({
slug: String(company.slug || ""),
name: String(company.name || ""),
description: String(company.description || ""),
creationDate: company.creationDate,
})),
page,
pages: Math.ceil(count / limit),
};
}
public async wapView(data: BotEventParams): Promise<BotView> { public async wapView(data: BotEventParams): Promise<BotView> {
return await WapServerList.botView(data); return await WapServerList.botView(data);
} }
+22
View File
@@ -9,6 +9,8 @@ import {
PublicOrganizationServiceView, PublicOrganizationServiceView,
UserAppointmentViewParams, UserAppointmentViewParams,
UserAppointmentView, UserAppointmentView,
PublicOrganizationsParams,
PublicOrganizationsResult,
} from "../../Models/Views/Views"; } from "../../Models/Views/Views";
import { ViewsService } from "./Views.Service"; import { ViewsService } from "./Views.Service";
import { BotEventParams, BotView } from "../../Models/WapServer/WapServer.Interface"; import { BotEventParams, BotView } from "../../Models/WapServer/WapServer.Interface";
@@ -109,3 +111,23 @@ export class WapServerBotViewController extends Controller {
} }
} }
} }
@Route("views/public-organizations")
export class PublicOrganizationsController extends Controller {
@Response<ApiValidationError>(500, "Ha ocurrido un error")
@SuccessResponse(200, "Done")
@Post()
public async publicOrganizations(
@Body() requestBody: PublicOrganizationsParams
): Promise<PublicOrganizationsResult | ApiValidationError> {
try {
const result = await new ViewsService().publicOrganizations(requestBody);
this.setStatus(200);
return result;
} catch (e) {
const errorOccurred: Error = e as Error;
this.setStatus(500);
return new ApiValidationError(500, errorOccurred.message);
}
}
}
+10
View File
@@ -7,6 +7,8 @@ import views, {
PublicOrganizationServiceView, PublicOrganizationServiceView,
UserAppointmentViewParams, UserAppointmentViewParams,
UserAppointmentView, UserAppointmentView,
PublicOrganizationsParams,
PublicOrganizationsResult,
} from "../../Models/Views/Views"; } from "../../Models/Views/Views";
import { BotView, BotEventParams } from "../../Models/WapServer/WapServer.Interface"; import { BotView, BotEventParams } from "../../Models/WapServer/WapServer.Interface";
@@ -45,4 +47,12 @@ export class ViewsService {
const view = await views.wapView(data); const view = await views.wapView(data);
return view; return view;
} }
public async publicOrganizations(
data: PublicOrganizationsParams
): Promise<PublicOrganizationsResult> {
await connect(`${process.env.DATABASE_CONNECTION}`);
const result = await views.publicOrganizations(data);
return result;
}
} }
@@ -3,6 +3,7 @@ import ApiServerService from "@services/Api.Server.Service";
import { PublicOrganizationView } from "@core/Models/Company.model"; import { PublicOrganizationView } from "@core/Models/Company.model";
import OrganizationHeader from "@core/app/components/Home/OrganizationHeader/OrganizationHeader"; import OrganizationHeader from "@core/app/components/Home/OrganizationHeader/OrganizationHeader";
import OrganizationDataConnector from "./components/OrganizationData"; import OrganizationDataConnector from "./components/OrganizationData";
import type { Metadata } from "next";
const GetOrganizationData = async (companyId: string): Promise<PublicOrganizationView> => { const GetOrganizationData = async (companyId: string): Promise<PublicOrganizationView> => {
try { try {
@@ -28,6 +29,49 @@ type Props = {
children: React.ReactNode; children: React.ReactNode;
}; };
export async function generateMetadata({ params }: { params: Promise<Params> }): Promise<Metadata> {
const { oid } = await params;
const orgData = await GetOrganizationData(oid);
if (!orgData || !orgData.id) {
return {
robots: { index: false, follow: false },
};
}
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "https://turnosxpress.com.ar";
const canonicalUrl = `${baseUrl}/landing/org/${oid}`;
const description =
orgData.description && orgData.description.trim().length > 0
? orgData.description
: `${orgData.name} - TurnosXpress`;
return {
title: `${orgData.name} | TurnosXpress`,
description,
alternates: {
canonical: canonicalUrl,
},
openGraph: {
type: "website",
title: `${orgData.name} | TurnosXpress`,
description,
url: canonicalUrl,
siteName: "TurnosXpress",
locale: "es_AR",
},
twitter: {
card: "summary",
title: `${orgData.name} | TurnosXpress`,
description,
},
robots: {
index: true,
follow: true,
},
};
}
export default async function OrganizationPublicProfile({ params, children }: Props) { export default async function OrganizationPublicProfile({ params, children }: Props) {
const { oid } = await params; const { oid } = await params;
const orgData = await GetOrganizationData(oid); const orgData = await GetOrganizationData(oid);
+38 -1
View File
@@ -165,7 +165,44 @@ export async function GET() {
console.error("Error generating sitemap:", error); console.error("Error generating sitemap:", error);
} }
const allRoutes = [...staticRoutes, ...videoRoutes, ...authRoutes, ...marketingRoutes, ...explorationRoutes, ...legalRoutes, ...serviceRoutes]; let orgRoutes: SitemapRoute[] = [];
try {
let currentPage = 1;
let totalPages = 1;
do {
const orgsResult = await ApiServerService.post<{
data: { slug: string; name: string; description: string; creationDate?: string }[];
page: number;
pages: number;
}>("views/public-organizations", {
page: currentPage,
limit: 1000,
});
if (orgsResult && orgsResult.data) {
totalPages = orgsResult.pages || 1;
const newRoutes = orgsResult.data.map((org) => ({
url: `${baseUrl}/landing/org/${org.slug}`,
changeFrequency: "weekly" as const,
priority: 0.8,
}));
orgRoutes = [...orgRoutes, ...newRoutes];
} else {
break;
}
currentPage++;
} while (currentPage <= totalPages);
} catch (error) {
console.error("Error generating org sitemap:", error);
}
const allRoutes = [...staticRoutes, ...videoRoutes, ...authRoutes, ...marketingRoutes, ...orgRoutes, ...explorationRoutes, ...legalRoutes, ...serviceRoutes];
const sitemapXml = `<?xml version="1.0" encoding="UTF-8"?> const sitemapXml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"