Skip to content

Commit

Permalink
Create API client
Browse files Browse the repository at this point in the history
  • Loading branch information
agarun committed May 18, 2024
1 parent 460afd0 commit ec9ed71
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 65 deletions.
4 changes: 2 additions & 2 deletions src/app/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import dynamic from 'next/dynamic';
import { getPage } from '@/lib/api';
import { getAlbum } from '@/lib/api';
import Nav from '@/lib/nav';

const Masonry = dynamic(() => import('@/lib/masonry'), {
Expand All @@ -15,7 +15,7 @@ export async function generateStaticParams() {
}

async function Page({ params: { slug } }: { params: { slug: string } }) {
const photos = await getPage(capitalize(slug));
const photos = await getAlbum(capitalize(slug));

return (
<section className="flex my-20">
Expand Down
24 changes: 12 additions & 12 deletions src/lib/albums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ export const types = {
export const HexSchema = z.string().regex(/^#([A-Fa-f0-9]{6})$/);
export type Hex = z.infer<typeof HexSchema>;

export const AlbumNameSchema = z.string().brand<'AlbumName'>();
export type AlbumName = z.infer<typeof AlbumNameSchema>;
export const AlbumTitleSchema = z.string().brand<'AlbumTitle'>();
export type AlbumTitle = z.infer<typeof AlbumTitleSchema>;

export const AlbumSchema = z.object({
name: AlbumNameSchema,
title: AlbumTitleSchema,
lat: z.number(),
lng: z.number(),
locations: z.array(
Expand All @@ -30,7 +30,7 @@ export type Album = z.infer<typeof AlbumSchema>;
// TODO(agarun): Move this data to the API when I've uploaded photos and transferred schemas
const albums: Array<Album> = [
{
name: 'Japan' as AlbumName,
title: 'Japan' as AlbumTitle,
// Tokyo
lat: 35.68,
lng: 139.65,
Expand All @@ -50,23 +50,23 @@ const albums: Array<Album> = [
type: types.LOCATION
},
{
name: 'Miami' as AlbumName,
title: 'Miami' as AlbumTitle,
lat: 25.76,
lng: -80.19,
locations: [],
color: '#880808',
type: types.LOCATION
},
{
name: 'Curaçao' as AlbumName,
title: 'Curaçao' as AlbumTitle,
lat: 12.17,
lng: -68.99,
locations: [],
color: '#880808',
type: types.LOCATION
},
{
name: 'Azerbaijan' as AlbumName,
title: 'Azerbaijan' as AlbumTitle,
// Baku
lat: 40.14,
lng: 47.577,
Expand All @@ -81,7 +81,7 @@ const albums: Array<Album> = [
type: types.LOCATION
},
{
name: 'Europe' as AlbumName,
title: 'Europe' as AlbumTitle,
// Florence
lat: 43.77,
lng: 11.2577,
Expand Down Expand Up @@ -111,7 +111,7 @@ const albums: Array<Album> = [
type: types.LOCATION
},
{
name: 'New Jersey' as AlbumName,
title: 'New Jersey' as AlbumTitle,
// Metuchen
lat: 40.54,
lng: -74.36,
Expand All @@ -120,7 +120,7 @@ const albums: Array<Album> = [
type: types.LOCATION
},
{
name: 'New York' as AlbumName,
title: 'New York' as AlbumTitle,
// Generic NYC
lat: 40.713,
lng: -74,
Expand All @@ -129,7 +129,7 @@ const albums: Array<Album> = [
type: types.LOCATION
},
{
name: 'West' as AlbumName,
title: 'West' as AlbumTitle,
// Arizona
lat: 36.268,
lng: -112.35,
Expand Down Expand Up @@ -164,7 +164,7 @@ const albums: Array<Album> = [
type: types.LOCATION
},
{
name: 'Music' as AlbumName,
title: 'Music' as AlbumTitle,
lat: 0,
lng: 0,
locations: [],
Expand Down
50 changes: 8 additions & 42 deletions src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,11 @@
async function fetchGraphQL(query: string, preview = false): Promise<any> {
return fetch(
`https://graphql.contentful.com/content/v1/spaces/${process.env.CONTENTFUL_SPACE_ID}`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${
preview
? process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN
: process.env.CONTENTFUL_ACCESS_TOKEN
}`
},
body: JSON.stringify({ query }),
next: { tags: ['photos'] }
}
).then(response => response.json());
}
import { Client } from './client';

export async function getPage(slug: string) {
const sections = await fetchGraphQL(`
query {
photoGalleryCollection(where: { title: "${slug}" }) {
items {
title
description
photosCollection {
items {
size
url
width
height
}
}
contentfulMetadata {
tags {
name
}
}
}
}
export async function getAlbum(slug: string) {
const client = new Client();
return await client.albums.find(slug);
}
`);
return sections.data.photoGalleryCollection.items[0].photosCollection.items;

export async function getAlbums() {
const client = new Client();
return await client.albums.get();
}
194 changes: 194 additions & 0 deletions src/lib/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { z } from 'zod';
import { AlbumSchema } from './albums';

type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';

type ResponseDetail = {
success: boolean;
};

type Error = {
success: false;
message: string;
error?: z.ZodError | string;
};

type Config = {
method?: Method;
timeout?: number;
preview?: boolean;
headers?: Record<string, string>;
} & globalThis.RequestInit;

function authorizationHeader(preview: boolean | undefined): string {
return `Bearer ${
preview
? process.env.CONTENTFUL_PREVIEW_ACCESS_TOKEN
: process.env.CONTENTFUL_ACCESS_TOKEN
}`;
}

const url = `https://graphql.contentful.com/content/v1/spaces/${process.env.CONTENTFUL_SPACE_ID}`;

export class BaseClient {
constructor(protected baseUrl: string = url) {
this.baseUrl = baseUrl;
}

async request<Request, Response>(
requestSchema: z.ZodSchema<Request>,
responseSchema: z.ZodSchema<Response>,
config: Config
): Promise<(Response & ResponseDetail) | Error> {
const controller = new AbortController();
const { signal } = controller;

const timeout = config.timeout || 10000;
const timeoutRef = setTimeout(() => {
controller.abort(`Request cancelled after waiting ${timeout}ms`);
}, timeout);

try {
requestSchema.parse(config.body);

const response = await fetch(this.baseUrl, {
signal,
...config,
headers: {
'Content-Type': 'application/json',
Authorization: authorizationHeader(config.preview),
...config.headers
}
});

const data = await response.json();

console.log({ data });
responseSchema.parse(data);

return { ...data, success: true };
} catch (error) {
if (error instanceof z.ZodError) {
return { success: false, message: error.message, error };
}

if (error instanceof Error) {
return { success: false, message: error.message };
}

return { success: false, message: 'Unexpected server error.' };
} finally {
clearTimeout(timeoutRef);
controller.abort();
}
}
}

const ContentfulPhotoGallerySchema = z.object({
data: z.object({
photoGalleryCollection: z.object({
items: z.array(
AlbumSchema.extend({
photosCollection: z.object({
items: z.array(
z.object({
size: z.number(),
url: z.string(),
width: z.number(),
height: z.number()
})
)
})
})
)
})
})
});

export class Client extends BaseClient {
albums = new AlbumClient(this.baseUrl);
}

export class AlbumClient extends BaseClient {
async find(slug: string) {
const query = `
query {
photoGalleryCollection(where: { title: "${slug}" }) {
items {
title
description
lat
lng
color
type
locations
photosCollection {
items {
size
url
width
height
}
}
contentfulMetadata {
tags {
name
}
}
}
}
}`;

const response = await this.request(
z.string(),
ContentfulPhotoGallerySchema,
{
method: 'POST',
body: JSON.stringify({ query }),
next: { tags: ['photos'] }
}
);

if (response.success) {
const album = response.data.photoGalleryCollection.items[0];
const photos = album.photosCollection.items;
return photos;
} else {
return response;
}
}

async get() {
const query = `
query {
photoGalleryCollection {
items {
title
description
lat
lng
color
type
locations
}
}
}`;

const response = await this.request(
z.void(),
ContentfulPhotoGallerySchema,
{
method: 'POST',
body: JSON.stringify({ query }),
next: { tags: ['photos'] }
}
);

if (response.success) {
const albums = response.data.photoGalleryCollection.items;
return albums;
} else {
return response;
}
}
}
Loading

0 comments on commit ec9ed71

Please sign in to comment.