Skip to content

Commit

Permalink
Logto API integration
Browse files Browse the repository at this point in the history
  • Loading branch information
arjunkomath committed Jan 9, 2025
1 parent b725489 commit 097fbfd
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 51 deletions.
7 changes: 4 additions & 3 deletions app/(api)/api/calendar/[ownerId]/[projectId]/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { calendarEvent, project, task, taskList } from "@/drizzle/schema";
import { getDatabaseForOwner } from "@/lib/utils/useDatabase";
import { getVtimezoneComponent } from "@touch4it/ical-timezones";
import { and, desc, eq, lte } from "drizzle-orm";
import ical, { ICalCalendarMethod } from "ical-generator";

Expand Down Expand Up @@ -46,7 +47,7 @@ export async function GET(
const calendar = ical({
name: projectDetails.name,
method: ICalCalendarMethod.PUBLISH,
timezone: { name: "UTC" },
timezone: { name: "Australia/Sydney", generator: getVtimezoneComponent },
});

for (const event of events) {
Expand All @@ -60,7 +61,7 @@ export async function GET(
created: event.createdAt,
lastModified: event.updatedAt,
repeating: event.repeatRule,
timezone: "UTC",
timezone: "Australia/Sydney",
});
}

Expand All @@ -82,7 +83,7 @@ export async function GET(
allDay: true,
created: task.createdAt,
lastModified: task.updatedAt,
timezone: "UTC",
timezone: "Australia/Sydney",
});
}
}
Expand Down
8 changes: 4 additions & 4 deletions app/(dashboard)/[tenant]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import NavBar from "@/components/console/navbar";
import { ReportTimezone } from "@/components/core/report-timezone";
import { isDatabaseReady } from "@/lib/utils/useDatabase";
import { getOrgs, getOwner } from "@/lib/utils/useOwner";
import { getOrganizations, getOwner } from "@/lib/utils/useOwner";
import { redirect } from "next/navigation";

