Skip to content

Commit af114c3

Browse files
authored
Merge branch 'staging' into waleedlatif1/google-contacts
2 parents 49a6ff2 + 1f3dc52 commit af114c3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3136
-28
lines changed

apps/docs/components/icons.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5466,6 +5466,34 @@ export function GoogleMapsIcon(props: SVGProps<SVGSVGElement>) {
54665466
)
54675467
}
54685468

5469+
export function GoogleTranslateIcon(props: SVGProps<SVGSVGElement>) {
5470+
return (
5471+
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 998.1 998.3'>
5472+
<path
5473+
fill='#DBDBDB'
5474+
d='M931.7 998.3c36.5 0 66.4-29.4 66.4-65.4V265.8c0-36-29.9-65.4-66.4-65.4H283.6l260.1 797.9h388z'
5475+
/>
5476+
<path
5477+
fill='#DCDCDC'
5478+
d='M931.7 230.4c9.7 0 18.9 3.8 25.8 10.6 6.8 6.7 10.6 15.5 10.6 24.8v667.1c0 9.3-3.7 18.1-10.6 24.8-6.9 6.8-16.1 10.6-25.8 10.6H565.5L324.9 230.4h606.8m0-30H283.6l260.1 797.9h388c36.5 0 66.4-29.4 66.4-65.4V265.8c0-36-29.9-65.4-66.4-65.4z'
5479+
/>
5480+
<polygon fill='#4352B8' points='482.3,809.8 543.7,998.3 714.4,809.8' />
5481+
<path
5482+
fill='#607988'
5483+
d='M936.1 476.1V437H747.6v-63.2h-61.2V437H566.1v39.1h239.4c-12.8 45.1-41.1 87.7-68.7 120.8-48.9-57.9-49.1-76.7-49.1-76.7h-50.8s2.1 28.2 70.7 108.6c-22.3 22.8-39.2 36.3-39.2 36.3l15.6 48.8s23.6-20.3 53.1-51.6c29.6 32.1 67.8 70.7 117.2 116.7l32.1-32.1c-52.9-48-91.7-86.1-120.2-116.7 38.2-45.2 77-102.1 85.2-154.2H936v.1z'
5484+
/>
5485+
<path
5486+
fill='#4285F4'
5487+
d='M66.4 0C29.9 0 0 29.9 0 66.5v677c0 36.5 29.9 66.4 66.4 66.4h648.1L454.4 0h-388z'
5488+
/>
5489+
<path
5490+
fill='#EEEEEE'
5491+
d='M371.4 430.6c-2.5 30.3-28.4 75.2-91.1 75.2-54.3 0-98.3-44.9-98.3-100.2s44-100.2 98.3-100.2c30.9 0 51.5 13.4 63.3 24.3l41.2-39.6c-27.1-25-62.4-40.6-104.5-40.6-86.1 0-156 69.9-156 156s69.9 156 156 156c90.2 0 149.8-63.3 149.8-152.6 0-12.8-1.6-22.2-3.7-31.8h-146v53.4l91 .1z'
5492+
/>
5493+
</svg>
5494+
)
5495+
}
5496+
54695497
export function DsPyIcon(props: SVGProps<SVGSVGElement>) {
54705498
return (
54715499
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='30 28 185 175' fill='none'>

apps/docs/components/ui/icon-mapping.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import {
5353
GoogleMapsIcon,
5454
GoogleSheetsIcon,
5555
GoogleSlidesIcon,
56+
GoogleTranslateIcon,
5657
GoogleVaultIcon,
5758
GrafanaIcon,
5859
GrainIcon,
@@ -199,6 +200,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
199200
google_search: GoogleIcon,
200201
google_sheets_v2: GoogleSheetsIcon,
201202
google_slides_v2: GoogleSlidesIcon,
203+
google_translate: GoogleTranslateIcon,
202204
google_vault: GoogleVaultIcon,
203205
grafana: GrafanaIcon,
204206
grain: GrainIcon,

apps/docs/content/docs/en/tools/confluence.mdx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,8 @@ Get details about a specific version of a Confluence page.
326326
| --------- | ---- | ----------- |
327327
| `ts` | string | ISO 8601 timestamp of the operation |
328328
| `pageId` | string | ID of the page |
329+
| `title` | string | Page title at this version |
330+
| `content` | string | Page content with HTML tags stripped at this version |
329331
| `version` | object | Detailed version information |
330332
|`number` | number | Version number |
331333
|`message` | string | Version message |
@@ -336,6 +338,9 @@ Get details about a specific version of a Confluence page.
336338
|`collaborators` | array | List of collaborator account IDs for this version |
337339
|`prevVersion` | number | Previous version number |
338340
|`nextVersion` | number | Next version number |
341+
| `body` | object | Raw page body content in storage format at this version |
342+
|`value` | string | The content value in the specified format |
343+
|`representation` | string | Content representation type |
339344

340345
### `confluence_list_page_properties`
341346

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
---
2+
title: Google Translate
3+
description: Translate text using Google Cloud Translation
4+
---
5+
6+
import { BlockInfoCard } from "@/components/ui/block-info-card"
7+
8+
<BlockInfoCard
9+
type="google_translate"
10+
color="#E0E0E0"
11+
/>
12+
13+
## Usage Instructions
14+
15+
Translate and detect languages using the Google Cloud Translation API. Supports auto-detection of the source language.
16+
17+
18+
19+
## Tools
20+
21+
### `google_translate_text`
22+
23+
Translate text between languages using the Google Cloud Translation API. Supports auto-detection of the source language.
24+
25+
#### Input
26+
27+
| Parameter | Type | Required | Description |
28+
| --------- | ---- | -------- | ----------- |
29+
| `apiKey` | string | Yes | Google Cloud API key with Cloud Translation API enabled |
30+
| `text` | string | Yes | The text to translate |
31+
| `target` | string | Yes | Target language code \(e.g., "es", "fr", "de", "ja"\) |
32+
| `source` | string | No | Source language code. If omitted, the API will auto-detect the source language. |
33+
| `format` | string | No | Format of the text: "text" for plain text, "html" for HTML content |
34+
35+
#### Output
36+
37+
| Parameter | Type | Description |
38+
| --------- | ---- | ----------- |
39+
| `translatedText` | string | The translated text |
40+
| `detectedSourceLanguage` | string | The detected source language code \(if source was not specified\) |
41+
42+
### `google_translate_detect`
43+
44+
Detect the language of text using the Google Cloud Translation API.
45+
46+
#### Input
47+
48+
| Parameter | Type | Required | Description |
49+
| --------- | ---- | -------- | ----------- |
50+
| `apiKey` | string | Yes | Google Cloud API key with Cloud Translation API enabled |
51+
| `text` | string | Yes | The text to detect the language of |
52+
53+
#### Output
54+
55+
| Parameter | Type | Description |
56+
| --------- | ---- | ----------- |
57+
| `language` | string | The detected language code \(e.g., "en", "es", "fr"\) |
58+
| `confidence` | number | Confidence score of the detection |
59+
60+

apps/docs/content/docs/en/tools/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"google_search",
4949
"google_sheets",
5050
"google_slides",
51+
"google_translate",
5152
"google_vault",
5253
"grafana",
5354
"grain",

apps/sim/app/api/tools/confluence/page-versions/route.ts

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { checkSessionOrInternalAuth } from '@/lib/auth/hybrid'
44
import { validateAlphanumericId, validateJiraCloudId } from '@/lib/core/security/input-validation'
5-
import { getConfluenceCloudId } from '@/tools/confluence/utils'
5+
import { cleanHtmlContent, getConfluenceCloudId } from '@/tools/confluence/utils'
66

77
const logger = createLogger('ConfluencePageVersionsAPI')
88

@@ -55,42 +55,79 @@ export async function POST(request: NextRequest) {
5555
return NextResponse.json({ error: cloudIdValidation.error }, { status: 400 })
5656
}
5757

58-
// If versionNumber is provided, get specific version
58+
// If versionNumber is provided, get specific version with page content
5959
if (versionNumber !== undefined && versionNumber !== null) {
60-
const url = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/pages/${pageId}/versions/${versionNumber}`
60+
const versionUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/pages/${pageId}/versions/${versionNumber}`
61+
const pageUrl = `https://api.atlassian.com/ex/confluence/${cloudId}/wiki/api/v2/pages/${pageId}?version=${versionNumber}&body-format=storage`
6162

6263
logger.info(`Fetching version ${versionNumber} for page ${pageId}`)
6364

64-
const response = await fetch(url, {
65-
method: 'GET',
66-
headers: {
67-
Accept: 'application/json',
68-
Authorization: `Bearer ${accessToken}`,
69-
},
70-
})
71-
72-
if (!response.ok) {
73-
const errorData = await response.json().catch(() => null)
65+
const [versionResponse, pageResponse] = await Promise.all([
66+
fetch(versionUrl, {
67+
method: 'GET',
68+
headers: {
69+
Accept: 'application/json',
70+
Authorization: `Bearer ${accessToken}`,
71+
},
72+
}),
73+
fetch(pageUrl, {
74+
method: 'GET',
75+
headers: {
76+
Accept: 'application/json',
77+
Authorization: `Bearer ${accessToken}`,
78+
},
79+
}),
80+
])
81+
82+
if (!versionResponse.ok) {
83+
const errorData = await versionResponse.json().catch(() => null)
7484
logger.error('Confluence API error response:', {
75-
status: response.status,
76-
statusText: response.statusText,
85+
status: versionResponse.status,
86+
statusText: versionResponse.statusText,
7787
error: JSON.stringify(errorData, null, 2),
7888
})
79-
const errorMessage = errorData?.message || `Failed to get page version (${response.status})`
80-
return NextResponse.json({ error: errorMessage }, { status: response.status })
89+
const errorMessage =
90+
errorData?.message || `Failed to get page version (${versionResponse.status})`
91+
return NextResponse.json({ error: errorMessage }, { status: versionResponse.status })
8192
}
8293

83-
const data = await response.json()
94+
const versionData = await versionResponse.json()
95+
96+
let title: string | null = null
97+
let content: string | null = null
98+
let body: Record<string, unknown> | null = null
99+
100+
if (pageResponse.ok) {
101+
const pageData = await pageResponse.json()
102+
title = pageData.title ?? null
103+
body = pageData.body ?? null
104+
105+
const rawContent =
106+
pageData.body?.storage?.value ||
107+
pageData.body?.view?.value ||
108+
pageData.body?.atlas_doc_format?.value ||
109+
''
110+
if (rawContent) {
111+
content = cleanHtmlContent(rawContent)
112+
}
113+
} else {
114+
logger.warn(
115+
`Could not fetch page content for version ${versionNumber}: ${pageResponse.status}`
116+
)
117+
}
84118

85119
return NextResponse.json({
86120
version: {
87-
number: data.number,
88-
message: data.message ?? null,
89-
minorEdit: data.minorEdit ?? false,
90-
authorId: data.authorId ?? null,
91-
createdAt: data.createdAt ?? null,
121+
number: versionData.number,
122+
message: versionData.message ?? null,
123+
minorEdit: versionData.minorEdit ?? false,
124+
authorId: versionData.authorId ?? null,
125+
createdAt: versionData.createdAt ?? null,
92126
},
93127
pageId,
128+
title,
129+
content,
130+
body,
94131
})
95132
}
96133
// List all versions
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* GET /api/v1/admin/audit-logs/[id]
3+
*
4+
* Get a single audit log entry by ID.
5+
*
6+
* Response: AdminSingleResponse<AdminAuditLog>
7+
*/
8+
9+
import { db } from '@sim/db'
10+
import { auditLog } from '@sim/db/schema'
11+
import { createLogger } from '@sim/logger'
12+
import { eq } from 'drizzle-orm'
13+
import { withAdminAuthParams } from '@/app/api/v1/admin/middleware'
14+
import {
15+
internalErrorResponse,
16+
notFoundResponse,
17+
singleResponse,
18+
} from '@/app/api/v1/admin/responses'
19+
import { toAdminAuditLog } from '@/app/api/v1/admin/types'
20+
21+
const logger = createLogger('AdminAuditLogDetailAPI')
22+
23+
interface RouteParams {
24+
id: string
25+
}
26+
27+
export const GET = withAdminAuthParams<RouteParams>(async (request, context) => {
28+
const { id } = await context.params
29+
30+
try {
31+
const [log] = await db.select().from(auditLog).where(eq(auditLog.id, id)).limit(1)
32+
33+
if (!log) {
34+
return notFoundResponse('AuditLog')
35+
}
36+
37+
logger.info(`Admin API: Retrieved audit log ${id}`)
38+
39+
return singleResponse(toAdminAuditLog(log))
40+
} catch (error) {
41+
logger.error('Admin API: Failed to get audit log', { error, id })
42+
return internalErrorResponse('Failed to get audit log')
43+
}
44+
})
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* GET /api/v1/admin/audit-logs
3+
*
4+
* List all audit logs with pagination and filtering.
5+
*
6+
* Query Parameters:
7+
* - limit: number (default: 50, max: 250)
8+
* - offset: number (default: 0)
9+
* - action: string (optional) - Filter by action (e.g., "workflow.created")
10+
* - resourceType: string (optional) - Filter by resource type (e.g., "workflow")
11+
* - resourceId: string (optional) - Filter by resource ID
12+
* - workspaceId: string (optional) - Filter by workspace ID
13+
* - actorId: string (optional) - Filter by actor user ID
14+
* - actorEmail: string (optional) - Filter by actor email
15+
* - startDate: string (optional) - ISO 8601 date, filter createdAt >= startDate
16+
* - endDate: string (optional) - ISO 8601 date, filter createdAt <= endDate
17+
*
18+
* Response: AdminListResponse<AdminAuditLog>
19+
*/
20+
21+
import { db } from '@sim/db'
22+
import { auditLog } from '@sim/db/schema'
23+
import { createLogger } from '@sim/logger'
24+
import { and, count, desc, eq, gte, lte, type SQL } from 'drizzle-orm'
25+
import { withAdminAuth } from '@/app/api/v1/admin/middleware'
26+
import {
27+
badRequestResponse,
28+
internalErrorResponse,
29+
listResponse,
30+
} from '@/app/api/v1/admin/responses'
31+
import {
32+
type AdminAuditLog,
33+
createPaginationMeta,
34+
parsePaginationParams,
35+
toAdminAuditLog,
36+
} from '@/app/api/v1/admin/types'
37+
38+
const logger = createLogger('AdminAuditLogsAPI')
39+
40+
export const GET = withAdminAuth(async (request) => {
41+
const url = new URL(request.url)
42+
const { limit, offset } = parsePaginationParams(url)
43+
44+
const actionFilter = url.searchParams.get('action')
45+
const resourceTypeFilter = url.searchParams.get('resourceType')
46+
const resourceIdFilter = url.searchParams.get('resourceId')
47+
const workspaceIdFilter = url.searchParams.get('workspaceId')
48+
const actorIdFilter = url.searchParams.get('actorId')
49+
const actorEmailFilter = url.searchParams.get('actorEmail')
50+
const startDateFilter = url.searchParams.get('startDate')
51+
const endDateFilter = url.searchParams.get('endDate')
52+
53+
if (startDateFilter && Number.isNaN(Date.parse(startDateFilter))) {
54+
return badRequestResponse('Invalid startDate format. Use ISO 8601.')
55+
}
56+
if (endDateFilter && Number.isNaN(Date.parse(endDateFilter))) {
57+
return badRequestResponse('Invalid endDate format. Use ISO 8601.')
58+
}
59+
60+
try {
61+
const conditions: SQL<unknown>[] = []
62+
63+
if (actionFilter) conditions.push(eq(auditLog.action, actionFilter))
64+
if (resourceTypeFilter) conditions.push(eq(auditLog.resourceType, resourceTypeFilter))
65+
if (resourceIdFilter) conditions.push(eq(auditLog.resourceId, resourceIdFilter))
66+
if (workspaceIdFilter) conditions.push(eq(auditLog.workspaceId, workspaceIdFilter))
67+
if (actorIdFilter) conditions.push(eq(auditLog.actorId, actorIdFilter))
68+
if (actorEmailFilter) conditions.push(eq(auditLog.actorEmail, actorEmailFilter))
69+
if (startDateFilter) conditions.push(gte(auditLog.createdAt, new Date(startDateFilter)))
70+
if (endDateFilter) conditions.push(lte(auditLog.createdAt, new Date(endDateFilter)))
71+
72+
const whereClause = conditions.length > 0 ? and(...conditions) : undefined
73+
74+
const [countResult, logs] = await Promise.all([
75+
db.select({ total: count() }).from(auditLog).where(whereClause),
76+
db
77+
.select()
78+
.from(auditLog)
79+
.where(whereClause)
80+
.orderBy(desc(auditLog.createdAt))
81+
.limit(limit)
82+
.offset(offset),
83+
])
84+
85+
const total = countResult[0].total
86+
const data: AdminAuditLog[] = logs.map(toAdminAuditLog)
87+
const pagination = createPaginationMeta(total, limit, offset)
88+
89+
logger.info(`Admin API: Listed ${data.length} audit logs (total: ${total})`)
90+
91+
return listResponse(data, pagination)
92+
} catch (error) {
93+
logger.error('Admin API: Failed to list audit logs', { error })
94+
return internalErrorResponse('Failed to list audit logs')
95+
}
96+
})

0 commit comments

Comments
 (0)