Kareadita/Kavita · warning · KavitaException

external-source-required

Error message

external-source-required

What it means

Thrown by StreamService.CreateExternalSource when dto.Name is null or empty. Note the mismatch: the localization key 'external-source-required' renders as 'Host is required', but the guard actually checks the Name field, not the Host (the Host-format check is a separate throw). It is a localized KavitaException surfaced as HTTP 500.

Source

Thrown at Kavita.Services/StreamService.cs:297

        await unitOfWork.CommitAsync(ct);
        if (!stream.Visible) return;
        await eventHub.SendMessageToAsync(MessageFactory.SideNavUpdate, MessageFactory.SideNavUpdateEvent(userId),
            userId, ct);
    }

    public async Task<ExternalSourceDto> CreateExternalSource(int userId, ExternalSourceDto dto,
        CancellationToken ct = default)
    {
        var user = await unitOfWork.UserRepository.GetUserByIdAsync(userId,
            AppUserIncludes.ExternalSources, ct);
        if (user == null) throw new KavitaException("not-authenticated");

        if (user.ExternalSources.Any(s => s.Host == dto.Host))
        {
            throw new KavitaException("external-source-already-exists");
        }

        if (string.IsNullOrEmpty(dto.Name)) throw new KavitaException("external-source-required");
        if (!UrlHelper.StartsWithHttpOrHttps(dto.Host)) throw new KavitaException("external-source-host-format");


        var newSource = new AppUserExternalSource()
        {
            Name = dto.Name,
            Host = UrlHelper.EnsureEndsWithSlash(UrlHelper.EnsureStartsWithHttpOrHttps(dto.Host)),
            ApiKey = dto.ApiKey
        };
        user.ExternalSources.Add(newSource);

        unitOfWork.UserRepository.Update(user);
        await unitOfWork.CommitAsync(ct);

        dto.Id = newSource.Id;

        return dto;
    }

View on GitHub (pinned to 9c3e540000)

Solutions

  1. Make the Name field required in the form and disable Save until it is non-empty.
  2. Validate dto.name is a non-empty string before posting.
  3. Fix the misleading message server-side if the requirement truly is Name (or add a Host emptiness check to match the text).

Example fix

// before
save(dto: ExternalSourceDto) { return this.http.post('streams/create-external-source', dto); }
// after
save(dto: ExternalSourceDto) {
  if (!dto?.name?.trim()) return throwError(() => new Error('Name is required'));
  return this.http.post('streams/create-external-source', dto);
}
Defensive patterns

Strategy: validation

Validate before calling

function hasExternalSourceName(dto: ExternalSourceDto): boolean {
  return !!dto?.name?.trim();
}

Type guard

function isExternalSourceDtoWith(o: unknown): o is ExternalSourceDto {
  return typeof o === 'object' && o !== null && typeof (o as any).name === 'string' && (o as any).name.trim().length > 0;
}

Prevention

When it happens

Trigger: POST /api/streams/create-external-source with a dto whose Name is null, empty, or whitespace-only (the string.IsNullOrEmpty(dto.Name) branch).

Common situations: User fills in the Host/ApiKey but leaves the Name field blank; the form posts an incomplete object; a client bug drops the name property during serialization.

Related errors


AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13). Data as JSON: /api/errors/d10212649238f003. Report an issue: GitHub.