Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions .github/workflows/build-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ jobs:
LOG_TAGS="${REGISTRY}/flowly/log-consumer:${TAG}"
AI_API_TAGS="${REGISTRY}/flowly/ai-api:${TAG}"
AI_WORKER_TAGS="${REGISTRY}/flowly/ai-worker:${TAG}"
AI_INDEXER_TAGS="${REGISTRY}/flowly/ai-indexer:${TAG}"

# Add latest tag if requested
if [ "${{ inputs.push_latest }}" = "true" ]; then
Expand All @@ -103,6 +104,7 @@ jobs:
LOG_TAGS="${LOG_TAGS}"$'\n'"${REGISTRY}/flowly/log-consumer:${ENV}-latest"
AI_API_TAGS="${AI_API_TAGS}"$'\n'"${REGISTRY}/flowly/ai-api:${ENV}-latest"
AI_WORKER_TAGS="${AI_WORKER_TAGS}"$'\n'"${REGISTRY}/flowly/ai-worker:${ENV}-latest"
AI_INDEXER_TAGS="${AI_INDEXER_TAGS}"$'\n'"${REGISTRY}/flowly/ai-indexer:${ENV}-latest"
fi

{
Expand All @@ -124,6 +126,9 @@ jobs:
echo "ai_worker_tags<<EOF"
echo "${AI_WORKER_TAGS}"
echo "EOF"
echo "ai_indexer_tags<<EOF"
echo "${AI_INDEXER_TAGS}"
echo "EOF"
} >> $GITHUB_OUTPUT

- name: Build and push NestJS API image
Expand Down Expand Up @@ -195,6 +200,17 @@ jobs:
cache-from: type=gha,scope=ai-worker
cache-to: type=gha,mode=max,scope=ai-worker

- name: Build and push AI Indexer image
uses: docker/build-push-action@v5
with:
context: apps/ai-agent
file: apps/ai-agent/ai-indexer/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ steps.prepare-tags.outputs.ai_indexer_tags }}
cache-from: type=gha,scope=ai-indexer
cache-to: type=gha,mode=max,scope=ai-indexer

- name: Scan images for vulnerabilities
uses: aquasecurity/trivy-action@master
with:
Expand All @@ -220,3 +236,4 @@ jobs:
echo "| Log Consumer | \`${{ steps.login-ecr.outputs.registry }}/flowly/log-consumer:${{ steps.set-tag.outputs.image_tag }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| AI API | \`${{ steps.login-ecr.outputs.registry }}/flowly/ai-api:${{ steps.set-tag.outputs.image_tag }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| AI Worker | \`${{ steps.login-ecr.outputs.registry }}/flowly/ai-worker:${{ steps.set-tag.outputs.image_tag }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| AI Indexer | \`${{ steps.login-ecr.outputs.registry }}/flowly/ai-indexer:${{ steps.set-tag.outputs.image_tag }}\` |" >> $GITHUB_STEP_SUMMARY
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ jobs:
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --sarif-file-output=snyk-api-springboot.sarif --file=apps/api-springboot/build.gradle.kts
args: --sarif-file-output=snyk-api-springboot.sarif --file=apps/api-springboot/build.gradle
command: test

