This Valentine's Day, give her a different gift

Posted by creative on Wed, 02 Feb 2022 01:52:03 +0100

Today is February 2, 2022. It's less than two weeks from this year's Valentine's Day.

Introducing a website to you, Cunt. I love you

Reference sample website:

Application process

Open the home page of your website: Cunt. I love you

(Since the site database uses PlanetScale free service, located in the eastern United States, access may be slightly slower. Please be patient.)

Sign in with an Authin account (you can register with your mobile phone, your mailbox, your Github account, or your WeChat applet, and you'll add more ways to sign in later).

You can click to enter domain name application and mailbox application respectively.

Domain name application

The domain name application interface is as follows:

There are three types of binding supported:

  • CNAME: You can use Github Pages, Netlify, Gitee, Coding.net, Cloudflare, etc. Platforms that provide hosted services

    • Value reference: willin.github.io
    • Note: Vercel is not supported because by default Vercel does not support binding secondary domain names (unless ownership is under your personal name)
  • A:IPv4 needs to set up its own server and bind it

    • Value reference: 1.2.3.4 (ip address of your server)
  • AAAA: IPv6 needs to set up its own server and bind

    • Not expressing, less recommending for non-professionals
    • If you need to bind IPv4 and IPv6 at the same time, it is recommended to register type A and then contact me by ISSUE or email

There is also a Proxied (CDN) that you can try to turn on or off to test if you don't know what it does.

Mailbox application

The domain name application interface is as follows:

Cloudflare's mail forwarding service is currently used, but because IDN domain names are not yet supported, you can preempt and have them for the first time.

Other Instructions

If you need help

Welcome to follow me on Github: willin If you have problems preparing gifts for your loved one, you can get free technical advice.

Want another domain name

  • js.cool (Vercel binding is now supported after several negotiations)
  • log.lu (look forward to it)

Feel generous

Itch for a try

Maybe you also have a lot of ideas that you want to achieve. You can:

  • Use Authing Quick integration to develop your own applications
  • Fork source code for this project (fully open source) and provides your own domain name service

  • Perfect and optimize this project on Github

Open Source

Next, start an important part. As the saying goes, it is better to teach a person to fish than to teach a person to fish. I will Cunt. I love your source code Open source, and a detailed explanation of the design and implementation of the entire process.

Design

The project took me about three hours to complete. To enhance my strengths and avoid my weaknesses, I used a UI framework, so there was no additional UI design and I started the project quickly with a few basic components.

Technical Selection

First, the first step is to select the technology. Since I'm offering a free service, I try to choose some free service providers and related technology stacks as well.

Choice of service provider:

  • Cloudflare : Provide free domain name resolution, CDN acceleration, and open interfaces
  • Vercel : Free application hosting for individuals, supporting Node.js environment, using Next.js framework
  • PlanetScale : Cloud MySQL service with a free quota
  • Prisma : Cloud Studio Management Database

In fact, the Cloudflare family barrel was meant to be used Cloudflare Pages (Static Web Site)+ Cloudflare Workers (Serverless method execution) and KV (Key-value pair storage), but due to time and energy constraints, a simpler and faster implementation is used.

Technology stack:

  • Typescript : Although I like to do more with less code, TS gives me a more efficient stage for teamwork
  • Next.js : a full stack framework (front end uses React, back end is similar to http module and Express), supports SSR (server-side rendering) and SSG (static site generation)

  • Prisma : The next generation ORM framework that supports multiple databases (MySQL for this project) and database migration (Migration)
  • Tailwind CSS : next generation CSS framework, practical first

    • Daisy UI : Encapsulates some UI style components

Database Design

Since I use Authin user integration, the design of user tables and user-related interfaces are omitted.

// Domain Name Type
enum DomainType {
  A
  AAAA
  CNAME
}

// Audit Status
enum Status {
  // Pending review
  PENDING
  // activation
  ACTIVE
  // Deleted
  DELETED
  // Disabled by Administrator
  BANNED
}

// Domain Name Record Table
model Domains {
  // ID of the Cloudflare domain name record as the primary key ID of the table
  id        String      @id @default(cuid()) @db.VarChar(32)
  // Self-incrementing IDS makes no sense, just to reduce queries (after all, there is a call quota limit). Self-incrementing primary keys and self-incrementing IDs are not recommended for real projects
  no        Int         @default(autoincrement()) @db.UnsignedInt
  name      String      @db.VarChar(255)
  punycode  String      @db.VarChar(255)
  type      DomainType  @default(CNAME)
  content   String      @default("") @db.VarChar(255)
  proxied   Boolean     @default(true)
  // User id of Authin
  user      String      @default("") @db.VarChar(32)
  status    Status      @default(ACTIVE)
  createdAt DateTime    @default(now())
  updatedAt DateTime    @updatedAt

  @@index([no])
  @@index([name, punycode])
  @@index([user, status, createdAt])
}

// Mailbox table
model Emails {
  // Since the Cloudflare mailbox does not yet provide an open interface, manual auditing and manipulation is required, where the default cuid is filled in as the primary key id
  id        String      @id @default(cuid()) @db.VarChar(32)
  // Self-incrementing IDS makes no sense, just to reduce queries (after all, there is a call quota limit). Self-incrementing primary keys and self-incrementing IDs are not recommended for real projects
  no        Int         @default(autoincrement()) @db.UnsignedInt
  name      String      @db.VarChar(255)
  punycode  String      @db.VarChar(255)
  content   String      @default("") @db.VarChar(255)
  user      String      @default("") @db.VarChar(32)
  status    Status      @default(PENDING)
  createdAt DateTime    @default(now())
  updatedAt DateTime    @updatedAt

  @@index([no])
  @@index([name, punycode])
  @@index([user, status, createdAt])
}

Very simple, please refer to the notes for instructions. In addition, I was meant to save only one name, but since I will re-register it, for example, if I register a Chinese King, and you register a corresponding punycode code name xn--qbyt9x, there will be conflicts, so all of them will be saved.

Technical preparation

First put Next. The JS website framework is set up and deployed to Vercel for testing. You can add Tailwind CSS and Autohing SSO integration. The first step of preparation is complete.

interface design

For fast (lazy) implementation, I created four interfaces for add-delete check.

Query interface:

graph TD
    Start1(Start)
    --> |Check Domain Name Existence| check1{Whether to sign in}
    --> |F| fail1[fail]
    --> End1(End)
    check1 --> |T| check12{Check if domain name is reserved}
    --> |T| fail1
    check12 --> |F| check13{Check database duplication}
    --> |T| fail1
    check13 --> |F| success1[Allow registration]
    --> End1

Create an interface:

graph TD
    Start2(Start)
    --> |Create a domain name| check2{Whether to sign in}
    --> |F| fail2[fail]
    --> End2(End)
    check2 --> |T| check22{Check if domain name is reserved}
    --> |T| fail2
    check22 --> |F| check23{Whether the user has registered the domain name}
    --> |T| fail2
    check23 --> |F| check24{Check database duplication}
    --> |T| fail2
    check24 --> |F| success2[register]
    --> End2

The database queries whether the user has registered a domain name and whether the same name exists and can be completed with a single query, which is split to improve query performance.

Modify interface:

graph TD
    Start3(Start)
    --> |Check Domain Name Existence| check3{Whether to sign in}
    --> |F| fail3[fail]
    --> End3(End)
    check3 --> |T| check32{modify id Records matching the user}
    --> |F Modify Record Number 0| fail3
    check32 --> |T| success3[Successful modification]
    --> End3

Deleting an interface is the same as modifying it. The mailbox interface is similar to a domain name and will not be repeated.

code implementation

Encapsulate Cloudflare SDK

Of course, there are ready-made libraries that I can use directly, but with few lines of code, I'm doing it myself.

import { Domains } from '@prisma/client';
import { CfAPIToken, CfZoneId } from '../config';

const BASE_URL = 'https://api.cloudflare.com/client/v4';

export type CFResult = {
  success: boolean;
  result: {
    id: string;
  };
};

const headers = {
  Authorization: `Bearer ${CfAPIToken}`,
  'Content-Type': 'application/json'
};

export const createDomain = async (
  form: Pick<Domains, 'name' | 'content' | 'type' | 'proxied'>
): Promise<string> => {
  const res = await fetch(`${BASE_URL}/zones/${CfZoneId}/dns_records`, {
    method: 'POST',
    headers,
    body: JSON.stringify({ ...form, ttl: 1 })
  });
  const data = (await res.json()) as CFResult;
  if (data.success) {
    return data.result.id;
  }
  return '';
};

export const updateDomain = async (
  id: string,
  form: Pick<Domains, 'name' | 'content' | 'type' | 'proxied'>
): Promise<boolean> => {
  const res = await fetch(`${BASE_URL}/zones/${CfZoneId}/dns_records/${id}`, {
    method: 'PATCH',
    headers,
    body: JSON.stringify({ ...form, ttl: 1 })
  });
  const data = (await res.json()) as CFResult;
  console.error(data);
  return data.success;
};

export const deleteDomain = async (id: string): Promise<boolean> => {
  const res = await fetch(`${BASE_URL}/zones/${CfZoneId}/dns_records/${id}`, {
    method: 'DELETE',
    headers
  });
  const data = (await res.json()) as CFResult;
  return !!data.result.id;
};

Encapsulation Check Tool Class

There needs to be a regular basis, and if you need an online debugging tool, you can access: regexper.js.cool

Domain Name (CNAME) Check Rules:

/^((?!-))(xn--)?[a-z0-9][a-z0-9-_]{0,61}[a-z0-9]{0,1}\.(xn--)?([a-z0-9-]{1,61}|[a-z0-9-]{1,30}\.[a-z]{2,})$/;

Mailbox verification rules:

/^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;

IPv4 Check Rules:

/^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;

IPv6 Check Rule:

/^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}))|:)))(%.+)?$/;

