mirror of
https://github.com/outline/outline.git
synced 2026-06-13 11:25:03 +03:00
chore: resolve no-explicit-any lint warnings in plugins (#12237)
* chore: resolve no-explicit-any lint warnings in plugins Replaces uses of `any` in the plugins directory with concrete types, `unknown`, or structured type assertions, addressing the remaining typescript-eslint(no-explicit-any) warnings flagged by oxlint. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * chore: address review feedback in GitLabIssueProvider Drop trailing semicolon from log string and add early return in `destroyNamespace` when neither `user_id` nor `full_path` is present to avoid an unnecessary full-scan transaction. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,26 @@ import { Integration, IntegrationAuthentication } from "@server/models";
|
||||
import { BaseIssueProvider } from "@server/utils/BaseIssueProvider";
|
||||
import { GitLab } from "./gitlab";
|
||||
import { sequelize } from "@server/storage/database";
|
||||
import { Op } from "sequelize";
|
||||
import { Op, type WhereOptions } from "sequelize";
|
||||
|
||||
interface GitLabWebhookPayload {
|
||||
event_name?: string;
|
||||
old_full_path?: string;
|
||||
old_username?: string;
|
||||
full_path?: string;
|
||||
username?: string;
|
||||
group_id?: string;
|
||||
user_id?: string;
|
||||
project_id?: string | number;
|
||||
project_namespace_id?: string | number;
|
||||
name?: string;
|
||||
path_with_namespace?: string;
|
||||
changes?: { before: string }[];
|
||||
project?: {
|
||||
name: string;
|
||||
path_with_namespace: string;
|
||||
};
|
||||
}
|
||||
|
||||
export class GitLabIssueProvider extends BaseIssueProvider {
|
||||
constructor() {
|
||||
@@ -64,7 +83,8 @@ export class GitLabIssueProvider extends BaseIssueProvider {
|
||||
headers: Record<string, unknown>;
|
||||
}) {
|
||||
const hookId = headers["x-gitlab-webhook-uuid"] as string;
|
||||
const eventName = payload.event_name as string;
|
||||
const typedPayload = payload as GitLabWebhookPayload;
|
||||
const eventName = typedPayload.event_name;
|
||||
|
||||
if (!eventName) {
|
||||
Logger.warn(
|
||||
@@ -77,28 +97,28 @@ export class GitLabIssueProvider extends BaseIssueProvider {
|
||||
case "project_update":
|
||||
case "project_transfer":
|
||||
case "project_rename":
|
||||
await this.updateProject(payload);
|
||||
await this.updateProject(typedPayload);
|
||||
break;
|
||||
case "repository_update":
|
||||
await this.createProject(payload);
|
||||
await this.createProject(typedPayload);
|
||||
break;
|
||||
case "project_destroy":
|
||||
await this.destroyProject(payload);
|
||||
await this.destroyProject(typedPayload);
|
||||
break;
|
||||
case "group_rename":
|
||||
case "user_rename":
|
||||
await this.updateNamespace(payload);
|
||||
await this.updateNamespace(typedPayload);
|
||||
break;
|
||||
case "user_destroy":
|
||||
case "group_destroy":
|
||||
await this.destroyNamespace(payload);
|
||||
await this.destroyNamespace(typedPayload);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async updateNamespace(payload: Record<string, any>) {
|
||||
private async updateNamespace(payload: GitLabWebhookPayload) {
|
||||
const name = payload.old_full_path ?? payload.old_username;
|
||||
const where = {
|
||||
service: IntegrationService.GitLab,
|
||||
@@ -119,6 +139,12 @@ export class GitLabIssueProvider extends BaseIssueProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
const newName = payload.full_path ?? payload.username;
|
||||
if (!newName) {
|
||||
Logger.warn(`GitLab namespace_update event without new name`);
|
||||
return;
|
||||
}
|
||||
|
||||
const sources = integration.issueSources ?? [];
|
||||
const updatedSources = sources.map((source) => {
|
||||
if (source.owner.name === name) {
|
||||
@@ -126,7 +152,7 @@ export class GitLabIssueProvider extends BaseIssueProvider {
|
||||
...source,
|
||||
owner: {
|
||||
id: payload.group_id || source.owner.id,
|
||||
name: payload.full_path ?? payload.username,
|
||||
name: newName,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -139,19 +165,29 @@ export class GitLabIssueProvider extends BaseIssueProvider {
|
||||
});
|
||||
}
|
||||
|
||||
private async destroyNamespace(payload: Record<string, any>) {
|
||||
private async destroyNamespace(payload: GitLabWebhookPayload) {
|
||||
if (!payload.user_id && !payload.full_path) {
|
||||
Logger.warn(
|
||||
`GitLab namespace_destroy event without user_id or full_path`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let replacements = {};
|
||||
const whereCondition: any = {
|
||||
const whereCondition: WhereOptions = {
|
||||
service: IntegrationService.GitLab,
|
||||
...(payload.user_id && {
|
||||
"settings.gitlab.installation.account.id": payload.user_id,
|
||||
}),
|
||||
...(!payload.user_id &&
|
||||
payload.full_path && {
|
||||
[Op.and]: sequelize.literal(
|
||||
`"issueSources"::jsonb @> :jsonCondition`
|
||||
),
|
||||
}),
|
||||
};
|
||||
|
||||
if (payload.user_id) {
|
||||
whereCondition["settings.gitlab.installation.account.id"] =
|
||||
payload.user_id;
|
||||
} else if (payload.full_path) {
|
||||
whereCondition[Op.and] = sequelize.literal(
|
||||
`"issueSources"::jsonb @> :jsonCondition`
|
||||
);
|
||||
if (!payload.user_id && payload.full_path) {
|
||||
replacements = {
|
||||
jsonCondition: JSON.stringify([{ owner: { name: payload.full_path } }]),
|
||||
};
|
||||
@@ -187,7 +223,7 @@ export class GitLabIssueProvider extends BaseIssueProvider {
|
||||
});
|
||||
}
|
||||
|
||||
private async destroyProject(payload: Record<string, any>) {
|
||||
private async destroyProject(payload: GitLabWebhookPayload) {
|
||||
await sequelize.transaction(async (transaction) => {
|
||||
const integrations = await Integration.findAll({
|
||||
where: {
|
||||
@@ -223,14 +259,20 @@ export class GitLabIssueProvider extends BaseIssueProvider {
|
||||
});
|
||||
}
|
||||
|
||||
private async createProject(payload: Record<string, any>) {
|
||||
const createEvent = payload.changes.some((p: { before: string }) =>
|
||||
private async createProject(payload: GitLabWebhookPayload) {
|
||||
const createEvent = payload.changes?.some((p: { before: string }) =>
|
||||
/^0{40}$/.test(p.before)
|
||||
);
|
||||
|
||||
if (!createEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const project = payload.project;
|
||||
if (!project || !payload.project_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
await sequelize.transaction(async (transaction) => {
|
||||
const integration = (await Integration.findOne({
|
||||
where: {
|
||||
@@ -245,7 +287,6 @@ export class GitLabIssueProvider extends BaseIssueProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
const project = payload.project;
|
||||
const owner = {
|
||||
id: "", // namespace.id is not provided in this webhook payload
|
||||
name: project.path_with_namespace.split("/").slice(0, -1).join("/"),
|
||||
@@ -264,7 +305,13 @@ export class GitLabIssueProvider extends BaseIssueProvider {
|
||||
});
|
||||
}
|
||||
|
||||
private async updateProject(payload: Record<string, any>) {
|
||||
private async updateProject(payload: GitLabWebhookPayload) {
|
||||
if (!payload.name || !payload.path_with_namespace) {
|
||||
return;
|
||||
}
|
||||
const newName = payload.name;
|
||||
const pathWithNamespace = payload.path_with_namespace;
|
||||
|
||||
await sequelize.transaction(async (transaction) => {
|
||||
const integrations = await Integration.findAll({
|
||||
where: {
|
||||
@@ -293,8 +340,8 @@ export class GitLabIssueProvider extends BaseIssueProvider {
|
||||
);
|
||||
|
||||
if (source) {
|
||||
source.name = payload.name;
|
||||
source.owner.name = payload.path_with_namespace
|
||||
source.name = newName;
|
||||
source.owner.name = pathWithNamespace
|
||||
.split("/")
|
||||
.slice(0, -1)
|
||||
.join("/");
|
||||
|
||||
@@ -46,7 +46,14 @@ class Iframely {
|
||||
return { error: data.error } as UnfurlError; // In addition to our custom UnfurlError, sometimes iframely returns error in the response body.
|
||||
}
|
||||
|
||||
const parsedData = data as Record<string, any>;
|
||||
const parsedData = data as {
|
||||
url: string;
|
||||
meta: { title: string; description: string; site: string };
|
||||
links: {
|
||||
thumbnail?: { href: string }[];
|
||||
icon?: { href: string }[];
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
type: UnfurlResourceType.URL,
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import type { Request } from "express";
|
||||
import { HttpsProxyAgent } from "https-proxy-agent";
|
||||
import type OAuth2Strategy from "passport-oauth2";
|
||||
import { Strategy } from "passport-oauth2";
|
||||
|
||||
interface AuthenticateOptions {
|
||||
originalQuery?: Request["query"];
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export class OIDCStrategy extends Strategy {
|
||||
constructor(
|
||||
options: OAuth2Strategy.StrategyOptionsWithRequest,
|
||||
@@ -15,12 +21,12 @@ export class OIDCStrategy extends Strategy {
|
||||
}
|
||||
}
|
||||
|
||||
authenticate(req: any, options: any) {
|
||||
authenticate(req: Request, options: AuthenticateOptions) {
|
||||
options.originalQuery = req.query;
|
||||
super.authenticate(req, options);
|
||||
}
|
||||
|
||||
authorizationParams(options: any) {
|
||||
authorizationParams(options: AuthenticateOptions) {
|
||||
return {
|
||||
...options.originalQuery,
|
||||
...super.authorizationParams?.(options),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { startRegistration } from "@simplewebauthn/browser";
|
||||
import type { JSONObject } from "@shared/types";
|
||||
import { observer } from "mobx-react";
|
||||
import { KeyIcon, PlusIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
@@ -66,9 +67,13 @@ function PasskeysSettings() {
|
||||
}
|
||||
);
|
||||
const attResp = await startRegistration(resp.data);
|
||||
await client.post("/passkeys.verifyRegistration", attResp as any, {
|
||||
baseUrl: "/auth",
|
||||
});
|
||||
await client.post(
|
||||
"/passkeys.verifyRegistration",
|
||||
attResp as unknown as JSONObject,
|
||||
{
|
||||
baseUrl: "/auth",
|
||||
}
|
||||
);
|
||||
toast.success(t("Passkey added successfully"));
|
||||
await loadPasskeys();
|
||||
} catch (err) {
|
||||
|
||||
@@ -8,19 +8,20 @@ describe("getExpectedOrigin", () => {
|
||||
hostname: string;
|
||||
host: string;
|
||||
forwardedPort?: string;
|
||||
}): APIContext => ({
|
||||
protocol: options.protocol,
|
||||
request: {
|
||||
hostname: options.hostname,
|
||||
host: options.host,
|
||||
get: (header: string) => {
|
||||
if (header === "X-Forwarded-Port" && options.forwardedPort) {
|
||||
return options.forwardedPort;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
} as unknown,
|
||||
}) as unknown as APIContext;
|
||||
}): APIContext =>
|
||||
({
|
||||
protocol: options.protocol,
|
||||
request: {
|
||||
hostname: options.hostname,
|
||||
host: options.host,
|
||||
get: (header: string) => {
|
||||
if (header === "X-Forwarded-Port" && options.forwardedPort) {
|
||||
return options.forwardedPort;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
} as unknown,
|
||||
}) as unknown as APIContext;
|
||||
|
||||
it("should construct origin with non-standard HTTPS port from X-Forwarded-Port", () => {
|
||||
const ctx = createMockContext({
|
||||
|
||||
@@ -236,13 +236,13 @@ export default class PostgresSearchProvider extends BaseSearchProvider {
|
||||
where,
|
||||
limit,
|
||||
offset,
|
||||
}) as any as Promise<RankedDocument[]>;
|
||||
}) as unknown as Promise<RankedDocument[]>;
|
||||
|
||||
const countQuery = Document.unscoped().count({
|
||||
// @ts-expect-error Types are incorrect for count
|
||||
replacements: findOptions.replacements,
|
||||
where,
|
||||
}) as any as Promise<number>;
|
||||
}) as unknown as Promise<number>;
|
||||
const [results, count] = await Promise.all([resultsQuery, countQuery]);
|
||||
|
||||
// Final query to get associated document data
|
||||
@@ -428,7 +428,7 @@ export default class PostgresSearchProvider extends BaseSearchProvider {
|
||||
where,
|
||||
limit,
|
||||
offset,
|
||||
})) as any as RankedDocument[];
|
||||
})) as unknown as RankedDocument[];
|
||||
|
||||
const countQuery = Document.unscoped().count({
|
||||
// @ts-expect-error Types are incorrect for count
|
||||
@@ -436,7 +436,7 @@ export default class PostgresSearchProvider extends BaseSearchProvider {
|
||||
include,
|
||||
replacements: findOptions.replacements,
|
||||
where,
|
||||
}) as any as Promise<number>;
|
||||
}) as unknown as Promise<number>;
|
||||
|
||||
// Final query to get associated document data
|
||||
const [documents, count] = await Promise.all([
|
||||
|
||||
@@ -238,7 +238,8 @@ router.post(
|
||||
return;
|
||||
}
|
||||
|
||||
const { results, total } = await SearchProviderManager.getProvider().searchForUser(user, options);
|
||||
const { results, total } =
|
||||
await SearchProviderManager.getProvider().searchForUser(user, options);
|
||||
|
||||
await SearchQuery.create({
|
||||
userId: user ? user.id : null,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import querystring from "node:querystring";
|
||||
import querystring, { type ParsedUrlQueryInput } from "node:querystring";
|
||||
import { InvalidRequestError } from "@server/errors";
|
||||
import fetch from "@server/utils/fetch";
|
||||
import { SlackUtils } from "../shared/SlackUtils";
|
||||
@@ -13,7 +13,10 @@ const SLACK_API_URL = "https://slack.com/api";
|
||||
* @param body - the request body containing token and other parameters.
|
||||
* @returns the parsed JSON response from Slack.
|
||||
*/
|
||||
export async function post(endpoint: string, body: Record<string, any>) {
|
||||
export async function post(
|
||||
endpoint: string,
|
||||
body: { token: string } & Record<string, unknown>
|
||||
) {
|
||||
let data;
|
||||
const { token, ...bodyWithoutToken } = body;
|
||||
|
||||
@@ -44,7 +47,10 @@ export async function post(endpoint: string, body: Record<string, any>) {
|
||||
* @param body - the request parameters.
|
||||
* @returns the parsed JSON response from Slack.
|
||||
*/
|
||||
export async function request(endpoint: string, body: Record<string, any>) {
|
||||
export async function request(
|
||||
endpoint: string,
|
||||
body: { client_id?: string; client_secret?: string } & ParsedUrlQueryInput
|
||||
) {
|
||||
let data;
|
||||
const { client_id, client_secret, ...params } = body;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ export class SlackUtils {
|
||||
static createState(
|
||||
teamId: string,
|
||||
type: IntegrationType,
|
||||
data?: Record<string, any>
|
||||
data?: Record<string, unknown>
|
||||
) {
|
||||
return JSON.stringify({ type, teamId, ...data });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user