- name: Upload Snyk results (api-springboot)
Expand Down
14 changes: 8 additions & 6 deletions ansible/on-premise/environments/production/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ LOG SERVICES:
web Next.js frontend
ai-api AI API
ai-worker AI Worker
ai-indexer AI Indexer
ollama Ollama
postgres, postgresql, db PostgreSQL database
redis Redis cache
Expand Down Expand Up @@ -207,7 +208,7 @@ deploy_application() {
if [[ -z "$ARG1" ]]; then
# No args: default to latest, all
:
elif [[ "$ARG1" == "all" ]] || [[ "$ARG1" =~ , ]] || [[ "$ARG1" =~ ^(nest|spring|web|log-consumer|ai-all|ai-api|ai-worker|ollama)$ ]]; then
elif [[ "$ARG1" == "all" ]] || [[ "$ARG1" =~ , ]] || [[ "$ARG1" =~ ^(nest|spring|web|log-consumer|ai-all|ai-api|ai-worker|ai-indexer|ollama)$ ]]; then
# First arg is target: assume latest tag
TARGETS_RAW="$ARG1"
if [[ -n "$ARG2" ]]; then
Expand All @@ -225,20 +226,20 @@ deploy_application() {

# Expand 'ai-all' to individual components
if [[ "$TARGETS_RAW" == "ai-all" ]]; then
TARGETS_RAW="ai-api,ai-worker,ollama"
TARGETS_RAW="ai-api,ai-worker,ai-indexer,ollama"
elif [[ "$TARGETS_RAW" == "ai" ]]; then
# Backward compatibility or shorthand
TARGETS_RAW="ai-api,ai-worker,ollama"
TARGETS_RAW="ai-api,ai-worker,ai-indexer,ollama"
fi

# Validate targets
if [[ "$TARGETS_RAW" != "all" ]]; then
IFS=',' read -r -a _targets <<< "$TARGETS_RAW"
for x in "${_targets[@]}"; do
case "$x" in
nest|spring|web|log-consumer|ai-api|ai-worker|ollama) ;;
nest|spring|web|log-consumer|ai-api|ai-worker|ai-indexer|ollama) ;;
*)
echo "ERROR: Unknown target '$x'. Allowed: all, nest, spring, web, log-consumer, ai-all, ai-api, ai-worker, ollama (comma-separated)."
echo "ERROR: Unknown target '$x'. Allowed: all, nest, spring, web, log-consumer, ai-all, ai-api, ai-worker, ai-indexer, ollama (comma-separated)."
exit 1
;;
esac
Expand Down Expand Up @@ -517,7 +518,7 @@ show_logs() {
echo "ERROR: Service name required"
echo "Usage: $0 logs <service>"
echo
echo "Services: api, api-springboot, web, postgres, redis, prometheus, grafana, ai-api, ai-worker, ollama"
echo "Services: api, api-springboot, web, postgres, redis, prometheus, grafana, ai-api, ai-worker, ai-indexer, ollama"
echo
echo "Aliases:"
echo " nest, api → NestJS API"
Expand All @@ -539,6 +540,7 @@ show_logs() {
grafana) service="grafana" ;;
ai|ai-api) service="ai-api" ;;
ai-worker) service="ai-worker" ;;
ai-indexer) service="ai-indexer" ;;
ollama) service="ollama" ;;
*)
# Allow exact matches for other services if they exist
Expand Down
1 change: 1 addition & 0 deletions ansible/on-premise/roles/app-deploy/handlers/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
ansible.builtin.shell: |
cd "{{ app_base_dir }}"
docker compose up -d --force-recreate --remove-orphans
changed_when: true
2 changes: 1 addition & 1 deletion ansible/on-premise/roles/app-deploy/tasks/database.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
ansible.builtin.shell: |
set -euo pipefail
cd "{{ app_base_dir }}"

if [ -d "infra/postgres/migrations" ]; then
for sql_file in $(find infra/postgres/migrations -name "*.sql" | sort); do
echo "Applying migration: $sql_file"
Expand Down
9 changes: 8 additions & 1 deletion ansible/on-premise/roles/app-deploy/tasks/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,18 @@
force_source: true
when: "'ai-worker' in deploy_targets_list"

- name: Pull AI Indexer image
community.docker.docker_image:
name: "{{ ecr_registry }}/{{ ecr_repo_ai_indexer | default('flowly-ai-indexer') }}:{{ image_tag }}"
source: pull
force_source: true
when: "'ai-indexer' in deploy_targets_list"

- name: Deploy application containers
ansible.builtin.shell: |
set -euo pipefail
cd "{{ app_base_dir }}"

