feat(backend): migrate freelancer and portal runtimes
This commit is contained in:
@@ -1,106 +1,67 @@
|
||||
"use server";
|
||||
|
||||
import { createClient } from "@/lib/supabase/server";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { cleanText, optionalDate, requiredText } from "@/server/web/form-data";
|
||||
import { requireFreelancerBackend } from "@/server/web/freelancer";
|
||||
|
||||
const EVENT_TYPES = ["meeting", "focus", "deadline", "personal", "finance"] as const;
|
||||
|
||||
function cleanText(value: FormDataEntryValue | null) {
|
||||
const text = typeof value === "string" ? value.trim() : "";
|
||||
return text.length > 0 && text !== "__none" ? text : null;
|
||||
function eventType(value: FormDataEntryValue | null) {
|
||||
return typeof value === "string" && EVENT_TYPES.includes(value as (typeof EVENT_TYPES)[number])
|
||||
? value as (typeof EVENT_TYPES)[number]
|
||||
: "focus";
|
||||
}
|
||||
|
||||
function readType(value: FormDataEntryValue | null) {
|
||||
const type = typeof value === "string" ? value : "focus";
|
||||
return EVENT_TYPES.includes(type as (typeof EVENT_TYPES)[number]) ? type : "focus";
|
||||
}
|
||||
|
||||
async function getCurrentUserId() {
|
||||
const supabase = await createClient();
|
||||
const {
|
||||
data: { user },
|
||||
error,
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
if (error || !user) {
|
||||
throw new Error("Takvim işlemi için giriş yapmış kullanıcı bulunamadı.");
|
||||
}
|
||||
|
||||
return { supabase, userId: user.id };
|
||||
}
|
||||
|
||||
function readPayload(formData: FormData) {
|
||||
function payload(formData: FormData) {
|
||||
return {
|
||||
title: cleanText(formData.get("title")),
|
||||
title: requiredText(formData.get("title"), "Etkinlik başlığı zorunludur."),
|
||||
description: cleanText(formData.get("description")),
|
||||
type: readType(formData.get("type")),
|
||||
starts_at: cleanText(formData.get("starts_at")),
|
||||
ends_at: cleanText(formData.get("ends_at")),
|
||||
client_id: cleanText(formData.get("client_id")),
|
||||
project_id: cleanText(formData.get("project_id")),
|
||||
task_id: cleanText(formData.get("task_id")),
|
||||
type: eventType(formData.get("type")),
|
||||
startsAt: optionalDate(formData.get("starts_at")),
|
||||
endsAt: optionalDate(formData.get("ends_at")),
|
||||
clientId: cleanText(formData.get("client_id")),
|
||||
projectId: cleanText(formData.get("project_id")),
|
||||
taskId: cleanText(formData.get("task_id")),
|
||||
};
|
||||
}
|
||||
|
||||
function completeRelations(
|
||||
value: ReturnType<typeof payload>,
|
||||
service: Awaited<ReturnType<typeof requireFreelancerBackend>>["service"],
|
||||
actor: Awaited<ReturnType<typeof requireFreelancerBackend>>["actor"],
|
||||
) {
|
||||
const task = value.taskId ? service.listTasks(actor).find((item) => item.id === value.taskId) : null;
|
||||
const projectId = value.projectId ?? task?.projectId ?? null;
|
||||
const project = projectId ? service.getProject(actor, projectId) : null;
|
||||
return {
|
||||
...value,
|
||||
projectId,
|
||||
clientId: value.clientId ?? task?.clientId ?? project?.clientId ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export async function createCalendarEventRecord(formData: FormData) {
|
||||
const { supabase, userId } = await getCurrentUserId();
|
||||
const payload = readPayload(formData);
|
||||
|
||||
if (!payload.title || !payload.starts_at) {
|
||||
throw new Error("Etkinlik başlığı ve başlangıç zamanı zorunludur.");
|
||||
}
|
||||
|
||||
const { error } = await supabase.from("calendar_events").insert({
|
||||
user_id: userId,
|
||||
...payload,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(`Etkinlik eklenemedi: ${error.message}`);
|
||||
}
|
||||
|
||||
const backend = await requireFreelancerBackend();
|
||||
const value = completeRelations(payload(formData), backend.service, backend.actor);
|
||||
if (!value.startsAt) throw new Error("Etkinlik başlangıç zamanı zorunludur.");
|
||||
backend.service.createCalendarEvent(backend.actor, value);
|
||||
revalidatePath("/calendar");
|
||||
}
|
||||
|
||||
export async function updateCalendarEventRecord(formData: FormData) {
|
||||
const { supabase, userId } = await getCurrentUserId();
|
||||
const id = cleanText(formData.get("id"));
|
||||
const payload = readPayload(formData);
|
||||
|
||||
if (!id || !payload.title || !payload.starts_at) {
|
||||
throw new Error("Etkinlik güncellemek için başlık, başlangıç ve kayıt kimliği zorunludur.");
|
||||
}
|
||||
|
||||
const { error } = await supabase
|
||||
.from("calendar_events")
|
||||
.update(payload)
|
||||
.eq("id", id)
|
||||
.eq("user_id", userId);
|
||||
|
||||
if (error) {
|
||||
throw new Error(`Etkinlik güncellenemedi: ${error.message}`);
|
||||
}
|
||||
|
||||
const backend = await requireFreelancerBackend();
|
||||
const id = requiredText(formData.get("id"), "Etkinlik kaydı bulunamadı.");
|
||||
const value = completeRelations(payload(formData), backend.service, backend.actor);
|
||||
if (!value.startsAt) throw new Error("Etkinlik başlangıç zamanı zorunludur.");
|
||||
backend.service.updateCalendarEvent(backend.actor, id, value);
|
||||
revalidatePath("/calendar");
|
||||
}
|
||||
|
||||
export async function deleteCalendarEventRecord(formData: FormData) {
|
||||
const { supabase, userId } = await getCurrentUserId();
|
||||
const id = cleanText(formData.get("id"));
|
||||
|
||||
if (!id) {
|
||||
throw new Error("Silinecek etkinlik bulunamadı.");
|
||||
}
|
||||
|
||||
const { error } = await supabase
|
||||
.from("calendar_events")
|
||||
.delete()
|
||||
.eq("id", id)
|
||||
.eq("user_id", userId);
|
||||
|
||||
if (error) {
|
||||
throw new Error(`Etkinlik silinemedi: ${error.message}`);
|
||||
}
|
||||
|
||||
const { actor, service } = await requireFreelancerBackend();
|
||||
service.deleteCalendarEvent(
|
||||
actor,
|
||||
requiredText(formData.get("id"), "Silinecek etkinlik bulunamadı."),
|
||||
);
|
||||
revalidatePath("/calendar");
|
||||
}
|
||||
|
||||
@@ -1,107 +1,39 @@
|
||||
import {
|
||||
CalendarClient,
|
||||
type CalendarEventItem,
|
||||
type CalendarRelationOption,
|
||||
type CalendarTaskOption,
|
||||
} from "@/app/(dashboard)/calendar/calendar-client";
|
||||
import { createClient } from "@/lib/supabase/server";
|
||||
|
||||
type CalendarEventRow = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string | null;
|
||||
type: CalendarEventItem["type"];
|
||||
starts_at: string;
|
||||
ends_at: string | null;
|
||||
client_id: string | null;
|
||||
project_id: string | null;
|
||||
task_id: string | null;
|
||||
clients: { name: string } | { name: string }[] | null;
|
||||
projects: { name: string } | { name: string }[] | null;
|
||||
tasks: { title: string } | { title: string }[] | null;
|
||||
};
|
||||
import { CalendarClient, type CalendarEventItem, type CalendarRelationOption, type CalendarTaskOption } from "@/app/(dashboard)/calendar/calendar-client";
|
||||
import { requireFreelancerBackend } from "@/server/web/freelancer";
|
||||
|
||||
export default async function CalendarPage() {
|
||||
const supabase = await createClient();
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
const { actor, service } = await requireFreelancerBackend();
|
||||
const eventRows = service.listCalendarEvents(actor);
|
||||
const clientRows = service.listClients(actor);
|
||||
const projectRows = service.listProjects(actor);
|
||||
const taskRows = service.listTasks(actor);
|
||||
const clients = new Map(clientRows.map((item) => [item.id, item.name]));
|
||||
const projects = new Map(projectRows.map((item) => [item.id, item.name]));
|
||||
const tasks = new Map(taskRows.map((item) => [item.id, item.title]));
|
||||
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [{ data: eventRows }, { data: clientRows }, { data: projectRows }, { data: taskRows }] =
|
||||
await Promise.all([
|
||||
supabase
|
||||
.from("calendar_events")
|
||||
.select("id, title, description, type, starts_at, ends_at, client_id, project_id, task_id, clients(name), projects(name), tasks(title)")
|
||||
.eq("user_id", user.id)
|
||||
.order("starts_at", { ascending: true }),
|
||||
supabase
|
||||
.from("clients")
|
||||
.select("id, name")
|
||||
.eq("user_id", user.id)
|
||||
.neq("status", "archived")
|
||||
.order("name", { ascending: true }),
|
||||
supabase
|
||||
.from("projects")
|
||||
.select("id, name")
|
||||
.eq("user_id", user.id)
|
||||
.neq("status", "cancelled")
|
||||
.order("name", { ascending: true }),
|
||||
supabase
|
||||
.from("tasks")
|
||||
.select("id, title")
|
||||
.eq("user_id", user.id)
|
||||
.neq("status", "done")
|
||||
.order("created_at", { ascending: false }),
|
||||
]);
|
||||
|
||||
const events: CalendarEventItem[] = ((eventRows || []) as unknown as CalendarEventRow[]).map((event) => ({
|
||||
const events: CalendarEventItem[] = eventRows.map((event) => ({
|
||||
id: event.id,
|
||||
title: event.title,
|
||||
description: event.description,
|
||||
type: normalizeType(event.type),
|
||||
starts_at: event.starts_at,
|
||||
ends_at: event.ends_at,
|
||||
client_id: event.client_id,
|
||||
project_id: event.project_id,
|
||||
task_id: event.task_id,
|
||||
clientName: getRelationName(event.clients),
|
||||
projectName: getRelationName(event.projects),
|
||||
taskTitle: getRelationTitle(event.tasks),
|
||||
type: event.type,
|
||||
starts_at: event.startsAt.toISOString(),
|
||||
ends_at: event.endsAt?.toISOString() ?? null,
|
||||
client_id: event.clientId,
|
||||
project_id: event.projectId,
|
||||
task_id: event.taskId,
|
||||
clientName: event.clientId ? clients.get(event.clientId) ?? null : null,
|
||||
projectName: event.projectId ? projects.get(event.projectId) ?? null : null,
|
||||
taskTitle: event.taskId ? tasks.get(event.taskId) ?? null : null,
|
||||
}));
|
||||
const clientOptions: CalendarRelationOption[] = clientRows
|
||||
.filter((item) => item.status !== "archived")
|
||||
.map(({ id, name }) => ({ id, name }));
|
||||
const projectOptions: CalendarRelationOption[] = projectRows
|
||||
.filter((item) => item.status !== "cancelled")
|
||||
.map(({ id, name }) => ({ id, name }));
|
||||
const taskOptions: CalendarTaskOption[] = taskRows
|
||||
.filter((item) => item.status !== "done" && item.status !== "cancelled")
|
||||
.map(({ id, title }) => ({ id, title }));
|
||||
|
||||
return (
|
||||
<CalendarClient
|
||||
events={events}
|
||||
clients={(clientRows || []) as CalendarRelationOption[]}
|
||||
projects={(projectRows || []) as CalendarRelationOption[]}
|
||||
tasks={(taskRows || []) as CalendarTaskOption[]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function getRelationName(relation: CalendarEventRow["clients"] | CalendarEventRow["projects"]) {
|
||||
if (!relation) return null;
|
||||
return Array.isArray(relation) ? relation[0]?.name || null : relation.name;
|
||||
}
|
||||
|
||||
function getRelationTitle(relation: CalendarEventRow["tasks"]) {
|
||||
if (!relation) return null;
|
||||
return Array.isArray(relation) ? relation[0]?.title || null : relation.title;
|
||||
}
|
||||
|
||||
function normalizeType(type: string): CalendarEventItem["type"] {
|
||||
if (
|
||||
type === "meeting" ||
|
||||
type === "deadline" ||
|
||||
type === "personal" ||
|
||||
type === "finance"
|
||||
) {
|
||||
return type;
|
||||
}
|
||||
|
||||
return "focus";
|
||||
return <CalendarClient events={events} clients={clientOptions} projects={projectOptions} tasks={taskOptions} />;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user