Skip to content

Commit 244f27b

Browse files
committed
wip
2 parents e62b806 + f68f05d commit 244f27b

File tree

339 files changed

+4251
-10016
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

339 files changed

+4251
-10016
lines changed

.env.example

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,7 @@ GOTENBERG_URL=http://gotenberg:3000
7575
# Local setup
7676
NGINX_HOST_NAME=solidtime.test
7777
NETWORK_NAME=reverse-proxy-docker-traefik_routing
78-
FORWARD_DB_PORT=5432
79-
FORWARD_WEB_PORT=8083
78+
FORWARD_DB_PORT=54329
8079
VITE_HOST_NAME=vite.solidtime.test
8180
VITE_APP_NAME="${APP_NAME}"
8281
#SAIL_XDEBUG_MODE=develop,debug,coverage
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: NPM Format Check
2+
3+
on: [push]
4+
5+
jobs:
6+
format-check:
7+
runs-on: ubuntu-latest
8+
timeout-minutes: 10
9+
10+
steps:
11+
- name: "Checkout code"
12+
uses: actions/checkout@v4
13+
14+
- name: "Use Node.js"
15+
uses: actions/setup-node@v4
16+
with:
17+
node-version: '20.x'
18+
19+
- name: "Install npm dependencies"
20+
run: npm ci
21+
22+
- name: "Check code formatting"
23+
run: npm run format:check

.prettierignore

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Ignore build outputs
2+
node_modules/
3+
vendor/
4+
storage/
5+
bootstrap/cache/
6+
public/build/
7+
public/hot/
8+
9+
# Ignore lock files
10+
package-lock.json
11+
composer.lock
12+
13+
# Ignore generated files
14+
*.min.js
15+
*.min.css
16+
17+
# Ignore test results
18+
test-results/
19+
playwright-report/
20+
21+
# Ignore IDE files
22+
.idea/
23+
.vscode/
24+
25+
# Ignore OS files
26+
.DS_Store
27+
Thumbs.db

.prettierrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
"tabWidth": 4,
44
"singleQuote": true,
55
"bracketSameLine": true,
6-
"quoteProps": "preserve"
6+
"quoteProps": "preserve",
7+
"printWidth": 100
78
}

