Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
aebdd6f
feat(boards): implement initial boards module structure
maksberegovoi May 2, 2026
009105e
feat(boards): add module skeleton with controller, facade and use-cases
maksberegovoi May 4, 2026
43de7ff
feat(boards): implement board factory, repository, and controller for…
maksberegovoi May 6, 2026
dabf45b
feat(boards): add swagger docs and add unique index for board names
maksberegovoi May 6, 2026
6811948
feat(boards): implement CRUD operations for board columns and swagger
maksberegovoi May 6, 2026
23a5bac
feat(boards): implement CRUD operations for board views and swagger
maksberegovoi May 6, 2026
0504b4a
feat(boards): add k6 test script for boards CRUD operations
maksberegovoi May 7, 2026
b29e7f1
feat(boards): add k6 test script for boards-columns CRUD operations
maksberegovoi May 7, 2026
4f9f60b
feat(boards): add k6 test script for boards-views CRUD and update scr…
maksberegovoi May 7, 2026
82da4d7
feat(boards): enhance board columns with status and visibility attrib…
maksberegovoi May 7, 2026
894bc50
feat(boards): implement access validation for boards, columns, and views
maksberegovoi May 8, 2026
16b0a72
feat(boards): add migrations for boards, columns, and views
maksberegovoi May 8, 2026
f52fc23
feat(boards): changes per review, update use-cases and DTOs
maksberegovoi May 18, 2026
a1733de
feat(boards): update timestamp fields to use timezone and replace boa…
maksberegovoi May 20, 2026
39eb925
feat(boards): add metadata to schemas for response validation and moc…
maksberegovoi May 21, 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
5 changes: 4 additions & 1 deletion infra/k6/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
"test:teams-me": "k6 run scenarios/teams-me.js",
"test:projects": "k6 run scenarios/projects.js",
"test:users": "k6 run scenarios/users.js",
"test:board": "k6 run scenarios/board-full.js",
"test:boards:all": "k6 run scenarios/boards.js && k6 run scenarios/boards-columns.js && k6 run scenarios/boards-views.js",
"test:boards": "k6 run scenarios/boards.js",
"test:boards:columns": "k6 run scenarios/boards-columns.js",
"test:boards:views": "k6 run scenarios/boards-views.js",
"test:tasks": "k6 run scenarios/tasks.js",
"smoke": "k6 run smoke.js"
},
Expand Down
133 changes: 133 additions & 0 deletions infra/k6/scenarios/boards-columns.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { SharedArray } from 'k6/data';
import { sleep, check } from 'k6';
import { GET_OPTIONS } from '../common/config.js';
import getAuthUser from '../shared/get-auth-user.js';

const users = new SharedArray('test users', function () {
return JSON.parse(open('../data/users.json'));
});
const teams = new SharedArray('test teams', function () {
return JSON.parse(open('../data/teams.json'));
});
const randomStr = (len = 8) =>
Math.random()
.toString(36)
.substring(2, 2 + len);
const randomNum = (min = 1, max = 100) => Math.floor(Math.random() * (max - min + 1)) + min;

const baseOptions = GET_OPTIONS();
baseOptions.thresholds = Object.assign({}, baseOptions.thresholds, {
'http_req_duration{name:auth-sign-in}': ['p(95)<333'],
'http_req_duration{name:post-teams-projects}': ['p(95)<333'],
'http_req_duration{name:post-projects-boards}': ['p(95)<333'],
'http_req_duration{name:get-boards-columns}': ['p(95)<333'],
'http_req_duration{name:post-boards-columns}': ['p(95)<333'],
'http_req_duration{name:get-boards-columns-id}': ['p(95)<333'],
'http_req_duration{name:patch-boards-columns}': ['p(95)<333'],
'http_req_duration{name:delete-boards-columns}': ['p(95)<333'],
'http_req_duration{name:delete-teams-projects}': ['p(95)<333'],
});

export const options = baseOptions;

