Skip to content
Open
Show file tree
Hide file tree
Changes from 214 commits
Commits
Show all changes
236 commits
Select commit Hold shift + click to select a range
940fbc4
Make tracking salt adjustable (to support MAU metric)
Blaumaus Nov 30, 2025
4492d62
Merge branch 'main' into feature/adjustable-salt-setting
Blaumaus Nov 30, 2025
b9a8cf3
Remove legacy session salt mechanism
Blaumaus Nov 30, 2025
813f8fc
Remove unused countKeysByPattern command
Blaumaus Nov 30, 2025
572d235
smaller online users window
Blaumaus Dec 2, 2025
e5e0437
Eliminate Redis from deriving session ID
Blaumaus Dec 2, 2025
ed3a5b1
remove useless comments
Blaumaus Dec 2, 2025
4a21f0c
Introduce Profiles
Blaumaus Dec 3, 2025
c832af7
Profiles UI
Blaumaus Dec 3, 2025
5d06867
fix cut input
Blaumaus Dec 5, 2025
0fe9064
profile avatars, better UI, icons & fix aggregate info
Blaumaus Dec 5, 2025
9091bfb
activity calendar tooltips
Blaumaus Dec 5, 2025
cb8ea70
z-index
Blaumaus Dec 5, 2025
af1b9a0
knip
Blaumaus Dec 5, 2025
c66f1f2
salt entity - primary storage is mysql
Blaumaus Dec 5, 2025
8a22714
instant tooltip
Blaumaus Dec 5, 2025
7757f9a
CE support
Blaumaus Dec 5, 2025
ff45d0a
Merge branch 'main' into feature/adjustable-salt-setting
Blaumaus Dec 5, 2025
e0cd659
online window: 5 minutes
Blaumaus Dec 5, 2025
52b4db5
fix: analytics ingest race condition
Blaumaus Dec 5, 2025
aab5cac
take/skip type transformation
Blaumaus Dec 5, 2025
a751950
take/skip params min/max validation
Blaumaus Dec 5, 2025
324944d
remove weekly salt; remove salt selector from project settings
Blaumaus Dec 5, 2025
906dcce
load profiles on filters change
Blaumaus Dec 5, 2025
4a2d6d5
rm duplicate noData key
Blaumaus Dec 5, 2025
b02675a
CE - profileId support
Blaumaus Dec 5, 2025
1a32cb6
remove obsolete migrations
Blaumaus Dec 5, 2025
8e86d1e
more accurate migration
Blaumaus Dec 5, 2025
83f508e
accurate session updates
Blaumaus Dec 5, 2025
d6c16fa
Minor security improvements
Blaumaus Dec 5, 2025
f42aeee
format
Blaumaus Dec 5, 2025
fedda88
Merge pull request #425 from Swetrix/feature/adjustable-salt-setting
Blaumaus Dec 5, 2025
29de77b
gray out when disabled
Blaumaus Dec 5, 2025
818ebb3
report profiles count
Blaumaus Dec 5, 2025
86409d9
Merge pull request #440 from Swetrix/feature/adjustable-salt-setting
Blaumaus Dec 5, 2025
488a175
Move horizontal tabs to a vertical sidebar
Blaumaus Dec 5, 2025
3bb2c21
Generic Text component
Blaumaus Dec 5, 2025
ace088a
ring inset
Blaumaus Dec 5, 2025
1ced297
move new project creation to a modal window
Blaumaus Dec 6, 2025
ae381b0
add MAX_PROJECT_NAME_LENGTH var
Blaumaus Dec 6, 2025
7eafd96
better project title styling
Blaumaus Dec 6, 2025
c0abe2b
better colour coding
Blaumaus Dec 6, 2025
dd5eec9
better error handling for project creation
Blaumaus Dec 6, 2025
318761b
Merge pull request #441 from Swetrix/ui-updates
Blaumaus Dec 6, 2025
81357da
Create CAPTCHA landing page
Blaumaus Dec 6, 2025
57a375c
remove PeopleLoveSwetrix block
Blaumaus Dec 6, 2025
08a1bd2
better cursor for disabled input fields
Blaumaus Dec 6, 2025
a9c1045
Merge CAPTCHA projects with regular projects
Blaumaus Dec 6, 2025
d58938c
migrate captcha to proof-of-work mechanism
Blaumaus Dec 6, 2025
573ab7f
captcha difficulty selector
Blaumaus Dec 6, 2025
efa19b0
add missing translations
Blaumaus Dec 6, 2025
3f0511c
fix lint
Blaumaus Dec 6, 2025
b463e6f
Bind PoW challenges to the issuing project to prevent cross‑project d…
Blaumaus Dec 6, 2025
e979d43
fix potential security issues
Blaumaus Dec 6, 2025
d1d0615
Merge pull request #443 from Swetrix/revive-captcha
Blaumaus Dec 6, 2025
ee47c35
Remove unused code
Blaumaus Dec 6, 2025
454b485
Goals analytics
Blaumaus Dec 6, 2025
d30ef45
better translation
Blaumaus Dec 6, 2025
7fec2fa
Better UI
Blaumaus Dec 6, 2025
224bfe7
better sidebar design
Blaumaus Dec 6, 2025
c8697b6
header height var
Blaumaus Dec 6, 2025
a2c00b9
Better Goals UI
Blaumaus Dec 6, 2025
7ad2ae0
fix loading indicator issues
Blaumaus Dec 6, 2025
effca2c
add ability to expand goal cards to see chart
Blaumaus Dec 6, 2025
312cdea
review fixes
Blaumaus Dec 7, 2025
e9b6dc7
CE goals support
Blaumaus Dec 7, 2025
deeed9d
fix potential security issues
Blaumaus Dec 7, 2025
9403f89
remove regex
Blaumaus Dec 7, 2025
03ff456
remove unused code
Blaumaus Dec 7, 2025
3e57a81
Merge pull request #446 from Swetrix/goals
Blaumaus Dec 7, 2025
96e11f1
Ask AI feature
Blaumaus Dec 7, 2025
5ac2fb7
fix incorrect tool calling
Blaumaus Dec 7, 2025
c9ba7ce
style tool calls differently
Blaumaus Dec 7, 2025
2ddfa8f
thinking process
Blaumaus Dec 7, 2025
c764c4c
medium reasoning effort
Blaumaus Dec 7, 2025
d24f3da
better autoscroll
Blaumaus Dec 7, 2025
ea2a527
better UI
Blaumaus Dec 7, 2025
cfa98e6
capabilities tooltip
Blaumaus Dec 7, 2025
7e50f77
add some logging
Blaumaus Dec 7, 2025
c3eead1
minimalistic tools display
Blaumaus Dec 7, 2025
e077bbe
google vertex provider
Blaumaus Dec 7, 2025
0a7204a
fix colours
Blaumaus Dec 7, 2025
d785912
use claude haiku
Blaumaus Dec 7, 2025
7ed290f
askAi -> ai
Blaumaus Dec 7, 2025
1f67ba3
AI chat history
Blaumaus Dec 7, 2025
1672b3e
a few improvements to the AI chat
Blaumaus Dec 8, 2025
4b59806
fix lint
Blaumaus Dec 8, 2025
79efbc2
AI fixes
Blaumaus Dec 8, 2025
9471b6a
security improvements
Blaumaus Dec 8, 2025
982ec5d
Merge pull request #447 from Swetrix/ask-ai
Blaumaus Dec 8, 2025
b3bd3ff
feat: Feature flags
Blaumaus Dec 8, 2025
ecd445d
rm attributes
Blaumaus Dec 8, 2025
70e929f
fix inaccessible endpoint
Blaumaus Dec 9, 2025
61aa5ae
use profileId instead of visitorId
Blaumaus Dec 9, 2025
c913864
fix broken refresh for feature flags tab
Blaumaus Dec 9, 2025
5999166
colour feature flags bar
Blaumaus Dec 9, 2025
392f37f
collapsible sidebar
Blaumaus Dec 9, 2025
9394bfe
visitors -> profiles
Blaumaus Dec 9, 2025
fb42ea0
remove unenforcable filters
Blaumaus Dec 9, 2025
c762b59
feature flag usage statistics
Blaumaus Dec 9, 2025
ef6b731
show served / not served feature flags
Blaumaus Dec 9, 2025
4e89bbc
use Text component
Blaumaus Dec 9, 2025
927a6fd
online status
Blaumaus Dec 9, 2025
9f96f6e
tooltip
Blaumaus Dec 10, 2025
5666b46
display profile info for sessions list
Blaumaus Dec 10, 2025
4970c6d
simplify
Blaumaus Dec 10, 2025
81a21ee
remove duplicated code
Blaumaus Dec 10, 2025
13003c5
better pageflow
Blaumaus Dec 10, 2025
71ad3ed
display metadata as a table
Blaumaus Dec 10, 2025
365392d
CE support
Blaumaus Dec 10, 2025
396745b
code refactoring
Blaumaus Dec 10, 2025
d6a1a3e
fix potential security issues
Blaumaus Dec 10, 2025
6aadc62
Merge pull request #450 from Swetrix/feature-flags
Blaumaus Dec 10, 2025
a0e882c
Update errors list & error view UI
Blaumaus Dec 10, 2025
9aeafee
move errors logic from viewproject to a dedicated component
Blaumaus Dec 10, 2025
b63a7c8
move more stuff to ErrorsView.tsx
Blaumaus Dec 10, 2025
3cce7dc
separate sessions tab
Blaumaus Dec 11, 2025
8b5c744
separate performanceview tab
Blaumaus Dec 11, 2025
1533cf6
move funnels stuff to a separate component
Blaumaus Dec 11, 2025
29c4eec
move profiles into it's own component
Blaumaus Dec 11, 2025
e6907b0
a little bit of refactoring
Blaumaus Dec 11, 2025
3af857b
lint
Blaumaus Dec 11, 2025
c788e69
format
Blaumaus Dec 11, 2025
ddf0f32
Experiments (A/B tests) feature
Blaumaus Dec 11, 2025
0fb82f0
fix experiments modal
Blaumaus Dec 11, 2025
51fd905
Better UI & more metrics
Blaumaus Dec 12, 2025
62e3cd8
Improve navigation
Blaumaus Dec 12, 2025
e9a6900
nicer bar charts
Blaumaus Dec 12, 2025
99cacc9
Sidebar positioning improvements
Blaumaus Dec 13, 2025
df35e53
position project name on the left
Blaumaus Dec 13, 2025
ecab7d3
Show proper probabilities of winning; improve experiments ingest API
Blaumaus Dec 13, 2025
d5c769d
fix mismatch in data in chart and conversions table
Blaumaus Dec 13, 2025
00e71ba
better buttons style
Blaumaus Dec 14, 2025
d9df715
lint fixes
Blaumaus Dec 14, 2025
d5ecbc3
Remove dead code
Blaumaus Dec 14, 2025
2062289
Add FUNNELS_PERIOD_PAIRS
Blaumaus Dec 14, 2025
3845f1f
fix review suggestions
Blaumaus Dec 14, 2025
2b9e281
fix potential security issues & code quality improvements
Blaumaus Dec 14, 2025
2b489e5
Merge pull request #451 from Swetrix/experiments
Blaumaus Dec 14, 2025
9e787e4
fixes
Blaumaus Dec 14, 2025
880c3b9
layout improvements
Blaumaus Dec 14, 2025
35eb448
better tables UI
Blaumaus Dec 14, 2025
3896810
Update errors UI
Blaumaus Dec 14, 2025
f07c458
Display tab hints on hover
Blaumaus Dec 14, 2025
c8441e7
Display session new / return status
Blaumaus Dec 14, 2025
9d8039b
tooltip delay: 750ms
Blaumaus Dec 14, 2025
bf52a9d
fix layout shift
Blaumaus Dec 14, 2025
8e4a1cd
Simpler sessions display within profile view
Blaumaus Dec 14, 2025
18b135f
include MAU, goals & errors into reports
Blaumaus Dec 14, 2025
2134c43
more convertable auth pages
Blaumaus Dec 14, 2025
b4adbde
Display events charts on dashboard
Blaumaus Dec 14, 2025
dec7c73
project password required - display as a modal, not a separate page
Blaumaus Dec 14, 2025
f1f72d2
remove unused pages
Blaumaus Dec 14, 2025
d9b1915
feat: pinned projects
Blaumaus Dec 14, 2025
0d2dd5b
use Text component
Blaumaus Dec 14, 2025
008979b
SSR safety check
Blaumaus Dec 14, 2025
14c47f5
nicer 'no data' display
Blaumaus Dec 14, 2025
64ca5fb
Merge branch 'swetrix-revamp' into new-reports
Blaumaus Dec 14, 2025
0c4027c
PR review fixes
Blaumaus Dec 14, 2025
b130791
Performance optimisations
Blaumaus Dec 14, 2025
959c67f
Merge pull request #453 from Swetrix/new-reports
Blaumaus Dec 14, 2025
75239ad
better placeholder
Blaumaus Dec 14, 2025
947210c
better link styling
Blaumaus Dec 14, 2025
31167f0
give signup form more space
Blaumaus Dec 14, 2025
015a0ce
add missing migrations
Blaumaus Dec 14, 2025
5cd3b6e
Clickhouse compression
Blaumaus Dec 14, 2025
f3dcc4f
better project cards display
Blaumaus Dec 14, 2025
a3484e9
better alignment
Blaumaus Dec 14, 2025
cf6cc51
better metadata overview
Blaumaus Dec 14, 2025
54ad0a2
Remove obsolete internal feature flags functionality
Blaumaus Dec 14, 2025
a840aca
remove isAnalyticsProject project flag
Blaumaus Dec 14, 2025
af55d01
Websocket based heartbeats
Blaumaus Dec 14, 2025
2b26027
display custom events on a bar chart
Blaumaus Dec 15, 2025
c7d8b16
fix: loading indicator issues
Blaumaus Dec 15, 2025
71711cc
Inset ring for buttons
Blaumaus Dec 15, 2025
f4ac2b1
stick language selector to bottom
Blaumaus Dec 15, 2025
61bbf7c
feat: Revenue analytics
Blaumaus Dec 17, 2025
77347e1
stripe integrations
Blaumaus Dec 18, 2025
129779d
fix pinned projects order
Blaumaus Dec 19, 2025
bf7ff26
frontend skill
Blaumaus Dec 19, 2025
261f2c5
better revenue UI
Blaumaus Dec 19, 2025
06a4efa
fix refunds chart
Blaumaus Dec 19, 2025
bf62065
fix heading bg colours
Blaumaus Dec 19, 2025
c56df4f
Remove manual payment logging
Blaumaus Dec 19, 2025
0001f4e
real currency conversion
Blaumaus Dec 19, 2025
cce0104
only live keys
Blaumaus Dec 19, 2025
2f81d1e
rm unsupported providers
Blaumaus Dec 19, 2025
0ff442b
proper dto typing
Blaumaus Dec 19, 2025
2b49951
use deriveKey
Blaumaus Dec 19, 2025
5175309
remove dead code
Blaumaus Dec 19, 2025
c78ed76
minor fixes
Blaumaus Dec 19, 2025
ee90662
security improvements
Blaumaus Dec 19, 2025
caf6916
lint
Blaumaus Dec 19, 2025
92e0c5b
rm unused var
Blaumaus Dec 19, 2025
0789afb
lint
Blaumaus Dec 19, 2025
615f2b2
if revenue tracking not configured for the project, don't show it as …
Blaumaus Dec 19, 2025
885ff37
Merge pull request #454 from Swetrix/revenue-analytics
Blaumaus Dec 19, 2025
4d10fb0
Add more analytics to track new features usage
Blaumaus Dec 19, 2025
d47c8a2
Redesign interactive map component
Blaumaus Dec 19, 2025
644f0af
update map colour scheme
Blaumaus Dec 19, 2025
b8996fc
better map hover outline colour
Blaumaus Dec 19, 2025
c9abc82
proper map fullscreen size
Blaumaus Dec 19, 2025
0439d22
Better no events yet banner
Blaumaus Dec 19, 2025
5449705
Exclude null regions & cities
Blaumaus Dec 19, 2025
8be0458
better no data view for empty panel
Blaumaus Dec 19, 2025
8bab537
fix devices overview
Blaumaus Dec 19, 2025
f9a274d
minor patch to be in line with cloud edition
Blaumaus Dec 19, 2025
ac4b72e
Security improvements & code hardening
Blaumaus Dec 20, 2025
6d79b5f
rm deletedAt
Blaumaus Dec 20, 2025
fa8eb7b
lint
Blaumaus Dec 20, 2025
88c6883
rm dup comments
Blaumaus Dec 20, 2025
59cae9c
add missing analytics
Blaumaus Dec 20, 2025
0739a8c
rm non existent branch
Blaumaus Dec 20, 2025
12c6f70
bounce rate fix
Blaumaus Dec 20, 2025
aa75e9c
better types
Blaumaus Dec 20, 2025
d369412
add IsOptional
Blaumaus Dec 20, 2025
09de7c1
better filtering query
Blaumaus Dec 20, 2025
d6d11a5
code refactoring
Blaumaus Dec 20, 2025
445841a
check if revenue is enabled
Blaumaus Dec 20, 2025
d4b6ecd
better code
Blaumaus Dec 21, 2025
73764ff
better type validation
Blaumaus Dec 21, 2025
2e93a42
improve sidebar UI
Blaumaus Dec 21, 2025
e8f4fe0
fix sessions list crash
Blaumaus Dec 21, 2025
315a5ac
misc UI improvements
Blaumaus Dec 21, 2025
56f9f86
Restructuring
Blaumaus Dec 21, 2025
13ee80f
Show examples of Revenue analytics, Feature flags and A/B testing & e…
Blaumaus Dec 21, 2025
5e80f9f
rm unused translations
Blaumaus Dec 21, 2025
641cc1e
remove useless spacing
Blaumaus Dec 21, 2025
73175e7
less cluttered alerts view
Blaumaus Dec 21, 2025
fb9da11
nicer link display
Blaumaus Dec 21, 2025
f610aa1
fix linting
Blaumaus Dec 21, 2025
5233717
use transactions
Blaumaus Dec 21, 2025
58c1846
add missing import
Blaumaus Dec 21, 2025
57b329c
smaller gap
Blaumaus Dec 21, 2025
90ee5ae
add missing helpers
Blaumaus Dec 21, 2025
c3ca506
fix: optional auth
Blaumaus Dec 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
45 changes: 45 additions & 0 deletions .claude/frontend-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
name: frontend-design
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS/Tailwind CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics.
license: Apache License
---

