mirror of
				https://github.com/uetchy/namae.git
				synced 2025-11-04 15:38:57 +09:00 
			
		
		
		
	feat: support Docker Hub
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -99,3 +99,4 @@ typings/
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.now
 | 
					.now
 | 
				
			||||||
.vercel
 | 
					.vercel
 | 
				
			||||||
 | 
					.sentryclirc
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,9 @@ export default async function handler(
 | 
				
			|||||||
  req: VercelRequest,
 | 
					  req: VercelRequest,
 | 
				
			||||||
  res: VercelResponse
 | 
					  res: VercelResponse
 | 
				
			||||||
): Promise<void> {
 | 
					): Promise<void> {
 | 
				
			||||||
  const { query } = req.query;
 | 
					  const { query, existIf = '404' } = req.query;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const availableStatus = (existIf as string).split(',').map((s) => s.trim());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (!query || typeof query !== 'string') {
 | 
					  if (!query || typeof query !== 'string') {
 | 
				
			||||||
    return sendError(res, new Error('no query given'));
 | 
					    return sendError(res, new Error('no query given'));
 | 
				
			||||||
@@ -18,7 +20,7 @@ export default async function handler(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const response = await fetch(`https://${query}`);
 | 
					    const response = await fetch(`https://${query}`);
 | 
				
			||||||
    const availability = response.status === 404;
 | 
					    const availability = availableStatus.includes(response.status.toString());
 | 
				
			||||||
    send(res, { availability });
 | 
					    send(res, { availability });
 | 
				
			||||||
  } catch (err: any) {
 | 
					  } catch (err: any) {
 | 
				
			||||||
    if ((err as any).code === 'ENOTFOUND') {
 | 
					    if ((err as any).code === 'ENOTFOUND') {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,7 +51,8 @@
 | 
				
			|||||||
    "youtube": "YouTube",
 | 
					    "youtube": "YouTube",
 | 
				
			||||||
    "hexpm": "Hex",
 | 
					    "hexpm": "Hex",
 | 
				
			||||||
    "fly": "Fly.io",
 | 
					    "fly": "Fly.io",
 | 
				
			||||||
    "productHunt": "Product Hunt"
 | 
					    "productHunt": "Product Hunt",
 | 
				
			||||||
 | 
					    "docker": "Docker Hub"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "showMore": "show more",
 | 
					  "showMore": "show more",
 | 
				
			||||||
  "try": "How about this?",
 | 
					  "try": "How about this?",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,29 +3,31 @@ import { useTranslation } from 'react-i18next';
 | 
				
			|||||||
import { DiHeroku } from 'react-icons/di';
 | 
					import { DiHeroku } from 'react-icons/di';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  FaAws,
 | 
					  FaAws,
 | 
				
			||||||
 | 
					  FaCloudflare,
 | 
				
			||||||
 | 
					  FaDocker,
 | 
				
			||||||
 | 
					  FaFirefoxBrowser,
 | 
				
			||||||
 | 
					  FaFly,
 | 
				
			||||||
  FaGithub,
 | 
					  FaGithub,
 | 
				
			||||||
  FaGithubAlt,
 | 
					  FaGithubAlt,
 | 
				
			||||||
  FaGitlab,
 | 
					  FaGitlab,
 | 
				
			||||||
  // FaInstagram,
 | 
					  // FaInstagram,
 | 
				
			||||||
  FaJsSquare,
 | 
					  FaJsSquare,
 | 
				
			||||||
 | 
					  FaProductHunt,
 | 
				
			||||||
  FaPython,
 | 
					  FaPython,
 | 
				
			||||||
  FaReddit,
 | 
					  FaReddit,
 | 
				
			||||||
  FaSlack,
 | 
					  FaSlack,
 | 
				
			||||||
  FaTwitter,
 | 
					  FaTwitter,
 | 
				
			||||||
  FaCloudflare,
 | 
					 | 
				
			||||||
  FaFirefoxBrowser,
 | 
					 | 
				
			||||||
  FaYoutube,
 | 
					  FaYoutube,
 | 
				
			||||||
  FaProductHunt,
 | 
					 | 
				
			||||||
  FaFly,
 | 
					 | 
				
			||||||
} from 'react-icons/fa';
 | 
					} from 'react-icons/fa';
 | 
				
			||||||
import { IoIosBeer, IoMdAppstore } from 'react-icons/io';
 | 
					import { IoIosBeer } from 'react-icons/io';
 | 
				
			||||||
import { MdDomain } from 'react-icons/md';
 | 
					import { MdDomain } from 'react-icons/md';
 | 
				
			||||||
import { RiBuilding2Fill, RiChromeFill, RiNpmjsFill } from 'react-icons/ri';
 | 
					import { RiBuilding2Fill, RiChromeFill, RiNpmjsFill } from 'react-icons/ri';
 | 
				
			||||||
import { SiDeno, SiElixir } from 'react-icons/si';
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  SiAppstore,
 | 
					  SiAppstore,
 | 
				
			||||||
  SiArchlinux,
 | 
					  SiArchlinux,
 | 
				
			||||||
  SiDebian,
 | 
					  SiDebian,
 | 
				
			||||||
 | 
					  SiDeno,
 | 
				
			||||||
 | 
					  SiElixir,
 | 
				
			||||||
  SiFirebase,
 | 
					  SiFirebase,
 | 
				
			||||||
  SiRubygems,
 | 
					  SiRubygems,
 | 
				
			||||||
  SiRust,
 | 
					  SiRust,
 | 
				
			||||||
@@ -41,37 +43,38 @@ const supportedProviders: Record<string, React.ReactNode> = {
 | 
				
			|||||||
  domains: <MdDomain />,
 | 
					  domains: <MdDomain />,
 | 
				
			||||||
  github: <FaGithub />,
 | 
					  github: <FaGithub />,
 | 
				
			||||||
  gitlab: <FaGitlab />,
 | 
					  gitlab: <FaGitlab />,
 | 
				
			||||||
 | 
					  slack: <FaSlack />,
 | 
				
			||||||
 | 
					  productHunt: <FaProductHunt />,
 | 
				
			||||||
 | 
					  githubSearch: <FaGithubAlt />,
 | 
				
			||||||
 | 
					  nta: <RiBuilding2Fill />,
 | 
				
			||||||
  twitter: <FaTwitter />,
 | 
					  twitter: <FaTwitter />,
 | 
				
			||||||
 | 
					  reddit: <FaReddit />,
 | 
				
			||||||
  youtube: <FaYoutube />,
 | 
					  youtube: <FaYoutube />,
 | 
				
			||||||
 | 
					  docker: <FaDocker />,
 | 
				
			||||||
  homebrew: <IoIosBeer />,
 | 
					  homebrew: <IoIosBeer />,
 | 
				
			||||||
 | 
					  archlinux: <SiArchlinux />,
 | 
				
			||||||
 | 
					  debian: <SiDebian />,
 | 
				
			||||||
 | 
					  ubuntu: <SiUbuntu />,
 | 
				
			||||||
  npm: <RiNpmjsFill />,
 | 
					  npm: <RiNpmjsFill />,
 | 
				
			||||||
  rust: <SiRust />,
 | 
					 | 
				
			||||||
  pypi: <FaPython />,
 | 
					  pypi: <FaPython />,
 | 
				
			||||||
 | 
					  rust: <SiRust />,
 | 
				
			||||||
  rubygems: <SiRubygems />,
 | 
					  rubygems: <SiRubygems />,
 | 
				
			||||||
  hexpm: <SiElixir />,
 | 
					  hexpm: <SiElixir />,
 | 
				
			||||||
  ocaml: <OcamlIcon />,
 | 
					  ocaml: <OcamlIcon />,
 | 
				
			||||||
  archlinux: <SiArchlinux />,
 | 
					 | 
				
			||||||
  ubuntu: <SiUbuntu />,
 | 
					 | 
				
			||||||
  debian: <SiDebian />,
 | 
					 | 
				
			||||||
  reddit: <FaReddit />,
 | 
					 | 
				
			||||||
  // instagram: <FaInstagram />,
 | 
					  // instagram: <FaInstagram />,
 | 
				
			||||||
  slack: <FaSlack />,
 | 
					 | 
				
			||||||
  fly: <FaFly />,
 | 
					  fly: <FaFly />,
 | 
				
			||||||
  heroku: <DiHeroku />,
 | 
					 | 
				
			||||||
  now: <NowIcon />,
 | 
					  now: <NowIcon />,
 | 
				
			||||||
 | 
					  heroku: <DiHeroku />,
 | 
				
			||||||
  netlify: <NetlifyIcon />,
 | 
					  netlify: <NetlifyIcon />,
 | 
				
			||||||
  cloudflare: <FaCloudflare />,
 | 
					  cloudflare: <FaCloudflare />,
 | 
				
			||||||
  s3: <FaAws />,
 | 
					  s3: <FaAws />,
 | 
				
			||||||
  firebase: <SiFirebase />,
 | 
					  firebase: <SiFirebase />,
 | 
				
			||||||
  jsorg: <FaJsSquare />,
 | 
					  jsorg: <FaJsSquare />,
 | 
				
			||||||
  modland: <SiDeno />,
 | 
					  modland: <SiDeno />,
 | 
				
			||||||
  productHunt: <FaProductHunt />,
 | 
					 | 
				
			||||||
  githubSearch: <FaGithubAlt />,
 | 
					 | 
				
			||||||
  appStore: <SiAppstore />,
 | 
					  appStore: <SiAppstore />,
 | 
				
			||||||
  // playStore: <IoMdAppstore />,
 | 
					  // playStore: <IoMdAppstore />,
 | 
				
			||||||
  firefoxAddons: <FaFirefoxBrowser />,
 | 
					  firefoxAddons: <FaFirefoxBrowser />,
 | 
				
			||||||
  chromeWebStore: <RiChromeFill />,
 | 
					  chromeWebStore: <RiChromeFill />,
 | 
				
			||||||
  nta: <RiBuilding2Fill />,
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Welcome: React.FC = () => {
 | 
					const Welcome: React.FC = () => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,6 +83,7 @@ class NotFoundError extends Error {
 | 
				
			|||||||
export const DedicatedAvailability: React.FC<{
 | 
					export const DedicatedAvailability: React.FC<{
 | 
				
			||||||
  name: string;
 | 
					  name: string;
 | 
				
			||||||
  query?: string;
 | 
					  query?: string;
 | 
				
			||||||
 | 
					  args?: Record<string, string>;
 | 
				
			||||||
  message?: string;
 | 
					  message?: string;
 | 
				
			||||||
  messageIfTaken?: string;
 | 
					  messageIfTaken?: string;
 | 
				
			||||||
  service: string;
 | 
					  service: string;
 | 
				
			||||||
@@ -94,6 +95,7 @@ export const DedicatedAvailability: React.FC<{
 | 
				
			|||||||
}> = ({
 | 
					}> = ({
 | 
				
			||||||
  name,
 | 
					  name,
 | 
				
			||||||
  query = undefined,
 | 
					  query = undefined,
 | 
				
			||||||
 | 
					  args = {},
 | 
				
			||||||
  message = '',
 | 
					  message = '',
 | 
				
			||||||
  messageIfTaken = undefined,
 | 
					  messageIfTaken = undefined,
 | 
				
			||||||
  service,
 | 
					  service,
 | 
				
			||||||
@@ -105,7 +107,8 @@ export const DedicatedAvailability: React.FC<{
 | 
				
			|||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
  const increaseCounter = useStoreActions((actions) => actions.stats.add);
 | 
					  const increaseCounter = useStoreActions((actions) => actions.stats.add);
 | 
				
			||||||
  const response = useFetch(
 | 
					  const response = useFetch(
 | 
				
			||||||
    `/api/services/${service}/${encodeURIComponent(query || name)}`
 | 
					    `/api/services/${service}/${encodeURIComponent(query || name)}` +
 | 
				
			||||||
 | 
					      new URLSearchParams(args).toString()
 | 
				
			||||||
  ) as Response;
 | 
					  ) as Response;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (response.error) {
 | 
					  if (response.error) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ import AppStoreCard from './providers/AppStore';
 | 
				
			|||||||
import ChromeWebStoreCard from './providers/ChromeWebStore';
 | 
					import ChromeWebStoreCard from './providers/ChromeWebStore';
 | 
				
			||||||
import CloudflareCard from './providers/Cloudflare';
 | 
					import CloudflareCard from './providers/Cloudflare';
 | 
				
			||||||
import CratesioCard from './providers/Cratesio';
 | 
					import CratesioCard from './providers/Cratesio';
 | 
				
			||||||
 | 
					import DockerCard from './providers/Docker';
 | 
				
			||||||
import DomainCard from './providers/Domains';
 | 
					import DomainCard from './providers/Domains';
 | 
				
			||||||
import FirebaseCard from './providers/Firebase';
 | 
					import FirebaseCard from './providers/Firebase';
 | 
				
			||||||
import FirefoxAddonsCard from './providers/FirefoxAddons';
 | 
					import FirefoxAddonsCard from './providers/FirefoxAddons';
 | 
				
			||||||
@@ -23,7 +24,7 @@ import NetlifyCard from './providers/Netlify';
 | 
				
			|||||||
import NpmCard from './providers/Npm';
 | 
					import NpmCard from './providers/Npm';
 | 
				
			||||||
import NtaCard from './providers/Nta';
 | 
					import NtaCard from './providers/Nta';
 | 
				
			||||||
import OcamlCard from './providers/Ocaml';
 | 
					import OcamlCard from './providers/Ocaml';
 | 
				
			||||||
import PlayStoreCard from './providers/PlayStore';
 | 
					// import PlayStoreCard from './providers/PlayStore';
 | 
				
			||||||
import ProductHuntCard from './providers/ProductHunt';
 | 
					import ProductHuntCard from './providers/ProductHunt';
 | 
				
			||||||
import PypiCard from './providers/PyPI';
 | 
					import PypiCard from './providers/PyPI';
 | 
				
			||||||
import RubyGemsCard from './providers/RubyGems';
 | 
					import RubyGemsCard from './providers/RubyGems';
 | 
				
			||||||
@@ -64,6 +65,7 @@ const Index: React.FC<{ query: string }> = ({ query }) => {
 | 
				
			|||||||
      <Section>
 | 
					      <Section>
 | 
				
			||||||
        <Title>{t('section.package')}</Title>
 | 
					        <Title>{t('section.package')}</Title>
 | 
				
			||||||
        <Cards>
 | 
					        <Cards>
 | 
				
			||||||
 | 
					          <DockerCard query={query} />
 | 
				
			||||||
          <HomebrewCard query={query} />
 | 
					          <HomebrewCard query={query} />
 | 
				
			||||||
          <LinuxCard query={query} />
 | 
					          <LinuxCard query={query} />
 | 
				
			||||||
          <NpmCard query={query} />
 | 
					          <NpmCard query={query} />
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										33
									
								
								src/components/cards/providers/Docker.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/components/cards/providers/Docker.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					import { useTranslation } from 'react-i18next';
 | 
				
			||||||
 | 
					import { FaDocker } from 'react-icons/fa';
 | 
				
			||||||
 | 
					import { normalize } from '../../../util/text';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { Card, DedicatedAvailability, Repeater } from '../core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const DockerCard: React.FC<{ query: string }> = ({ query }) => {
 | 
				
			||||||
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
 | 
					  const normalizedQuery = normalize(query, { allowUnderscore: false });
 | 
				
			||||||
 | 
					  const lowerCase = normalizedQuery.toLowerCase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const names = [lowerCase];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Card title={t('providers.docker')}>
 | 
				
			||||||
 | 
					      <Repeater items={names}>
 | 
				
			||||||
 | 
					        {(name) => (
 | 
				
			||||||
 | 
					          <DedicatedAvailability
 | 
				
			||||||
 | 
					            name={name}
 | 
				
			||||||
 | 
					            query={`hub.docker.com/v2/orgs/${name}`}
 | 
				
			||||||
 | 
					            service="existence"
 | 
				
			||||||
 | 
					            message="Go to Docker Hub"
 | 
				
			||||||
 | 
					            link={`https://hub.docker.com/orgs/${name}`}
 | 
				
			||||||
 | 
					            icon={<FaDocker />}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					      </Repeater>
 | 
				
			||||||
 | 
					    </Card>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default DockerCard;
 | 
				
			||||||
@@ -5,7 +5,7 @@ import XHR from 'i18next-xhr-backend';
 | 
				
			|||||||
import LanguageDetector from 'i18next-browser-languagedetector';
 | 
					import LanguageDetector from 'i18next-browser-languagedetector';
 | 
				
			||||||
import { initReactI18next } from 'react-i18next';
 | 
					import { initReactI18next } from 'react-i18next';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TRANSLATION_VERSION = '19';
 | 
					const TRANSLATION_VERSION = '20';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
i18n
 | 
					i18n
 | 
				
			||||||
  .use(Backend)
 | 
					  .use(Backend)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user