export default function () {
const user = users[(__VU - 1) % users.length];
const team = teams[(__VU - 1) % teams.length];
const { client } = getAuthUser(user);

sleep(1);

// --- create project ---
const project = {
name: `k6_columns_project_${randomStr(6)}`,
key: `K6${randomNum(1000, 9999)}`,
description: 'k6 columns scenario project',
visibility: 'public',
};
const createProjectRes = client.post(`/teams/${team.slug}/projects`, project, {
tags: { name: 'post-teams-projects' },
});
const projectId = createProjectRes.json().projectId;

check(createProjectRes, {
'POST /teams/:slug/projects: has projectId': (r) => r.json().projectId !== undefined,
});

sleep(1);

// --- create board ---
const boardPayload = {
name: `k6_columns_board_${randomStr(6)}`,
position: Date.now(),
};
const createBoardRes = client.post(`/projects/${projectId}/boards`, boardPayload, {
tags: { name: 'post-projects-boards' },
});
const boardId = createBoardRes.json().boardId;

check(createBoardRes, {
'POST /projects/:id/boards: has board id': (r) => r.json().boardId !== undefined,
});

sleep(1);

// --- get all columns ---
const listRes = client.get(
`/boards/${boardId}/columns`,
{},
{ tags: { name: 'get-boards-columns' } },
);

sleep(1);

// --- create column ---
const columnPayload = {
name: `k6_column_${randomStr(6)}`,
position: 4000,
color: '#22c55e',
};
const createColumnRes = client.post(`/boards/${boardId}/columns`, columnPayload, {
tags: { name: 'post-boards-columns' },
});
const columnId = createColumnRes.json().columnId;

check(createColumnRes, {
'POST /boards/:id/columns: has column id': (r) => r.json().columnId !== undefined,
});

sleep(1);

// --- get column ---
client.get(
`/boards/${boardId}/columns/${columnId}`,
{},
{ tags: { name: 'get-boards-columns-id' } },
);

sleep(1);

// --- update column ---
const updatedColumn = {
name: `k6_column_${randomStr(7)}`,
color: '#3b82f6',
};
client.patch(`/boards/${boardId}/columns/${columnId}`, updatedColumn, {
tags: { name: 'patch-boards-columns' },
});

sleep(1);

// --- delete column ---
client.delete(`/boards/${boardId}/columns/${columnId}`, {
tags: { name: 'delete-boards-columns' },
});

sleep(1);

// --- delete project ---
client.delete(`/teams/${team.slug}/projects/${projectId}`, {
tags: { name: 'delete-teams-projects' },
});

sleep(1);
}
129 changes: 129 additions & 0 deletions infra/k6/scenarios/boards-views.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { SharedArray } from 'k6/data';
import { sleep, check } from 'k6';
import { GET_OPTIONS } from '../common/config.js';
import getAuthUser from '../shared/get-auth-user.js';

const users = new SharedArray('test users', function () {
return JSON.parse(open('../data/users.json'));
});
const teams = new SharedArray('test teams', function () {
return JSON.parse(open('../data/teams.json'));
});
const randomStr = (len = 8) =>
Math.random()
.toString(36)
.substring(2, 2 + len);
const randomNum = (min = 1, max = 100) => Math.floor(Math.random() * (max - min + 1)) + min;

const baseOptions = GET_OPTIONS();
baseOptions.thresholds = Object.assign({}, baseOptions.thresholds, {
'http_req_duration{name:auth-sign-in}': ['p(95)<333'],
'http_req_duration{name:post-teams-projects}': ['p(95)<333'],
'http_req_duration{name:post-projects-boards}': ['p(95)<333'],
'http_req_duration{name:get-boards-views}': ['p(95)<333'],
'http_req_duration{name:post-boards-views}': ['p(95)<333'],
'http_req_duration{name:get-boards-views-id}': ['p(95)<333'],
'http_req_duration{name:patch-boards-views}': ['p(95)<333'],
'http_req_duration{name:delete-boards-views}': ['p(95)<333'],
'http_req_duration{name:delete-teams-projects}': ['p(95)<333'],
});

export const options = baseOptions;