export const fetchCache = "force-no-store"; // disable cache for console pages
Expand All @@ -19,16 +19,16 @@ export default async function ConsoleLayout(props: {

const ready = await isDatabaseReady();
const { orgId, orgSlug } = await getOwner();
const orgs = await getOrgs();
const activatedOrg = orgs.find((org) => org.id === orgId) ?? null;
const organizations = await getOrganizations();
const activatedOrg = organizations.find((org) => org.id === orgId) ?? null;

if (!ready || params.tenant !== orgSlug) {
redirect("/start");
}

return (
<div className="relative flex min-h-full flex-col">
<NavBar orgs={orgs} activeOrg={activatedOrg} />
<NavBar orgs={organizations} activeOrg={activatedOrg} />

<div className="mx-auto w-full flex-grow lg:flex">
<div className="min-w-0 flex-1 xl:flex">
Expand Down
5 changes: 5 additions & 0 deletions app/(dashboard)/[tenant]/settings/actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"use server";

import { updateUser } from "@/lib/ops/auth";
import { getOwner } from "@/lib/utils/useOwner";
import { cookies } from "next/headers";

export async function saveUserTimezone(timezone: string) {
Expand All @@ -9,4 +11,7 @@ export async function saveUserTimezone(timezone: string) {
sameSite: "strict",
maxAge: 60 * 60 * 24 * 365,
});

const { userId } = await getOwner();
await updateUser(userId, { customData: { timezone } });
}
3 changes: 2 additions & 1 deletion components/console/navbar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { Organization } from "@/lib/ops/auth";
import Image from "next/image";
import Link from "next/link";
import logo from "../../public/images/logo.png";
import { OrgSwitcher, type Organization, UserButton } from "../core/auth";
import { OrgSwitcher, UserButton } from "../core/auth";
import NavBarLinks from "./navbar-links";

export default function NavBar({
Expand Down
14 changes: 6 additions & 8 deletions components/core/auth.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { logtoConfig } from "@/app/logto";
import type { Organization } from "@/lib/ops/auth";
import { signOut } from "@logto/next/server-actions";
import { ChevronsUpDown, Plus, User } from "lucide-react";
import Link from "next/link";
Expand All @@ -12,13 +13,6 @@ import {
DropdownMenuTrigger,
} from "../ui/dropdown-menu";

// WIP, this should be changed
export type Organization = {
id: string;
name: string;
slug: string;
};

export const OrgSwitcher = ({
orgs,
activeOrg,
Expand Down Expand Up @@ -67,7 +61,11 @@ export const OrgSwitcher = ({
// }
>
<input type="hidden" name="id" value={org.id} />
<input type="hidden" name="slug" value={org.slug} />
<input
type="hidden"
name="slug"
value={String(org.customData?.slug)}
/>
<button
type="submit"
className="flex w-full"
Expand Down
71 changes: 41 additions & 30 deletions lib/ops/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,31 @@ const applicationId = process.env.LOGTO_M2M_APP_ID!;
const applicationSecret = process.env.LOGTO_M2M_APP_SECRET!;
const tenantId = "default";

/**
* Docs
* https://openapi.logto.io
*/

export interface Organization {
tenantId: string;
id: string;
name: string;
description: string;
customData: Record<string, string | number | boolean>;
isMfaRequired: boolean;
branding: {
logoUrl: string;
darkLogoUrl: string;
favicon: string;
darkFavicon: string;
};
createdAt: number;
organizationRoles: {
id: string;
name: string;
}[];
}

export const fetchAccessToken = async () => {
const { endpoint } = logtoConfig;
return await fetch(`${endpoint}oidc/token`, {
Expand All @@ -21,59 +46,47 @@ export const fetchAccessToken = async () => {
}).toString(),
});
};

export const createOrganizationForUser = async (
export const updateUser = async (
userId: string,
name: string,
data: {
name?: string;
primaryEmail?: string;
customData?: Record<string, unknown>;
},
) => {
const { access_token } = await fetchAccessToken().then((res) => res.json());
if (!access_token) {
throw new Error("Access token not found");
}

const { endpoint } = logtoConfig;
const response = await fetch(`${endpoint}api/organizations`, {
method: "POST",
const response = await fetch(`${endpoint}api/users/${userId}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name,
}),
body: JSON.stringify(data),
});

if (!response.ok) {
console.error("Failed to create organization", response.status);
throw new Error("Failed to create organization");
console.error("Failed to update user", response.status);
throw new Error("Failed to update user");
}

const organization = await response.json();
console.log("organization", organization);

// Add user to organization
await fetch(`${endpoint}api/organizations/${organization.id}/users`, {
method: "POST",
headers: {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
userIds: [userId],
}),
});

return organization;
return await response.json();
};

export const getOrganizationsForUser = async (userId: string) => {
export const getOrganizationsForUser = async (
userId: string,
): Promise<Organization[]> => {
const { access_token } = await fetchAccessToken().then((res) => res.json());
if (!access_token) {
throw new Error("Access token not found");
}

const { endpoint } = logtoConfig;
const response = await fetch(`${endpoint}api/organizations`, {
const response = await fetch(`${endpoint}api/users/${userId}/organizations`, {
method: "GET",
headers: {
Authorization: `Bearer ${access_token}`,
Expand All @@ -85,9 +98,7 @@ export const getOrganizationsForUser = async (userId: string) => {
console.error("Failed to fetch organizations", response.status);
throw new Error("Failed to fetch organizations");
}

const organizations = await response.json();
console.log("organizations", organizations);

return organizations;
};
8 changes: 5 additions & 3 deletions lib/utils/useOwner.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { logtoConfig } from "@/app/logto";
import type { Organization } from "@/components/core/auth";
import { user } from "@/drizzle/schema";
import type { User } from "@/drizzle/types";
import { getLogtoContext } from "@logto/next/server-actions";
Expand All @@ -8,6 +7,7 @@ import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc";
import { eq } from "drizzle-orm";
import { cookies } from "next/headers";
import { type Organization, getOrganizationsForUser } from "../ops/auth";
import { database } from "./useDatabase";

dayjs.extend(utc);
Expand Down Expand Up @@ -39,8 +39,10 @@ export async function getUser(): Promise<User> {
return userDetails;
}

export async function getOrgs(): Promise<Organization[]> {
return [];
export async function getOrganizations(): Promise<Organization[]> {
const { userId } = await getOwner();
const organizations = await getOrganizationsForUser(userId);
return organizations;
}

export async function getOwner(): Promise<Result> {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0",
"@touch4it/ical-timezones": "^1.9.0",
"autoprefixer": "10.4.14",
"better-sqlite3": "^11.7.0",
"class-variance-authority": "^0.6.1",
Expand Down
14 changes: 12 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 097fbfd

Please sign in to comment.