This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.

The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.

## Design Thinking

Before coding, understand the context and commit to a BOLD aesthetic direction:

- **Purpose**: What problem does this interface solve? Who uses it?
- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
- **Constraints**: Technical requirements (framework, performance, accessibility).
- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?

**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.

Then implement working code (HTML/CSS/JS, Tailwind CSS, React, Vue, etc.) that is:

- Production-grade and functional
- Visually striking and memorable
- Cohesive with a clear aesthetic point-of-view
- Meticulously refined in every detail

## Frontend Aesthetics Guidelines

Focus on:

- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
- **Color & Theme**: Commit to a cohesive aesthetic. Use Tailwind CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS/Tailwind CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.

NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.

Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.

**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.

Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.
8 changes: 5 additions & 3 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ OIDC_CLIENT_SECRET=
CDN_URL=http://localhost:5006
CDN_ACCESS_TOKEN=SOME_SECRET_TOKEN

# Captcha API
CAPTCHA_SALT=

# Google SSO
GOOGLE_OAUTH2_CLIENT_ID=
GOOGLE_OAUTH2_CLIENT_SECRET=
Expand Down Expand Up @@ -100,3 +97,8 @@ EMAIL_ACTION_ENCRYPTION_KEY=

# Swetrix Enterprise (Cloud)
SWETRIX_LICENSE_KEY=

