Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
11c6b9b
SEO & GEO insights
Blaumaus Apr 5, 2026
e4798f2
Pull devices & countries from GSC
Blaumaus Apr 5, 2026
ce68772
fix: Yandex favicon display
Blaumaus Apr 5, 2026
153add4
fix: Brave favicon display
Blaumaus Apr 5, 2026
b1fc247
Update translations
Blaumaus Apr 5, 2026
f0327dd
Branded vs Non-branded traffic analytics
Blaumaus Apr 6, 2026
3f6f5e2
feat: SEO Quadrant
Blaumaus Apr 7, 2026
13d7986
Better UI
Blaumaus Apr 7, 2026
8b619ee
Remove keywords panel
Blaumaus Apr 7, 2026
da944fe
Move SEO up
Blaumaus Apr 7, 2026
67a4e5e
SEO mode docs
Blaumaus Apr 7, 2026
e5e76d6
Remove unused code
Blaumaus Apr 7, 2026
cf18500
Minor fixes & improvements
Blaumaus Apr 7, 2026
a165318
fix: OpenAPI schema
Blaumaus Apr 7, 2026
0f0f217
fix: Filter out blank brand keywords before serializing them
Blaumaus Apr 7, 2026
135966c
fix: Only return brandKeywords when the JSON decodes to string[]
Blaumaus Apr 7, 2026
0df29b0
fix: Gate brandKeywords behind the cloud-only settings path
Blaumaus Apr 7, 2026
978b658
fix: Escape query names before returning tooltip HTML to prevent XSS
Blaumaus Apr 7, 2026
4e96969
improvement: Allow callers to intentionally clear the default content…
Blaumaus Apr 7, 2026
0f0f9a9
fix: Keep getGSCDetails on the typed params contract
Blaumaus Apr 7, 2026
0be694e
Avoid deriving brand keywords from the penultimate hostname label
Blaumaus Apr 7, 2026
2d899ac
fix: Preserve the timezone on hourly bucket keys
Blaumaus Apr 7, 2026
9b05191
Lint
Blaumaus Apr 7, 2026
a410fbc
fix: previous-period window calculation
Blaumaus Apr 7, 2026
318add9
Better validation
Blaumaus Apr 7, 2026
fde3934
Security improvements & refactoring
Blaumaus Apr 7, 2026
1ed651e
Remove comments
Blaumaus Apr 7, 2026
c09e0d8
Throw on incorrect filters
Blaumaus Apr 7, 2026
4a4ddef
Merge branch 'main' into feature/seo-geo
Blaumaus Apr 7, 2026
7e9c4c4
Place SEO tab above Perfomance
Blaumaus Apr 7, 2026
c66da93
SEO translations
Blaumaus Apr 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions backend/apps/cloud/src/analytics/analytics.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,150 @@ export class AnalyticsController {
return { keywords }
}

@Get('gsc-dashboard')
@Auth(true, true)
async getGSCDashboard(
@Query() data: GetKeywordsDto,
@CurrentUserId() uid: string,
@Headers() headers: { 'x-password'?: string },
) {
const {
pid,
period,
from,
to,
timezone = DEFAULT_TIMEZONE,
timeBucket,
filters,
} = data

await this.analyticsService.checkProjectAccess(
pid,
uid,
headers['x-password'],
)

await this.analyticsService.checkBillingAccess(pid)

const finalTimeBucket =
timeBucket ||
(['1h', 'today', 'yesterday', '1d'].includes(period) ? 'hour' : 'day')

this.logger.log(
`pid: ${pid}, period: ${period}, timeBucket: ${finalTimeBucket}, filters: ${filters}`,
'GET /analytics/gsc-dashboard',
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

let diff

if (period === 'all') {
const res = await this.analyticsService.calculateTimeBucketForAllTime(
pid,
'analytics',
)

diff = res.diff
}

const safeTimezone = this.analyticsService.getSafeTimezone(timezone)
const { groupFrom, groupTo } = this.analyticsService.getGroupFromTo(
from,
to,
null,
period,
safeTimezone,
diff,
)

return this.gscService.getDashboard(
pid,
groupFrom,
groupTo,
finalTimeBucket,
filters,
)
}

@Get('gsc-details')
@Auth(true, true)
async getGSCDetails(
@Query() data: GetKeywordsDto,
@CurrentUserId() uid: string,
@Headers() headers: { 'x-password'?: string },
) {
const {
pid,
period,
from,
to,
timezone = DEFAULT_TIMEZONE,
page,
query,
} = data

this.logger.log(
`pid: ${pid}, period: ${period}, page: ${page}, query: ${query}`,
'GET /analytics/gsc-details',
)

await this.analyticsService.checkProjectAccess(
pid,
uid,
headers['x-password'],
)

await this.analyticsService.checkBillingAccess(pid)

let diff

if (period === 'all') {
const res = await this.analyticsService.calculateTimeBucketForAllTime(
pid,
'analytics',
)

diff = res.diff
}

const safeTimezone = this.analyticsService.getSafeTimezone(timezone)
const { groupFrom, groupTo } = this.analyticsService.getGroupFromTo(
from,
to,
null,
period,
safeTimezone,
diff,
)

if (page) {
const keywords = await this.gscService.getKeywords(
pid,
groupFrom,
groupTo,
50,
0,
undefined,
page,
)
return { type: 'queries', data: keywords }
}

if (query) {
const pages = await this.gscService.getTopPages(
pid,
groupFrom,
groupTo,
50,
0,
undefined,
query,
)
return { type: 'pages', data: pages }
}

return { type: 'none', data: [] }
}

@Get('birdseye')
@Auth(true, true)
// returns overall short statistics per project
Expand Down
16 changes: 16 additions & 0 deletions backend/apps/cloud/src/analytics/dto/get-keywords.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,20 @@ export class GetKeywordsDto {
@IsOptional()
@IsString()
timezone?: string

@IsOptional()
@IsString()
timeBucket?: string

@IsOptional()
@IsString()
filters?: string

@IsOptional()
@IsString()
page?: string

@IsOptional()
@IsString()
query?: string
}
13 changes: 13 additions & 0 deletions backend/apps/cloud/src/project/dto/project.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
IsEnum,
IsOptional,
IsArray,
IsString,
IsUrl,
MaxLength,
ValidateIf,
Expand Down Expand Up @@ -87,4 +88,16 @@ export class ProjectDTO {
@IsUrl({}, { message: 'websiteUrl must be a valid URL' })
@MaxLength(512)
websiteUrl?: string | null

@ApiProperty({
example: ['swetrix', 'swetrix analytics'],
required: false,
description:
'Brand keywords used to classify search queries as branded vs non-branded traffic.',
})
@IsOptional()
@ValidateIf((o) => o.brandKeywords !== null && o.brandKeywords !== undefined)
@IsArray()
@IsString({ each: true })
brandKeywords?: string[] | null
}
4 changes: 4 additions & 0 deletions backend/apps/cloud/src/project/entity/project.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,4 +179,8 @@ export class Project {
@ApiProperty()
@Column('varchar', { nullable: true, default: null, length: 512 })
websiteUrl: string | null

@ApiProperty({ type: [String], nullable: true })
@Column('text', { nullable: true, default: null })
brandKeywords: string | null
}
Loading
Loading