Repository: strapi/strapi
File: packages/core/admin/server/src/controllers/admin.ts (TypeScript)

Code before:
    return data;
  },

  async getGuidedTourMeta(ctx: Context) {
    const userService = getService('user');
    const { id } = ctx.query as GetGuidedTourMeta.Request['query'];

    const isFirstSuperAdminUser = id ? await userService.isFirstSuperAdminUser(id) : false;

    // Check if any content-type schemas have been create on the api:: namespace
    const contentTypeSchemaNames = Object.keys(strapi.contentTypes).filter((contentTypeUid) =>
      contentTypeUid.startsWith('api::')
    );
    const didCreateContentTypeSchema = contentTypeSchemaNames.length > 0;

    // Check if any content has been created for content-types on the api:: namespace
    let hasContent = false;
    for (const contentTypeSchemaName of contentTypeSchemaNames) {
      const docs = await strapi
        .documents(contentTypeSchemaName as Internal.UID.ContentType)
        .count({});
      if (docs > 0) {
        hasContent = true;
        break;
      }
    }
    const didCreateContent = didCreateContentTypeSchema && hasContent;

    // Check if any api tokens have been created besides the default ones
    const DEFAULT_API_TOKENS = ['Read Only', 'Full Access'];
    const apiTokenNames = (await strapi.documents('admin::api-token').findMany()).map(
      (token) => token.name
    );
    const didCreateApiToken = apiTokenNames.some((name) => !DEFAULT_API_TOKENS.includes(name));

    return {
      data: {
        isFirstSuperAdminUser,
        didCreateContentTypeSchema,
        didCreateContent,
        didCreateApiToken,
      },
    } satisfies GetGuidedTourMeta.Response;
  },
};


Review comment (suggestion):
I think we should try to follow our more functional style habites. Maybe use Array.some instead? (You may have picked your current approach to be able to break out of the loop early but I wonder if you tried different things)

Code after:
import type { Context } from 'koa';

import path from 'path';

import { map, values, sumBy, pipe, flatMap, propEq } from 'lodash/fp';
import _ from 'lodash';
import { exists } from 'fs-extra';
import { env } from '@strapi/utils';
import tsUtils from '@strapi/typescript-utils';
import {
  validateUpdateProjectSettings,
  validateUpdateProjectSettingsFiles,
  validateUpdateProjectSettingsImagesDimensions,
} from '../validation/project-settings';
import { getService } from '../utils';

import type {
  Init,
  GetProjectSettings,
  Information,
  Plugins,
  TelemetryProperties,
  UpdateProjectSettings,
  GetGuidedTourMeta,
} from '../../../shared/contracts/admin';

const { isUsingTypeScript } = tsUtils;

/**
 * A set of functions called "actions" for `Admin`
 */
