Skip to content

Commit 97717a2

Browse files
TatevikGrtatevikg1
andauthored
release: dev → main (implement SPA with subscriber, campaign, list, template and bounce management) (#73)
* dev * CodeRabbit config * Remove ApiClient * Login * PublicRoute Attr * SessionAuthenticator * Vue dashboard * Icons * Feat: subscribers (#78) * Css * Css + bootstrap * Sidebar * grid * sticky * routes * Update rest-client and core * Redirect to home if logged in * Exclude api from firewall * ApiSessionListener * SubscribersController * testing * Use tailwind * Subscribers * Subscribers pass data * Color ext-wf1 * Align icon with text * Mobile * Filter subscribers * Filter subscribers * Fix: created * ref: SubscribersController * Fix: loading for SPA * Fix: test * Fix: style * Fix: tests * spa.html.twig * subscriberFilters * find subscriber by email * find subscriber by email, foreign key and unique id * download csv * Export CSV * Apache config * Export filtered * Sort * Fix: logout * Fix: autowiring * Add: auth user data retrieval * remove example page * Logout button * Add subscriber modal * Add details to subscriber modal * install js client * Fix: js client * Fix: filter * ImportSubscribers * coderabbit config * After review 0 * After review 1 --------- Co-authored-by: Tatevik <tatevikg1@gmail.com> * Feat: dashboard (#76) * Dashboard stats from api * VueApexCharts * Quick Actions Card * After review 0 --------- Co-authored-by: Tatevik <tatevikg1@gmail.com> * Feat: lists (#79) New Features Mailing lists UI: directory, create/edit/delete modals, add-subscribers modal, per-list subscribers view with selection, pagination, copy/move/delete actions Export UI for list subscribers with selectable columns and date-range options; export panel added to subscribers view New frontend routes for Lists and List Subscribers Refactor CSV export now proxied from upstream export service Chores API client surface extended; runtime dependency bumped Removed Legacy subscribers table component removed * Feat: campaigns (#81) New Features Campaigns area: directory with filtering, pagination and per-campaign actions (View, Edit, Delete, Suspend, Requeue, Copy to draft) Create/Edit campaign pages and routes; multi-step edit workflow with templates, lists, scheduling, test send and save flows Campaign details modal with preview and “Resend to lists” Topbar global search across subscribers and campaigns Style / Layout Sidebar responsiveness updated for improved desktop behavior Frontend asset loading changed to explicit asset package and deferred scripts; favicon served via frontend asset package Chores Expanded REST API client surface and added a paginated fetch helper for lists Added CKEditor rich-text editor integration Documentation Installation guidance for publishing bundle assets to the host app public path --------- Co-authored-by: Tatevik <tatevikg1@gmail.com> * Feat: templates (#84) New Features Templates section: listing, create and edit pages; responsive template library with add-from-default, preview, edit, and delete flows; full template editor with save, create, update, and derive-text utility. UI Changes Updated action buttons and icons across dashboard, lists, campaigns, subscribers and subscriber list views; renamed headings and adjusted navigation targets. Chore Internal dependency updated. --------- Co-authored-by: Tatevik <tatevikg1@gmail.com> * Feat: bounces (#85) * Feat: bounces * Bounces paginated * Bounces rules * Bounces filter by status * Per List/Campaign * Create bounce rule * Create form errors * Update dependencies to main branches * After review 0 * Set auth error handler * Remove unused authentication error handlers and related logic --------- Co-authored-by: Tatevik <tatevikg1@gmail.com> * Add git attributes file * SessionAuthenticator --------- Co-authored-by: Tatevik <tatevikg1@gmail.com>
1 parent d6333ed commit 97717a2

109 files changed

Lines changed: 23082 additions & 926 deletions

File tree

Some content is hidden

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

.gitattributes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/tests export-ignore
2+
/.github export-ignore
3+

.github/workflows/ci.yml

Lines changed: 96 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ jobs:
99
DB_USERNAME: root
1010
DB_PASSWORD: phplist
1111
BROADCAST_DRIVER: log
12+
API_BASE_URL: http://api.phplist.local/
13+
REST_API_BASE_URL: http://api.phplist.local/api/v2
1214
services:
1315
mysql:
1416
image: mysql:5.7
@@ -31,7 +33,20 @@ jobs:
3133
with:
3234
php-version: ${{ matrix.php-versions }}
3335
extensions: mbstring, dom, fileinfo, mysql
34-
coverage: xdebug #optional
36+
coverage: xdebug
37+
38+
- name: Setup Node.js
39+
uses: actions/setup-node@v4
40+
with:
41+
node-version: 20
42+
cache: yarn
43+
44+
- name: Install JS dependencies
45+
run: yarn install --frozen-lockfile
46+
47+
- name: Build frontend assets
48+
run: yarn encore dev
49+
3550
- name: Install Symfony CLI
3651
run: |
3752
curl -sS https://get.symfony.com/cli/installer | bash
@@ -43,14 +58,11 @@ jobs:
4358
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google.gpg] http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee /etc/apt/sources.list.d/google-chrome.list
4459
sudo apt-get update
4560
sudo apt-get install -y google-chrome-stable
46-
- name: Set Panther to use Chrome
47-
run: |
48-
echo "PANTHER_NO_HEADLESS=0" >> .env.test
49-
echo "PANTHER_CHROME_BINARY=/usr/bin/google-chrome" >> .env.test
50-
- name: Start mysql service
51-
run: sudo /etc/init.d/mysql start
61+
sudo apt install socat
62+
5263
- name: Verify MySQL connection on host
5364
run: mysql --host 127.0.0.1 --port ${{ job.services.mysql.ports['3306'] }} -u${{ env.DB_USERNAME }} -p${{ env.DB_PASSWORD }} -e "SHOW DATABASES"
65+
5466
- name: Get composer cache directory
5567
id: composer-cache
5668
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
@@ -60,21 +72,82 @@ jobs:
6072
path: ${{ steps.composer-cache.outputs.dir }}
6173
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
6274
restore-keys: ${{ runner.os }}-composer-
63-
- name: Install the latest dependencies
64-
run: composer install
75+
- name: Install PHP dependencies
76+
run: composer install --no-interaction --prefer-dist
77+
6578
- name: Set up database schema
6679
run: mysql --host 127.0.0.1 --port ${{ job.services.mysql.ports['3306'] }} -u${{ env.DB_USERNAME }} -p${{ env.DB_PASSWORD }} ${{ env.DB_DATABASE }} < vendor/phplist/core/resources/Database/Schema.sql
67-
- name: Validating composer.json
68-
run: composer validate --no-check-all --no-check-lock --strict;
69-
- name: Linting all php files
70-
run: find src/ tests/ public/ -name ''*.php'' -print0 | xargs -0 -n 1 -P 4 php -l; php -l;
71-
- name: Running integration tests with phpunit
72-
run: vendor/bin/phpunit tests/Integration/;
73-
- name: Running the system tests
74-
run: vendor/bin/phpunit tests/System/;
75-
- name: Running static analysis
76-
run: vendor/bin/phpstan analyse -l 5 src/ tests/;
77-
- name: Running PHPMD
78-
run: vendor/bin/phpmd src/ text vendor/phplist/core/config/PHPMD/rules.xml;
79-
- name: Running PHP_CodeSniffer
80-
run: vendor/bin/phpcs --standard=vendor/phplist/core/config/PhpCodeSniffer/ src/ tests/;
80+
- name: Validate composer.json
81+
run: composer validate --no-check-all --no-check-lock --strict
82+
- name: Lint PHP files
83+
run: find src/ tests/ public/ -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l
84+
85+
- name: Run static analysis
86+
run: vendor/bin/phpstan analyse -l 5 src/ tests/
87+
- name: Run PHPMD
88+
run: vendor/bin/phpmd src/ text vendor/phplist/core/config/PHPMD/rules.xml
89+
- name: Run PHP_CodeSniffer
90+
run: vendor/bin/phpcs --standard=vendor/phplist/core/config/PhpCodeSniffer/ src/ tests/
91+
92+
- name: Install Prism
93+
run: npm install -g @stoplight/prism-cli
94+
- name: Start Prism Mock Server
95+
run: |
96+
prism mock --host 127.0.0.1 --port 4010 ./openapi.json &
97+
- name: Add local hostname
98+
run: echo "127.0.0.1 api.phplist.local" | sudo tee -a /etc/hosts
99+
- name: Proxy port 80 to 4010
100+
run: |
101+
sudo socat -d -d TCP-LISTEN:80,reuseaddr,fork TCP:127.0.0.1:4010 &
102+
- name: Wait for Prism and proxy
103+
run: |
104+
set -euo pipefail
105+
prism_ready=0
106+
for i in $(seq 1 30); do
107+
if curl -sS -o /tmp/prism-health-body.txt -w "%{http_code}" \
108+
-H 'Content-Type: application/json' \
109+
-X POST http://127.0.0.1:4010/api/v2/sessions \
110+
--data '{"login_name":"healthcheck","password":"healthcheck"}' > /tmp/prism-health-code.txt; then
111+
code=$(cat /tmp/prism-health-code.txt)
112+
if [ "$code" != "000" ]; then
113+
echo "Prism is reachable on 127.0.0.1:4010 with HTTP ${code}"
114+
prism_ready=1
115+
break
116+
fi
117+
fi
118+
sleep 1
119+
done
120+
if [ "$prism_ready" -ne 1 ]; then
121+
echo "Prism did not become reachable in time."
122+
exit 1
123+
fi
124+
125+
proxy_ready=0
126+
for i in $(seq 1 30); do
127+
if curl -sS -o /tmp/proxy-health-body.txt -w "%{http_code}" \
128+
-H 'Content-Type: application/json' \
129+
-X POST http://api.phplist.local/api/v2/sessions \
130+
--data '{"login_name":"healthcheck","password":"healthcheck"}' > /tmp/proxy-health-code.txt; then
131+
code=$(cat /tmp/proxy-health-code.txt)
132+
if [ "$code" != "000" ]; then
133+
echo "Proxy is reachable on api.phplist.local with HTTP ${code}"
134+
proxy_ready=1
135+
break
136+
fi
137+
fi
138+
sleep 1
139+
done
140+
if [ "$proxy_ready" -ne 1 ]; then
141+
echo "Proxy did not become reachable in time."
142+
exit 1
143+
fi
144+
145+
- name: Run tests with phpunit
146+
run: vendor/bin/phpunit tests
147+
148+
- name: Upload Panther screenshots
149+
if: failure()
150+
uses: actions/upload-artifact@v4
151+
with:
152+
name: panther-screenshots
153+
path: var/screenshots

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@
1717
.env
1818
/node_modules
1919
/drivers/
20+
.codex

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ Please install this package via Composer from within the
2828
which also has more detailed installation instructions in the README.
2929

3030

31+
### When this module is installed as a dependency, publish bundle assets to the host application's `public/` directory:
32+
```bash
33+
php bin/console assets:install public --symlink --relative
34+
```
35+
36+
This module serves its frontend files from `/`.
37+
38+
3139
## Contributing to this package
3240

3341
Please read the [contribution guide](.github/CONTRIBUTING.md) on how to

apache/web-frontend.conf

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<VirtualHost *:80>
2+
ServerName frontend.phplist.local
3+
ServerAdmin webmaster@localhost
4+
5+
DocumentRoot /{pathToTheProject}/web-frontend/public
6+
7+
ErrorLog ${APACHE_LOG_DIR}/web-frontend-error.log
8+
CustomLog ${APACHE_LOG_DIR}/web-frontend-access.log combined
9+
10+
<Directory /{pathToTheProject}/web-frontend/public>
11+
Options FollowSymLinks
12+
AllowOverride All
13+
Require all granted
14+
DirectoryIndex app.php
15+
</Directory>
16+
17+
<FilesMatch "^\.">
18+
Require all denied
19+
</FilesMatch>
20+
21+
<IfModule proxy_fcgi_module>
22+
<FilesMatch \.php$>
23+
SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost"
24+
</FilesMatch>
25+
</IfModule>
26+
</VirtualHost>

assets/app.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
import './styles/app.css';
12
import { createApp } from 'vue';
23
import App from './vue/App.vue';
4+
import { router } from './router';
35

4-
// Mount the main app if the element exists
56
const appElement = document.getElementById('vue-app');
7+
68
if (appElement) {
7-
createApp(App).mount('#vue-app');
9+
const app = createApp(App);
10+
app.use(router);
11+
app.mount(appElement);
812
}
913

assets/images/avatar.jpg

17.5 KB
Loading

assets/images/logo-48.png

2.78 KB
Loading

assets/images/logo.png

4.98 KB
Loading

assets/router/index.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { createRouter, createWebHistory } from 'vue-router';
2+
import DashboardView from '../vue/views/DashboardView.vue'
3+
import SubscribersView from '../vue/views/SubscribersView.vue'
4+
import ListsView from '../vue/views/ListsView.vue'
5+
import ListSubscribersView from '../vue/views/ListSubscribersView.vue'
6+
import CampaignsView from '../vue/views/CampaignsView.vue'
7+
import CampaignEditView from '../vue/views/CampaignEditView.vue'
8+
import TemplatesView from '../vue/views/TemplatesView.vue'
9+
import TemplateEditView from '../vue/views/TemplateEditView.vue'
10+
import BouncesView from '../vue/views/BouncesView.vue'
11+
12+
export const router = createRouter({
13+
history: createWebHistory(),
14+
routes: [
15+
{ path: '/', name: 'dashboard', component: DashboardView, meta: { title: 'Dashboard' } },
16+
{ path: '/subscribers', name: 'subscribers', component: SubscribersView, meta: { title: 'Subscribers' } },
17+
{ path: '/lists', name: 'lists', component: ListsView, meta: { title: 'Lists' } },
18+
{ path: '/campaigns', name: 'campaigns', component: CampaignsView, meta: { title: 'Campaigns' } },
19+
{ path: '/templates', name: 'templates', component: TemplatesView, meta: { title: 'Templates' } },
20+
{ path: '/templates/create', name: 'template-create', component: TemplateEditView, meta: { title: 'Create Template' } },
21+
{ path: '/templates/:templateId/edit', name: 'template-edit', component: TemplateEditView, meta: { title: 'Edit Template' } },
22+
{ path: '/campaigns/create', name: 'campaign-create', component: CampaignEditView, meta: { title: 'Create Campaign' } },
23+
{ path: '/campaigns/:campaignId/edit', name: 'campaign-edit', component: CampaignEditView, meta: { title: 'Edit Campaign' } },
24+
{ path: '/lists/:listId/subscribers', name: 'list-subscribers', component: ListSubscribersView, meta: { title: 'List Subscribers' } },
25+
{ path: '/bounces', name: 'bounces', component: BouncesView, meta: { title: 'Bounces' } },
26+
{ path: '/:pathMatch(.*)*', redirect: '/' },
27+
],
28+
});
29+
30+
router.afterEach((to) => {
31+
const defaultTitle = 'phpList';
32+
const pageTitle = to.meta.title;
33+
document.title = pageTitle ? `${defaultTitle} - ${pageTitle}` : defaultTitle;
34+
});

0 commit comments

Comments
 (0)