Skip to content

Commit

Permalink
Merge pull request #87
Browse files Browse the repository at this point in the history
refactor/no-ref/improve-server-api
  • Loading branch information
botmaster authored Feb 9, 2024
2 parents ab85e51 + 9e44e78 commit 90007d6
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 86 deletions.
6 changes: 3 additions & 3 deletions components/ArticleListActionBar.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<script setup lang="ts">
// Define props
import type { IOption } from '~/components/MultiSelectTag.vue';
import type { IMultiSelectTagOption } from '~/components/MultiSelectTag.vue';
defineProps<{
tags: IOption[]
tags: IMultiSelectTagOption[]
pending: boolean
}>();
Expand All @@ -13,7 +13,7 @@ const emit = defineEmits(['clearFilters']);
const { t } = useI18n();
// Define model
const selectedOptions = defineModel<IOption[]>('selectedOptions', { required: true });
const selectedOptions = defineModel<IMultiSelectTagOption[]>('selectedOptions', { required: true });
const search = defineModel<string>('search', { required: true });
const status = defineModel<string>('status', { required: true });
const sort = defineModel<string>('sort', { required: true });
Expand Down
8 changes: 4 additions & 4 deletions components/MultiSelectTag.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@
import { Combobox, ComboboxButton, ComboboxInput, ComboboxLabel, ComboboxOption, ComboboxOptions } from '@headlessui/vue';
// Interfaces
export interface IOption {
export interface IMultiSelectTagOption {
id: string
name: string
color: 'blue' | 'brown' | 'default' | 'gray' | 'green' | 'orange' | 'pink' | 'purple' | 'red' | 'yellow'
}
// Props
const props = defineProps<{
options: IOption[]
options: IMultiSelectTagOption[]
}>();
// Model
const selectedOptions = defineModel<IOption[]>({ required: true });
const selectedOptions = defineModel<IMultiSelectTagOption[]>({ required: true });
const query = ref('');
Expand All @@ -29,7 +29,7 @@ const filteredOption = computed(() =>
),
);
function displayValue(options: IOption[]) {
function displayValue(options: IMultiSelectTagOption[]) {
return options.map(option => option.name).join(', ');
}
</script>
Expand Down
29 changes: 19 additions & 10 deletions pages/readings.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<script setup lang="ts">
import { useRouteQuery } from '@vueuse/router';
import type { PartialDatabaseObjectResponse } from '@notionhq/client/build/src/api-endpoints';
import type { IPage } from '~/types/types';
import type { SanitizedResponse } from '~/server/api/notion-page-list.post';
import type { IOption } from '~/components/MultiSelectTag.vue';
import type { INotionArticle } from '~/server/api/notion-page-list.post';
import type { IMultiSelectTagOption } from '~/components/MultiSelectTag.vue';
const config = useRuntimeConfig();
const { locale: currentLocale, t } = useI18n();
Expand All @@ -21,14 +22,14 @@ const DEFAULT_LIMIT = 12;
const observerRoot = ref<HTMLElement | null>(null);
const pageListCollection = ref<SanitizedResponse[]>([]);
const pageListCollection = ref<INotionArticle[]>([]);
const cursor = ref<string | null>(null);
const pageSize = useRouteQuery('pageSize', DEFAULT_LIMIT, { transform: Number });
const status = useRouteQuery('status', '', { transform: String });
const search = useRouteQuery('search', '', { transform: String });
const sort = useRouteQuery('sort', 'Created time', { transform: String });
const isLoadingMore = ref(false);
const selectedTags = ref<IOption[]>([]);
const selectedTags = ref<IMultiSelectTagOption[]>([]);
// Fetch database info
const { data: database } = await useAsyncData('database-info', () =>
Expand All @@ -40,9 +41,17 @@ const { data: database } = await useAsyncData('database-info', () =>
}));
// Computed - Get "tags" multi-select value from dbResponse
const tagList = computed<IOption[]>(() => {
// @ts-expect-error - TS doesn't know "Tags" exists
return database.value?.properties.Tags.multi_select.options || [];
const tagList = computed<IMultiSelectTagOption[]>(() => {
const data = database.value as PartialDatabaseObjectResponse;
const selectProperty = data.properties.Tags as {
type: 'multi_select'
multi_select: {
options: Array<IMultiSelectTagOption>
}
id: string
name: string
};
return selectProperty.multi_select.options || [];
});
// Fetch page list
Expand Down Expand Up @@ -120,10 +129,10 @@ watch(
if (isLoadingMore.value) {
isLoadingMore.value = false;
pageListCollection.value = [...pageListCollection.value, ...newVal.results as SanitizedResponse[]];
pageListCollection.value = [...pageListCollection.value, ...newVal.results as INotionArticle[]];
}
else {
pageListCollection.value = newVal.results as SanitizedResponse[];
pageListCollection.value = newVal.results as INotionArticle[];
}
},
{ immediate: true },
Expand Down Expand Up @@ -232,7 +241,7 @@ watch(
<!-- Action bar -->
<ArticleListActionBar
v-model:selected-options="selectedTags" v-model:search="search" v-model:status="status"
v-model:sort="sort" :tags="tagList" :pending @clear-filters="clearFilters"
v-model:sort="sort" :tags="tagList" :pending="pending" @clear-filters="clearFilters"
/>

<!-- Article list -->
Expand Down
22 changes: 22 additions & 0 deletions server/NotionClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Client } from '@notionhq/client';

export class NotionClient {
private static instance: NotionClient;
private readonly client: Client;

private constructor() {
const config = useRuntimeConfig();
this.client = new Client({ auth: config.notionApiKey });
}

public static getInstance(): NotionClient {
if (!NotionClient.instance)
NotionClient.instance = new NotionClient();

return NotionClient.instance;
}

public getClient() {
return this.client;
}
}
7 changes: 2 additions & 5 deletions server/api/notion-database-info.get.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { Client } from '@notionhq/client';

// Get the runtime config
const config = useRuntimeConfig();
import { NotionClient } from '~/server/NotionClient';

// Initialize Notion Client
const notion = new Client({ auth: config.notionApiKey });
const notion = NotionClient.getInstance().getClient();

export default defineEventHandler(async (event) => {
// Get Query
Expand Down
10 changes: 5 additions & 5 deletions server/api/notion-page-image.post.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { Client } from '@notionhq/client';

const config = useRuntimeConfig();
import { NotionClient } from '~/server/NotionClient';

// Initialize Notion Client
const notion = new Client({ auth: config.notionApiKey });
const notion = NotionClient.getInstance().getClient();

// Cache the image url
const imageUrlCache = new Map<string, string>();
Expand Down Expand Up @@ -32,7 +30,9 @@ export default defineEventHandler(async (event) => {
});

// Récupérer l'url du premier block image
const imageBlock = blockResponse.results.find(block => block.type === 'image');
const imageBlock = blockResponse.results.find((block: any) => block.type === 'image') as {
image: { type: 'external' | 'file', external: { url: string }, file: { url: string } }
} | undefined;
if (!imageBlock)
return '';

Expand Down
98 changes: 39 additions & 59 deletions server/api/notion-page-list.post.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Client, isFullPage } from '@notionhq/client';
import { NotionClient } from '~/server/NotionClient';

export interface SanitizedResponse {
export interface INotionArticle {
title: string
description?: string
tags: string[]
Expand All @@ -16,20 +16,8 @@ export interface SanitizedResponse {
}
}

const config = useRuntimeConfig();

// Initialize Notion Client
const notion = new Client({ auth: config.notionApiKey });

// Fetch the image url for a page
async function getImageUrl(pageId: string) {
return await $fetch('/api/notion-page-image', {
body: {
page_id: pageId,
},
method: 'POST',
});
}
const notion = NotionClient.getInstance().getClient();

// Define the event handler
export default defineEventHandler(async (event) => {
Expand All @@ -54,54 +42,46 @@ export default defineEventHandler(async (event) => {
},
);

await Promise.all(response.results.map(async (item) => {
item.imageUrl = await getImageUrl(item.id);
}));

// Sanitize the response
const results = response.results.map<SanitizedResponse | undefined>((result) => {
if (!isFullPage(result))
console.error('Notion response is not a full page or database');

// Check if result has the necessary properties
if ('id' in result && 'properties' in result) {
const id = result.id;
const properties = result.properties;

// Check if properties have the necessary sub-properties
const title = properties.Name.title[0]?.plain_text || '';
const description = properties.Description?.rich_text?.[0]?.plain_text || '';
const tags = properties.Tags?.multi_select?.map(tag => tag.name) || [];
const image = result.imageUrl || properties.Image?.url || '';
const url = properties.URL?.url || '';
const date = properties.Date?.date?.start || '';
const score = properties.Score?.select?.name;
const status = {
name: properties.Status?.select?.name,
color: properties.Status?.select?.color,
id: properties.Status?.select?.id,
};

return {
title,
description,
tags,
image,
url,
date,
score,
status,
id,
};
}

// Return undefined if result does not have the necessary properties
return undefined;
}).filter(Boolean); // Filter out undefined values
// Transform the pages
const results = await Promise.all(response.results.map(transformPage));

// return
return {
response,
results,
};
});

// Fetch the image url for a page
async function getImageUrl(pageId: string) {
return await $fetch('/api/notion-page-image', {
body: {
page_id: pageId,
},
method: 'POST',
});
}

// function - transform a page to add imageUrl and sanitize the response
// @note - page is type any because I don't know how to type it.
// @param page - the page to transform
// @returns the transformed page
//
async function transformPage(page: any): Promise<INotionArticle> {
const imageUrl = await getImageUrl(page.id);
return {
title: page.properties.Name.title[0]?.plain_text || '',
description: page.properties.Description?.rich_text?.[0]?.plain_text || '',
tags: page.properties.Tags?.multi_select?.map((tag: any) => tag.name) || [],
image: imageUrl || page.properties.Image?.url || '',
url: page.properties.URL?.url || '',
date: page.properties.Date?.date?.start || '',
score: page.properties.Score?.select?.name,
status: {
name: page.properties.Status?.select?.name,
color: page.properties.Status?.select?.color,
id: page.properties.Status?.select?.id,
},
id: page.id,
};
}

0 comments on commit 90007d6

Please sign in to comment.