# OpenRouter API key for AI features
OPENROUTER_API_KEY=

SWETRIX_PID=STEzHcB1rALV
10 changes: 3 additions & 7 deletions backend/apps/cloud/src/action-tokens/action-tokens.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository } from 'typeorm'
import { Repository, FindOptionsWhere } from 'typeorm'

import { ActionToken, ActionTokenType } from './action-token.entity'
import { User } from '../user/entities/user.entity'
Expand All @@ -12,12 +12,8 @@ export class ActionTokensService {
private actionTokensRepository: Repository<ActionToken>,
) {}

async deleteMultiple(where: string): Promise<any> {
return this.actionTokensRepository
.createQueryBuilder()
.delete()
.where(where)
.execute()
async deleteMultiple(where: FindOptionsWhere<ActionToken>): Promise<any> {
return this.actionTokensRepository.delete(where)
}

async createForUser(
Expand Down
194 changes: 194 additions & 0 deletions backend/apps/cloud/src/ai/ai-chat.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { Repository, FindManyOptions, FindOneOptions } from 'typeorm'
import { AiChat, ChatMessage } from './entity/ai-chat.entity'

@Injectable()
export class AiChatService {
constructor(
@InjectRepository(AiChat)
private aiChatRepository: Repository<AiChat>,
) {}

async findOne(options: FindOneOptions<AiChat>): Promise<AiChat | null> {
return this.aiChatRepository.findOne(options)
}

async find(options: FindManyOptions<AiChat>): Promise<AiChat[]> {
return this.aiChatRepository.find(options)
}

async findRecentByProject(
projectId: string,
userId: string | null,
limit: number = 5,
): Promise<AiChat[]> {
// Do not return stored chats to unauthenticated users.
// (Public project analytics may be viewable, but chats contain user prompts and context.)
if (!userId) {
return []
}

const queryBuilder = this.aiChatRepository
.createQueryBuilder('chat')
.where('chat.projectId = :projectId', { projectId })
.orderBy('chat.updated', 'DESC')
.take(limit)

queryBuilder.andWhere('(chat.userId = :userId OR chat.userId IS NULL)', {
userId,
})

return queryBuilder.getMany()
}

async findAllByProject(
projectId: string,
userId: string | null,
skip: number = 0,
take: number = 20,
): Promise<{ chats: AiChat[]; total: number }> {
// Do not return stored chats to unauthenticated users.
if (!userId) {
return { chats: [], total: 0 }
}

const queryBuilder = this.aiChatRepository
.createQueryBuilder('chat')
.where('chat.projectId = :projectId', { projectId })
.orderBy('chat.updated', 'DESC')
.skip(skip)
.take(take)

queryBuilder.andWhere('(chat.userId = :userId OR chat.userId IS NULL)', {
userId,
})

const [chats, total] = await queryBuilder.getManyAndCount()
return { chats, total }
}

async create(data: {
projectId: string
userId: string | null
messages: ChatMessage[]
name?: string
}): Promise<AiChat> {
const chat = this.aiChatRepository.create({
project: { id: data.projectId },
user: data.userId ? { id: data.userId } : null,
messages: data.messages,
name: data.name || this.generateChatName(data.messages),
})
return this.aiChatRepository.save(chat)
}

async update(
id: string,
data: { messages?: ChatMessage[]; name?: string },
): Promise<AiChat | null> {
const chat = await this.aiChatRepository.findOne({ where: { id } })
if (!chat) return null

if (data.messages) {
const previousMessages = chat.messages
chat.messages = data.messages
// Update name if not manually set and we have a new first user message
if (!chat.name || chat.name === this.generateChatName(previousMessages)) {
chat.name = this.generateChatName(data.messages)
}
}
if (data.name !== undefined) {
chat.name = data.name
}

return this.aiChatRepository.save(chat)
}

async delete(id: string): Promise<boolean> {
const result = await this.aiChatRepository.delete(id)
return (result.affected ?? 0) > 0
}

private generateChatName(messages: ChatMessage[]): string {
// Use the first user message as the chat name
const firstUserMessage = messages.find(m => m.role === 'user')
if (firstUserMessage) {
const content = firstUserMessage.content.trim()
// Truncate to reasonable length
return content.length > 100 ? content.slice(0, 97) + '...' : content
}
return 'New conversation'
}

async verifyAccess(
chatId: string,
projectId: string,
userId: string | null,
): Promise<AiChat | null> {
// Explicitly require authentication for user-scoped access checks.
if (!userId) return null

const queryBuilder = this.aiChatRepository
.createQueryBuilder('chat')
.where('chat.id = :chatId', { chatId })
.andWhere('chat.projectId = :projectId', { projectId })

queryBuilder.andWhere('(chat.userId = :userId OR chat.userId IS NULL)', {
userId,
})

return queryBuilder.getOne()
}

/**
* Verifies ownership of a chat (authenticated users only).
* Used for update/delete operations to prevent modifying shared (NULL owner) chats.
*/
async verifyOwnerAccess(
chatId: string,
projectId: string,
userId: string | null,
): Promise<AiChat | null> {
if (!userId) return null

return this.aiChatRepository
.createQueryBuilder('chat')
.where('chat.id = :chatId', { chatId })
.andWhere('chat.projectId = :projectId', { projectId })
.andWhere('chat.userId = :userId', { userId })
.getOne()
}

/**
* Verifies access to an anonymously-owned chat (userId IS NULL) by chatId+projectId.
* This supports share-by-id without allowing listing/enumeration.
*/
async verifyPublicChatAccess(
chatId: string,
projectId: string,
): Promise<AiChat | null> {
return this.aiChatRepository
.createQueryBuilder('chat')
.where('chat.id = :chatId', { chatId })
.andWhere('chat.projectId = :projectId', { projectId })
.andWhere('chat.userId IS NULL')
.getOne()
}

/**
* Verify that a chat belongs to a project (without checking user ownership).
* Used for shared chat links where anyone who can view the project can access the chat.
*/
async verifyProjectAccess(
chatId: string,
projectId: string,
): Promise<AiChat | null> {
return this.aiChatRepository.findOne({
where: {
id: chatId,
project: { id: projectId },
},
})
}
}
Loading
Loading