A11yWatch/a11ywatch-core

View on GitHub
src/core/mutations.ts

Summary

Maintainability
C
1 day
Test Coverage
import { RATE_EXCEEDED_ERROR, EMAIL_NEEDS_CONFIRMATION } from "./strings";
import {
  updateUser,
  addWebsite,
  addPaymentSubscription,
  cancelSubscription,
  filterEmailDates,
  login,
  register,
  logout,
} from "./graph/mutations";
import { watcherCrawl } from "./actions/accessibility/watcher_crawl";
import { scanWebsite, crawlPage } from "../core/actions";
import { gqlRateLimiter } from "../web/limiters/scan";
import { getWebsite, WebsitesController } from "./controllers/websites";
import { websiteFormatter } from "./utils/shapes/website-gql";
import { UsersController } from "./controllers";
import { SUPER_MODE } from "../config/config";
import { CRAWLER_COMMENCED } from "./strings/success";
import { crawlingSet, getKey } from "../event/crawl-tracking";
import { StatusCode } from "../web/messages/message";

const defaultPayload = {
  keyid: undefined,
  audience: undefined,
};

// TODO: move to limiter control file
const scanRateLimitConfig = {
  max: 2,
  window: "10s",
};

const defaultScanLimit = {
  max: 3,
  window: "60s",
};

// [Deprecated]: Move all to OpenAPI | gRPC
export const Mutation = {
  updateUser,
  login,
  register,
  logout,
  addWebsite,
  crawlWebsite: async (_, { url }, context) => {
    if (!url) {
      return {
        website: null,
        code: StatusCode.NotFound,
        success: true,
        message: "A valid Url is required.",
      };
    }

    const { keyid } = context.user?.payload || defaultPayload;

    const [canScan, u] = !SUPER_MODE
      ? await UsersController().updateScanAttempt({
          userId: keyid,
        })
      : [true];

    if (canScan) {
      const [website] = await getWebsite({ userId: keyid, url });

      if (crawlingSet.has(getKey(url, [], keyid))) {
        return {
          website: null,
          code: StatusCode.Accepted,
          success: true,
          message: "Scan already in progress...",
        };
      }

      const { subdomains, tld, ua, proxy, sitemap } = website ?? {};

      await watcherCrawl({
        url: url,
        userId: keyid,
        subdomains: subdomains,
        tld: tld,
        scan: true,
        agent: ua,
        proxy: proxy,
        sitemap: !!sitemap,
      });

      return {
        website: null,
        code: 200,
        success: true,
        message: CRAWLER_COMMENCED,
      };
    } else {
      throw new Error(
        u?.emailConfirmed ? RATE_EXCEEDED_ERROR : EMAIL_NEEDS_CONFIRMATION
      );
    }
  },
  // run scans to the pagemind -> browser -> mav -> api
  scanWebsite: async (parent, args, context, info) => {
    const { url } = args;
    const { keyid } = context.user?.payload || defaultPayload;
    const unauth = typeof keyid === "undefined";

    const rateLimitConfig = !unauth ? defaultScanLimit : scanRateLimitConfig;

    let errorMessage;

    if (!SUPER_MODE) {
      const [canScan, u] = await UsersController().updateScanAttempt({
        userId: keyid,
      });

      // if the request did not come from the server update api usage
      if (!canScan) {
        errorMessage = u.emailConfirmed
          ? RATE_EXCEEDED_ERROR
          : EMAIL_NEEDS_CONFIRMATION;
      }

      // check rate limits for request. TODO: adjust r
      if (!errorMessage) {
        // apply rate limit on un-auth.
        errorMessage = await gqlRateLimiter(
          {
            parent,
            args,
            context,
            info,
          },
          rateLimitConfig
        );
      }

      if (errorMessage) {
        throw new Error(errorMessage);
      }
    }

    let data = {};

    if (!unauth) {
      data = await crawlPage(
        {
          url,
          userId: keyid,
          sendSub: true,
        },
        false
      );
    } else {
      data = await scanWebsite({
        url,
        noStore: true,
      });
    }

    return websiteFormatter(data);
  },
  removeWebsite: async (_, { url, deleteMany = false }, context) => {
    const { keyid } = context.user?.payload || defaultPayload;

    const websiteRemoved = await WebsitesController().removeWebsite({
      userId: keyid,
      url,
      deleteMany,
    });

    if (websiteRemoved && deleteMany) {
      return {
        ...websiteRemoved,
        url: `Success all websites and related items deleted.`,
        id: 0,
      };
    }

    return websiteRemoved;
  },
  updateWebsite: async (
    _,
    {
      userId,
      url,
      customHeaders,
      pageInsights,
      mobile,
      standard,
      ua,
      robots,
      subdomains,
      tld,
      ignore,
      rules,
      runners,
      proxy,
      sitemap,
      monitoringEnabled,
    },
    context
  ) => {
    const { keyid, audience } = context.user?.payload || defaultPayload;
    const featsEnabled = SUPER_MODE || audience;

    return await WebsitesController().updateWebsite({
      userId: userId || keyid,
      url,
      pageHeaders: customHeaders,
      pageInsights,
      mobile,
      standard,
      ua,
      robots,
      ignore,
      rules,
      runners,
      tld: featsEnabled ? tld : false,
      subdomains: featsEnabled ? subdomains : false,
      proxy: featsEnabled ? proxy : undefined,
      sitemap: featsEnabled ? sitemap : false,
      monitoringEnabled,
    });
  },
  forgotPassword: async (_, { email }, _context) => {
    return await UsersController().forgotPassword({
      email,
    });
  },
  confirmEmail: async (_, { email }, context) => {
    const { keyid } = context.user?.payload || defaultPayload;

    return await UsersController().confirmEmail({
      email,
      keyid,
    });
  },
  resetPassword: async (_, { email, resetCode }, _context) => {
    return await UsersController().resetPassword({
      email,
      resetCode,
    });
  },
  toggleAlert: async (_, { alertEnabled }, context) => {
    const { keyid } = context.user?.payload || defaultPayload;

    return await UsersController().toggleAlert({
      keyid,
      alertEnabled,
    });
  },
  sortWebsites: async (_, { order }, context) => {
    const { keyid } = context.user?.payload || defaultPayload;

    return await WebsitesController().sortWebsites({
      userId: keyid,
      order,
    });
  },
  setPageSpeedKey: async (_, { pageSpeedApiKey }, context) => {
    return await UsersController().setPageSpeedKey({
      id: context.user?.payload?.keyid,
      pageSpeedApiKey,
    });
  },
  addPaymentSubscription,
  cancelSubscription,
  filterEmailDates,
};