export default function () {
const user = users[(__VU - 1) % users.length];
const team = teams[(__VU - 1) % teams.length];
const { client } = getAuthUser(user);

sleep(1);

// --- create project ---
const project = {
name: `k6_views_project_${randomStr(6)}`,
key: `K6${randomNum(1000, 9999)}`,
description: 'k6 views scenario project',
visibility: 'public',
};
const createProjectRes = client.post(`/teams/${team.slug}/projects`, project, {
tags: { name: 'post-teams-projects' },
});
const projectId = createProjectRes.json().projectId;

check(createProjectRes, {
'POST /teams/:slug/projects: has projectId': (r) => r.json().projectId !== undefined,
});

sleep(1);

// --- create board ---
const boardPayload = {
name: `k6_views_board_${randomStr(6)}`,
position: Date.now(),
};
const createBoardRes = client.post(`/projects/${projectId}/boards`, boardPayload, {
tags: { name: 'post-projects-boards' },
});
const boardId = createBoardRes.json().boardId;

check(createBoardRes, {
'POST /projects/:id/boards: has board id': (r) => r.json().boardId !== undefined,
});

sleep(1);

// --- get all views ---
const listRes = client.get(
`/boards/${boardId}/views`,
{},
{ tags: { name: 'get-boards-views' } },
);

sleep(1);

// --- create view ---
const viewPayload = {
type: 'kanban',
name: `k6_view_${randomStr(6)}`,
position: 4000,
settings: { mock: 'k6' },
};
const createViewRes = client.post(`/boards/${boardId}/views`, viewPayload, {
tags: { name: 'post-boards-views' },
});
const viewId = createViewRes.json().viewId;

check(createViewRes, {
'POST /boards/:id/views: has view id': (r) => r.json().viewId !== undefined,
});

sleep(1);

// --- get view ---
client.get(`/boards/${boardId}/views/${viewId}`, {}, { tags: { name: 'get-boards-views-id' } });

sleep(1);

// --- update view ---
const updatedView = {
name: `k6_view_${randomStr(7)}`,
};
client.patch(`/boards/${boardId}/views/${viewId}`, updatedView, {
tags: { name: 'patch-boards-views' },
});

sleep(1);

// --- delete view ---
client.delete(`/boards/${boardId}/views/${viewId}`, {
tags: { name: 'delete-boards-views' },
});

sleep(1);

// --- delete project ---
client.delete(`/teams/${team.slug}/projects/${projectId}`, {
tags: { name: 'delete-teams-projects' },
});

sleep(1);
}
114 changes: 114 additions & 0 deletions infra/k6/scenarios/boards.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { SharedArray } from 'k6/data';
import { sleep, check } from 'k6';
import { GET_OPTIONS } from '../common/config.js';
import getAuthUser from '../shared/get-auth-user.js';

const users = new SharedArray('test users', function () {
return JSON.parse(open('../data/users.json'));
});
const teams = new SharedArray('test teams', function () {
return JSON.parse(open('../data/teams.json'));
});
const randomStr = (len = 8) =>
Math.random()
.toString(36)
.substring(2, 2 + len);
const randomNum = (min = 1, max = 100) => Math.floor(Math.random() * (max - min + 1)) + min;

const baseOptions = GET_OPTIONS();
baseOptions.thresholds = Object.assign({}, baseOptions.thresholds, {
'http_req_duration{name:auth-sign-in}': ['p(95)<333'],
'http_req_duration{name:post-teams-projects}': ['p(95)<333'],
'http_req_duration{name:post-projects-boards}': ['p(95)<333'],
'http_req_duration{name:get-projects-boards}': ['p(95)<333'],
'http_req_duration{name:get-projects-boards-id}': ['p(95)<333'],
'http_req_duration{name:patch-projects-boards}': ['p(95)<333'],
'http_req_duration{name:delete-projects-boards}': ['p(95)<333'],
'http_req_duration{name:delete-teams-projects}': ['p(95)<333'],
});

export const options = baseOptions;

export default function () {
const user = users[(__VU - 1) % users.length];
const team = teams[(__VU - 1) % teams.length];
const { client } = getAuthUser(user);

sleep(1);

// --- create project ---
const project = {
name: `k6_board_project_${randomStr(6)}`,
key: `K6${randomNum(1000, 9999)}`,
description: 'k6 boards scenario project',
visibility: 'public',
};
const createProjectRes = client.post(`/teams/${team.slug}/projects`, project, {
tags: { name: 'post-teams-projects' },
});
const projectId = createProjectRes.json().projectId;

check(createProjectRes, {
'POST /teams/:slug/projects: has projectId': (r) => r.json().projectId !== undefined,
});

sleep(1);

// --- create board ---
const boardPayload = {
name: `k6_board_${randomStr(6)}`,
position: Date.now(),
};
const createBoardRes = client.post(`/projects/${projectId}/boards`, boardPayload, {
tags: { name: 'post-projects-boards' },
});
const boardId = createBoardRes.json().boardId;

check(createBoardRes, {
'POST /projects/:id/boards: has board id': (r) => r.json().boardId !== undefined,
});

sleep(1);

// --- get all boards ---
const listRes = client.get(
`/projects/${projectId}/boards`,
{},
{ tags: { name: 'get-projects-boards' } },
);

sleep(1);

// --- get board ---
client.get(
`/projects/${projectId}/boards/${boardId}`,
{},
{ tags: { name: 'get-projects-boards-id' } },
);

sleep(1);

// --- update board ---
const updatedBoard = {
name: `k6_board_${randomStr(7)}`,
};
client.patch(`/projects/${projectId}/boards/${boardId}`, updatedBoard, {
tags: { name: 'patch-projects-boards' },
});

sleep(1);

// --- delete board ---
client.delete(`/projects/${projectId}/boards/${boardId}`, {
tags: { name: 'delete-projects-boards' },
});

sleep(1);

// --- delete project ---
client.delete(`/teams/${team.slug}/projects/${projectId}`, {
tags: { name: 'delete-teams-projects' },
});

sleep(1);
}
Loading
Loading