├── app ├── favicon.ico ├── (marketing) │ ├── [account] │ │ ├── @modal │ │ │ ├── default.tsx │ │ │ └── (.)photo │ │ │ │ └── [id] │ │ │ │ ├── page.tsx │ │ │ │ └── InstagramDialog.tsx │ │ ├── layout.tsx │ │ ├── loading.tsx │ │ ├── photo │ │ │ └── [id] │ │ │ │ └── page.tsx │ │ ├── APITabs.tsx │ │ └── page.tsx │ ├── auth-integration │ │ ├── page.tsx │ │ └── Redirect.tsx │ ├── faq │ │ └── page.tsx │ ├── install │ │ └── page.tsx │ ├── components │ │ └── CodeExamples.tsx │ ├── layout.tsx │ ├── page.tsx │ ├── pricing │ │ └── page.tsx │ └── account-selector │ │ └── page.tsx ├── dashboard │ ├── [account] │ │ ├── @feedModal │ │ │ ├── default.tsx │ │ │ └── (.)photo │ │ │ │ └── [id] │ │ │ │ ├── loading.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── MetricsSlider.tsx │ │ ├── photo │ │ │ └── [id] │ │ │ │ ├── loading.tsx │ │ │ │ ├── page.tsx │ │ │ │ ├── actions.ts │ │ │ │ └── MediaMetrics.tsx │ │ ├── layout.tsx │ │ ├── loading.tsx │ │ ├── metrics │ │ │ ├── DatePicker.tsx │ │ │ ├── actions.ts │ │ │ └── page.tsx │ │ ├── APITabs.tsx │ │ └── page.tsx │ ├── components │ │ ├── AddAccountButton.tsx │ │ ├── DisconnectButton.tsx │ │ ├── main-nav.tsx │ │ └── user-nav.tsx │ ├── account-selector │ │ ├── ConnectButton.tsx │ │ └── page.tsx │ ├── auth-integration │ │ ├── page.tsx │ │ └── Redirect.tsx │ ├── FacebookLogin.tsx │ ├── layout.tsx │ └── page.tsx ├── (auth) │ ├── login │ │ ├── layout.tsx │ │ ├── page.tsx │ │ └── LoginForm.tsx │ ├── logout │ │ ├── layout.tsx │ │ ├── LogoutForm.tsx │ │ └── page.tsx │ ├── register │ │ ├── layout.tsx │ │ ├── actions.ts │ │ ├── page.tsx │ │ └── RegisterForm.tsx │ └── layout.tsx ├── Signin.tsx ├── robots.ts ├── _actions.ts ├── api-doc │ ├── layout.tsx │ ├── page.tsx │ └── react-swagger.tsx ├── api │ ├── auth │ │ ├── [...nextauth] │ │ │ └── route.ts │ │ └── create │ │ │ └── route.ts │ ├── [account] │ │ └── route.ts │ └── v2 │ │ └── [account] │ │ └── route.ts ├── Footer.tsx ├── globals.css └── cors.ts ├── postcss.config.js ├── lib ├── password.ts ├── utils.ts ├── db.ts ├── cache.ts ├── fb-types.ts ├── temporal.ts ├── auth-options.ts ├── swagger.ts └── scrape.ts ├── components.json ├── next.config.js ├── .gitignore ├── public ├── vercel.svg └── next.svg ├── tsconfig.json ├── components ├── loading-modal.tsx └── ui │ ├── label.tsx │ ├── input.tsx │ ├── badge.tsx │ ├── avatar.tsx │ ├── alert.tsx │ ├── card.tsx │ ├── tabs.tsx │ ├── button.tsx │ ├── table.tsx │ ├── Dialog.tsx │ ├── sheet.tsx │ ├── alert-dialog.tsx │ └── dropdown-menu.tsx ├── README.md ├── package.json ├── prisma └── schema.prisma └── tailwind.config.js /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scribo-dev/instagram-feed-api/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /app/(marketing)/[account]/@modal/default.tsx: -------------------------------------------------------------------------------- 1 | export default function Default() { 2 | return null; 3 | } 4 | -------------------------------------------------------------------------------- /app/dashboard/[account]/@feedModal/default.tsx: -------------------------------------------------------------------------------- 1 | export default function Default() { 2 | return null; 3 | } 4 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /app/(auth)/login/layout.tsx: -------------------------------------------------------------------------------- 1 | export default function RootLayout({ 2 | children, 3 | }: { 4 | children: React.ReactNode; 5 | }) { 6 | return
{children}
; 7 | } 8 | -------------------------------------------------------------------------------- /app/dashboard/[account]/photo/[id]/loading.tsx: -------------------------------------------------------------------------------- 1 | import LoadingModal from "@/components/loading-modal"; 2 | 3 | export default function Loading() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /lib/password.ts: -------------------------------------------------------------------------------- 1 | import * as bcrypt from "bcrypt"; 2 | 3 | const saltRounds = 10; 4 | 5 | export const hashPassword = (string: string) => { 6 | return bcrypt.hash(string, saltRounds); 7 | }; 8 | -------------------------------------------------------------------------------- /app/dashboard/[account]/@feedModal/(.)photo/[id]/loading.tsx: -------------------------------------------------------------------------------- 1 | import LoadingModal from "@/components/loading-modal"; 2 | 3 | export default function Loading() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /app/(marketing)/[account]/layout.tsx: -------------------------------------------------------------------------------- 1 | export default function Layout(props: { 2 | children: React.ReactNode; 3 | modal: React.ReactNode; 4 | }) { 5 | return ( 6 | <> 7 | {props.children} 8 | {props.modal} 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /app/Signin.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { signIn } from "next-auth/react"; 3 | 4 | export default () => ( 5 | 8 | ); 9 | -------------------------------------------------------------------------------- /app/robots.ts: -------------------------------------------------------------------------------- 1 | import type { MetadataRoute } from "next"; 2 | 3 | export default function robots(): MetadataRoute.Robots { 4 | return { 5 | rules: { 6 | userAgent: "*", 7 | allow: "/", 8 | disallow: "/api/", 9 | }, 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /app/dashboard/[account]/layout.tsx: -------------------------------------------------------------------------------- 1 | export default function Layout(props: { 2 | children: React.ReactNode; 3 | feedModal: React.ReactNode; 4 | }) { 5 | return ( 6 | <> 7 | {props.children} 8 | {props.feedModal} 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /app/(auth)/logout/layout.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: "Next.js", 3 | description: "Generated by Next.js", 4 | }; 5 | 6 | export default function RootLayout({ 7 | children, 8 | }: { 9 | children: React.ReactNode; 10 | }) { 11 | return
{children}
; 12 | } 13 | -------------------------------------------------------------------------------- /app/(auth)/register/layout.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: "Next.js", 3 | description: "Generated by Next.js", 4 | }; 5 | 6 | export default function RootLayout({ 7 | children, 8 | }: { 9 | children: React.ReactNode; 10 | }) { 11 | return
{children}
; 12 | } 13 | -------------------------------------------------------------------------------- /app/_actions.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { redirect } from "next/navigation"; 4 | 5 | export async function GenerateFeed(data: FormData) { 6 | let account = data.get("account") as string; 7 | if (!account) throw new Error("Account not provided"); 8 | 9 | redirect(`/${account.replace("@", "").trim()}`); 10 | } 11 | -------------------------------------------------------------------------------- /app/api-doc/layout.tsx: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Next.js', 3 | description: 'Generated by Next.js', 4 | } 5 | 6 | export default function RootLayout({ 7 | children, 8 | }: { 9 | children: React.ReactNode 10 | }) { 11 | return ( 12 | 13 | {children} 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /app/api-doc/page.tsx: -------------------------------------------------------------------------------- 1 | import { getApiDocs } from "@/lib/swagger"; 2 | 3 | import ReactSwagger from "./react-swagger"; 4 | 5 | export default async function IndexPage() { 6 | const spec = await getApiDocs(); 7 | return ( 8 |
9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth"; 2 | 3 | import type { DefaultUser } from "next-auth"; 4 | import { authOptions } from "@/lib/auth-options"; 5 | 6 | declare module "next-auth" { 7 | interface Session { 8 | user?: DefaultUser & { id: string }; 9 | } 10 | } 11 | 12 | const handler = NextAuth(authOptions); 13 | 14 | export { handler as GET, handler as POST }; 15 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": false 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | export type InstagramImage = { 8 | slug: string; 9 | type: "video" | "image" | "carousel_album"; 10 | image: string; 11 | description: string; 12 | takenAt: string; 13 | pinned: boolean; 14 | video?: string; 15 | }; 16 | -------------------------------------------------------------------------------- /lib/db.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client"; 2 | 3 | declare global { 4 | // eslint-disable-next-line no-var 5 | var prisma: PrismaClient | undefined; 6 | } 7 | 8 | export const prisma = 9 | global.prisma || 10 | new PrismaClient({ 11 | log: process.env.NODE_ENV === "development" ? ["error", "warn"] : ["error"], 12 | }); 13 | 14 | if (process.env.NODE_ENV !== "production") { 15 | global.prisma = prisma; 16 | } 17 | -------------------------------------------------------------------------------- /app/api-doc/react-swagger.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import dynamic from "next/dynamic"; 3 | //@ts-ignore 4 | const SwaggerUI = dynamic(() => import("swagger-ui-react"), { ssr: false }); 5 | 6 | import "swagger-ui-react/swagger-ui.css"; 7 | 8 | type Props = { 9 | spec: Record; 10 | }; 11 | 12 | function ReactSwagger({ spec }: Props) { 13 | //@ts-ignore 14 | return ; 15 | } 16 | 17 | export default ReactSwagger; 18 | -------------------------------------------------------------------------------- /lib/cache.ts: -------------------------------------------------------------------------------- 1 | import { Redis } from "@upstash/redis"; 2 | 3 | if (!process.env.KV_URL || !process.env.KV_TOKEN) throw "env not found"; 4 | 5 | declare global { 6 | // eslint-disable-next-line no-var 7 | var kv: Redis | undefined; 8 | } 9 | 10 | export const kv = 11 | global.kv || 12 | new Redis({ 13 | url: process.env.KV_URL, 14 | token: process.env.KV_TOKEN, 15 | }); 16 | 17 | if (process.env.NODE_ENV !== "production") { 18 | global.kv = kv; 19 | } 20 | -------------------------------------------------------------------------------- /app/Footer.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | export default function Footer() { 4 | return ( 5 |
6 | 7 | Made by Scribo.dev 8 | 9 | {/* Terms of service 10 | Privacy */} 11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /app/dashboard/components/AddAccountButton.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | //@ts-ignore 4 | import { useFormStatus } from "react-dom"; 5 | import { Button } from "@/components/ui/button"; 6 | import { PlusIcon } from "lucide-react"; 7 | 8 | export default function DisconnectButton() { 9 | const { pending } = useFormStatus(); 10 | 11 | return ( 12 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | experimental: { 4 | after: true, 5 | }, 6 | serverExternalPackages: ["prisma", "@temporalio/client"], 7 | images: { 8 | remotePatterns: [ 9 | { 10 | protocol: "https", 11 | hostname: "d2b8b46ja6xujp.cloudfront.net", 12 | }, 13 | { 14 | protocol: "https", 15 | hostname: "**.fbcdn.net", 16 | }, 17 | ], 18 | }, 19 | }; 20 | 21 | module.exports = nextConfig; 22 | -------------------------------------------------------------------------------- /lib/fb-types.ts: -------------------------------------------------------------------------------- 1 | export interface MetricsResponse { 2 | data: Metric[]; 3 | } 4 | 5 | export interface Metric { 6 | name: string; 7 | values: MetricValue[]; 8 | period: string; 9 | description: string; 10 | title: string; 11 | id: string; 12 | } 13 | 14 | export interface MetricValue { 15 | value: number; 16 | end_time?: string; 17 | } 18 | 19 | export interface ResponseError { 20 | error: { 21 | message: string; 22 | type: string; 23 | code: number; 24 | fbtrace_id: string; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /app/dashboard/[account]/@feedModal/(.)photo/[id]/page.tsx: -------------------------------------------------------------------------------- 1 | import { getMediaMetrics } from "../../../photo/[id]/actions"; 2 | import MetricsSlider from "./MetricsSlider"; 3 | 4 | export default async function Page( 5 | props: { 6 | params: Promise<{ account: string; id: string }>; 7 | } 8 | ) { 9 | const params = await props.params; 10 | const { media, metrics } = await getMediaMetrics(params.account, params.id); 11 | 12 | if (!media) return null; 13 | 14 | return ; 15 | } 16 | -------------------------------------------------------------------------------- /app/dashboard/account-selector/ConnectButton.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | //@ts-ignore 4 | import { useFormStatus } from "react-dom"; 5 | import { Button } from "@/components/ui/button"; 6 | import { PlusCircleIcon } from "lucide-react"; 7 | 8 | export default function ConnectButton() { 9 | const { pending } = useFormStatus(); 10 | 11 | return ( 12 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /app/dashboard/components/DisconnectButton.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | //@ts-ignore 4 | import { useFormStatus } from "react-dom"; 5 | import { Button } from "@/components/ui/button"; 6 | import { Ban } from "lucide-react"; 7 | 8 | export default function DisconnectButton() { 9 | const { pending } = useFormStatus(); 10 | 11 | return ( 12 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /app/(auth)/logout/LogoutForm.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | // @ts-ignore 4 | import { Button } from "@/components/ui/button"; 5 | import { signIn, signOut } from "next-auth/react"; 6 | import { LogOutIcon } from "lucide-react"; 7 | 8 | export function LogoutForm() { 9 | return ( 10 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | .env 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /app/(auth)/layout.tsx: -------------------------------------------------------------------------------- 1 | import "../globals.css"; 2 | import { Inter } from "next/font/google"; 3 | import { Analytics } from "@vercel/analytics/react"; 4 | import Footer from "../Footer"; 5 | 6 | const inter = Inter({ subsets: ["latin"] }); 7 | 8 | export default function RootLayout({ 9 | children, 10 | }: { 11 | children: React.ReactNode; 12 | }) { 13 | return ( 14 | 15 | 16 | {children} 17 |