mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-03 17:26:07 +00:00
Merge pull request #2242 from akhilmhdh/fix/tag-filter-secret-api
Tag based filtering for secret endpoint
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
const hasNameField = await knex.schema.hasColumn(TableName.SecretTag, "name");
|
||||||
|
if (hasNameField) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretTag, (t) => {
|
||||||
|
t.dropColumn("name");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
const hasNameField = await knex.schema.hasColumn(TableName.SecretTag, "name");
|
||||||
|
if (!hasNameField) {
|
||||||
|
await knex.schema.alterTable(TableName.SecretTag, (t) => {
|
||||||
|
t.string("name");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,6 @@ import { TImmutableDBKeys } from "./models";
|
|||||||
|
|
||||||
export const SecretTagsSchema = z.object({
|
export const SecretTagsSchema = z.object({
|
||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
name: z.string(),
|
|
||||||
slug: z.string(),
|
slug: z.string(),
|
||||||
color: z.string().nullable().optional(),
|
color: z.string().nullable().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
|
|||||||
+6
-8
@@ -88,8 +88,7 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||||
db.ref("id").withSchema(TableName.SecretApprovalRequestSecretTag).as("tagJnId"),
|
db.ref("id").withSchema(TableName.SecretApprovalRequestSecretTag).as("tagJnId"),
|
||||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
|
||||||
)
|
)
|
||||||
.select(
|
.select(
|
||||||
db.ref("secretBlindIndex").withSchema(TableName.Secret).as("orgSecBlindIndex"),
|
db.ref("secretBlindIndex").withSchema(TableName.Secret).as("orgSecBlindIndex"),
|
||||||
@@ -124,9 +123,9 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagJnId",
|
key: "tagJnId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color }) => ({
|
mapper: ({ tagId: id, tagSlug: slug, tagColor: color }) => ({
|
||||||
id,
|
id,
|
||||||
name,
|
name: slug,
|
||||||
slug,
|
slug,
|
||||||
color
|
color
|
||||||
})
|
})
|
||||||
@@ -269,8 +268,7 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||||
db.ref("id").withSchema(TableName.SecretApprovalRequestSecretTagV2).as("tagJnId"),
|
db.ref("id").withSchema(TableName.SecretApprovalRequestSecretTagV2).as("tagJnId"),
|
||||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
|
||||||
)
|
)
|
||||||
.select(
|
.select(
|
||||||
db.ref("version").withSchema(TableName.SecretV2).as("orgSecVersion"),
|
db.ref("version").withSchema(TableName.SecretV2).as("orgSecVersion"),
|
||||||
@@ -292,9 +290,9 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagJnId",
|
key: "tagJnId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color }) => ({
|
mapper: ({ tagId: id, tagSlug: slug, tagColor: color }) => ({
|
||||||
id,
|
id,
|
||||||
name,
|
name: slug,
|
||||||
slug,
|
slug,
|
||||||
color
|
color
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -100,8 +100,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||||
db.ref("id").withSchema(TableName.SecretVersionTag).as("tagVersionId"),
|
db.ref("id").withSchema(TableName.SecretVersionTag).as("tagVersionId"),
|
||||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
|
||||||
);
|
);
|
||||||
return sqlNestRelationships({
|
return sqlNestRelationships({
|
||||||
data,
|
data,
|
||||||
@@ -132,9 +131,9 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagVersionId",
|
key: "tagVersionId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
mapper: ({ tagId: id, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
||||||
id,
|
id,
|
||||||
name,
|
name: slug,
|
||||||
slug,
|
slug,
|
||||||
color,
|
color,
|
||||||
vId
|
vId
|
||||||
@@ -195,8 +194,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||||
db.ref("id").withSchema(TableName.SecretVersionV2Tag).as("tagVersionId"),
|
db.ref("id").withSchema(TableName.SecretVersionV2Tag).as("tagVersionId"),
|
||||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
|
||||||
);
|
);
|
||||||
return sqlNestRelationships({
|
return sqlNestRelationships({
|
||||||
data,
|
data,
|
||||||
@@ -227,9 +225,9 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagVersionId",
|
key: "tagVersionId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
mapper: ({ tagId: id, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
||||||
id,
|
id,
|
||||||
name,
|
name: slug,
|
||||||
slug,
|
slug,
|
||||||
color,
|
color,
|
||||||
vId
|
vId
|
||||||
@@ -353,8 +351,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||||
db.ref("id").withSchema(TableName.SecretVersionTag).as("tagVersionId"),
|
db.ref("id").withSchema(TableName.SecretVersionTag).as("tagVersionId"),
|
||||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const formated = sqlNestRelationships({
|
const formated = sqlNestRelationships({
|
||||||
@@ -377,9 +374,9 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagVersionId",
|
key: "tagVersionId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
mapper: ({ tagId: id, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
||||||
id,
|
id,
|
||||||
name,
|
name: slug,
|
||||||
slug,
|
slug,
|
||||||
color,
|
color,
|
||||||
vId
|
vId
|
||||||
@@ -508,8 +505,7 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
|||||||
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
db.ref("id").withSchema(TableName.SecretTag).as("tagId"),
|
||||||
db.ref("id").withSchema(TableName.SecretVersionV2Tag).as("tagVersionId"),
|
db.ref("id").withSchema(TableName.SecretVersionV2Tag).as("tagVersionId"),
|
||||||
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
db.ref("color").withSchema(TableName.SecretTag).as("tagColor"),
|
||||||
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"),
|
db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")
|
||||||
db.ref("name").withSchema(TableName.SecretTag).as("tagName")
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const formated = sqlNestRelationships({
|
const formated = sqlNestRelationships({
|
||||||
@@ -532,9 +528,9 @@ export const snapshotDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagVersionId",
|
key: "tagVersionId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
mapper: ({ tagId: id, tagSlug: slug, tagColor: color, tagVersionId: vId }) => ({
|
||||||
id,
|
id,
|
||||||
name,
|
name: slug,
|
||||||
slug,
|
slug,
|
||||||
color,
|
color,
|
||||||
vId
|
vId
|
||||||
|
|||||||
@@ -596,7 +596,8 @@ export const RAW_SECRETS = {
|
|||||||
"The slug of the project to list secrets from. This parameter is only applicable by machine identities.",
|
"The slug of the project to list secrets from. This parameter is only applicable by machine identities.",
|
||||||
environment: "The slug of the environment to list secrets from.",
|
environment: "The slug of the environment to list secrets from.",
|
||||||
secretPath: "The secret path to list secrets from.",
|
secretPath: "The secret path to list secrets from.",
|
||||||
includeImports: "Weather to include imported secrets or not."
|
includeImports: "Weather to include imported secrets or not.",
|
||||||
|
tagSlugs: "The comma separated tag slugs to filter secrets"
|
||||||
},
|
},
|
||||||
CREATE: {
|
CREATE: {
|
||||||
secretName: "The name of the secret to create.",
|
secretName: "The name of the secret to create.",
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { SecretTagsSchema } from "@app/db/schemas";
|
import { SecretTagsSchema } from "@app/db/schemas";
|
||||||
@@ -49,7 +50,8 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
workspaceTag: SecretTagsSchema
|
// akhilmhdh: for terraform backward compatiability
|
||||||
|
workspaceTag: SecretTagsSchema.extend({ name: z.string() })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -79,7 +81,8 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
workspaceTag: SecretTagsSchema
|
// akhilmhdh: for terraform backward compatiability
|
||||||
|
workspaceTag: SecretTagsSchema.extend({ name: z.string() })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -108,8 +111,14 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
|
|||||||
projectId: z.string().trim().describe(SECRET_TAGS.CREATE.projectId)
|
projectId: z.string().trim().describe(SECRET_TAGS.CREATE.projectId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim().describe(SECRET_TAGS.CREATE.name),
|
slug: z
|
||||||
slug: z.string().trim().describe(SECRET_TAGS.CREATE.slug),
|
.string()
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.describe(SECRET_TAGS.CREATE.slug)
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Invalid slug. Slug can only contain alphanumeric characters and hyphens."
|
||||||
|
}),
|
||||||
color: z.string().trim().describe(SECRET_TAGS.CREATE.color)
|
color: z.string().trim().describe(SECRET_TAGS.CREATE.color)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@@ -144,8 +153,14 @@ export const registerSecretTagRouter = async (server: FastifyZodProvider) => {
|
|||||||
tagId: z.string().trim().describe(SECRET_TAGS.UPDATE.tagId)
|
tagId: z.string().trim().describe(SECRET_TAGS.UPDATE.tagId)
|
||||||
}),
|
}),
|
||||||
body: z.object({
|
body: z.object({
|
||||||
name: z.string().trim().describe(SECRET_TAGS.UPDATE.name),
|
slug: z
|
||||||
slug: z.string().trim().describe(SECRET_TAGS.UPDATE.slug),
|
.string()
|
||||||
|
.toLowerCase()
|
||||||
|
.trim()
|
||||||
|
.describe(SECRET_TAGS.UPDATE.slug)
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Invalid slug. Slug can only contain alphanumeric characters and hyphens."
|
||||||
|
}),
|
||||||
color: z.string().trim().describe(SECRET_TAGS.UPDATE.color)
|
color: z.string().trim().describe(SECRET_TAGS.UPDATE.color)
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
|
|||||||
@@ -59,9 +59,10 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
tags: SecretTagsSchema.pick({
|
tags: SecretTagsSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
name: true,
|
|
||||||
color: true
|
color: true
|
||||||
}).array()
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -116,16 +117,15 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
secret: SecretsSchema.omit({ secretBlindIndex: true }).merge(
|
secret: SecretsSchema.omit({ secretBlindIndex: true }).extend({
|
||||||
z.object({
|
tags: SecretTagsSchema.pick({
|
||||||
tags: SecretTagsSchema.pick({
|
id: true,
|
||||||
id: true,
|
slug: true,
|
||||||
slug: true,
|
color: true
|
||||||
name: true,
|
|
||||||
color: true
|
|
||||||
}).array()
|
|
||||||
})
|
})
|
||||||
)
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -180,7 +180,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
.enum(["true", "false"])
|
.enum(["true", "false"])
|
||||||
.default("false")
|
.default("false")
|
||||||
.transform((value) => value === "true")
|
.transform((value) => value === "true")
|
||||||
.describe(RAW_SECRETS.LIST.includeImports)
|
.describe(RAW_SECRETS.LIST.includeImports),
|
||||||
|
tagSlugs: z
|
||||||
|
.string()
|
||||||
|
.describe(RAW_SECRETS.LIST.tagSlugs)
|
||||||
|
.optional()
|
||||||
|
// split by comma and trim the strings
|
||||||
|
.transform((el) => (el ? el.split(",").map((i) => i.trim()) : []))
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
@@ -190,9 +196,9 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
tags: SecretTagsSchema.pick({
|
tags: SecretTagsSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
name: true,
|
|
||||||
color: true
|
color: true
|
||||||
})
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
.array()
|
.array()
|
||||||
.optional()
|
.optional()
|
||||||
})
|
})
|
||||||
@@ -251,7 +257,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
projectId: workspaceId,
|
projectId: workspaceId,
|
||||||
path: secretPath,
|
path: secretPath,
|
||||||
includeImports: req.query.include_imports,
|
includeImports: req.query.include_imports,
|
||||||
recursive: req.query.recursive
|
recursive: req.query.recursive,
|
||||||
|
tagSlugs: req.query.tagSlugs
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@@ -325,9 +332,9 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
tags: SecretTagsSchema.pick({
|
tags: SecretTagsSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
name: true,
|
|
||||||
color: true
|
color: true
|
||||||
})
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
.array()
|
.array()
|
||||||
.optional()
|
.optional()
|
||||||
})
|
})
|
||||||
@@ -731,9 +738,10 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => {
|
|||||||
tags: SecretTagsSchema.pick({
|
tags: SecretTagsSchema.pick({
|
||||||
id: true,
|
id: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
name: true,
|
|
||||||
color: true
|
color: true
|
||||||
}).array()
|
})
|
||||||
|
.extend({ name: z.string() })
|
||||||
|
.array()
|
||||||
})
|
})
|
||||||
.array(),
|
.array(),
|
||||||
imports: z
|
imports: z
|
||||||
|
|||||||
@@ -22,16 +22,7 @@ type TSecretTagServiceFactoryDep = {
|
|||||||
export type TSecretTagServiceFactory = ReturnType<typeof secretTagServiceFactory>;
|
export type TSecretTagServiceFactory = ReturnType<typeof secretTagServiceFactory>;
|
||||||
|
|
||||||
export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSecretTagServiceFactoryDep) => {
|
export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSecretTagServiceFactoryDep) => {
|
||||||
const createTag = async ({
|
const createTag = async ({ slug, actor, color, actorId, actorOrgId, actorAuthMethod, projectId }: TCreateTagDTO) => {
|
||||||
name,
|
|
||||||
slug,
|
|
||||||
actor,
|
|
||||||
color,
|
|
||||||
actorId,
|
|
||||||
actorOrgId,
|
|
||||||
actorAuthMethod,
|
|
||||||
projectId
|
|
||||||
}: TCreateTagDTO) => {
|
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -46,7 +37,6 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
|
|||||||
|
|
||||||
const newTag = await secretTagDAL.create({
|
const newTag = await secretTagDAL.create({
|
||||||
projectId,
|
projectId,
|
||||||
name,
|
|
||||||
slug,
|
slug,
|
||||||
color,
|
color,
|
||||||
createdBy: actorId,
|
createdBy: actorId,
|
||||||
@@ -55,7 +45,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
|
|||||||
return newTag;
|
return newTag;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateTag = async ({ actorId, actor, actorOrgId, actorAuthMethod, id, name, color, slug }: TUpdateTagDTO) => {
|
const updateTag = async ({ actorId, actor, actorOrgId, actorAuthMethod, id, color, slug }: TUpdateTagDTO) => {
|
||||||
const tag = await secretTagDAL.findById(id);
|
const tag = await secretTagDAL.findById(id);
|
||||||
if (!tag) throw new BadRequestError({ message: "Tag doesn't exist" });
|
if (!tag) throw new BadRequestError({ message: "Tag doesn't exist" });
|
||||||
|
|
||||||
@@ -73,7 +63,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Tags);
|
||||||
|
|
||||||
const updatedTag = await secretTagDAL.updateById(tag.id, { name, color, slug });
|
const updatedTag = await secretTagDAL.updateById(tag.id, { color, slug });
|
||||||
return updatedTag;
|
return updatedTag;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -107,7 +97,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
||||||
|
|
||||||
return tag;
|
return { ...tag, name: tag.slug };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTagBySlug = async ({ actorId, actor, actorOrgId, actorAuthMethod, slug, projectId }: TGetTagBySlugDTO) => {
|
const getTagBySlug = async ({ actorId, actor, actorOrgId, actorAuthMethod, slug, projectId }: TGetTagBySlugDTO) => {
|
||||||
@@ -123,7 +113,7 @@ export const secretTagServiceFactory = ({ secretTagDAL, permissionService }: TSe
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Tags);
|
||||||
|
|
||||||
return tag;
|
return { ...tag, name: tag.slug };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProjectTags = async ({ actor, actorId, actorOrgId, actorAuthMethod, projectId }: TListProjectTagsDTO) => {
|
const getProjectTags = async ({ actor, actorId, actorOrgId, actorAuthMethod, projectId }: TListProjectTagsDTO) => {
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
export type TCreateTagDTO = {
|
export type TCreateTagDTO = {
|
||||||
name: string;
|
|
||||||
color: string;
|
color: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TUpdateTagDTO = {
|
export type TUpdateTagDTO = {
|
||||||
id: string;
|
id: string;
|
||||||
name?: string;
|
|
||||||
slug?: string;
|
slug?: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|||||||
@@ -136,7 +136,6 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
||||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"))
|
|
||||||
.orderBy("id", "asc");
|
.orderBy("id", "asc");
|
||||||
|
|
||||||
const data = sqlNestRelationships({
|
const data = sqlNestRelationships({
|
||||||
@@ -147,11 +146,11 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagId",
|
key: "tagId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({
|
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
||||||
id,
|
id,
|
||||||
color,
|
color,
|
||||||
slug,
|
slug,
|
||||||
name
|
name: slug
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -169,14 +168,13 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
.where({ [`${TableName.SecretV2}Id` as const]: secretId })
|
.where({ [`${TableName.SecretV2}Id` as const]: secretId })
|
||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"));
|
||||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"));
|
|
||||||
|
|
||||||
return tags.map((el) => ({
|
return tags.map((el) => ({
|
||||||
id: el.tagId,
|
id: el.tagId,
|
||||||
color: el.tagColor,
|
color: el.tagColor,
|
||||||
slug: el.tagSlug,
|
slug: el.tagSlug,
|
||||||
name: el.tagName
|
name: el.tagSlug
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "get secret tags" });
|
throw new DatabaseError({ error, name: "get secret tags" });
|
||||||
@@ -210,7 +208,6 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
||||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"))
|
|
||||||
.orderBy("id", "asc");
|
.orderBy("id", "asc");
|
||||||
|
|
||||||
const data = sqlNestRelationships({
|
const data = sqlNestRelationships({
|
||||||
@@ -221,11 +218,11 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagId",
|
key: "tagId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({
|
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
||||||
id,
|
id,
|
||||||
color,
|
color,
|
||||||
slug,
|
slug,
|
||||||
name
|
name: slug
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -350,8 +347,7 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
.select(selectAllTableCols(TableName.SecretV2))
|
.select(selectAllTableCols(TableName.SecretV2))
|
||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"));
|
||||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"));
|
|
||||||
const docs = sqlNestRelationships({
|
const docs = sqlNestRelationships({
|
||||||
data: rawDocs,
|
data: rawDocs,
|
||||||
key: "id",
|
key: "id",
|
||||||
@@ -360,11 +356,11 @@ export const secretV2BridgeDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagId",
|
key: "tagId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({
|
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
||||||
id,
|
id,
|
||||||
color,
|
color,
|
||||||
slug,
|
slug,
|
||||||
name
|
name: slug
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -429,6 +429,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
includeImports,
|
includeImports,
|
||||||
recursive,
|
recursive,
|
||||||
|
tagSlugs = [],
|
||||||
expandSecretReferences: shouldExpandSecretReferences
|
expandSecretReferences: shouldExpandSecretReferences
|
||||||
}: TGetSecretsDTO) => {
|
}: TGetSecretsDTO) => {
|
||||||
const { permission } = await permissionService.getProjectPermission(
|
const { permission } = await permissionService.getProjectPermission(
|
||||||
@@ -496,6 +497,9 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
: ""
|
: ""
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
const filteredSecrets = tagSlugs.length
|
||||||
|
? decryptedSecrets.filter((secret) => Boolean(secret.tags?.find((el) => tagSlugs.includes(el.slug))))
|
||||||
|
: decryptedSecrets;
|
||||||
const expandSecretReferences = expandSecretReferencesFactory({
|
const expandSecretReferences = expandSecretReferencesFactory({
|
||||||
projectId,
|
projectId,
|
||||||
folderDAL,
|
folderDAL,
|
||||||
@@ -504,7 +508,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (shouldExpandSecretReferences) {
|
if (shouldExpandSecretReferences) {
|
||||||
const secretsGroupByPath = groupBy(decryptedSecrets, (i) => i.secretPath);
|
const secretsGroupByPath = groupBy(filteredSecrets, (i) => i.secretPath);
|
||||||
for (const secretPathKey in secretsGroupByPath) {
|
for (const secretPathKey in secretsGroupByPath) {
|
||||||
if (Object.hasOwn(secretsGroupByPath, secretPathKey)) {
|
if (Object.hasOwn(secretsGroupByPath, secretPathKey)) {
|
||||||
const secretsGroupByKey = secretsGroupByPath[secretPathKey].reduce(
|
const secretsGroupByKey = secretsGroupByPath[secretPathKey].reduce(
|
||||||
@@ -530,7 +534,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
|
|
||||||
if (!includeImports) {
|
if (!includeImports) {
|
||||||
return {
|
return {
|
||||||
secrets: decryptedSecrets
|
secrets: filteredSecrets
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,7 +562,7 @@ export const secretV2BridgeServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
secrets: decryptedSecrets,
|
secrets: filteredSecrets,
|
||||||
imports: importedSecrets
|
imports: importedSecrets
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export type TGetSecretsDTO = {
|
|||||||
environment: string;
|
environment: string;
|
||||||
includeImports?: boolean;
|
includeImports?: boolean;
|
||||||
recursive?: boolean;
|
recursive?: boolean;
|
||||||
|
tagSlugs?: string[];
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TGetASecretDTO = {
|
export type TGetASecretDTO = {
|
||||||
|
|||||||
@@ -123,7 +123,6 @@ export const secretDALFactory = (db: TDbClient) => {
|
|||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
||||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"))
|
|
||||||
.orderBy("id", "asc");
|
.orderBy("id", "asc");
|
||||||
const data = sqlNestRelationships({
|
const data = sqlNestRelationships({
|
||||||
data: secs,
|
data: secs,
|
||||||
@@ -133,11 +132,11 @@ export const secretDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagId",
|
key: "tagId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({
|
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
||||||
id,
|
id,
|
||||||
color,
|
color,
|
||||||
slug,
|
slug,
|
||||||
name
|
name: slug
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -155,14 +154,13 @@ export const secretDALFactory = (db: TDbClient) => {
|
|||||||
.where({ [`${TableName.Secret}Id` as const]: secretId })
|
.where({ [`${TableName.Secret}Id` as const]: secretId })
|
||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"));
|
||||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"));
|
|
||||||
|
|
||||||
return tags.map((el) => ({
|
return tags.map((el) => ({
|
||||||
id: el.tagId,
|
id: el.tagId,
|
||||||
color: el.tagColor,
|
color: el.tagColor,
|
||||||
slug: el.tagSlug,
|
slug: el.tagSlug,
|
||||||
name: el.tagName
|
name: el.tagSlug
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "get secret tags" });
|
throw new DatabaseError({ error, name: "get secret tags" });
|
||||||
@@ -188,7 +186,6 @@ export const secretDALFactory = (db: TDbClient) => {
|
|||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
||||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"))
|
|
||||||
.orderBy("id", "asc");
|
.orderBy("id", "asc");
|
||||||
const data = sqlNestRelationships({
|
const data = sqlNestRelationships({
|
||||||
data: secs,
|
data: secs,
|
||||||
@@ -198,11 +195,11 @@ export const secretDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagId",
|
key: "tagId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({
|
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
||||||
id,
|
id,
|
||||||
color,
|
color,
|
||||||
slug,
|
slug,
|
||||||
name
|
name: slug
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -318,8 +315,7 @@ export const secretDALFactory = (db: TDbClient) => {
|
|||||||
.select(selectAllTableCols(TableName.Secret))
|
.select(selectAllTableCols(TableName.Secret))
|
||||||
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
.select(db.ref("id").withSchema(TableName.SecretTag).as("tagId"))
|
||||||
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
.select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor"))
|
||||||
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"))
|
.select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"));
|
||||||
.select(db.ref("name").withSchema(TableName.SecretTag).as("tagName"));
|
|
||||||
const docs = sqlNestRelationships({
|
const docs = sqlNestRelationships({
|
||||||
data: rawDocs,
|
data: rawDocs,
|
||||||
key: "id",
|
key: "id",
|
||||||
@@ -328,11 +324,11 @@ export const secretDALFactory = (db: TDbClient) => {
|
|||||||
{
|
{
|
||||||
key: "tagId",
|
key: "tagId",
|
||||||
label: "tags" as const,
|
label: "tags" as const,
|
||||||
mapper: ({ tagId: id, tagColor: color, tagSlug: slug, tagName: name }) => ({
|
mapper: ({ tagId: id, tagColor: color, tagSlug: slug }) => ({
|
||||||
id,
|
id,
|
||||||
color,
|
color,
|
||||||
slug,
|
slug,
|
||||||
name
|
name: slug
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -370,7 +370,6 @@ export const decryptSecretRaw = (
|
|||||||
id: string;
|
id: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
color?: string | null;
|
color?: string | null;
|
||||||
name: string;
|
|
||||||
}[];
|
}[];
|
||||||
},
|
},
|
||||||
key: string
|
key: string
|
||||||
@@ -412,7 +411,7 @@ export const decryptSecretRaw = (
|
|||||||
_id: secret.id,
|
_id: secret.id,
|
||||||
id: secret.id,
|
id: secret.id,
|
||||||
user: secret.userId,
|
user: secret.userId,
|
||||||
tags: secret.tags,
|
tags: secret.tags?.map((el) => ({ ...el, name: el.slug })),
|
||||||
skipMultilineEncoding: secret.skipMultilineEncoding,
|
skipMultilineEncoding: secret.skipMultilineEncoding,
|
||||||
secretReminderRepeatDays: secret.secretReminderRepeatDays,
|
secretReminderRepeatDays: secret.secretReminderRepeatDays,
|
||||||
secretReminderNote: secret.secretReminderNote,
|
secretReminderNote: secret.secretReminderNote,
|
||||||
|
|||||||
@@ -964,7 +964,8 @@ export const secretServiceFactory = ({
|
|||||||
environment,
|
environment,
|
||||||
includeImports,
|
includeImports,
|
||||||
expandSecretReferences,
|
expandSecretReferences,
|
||||||
recursive
|
recursive,
|
||||||
|
tagSlugs = []
|
||||||
}: TGetSecretsRawDTO) => {
|
}: TGetSecretsRawDTO) => {
|
||||||
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
const { botKey, shouldUseSecretV2Bridge } = await projectBotService.getBotKey(projectId);
|
||||||
if (shouldUseSecretV2Bridge) {
|
if (shouldUseSecretV2Bridge) {
|
||||||
@@ -978,7 +979,8 @@ export const secretServiceFactory = ({
|
|||||||
path,
|
path,
|
||||||
recursive,
|
recursive,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
includeImports
|
includeImports,
|
||||||
|
tagSlugs
|
||||||
});
|
});
|
||||||
return { secrets, imports };
|
return { secrets, imports };
|
||||||
}
|
}
|
||||||
@@ -998,6 +1000,9 @@ export const secretServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const decryptedSecrets = secrets.map((el) => decryptSecretRaw(el, botKey));
|
const decryptedSecrets = secrets.map((el) => decryptSecretRaw(el, botKey));
|
||||||
|
const filteredSecrets = tagSlugs.length
|
||||||
|
? decryptedSecrets.filter((secret) => Boolean(secret.tags?.find((el) => tagSlugs.includes(el.slug))))
|
||||||
|
: decryptedSecrets;
|
||||||
const processedImports = (imports || [])?.map(({ secrets: importedSecrets, ...el }) => {
|
const processedImports = (imports || [])?.map(({ secrets: importedSecrets, ...el }) => {
|
||||||
const decryptedImportSecrets = importedSecrets.map((sec) =>
|
const decryptedImportSecrets = importedSecrets.map((sec) =>
|
||||||
decryptSecretRaw(
|
decryptSecretRaw(
|
||||||
@@ -1106,14 +1111,14 @@ export const secretServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// expand secrets
|
// expand secrets
|
||||||
await batchSecretsExpand(decryptedSecrets);
|
await batchSecretsExpand(filteredSecrets);
|
||||||
|
|
||||||
// expand imports by batch
|
// expand imports by batch
|
||||||
await Promise.all(processedImports.map((processedImport) => batchSecretsExpand(processedImport.secrets)));
|
await Promise.all(processedImports.map((processedImport) => batchSecretsExpand(processedImport.secrets)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
secrets: decryptedSecrets,
|
secrets: filteredSecrets,
|
||||||
imports: processedImports
|
imports: processedImports
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -2081,7 +2086,7 @@ export const secretServiceFactory = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...updatedSecret[0],
|
...updatedSecret[0],
|
||||||
tags: [...existingSecretTags, ...tags].map((t) => ({ id: t.id, slug: t.slug, name: t.name, color: t.color }))
|
tags: [...existingSecretTags, ...tags].map((t) => ({ id: t.id, slug: t.slug, name: t.slug, color: t.color }))
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,7 @@ export type TGetSecretsRawDTO = {
|
|||||||
environment: string;
|
environment: string;
|
||||||
includeImports?: boolean;
|
includeImports?: boolean;
|
||||||
recursive?: boolean;
|
recursive?: boolean;
|
||||||
|
tagSlugs?: string[];
|
||||||
} & TProjectPermission;
|
} & TProjectPermission;
|
||||||
|
|
||||||
export type TGetASecretRawDTO = {
|
export type TGetASecretRawDTO = {
|
||||||
|
|||||||
@@ -404,6 +404,10 @@ func CallGetRawSecretsV3(httpClient *resty.Client, request GetRawSecretsV3Reques
|
|||||||
SetQueryParam("environment", request.Environment).
|
SetQueryParam("environment", request.Environment).
|
||||||
SetQueryParam("secretPath", request.SecretPath)
|
SetQueryParam("secretPath", request.SecretPath)
|
||||||
|
|
||||||
|
if request.TagSlugs != "" {
|
||||||
|
req.SetQueryParam("tagSlugs", request.TagSlugs)
|
||||||
|
}
|
||||||
|
|
||||||
if request.IncludeImport {
|
if request.IncludeImport {
|
||||||
req.SetQueryParam("include_imports", "true")
|
req.SetQueryParam("include_imports", "true")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -574,6 +574,7 @@ type GetRawSecretsV3Request struct {
|
|||||||
SecretPath string `json:"secretPath"`
|
SecretPath string `json:"secretPath"`
|
||||||
IncludeImport bool `json:"include_imports"`
|
IncludeImport bool `json:"include_imports"`
|
||||||
Recursive bool `json:"recursive"`
|
Recursive bool `json:"recursive"`
|
||||||
|
TagSlugs string `json:"tagSlugs,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetRawSecretsV3Response struct {
|
type GetRawSecretsV3Response struct {
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ func ParseAgentConfig(configFile []byte) (*Config, error) {
|
|||||||
|
|
||||||
func secretTemplateFunction(accessToken string, existingEtag string, currentEtag *string) func(string, string, string) ([]models.SingleEnvironmentVariable, error) {
|
func secretTemplateFunction(accessToken string, existingEtag string, currentEtag *string) func(string, string, string) ([]models.SingleEnvironmentVariable, error) {
|
||||||
return func(projectID, envSlug, secretPath string) ([]models.SingleEnvironmentVariable, error) {
|
return func(projectID, envSlug, secretPath string) ([]models.SingleEnvironmentVariable, error) {
|
||||||
res, err := util.GetPlainTextSecretsV3(accessToken, projectID, envSlug, secretPath, false, false)
|
res, err := util.GetPlainTextSecretsV3(accessToken, projectID, envSlug, secretPath, false, false, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import (
|
|||||||
"github.com/zalando/go-keyring"
|
"github.com/zalando/go-keyring"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment string, secretPath string, includeImports bool, recursive bool) ([]models.SingleEnvironmentVariable, error) {
|
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment string, secretPath string, includeImports bool, recursive bool, tagSlugs string) ([]models.SingleEnvironmentVariable, error) {
|
||||||
serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
|
serviceTokenParts := strings.SplitN(fullServiceToken, ".", 4)
|
||||||
if len(serviceTokenParts) < 4 {
|
if len(serviceTokenParts) < 4 {
|
||||||
return nil, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
|
return nil, fmt.Errorf("invalid service token entered. Please double check your service token and try again")
|
||||||
@@ -54,6 +54,7 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment str
|
|||||||
SecretPath: secretPath,
|
SecretPath: secretPath,
|
||||||
IncludeImport: includeImports,
|
IncludeImport: includeImports,
|
||||||
Recursive: recursive,
|
Recursive: recursive,
|
||||||
|
TagSlugs: tagSlugs,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -77,7 +78,7 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment str
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPlainTextSecretsV3(accessToken string, workspaceId string, environmentName string, secretsPath string, includeImports bool, recursive bool) (models.PlaintextSecretResult, error) {
|
func GetPlainTextSecretsV3(accessToken string, workspaceId string, environmentName string, secretsPath string, includeImports bool, recursive bool, tagSlugs string) (models.PlaintextSecretResult, error) {
|
||||||
httpClient := resty.New()
|
httpClient := resty.New()
|
||||||
httpClient.SetAuthToken(accessToken).
|
httpClient.SetAuthToken(accessToken).
|
||||||
SetHeader("Accept", "application/json")
|
SetHeader("Accept", "application/json")
|
||||||
@@ -87,7 +88,7 @@ func GetPlainTextSecretsV3(accessToken string, workspaceId string, environmentNa
|
|||||||
Environment: environmentName,
|
Environment: environmentName,
|
||||||
IncludeImport: includeImports,
|
IncludeImport: includeImports,
|
||||||
Recursive: recursive,
|
Recursive: recursive,
|
||||||
// TagSlugs: tagSlugs,
|
TagSlugs: tagSlugs,
|
||||||
}
|
}
|
||||||
|
|
||||||
if secretsPath != "" {
|
if secretsPath != "" {
|
||||||
@@ -282,7 +283,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
res, err := GetPlainTextSecretsV3(loggedInUserDetails.UserCredentials.JTWToken, infisicalDotJson.WorkspaceId,
|
res, err := GetPlainTextSecretsV3(loggedInUserDetails.UserCredentials.JTWToken, infisicalDotJson.WorkspaceId,
|
||||||
params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive)
|
params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive, params.TagSlugs)
|
||||||
log.Debug().Msgf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", err)
|
log.Debug().Msgf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", err)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -311,7 +312,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
|
|||||||
} else {
|
} else {
|
||||||
if params.InfisicalToken != "" {
|
if params.InfisicalToken != "" {
|
||||||
log.Debug().Msg("Trying to fetch secrets using service token")
|
log.Debug().Msg("Trying to fetch secrets using service token")
|
||||||
secretsToReturn, errorToReturn = GetPlainTextSecretsViaServiceToken(params.InfisicalToken, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive)
|
secretsToReturn, errorToReturn = GetPlainTextSecretsViaServiceToken(params.InfisicalToken, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive, params.TagSlugs)
|
||||||
} else if params.UniversalAuthAccessToken != "" {
|
} else if params.UniversalAuthAccessToken != "" {
|
||||||
|
|
||||||
if params.WorkspaceId == "" {
|
if params.WorkspaceId == "" {
|
||||||
@@ -319,7 +320,7 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters, projectCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msg("Trying to fetch secrets using universal auth")
|
log.Debug().Msg("Trying to fetch secrets using universal auth")
|
||||||
res, err := GetPlainTextSecretsV3(params.UniversalAuthAccessToken, params.WorkspaceId, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive)
|
res, err := GetPlainTextSecretsV3(params.UniversalAuthAccessToken, params.WorkspaceId, params.Environment, params.SecretsPath, params.IncludeImport, params.Recursive, params.TagSlugs)
|
||||||
|
|
||||||
errorToReturn = err
|
errorToReturn = err
|
||||||
secretsToReturn = res.Secrets
|
secretsToReturn = res.Secrets
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Controller, useForm } from "react-hook-form";
|
|||||||
import { faCheck } from "@fortawesome/free-solid-svg-icons";
|
import { faCheck } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import slugify from "@sindresorhus/slugify";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
@@ -87,7 +88,13 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createTagSchema = z.object({
|
const createTagSchema = z.object({
|
||||||
name: z.string().trim(),
|
slug: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.refine((v) => slugify(v) === v, {
|
||||||
|
message: "Invalid slug. Slug can only contain alphanumeric characters and hyphens."
|
||||||
|
}),
|
||||||
color: z.string().trim()
|
color: z.string().trim()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -110,7 +117,7 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => {
|
|||||||
} = useForm<FormData>({
|
} = useForm<FormData>({
|
||||||
resolver: zodResolver(createTagSchema)
|
resolver: zodResolver(createTagSchema)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const workspaceId = currentWorkspace?.id || "";
|
const workspaceId = currentWorkspace?.id || "";
|
||||||
|
|
||||||
@@ -123,13 +130,12 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => {
|
|||||||
if (!isOpen) reset();
|
if (!isOpen) reset();
|
||||||
}, [isOpen]);
|
}, [isOpen]);
|
||||||
|
|
||||||
const onFormSubmit = async ({ name, color }: FormData) => {
|
const onFormSubmit = async ({ slug, color }: FormData) => {
|
||||||
try {
|
try {
|
||||||
await createWsTag({
|
await createWsTag({
|
||||||
workspaceID: workspaceId,
|
workspaceID: workspaceId,
|
||||||
tagName: name,
|
|
||||||
tagColor: color,
|
tagColor: color,
|
||||||
tagSlug: name.replace(" ", "_")
|
tagSlug: slug
|
||||||
});
|
});
|
||||||
onToggle(false);
|
onToggle(false);
|
||||||
reset();
|
reset();
|
||||||
@@ -155,11 +161,11 @@ export const CreateTagModal = ({ isOpen, onToggle }: Props): JSX.Element => {
|
|||||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="name"
|
name="slug"
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl label="Tag Name" isError={Boolean(error)} errorText={error?.message}>
|
<FormControl label="Tag Slug" isError={Boolean(error)} errorText={error?.message}>
|
||||||
<Input {...field} placeholder="Type your tag name" />
|
<Input {...field} placeholder="Type your tag slug" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -28,11 +28,10 @@ export const useCreateWsTag = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
return useMutation<WsTag, {}, CreateTagDTO>({
|
return useMutation<WsTag, {}, CreateTagDTO>({
|
||||||
mutationFn: async ({ workspaceID, tagName, tagColor, tagSlug }) => {
|
mutationFn: async ({ workspaceID, tagColor, tagSlug }) => {
|
||||||
const { data } = await apiRequest.post<{ workspaceTag: WsTag }>(
|
const { data } = await apiRequest.post<{ workspaceTag: WsTag }>(
|
||||||
`/api/v1/workspace/${workspaceID}/tags`,
|
`/api/v1/workspace/${workspaceID}/tags`,
|
||||||
{
|
{
|
||||||
name: tagName,
|
|
||||||
color: tagColor || "",
|
color: tagColor || "",
|
||||||
slug: tagSlug
|
slug: tagSlug
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ export type UserWsTags = WsTag[];
|
|||||||
|
|
||||||
export type WsTag = {
|
export type WsTag = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
|
||||||
slug: string;
|
slug: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
@@ -16,7 +15,6 @@ export type WorkspaceTag = { id: string; name: string; slug: string };
|
|||||||
export type CreateTagDTO = {
|
export type CreateTagDTO = {
|
||||||
workspaceID: string;
|
workspaceID: string;
|
||||||
tagSlug: string;
|
tagSlug: string;
|
||||||
tagName: string;
|
|
||||||
tagColor: string;
|
tagColor: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -97,7 +97,7 @@ export const SecretApprovalRequestChangeItem = ({
|
|||||||
</Td>
|
</Td>
|
||||||
<Td>{secretVersion?.secretComment}</Td>
|
<Td>{secretVersion?.secretComment}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
{secretVersion?.tags?.map(({ name, id: tagId, color }) => (
|
{secretVersion?.tags?.map(({ slug, id: tagId, color }) => (
|
||||||
<Tag
|
<Tag
|
||||||
className="flex w-min items-center space-x-2"
|
className="flex w-min items-center space-x-2"
|
||||||
key={`${secretVersion.id}-${tagId}`}
|
key={`${secretVersion.id}-${tagId}`}
|
||||||
@@ -106,7 +106,7 @@ export const SecretApprovalRequestChangeItem = ({
|
|||||||
className="h-3 w-3 rounded-full"
|
className="h-3 w-3 rounded-full"
|
||||||
style={{ backgroundColor: color || "#bec2c8" }}
|
style={{ backgroundColor: color || "#bec2c8" }}
|
||||||
/>
|
/>
|
||||||
<div className="text-sm">{name}</div>
|
<div className="text-sm">{slug}</div>
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
</Td>
|
</Td>
|
||||||
@@ -119,7 +119,7 @@ export const SecretApprovalRequestChangeItem = ({
|
|||||||
</Td>
|
</Td>
|
||||||
<Td>{newVersion?.secretComment}</Td>
|
<Td>{newVersion?.secretComment}</Td>
|
||||||
<Td>
|
<Td>
|
||||||
{newVersion?.tags?.map(({ name, id: tagId, color }) => (
|
{newVersion?.tags?.map(({ slug, id: tagId, color }) => (
|
||||||
<Tag
|
<Tag
|
||||||
className="flex w-min items-center space-x-2"
|
className="flex w-min items-center space-x-2"
|
||||||
key={`${newVersion.id}-${tagId}`}
|
key={`${newVersion.id}-${tagId}`}
|
||||||
@@ -128,7 +128,7 @@ export const SecretApprovalRequestChangeItem = ({
|
|||||||
className="h-3 w-3 rounded-full"
|
className="h-3 w-3 rounded-full"
|
||||||
style={{ backgroundColor: color || "#bec2c8" }}
|
style={{ backgroundColor: color || "#bec2c8" }}
|
||||||
/>
|
/>
|
||||||
<div className="text-sm">{name}</div>
|
<div className="text-sm">{slug}</div>
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
</Td>
|
</Td>
|
||||||
@@ -157,7 +157,7 @@ export const SecretApprovalRequestChangeItem = ({
|
|||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
{(op === CommitType.CREATE ? newVersion?.tags : secretVersion?.tags)?.map(
|
{(op === CommitType.CREATE ? newVersion?.tags : secretVersion?.tags)?.map(
|
||||||
({ name, id: tagId, color }) => (
|
({ slug, id: tagId, color }) => (
|
||||||
<Tag
|
<Tag
|
||||||
className="flex w-min items-center space-x-2"
|
className="flex w-min items-center space-x-2"
|
||||||
key={`${
|
key={`${
|
||||||
@@ -168,7 +168,7 @@ export const SecretApprovalRequestChangeItem = ({
|
|||||||
className="h-3 w-3 rounded-full"
|
className="h-3 w-3 rounded-full"
|
||||||
style={{ backgroundColor: color || "#bec2c8" }}
|
style={{ backgroundColor: color || "#bec2c8" }}
|
||||||
/>
|
/>
|
||||||
<div className="text-sm">{name}</div>
|
<div className="text-sm">{slug}</div>
|
||||||
</Tag>
|
</Tag>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -327,7 +327,7 @@ export const ActionBar = ({
|
|||||||
</DropdownSubMenuTrigger>
|
</DropdownSubMenuTrigger>
|
||||||
<DropdownSubMenuContent className="rounded-l-none">
|
<DropdownSubMenuContent className="rounded-l-none">
|
||||||
<DropdownMenuLabel>Apply tags to filter secrets</DropdownMenuLabel>
|
<DropdownMenuLabel>Apply tags to filter secrets</DropdownMenuLabel>
|
||||||
{tags.map(({ id, name, color }) => (
|
{tags.map(({ id, slug, color }) => (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={(evt) => {
|
onClick={(evt) => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
@@ -342,7 +342,7 @@ export const ActionBar = ({
|
|||||||
className="mr-2 h-2 w-2 rounded-full"
|
className="mr-2 h-2 w-2 rounded-full"
|
||||||
style={{ background: color || "#bec2c8" }}
|
style={{ background: color || "#bec2c8" }}
|
||||||
/>
|
/>
|
||||||
{name}
|
{slug}
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ export const SecretDetailSidebar = ({
|
|||||||
)}
|
)}
|
||||||
<FormControl label="Tags" className="">
|
<FormControl label="Tags" className="">
|
||||||
<div className="grid auto-cols-min grid-flow-col gap-2 overflow-hidden pt-2">
|
<div className="grid auto-cols-min grid-flow-col gap-2 overflow-hidden pt-2">
|
||||||
{fields.map(({ tagColor, id: formId, name, id }) => (
|
{fields.map(({ tagColor, id: formId, slug, id }) => (
|
||||||
<Tag
|
<Tag
|
||||||
className="flex w-min items-center space-x-2"
|
className="flex w-min items-center space-x-2"
|
||||||
key={formId}
|
key={formId}
|
||||||
@@ -271,7 +271,7 @@ export const SecretDetailSidebar = ({
|
|||||||
className="h-3 w-3 rounded-full"
|
className="h-3 w-3 rounded-full"
|
||||||
style={{ backgroundColor: tagColor || "#bec2c8" }}
|
style={{ backgroundColor: tagColor || "#bec2c8" }}
|
||||||
/>
|
/>
|
||||||
<div className="text-sm">{name}</div>
|
<div className="text-sm">{slug}</div>
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@@ -296,7 +296,7 @@ export const SecretDetailSidebar = ({
|
|||||||
<DropdownMenuContent align="end" className="z-[100]">
|
<DropdownMenuContent align="end" className="z-[100]">
|
||||||
<DropdownMenuLabel>Add tags to this secret</DropdownMenuLabel>
|
<DropdownMenuLabel>Add tags to this secret</DropdownMenuLabel>
|
||||||
{tags.map((tag) => {
|
{tags.map((tag) => {
|
||||||
const { id: tagId, name, color } = tag;
|
const { id: tagId, slug, color } = tag;
|
||||||
|
|
||||||
const isSelected = selectedTagsGroupById?.[tagId];
|
const isSelected = selectedTagsGroupById?.[tagId];
|
||||||
return (
|
return (
|
||||||
@@ -311,7 +311,7 @@ export const SecretDetailSidebar = ({
|
|||||||
className="mr-2 h-2 w-2 rounded-full"
|
className="mr-2 h-2 w-2 rounded-full"
|
||||||
style={{ background: color || "#bec2c8" }}
|
style={{ background: color || "#bec2c8" }}
|
||||||
/>
|
/>
|
||||||
{name}
|
{slug}
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -336,7 +336,7 @@ export const SecretItem = memo(
|
|||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuLabel>Add tags to this secret</DropdownMenuLabel>
|
<DropdownMenuLabel>Add tags to this secret</DropdownMenuLabel>
|
||||||
{tags.map((tag) => {
|
{tags.map((tag) => {
|
||||||
const { id: tagId, name, color } = tag;
|
const { id: tagId, slug, color } = tag;
|
||||||
|
|
||||||
const isTagSelected = selectedTagsGroupById?.[tagId];
|
const isTagSelected = selectedTagsGroupById?.[tagId];
|
||||||
return (
|
return (
|
||||||
@@ -358,7 +358,7 @@ export const SecretItem = memo(
|
|||||||
className="mr-2 h-2 w-2 rounded-full"
|
className="mr-2 h-2 w-2 rounded-full"
|
||||||
style={{ background: color || "#bec2c8" }}
|
style={{ background: color || "#bec2c8" }}
|
||||||
/>
|
/>
|
||||||
{name}
|
{slug}
|
||||||
</div>
|
</div>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ export const formSchema = z.object({
|
|||||||
tags: z
|
tags: z
|
||||||
.object({
|
.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string(),
|
|
||||||
slug: z.string(),
|
slug: z.string(),
|
||||||
tagColor: z.string().optional()
|
tagColor: z.string().optional()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ export const SecretItem = ({ mode, preSecret, postSecret }: Props) => {
|
|||||||
<Td className="border-r border-mineshaft-600">Tags</Td>
|
<Td className="border-r border-mineshaft-600">Tags</Td>
|
||||||
{isModified && (
|
{isModified && (
|
||||||
<Td className="border-r border-mineshaft-600">
|
<Td className="border-r border-mineshaft-600">
|
||||||
{preSecret?.tags?.map(({ name, id: tagId, color }) => (
|
{preSecret?.tags?.map(({ slug, id: tagId, color }) => (
|
||||||
<Tag
|
<Tag
|
||||||
className="flex w-min items-center space-x-2"
|
className="flex w-min items-center space-x-2"
|
||||||
key={`${preSecret.id}-${tagId}`}
|
key={`${preSecret.id}-${tagId}`}
|
||||||
@@ -160,13 +160,13 @@ export const SecretItem = ({ mode, preSecret, postSecret }: Props) => {
|
|||||||
className="h-3 w-3 rounded-full"
|
className="h-3 w-3 rounded-full"
|
||||||
style={{ backgroundColor: color || "#bec2c8" }}
|
style={{ backgroundColor: color || "#bec2c8" }}
|
||||||
/>
|
/>
|
||||||
<div className="text-sm">{name}</div>
|
<div className="text-sm">{slug}</div>
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
<Td>
|
<Td>
|
||||||
{postSecret?.tags?.map(({ name, id: tagId, color }) => (
|
{postSecret?.tags?.map(({ slug, id: tagId, color }) => (
|
||||||
<Tag
|
<Tag
|
||||||
className="flex w-min items-center space-x-2"
|
className="flex w-min items-center space-x-2"
|
||||||
key={`${postSecret.id}-${tagId}`}
|
key={`${postSecret.id}-${tagId}`}
|
||||||
@@ -175,7 +175,7 @@ export const SecretItem = ({ mode, preSecret, postSecret }: Props) => {
|
|||||||
className="h-3 w-3 rounded-full"
|
className="h-3 w-3 rounded-full"
|
||||||
style={{ backgroundColor: color || "#bec2c8" }}
|
style={{ backgroundColor: color || "#bec2c8" }}
|
||||||
/>
|
/>
|
||||||
<div className="text-sm">{name}</div>
|
<div className="text-sm">{slug}</div>
|
||||||
</Tag>
|
</Tag>
|
||||||
))}
|
))}
|
||||||
</Td>
|
</Td>
|
||||||
|
|||||||
+14
-13
@@ -1,6 +1,7 @@
|
|||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import * as yup from "yup";
|
import slugify from "@sindresorhus/slugify";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, FormControl, Input, Modal, ModalClose, ModalContent } from "@app/components/v2";
|
import { Button, FormControl, Input, Modal, ModalClose, ModalContent } from "@app/components/v2";
|
||||||
@@ -8,11 +9,13 @@ import { useWorkspace } from "@app/context";
|
|||||||
import { useCreateWsTag } from "@app/hooks/api";
|
import { useCreateWsTag } from "@app/hooks/api";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
const schema = yup.object({
|
const schema = z.object({
|
||||||
name: yup.string().required().label("Tag Name")
|
slug: z.string().refine((v) => slugify(v) === v, {
|
||||||
|
message: "Invalid slug. Slug can only contain alphanumeric characters and hyphens."
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
export type FormData = yup.InferType<typeof schema>;
|
export type FormData = z.infer<typeof schema>;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
popUp: UsePopUpState<["CreateSecretTag", "deleteTagConfirmation"]>;
|
popUp: UsePopUpState<["CreateSecretTag", "deleteTagConfirmation"]>;
|
||||||
@@ -26,7 +29,6 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const AddSecretTagModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => {
|
export const AddSecretTagModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => {
|
||||||
|
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const createWsTag = useCreateWsTag();
|
const createWsTag = useCreateWsTag();
|
||||||
const {
|
const {
|
||||||
@@ -35,17 +37,16 @@ export const AddSecretTagModal = ({ popUp, handlePopUpClose, handlePopUpToggle }
|
|||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { isSubmitting }
|
formState: { isSubmitting }
|
||||||
} = useForm<FormData>({
|
} = useForm<FormData>({
|
||||||
resolver: yupResolver(schema)
|
resolver: zodResolver(schema)
|
||||||
});
|
});
|
||||||
|
|
||||||
const onFormSubmit = async ({ name }: FormData) => {
|
const onFormSubmit = async ({ slug }: FormData) => {
|
||||||
try {
|
try {
|
||||||
if (!currentWorkspace?.id) return;
|
if (!currentWorkspace?.id) return;
|
||||||
|
|
||||||
await createWsTag.mutateAsync({
|
await createWsTag.mutateAsync({
|
||||||
workspaceID: currentWorkspace?.id,
|
workspaceID: currentWorkspace?.id,
|
||||||
tagName: name,
|
tagSlug: slug,
|
||||||
tagSlug: name.replace(/\s+/g, " ").replace(" ", "_"),
|
|
||||||
tagColor: ""
|
tagColor: ""
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -80,11 +81,11 @@ export const AddSecretTagModal = ({ popUp, handlePopUpClose, handlePopUpToggle }
|
|||||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="name"
|
name="slug"
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl label="Tag Name" isError={Boolean(error)} errorText={error?.message}>
|
<FormControl label="Tag Slug" isError={Boolean(error)} errorText={error?.message}>
|
||||||
<Input {...field} placeholder="Type your tag name" />
|
<Input {...field} placeholder="Type your tag slug" />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
+3
-5
@@ -40,7 +40,6 @@ export const SecretTagsTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
<Table>
|
<Table>
|
||||||
<THead>
|
<THead>
|
||||||
<Tr>
|
<Tr>
|
||||||
<Th>Tag</Th>
|
|
||||||
<Th>Slug</Th>
|
<Th>Slug</Th>
|
||||||
<Th aria-label="button" />
|
<Th aria-label="button" />
|
||||||
</Tr>
|
</Tr>
|
||||||
@@ -49,9 +48,8 @@ export const SecretTagsTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
{isLoading && <TableSkeleton columns={3} innerKey="secret-tags" />}
|
{isLoading && <TableSkeleton columns={3} innerKey="secret-tags" />}
|
||||||
{!isLoading &&
|
{!isLoading &&
|
||||||
data &&
|
data &&
|
||||||
data.map(({ id, name, slug }) => (
|
data.map(({ id, slug }) => (
|
||||||
<Tr key={name}>
|
<Tr key={id}>
|
||||||
<Td>{name}</Td>
|
|
||||||
<Td>{slug}</Td>
|
<Td>{slug}</Td>
|
||||||
<Td className="flex items-center justify-end">
|
<Td className="flex items-center justify-end">
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
@@ -62,7 +60,7 @@ export const SecretTagsTable = ({ handlePopUpOpen }: Props) => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handlePopUpOpen("deleteTagConfirmation", {
|
handlePopUpOpen("deleteTagConfirmation", {
|
||||||
name,
|
name: slug,
|
||||||
id
|
id
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user