# Clean up existing containers for target services to avoid conflicts
# This acts as a forced recreation/rolling update step
docker compose -f "{{ docker_compose_file }}" rm -fsv {{ deploy_services_list | join(' ') }} || true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
{{
(deploy_targets is not defined or (deploy_targets | lower) == 'all')
| ternary(
['nest','spring','web','log-consumer','ai-api','ai-worker','ollama'],
['nest','spring','web','log-consumer','ai-api','ai-worker','ai-indexer','ollama'],
(deploy_targets | lower | regex_replace('\\s+', '')).split(',')
)
}}
Expand All @@ -15,7 +15,7 @@
{{
(deploy_targets is not defined or (deploy_targets | lower) == 'all')
| ternary(
['api-nestjs','api-springboot','web','log-consumer','ai-api','ai-worker','ollama','ollama-init'],
['api-nestjs','api-springboot','web','log-consumer','ai-api','ai-worker','ai-indexer','ollama','ollama-init'],
(
((
(deploy_targets | lower | regex_replace('\\s+', '')).split(',')
Expand All @@ -25,9 +25,10 @@
| map('regex_replace', '^log-consumer$', 'log-consumer')
| map('regex_replace', '^ai-api$', 'ai-api')
| map('regex_replace', '^ai-worker$', 'ai-worker')
| map('regex_replace', '^ai-indexer$', 'ai-indexer')
| map('regex_replace', '^ollama$', 'ollama')
| list
| select('match', '^(api-nestjs|api-springboot|web|log-consumer|ai-api|ai-worker|ollama)$')
| select('match', '^(api-nestjs|api-springboot|web|log-consumer|ai-api|ai-worker|ai-indexer|ollama)$')
| list
) + (
('ollama' in (deploy_targets | lower | regex_replace('\\s+', '')).split(',')) | ternary(['ollama-init'], [])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class AuthControllerTest {
id = 1L,
email = request.email,
nickname = request.nickname,
workspaceId = "test-workspace-id",
createdAt = "2025-12-09T00:00:00Z",
),
tokens =
Expand Down Expand Up @@ -145,6 +146,7 @@ class AuthControllerTest {
id = 1L,
email = request.email,
nickname = "Test User",
workspaceId = "test-workspace-id",
createdAt = "2025-12-09T00:00:00Z",
),
tokens =
Expand Down Expand Up @@ -207,6 +209,7 @@ class AuthControllerTest {
id = 1L,
email = "[email protected]",
nickname = "Test User",
workspaceId = "test-workspace-id",
createdAt = "2025-12-09T00:00:00Z",
),
tokens =
Expand Down Expand Up @@ -318,6 +321,7 @@ class AuthControllerTest {
id = 1L,
email = "[email protected]",
nickname = "Test User",
workspaceId = "test-workspace-id",
createdAt = "2025-12-09T00:00:00Z",
)

Expand Down
6 changes: 6 additions & 0 deletions apps/api/src/chat/chat.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ describe('ChatService', () => {
provide: CACHE_MANAGER,
useValue: mockCacheManager,
},
{
provide: 'REDIS_PUBLISHER_CLIENT',
useValue: {
publish: jest.fn(),
},
},
],
}).compile();

Expand Down
18 changes: 12 additions & 6 deletions apps/api/src/todo/todo.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Todo } from './todo.entity';
import { User } from '../user/user.entity';
import { createMockRepository } from '../test-utils/mock-repository';
import { createMockCacheManager } from '../test/helpers/cache-mock.helper';
import {Friendship} from "../friend/friendship.entity";
import { Friendship } from "../friend/friendship.entity";

describe('TodoService', () => {
let service: TodoService;
Expand Down Expand Up @@ -77,6 +77,9 @@ describe('TodoService', () => {
ownerId: mockUser.id,
visibility: 'PRIVATE',
done: false,
status: 'TODO',
startDate: undefined,
endDate: undefined,
});
expect(repository.save).toHaveBeenCalledWith(expectedTodo);
expect(result).toEqual(expectedTodo);
Expand All @@ -96,6 +99,9 @@ describe('TodoService', () => {
ownerId: mockUser.id,
visibility: 'FRIENDS',
done: false,
status: 'TODO',
startDate: undefined,
endDate: undefined,
});
expect(result).toEqual(expectedTodo);
});
Expand Down Expand Up @@ -222,11 +228,11 @@ describe('TodoService', () => {
expect(mockQueryBuilder.leftJoinAndSelect).toHaveBeenCalledWith('todo.owner', 'owner');
expect(mockQueryBuilder.where).toHaveBeenCalledWith('todo.visibility = :visibility', { visibility: 'FRIENDS' });
expect(mockQueryBuilder.innerJoin).toHaveBeenCalledWith(
Friendship,
'friendship',
'(friendship.requesterId = :userId AND friendship.addresseeId = todo.ownerId AND friendship.status = :status) OR ' +
'(friendship.addresseeId = :userId AND friendship.requesterId = todo.ownerId AND friendship.status = :status)',
{ userId, status: 'ACCEPTED' }
Friendship,
'friendship',
'(friendship.requesterId = :userId AND friendship.addresseeId = todo.ownerId AND friendship.status = :status) OR ' +
'(friendship.addresseeId = :userId AND friendship.requesterId = todo.ownerId AND friendship.status = :status)',
{ userId, status: 'ACCEPTED' }
);
expect(mockQueryBuilder.orderBy).toHaveBeenCalledWith('todo.createdAt', 'DESC');
expect(result).toEqual(friendsTodos);
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/app/calendar/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { Todo } from '@/types';
import { apiClient } from '@/lib/api';
import { useLanguage } from '@/contexts/LanguageContext';
import { ChevronLeft, ChevronRight, Calendar as CalendarIcon, Clock, Plus } from 'lucide-react';

Check warning on line 10 in apps/web/src/app/calendar/page.tsx

View workflow job for this annotation

GitHub Actions / Web - Build & Test

'ChevronRight' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 10 in apps/web/src/app/calendar/page.tsx

View workflow job for this annotation

GitHub Actions / Web - Build & Test

'ChevronLeft' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 10 in apps/web/src/app/calendar/page.tsx

View workflow job for this annotation

GitHub Actions / Web - Build & Test

'ChevronRight' is defined but never used. Allowed unused vars must match /^_/u

Check warning on line 10 in apps/web/src/app/calendar/page.tsx

View workflow job for this annotation

GitHub Actions / Web - Build & Test

'ChevronLeft' is defined but never used. Allowed unused vars must match /^_/u
import { motion, AnimatePresence } from 'framer-motion';
import Button from '@/components/ui/Button';
import Link from 'next/link';
Expand Down Expand Up @@ -193,6 +193,7 @@
</div>
</div>

{/* @ts-ignore */}
<style jsx global>{`
.react-calendar {
width: 100% !important;
Expand Down
30 changes: 27 additions & 3 deletions scripts/build-and-push.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ TARGETS_RAW="$(echo "$TARGETS_RAW" | tr -d ' ' | tr '[:upper:]' '[:lower:]')"

# Expand 'ai-all' to individual components
if [[ "$TARGETS_RAW" == "ai-all" ]] || [[ "$TARGETS_RAW" == "ai" ]]; then
TARGETS_RAW="ai-api,ai-worker"
TARGETS_RAW="ai-api,ai-worker,ai-indexer"
fi

want_target() {
Expand All @@ -50,9 +50,9 @@ if [[ "$TARGETS_RAW" != "all" ]]; then
IFS=',' read -r -a _targets <<< "$TARGETS_RAW"
for x in "${_targets[@]}"; do
case "$x" in
nest|spring|web|log-consumer|ai-api|ai-worker) ;;
nest|spring|web|log-consumer|ai-api|ai-worker|ai-indexer) ;;
*)
echo "ERROR: Unknown target '$x'. Allowed: all, nest, spring, web, log-consumer, ai-all, ai-api, ai-worker (comma-separated)."
echo "ERROR: Unknown target '$x'. Allowed: all, nest, spring, web, log-consumer, ai-all, ai-api, ai-worker, ai-indexer (comma-separated)."
exit 1
;;
esac
Expand Down Expand Up @@ -122,6 +122,9 @@ fi
if want_target "ai-worker"; then
create_ecr_repo "$PROJECT_NAME/ai-worker"
fi
if want_target "ai-indexer"; then
create_ecr_repo "$PROJECT_NAME/ai-indexer"
fi
echo

# Build and push images
Expand Down Expand Up @@ -244,6 +247,24 @@ else
echo
fi

# Build AI Indexer
if want_target "ai-indexer"; then
echo "Building AI Indexer for linux/amd64..."
docker buildx build \
--platform linux/amd64 \
-t "$ECR_REGISTRY/$PROJECT_NAME/ai-indexer:$IMAGE_TAG" \
-t "$ECR_REGISTRY/$PROJECT_NAME/ai-indexer:latest" \
-f apps/ai-agent/ai-indexer/Dockerfile \
--push \
apps/ai-agent

echo "✓ Built and pushed AI Indexer image"
echo
else
echo "Skipping AI Indexer"
echo
fi

echo "========================================="
echo "Build and Push Complete!"
echo "========================================="
Expand All @@ -267,6 +288,9 @@ fi
if want_target "ai-worker"; then
echo " - $ECR_REGISTRY/$PROJECT_NAME/ai-worker:$IMAGE_TAG"
fi
if want_target "ai-indexer"; then
echo " - $ECR_REGISTRY/$PROJECT_NAME/ai-indexer:$IMAGE_TAG"
fi
echo
echo "Next steps:"
echo " cd ansible/on-premise/environments/production"
Expand Down
Loading