export default {
  // TODO very temporary to check the switch ee/ce
  // When removing this we need to update the /admin/src/index.js file
  // whe,re we set the strapi.window.isEE value

  // NOTE: admin/ee/server overrides this controller, and adds the EE features
  // This returns an empty feature list for CE
  async getProjectType() {
    const flags = strapi.config.get('admin.flags', {});
    return { data: { isEE: false, features: [], flags } };
  },

  async init() {
    let uuid = strapi.config.get('uuid', false);
    const hasAdmin = await getService('user').exists();
    const { menuLogo, authLogo } = await getService('project-settings').getProjectSettings();
    // set to null if telemetryDisabled flag not avaialble in package.json
    const telemetryDisabled: boolean | null = strapi.config.get(
      'packageJsonStrapi.telemetryDisabled',
      null
    );

    if (telemetryDisabled !== null && telemetryDisabled === true) {
      uuid = false;
    }

    return {
      data: {
        uuid,
        hasAdmin,
        menuLogo: menuLogo ? menuLogo.url : null,
        authLogo: authLogo ? authLogo.url : null,
      },
    } satisfies Init.Response;
  },

  async getProjectSettings() {
    return getService(
      'project-settings'
    ).getProjectSettings() satisfies Promise<GetProjectSettings.Response>;
  },

  async updateProjectSettings(ctx: Context) {
    const {
      request: { files, body },
    } = ctx as { request: UpdateProjectSettings.Request };

    const projectSettingsService = getService('project-settings');

    await validateUpdateProjectSettings(body);
    await validateUpdateProjectSettingsFiles(files);

    const formatedFiles = await projectSettingsService.parseFilesData(files);
    await validateUpdateProjectSettingsImagesDimensions(formatedFiles);

    return projectSettingsService.updateProjectSettings({
      ...body,
      ...formatedFiles,
    }) satisfies Promise<UpdateProjectSettings.Response>;
  },

  async telemetryProperties(ctx: Context) {
    // If the telemetry is disabled, ignore the request and return early
    if (strapi.telemetry.isDisabled) {
      ctx.status = 204;
      return;
    }

    const useTypescriptOnServer = await isUsingTypeScript(strapi.dirs.app.root);
    const useTypescriptOnAdmin = await isUsingTypeScript(
      path.join(strapi.dirs.app.root, 'src', 'admin')
    );
    const isHostedOnStrapiCloud = env('STRAPI_HOSTING', null) === 'strapi.cloud';

    const numberOfAllContentTypes = _.size(strapi.contentTypes);
    const numberOfComponents = _.size(strapi.components);

    const getNumberOfDynamicZones = () => {
      return pipe(
        map('attributes'),
        flatMap(values),
        // @ts-expect-error lodash types
        sumBy(propEq('type', 'dynamiczone'))
      )(strapi.contentTypes as any);
    };

    return {
      data: {
        useTypescriptOnServer,
        useTypescriptOnAdmin,
        isHostedOnStrapiCloud,
        numberOfAllContentTypes, // TODO: V5: This event should be renamed numberOfContentTypes in V5 as the name is already taken to describe the number of content types using i18n.
        numberOfComponents,
        numberOfDynamicZones: getNumberOfDynamicZones(),
      },
    } satisfies TelemetryProperties.Response;
  },

  async information() {
    const currentEnvironment: string = strapi.config.get('environment');
    const autoReload = strapi.config.get('autoReload', false);
    const strapiVersion = strapi.config.get('info.strapi', null);
    const dependencies = strapi.config.get('info.dependencies', {});
    const projectId = strapi.config.get('uuid', null);
    const nodeVersion = process.version;
    const communityEdition = !strapi.EE;
    const useYarn: boolean = await exists(path.join(process.cwd(), 'yarn.lock'));

    return {
      data: {
        currentEnvironment,
        autoReload,
        strapiVersion,
        dependencies,
        projectId,
        nodeVersion,
        communityEdition,
        useYarn,
      },
    } satisfies Information.Response;
  },

  async plugins(ctx: Context) {
    const enabledPlugins = strapi.config.get('enabledPlugins') as any;

    // List of core plugins that are always enabled,
    // and so it's not necessary to display them in the plugins list
    const CORE_PLUGINS = [
      'content-manager',
      'content-type-builder',
      'email',
      'upload',
      'i18n',
      'content-releases',
      'review-workflows',
    ];

    const plugins = Object.entries(enabledPlugins)
      .filter(([key]: any) => !CORE_PLUGINS.includes(key))
      .map(([key, plugin]: any) => ({
        name: plugin.info.name || key,
        displayName: plugin.info.displayName || plugin.info.name || key,
        description: plugin.info.description || '',
        packageName: plugin.info.packageName,
      }));

    ctx.send({ plugins }) satisfies Plugins.Response;
  },

  async licenseTrialTimeLeft() {
    const data = await strapi.ee.getTrialEndDate({
      strapi,
    });

    return data;
  },

  async getGuidedTourMeta(ctx: Context) {
    const [isFirstSuperAdminUser, completedActions] = await Promise.all([
      getService('user').isFirstSuperAdminUser(ctx.state.user.id),
      getService('guided-tour').getCompletedActions(),
    ]);

    return {
      data: {
        isFirstSuperAdminUser,
        completedActions,
      },
    } satisfies GetGuidedTourMeta.Response;
  },
};