Skip to content

Commit

Permalink
refactor: move nuxt content queries to content api composable
Browse files Browse the repository at this point in the history
  • Loading branch information
EvgenyWas committed Jul 15, 2024
1 parent 13f6228 commit 1c6a324
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 44 deletions.
46 changes: 46 additions & 0 deletions composables/api/useContentAPI.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { BEST_ARTICLES_LIMIT, MOST_VIEWED_ARTICLES_LIMIT } from '~/configs/properties';
import type { ArticleContent, ArticleListItem } from '~/types/responses';

const ARTICLE_LIST_ONLY: Array<keyof ArticleListItem> = ['_path', 'title', 'description', 'image', 'keywords'];
const ARTICLE_SIBLINGS_ONLY: Array<keyof ArticleListItem> = ['_path', 'title'];
const PAGINATION_LIMIT = 10;

interface PaginationOptions {
skip?: number;
limit?: number;
}

export default function useContentAPI() {
const fetchArticle = (path: string) => queryContent<ArticleContent>(path).findOne();

const fetchArticleSiblings = (path: string, topic?: string) =>
queryContent<ArticleContent>()
.only(ARTICLE_SIBLINGS_ONLY)
.where({ _path: { $contains: `/articles/${topic}` } })
.findSurround(path);

const fetchArticlesListByTopic = (topic: string) =>
queryContent<ArticleListItem>(`articles/${topic}`).only(ARTICLE_LIST_ONLY).find();

const fetchMostViewedArticlesList = () =>
queryContent<ArticleListItem>('articles').only(ARTICLE_LIST_ONLY).limit(MOST_VIEWED_ARTICLES_LIMIT).find(); // TODO: update

const fetchBestArticlesList = () =>
queryContent<ArticleListItem>('articles').only(ARTICLE_LIST_ONLY).limit(BEST_ARTICLES_LIMIT).find(); // TODO: update

const fetchPaginalArticlesList = (options = {} as PaginationOptions) =>
queryContent<ArticleListItem>('articles')
.only(ARTICLE_LIST_ONLY)
.skip(options.skip ?? 0)
.limit(options.limit || PAGINATION_LIMIT)
.find();

return {
fetchArticle,
fetchArticleSiblings,
fetchArticlesListByTopic,
fetchMostViewedArticlesList,
fetchBestArticlesList,
fetchPaginalArticlesList,
};
}
2 changes: 2 additions & 0 deletions configs/properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,5 @@ export const ARTICLE_TOPICS = [
export const ARTICLE_KEYWORDS = ['api', 'architecture', 'design', 'fundamentals', 'perfomance', 'refactoring'] as const;
export const MAX_ARTICLE_RATE = 5;
export const ARTICLE_RATE_MAX_AGE = 30 * 24 * 60 * 60;
export const MOST_VIEWED_ARTICLES_LIMIT = 5;
export const BEST_ARTICLES_LIMIT = 5;
15 changes: 3 additions & 12 deletions pages/articles/[topic]/[slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ const pageTransition = useState<ArticlePageTransition>('article-page-transition'
const route = useRoute();
const { openSuccessfulSnackbar, openErrorSnackbar } = useSnackbar();
const { fetchArticleStats, updateArticleRate, updateArticleViews } = usePublicAPI();
const { fetchArticle, fetchArticleSiblings } = useContentAPI();
const topic = route.params.topic as string;
const title = route.params.slug as string;
Expand All @@ -161,9 +162,7 @@ const rateCookie = useCookie(`article-rate-${topic}-${title}`, { maxAge: ARTICLE
const rateModelValue = ref<number>(0);
const { data: article, error } = await useAsyncData(route.path, () =>
queryContent<ArticleContent>(route.path).sort({ id: 1, $numeric: true }).findOne(),
);
const { data: article, error } = await useAsyncData(route.path, () => fetchArticle(route.path), { deep: false });
if (!article.value || error.value) {
throw createError({ statusCode: 404, data: { to: '/articles' }, fatal: true });
}
Expand All @@ -175,15 +174,7 @@ const { data: stats, pending: statsPending } = await useLazyAsyncData(
{ deep: false },
);
const { data: siblings } = await useLazyAsyncData(
() =>
queryContent<ArticleContent>()
.only(['_path', 'title'])
.sort({ id: 1, $numeric: true })
.where({ _path: { $contains: `/articles/${topic}` } })
.findSurround(route.path),
{ deep: false },
);
const { data: siblings } = await useLazyAsyncData(() => fetchArticleSiblings(route.path, topic), { deep: false });
const prevSibling = computed(() => siblings.value?.[0] as ArticleContent | undefined);
Expand Down
6 changes: 2 additions & 4 deletions pages/articles/[topic]/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,17 @@

<script setup lang="ts">
import { ARTICLE_TOPICS } from '~/configs/properties';
import type { ArticleListItem } from '~/types/responses';
const ARTICLE_ONLY: Array<keyof ArticleListItem> = ['_path', 'title', 'description', 'image'];
const route = useRoute();
const { fetchArticlesListByTopic } = useContentAPI();
const topic = ARTICLE_TOPICS.find(({ name }) => name === route.params.topic);
if (!topic) {
throw createError({ statusCode: 404, statusMessage: 'The requested articles topic does not exist', fatal: true });
}
const { data: articles, error } = await useAsyncData(`articles/${topic.name}`, () =>
queryContent<ArticleListItem>(`articles/${topic.name}`).only(ARTICLE_ONLY).find(),
fetchArticlesListByTopic(topic.name),
);
if (error.value) {
throw createError({ statusCode: 400, fatal: true });
Expand Down
15 changes: 3 additions & 12 deletions pages/articles/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -104,22 +104,17 @@ import type { VInfiniteScroll } from 'vuetify/components';
import { ARTICLE_KEYWORDS, ARTICLE_TOPICS } from '~/configs/properties';
import type { ArticleListItem } from '~/types/responses';
const START_LIMIT = 10;
const EXTRA_LIMIT = 10;
const ARTICLE_ONLY: Array<keyof ArticleListItem> = ['_path', 'title', 'description', 'image', 'keywords'];
useSeoMeta({ title: 'Articles', description: 'All the blog articles' });
const { mobile } = useDisplay();
const { openErrorSnackbar } = useSnackbar();
const { fetchPaginalArticlesList } = useContentAPI();
const keywords = ref<Array<(typeof ARTICLE_KEYWORDS)[number]>>([]);
const extraArticles = reactive<Array<ArticleListItem>>([]);
const { data, error } = await useAsyncData('articles', () =>
queryContent<ArticleListItem>('articles').only(ARTICLE_ONLY).limit(START_LIMIT).find(),
);
const { data, error } = await useAsyncData('articles', () => fetchPaginalArticlesList());
if (!data.value || error.value) {
throw createError({ statusCode: 404, fatal: true });
}
Expand All @@ -138,11 +133,7 @@ const getTopic = (path: string) => path.split('/')[2];
const onInfiniteScrollLoad: VInfiniteScroll['$props']['onLoad'] = async ({ done }) => {
try {
const loadedArticles = await queryContent<ArticleListItem>('articles')
.only(ARTICLE_ONLY)
.skip(articles.value.length)
.limit(EXTRA_LIMIT)
.find();
const loadedArticles = await fetchPaginalArticlesList({ skip: articles.value.length });
if (!loadedArticles.length) {
return done('empty');
}
Expand Down
23 changes: 7 additions & 16 deletions pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -209,38 +209,29 @@
</template>

<script setup lang="ts">
import type { ArticleListItem } from '~/types/responses';
import { BEST_ARTICLES_LIMIT, MOST_VIEWED_ARTICLES_LIMIT } from '~/configs/properties';
const ARTICLE_IMAGE_FALLBACK = 'storage/app/article-fallback?height=450';
const ARTICLE_ONLY: Array<keyof ArticleListItem> = ['_path', 'title', 'description', 'image'];
const MOST_VIEWED_ARTICLES_LIMIT = 5;
const BEST_ARTICLES_LIMIT = 5;
useHead({ link: [{ href: 'storage/app/home-parallax', as: 'image', type: 'image/webp', rel: 'preload' }] });
useSeoMeta({
title: 'Nuxt 3 blog',
description:
// eslint-disable-next-line max-len
'Nuxt 3 blog is a pet-project of Yauheni Vasiukevich. It consists of a wide collection of articles on various web topics.',
'Nuxt 3 blog is a pet-project of Yauheni Vasiukevich. ' +
'It consists of a wide collection of articles on various Web development topics.',
});
const { fetchMostViewedArticlesList, fetchBestArticlesList } = useContentAPI();
const {
data: mostViewedArticles,
pending: mostViewedArticlesPending,
error: mostViewedArticlesError,
} = await useLazyAsyncData(
'most-viewed-articles',
() => queryContent<ArticleListItem>('articles').only(ARTICLE_ONLY).limit(MOST_VIEWED_ARTICLES_LIMIT).find(),
{ deep: false },
);
} = await useLazyAsyncData('most-viewed-articles', () => fetchMostViewedArticlesList(), { deep: false });
const {
data: bestArticles,
pending: bestArticlesPending,
error: bestArticlesError,
} = await useLazyAsyncData(
'best-articles',
() => queryContent<ArticleListItem>('articles').only(ARTICLE_ONLY).limit(BEST_ARTICLES_LIMIT).find(),
{ deep: false },
);
} = await useLazyAsyncData('best-articles', () => fetchBestArticlesList(), { deep: false });
</script>

0 comments on commit 1c6a324

Please sign in to comment.