CONTRIBUTING.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Contributing to solidtime
2+
3+
Contributions are greatly apprecited, please make sure to read the rules and vision for solidtime before contributing.
4+
5+
## Rules
6+
7+
### Issues for Bugs, Discussions for Feature requests
8+
9+
In order to keep the issues of the repository clean we decided to only use them for bugs. Feature Requests and enhancement are handled in discussions. This also helps us to see which feature requests are popular as they can be upvoted.
10+
11+
### Only work on approved issues
12+
13+
To respect your time and help us manage contributions effectively, please open an issue or start a discussion and wait for approval before submitting a pull request (PR). This does not apply to tiny fixes or changes however, please keep in mind that we might not merge PRs for various reasons.
14+
15+
### Contributor License Agreement
16+
17+
You'll also notice that we’ve set up a [Contributor License Agreement (CLA)](https://cla-assistant.io/solidtime-io/solidtime), which must be signed before any PR can be merged. Don’t worry - the process is quick and only takes a few clicks.
18+
19+
We want to be transparent about why we require the CLA and what it means for your contributions and the codebase. That’s why we’ve written a few paragraphs below outlining our plans and vision for solidtime in the **Vision** part of this document.
20+
21+
### Prevent Duplicate Work
22+
23+
Before you submit a new PR, make sure that none exists already. If you plan to work on an issue, make sure to let us and others know by commenting on the issue/discussion.
24+
25+
### Give context
26+
27+
Tell us what you thinking was behind the decisions you made while drafting the PR. Treat the PR itself as documentation for everyone who wants to go back and understand why certain decisions were made.
28+
29+
### Summarize your PR
30+
31+
Please make sure to include a short summary at the top of your PR to make it easy for us to quickly check what the PR is about, without looking at the code changes.
32+
33+
### Use Github Keywords and Auto-Link Issues
34+
35+
Use phrases like "Closes #123" or "Fixes #123" in the PR description to link the PR with the issue that you are adressing.
36+
37+
### Mention what you tested and how
38+
39+
Explain how you tested and validated the implementation.
40+
41+
### Keep Naming consistent
42+
43+
Look at existing code patterns and use naming conventions that already exist in the code base.
44+
45+
### Testing
46+
47+
We have an exhaustive test-suite of PHPUnit (Backend) and Playwright (Frontend) testing. Whereever applicable please make sure to write add tests to the codebase.
48+
49+
### Linting & Formatting
50+
51+
Make sure to run linting and formatting commands before you commit the changes.
52+
53+
For backend changes:
54+
55+
```
56+
composer fix
57+
composer analyse
58+
```
59+
60+
For frontend changes:
61+
62+
```
63+
npm run lint:fix
64+
npm run format
65+
```
66+
67+
## Vision
68+
69+
We started solidtime to provide an open infrastructure solution for time tracking—one that empowers teams and individuals to fully own their data, instead of depending on proprietary platforms. We believe infrastructure software should be open, accessible, and built to last. However, competing with established market leaders in this space requires long-term financial sustainability.
70+
71+
solidtime is licensed under the AGPL, which we believe is the best available license to strike a balance between openness and financial viability. The AGPL gives us, as the copyright holders, certain exclusive rights that we plan to leverage to fund development. To ensure we retain those rights across the entire codebase, we've put a CLA in place that contributors must sign before submitting code.
72+
73+
One of solidtime’s key advantages is that it's built to be self-hostable. This makes it a great solution for organizations like governments, healthcare providers, and enterprises that are required to keep data on their own infrastructure due to regulations or internal policies. These organizations may need custom licenses, integrations, or modifications that aren't suitable for the open-source version. To support them, we offer relicensed versions of solidtime along with support plans.
74+
75+
We’ll also provide proprietary extensions for solidtime. These will be available to enterprise customers with support plans, but also to individual users or teams who don’t need support, at much more accessible price points. For companies running solidtime on their own infrastructure, this is the easiest way to support the project while gaining additional functionality. While we plan to make it easier to build custom extensions in the future, our current APIs are still highly experimental.
76+
77+
Finally - and perhaps most importantly - we offer a hosted SaaS version called solidtime Cloud, for users who can’t or don’t want to run the software themselves. This version includes proprietary extensions, always runs the latest commit, and includes monitoring and billing features available exclusively on this hosted instance. We expect solidtime Cloud to play a critical role in funding the project long-term.
78+
79+
Having full control over the source code’s licensing also gives us the ability to change the license of the main project in the future. That said, we have no plans to do so and would only consider it in extreme cases - for example, if a malicious actor were to directly compete with our hosted service in a way that threatens the sustainability of the project, the legal interpretation of AGPL changes in a way that would make it unreasonable to use for certain companies, or a new similar license gains wide-spread adoption. Regardless, solidtime will always remain free to self-host for individuals and companies who use it as part of their work, and all previous releases will remain licensed under AGPL.
80+
81+
If you are using the open-source version of solidtime and want to support us, the best way to do so is to spread the word.

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,9 @@ If you have a **feature request**, please [**create a discussion**](https://gith
3535

3636
## Contributing
3737

38-
This project is in a very early stage. The structure and APIs are still subject to change and not stable.
39-
Therefore, we do not currently accept any contributions, unless you are a member of the team.
38+
Please open an issue or start a discussion and wait for approval before submitting a pull request. This does not apply to tiny fixes or changes however, please keep in mind that we might not merge PRs for various reasons.
4039

41-
As soon as we feel comfortable enough that the application structure is stable enough, we will open up the project for contributions.
40+
Please read the [CONTRIBUTING.md](./CONTRIBUTING.md) before sumbitting a Pull Request.
4241

4342
We do accept contributions in the [documentation repository](https://github.com/solidtime-io/docs) f.e. to add new self-hosting guides.
4443

app/Http/Controllers/Api/V1/ChartController.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
class ChartController extends Controller
1515
{
1616
/**
17+
* Get chart data for the weekly project overview.
18+
*
1719
* @throws AuthorizationException
1820
*
1921
* @operationId weeklyProjectOverview
@@ -31,6 +33,8 @@ public function weeklyProjectOverview(Organization $organization, DashboardServi
3133
}
3234

3335
/**
36+
* Get chart data for the latest tasks.
37+
*
3438
* @throws AuthorizationException
3539
*
3640
* @operationId latestTasks
@@ -48,6 +52,8 @@ public function latestTasks(Organization $organization, DashboardService $dashbo
4852
}
4953

5054
/**
55+
* Get chart data for the last seven days.
56+
*
5157
* @throws AuthorizationException
5258
*
5359
* @operationId lastSevenDays
@@ -65,6 +71,8 @@ public function lastSevenDays(Organization $organization, DashboardService $dash
6571
}
6672

6773
/**
74+
* Get chart data for the latest team activity.
75+
*
6876
* @throws AuthorizationException
6977
*
7078
* @operationId latestTeamActivity
@@ -81,6 +89,8 @@ public function latestTeamActivity(Organization $organization, DashboardService
8189
}
8290

8391
/**
92+
* Get chart data for daily tracked hours.
93+
*
8494
* @throws AuthorizationException
8595
*
8696
* @operationId dailyTrackedHours
@@ -98,6 +108,8 @@ public function dailyTrackedHours(Organization $organization, DashboardService $
98108
}
99109

100110
/**
111+
* Get chart data for total weekly time.
112+
*
101113
* @throws AuthorizationException
102114
*
103115
* @operationId totalWeeklyTime
@@ -115,6 +127,8 @@ public function totalWeeklyTime(Organization $organization, DashboardService $da
115127
}
116128

117129
/**
130+
* Get chart data for total weekly billable time.
131+
*
118132
* @throws AuthorizationException
119133
*
120134
* @operationId totalWeeklyBillableTime
@@ -132,6 +146,8 @@ public function totalWeeklyBillableTime(Organization $organization, DashboardSer
132146
}
133147

134148
/**
149+
* Get chart data for total weekly billable amount.
150+
*
135151
* @throws AuthorizationException
136152
*
137153
* @operationId totalWeeklyBillableAmount
@@ -154,6 +170,8 @@ public function totalWeeklyBillableAmount(Organization $organization, DashboardS
154170
}
155171

156172
/**
173+
* Get chart data for weekly history.
174+
*
157175
* @throws AuthorizationException
158176
*
159177
* @operationId weeklyHistory

app/Http/Controllers/Api/V1/TimeEntryController.php

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ public function index(Organization $organization, TimeEntryIndexRequest $request
8686
$this->checkPermission($organization, 'time-entries:view:all');
8787
}
8888

89-
$timeEntriesQuery = $this->getTimeEntriesQuery($organization, $request, $member);
89+
$canAccessPremiumFeatures = $this->canAccessPremiumFeatures($organization);
90+
$timeEntriesQuery = $this->getTimeEntriesQuery($organization, $request, $member, $canAccessPremiumFeatures);
9091

9192
$totalCount = $timeEntriesQuery->count();
9293

@@ -140,13 +141,15 @@ public function index(Organization $organization, TimeEntryIndexRequest $request
140141
/**
141142
* @return Builder<TimeEntry>
142143
*/
143-
private function getTimeEntriesQuery(Organization $organization, TimeEntryIndexRequest|TimeEntryIndexExportRequest $request, ?Member $member): Builder
144+
private function getTimeEntriesQuery(Organization $organization, TimeEntryIndexRequest|TimeEntryIndexExportRequest $request, ?Member $member, bool $canAccessPremiumFeatures): Builder
144145
{
145146
$select = TimeEntry::SELECT_COLUMNS;
146-
if ($request->getRoundingType() !== null && $request->getRoundingMinutes() !== null) {
147+
$roundingType = $canAccessPremiumFeatures ? $request->getRoundingType() : null;
148+
$roundingMinutes = $canAccessPremiumFeatures ? $request->getRoundingMinutes() : null;
149+
if ($roundingType !== null && $roundingMinutes !== null) {
147150
$select = array_diff($select, ['start', 'end']);
148-
$select[] = DB::raw(app(TimeEntryService::class)->getStartSelectRawForRounding($request->getRoundingType(), $request->getRoundingMinutes()).' as start');
149-
$select[] = DB::raw(app(TimeEntryService::class)->getEndSelectRawForRounding($request->getRoundingType(), $request->getRoundingMinutes()).' as end');
151+
$select[] = DB::raw(app(TimeEntryService::class)->getStartSelectRawForRounding($roundingType, $roundingMinutes).' as start');
152+
$select[] = DB::raw(app(TimeEntryService::class)->getEndSelectRawForRounding($roundingType, $roundingMinutes).' as end');
150153
}
151154
$timeEntriesQuery = TimeEntry::query()
152155
->whereBelongsTo($organization, 'organization')
@@ -184,18 +187,19 @@ public function indexExport(Organization $organization, TimeEntryIndexExportRequ
184187
} else {
185188
$this->checkPermission($organization, 'time-entries:view:all');
186189
}
190+
$canAccessPremiumFeatures = $this->canAccessPremiumFeatures($organization);
187191
$debug = $request->getDebug();
188192
$format = $request->getFormatValue();
189-
if ($format === ExportFormat::PDF && ! $this->canAccessPremiumFeatures($organization)) {
193+
if ($format === ExportFormat::PDF && ! $canAccessPremiumFeatures) {
190194
throw new FeatureIsNotAvailableInFreePlanApiException;
191195
}
192196
$user = $this->user();
193197
$timezone = $user->timezone;
194198
$showBillableRate = $this->member($organization)->role !== Role::Employee->value || $organization->employees_can_see_billable_rates;
195-
$roundingType = $request->getRoundingType();
196-
$roundingMinutes = $request->getRoundingMinutes();
199+
$roundingType = $canAccessPremiumFeatures ? $request->getRoundingType() : null;
200+
$roundingMinutes = $canAccessPremiumFeatures ? $request->getRoundingMinutes() : null;
197201

198-
$timeEntriesQuery = $this->getTimeEntriesQuery($organization, $request, $member);
202+
$timeEntriesQuery = $this->getTimeEntriesQuery($organization, $request, $member, $canAccessPremiumFeatures);
199203
$timeEntriesQuery->with([
200204
'task',
201205
'client',
@@ -332,14 +336,15 @@ public function aggregate(Organization $organization, TimeEntryAggregateRequest
332336
} else {
333337
$this->checkPermission($organization, 'time-entries:view:all');
334338
}
339+
$canAccessPremiumFeatures = $this->canAccessPremiumFeatures($organization);
335340
$user = $this->user();
336341
$showBillableRate = $this->member($organization)->role !== Role::Employee->value || $organization->employees_can_see_billable_rates;
337342

338343
$group1Type = $request->getGroup();
339344
$group2Type = $request->getSubGroup();
340345
$timeEntriesAggregateQuery = $this->getTimeEntriesAggregateQuery($organization, $request, $member);
341-
$roundingType = $request->getRoundingType();
342-
$roundingMinutes = $request->getRoundingMinutes();
346+
$roundingType = $canAccessPremiumFeatures ? $request->getRoundingType() : null;
347+
$roundingMinutes = $canAccessPremiumFeatures ? $request->getRoundingMinutes() : null;
343348

344349
$aggregatedData = $timeEntryAggregationService->getAggregatedTimeEntries(
345350
$timeEntriesAggregateQuery,
@@ -380,6 +385,7 @@ public function aggregateExport(Organization $organization, TimeEntryAggregateEx
380385
} else {
381386
$this->checkPermission($organization, 'time-entries:view:all');
382387
}
388+
$canAccessPremiumFeatures = $this->canAccessPremiumFeatures($organization);
383389
$format = $request->getFormatValue();
384390
if ($format === ExportFormat::PDF && ! $this->canAccessPremiumFeatures($organization)) {
385391
throw new FeatureIsNotAvailableInFreePlanApiException;
@@ -391,8 +397,8 @@ public function aggregateExport(Organization $organization, TimeEntryAggregateEx
391397
$group = $request->getGroup();
392398
$subGroup = $request->getSubGroup();
393399
$timeEntriesAggregateQuery = $this->getTimeEntriesAggregateQuery($organization, $request, $member);
394-
$roundingType = $request->getRoundingType();
395-
$roundingMinutes = $request->getRoundingMinutes();
400+
$roundingType = $canAccessPremiumFeatures ? $request->getRoundingType() : null;
401+
$roundingMinutes = $canAccessPremiumFeatures ? $request->getRoundingMinutes() : null;
396402

397403
$aggregatedData = $timeEntryAggregationService->getAggregatedTimeEntriesWithDescriptions(
398404
$timeEntriesAggregateQuery->clone(),

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@
119119
"extra": {
120120
"laravel": {
121121
"dont-discover": [
122-
"laravel/telescope"
122+
"laravel/telescope",
123+
"nwidart/laravel-modules"
123124
]
124125
}
125126
},

config/app.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use App\Enums\TimeFormat;
1010
use Illuminate\Support\Facades\Facade;
1111
use Illuminate\Support\ServiceProvider;
12+
use Nwidart\Modules\LaravelModulesServiceProvider;
1213

1314
return [
1415

@@ -197,6 +198,7 @@
197198
App\Providers\FortifyServiceProvider::class,
198199
App\Providers\JetstreamServiceProvider::class,
199200
// Warning: Do not add TelescopeServiceProvider here since it is already conditionally registered in AppServiceProvider
201+
LaravelModulesServiceProvider::class,
200202
])->toArray(),
201203

202204
/*

0 commit comments

Comments
 (0)