{{ trans('dates.title') }}
13 |{{ $month }}
17 | 18 |
{{ $date->project }}
23 |
24 | {{ $date->description }}
25 |├── .circleci
└── config.yml
├── .editorconfig
├── .env.example
├── .gitattributes
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── README.md
├── app
├── Console
│ └── Kernel.php
├── Date.php
├── Exceptions
│ └── Handler.php
├── Http
│ ├── Controllers
│ │ ├── Auth
│ │ │ ├── ForgotPasswordController.php
│ │ │ ├── LoginController.php
│ │ │ ├── RegisterController.php
│ │ │ ├── ResetPasswordController.php
│ │ │ └── VerificationController.php
│ │ ├── Controller.php
│ │ ├── DatesController.php
│ │ ├── HomeController.php
│ │ ├── LanguagesController.php
│ │ └── ReportsController.php
│ ├── Kernel.php
│ └── Middleware
│ │ ├── Authenticate.php
│ │ ├── CheckForMaintenanceMode.php
│ │ ├── EncryptCookies.php
│ │ ├── RedirectIfAuthenticated.php
│ │ ├── ReferrerPolicy.php
│ │ ├── RestoreLocale.php
│ │ ├── SeeReportsHistory.php
│ │ ├── TrimStrings.php
│ │ ├── TrustProxies.php
│ │ └── VerifyCsrfToken.php
├── Mail
│ ├── FillBulletinReminder.php
│ └── WeeklyReport.php
├── Project.php
├── Projects.php
├── Providers
│ ├── AppServiceProvider.php
│ ├── AuthServiceProvider.php
│ ├── BroadcastServiceProvider.php
│ ├── EventServiceProvider.php
│ └── RouteServiceProvider.php
├── Report.php
├── Slack.php
└── User.php
├── artisan
├── bootstrap
├── app.php
└── cache
│ └── .gitignore
├── composer.json
├── composer.lock
├── config
├── app.php
├── auth.php
├── broadcasting.php
├── cache.php
├── cors.php
├── database.php
├── filesystems.php
├── hashing.php
├── logging.php
├── mail.php
├── projects.example.yml
├── queue.php
├── services.php
├── session.php
└── view.php
├── database
├── .gitignore
├── factories
│ ├── DateFactory.php
│ ├── ProjectFactory.php
│ ├── ReportFactory.php
│ └── UserFactory.php
├── migrations
│ ├── 2019_02_28_102730_create_reports_table.php
│ ├── 2019_05_22_193204_create_dates_table.php
│ └── 2019_08_19_000000_create_failed_jobs_table.php
└── seeds
│ └── DatabaseSeeder.php
├── docker-compose.yml
├── docker
├── nginx-log.conf
└── nginx.conf
├── package-lock.json
├── package.json
├── phpunit.xml
├── public
├── .htaccess
├── css
│ ├── all.css
│ └── app.css
├── favicon.ico
├── images
│ ├── circuit-board.svg
│ ├── favicon.png
│ └── logos
│ │ └── .gitkeep
├── index.php
├── js
│ └── app.js
├── mix-manifest.json
├── robots.txt
└── web.config
├── resources
├── css
│ └── all.css
├── js
│ ├── TextareaAutoResize.js
│ ├── TextareaMarkdown.js
│ ├── app.js
│ ├── bootstrap.js
│ └── components
│ │ └── ExampleComponent.vue
├── lang
│ ├── en
│ │ ├── about.php
│ │ ├── auth.php
│ │ ├── dates.php
│ │ ├── emails.php
│ │ ├── form.php
│ │ ├── layout.php
│ │ ├── login.php
│ │ ├── notifications.php
│ │ ├── pagination.php
│ │ ├── passwords.php
│ │ ├── reports.php
│ │ ├── success.php
│ │ └── validation.php
│ └── fr
│ │ ├── about.php
│ │ ├── auth.php
│ │ ├── dates.php
│ │ ├── emails.php
│ │ ├── form.php
│ │ ├── layout.php
│ │ ├── login.php
│ │ ├── notifications.php
│ │ ├── pagination.php
│ │ ├── passwords.php
│ │ ├── reports.php
│ │ ├── success.php
│ │ └── validation.php
├── sass
│ ├── _variables.scss
│ └── app.scss
└── views
│ ├── about.blade.php
│ ├── dates
│ └── index.blade.php
│ ├── emails
│ ├── fill_reminder.blade.php
│ └── report.blade.php
│ ├── index.blade.php
│ ├── login.blade.php
│ ├── master.blade.php
│ ├── reports
│ ├── _info.blade.php
│ ├── index.blade.php
│ └── week_index.blade.php
│ ├── success.blade.php
│ └── vendor
│ └── mail
│ ├── html
│ ├── button.blade.php
│ ├── footer.blade.php
│ ├── header.blade.php
│ ├── layout.blade.php
│ ├── message.blade.php
│ ├── panel.blade.php
│ ├── subcopy.blade.php
│ ├── table.blade.php
│ └── themes
│ │ └── default.css
│ └── text
│ ├── button.blade.php
│ ├── footer.blade.php
│ ├── header.blade.php
│ ├── layout.blade.php
│ ├── message.blade.php
│ ├── panel.blade.php
│ ├── subcopy.blade.php
│ └── table.blade.php
├── routes
├── api.php
├── channels.php
├── console.php
└── web.php
├── server.php
├── storage
├── app
│ ├── .gitignore
│ └── public
│ │ └── .gitignore
├── framework
│ ├── .gitignore
│ ├── cache
│ │ ├── .gitignore
│ │ └── data
│ │ │ └── .gitignore
│ ├── sessions
│ │ └── .gitignore
│ ├── testing
│ │ └── .gitignore
│ └── views
│ │ └── .gitignore
└── logs
│ └── .gitignore
├── tests
├── CreatesApplication.php
├── Feature
│ ├── KeyDatesHistoryTest.php
│ ├── LoginTest.php
│ ├── PagesTest.php
│ ├── ReportsHistoryByProjectTest.php
│ ├── ReportsHistoryByWeekTest.php
│ ├── ReportsProjectExportTest.php
│ ├── SwitchLocaleTest.php
│ ├── WeeklyReportTest.php
│ └── WriteReportTest.php
├── TestCase.php
├── Unit
│ ├── DateTest.php
│ ├── ProjectTest.php
│ ├── ProjectsTest.php
│ └── ReportTest.php
└── boostrap.php
└── webpack.mix.js
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: circleci/php:7.2-node-browsers
6 | steps:
7 | - checkout
8 | - restore_cache:
9 | keys:
10 | - composer-v1-{{ checksum "composer.lock" }}
11 | - composer-v1-
12 |
13 | - run:
14 | name: Composer install
15 | command: |
16 | composer install -n --prefer-dist
17 |
18 | - save_cache:
19 | key: composer-v1-{{ checksum "composer.lock" }}
20 | paths:
21 | - vendor
22 |
23 | - restore_cache:
24 | keys:
25 | - node-v3-{{ checksum "package.json" }}
26 | - node-v3-
27 |
28 | - run:
29 | name: npm install
30 | command: |
31 | npm install
32 |
33 | - save_cache:
34 | key: node-v3-{{ checksum "package.json" }}
35 | paths:
36 | - node_modules
37 |
38 | - run:
39 | name: Generate env file
40 | command: |
41 | cp .env.example .env
42 | cp config/projects.example.yml config/projects.yml
43 | php artisan key:generate
44 |
45 | - run:
46 | name: Run tests
47 | command: |
48 | php artisan test
49 |
50 | install_from_composer:
51 | docker:
52 | - image: circleci/php:7.2-node-browsers
53 | steps:
54 | - run:
55 | name: Composer create project
56 | command: |
57 | composer create-project --prefer-dist --stability=dev entrepreneur-interet-general/bulletins
58 |
59 | - run:
60 | name: Copy default env file
61 | command: |
62 | cd bulletins
63 | cp .env.example .env
64 | cp config/projects.example.yml config/projects.yml
65 | php artisan key:generate
66 |
67 | - run:
68 | name: Create database with seeding data
69 | command: |
70 | cd bulletins
71 | touch database/database.sqlite
72 | php artisan migrate:fresh --seed
73 |
74 | - run:
75 | name: Start PHP server
76 | command: |
77 | cd bulletins
78 | php artisan serve --port=8000
79 | background: true
80 |
81 | - run:
82 | name: Test GET /
83 | command: |
84 | sleep 1
85 | wget --tries=1 http://localhost:8000
86 | workflows:
87 | version: 2
88 | commit:
89 | jobs:
90 | - build
91 | cron:
92 | triggers:
93 | - schedule:
94 | cron: "20 1 * * *"
95 | filters:
96 | branches:
97 | only:
98 | - master
99 | jobs:
100 | - install_from_composer
101 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = space
8 | indent_size = 4
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.yml]
15 | indent_size = 2
16 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | APP_NAME=Bulletins
2 | APP_ENV=local
3 | APP_KEY=
4 | APP_DEBUG=true
5 | APP_URL=http://localhost
6 | APP_LOCALE=en
7 |
8 | LOG_CHANNEL=stack
9 |
10 | DB_CONNECTION=sqlite
11 |
12 | MAIL_DRIVER=smtp
13 | MAIL_HOST=smtp.mailtrap.io
14 | MAIL_PORT=2525
15 | MAIL_USERNAME=null
16 | MAIL_PASSWORD=null
17 | MAIL_ENCRYPTION=null
18 |
19 | PROJECTS_CONFIG_FILENAME=projects.yml
20 | REPORT_TIMEZONE=UTC
21 | REPORT_COUNTRY_CODE=FR
22 | REPORT_EMAIL=hello@example.com
23 | REPORT_SECRET=password
24 | REPORTS_PASSWORD_HINT=
25 | SLACK_TOKEN=
26 | SLACK_GENERAL_CHANNEL=
27 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.css linguist-vendored
3 | *.scss linguist-vendored
4 | *.js linguist-vendored
5 | CHANGELOG.md export-ignore
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 |
16 | 1. Go to '...'
17 | 2. Click on '....'
18 | 3. Scroll down to '....'
19 | 4. See error
20 |
21 | **Expected behavior**
22 | A clear and concise description of what you expected to happen.
23 |
24 | **Screenshots**
25 | If applicable, add screenshots to help explain your problem.
26 |
27 | **Desktop (please complete the following information):**
28 | - OS: [e.g. iOS]
29 | - Browser [e.g. chrome, safari]
30 | - Version [e.g. 22]
31 |
32 | **Smartphone (please complete the following information):**
33 | - Device: [e.g. iPhone6]
34 | - OS: [e.g. iOS8.1]
35 | - Browser [e.g. stock browser, safari]
36 | - Version [e.g. 22]
37 |
38 | **Additional context**
39 | Add any other context about the problem here.
40 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /public/hot
3 | /public/storage
4 | /storage/*.key
5 | /vendor
6 | .env
7 | .phpunit.result.cache
8 | Homestead.json
9 | Homestead.yaml
10 | npm-debug.log
11 | yarn-error.log
12 | config/projects.yml
13 | public/images/logos
14 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are **welcome**. Most active contributors will be credited in the README. We accept contributions via pull requests on GitHub.
4 |
5 | ## Pull Requests
6 |
7 | - **[PSR-2 Coding Standard.](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** The easiest way to apply the conventions is to install [PHP CS Fixer](https://github.com/FriendsOfPHP/PHP-CS-Fixer).
8 | - **Ask before working on a feature.** Before spending hours on a feature, open an issue and let's discuss about it.
9 | - **Add tests!** Patches get accepted only when they include tests.
10 | - **Document any change in behaviour.** Make sure the `README.md` and any other relevant documentation are kept up-to-date.
11 | - **Create feature branches.** Small fixes can be pulled from your master branch, but features needs to be pulled from a feature branch.
12 | - **One pull request per feature.** If you want to do more than one thing, send multiple pull requests.
13 | - **Send coherent history.** Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
14 |
15 | *Happy coding!*
16 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM php:7.2-fpm
2 |
3 | # Install dependencies
4 | RUN apt-get update && apt-get install -y \
5 | build-essential \
6 | libpng-dev \
7 | libjpeg62-turbo-dev \
8 | libfreetype6-dev \
9 | locales \
10 | zip \
11 | jpegoptim optipng pngquant gifsicle \
12 | vim \
13 | unzip \
14 | git \
15 | curl
16 |
17 | # Clear cache
18 | RUN apt-get clean && rm -rf /var/lib/apt/lists/*
19 |
20 | # Install extensions
21 | RUN docker-php-ext-install pdo_mysql mbstring zip exif pcntl
22 | RUN docker-php-ext-configure gd --with-gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include/
23 | RUN docker-php-ext-install gd
24 |
25 | # Install composer
26 | RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
27 |
28 | # Add user for laravel application
29 | RUN groupadd -g 1000 www
30 | RUN useradd -u 1000 -ms /bin/bash -g www www
31 | RUN chown -R www:www /var/www
32 |
33 | # Set working directory
34 | WORKDIR /var/www
35 |
36 | RUN rm -rf ./*
37 |
38 | USER www
39 |
40 | RUN composer create-project --prefer-dist --stability=dev entrepreneur-interet-general/bulletins .
41 |
42 | RUN cp ./config/projects.example.yml ./config/projects.yml
43 |
44 | RUN composer install
45 |
46 | # Expose port 9000 and start php-fpm server
47 | EXPOSE 9000
48 |
49 | CMD ["php-fpm"]
50 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://circleci.com/gh/entrepreneur-interet-general/bulletins)
2 | [](https://github.com/entrepreneur-interet-general/bulletins/blob/master/LICENSE)
3 |
4 | # What is it
5 | Bulletins is a weekly retrospective tool for multiple projects or teams. It lets people reflect on their past week with 4 questions which can be answered super quickly:
6 | - What's the team mood?
7 | - What were the main goals this week?
8 | - What worked great and what was harder?
9 | - Do we need help?
10 |
11 | It's asynchronous and transparent at its heart. All teams can fill their retrospective when they want through a simple web interface, as long as it's before Friday 3 PM. On Fridays at 3 PM, everyone gets a weekly recap email with all filled retrospectives. The web interface lets everyone browse through previous retrospectives by week or by team.
12 |
13 | ## Documentation
14 | The documentation can be seen online at [bulletins.eig-forever.org](https://bulletins.eig-forever.org).
15 |
16 | ## License
17 | GNU Affero General Public License. Please see the [license file](LICENSE) for more information.
18 |
19 | 2019 Direction interministérielle du numérique et du système d’information et de communication de l'État. Antoine Augusti.
20 |
21 | 2019 Contributors accessible through the Git history.
22 |
--------------------------------------------------------------------------------
/app/Console/Kernel.php:
--------------------------------------------------------------------------------
1 | isSameDay($lastWorkingDay);
37 | };
38 |
39 | $schedule->call(function () {
40 | $text = trans('notifications.general_reminder', ['url' => config('app.url')]);
41 | Slack::sendMessage(config('app.slack_general_channel'), $text);
42 | })->timezone(config('app.report_timezone'))->when($isLatestWorkingDay)->at('10:00');
43 |
44 | $schedule->call(function () {
45 | $this->unfilledProjects()->map->notify();
46 | })->timezone(config('app.report_timezone'))->when($isLatestWorkingDay)->at('14:00');
47 |
48 | $schedule->call(function () {
49 | $this->unfilledProjects()->map->notify();
50 | })->timezone(config('app.report_timezone'))->when($isLatestWorkingDay)->at('14:45');
51 |
52 | $schedule->call(function () {
53 | $builder = new WeeklyReport;
54 | if ($builder->hasReports()) {
55 | Mail::to(config('app.report_email'))->send($builder);
56 | }
57 | })->timezone(config('app.report_timezone'))->fridays()->at('15:05');
58 | }
59 |
60 | private function unfilledProjects()
61 | {
62 | $currentWeek = now()->format('Y-W');
63 |
64 | return config('app.projects')->active()->unfilledProjectsFor($currentWeek);
65 | }
66 |
67 | /**
68 | * Register the commands for the application.
69 | *
70 | * @return void
71 | */
72 | protected function commands()
73 | {
74 | $this->load(__DIR__.'/Commands');
75 |
76 | require base_path('routes/console.php');
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/app/Date.php:
--------------------------------------------------------------------------------
1 | 'datetime:Y-m-d'];
11 |
12 | public function getMonthAttribute()
13 | {
14 | return $this->date->monthName.' '.$this->date->year;
15 | }
16 |
17 | public function scopeForProject($query, $project)
18 | {
19 | return $query->where('project', '=', $project);
20 | }
21 |
22 | public function scopeUpcoming($query)
23 | {
24 | return $query->where('date', '>=', now())->orderBy('date');
25 | }
26 |
27 | public function scopePast($query)
28 | {
29 | return $query->where('date', '<', now())->orderBy('date');
30 | }
31 |
32 | public function projectObject()
33 | {
34 | return config('app.projects')->where('name', $this->project)->first();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/Exceptions/Handler.php:
--------------------------------------------------------------------------------
1 | middleware('guest');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/LoginController.php:
--------------------------------------------------------------------------------
1 | middleware('guest')->except('logout');
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/RegisterController.php:
--------------------------------------------------------------------------------
1 | middleware('guest');
41 | }
42 |
43 | /**
44 | * Get a validator for an incoming registration request.
45 | *
46 | * @param array $data
47 | *
48 | * @return \Illuminate\Contracts\Validation\Validator
49 | */
50 | protected function validator(array $data)
51 | {
52 | return Validator::make($data, [
53 | 'name' => ['required', 'string', 'max:255'],
54 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
55 | 'password' => ['required', 'string', 'min:8', 'confirmed'],
56 | ]);
57 | }
58 |
59 | /**
60 | * Create a new user instance after a valid registration.
61 | *
62 | * @param array $data
63 | *
64 | * @return \App\User
65 | */
66 | protected function create(array $data)
67 | {
68 | return User::create([
69 | 'name' => $data['name'],
70 | 'email' => $data['email'],
71 | 'password' => Hash::make($data['password']),
72 | ]);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/ResetPasswordController.php:
--------------------------------------------------------------------------------
1 | middleware('guest');
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/VerificationController.php:
--------------------------------------------------------------------------------
1 | middleware('auth');
38 | $this->middleware('signed')->only('verify');
39 | $this->middleware('throttle:6,1')->only('verify', 'resend');
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Controller.php:
--------------------------------------------------------------------------------
1 | format('W');
17 | }
18 |
19 | final protected function weekNumber()
20 | {
21 | return now()->format('Y-W');
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/app/Http/Controllers/DatesController.php:
--------------------------------------------------------------------------------
1 | Date::orderBy('date', 'desc')->get()->groupBy->month,
16 | ]);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/Http/Controllers/HomeController.php:
--------------------------------------------------------------------------------
1 | $projects = config('app.projects')->active(),
14 | 'filledProjects' => $filledProjects = $this->filledProjects(),
15 | 'allFilled' => $filledProjects->count() === $projects->count(),
16 | 'week' => $this->week(),
17 | 'canBeFilled' => Report::canBeFilled(),
18 | ]);
19 | }
20 |
21 | public function login()
22 | {
23 | return view('login', ['passwordHint' => config('app.reports_password_hint')]);
24 | }
25 |
26 | public function authenticate(Request $request)
27 | {
28 | if ($request->input('password') !== config('app.report_secret')) {
29 | return back()->with('error', trans('auth.failed'));
30 | }
31 |
32 | session(['logged_in' => true]);
33 |
34 | return redirect()->intended(route('reports.choose'));
35 | }
36 |
37 | private function filledProjects()
38 | {
39 | return config('app.projects')->active()->filledProjectsFor($this->weekNumber())->names();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/Http/Controllers/LanguagesController.php:
--------------------------------------------------------------------------------
1 | $locale]);
16 |
17 | return redirect()->back();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/Http/Controllers/ReportsController.php:
--------------------------------------------------------------------------------
1 | validate([
19 | 'spirit' => ['required', Rule::in(['☹️', '😐', '🙂', '😀'])],
20 | 'project' => ['required', Rule::in(config('app.projects')->active()->names())],
21 | 'priorities' => 'required|max:300',
22 | 'victories' => 'required|max:300',
23 | 'help' => 'max:300',
24 | 'key_date' => [
25 | 'nullable',
26 | 'date',
27 | 'date_format:Y-m-d',
28 | 'required_with:key_date_description',
29 | 'unique:dates,date,null,id,project,'.$request->input('project'),
30 | ],
31 | 'key_date_description' => 'nullable|max:200|required_with:key_date',
32 | ]);
33 |
34 | Report::create([
35 | 'project' => $request->input('project'),
36 | 'week_number' => $this->weekNumber(),
37 | 'spirit' => $request->input('spirit'),
38 | 'priorities' => $request->input('priorities'),
39 | 'victories' => $request->input('victories'),
40 | 'help' => $request->input('help'),
41 | ])->save();
42 |
43 | if ($request->filled('key_date')) {
44 | Date::create([
45 | 'project' => $request->input('project'),
46 | 'date' => $request->input('key_date'),
47 | 'description' => $request->input('key_date_description'),
48 | ]);
49 | }
50 |
51 | return view('success', [
52 | 'week' => $this->week(),
53 | 'gif' => $this->randomGif(),
54 | ]);
55 | }
56 |
57 | public function choose()
58 | {
59 | abort_if(Report::published()->count() == 0, 404);
60 |
61 | $name = Report::published()->orderBy('project')->first()->project;
62 |
63 | return redirect()->route('reports.index', $name);
64 | }
65 |
66 | public function index(Request $request, Collection $reports)
67 | {
68 | abort_if($reports->count() == 0, 404);
69 |
70 | $currentProject = $request->route()->originalParameter('reports');
71 |
72 | if (! is_null($request->query('signature'))) {
73 | $projects = collect([$currentProject]);
74 | } else {
75 | $projects = Report::published()->select('project')->distinct()->get()->pluck('project');
76 | }
77 |
78 | return view('reports.index', [
79 | 'reports' => $reports->groupBy->month,
80 | 'currentProject' => $reports->first()->projectObject(),
81 | 'projects' => $projects,
82 | 'shareUrl' => URL::signedRoute('reports.index', $currentProject),
83 | 'downloadUrl' => URL::signedRoute('reports.export', $currentProject),
84 | 'upcomingDates' => Date::forProject($currentProject)->upcoming()->get(),
85 | 'pastDates' => Date::forProject($currentProject)->past()->get(),
86 | ]);
87 | }
88 |
89 | public function weekIndex()
90 | {
91 | $weekNumbers = Report::published()->latest()->select('week_number')->distinct();
92 |
93 | return view('reports.week_index', [
94 | 'data' => $weekNumbers->get()->groupBy->month,
95 | ]);
96 | }
97 |
98 | public function export(Collection $reports)
99 | {
100 | $headers = [
101 | 'Content-type' => 'text/csv',
102 | 'Pragma' => 'no-cache',
103 | 'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0',
104 | 'Expires' => '0',
105 | ];
106 |
107 | $project = $reports->first()->project;
108 | $filename = $project.'-'.now()->format('Y-m-d').'.csv';
109 |
110 | $callback = function () use ($reports) {
111 | $file = fopen('php://output', 'w');
112 | fputcsv($file, array_keys(Report::first()->toArray()));
113 |
114 | foreach ($reports as $report) {
115 | fputcsv($file, $report->toArray());
116 | }
117 | fclose($file);
118 | };
119 |
120 | return response()->streamDownload($callback, $filename, $headers);
121 | }
122 |
123 | private function randomGif()
124 | {
125 | return (object) collect([
126 | ['id' => '4Zo41lhzKt6iZ8xff9', 'width' => 480, 'height' => 480],
127 | ['id' => 'Ztw0p2RGR36E0', 'width' => 480, 'height' => 270],
128 | ['id' => 'SwImQhtiNA7io', 'width' => 480, 'height' => 297],
129 | ['id' => 'eYilisUwipOEM', 'width' => 480, 'height' => 348],
130 | ['id' => '1136UBdSNn6Bu8', 'width' => 250, 'height' => 163],
131 | ])->shuffle()->first();
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/app/Http/Kernel.php:
--------------------------------------------------------------------------------
1 | [
33 | \App\Http\Middleware\EncryptCookies::class,
34 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
35 | \Illuminate\Session\Middleware\StartSession::class,
36 | // \Illuminate\Session\Middleware\AuthenticateSession::class,
37 | \Illuminate\View\Middleware\ShareErrorsFromSession::class,
38 | \App\Http\Middleware\VerifyCsrfToken::class,
39 | \Illuminate\Routing\Middleware\SubstituteBindings::class,
40 | \App\Http\Middleware\RestoreLocale::class,
41 | \App\Http\Middleware\ReferrerPolicy::class,
42 | ],
43 |
44 | 'api' => [
45 | 'throttle:60,1',
46 | \Illuminate\Routing\Middleware\SubstituteBindings::class,
47 | ],
48 | ];
49 |
50 | /**
51 | * The application's route middleware.
52 | *
53 | * These middleware may be assigned to groups or used individually.
54 | *
55 | * @var array
56 | */
57 | protected $routeMiddleware = [
58 | 'auth' => \App\Http\Middleware\Authenticate::class,
59 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
60 | 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
61 | 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
62 | 'can' => \Illuminate\Auth\Middleware\Authorize::class,
63 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
64 | 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
65 | 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
66 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
67 | 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
68 | 'logged_in' => SeeReportsHistory::class,
69 | ];
70 | }
71 |
--------------------------------------------------------------------------------
/app/Http/Middleware/Authenticate.php:
--------------------------------------------------------------------------------
1 | expectsJson()) {
19 | return route('login');
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/Http/Middleware/CheckForMaintenanceMode.php:
--------------------------------------------------------------------------------
1 | check()) {
22 | return redirect('/home');
23 | }
24 |
25 | return $next($request);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/Http/Middleware/ReferrerPolicy.php:
--------------------------------------------------------------------------------
1 | header('Referrer-Policy', 'no-referrer');
23 | }
24 |
25 | return $response;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/Http/Middleware/RestoreLocale.php:
--------------------------------------------------------------------------------
1 | setLocale($request->session()->get('locale', config()->get('app.locale')));
19 |
20 | return $next($request);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/Http/Middleware/SeeReportsHistory.php:
--------------------------------------------------------------------------------
1 | session()->has('logged_in');
13 |
14 | if ($loggedIn or $request->hasValidSignature()) {
15 | return $next($request);
16 | }
17 |
18 | return redirect()->guest(route('login'));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/Http/Middleware/TrimStrings.php:
--------------------------------------------------------------------------------
1 | project = $project;
19 | $this->url = $url;
20 | }
21 |
22 | public function build()
23 | {
24 | return $this->markdown('emails.fill_reminder');
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/Mail/WeeklyReport.php:
--------------------------------------------------------------------------------
1 | week = $week ?? now()->format('Y-W');
22 |
23 | if (! preg_match('/^\d{4}-\d{2}$/', $this->week)) {
24 | throw new UnexpectedValueException($week.' is not a valid week.');
25 | }
26 | }
27 |
28 | public function hasReports()
29 | {
30 | return Report::forWeek($this->week)->count() > 0;
31 | }
32 |
33 | public function build()
34 | {
35 | if (! $this->hasReports()) {
36 | throw new InvalidArgumentException('No reports for week '.$this->week);
37 | }
38 |
39 | $reports = Report::forWeek($this->week)->get()->shuffle();
40 |
41 | $helpRequests = Report::forWeek($this->week)->orderBy('project')->pluck('help', 'project')->filter();
42 | $projectsNoInfo = config('app.projects')->active()->unfilledProjectsFor($this->week)->map->name;
43 |
44 | $subject = trans('emails.subject', ['week' => $this->week]);
45 |
46 | return $this->markdown('emails.report', [
47 | 'reports' => $reports,
48 | 'upcomingDates' => $this->upcomingDates($reports),
49 | 'weekNumber' => $this->week,
50 | 'helpRequests' => $helpRequests,
51 | 'projectsNoInfo' => $projectsNoInfo,
52 | ])->subject($subject);
53 | }
54 |
55 | private function upcomingDates($reports)
56 | {
57 | $friday = $reports->first()->endOfWeek;
58 | $nextFriday = $friday->copy()->addWeek(1);
59 |
60 | return Date::whereBetween('date', [$friday, $nextFriday])->orderBy('date')->get();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/Project.php:
--------------------------------------------------------------------------------
1 | name = $attributes['name'];
26 | $this->channel = $attributes['channel'];
27 | $this->members = $attributes['members'];
28 | $this->logoUrl = $attributes['logoUrl'];
29 | $this->endsOn = is_null($attributes['endsOn']) ? null : Carbon::parse($attributes['endsOn']);
30 | }
31 |
32 | public function isActive()
33 | {
34 | return is_null($this->endsOn) || $this->endsOn->isFuture();
35 | }
36 |
37 | public function notify()
38 | {
39 | switch ($this->channel) {
40 | case 'slack':
41 | $this->slackNotify();
42 | break;
43 |
44 | case 'email':
45 | $this->emailNotify();
46 | break;
47 |
48 | default:
49 | return;
50 | }
51 | }
52 |
53 | private function emailNotify()
54 | {
55 | foreach ($this->members as $member) {
56 | Mail::to($member)->send(new FillBulletinReminder($this->name, $this->fillUrl()));
57 | }
58 | }
59 |
60 | private function slackNotify()
61 | {
62 | $text = trans('notifications.individual_reminder', ['project' => $this->name, 'url' => $this->fillUrl()]);
63 |
64 | foreach ($this->members as $member) {
65 | Slack::sendMessage($member, $text);
66 | }
67 | }
68 |
69 | private function fillUrl()
70 | {
71 | return route('home', ['project' => $this->name]);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/Projects.php:
--------------------------------------------------------------------------------
1 | map(function ($project) {
21 | $attributes = [
22 | 'name' => $project['name'],
23 | 'channel' => Arr::get($project, 'notification'),
24 | 'members' => $project['members'],
25 | 'logoUrl' => $project['logo'],
26 | 'endsOn' => Arr::get($project, 'ends_on'),
27 | ];
28 |
29 | return new Project($attributes);
30 | })->sortBy('name');
31 |
32 | return new self($projects);
33 | }
34 |
35 | public function active()
36 | {
37 | return $this->filter->isActive();
38 | }
39 |
40 | public function names()
41 | {
42 | return $this->map->name;
43 | }
44 |
45 | public function add($item)
46 | {
47 | if ($this->contains($item->name)) {
48 | throw new UnexpectedValueException;
49 | }
50 |
51 | parent::add($item);
52 | }
53 |
54 | public function filledProjectsFor($week)
55 | {
56 | $this->checkIsWeek($week);
57 |
58 | return $this->whereIn('name', Report::where('week_number', $week)->pluck('project'));
59 | }
60 |
61 | public function unfilledProjectsFor($week)
62 | {
63 | $this->checkIsWeek($week);
64 |
65 | return $this->whereNotIn('name', Report::where('week_number', $week)->pluck('project'));
66 | }
67 |
68 | private function checkIsWeek($week)
69 | {
70 | if (! preg_match('/^\d{4}-\d{2}$/', $week)) {
71 | throw new UnexpectedValueException($week.' is not a valid week.');
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/Providers/AppServiceProvider.php:
--------------------------------------------------------------------------------
1 | 'App\Policies\ModelPolicy',
16 | ];
17 |
18 | /**
19 | * Register any authentication / authorization services.
20 | *
21 | * @return void
22 | */
23 | public function boot()
24 | {
25 | $this->registerPolicies();
26 |
27 | //
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/Providers/BroadcastServiceProvider.php:
--------------------------------------------------------------------------------
1 | [
19 | SendEmailVerificationNotification::class,
20 | ],
21 | ];
22 |
23 | /**
24 | * Register any events for your application.
25 | *
26 | * @return void
27 | */
28 | public function boot()
29 | {
30 | parent::boot();
31 |
32 | //
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/Providers/RouteServiceProvider.php:
--------------------------------------------------------------------------------
1 | published()
32 | ->orderBy('week_number', 'DESC')
33 | ->get() ?? abort(404);
34 | });
35 | }
36 |
37 | /**
38 | * Define the routes for the application.
39 | *
40 | * @return void
41 | */
42 | public function map()
43 | {
44 | $this->mapApiRoutes();
45 |
46 | $this->mapWebRoutes();
47 |
48 | //
49 | }
50 |
51 | /**
52 | * Define the "web" routes for the application.
53 | *
54 | * These routes all receive session state, CSRF protection, etc.
55 | *
56 | * @return void
57 | */
58 | protected function mapWebRoutes()
59 | {
60 | Route::middleware('web')
61 | ->namespace($this->namespace)
62 | ->group(base_path('routes/web.php'));
63 | }
64 |
65 | /**
66 | * Define the "api" routes for the application.
67 | *
68 | * These routes are typically stateless.
69 | *
70 | * @return void
71 | */
72 | protected function mapApiRoutes()
73 | {
74 | Route::prefix('api')
75 | ->middleware('api')
76 | ->namespace($this->namespace)
77 | ->group(base_path('routes/api.php'));
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/Report.php:
--------------------------------------------------------------------------------
1 | week_number);
17 |
18 | return now()->setIsoDate($year, $week)->startOfWeek();
19 | }
20 |
21 | public function getEndOfWeekAttribute()
22 | {
23 | return $this->startOfWeek->next(Carbon::FRIDAY);
24 | }
25 |
26 | public function getMonthAttribute()
27 | {
28 | return $this->startOfWeek->monthName.' '.$this->startOfWeek->year;
29 | }
30 |
31 | public static function canBeFilled()
32 | {
33 | $now = now(config('app.report_timezone'));
34 | $target = Carbon::parse('Friday this week 15:05', config('app.report_timezone'));
35 |
36 | return $now < $target;
37 | }
38 |
39 | public static function latestPublishedWeek()
40 | {
41 | $startOfWeek = now()->startOfWeek();
42 |
43 | if (self::canBeFilled()) {
44 | return $startOfWeek->subWeek(1)->format('Y-W');
45 | }
46 |
47 | return $startOfWeek->format('Y-W');
48 | }
49 |
50 | public static function lastWorkingDayOfWeek()
51 | {
52 | $now = now(config('app.report_timezone'));
53 | $candidate = Carbon::parse('Friday this week', config('app.report_timezone'));
54 |
55 | try {
56 | $country = Yasumi::getProviders()[config('app.report_country_code')];
57 |
58 | return Yasumi::prevWorkingDay($country, $candidate->addDay());
59 | } catch (\Exception $e) {
60 | return $candidate;
61 | }
62 | }
63 |
64 | public function scopePublished($query)
65 | {
66 | return $query->where('week_number', '<=', self::latestPublishedWeek());
67 | }
68 |
69 | public function scopeForWeek($query, $week)
70 | {
71 | return $query->where('week_number', $week);
72 | }
73 |
74 | public function projectObject()
75 | {
76 | return config('app.projects')->where('name', $this->project)->first();
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/app/Slack.php:
--------------------------------------------------------------------------------
1 | chat->postMessage(compact('channel', 'text'));
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/User.php:
--------------------------------------------------------------------------------
1 | 'datetime',
37 | ];
38 | }
39 |
--------------------------------------------------------------------------------
/artisan:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | make(Illuminate\Contracts\Console\Kernel::class);
34 |
35 | $status = $kernel->handle(
36 | $input = new Symfony\Component\Console\Input\ArgvInput,
37 | new Symfony\Component\Console\Output\ConsoleOutput
38 | );
39 |
40 | /*
41 | |--------------------------------------------------------------------------
42 | | Shutdown The Application
43 | |--------------------------------------------------------------------------
44 | |
45 | | Once Artisan has finished running, we will fire off the shutdown events
46 | | so that any final work may be done by the application before we shut
47 | | down the process. This is the last thing to happen to the request.
48 | |
49 | */
50 |
51 | $kernel->terminate($input, $status);
52 |
53 | exit($status);
54 |
--------------------------------------------------------------------------------
/bootstrap/app.php:
--------------------------------------------------------------------------------
1 | singleton(
30 | Illuminate\Contracts\Http\Kernel::class,
31 | App\Http\Kernel::class
32 | );
33 |
34 | $app->singleton(
35 | Illuminate\Contracts\Console\Kernel::class,
36 | App\Console\Kernel::class
37 | );
38 |
39 | $app->singleton(
40 | Illuminate\Contracts\Debug\ExceptionHandler::class,
41 | App\Exceptions\Handler::class
42 | );
43 |
44 | /*
45 | |--------------------------------------------------------------------------
46 | | Return The Application
47 | |--------------------------------------------------------------------------
48 | |
49 | | This script returns the application instance. The instance is given to
50 | | the calling script so we can separate the building of the instances
51 | | from the actual running of the application and sending responses.
52 | |
53 | */
54 |
55 | return $app;
56 |
--------------------------------------------------------------------------------
/bootstrap/cache/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name":"entrepreneur-interet-general/bulletins",
3 | "type":"project",
4 | "description":"Bulletins is a simple weekly retrospective tool for multiple projects or teams",
5 | "keywords":[
6 | "project-management",
7 | "reporting",
8 | "reports",
9 | "retrospective",
10 | "standup",
11 | "archive",
12 | "bulletin",
13 | "bulletins",
14 | "laravel"
15 | ],
16 | "license":"AGPL-3.0",
17 | "authors":[
18 | {
19 | "name":"Antoine Augusti",
20 | "email":"antoine.augusti@data.gouv.fr",
21 | "homepage":"https://www.antoine-augusti.fr",
22 | "role":"Developer"
23 | }
24 | ],
25 | "support":{
26 | "issues":"https://github.com/entrepreneur-interet-general/bulletins/issues"
27 | },
28 | "require":{
29 | "php": "^7.2.5",
30 | "azuyalabs/yasumi": "^2.2",
31 | "bugsnag/bugsnag-laravel": "^2.0",
32 | "fideloper/proxy": "^4.0",
33 | "fruitcake/laravel-cors": "^1.0",
34 | "laravel/framework": "^7.0",
35 | "laravel/tinker": "^2.2",
36 | "symfony/yaml": "^4.3",
37 | "wrapi/slack": "^1.0"
38 | },
39 | "require-dev": {
40 | "filp/whoops": "^2.0",
41 | "fzaninotto/faker": "^1.4",
42 | "mockery/mockery": "^1.0",
43 | "nunomaduro/collision": "^4.1",
44 | "phpunit/phpunit": "^8.0"
45 | },
46 | "config":{
47 | "optimize-autoloader":true,
48 | "preferred-install":"dist",
49 | "sort-packages":true
50 | },
51 | "extra":{
52 | "laravel":{
53 | "dont-discover":[
54 |
55 | ]
56 | }
57 | },
58 | "autoload":{
59 | "psr-4":{
60 | "App\\":"app/"
61 | },
62 | "classmap":[
63 | "database/seeds",
64 | "database/factories"
65 | ]
66 | },
67 | "autoload-dev":{
68 | "psr-4":{
69 | "Tests\\":"tests/"
70 | }
71 | },
72 | "minimum-stability":"dev",
73 | "prefer-stable":true,
74 | "scripts":{
75 | "post-autoload-dump":[
76 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
77 | "php artisan package:discover --ansi"
78 | ],
79 | "post-root-package-install":[
80 | "php -r \"file_exists('.env') || copy('.env.example', '.env');\""
81 | ],
82 | "post-create-project-cmd":[
83 | "php artisan key:generate --ansi"
84 | ]
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/config/auth.php:
--------------------------------------------------------------------------------
1 | [
17 | 'guard' => 'web',
18 | 'passwords' => 'users',
19 | ],
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Authentication Guards
24 | |--------------------------------------------------------------------------
25 | |
26 | | Next, you may define every authentication guard for your application.
27 | | Of course, a great default configuration has been defined for you
28 | | here which uses session storage and the Eloquent user provider.
29 | |
30 | | All authentication drivers have a user provider. This defines how the
31 | | users are actually retrieved out of your database or other storage
32 | | mechanisms used by this application to persist your user's data.
33 | |
34 | | Supported: "session", "token"
35 | |
36 | */
37 |
38 | 'guards' => [
39 | 'web' => [
40 | 'driver' => 'session',
41 | 'provider' => 'users',
42 | ],
43 |
44 | 'api' => [
45 | 'driver' => 'token',
46 | 'provider' => 'users',
47 | 'hash' => false,
48 | ],
49 | ],
50 |
51 | /*
52 | |--------------------------------------------------------------------------
53 | | User Providers
54 | |--------------------------------------------------------------------------
55 | |
56 | | All authentication drivers have a user provider. This defines how the
57 | | users are actually retrieved out of your database or other storage
58 | | mechanisms used by this application to persist your user's data.
59 | |
60 | | If you have multiple user tables or models you may configure multiple
61 | | sources which represent each model / table. These sources may then
62 | | be assigned to any extra authentication guards you have defined.
63 | |
64 | | Supported: "database", "eloquent"
65 | |
66 | */
67 |
68 | 'providers' => [
69 | 'users' => [
70 | 'driver' => 'eloquent',
71 | 'model' => App\User::class,
72 | ],
73 |
74 | // 'users' => [
75 | // 'driver' => 'database',
76 | // 'table' => 'users',
77 | // ],
78 | ],
79 |
80 | /*
81 | |--------------------------------------------------------------------------
82 | | Resetting Passwords
83 | |--------------------------------------------------------------------------
84 | |
85 | | You may specify multiple password reset configurations if you have more
86 | | than one user table or model in the application and you want to have
87 | | separate password reset settings based on the specific user types.
88 | |
89 | | The expire time is the number of minutes that the reset token should be
90 | | considered valid. This security feature keeps tokens short-lived so
91 | | they have less time to be guessed. You may change this as needed.
92 | |
93 | */
94 |
95 | 'passwords' => [
96 | 'users' => [
97 | 'provider' => 'users',
98 | 'table' => 'password_resets',
99 | 'expire' => 60,
100 | ],
101 | ],
102 |
103 | ];
104 |
--------------------------------------------------------------------------------
/config/broadcasting.php:
--------------------------------------------------------------------------------
1 | env('BROADCAST_DRIVER', 'null'),
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Broadcast Connections
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may define all of the broadcast connections that will be used
26 | | to broadcast events to other systems or over websockets. Samples of
27 | | each available type of connection are provided inside this array.
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'pusher' => [
34 | 'driver' => 'pusher',
35 | 'key' => env('PUSHER_APP_KEY'),
36 | 'secret' => env('PUSHER_APP_SECRET'),
37 | 'app_id' => env('PUSHER_APP_ID'),
38 | 'options' => [
39 | 'cluster' => env('PUSHER_APP_CLUSTER'),
40 | 'encrypted' => true,
41 | ],
42 | ],
43 |
44 | 'redis' => [
45 | 'driver' => 'redis',
46 | 'connection' => 'default',
47 | ],
48 |
49 | 'log' => [
50 | 'driver' => 'log',
51 | ],
52 |
53 | 'null' => [
54 | 'driver' => 'null',
55 | ],
56 |
57 | ],
58 |
59 | ];
60 |
--------------------------------------------------------------------------------
/config/cache.php:
--------------------------------------------------------------------------------
1 | env('CACHE_DRIVER', 'file'),
22 |
23 | /*
24 | |--------------------------------------------------------------------------
25 | | Cache Stores
26 | |--------------------------------------------------------------------------
27 | |
28 | | Here you may define all of the cache "stores" for your application as
29 | | well as their drivers. You may even define multiple stores for the
30 | | same cache driver to group types of items stored in your caches.
31 | |
32 | */
33 |
34 | 'stores' => [
35 |
36 | 'apc' => [
37 | 'driver' => 'apc',
38 | ],
39 |
40 | 'array' => [
41 | 'driver' => 'array',
42 | ],
43 |
44 | 'database' => [
45 | 'driver' => 'database',
46 | 'table' => 'cache',
47 | 'connection' => null,
48 | ],
49 |
50 | 'file' => [
51 | 'driver' => 'file',
52 | 'path' => storage_path('framework/cache/data'),
53 | ],
54 |
55 | 'memcached' => [
56 | 'driver' => 'memcached',
57 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'),
58 | 'sasl' => [
59 | env('MEMCACHED_USERNAME'),
60 | env('MEMCACHED_PASSWORD'),
61 | ],
62 | 'options' => [
63 | // Memcached::OPT_CONNECT_TIMEOUT => 2000,
64 | ],
65 | 'servers' => [
66 | [
67 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
68 | 'port' => env('MEMCACHED_PORT', 11211),
69 | 'weight' => 100,
70 | ],
71 | ],
72 | ],
73 |
74 | 'redis' => [
75 | 'driver' => 'redis',
76 | 'connection' => 'cache',
77 | ],
78 |
79 | 'dynamodb' => [
80 | 'driver' => 'dynamodb',
81 | 'key' => env('AWS_ACCESS_KEY_ID'),
82 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
83 | 'region' => env('AWS_REGION', 'us-east-1'),
84 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'),
85 | ],
86 |
87 | ],
88 |
89 | /*
90 | |--------------------------------------------------------------------------
91 | | Cache Key Prefix
92 | |--------------------------------------------------------------------------
93 | |
94 | | When utilizing a RAM based store such as APC or Memcached, there might
95 | | be other applications utilizing the same cache. So, we'll specify a
96 | | value to get prefixed to all our keys so we can avoid collisions.
97 | |
98 | */
99 |
100 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'),
101 |
102 | ];
103 |
--------------------------------------------------------------------------------
/config/cors.php:
--------------------------------------------------------------------------------
1 | ['api/*'],
19 |
20 | 'allowed_methods' => ['*'],
21 |
22 | 'allowed_origins' => ['*'],
23 |
24 | 'allowed_origins_patterns' => [],
25 |
26 | 'allowed_headers' => ['*'],
27 |
28 | 'exposed_headers' => false,
29 |
30 | 'max_age' => false,
31 |
32 | 'supports_credentials' => false,
33 |
34 | ];
35 |
--------------------------------------------------------------------------------
/config/database.php:
--------------------------------------------------------------------------------
1 | env('DB_CONNECTION', 'mysql'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Database Connections
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here are each of the database connections setup for your application.
24 | | Of course, examples of configuring each database platform that is
25 | | supported by Laravel is shown below to make development simple.
26 | |
27 | |
28 | | All database work in Laravel is done through the PHP PDO facilities
29 | | so make sure you have the driver for your particular database of
30 | | choice installed on your machine before you begin development.
31 | |
32 | */
33 |
34 | 'connections' => [
35 |
36 | 'sqlite' => [
37 | 'driver' => 'sqlite',
38 | 'database' => env('DB_DATABASE', database_path('database.sqlite')),
39 | 'prefix' => '',
40 | 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
41 | ],
42 |
43 | 'mysql' => [
44 | 'driver' => 'mysql',
45 | 'host' => env('DB_HOST', '127.0.0.1'),
46 | 'port' => env('DB_PORT', '3306'),
47 | 'database' => env('DB_DATABASE', 'forge'),
48 | 'username' => env('DB_USERNAME', 'forge'),
49 | 'password' => env('DB_PASSWORD', ''),
50 | 'unix_socket' => env('DB_SOCKET', ''),
51 | 'charset' => 'utf8mb4',
52 | 'collation' => 'utf8mb4_unicode_ci',
53 | 'prefix' => '',
54 | 'prefix_indexes' => true,
55 | 'strict' => true,
56 | 'engine' => null,
57 | 'options' => array_filter([
58 | // PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
59 | ]),
60 | ],
61 |
62 | 'pgsql' => [
63 | 'driver' => 'pgsql',
64 | 'host' => env('DB_HOST', '127.0.0.1'),
65 | 'port' => env('DB_PORT', '5432'),
66 | 'database' => env('DB_DATABASE', 'forge'),
67 | 'username' => env('DB_USERNAME', 'forge'),
68 | 'password' => env('DB_PASSWORD', ''),
69 | 'charset' => 'utf8',
70 | 'prefix' => '',
71 | 'prefix_indexes' => true,
72 | 'schema' => 'public',
73 | 'sslmode' => 'prefer',
74 | ],
75 |
76 | 'sqlsrv' => [
77 | 'driver' => 'sqlsrv',
78 | 'host' => env('DB_HOST', 'localhost'),
79 | 'port' => env('DB_PORT', '1433'),
80 | 'database' => env('DB_DATABASE', 'forge'),
81 | 'username' => env('DB_USERNAME', 'forge'),
82 | 'password' => env('DB_PASSWORD', ''),
83 | 'charset' => 'utf8',
84 | 'prefix' => '',
85 | 'prefix_indexes' => true,
86 | ],
87 |
88 | ],
89 |
90 | /*
91 | |--------------------------------------------------------------------------
92 | | Migration Repository Table
93 | |--------------------------------------------------------------------------
94 | |
95 | | This table keeps track of all the migrations that have already run for
96 | | your application. Using this information, we can determine which of
97 | | the migrations on disk haven't actually been run in the database.
98 | |
99 | */
100 |
101 | 'migrations' => 'migrations',
102 |
103 | /*
104 | |--------------------------------------------------------------------------
105 | | Redis Databases
106 | |--------------------------------------------------------------------------
107 | |
108 | | Redis is an open source, fast, and advanced key-value store that also
109 | | provides a richer body of commands than a typical key-value system
110 | | such as APC or Memcached. Laravel makes it easy to dig right in.
111 | |
112 | */
113 |
114 | 'redis' => [
115 |
116 | 'client' => env('REDIS_CLIENT', 'phpredis'),
117 |
118 | 'options' => [
119 | 'cluster' => env('REDIS_CLUSTER', 'redis'),
120 | ],
121 |
122 | 'default' => [
123 | 'host' => env('REDIS_HOST', '127.0.0.1'),
124 | 'password' => env('REDIS_PASSWORD', null),
125 | 'port' => env('REDIS_PORT', 6379),
126 | 'database' => env('REDIS_DB', 0),
127 | ],
128 |
129 | 'cache' => [
130 | 'host' => env('REDIS_HOST', '127.0.0.1'),
131 | 'password' => env('REDIS_PASSWORD', null),
132 | 'port' => env('REDIS_PORT', 6379),
133 | 'database' => env('REDIS_CACHE_DB', 1),
134 | ],
135 |
136 | ],
137 |
138 | ];
139 |
--------------------------------------------------------------------------------
/config/filesystems.php:
--------------------------------------------------------------------------------
1 | env('FILESYSTEM_DRIVER', 'local'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Default Cloud Filesystem Disk
21 | |--------------------------------------------------------------------------
22 | |
23 | | Many applications store files both locally and in the cloud. For this
24 | | reason, you may specify a default "cloud" driver here. This driver
25 | | will be bound as the Cloud disk implementation in the container.
26 | |
27 | */
28 |
29 | 'cloud' => env('FILESYSTEM_CLOUD', 's3'),
30 |
31 | /*
32 | |--------------------------------------------------------------------------
33 | | Filesystem Disks
34 | |--------------------------------------------------------------------------
35 | |
36 | | Here you may configure as many filesystem "disks" as you wish, and you
37 | | may even configure multiple disks of the same driver. Defaults have
38 | | been setup for each driver as an example of the required options.
39 | |
40 | | Supported Drivers: "local", "ftp", "sftp", "s3", "rackspace"
41 | |
42 | */
43 |
44 | 'disks' => [
45 |
46 | 'local' => [
47 | 'driver' => 'local',
48 | 'root' => storage_path('app'),
49 | ],
50 |
51 | 'public' => [
52 | 'driver' => 'local',
53 | 'root' => storage_path('app/public'),
54 | 'url' => env('APP_URL').'/storage',
55 | 'visibility' => 'public',
56 | ],
57 |
58 | 's3' => [
59 | 'driver' => 's3',
60 | 'key' => env('AWS_ACCESS_KEY_ID'),
61 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
62 | 'region' => env('AWS_DEFAULT_REGION'),
63 | 'bucket' => env('AWS_BUCKET'),
64 | 'url' => env('AWS_URL'),
65 | ],
66 |
67 | ],
68 |
69 | ];
70 |
--------------------------------------------------------------------------------
/config/hashing.php:
--------------------------------------------------------------------------------
1 | 'bcrypt',
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Bcrypt Options
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may specify the configuration options that should be used when
26 | | passwords are hashed using the Bcrypt algorithm. This will allow you
27 | | to control the amount of time it takes to hash the given password.
28 | |
29 | */
30 |
31 | 'bcrypt' => [
32 | 'rounds' => env('BCRYPT_ROUNDS', 10),
33 | ],
34 |
35 | /*
36 | |--------------------------------------------------------------------------
37 | | Argon Options
38 | |--------------------------------------------------------------------------
39 | |
40 | | Here you may specify the configuration options that should be used when
41 | | passwords are hashed using the Argon algorithm. These will allow you
42 | | to control the amount of time it takes to hash the given password.
43 | |
44 | */
45 |
46 | 'argon' => [
47 | 'memory' => 1024,
48 | 'threads' => 2,
49 | 'time' => 2,
50 | ],
51 |
52 | ];
53 |
--------------------------------------------------------------------------------
/config/logging.php:
--------------------------------------------------------------------------------
1 | env('LOG_CHANNEL', 'stack'),
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | Log Channels
24 | |--------------------------------------------------------------------------
25 | |
26 | | Here you may configure the log channels for your application. Out of
27 | | the box, Laravel uses the Monolog PHP logging library. This gives
28 | | you a variety of powerful log handlers / formatters to utilize.
29 | |
30 | | Available Drivers: "single", "daily", "slack", "syslog",
31 | | "errorlog", "monolog",
32 | | "custom", "stack"
33 | |
34 | */
35 |
36 | 'channels' => [
37 | 'stack' => [
38 | 'driver' => 'stack',
39 | 'channels' => ['single', 'bugsnag'],
40 | 'ignore_exceptions' => false,
41 | ],
42 |
43 | 'bugsnag' => [
44 | 'driver' => 'bugsnag',
45 | ],
46 |
47 | 'single' => [
48 | 'driver' => 'single',
49 | 'path' => storage_path('logs/laravel.log'),
50 | 'level' => 'debug',
51 | ],
52 |
53 | 'daily' => [
54 | 'driver' => 'daily',
55 | 'path' => storage_path('logs/laravel.log'),
56 | 'level' => 'debug',
57 | 'days' => 14,
58 | ],
59 |
60 | 'slack' => [
61 | 'driver' => 'slack',
62 | 'url' => env('LOG_SLACK_WEBHOOK_URL'),
63 | 'username' => 'Laravel Log',
64 | 'emoji' => ':boom:',
65 | 'level' => 'critical',
66 | ],
67 |
68 | 'papertrail' => [
69 | 'driver' => 'monolog',
70 | 'level' => 'debug',
71 | 'handler' => SyslogUdpHandler::class,
72 | 'handler_with' => [
73 | 'host' => env('PAPERTRAIL_URL'),
74 | 'port' => env('PAPERTRAIL_PORT'),
75 | ],
76 | ],
77 |
78 | 'stderr' => [
79 | 'driver' => 'monolog',
80 | 'handler' => StreamHandler::class,
81 | 'formatter' => env('LOG_STDERR_FORMATTER'),
82 | 'with' => [
83 | 'stream' => 'php://stderr',
84 | ],
85 | ],
86 |
87 | 'syslog' => [
88 | 'driver' => 'syslog',
89 | 'level' => 'debug',
90 | ],
91 |
92 | 'errorlog' => [
93 | 'driver' => 'errorlog',
94 | 'level' => 'debug',
95 | ],
96 | ],
97 |
98 | ];
99 |
--------------------------------------------------------------------------------
/config/mail.php:
--------------------------------------------------------------------------------
1 | env('MAIL_DRIVER', 'smtp'),
20 |
21 | /*
22 | |--------------------------------------------------------------------------
23 | | SMTP Host Address
24 | |--------------------------------------------------------------------------
25 | |
26 | | Here you may provide the host address of the SMTP server used by your
27 | | applications. A default option is provided that is compatible with
28 | | the Mailgun mail service which will provide reliable deliveries.
29 | |
30 | */
31 |
32 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
33 |
34 | /*
35 | |--------------------------------------------------------------------------
36 | | SMTP Host Port
37 | |--------------------------------------------------------------------------
38 | |
39 | | This is the SMTP port used by your application to deliver e-mails to
40 | | users of the application. Like the host we have set this value to
41 | | stay compatible with the Mailgun e-mail application by default.
42 | |
43 | */
44 |
45 | 'port' => env('MAIL_PORT', 587),
46 |
47 | /*
48 | |--------------------------------------------------------------------------
49 | | Global "From" Address
50 | |--------------------------------------------------------------------------
51 | |
52 | | You may wish for all e-mails sent by your application to be sent from
53 | | the same address. Here, you may specify a name and address that is
54 | | used globally for all e-mails that are sent by your application.
55 | |
56 | */
57 |
58 | 'from' => [
59 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'),
60 | 'name' => env('MAIL_FROM_NAME', 'Example'),
61 | ],
62 |
63 | /*
64 | |--------------------------------------------------------------------------
65 | | E-Mail Encryption Protocol
66 | |--------------------------------------------------------------------------
67 | |
68 | | Here you may specify the encryption protocol that should be used when
69 | | the application send e-mail messages. A sensible default using the
70 | | transport layer security protocol should provide great security.
71 | |
72 | */
73 |
74 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'),
75 |
76 | /*
77 | |--------------------------------------------------------------------------
78 | | SMTP Server Username
79 | |--------------------------------------------------------------------------
80 | |
81 | | If your SMTP server requires a username for authentication, you should
82 | | set it here. This will get used to authenticate with your server on
83 | | connection. You may also set the "password" value below this one.
84 | |
85 | */
86 |
87 | 'username' => env('MAIL_USERNAME'),
88 |
89 | 'password' => env('MAIL_PASSWORD'),
90 |
91 | /*
92 | |--------------------------------------------------------------------------
93 | | Sendmail System Path
94 | |--------------------------------------------------------------------------
95 | |
96 | | When using the "sendmail" driver to send e-mails, we will need to know
97 | | the path to where Sendmail lives on this server. A default path has
98 | | been provided here, which will work well on most of your systems.
99 | |
100 | */
101 |
102 | 'sendmail' => '/usr/sbin/sendmail -bs',
103 |
104 | /*
105 | |--------------------------------------------------------------------------
106 | | Markdown Mail Settings
107 | |--------------------------------------------------------------------------
108 | |
109 | | If you are using Markdown based email rendering, you may configure your
110 | | theme and component paths here, allowing you to customize the design
111 | | of the emails. Or, you may simply stick with the Laravel defaults!
112 | |
113 | */
114 |
115 | 'markdown' => [
116 | 'theme' => 'default',
117 |
118 | 'paths' => [
119 | resource_path('views/vendor/mail'),
120 | ],
121 | ],
122 |
123 | /*
124 | |--------------------------------------------------------------------------
125 | | Log Channel
126 | |--------------------------------------------------------------------------
127 | |
128 | | If you are using the "log" driver, you may specify the logging channel
129 | | if you prefer to keep mail messages separate from other log entries
130 | | for simpler reading. Otherwise, the default channel will be used.
131 | |
132 | */
133 |
134 | 'log_channel' => env('MAIL_LOG_CHANNEL'),
135 |
136 | ];
137 |
--------------------------------------------------------------------------------
/config/projects.example.yml:
--------------------------------------------------------------------------------
1 | -
2 | name: "Example project"
3 | notification: slack
4 | logo: images/logos/example.png
5 | members:
6 | - UEMA8DE9Y
7 | - UEN897F5U
8 | -
9 | name: "Bar"
10 | notification: email
11 | logo: images/logos/bar.png
12 | members:
13 | - bar@example.com
14 | -
15 | name: "Inactive project"
16 | notification: email
17 | logo: images/logos/example.png
18 | members:
19 | - inactive@example.com
20 | ends_on: "2000-01-01"
21 |
--------------------------------------------------------------------------------
/config/queue.php:
--------------------------------------------------------------------------------
1 | env('QUEUE_CONNECTION', 'sync'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Queue Connections
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may configure the connection information for each server that
24 | | is used by your application. A default configuration has been added
25 | | for each back-end shipped with Laravel. You are free to add more.
26 | |
27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
28 | |
29 | */
30 |
31 | 'connections' => [
32 |
33 | 'sync' => [
34 | 'driver' => 'sync',
35 | ],
36 |
37 | 'database' => [
38 | 'driver' => 'database',
39 | 'table' => 'jobs',
40 | 'queue' => 'default',
41 | 'retry_after' => 90,
42 | ],
43 |
44 | 'beanstalkd' => [
45 | 'driver' => 'beanstalkd',
46 | 'host' => 'localhost',
47 | 'queue' => 'default',
48 | 'retry_after' => 90,
49 | 'block_for' => 0,
50 | ],
51 |
52 | 'sqs' => [
53 | 'driver' => 'sqs',
54 | 'key' => env('AWS_ACCESS_KEY_ID'),
55 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
56 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
57 | 'queue' => env('SQS_QUEUE', 'your-queue-name'),
58 | 'region' => env('AWS_REGION', 'us-east-1'),
59 | ],
60 |
61 | 'redis' => [
62 | 'driver' => 'redis',
63 | 'connection' => 'default',
64 | 'queue' => env('REDIS_QUEUE', 'default'),
65 | 'retry_after' => 90,
66 | 'block_for' => null,
67 | ],
68 |
69 | ],
70 |
71 | /*
72 | |--------------------------------------------------------------------------
73 | | Failed Queue Jobs
74 | |--------------------------------------------------------------------------
75 | |
76 | | These options configure the behavior of failed queue job logging so you
77 | | can control which database and table are used to store the jobs that
78 | | have failed. You may change them to any database / table you wish.
79 | |
80 | */
81 |
82 | 'failed' => [
83 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database'),
84 | 'database' => env('DB_CONNECTION', 'mysql'),
85 | 'table' => 'failed_jobs',
86 | ],
87 |
88 | ];
89 |
--------------------------------------------------------------------------------
/config/services.php:
--------------------------------------------------------------------------------
1 | [
18 | 'domain' => env('MAILGUN_DOMAIN'),
19 | 'secret' => env('MAILGUN_SECRET'),
20 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'),
21 | ],
22 |
23 | 'postmark' => [
24 | 'token' => env('POSTMARK_TOKEN'),
25 | ],
26 |
27 | 'ses' => [
28 | 'key' => env('AWS_ACCESS_KEY_ID'),
29 | 'secret' => env('AWS_SECRET_ACCESS_KEY'),
30 | 'region' => env('AWS_REGION', 'us-east-1'),
31 | ],
32 |
33 | 'sparkpost' => [
34 | 'secret' => env('SPARKPOST_SECRET'),
35 | ],
36 |
37 | 'stripe' => [
38 | 'model' => App\User::class,
39 | 'key' => env('STRIPE_KEY'),
40 | 'secret' => env('STRIPE_SECRET'),
41 | 'webhook' => [
42 | 'secret' => env('STRIPE_WEBHOOK_SECRET'),
43 | 'tolerance' => env('STRIPE_WEBHOOK_TOLERANCE', 300),
44 | ],
45 | ],
46 |
47 | 'slack' => [
48 | 'token' => env('SLACK_TOKEN'),
49 | ],
50 |
51 | ];
52 |
--------------------------------------------------------------------------------
/config/session.php:
--------------------------------------------------------------------------------
1 | env('SESSION_DRIVER', 'file'),
22 |
23 | /*
24 | |--------------------------------------------------------------------------
25 | | Session Lifetime
26 | |--------------------------------------------------------------------------
27 | |
28 | | Here you may specify the number of minutes that you wish the session
29 | | to be allowed to remain idle before it expires. If you want them
30 | | to immediately expire on the browser closing, set that option.
31 | |
32 | */
33 |
34 | 'lifetime' => env('SESSION_LIFETIME', 120),
35 |
36 | 'expire_on_close' => false,
37 |
38 | /*
39 | |--------------------------------------------------------------------------
40 | | Session Encryption
41 | |--------------------------------------------------------------------------
42 | |
43 | | This option allows you to easily specify that all of your session data
44 | | should be encrypted before it is stored. All encryption will be run
45 | | automatically by Laravel and you can use the Session like normal.
46 | |
47 | */
48 |
49 | 'encrypt' => false,
50 |
51 | /*
52 | |--------------------------------------------------------------------------
53 | | Session File Location
54 | |--------------------------------------------------------------------------
55 | |
56 | | When using the native session driver, we need a location where session
57 | | files may be stored. A default has been set for you but a different
58 | | location may be specified. This is only needed for file sessions.
59 | |
60 | */
61 |
62 | 'files' => storage_path('framework/sessions'),
63 |
64 | /*
65 | |--------------------------------------------------------------------------
66 | | Session Database Connection
67 | |--------------------------------------------------------------------------
68 | |
69 | | When using the "database" or "redis" session drivers, you may specify a
70 | | connection that should be used to manage these sessions. This should
71 | | correspond to a connection in your database configuration options.
72 | |
73 | */
74 |
75 | 'connection' => env('SESSION_CONNECTION', null),
76 |
77 | /*
78 | |--------------------------------------------------------------------------
79 | | Session Database Table
80 | |--------------------------------------------------------------------------
81 | |
82 | | When using the "database" session driver, you may specify the table we
83 | | should use to manage the sessions. Of course, a sensible default is
84 | | provided for you; however, you are free to change this as needed.
85 | |
86 | */
87 |
88 | 'table' => 'sessions',
89 |
90 | /*
91 | |--------------------------------------------------------------------------
92 | | Session Cache Store
93 | |--------------------------------------------------------------------------
94 | |
95 | | When using the "apc", "memcached", or "dynamodb" session drivers you may
96 | | list a cache store that should be used for these sessions. This value
97 | | must match with one of the application's configured cache "stores".
98 | |
99 | */
100 |
101 | 'store' => env('SESSION_STORE', null),
102 |
103 | /*
104 | |--------------------------------------------------------------------------
105 | | Session Sweeping Lottery
106 | |--------------------------------------------------------------------------
107 | |
108 | | Some session drivers must manually sweep their storage location to get
109 | | rid of old sessions from storage. Here are the chances that it will
110 | | happen on a given request. By default, the odds are 2 out of 100.
111 | |
112 | */
113 |
114 | 'lottery' => [2, 100],
115 |
116 | /*
117 | |--------------------------------------------------------------------------
118 | | Session Cookie Name
119 | |--------------------------------------------------------------------------
120 | |
121 | | Here you may change the name of the cookie used to identify a session
122 | | instance by ID. The name specified here will get used every time a
123 | | new session cookie is created by the framework for every driver.
124 | |
125 | */
126 |
127 | 'cookie' => env(
128 | 'SESSION_COOKIE',
129 | Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
130 | ),
131 |
132 | /*
133 | |--------------------------------------------------------------------------
134 | | Session Cookie Path
135 | |--------------------------------------------------------------------------
136 | |
137 | | The session cookie path determines the path for which the cookie will
138 | | be regarded as available. Typically, this will be the root path of
139 | | your application but you are free to change this when necessary.
140 | |
141 | */
142 |
143 | 'path' => '/',
144 |
145 | /*
146 | |--------------------------------------------------------------------------
147 | | Session Cookie Domain
148 | |--------------------------------------------------------------------------
149 | |
150 | | Here you may change the domain of the cookie used to identify a session
151 | | in your application. This will determine which domains the cookie is
152 | | available to in your application. A sensible default has been set.
153 | |
154 | */
155 |
156 | 'domain' => env('SESSION_DOMAIN', null),
157 |
158 | /*
159 | |--------------------------------------------------------------------------
160 | | HTTPS Only Cookies
161 | |--------------------------------------------------------------------------
162 | |
163 | | By setting this option to true, session cookies will only be sent back
164 | | to the server if the browser has a HTTPS connection. This will keep
165 | | the cookie from being sent to you if it can not be done securely.
166 | |
167 | */
168 |
169 | 'secure' => env('SESSION_SECURE_COOKIE', null),
170 |
171 | 'same_site' => 'lax',
172 |
173 | /*
174 | |--------------------------------------------------------------------------
175 | | HTTP Access Only
176 | |--------------------------------------------------------------------------
177 | |
178 | | Setting this value to true will prevent JavaScript from accessing the
179 | | value of the cookie and the cookie will only be accessible through
180 | | the HTTP protocol. You are free to modify this option if needed.
181 | |
182 | */
183 |
184 | 'http_only' => true,
185 |
186 | /*
187 | |--------------------------------------------------------------------------
188 | | Same-Site Cookies
189 | |--------------------------------------------------------------------------
190 | |
191 | | This option determines how your cookies behave when cross-site requests
192 | | take place, and can be used to mitigate CSRF attacks. By default, we
193 | | do not enable this as other CSRF protection services are in place.
194 | |
195 | | Supported: "lax", "strict"
196 | |
197 | */
198 |
199 | 'same_site' => null,
200 |
201 | ];
202 |
--------------------------------------------------------------------------------
/config/view.php:
--------------------------------------------------------------------------------
1 | [
17 | resource_path('views'),
18 | ],
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Compiled View Path
23 | |--------------------------------------------------------------------------
24 | |
25 | | This option determines where all the compiled Blade templates will be
26 | | stored for your application. Typically, this is within the storage
27 | | directory. However, as usual, you are free to change this value.
28 | |
29 | */
30 |
31 | 'compiled' => env(
32 | 'VIEW_COMPILED_PATH',
33 | realpath(storage_path('framework/views'))
34 | ),
35 |
36 | ];
37 |
--------------------------------------------------------------------------------
/database/.gitignore:
--------------------------------------------------------------------------------
1 | *.sqlite
2 |
--------------------------------------------------------------------------------
/database/factories/DateFactory.php:
--------------------------------------------------------------------------------
1 | define(Date::class, function (Faker $faker) {
8 | return [
9 | 'project' => $faker->randomElement(config('app.projects')->names()),
10 | 'date' => $faker->dateTimeThisYear(now()->addYear(1))->format('Y-m-d'),
11 | 'description' => $faker->text(100),
12 | ];
13 | });
14 |
--------------------------------------------------------------------------------
/database/factories/ProjectFactory.php:
--------------------------------------------------------------------------------
1 | define(Project::class, function (Faker $faker) {
9 | return [
10 | 'name' => 'The name',
11 | 'channel' => 'slack',
12 | 'members' => [],
13 | 'logoUrl' => 'img.jpg',
14 | 'endsOn' => Carbon::tomorrow(),
15 | ];
16 | });
17 |
18 | $factory->state(Project::class, 'inactive', [
19 | 'endsOn' => Carbon::yesterday(),
20 | ]);
21 |
--------------------------------------------------------------------------------
/database/factories/ReportFactory.php:
--------------------------------------------------------------------------------
1 | define(Report::class, function (Faker $faker) {
8 | return [
9 | 'project' => $faker->randomElement(config('app.projects')->names()),
10 | 'week_number' => now()->subWeek(1)->format('Y-W'),
11 | 'spirit' => $faker->randomElement(['☹️', '😐', '🙂', '😀']),
12 | 'priorities' => $faker->text(300),
13 | 'victories' => $faker->text(300),
14 | 'help' => $faker->randomElement([$faker->text(300), null]),
15 | ];
16 | });
17 |
--------------------------------------------------------------------------------
/database/factories/UserFactory.php:
--------------------------------------------------------------------------------
1 | define(User::class, function (Faker $faker) {
20 | return [
21 | 'name' => $faker->name,
22 | 'email' => $faker->unique()->safeEmail,
23 | 'email_verified_at' => now(),
24 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
25 | 'remember_token' => Str::random(10),
26 | ];
27 | });
28 |
--------------------------------------------------------------------------------
/database/migrations/2019_02_28_102730_create_reports_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->string('project', 100);
19 | $table->string('week_number', 7);
20 | $table->string('spirit', 10);
21 | $table->string('priorities', 500);
22 | $table->string('victories', 500);
23 | $table->string('help', 500)->nullable();
24 | $table->timestamps();
25 |
26 | $table->unique(['project', 'week_number']);
27 | });
28 | }
29 |
30 | /**
31 | * Reverse the migrations.
32 | *
33 | * @return void
34 | */
35 | public function down()
36 | {
37 | Schema::dropIfExists('reports');
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/database/migrations/2019_05_22_193204_create_dates_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->string('project', 100);
19 | $table->date('date');
20 | $table->string('description', 200);
21 | $table->timestamps();
22 |
23 | $table->unique(['project', 'date']);
24 | });
25 | }
26 |
27 | /**
28 | * Reverse the migrations.
29 | *
30 | * @return void
31 | */
32 | public function down()
33 | {
34 | Schema::dropIfExists('dates');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/database/migrations/2019_08_19_000000_create_failed_jobs_table.php:
--------------------------------------------------------------------------------
1 | bigIncrements('id');
18 | $table->text('connection');
19 | $table->text('queue');
20 | $table->longText('payload');
21 | $table->longText('exception');
22 | $table->timestamp('failed_at')->useCurrent();
23 | });
24 | }
25 |
26 | /**
27 | * Reverse the migrations.
28 | *
29 | * @return void
30 | */
31 | public function down()
32 | {
33 | Schema::dropIfExists('failed_jobs');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/database/seeds/DatabaseSeeder.php:
--------------------------------------------------------------------------------
1 | names() as $name) {
16 | foreach ($this->weeks() as $week) {
17 | if ($faker->numberBetween(1, 100) <= 80) {
18 | factory(Report::class)->create([
19 | 'project' => $name,
20 | 'week_number' => $week,
21 | ]);
22 | }
23 | }
24 |
25 | for ($i = 0; $i < $faker->numberBetween(3, 10); $i++) {
26 | factory(Date::class)->create([
27 | 'project' => $name,
28 | 'date' => $this->randomDate($faker),
29 | ]);
30 | }
31 | }
32 | }
33 |
34 | private function weeks()
35 | {
36 | $thisFriday = new Carbon('friday');
37 | $weeks = [$thisFriday->format('Y-W')];
38 |
39 | foreach (range(1, 30) as $weekSub) {
40 | $weeks[] = $thisFriday->copy()->subWeek($weekSub)->format('Y-W');
41 | }
42 |
43 | return $weeks;
44 | }
45 |
46 | private function randomDate($faker)
47 | {
48 | return now()->addDays($faker->unique()->numberBetween(-200, 200))->toDateString();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.0"
2 | services:
3 | web:
4 | image: nginx:latest
5 | restart: always
6 | ports:
7 | - "8080:80"
8 | volumes:
9 | - .:/var/www
10 | - ./docker/nginx.conf:/etc/nginx/conf.d/default.conf
11 | - ./docker/nginx-log.conf:/usr/local/etc/php-fpm.d/zz-log.conf
12 | links:
13 | - php
14 | php:
15 | image: php:7-fpm
16 | restart: always
17 | build:
18 | context: .
19 | dockerfile: Dockerfile
20 | environment:
21 | - DB_CONNECTION="sqlite"
22 | - REPORT_SECRET="password"
23 | - APP_LOCALE="fr"
24 | - APP_ENV="production"
25 | # volumes:
26 | # - .:/var/www # dev only
27 |
--------------------------------------------------------------------------------
/docker/nginx-log.conf:
--------------------------------------------------------------------------------
1 | php_admin_flag[log_errors] = on
2 | php_flag[display_errors] = off
--------------------------------------------------------------------------------
/docker/nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen 80;
3 | index index.php index.html;
4 | server_name localhost;
5 | error_log /var/log/nginx/error.log;
6 | access_log /var/log/nginx/access.log;
7 | root /var/www/public;
8 |
9 | location ~ \.php$ {
10 | try_files $uri =404;
11 | fastcgi_split_path_info ^(.+\.php)(/.+)$;
12 | fastcgi_pass php:9000;
13 | fastcgi_index index.php;
14 | include fastcgi_params;
15 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
16 | fastcgi_param PATH_INFO $fastcgi_path_info;
17 | }
18 | location / {
19 | try_files $uri $uri/ /index.php?$query_string;
20 | gzip_static on;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "npm run development",
5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
6 | "watch": "npm run development -- --watch",
7 | "watch-poll": "npm run watch -- --watch-poll",
8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
9 | "prod": "npm run production",
10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
11 | },
12 | "devDependencies": {
13 | "cross-env": "^5.1",
14 | "laravel-mix": "^4.0.7",
15 | "vue-template-compiler": "^2.6.8"
16 | },
17 | "dependencies": {
18 | "simplemde": "^1.11.2"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
This tool is an asynchronous alternative to stand-up meetings—also called status meetings, or updates.
", 6 | 'feature_title' => 'What this tool does', 7 | 'feature_description' => " 8 |9 | During stand-up meetings, some coworkers come together at the same time, in the same place, and they speak one at a time about what's going on. In other words, stand-up meetings require several people to stop what they're doing so that everybody can hear the exact same thing at the exact same time. The problem is: some things can wait. The kind of things shared during status meetings can usually wait a few hours for people to be ready to absorb it. 10 |
11 |12 | So instead of pulling people away from from their work at the exact same time, this tool allows each team to write up a weekly statut report on their own schedule. Status repots are posted to a form so that each team can write it up whenever they're ready. When every team has posted its status report, the reports are aggregated into a newsletter that is sent to all the teams. Just like a status meeting would do, but without breaking people's productivity. 13 |
14 | ", 15 | 'works_title' => 'How this tool works', 16 | 'works_description' => ' 17 |25 | Although this tool's code is open source, status reports posted to it are not public. That being said, status reports are written up to be shared with some people. Therefore, this tool acts as a permanent home for your team's status report, which allow you to: 26 |
36 | Keep in mind the following deadlines: 37 |
Cet outil est une alternative asynchrone et écrite aux réunions de suivi hebdomadaires, aussi appelées « stand-up meetings »
", 6 | 'feature_title' => 'Principe', 7 | 'feature_description' => ' 8 |9 | Les stand-up sont des réunions durant lesquels plusieurs personnes se réunissent au même endroit, au même moment, pour dire à tour de rôle ce sur quoi elles travaillent. Ce sont des points d’étapes récurrents, qui servent à faire circuler des informations au sein d’une équipe. Problème : les stand-up demandent à plusieurs personnes d’interrompre leur travail respectif au même moment pour absorber des informations qui ne leur seront sans doute pas utiles au même moment. 10 |
11 | 12 |13 | Au lieu d’interrompre le travail de tout le monde, ceci permet à chaque équipe de faire un bilan écrit de la semaine écoulée. Les bilans sont saisis dans un formulaire, que chaque équipe remplit quand bon lui semble — du moment qu’elle le remplit à temps. Une fois renseignés, les bilans sont ensuite agrégés dans un e-mail qui est envoyé à tout le monde, et que chacun peut lire quand bon lui semble. Les informations circulent ainsi sans nécessiter de réunion. 14 |
15 | ', 16 | 'works_title' => 'Fonctionnement', 17 | 'works_description' => ' 18 |26 | Bien que le code de l’application soit ouvert, les réponses saisies dans le formulaire de l'application ne sont pas publiques. Toutefois, ces informations sont utiles pour chaque équipe : c'est pourquoi il est possible d'accéder à un historique de ses bilans, partager les bilans à l'aide d'un lien unique et d'exporter les données. 27 |
28 | ", 29 | 'deadline_title' => 'Échéances hebdomadaires', 30 | 'deadline_description' => ' 31 |32 | Les échéances à garder en tête, chaque semaine : 33 |
{{ $date->description }}
25 |76 | {{ trans('form.cant_fill') }} 77 |
78 | @endif 79 | @endsection 80 | -------------------------------------------------------------------------------- /resources/views/login.blade.php: -------------------------------------------------------------------------------- 1 | @extends('master') 2 | 3 | @section('content') 4 | 31 | @endsection 32 | -------------------------------------------------------------------------------- /resources/views/reports/_info.blade.php: -------------------------------------------------------------------------------- 1 |{{ trans('success.thanks') }}
8 | 9 | 10 | @endsection 11 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/button.blade.php: -------------------------------------------------------------------------------- 1 |
4 |
|
18 |
6 | {{ Illuminate\Mail\Markdown::parse($slot) }} 7 | | 8 |
29 |
|
51 |
4 |
|
12 |
4 | {{ Illuminate\Mail\Markdown::parse($slot) }} 5 | | 6 |