Skip to content

Commit 855f9e6

Browse files
authored
Merge pull request #7 from kavinda-100/develop
Develop
2 parents e784f1d + 3e8b698 commit 855f9e6

19 files changed

Lines changed: 925 additions & 122 deletions

migrations/20260311154909_init_sprintly_schema.sql

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,6 @@ CREATE TABLE IF NOT EXISTS projects (
5858
-- =======================
5959
CREATE INDEX IF NOT EXISTS idx_projects_workspace_id ON projects(workspace_id);
6060

61-
-- =======================
62-
-- TASK STATUS & PRIORITY
63-
-- =======================
64-
CREATE TABLE IF NOT EXISTS task_status (
65-
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
66-
name TEXT NOT NULL
67-
);
68-
69-
CREATE TABLE IF NOT EXISTS task_priority (
70-
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
71-
name TEXT NOT NULL
72-
);
73-
7461
-- =======================
7562
-- TASKS TABLE
7663
-- =======================
@@ -79,9 +66,20 @@ CREATE TABLE IF NOT EXISTS tasks (
7966
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
8067
title TEXT NOT NULL,
8168
description TEXT,
82-
status_id UUID NOT NULL REFERENCES task_status(id),
83-
priority_id UUID NOT NULL REFERENCES task_priority(id),
69+
70+
task_status TEXT NOT NULL CHECK (
71+
task_status IN ('todo', 'in_progress', 'done')
72+
),
73+
74+
task_priority TEXT NOT NULL CHECK (
75+
task_priority IN ('low', 'medium', 'high')
76+
),
77+
8478
owner_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
79+
80+
due_date TIMESTAMP,
81+
position INTEGER DEFAULT 0,
82+
8583
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
8684
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
8785
);
@@ -90,9 +88,9 @@ CREATE TABLE IF NOT EXISTS tasks (
9088
-- TASKS TABLE INDEXES
9189
-- =======================
9290
CREATE INDEX IF NOT EXISTS idx_tasks_project_id ON tasks(project_id);
93-
CREATE INDEX IF NOT EXISTS idx_tasks_status_id ON tasks(status_id);
94-
CREATE INDEX IF NOT EXISTS idx_tasks_priority_id ON tasks(priority_id);
9591
CREATE INDEX IF NOT EXISTS idx_tasks_owner_id ON tasks(owner_id);
92+
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(task_status);
93+
CREATE INDEX IF NOT EXISTS idx_tasks_priority ON tasks(task_priority);
9694

9795

9896
-- =======================

public/index.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,15 @@ <h2>Available Endpoints</h2>
3636
<table class="endpoints-table">
3737
<thead>
3838
<tr>
39+
<th>Group</th>
3940
<th>Endpoint</th>
4041
<th>Method</th>
4142
<th>Description</th>
4243
<th>Status</th>
4344
</tr>
4445
</thead>
4546
<tbody>
46-
<!-- Table rows will be dynamically generated by table.js -->
47+
<!-- Grouped rows will be dynamically generated by table.js -->
4748
</tbody>
4849
</table>
4950
</div>

public/table.js

Lines changed: 189 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,154 @@
11
// Endpoints data
22
const endpoints = [
33
{
4-
endpoint: '/health',
5-
method: 'GET',
6-
description: 'Health check endpoint',
7-
status: 'active',
4+
endpoints: [
5+
{
6+
endpoint: '/health',
7+
method: 'GET',
8+
description: 'Health check endpoint',
9+
status: 'active',
10+
},
11+
],
812
},
913
{
10-
endpoint: '/api/v1/auth/register',
11-
method: 'POST',
12-
description: 'Register a new user account',
13-
status: 'active',
14+
name: 'Authentication',
15+
endpoints: [
16+
{
17+
endpoint: '/api/v1/auth/register',
18+
method: 'POST',
19+
description: 'Register a new user account',
20+
status: 'active',
21+
},
22+
{
23+
endpoint: '/api/v1/auth/login',
24+
method: 'POST',
25+
description: 'Login with email and password',
26+
status: 'active',
27+
},
28+
{
29+
endpoint: '/api/v1/auth/logout',
30+
method: 'POST',
31+
description: 'Logout the current user',
32+
status: 'active',
33+
},
34+
{
35+
endpoint: '/api/v1/auth/google',
36+
method: 'GET',
37+
description: 'Login with Google OAuth',
38+
status: 'inactive',
39+
},
40+
],
1441
},
1542
{
16-
endpoint: '/api/v1/auth/login',
17-
method: 'POST',
18-
description: 'Login with email and password',
19-
status: 'active',
43+
name: 'Workspaces',
44+
endpoints: [
45+
{
46+
endpoint: '/api/v1/workspaces',
47+
method: 'POST',
48+
description: 'Create a new workspace',
49+
status: 'active',
50+
},
51+
{
52+
endpoint: '/api/v1/workspaces',
53+
method: 'GET',
54+
description: 'Fetch all workspaces for the authenticated user',
55+
status: 'active',
56+
},
57+
{
58+
endpoint: '/api/v1/workspaces/{workspace_id}',
59+
method: 'GET',
60+
description:
61+
'Fetch a specific workspace for the authenticated user',
62+
status: 'active',
63+
},
64+
{
65+
endpoint: '/api/v1/workspaces/{workspace_id}',
66+
method: 'PUT',
67+
description:
68+
'Update a specific workspace for the authenticated user',
69+
status: 'active',
70+
},
71+
{
72+
endpoint: '/api/v1/workspaces/{workspace_id}',
73+
method: 'DELETE',
74+
description:
75+
'Delete a specific workspace for the authenticated user',
76+
status: 'active',
77+
},
78+
{
79+
endpoint:
80+
'/api/v1/workspaces/{workspace_id}/projects?page=1&page_size=20',
81+
method: 'GET',
82+
description:
83+
'Fetch projects for a specific workspace for the authenticated user',
84+
status: 'active',
85+
},
86+
],
2087
},
21-
,
2288
{
23-
endpoint: '/api/v1/auth/logout',
24-
method: 'POST',
25-
description: 'Logout the current user',
26-
status: 'active',
89+
name: 'Projects',
90+
endpoints: [
91+
{
92+
endpoint: '/api/v1/projects/',
93+
method: 'POST',
94+
description: 'Create a new project for the specific workspace.',
95+
status: 'active',
96+
},
97+
{
98+
endpoint: '/api/v1/projects/{project_id}',
99+
method: 'GET',
100+
description:
101+
'Fetch a specific project for the authenticated user',
102+
status: 'active',
103+
},
104+
{
105+
endpoint: '/api/v1/projects/{project_id}',
106+
method: 'PUT',
107+
description:
108+
'Update a specific project for the authenticated user',
109+
status: 'active',
110+
},
111+
{
112+
endpoint: '/api/v1/projects/{project_id}',
113+
method: 'DELETE',
114+
description:
115+
'Delete a specific project for the authenticated user',
116+
status: 'active',
117+
},
118+
{
119+
endpoint:
120+
'/api/v1/projects/{project_id}/tasks?status=todo&priority=high&page=1&page_size=20',
121+
method: 'GET',
122+
description:
123+
'Fetch tasks for a specific project for the authenticated user',
124+
status: 'active',
125+
},
126+
],
27127
},
28128
{
29-
endpoint: '/api/v1/auth/google',
30-
method: 'GET',
31-
description: 'Login with Google OAuth',
32-
status: 'active',
129+
name: 'Tasks',
130+
endpoints: [
131+
{
132+
endpoint: '/api/v1/tasks',
133+
method: 'POST',
134+
description: 'Create a new task for project.',
135+
status: 'active',
136+
},
137+
{
138+
endpoint: '/api/v1/tasks/{task_id}',
139+
method: 'PUT',
140+
description:
141+
'Update a specific task for the authenticated user',
142+
status: 'active',
143+
},
144+
{
145+
endpoint: '/api/v1/tasks/{task_id}',
146+
method: 'DELETE',
147+
description:
148+
'Delete a specific task for the authenticated user',
149+
status: 'active',
150+
},
151+
],
33152
},
34153
];
35154

@@ -52,6 +171,19 @@ function getStatusClass(status) {
52171
return statusLower === 'active' ? 'active' : 'inactive';
53172
}
54173

174+
// Function to get subtle background color for each group
175+
function getGroupColor(groupName) {
176+
const groupLower = (groupName || 'General').toLowerCase();
177+
const colorMap = {
178+
general: '#f9fafb',
179+
authentication: '#f3e8ff',
180+
workspaces: '#ecfdf5',
181+
projects: '#eff6ff',
182+
tasks: '#fffbeb',
183+
};
184+
return colorMap[groupLower] || '#f9fafb';
185+
}
186+
55187
// Function to render table rows
56188
function renderTable() {
57189
const tbody = document.querySelector('.endpoints-table tbody');
@@ -64,35 +196,48 @@ function renderTable() {
64196
// Clear existing rows
65197
tbody.innerHTML = '';
66198

67-
// Loop through endpoints and create rows
68-
endpoints.forEach((item) => {
69-
const row = document.createElement('tr');
199+
// Loop through grouped endpoints and create rows
200+
endpoints.forEach((group) => {
201+
const groupName = group.name || 'General';
202+
const groupEndpoints = Array.isArray(group.endpoints)
203+
? group.endpoints
204+
: [];
205+
206+
if (groupEndpoints.length === 0) {
207+
return;
208+
}
209+
210+
groupEndpoints.forEach((item) => {
211+
const row = document.createElement('tr');
212+
row.style.backgroundColor = getGroupColor(groupName);
213+
214+
const groupCell = document.createElement('td');
215+
groupCell.textContent = groupName;
216+
row.appendChild(groupCell);
70217

71-
// Create cells
72-
const endpointCell = document.createElement('td');
73-
endpointCell.innerHTML = `<code>${item.endpoint}</code>`;
218+
const endpointCell = document.createElement('td');
219+
endpointCell.innerHTML = `<code>${item.endpoint}</code>`;
74220

75-
const methodCell = document.createElement('td');
76-
const methodClass = getMethodClass(item.method);
77-
methodCell.innerHTML = `<span class="method-badge ${methodClass}">${item.method.toUpperCase()}</span>`;
221+
const methodCell = document.createElement('td');
222+
const methodClass = getMethodClass(item.method);
223+
methodCell.innerHTML = `<span class="method-badge ${methodClass}">${item.method.toUpperCase()}</span>`;
78224

79-
const descriptionCell = document.createElement('td');
80-
descriptionCell.textContent = item.description;
225+
const descriptionCell = document.createElement('td');
226+
descriptionCell.textContent = item.description;
81227

82-
const statusCell = document.createElement('td');
83-
const statusClass = getStatusClass(item.status);
84-
const statusText =
85-
item.status.charAt(0).toUpperCase() + item.status.slice(1);
86-
statusCell.innerHTML = `<span class="status-badge ${statusClass}">${statusText}</span>`;
228+
const statusCell = document.createElement('td');
229+
const statusClass = getStatusClass(item.status);
230+
const statusText =
231+
item.status.charAt(0).toUpperCase() + item.status.slice(1);
232+
statusCell.innerHTML = `<span class="status-badge ${statusClass}">${statusText}</span>`;
87233

88-
// Append cells to row
89-
row.appendChild(endpointCell);
90-
row.appendChild(methodCell);
91-
row.appendChild(descriptionCell);
92-
row.appendChild(statusCell);
234+
row.appendChild(endpointCell);
235+
row.appendChild(methodCell);
236+
row.appendChild(descriptionCell);
237+
row.appendChild(statusCell);
93238

94-
// Append row to tbody
95-
tbody.appendChild(row);
239+
tbody.appendChild(row);
240+
});
96241
});
97242
}
98243

src/config/env.rs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,52 @@
11
use std::env;
22

3-
#[derive(Debug, Clone)]
3+
#[derive(Debug, Clone, PartialEq, Eq)]
4+
pub enum DevMode {
5+
Development,
6+
Test,
7+
Production,
8+
}
9+
10+
impl DevMode {
11+
fn from_env_value(value: &str) -> Self {
12+
match value.to_ascii_lowercase().as_str() {
13+
"development" | "dev" => Self::Development,
14+
"test" => Self::Test,
15+
"production" | "prod" => Self::Production,
16+
_ => panic!("DEV_MODE must be one of: development|dev, test, production|prod"),
17+
}
18+
}
19+
}
20+
21+
#[derive(Debug, Clone, PartialEq, Eq)]
422
#[allow(dead_code)]
523
pub struct EnvConfig {
6-
pub dev_mode: String,
24+
pub dev_mode: DevMode,
725
pub database_url: String,
826
pub port: u16,
927
pub jwt_secret: String,
1028
}
1129

1230
impl EnvConfig {
1331
pub fn from_env() -> Self {
14-
let dev_mode = env::var("DEV_MODE").unwrap_or_else(|_| "development".to_string());
15-
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
32+
let dev_mode_raw = env::var("DEV_MODE").unwrap_or_else(|_| "development".to_string());
33+
let dev_mode = DevMode::from_env_value(&dev_mode_raw);
34+
let main_database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
35+
let test_database_url =
36+
env::var("TEST_DATABASE_URL").expect("TEST_DATABASE_URL must be set");
1637
let port = env::var("PORT")
1738
.unwrap_or_else(|_| "5000".to_string())
1839
.parse::<u16>()
1940
.expect("PORT must be a valid number");
2041
let jwt_secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
2142

43+
// Choose the appropriate database URL based on the development mode
44+
let database_url = if dev_mode == DevMode::Test {
45+
test_database_url
46+
} else {
47+
main_database_url
48+
};
49+
2250
EnvConfig {
2351
dev_mode,
2452
database_url,

0 commit comments

Comments
 (0)