Page Request Encapsulation

Take the domain name registration submission as an example:

async function submit(e: SyntheticEvent) {
  e.preventDefault();
  // Because I use both Vue and React less often
  // So to get form data, I'm using Vanilla JS, which is more versatile
  // If you are unfamiliar with it, you can use React
  const target = e.currentTarget as typeof e.currentTarget & {
    type: { value: DomainType };
    content: { value: string };
    proxied: { checked: boolean };
  };
  const type = target.type.value;
  const content = target.content.value;
  if (!validateContent(type, content)) {
    return;
  }
  const form = {
    type,
    content,
    proxied: target.proxied.checked,
    name,
    punycode: toASCII(name)
  };
  // I recommend encapsulating the Fetch, so I don't do it for efficiency (lazy)
  const res = await fetch(`/api/domain/create`, {
    method: 'POST',
    body: JSON.stringify(form),
    headers: {
      'content-type': 'application/json'
    }
  });
  // So processing like this is not elegant, and it can also be uniformly encapsulated, error prompting using notification bar components and so on
  const result = (await res.json()) as { success: boolean; id: string };
  if (result.success) {
    router.reload();
  } else {
    alert('Error! Please try again later');
  }
}

Reusable code can be encapsulated. Refer to the idea of software engineering: high cohesion, low coupling. What I'm pointing out here is a more negative textbook with bloated code and low readability.

Points of Attention

  • Because Tailwind CSS 3 uses a new JIT mechanism, purgecss no longer needs
  • Focus on React performance, such as Hooks like useState, and try to be at the page level, not at the component level (especially those that loop through the generated components)
  • Use useMemo, debounce, and so on to cache, shake, and limit streams to improve application performance
  • Use Next. When working with JS frameworks (or common React applications), in most cases, it's helpful to know more about swr and some of its core ideas

The rest of the code is dull and simple.

Almost that much. Remember to share it!

willin | Cunt. I love you | Project Source

Topics: Front-end Programmer Next.js sso