DevGraphQL API
GraphQL API
GitHub GraphQL API
Lấy tất cả các note
của một project(classic) trên github sỠdụng công cụ GitHub GraphQL API
Lấy columnId
của một project
BÆ°á»›c 1
query {
repository(name: "phannhatchanh.com", owner: "phannhatchanh") {
project(number: 1) {
name
columns(first: 1) {
nodes {
id
name
}
}
}
}
}
BÆ°á»›c 2
query ($columnId: ID!) {
node(id: $columnId) {
... on ProjectColumn {
cards {
nodes {
note
}
}
}
}
}
Trong phần QUERY VARIABLES
{
"columnId": "id ở bước 1"
}
Lấy tất cả các note
của một columns
trong project
query {
repository(name: "phannhatchanh.com", owner: "phannhatchanh") {
project(number: 1) {
name
columns(first: 1) {
nodes {
name
cards {
nodes {
note
}
}
}
}
}
}
}
GraphQL GitHub ProjectV2
query {
user(login: "phannhatchanh") {
projectV2(number: 15) {
items(first: 10) {
nodes {
fieldValues(first: 10) {
nodes {
... on ProjectV2ItemFieldTextValue {
text
}
}
}
}
}
}
}
}
https://phannhatchanh.com/tweets
import { Suspense } from "react";
import { EmbeddedTweet, TweetSkeleton } from "react-tweet";
import { getTweet } from "react-tweet/api";
import PageHeader from "@/components/page-header";
import { getPageSeo } from "@/lib/contentful";
import { cn } from "@/lib/utils";
import styles from "./tweet.module.css";
export async function generateMetadata() {
const seoData = await getPageSeo("tweets");
if (!seoData) return null;
const { seo: { title, description, keywords } } = seoData;
const siteUrl = "/tweets";
return {
title,
description,
keywords,
openGraph: {
title,
description,
url: siteUrl,
},
alternates: {
canonical: siteUrl,
},
};
}
export default async function Tweets() {
const { tweets } = await fetchData();
return (
<section>
<PageHeader
caption={"/tweets"}
title={"Inspired Tweets"}
description={"A collection of tweets that inspire me, make me laugh, and make me ponder."}
/>
<div className="columns-1 gap-4 sm:columns-2">
{tweets.tweets.map((tweet, index) => (
<div
key={index}
className={cn("thumbnail-shadow mb-4 relative w-full min-w-full overflow-hidden rounded-xl", styles.tweetCard)}
data-theme="light"
>
<Suspense fallback={<TweetSkeleton />}>
<EmbeddedTweet tweet={tweet} />
</Suspense>
</div>
))}
</div>
</section>
);
}
async function fetchData() {
const response = await fetch("https://api.github.com/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.GITHUB_PERSONAL_ACCESS_TOKEN}`,
},
body: JSON.stringify({
query: `query {
user(login: "phannhatchanh") {
projectV2(number: 15) {
items(first: 10) {
nodes {
fieldValues(first: 10) {
nodes {
... on ProjectV2ItemFieldTextValue {
text
}
}
}
}
}
}
}
}`,
}),
}).then((res) => res.json());
const tweetIds = response?.data?.user?.projectV2?.items?.nodes?.flatMap((node) =>
node.fieldValues.nodes.map((field) => field.text)
);
// Get the actual tweets from Twitter using the Twitter API
const tweets =
tweetIds && tweetIds.length > 0
? await Promise.all(tweetIds.map((id) => getTweet(id)))
: [];
return { tweets: { tweets } };
}
import React, { useEffect, useState } from 'react'
import Link from 'next/link'
import Image from 'next/image'
import { LoadingDots } from 'components/ui/LoadingDots'
import { ReactMarkdown } from 'react-markdown/lib/react-markdown'
import { formatShortDate } from 'lib/formatShortDate'
export const getStaticProps = async () => {
const response: GithubResponse = await fetch(
'https://api.github.com/graphql',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.GITHUB_PERSONAL_ACCESS_TOKEN}`,
},
body: JSON.stringify({
query: `
query {
repository(name: "phannhatchanh.com", owner: "phannhatchanh") {
project(number: 2) {
name
url
body
columns(first: 4) {
totalCount
nodes {
name
cards {
totalCount
nodes {
note
updatedAt
creator {
login
url
avatarUrl
}
}
}
}
}
}
}
}
`,
}),
},
).then((res) => res.json())
const project = response?.data?.repository?.project || {}
const columns = project?.columns?.nodes || []
const todos = columns.map((column) => {
const cards = column?.cards?.nodes || []
return {
name: column?.name,
totalCount: column?.cards?.totalCount || 0,
cards: cards.map((card) => ({
note: card?.note,
updatedAt: card?.updatedAt,
creator: card?.creator?.login || 'Unknown',
url: card?.creator?.url || 'Unknown',
avatarUrl: card?.creator?.avatarUrl || 'Unknown',
})),
}
})
return { props: { project, todos } }
}
export default function ProjectTasks({ project, todos }) {
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
setIsLoading(false)
}, [])
return (
<>
<h1 className="text-2xl font-bold">{project?.name}</h1>
<div>
<ReactMarkdown>{project?.body}</ReactMarkdown>
</div>
<div className="flex">
{project?.columns?.totalCount === 0 ? (
<div className="w-1/2">
This project doesn’t have any columns or cards.
<Link
href={project?.url}
target="_blank"
rel="noopener noreferrer"
className="button button--solid"
>
Add a column
</Link>
</div>
) : (
<>
{todos.map((column, columnIndex) => (
<div key={columnIndex} className="w-1/2">
<h2 className="text-lg font-semibold">
{column?.totalCount} - {column?.name}
</h2>
{isLoading ? (
<LoadingDots />
) : (
<>
{column?.cards.map((todo, index) => (
<div key={index} className="my-4">
<div className="font-bold">{todo?.note}</div>
<div className="text-sm">
Added by:{' '}
<Link href={todo?.url}>{todo?.creator}</Link>
</div>
<Image
src={todo?.avatarUrl}
alt={todo?.creator}
width={32}
height={32}
className="rounded-full"
/>
Updated at: {formatShortDate(todo?.updatedAt)}
</div>
))}
</>
)}
</div>
))}
</>
)}
</div>
</>
)
}
type GithubResponse = {
data?: {
repository: {
project?: {
name?: string
url?: string
body?: string
columns?: {
totalCount: number
nodes: {
name?: string
cards?: {
totalCount: number
nodes: {
note?: string
updatedAt: string
creator?: {
login?: string
url?: string
avatarUrl?: string
}
}[]
}
}[]
}
}
}
}
}