Unverified Commit 5c5c6da4 authored by Yassine Doghri's avatar Yassine Doghri
Browse files

refactor: add rector to enforce type declarations, code quality + style and remove dead code

- update CI process to include quality stage (tests + code review)
- add captainhook to install git pre-commit & pre-push hooks
- remove .devcontainer Dockerfile to use project's docker-compose services: all
services can now be started automatically using vscode
- update docs/setup-development.md
parent a54a5964
FROM php:7.3-fpm
COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get update && \
apt-get install -y nodejs
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y git vim
// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.117.1/containers/docker-existing-dockerfile
{
"name": "Existing Dockerfile",
"dockerFile": "./Dockerfile",
"name": "Castopod Host dev",
"dockerComposeFile": ["../docker-compose.yml"],
"service": "app",
"workspaceFolder": "/castopod-host",
"postCreateCommand": "cron && php spark serve --host 0.0.0.0",
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"editor.formatOnSave": true,
......@@ -13,18 +16,18 @@
"color-highlight.markerType": "dot-before"
},
"extensions": [
"mikestead.dotenv",
"bmewburn.vscode-intelephense-client",
"streetsidesoftware.code-spell-checker",
"naumovs.color-highlight",
"heybourn.headwind",
"wayou.vscode-todo-highlight",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss",
"jamesbirtles.svelte-vscode",
"dbaeumer.vscode-eslint",
"stylelint.vscode-stylelint",
"wongjn.php-sniffer",
"eamodio.gitlens"
]
"mikestead.dotenv",
"bmewburn.vscode-intelephense-client",
"streetsidesoftware.code-spell-checker",
"naumovs.color-highlight",
"heybourn.headwind",
"wayou.vscode-todo-highlight",
"esbenp.prettier-vscode",
"bradlc.vscode-tailwindcss",
"jamesbirtles.svelte-vscode",
"dbaeumer.vscode-eslint",
"stylelint.vscode-stylelint",
"wongjn.php-sniffer",
"eamodio.gitlens"
]
}
image: php:7.3-fpm
stages:
- quality
- bundle
- release
......@@ -31,16 +32,30 @@ before_script:
- curl -sL https://deb.nodesource.com/setup_12.x | bash -
- apt-get update && apt-get install -y nodejs
# Install php and js dependencies
- php composer.phar install --no-dev --ignore-platform-reqs
# Install all php and js dependencies
- php composer.phar install --prefer-dist --no-ansi --no-interaction --no-progress --ignore-platform-reqs
- npm install
# build all UI assets
- npm run build
tests:
stage: quality
script:
- vendor/bin/phpunit
code-review:
stage: quality
script:
# run rector
- vendor/bin/rector process --dry-run
bundle_app:
stage: bundle
script:
# remove dev dependencies using the --no-dev option
- php composer.phar install --no-dev --prefer-dist --no-ansi --no-interaction --no-progress --ignore-platform-reqs
# build all UI assets
- npm run build
# download GeoLite2-City archive and extract it to writable/uploads
- wget -c "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=$MAXMIND_LICENCE_KEY&suffix=tar.gz" -O - | tar -xz -C ./writable/uploads/
......@@ -65,8 +80,8 @@ release_app:
- apt-get install jq -y
- apt-get install zip -y
# make prepare-release.sh executable
- chmod +x ./prepare-release.sh
# make scripts/prepare-release.sh executable
- chmod +x ./scripts/prepare-release.sh
# IMPORTANT: delete local git tags before release to prevent eventual script failure (ie. tag already exists)
- git tag | xargs git tag -d
......
......@@ -11,7 +11,7 @@
[
"@semantic-release/exec",
{
"prepareCmd": "./prepare-release.sh ${nextRelease.version}"
"prepareCmd": "./scripts/prepare-release.sh ${nextRelease.version}"
}
],
"@semantic-release/npm",
......
####################################################
# Castopod Host development Docker file
####################################################
# NOT optimized for production
# should be used only for development purposes
####################################################
FROM php:7.3-fpm
LABEL maintainer="Yassine Doghri<yassine@podlibre.org>"
COPY . /castopod-host
WORKDIR /castopod-host
# Install composer
COPY --from=composer /usr/bin/composer /usr/bin/composer
# Install npm
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt-get update && \
apt-get install -y nodejs
# Install git + vim
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y git vim
### Install CodeIgniter's server requirements
#-- https://github.com/codeigniter4/appstarter#server-requirements
......
......@@ -4,31 +4,13 @@ namespace App\Authorization;
class FlatAuthorization extends \Myth\Auth\Authorization\FlatAuthorization
{
//--------------------------------------------------------------------
// Actions
//--------------------------------------------------------------------
/**
* Checks a group to see if they have the specified permission.
*
* @param int|string $permission
* @param int $groupId
*
* @return mixed
*/
public function groupHasPermission($permission, int $groupId)
public function groupHasPermission($permission, int $groupId): bool
{
if (
empty($permission) ||
(!is_string($permission) && !is_numeric($permission))
) {
return null;
}
if (empty($groupId) || !is_numeric($groupId)) {
return null;
}
// Get the Permission ID
$permissionId = $this->getPermissionID($permission);
......@@ -36,36 +18,23 @@ class FlatAuthorization extends \Myth\Auth\Authorization\FlatAuthorization
return false;
}
if (
$this->permissionModel->doesGroupHavePermission(
$groupId,
(int) $permissionId
)
) {
return true;
}
return false;
return (bool) $this->permissionModel->doesGroupHavePermission(
$groupId,
$permissionId,
);
}
/**
* Makes user part of given groups.
*
* @param $userId
* @param array|null $groups // Either collection of ID or names
*
* @return bool
* @param array $groups Either collection of ID or names
*/
public function setUserGroups(int $userId, $groups)
public function setUserGroups(int $userId, array $groups = []): bool
{
if (empty($userId) || !is_numeric($userId)) {
return null;
}
// remove user from all groups before resetting it in new groups
$this->groupModel->removeUserFromAllGroups($userId);
if (empty($groups)) {
if ($groups = []) {
return true;
}
......
......@@ -4,14 +4,20 @@ namespace App\Authorization;
class GroupModel extends \Myth\Auth\Authorization\GroupModel
{
public function getContributorRoles()
/**
* @return mixed[]
*/
public function getContributorRoles(): array
{
return $this->select('auth_groups.*')
->like('name', 'podcast_', 'after')
->findAll();
}
public function getUserRoles()
/**
* @return mixed[]
*/
public function getUserRoles(): array
{
return $this->select('auth_groups.*')
->notLike('name', 'podcast_', 'after')
......
......@@ -7,11 +7,6 @@ class PermissionModel extends \Myth\Auth\Authorization\PermissionModel
/**
* Checks to see if a user, or one of their groups,
* has a specific permission.
*
* @param $userId
* @param $permissionId
*
* @return bool
*/
public function doesGroupHavePermission(
int $groupId,
......@@ -33,9 +28,7 @@ class PermissionModel extends \Myth\Auth\Authorization\PermissionModel
* id => name
* ]
*
* @param int $groupId
*
* @return array
* @return array<int, string>
*/
public function getPermissionsForGroup(int $groupId): array
{
......
......@@ -26,7 +26,10 @@ class Analytics extends AnalyticsBase
$this->gateway = config('App')->adminGateway . '/analytics';
}
public function getAudioFileUrl($audioFilePath)
/**
* get the full audio file url
*/
public function getAudioFileUrl(string $audioFilePath): string
{
helper('media');
......
......@@ -2,6 +2,7 @@
namespace Config;
use CodeIgniter\Session\Handlers\FileHandler;
use CodeIgniter\Config\BaseConfig;
class App extends BaseConfig
......@@ -34,6 +35,8 @@ class App extends BaseConfig
* WITH a trailing slash:
*
* http://cdn.example.com/
*
* @var string
*/
public $mediaBaseURL = 'http://127.0.0.2:8080/';
......@@ -163,7 +166,7 @@ class App extends BaseConfig
*
* @var string
*/
public $sessionDriver = 'CodeIgniter\Session\Handlers\FileHandler';
public $sessionDriver = FileHandler::class;
/**
* --------------------------------------------------------------------------
......@@ -480,6 +483,8 @@ class App extends BaseConfig
* Media root folder
* --------------------------------------------------------------------------
* Defines the root folder for media files storage
*
* @var string
*/
public $mediaRoot = 'media';
......@@ -488,6 +493,8 @@ class App extends BaseConfig
* Admin gateway
* --------------------------------------------------------------------------
* Defines a base route for all admin pages
*
* @var string
*/
public $adminGateway = 'cp-admin';
......@@ -496,6 +503,8 @@ class App extends BaseConfig
* Auth gateway
* --------------------------------------------------------------------------
* Defines a base route for all authentication related pages
*
* @var string
*/
public $authGateway = 'cp-auth';
......@@ -504,6 +513,8 @@ class App extends BaseConfig
* Install gateway
* --------------------------------------------------------------------------
* Defines a base route for instance installation
*
* @var string
*/
public $installGateway = 'cp-install';
}
......@@ -4,10 +4,13 @@ namespace Config;
class Auth extends \Myth\Auth\Config\Auth
{
//--------------------------------------------------------------------
// Views used by Auth Controllers
//--------------------------------------------------------------------
/**
* --------------------------------------------------------------------------
* Views used by Auth Controllers
* --------------------------------------------------------------------------
*
* @var array<string, string>
*/
public $views = [
'login' => 'auth/login',
'register' => 'auth/register',
......@@ -17,26 +20,35 @@ class Auth extends \Myth\Auth\Config\Auth
'emailActivation' => 'auth/emails/activation',
];
//--------------------------------------------------------------------
// Layout for the views to extend
//--------------------------------------------------------------------
/**
* --------------------------------------------------------------------------
* Layout for the views to extend
* --------------------------------------------------------------------------
*
* @var string
*/
public $viewLayout = 'auth/_layout';
//--------------------------------------------------------------------
// Allow User Registration
//--------------------------------------------------------------------
// When enabled (default) any unregistered user may apply for a new
// account. If you disable registration you may need to ensure your
// controllers and views know not to offer registration.
//
/**
* --------------------------------------------------------------------------
* Allow User Registration
* --------------------------------------------------------------------------
* When enabled (default) any unregistered user may apply for a new
* account. If you disable registration you may need to ensure your
* controllers and views know not to offer registration.
*
* @var bool
*/
public $allowRegistration = false;
//--------------------------------------------------------------------
// Require confirmation registration via email
//--------------------------------------------------------------------
// When enabled, every registered user will receive an email message
// with a special link he have to confirm to activate his account.
//
/**
* --------------------------------------------------------------------------
* Require confirmation registration via email
* --------------------------------------------------------------------------
* When enabled, every registered user will receive an email message
* with a special link he have to confirm to activate his account.
*
* @var bool
*/
public $requireActivation = false;
}
......@@ -32,7 +32,7 @@ class ContentSecurityPolicy extends BaseConfig
*
* @var string|null
*/
public $reportURI = null;
public $reportURI;
/**
* Instructs user agents to rewrite URL schemes, changing
......@@ -53,7 +53,7 @@ class ContentSecurityPolicy extends BaseConfig
*
* @var string|string[]|null
*/
public $defaultSrc = null;
public $defaultSrc;
/**
* Lists allowed scripts' URLs.
......@@ -83,7 +83,7 @@ class ContentSecurityPolicy extends BaseConfig
*
* @var string|string[]|null
*/
public $baseURI = null;
public $baseURI;
/**
* Lists the URLs for workers and embedded frame contents
......@@ -105,7 +105,7 @@ class ContentSecurityPolicy extends BaseConfig
*
* @var string|string[]
*/
public $fontSrc = null;
public $fontSrc;
/**
* Lists valid endpoints for submission from `<form>` tags.
......@@ -122,14 +122,14 @@ class ContentSecurityPolicy extends BaseConfig
*
* @var string|string[]|null
*/
public $frameAncestors = null;
public $frameAncestors;
/**
* Restricts the origins allowed to deliver video and audio.
*
* @var string|string[]|null
*/
public $mediaSrc = null;
public $mediaSrc;
/**
* Allows control over Flash and other plugins.
......@@ -141,19 +141,19 @@ class ContentSecurityPolicy extends BaseConfig
/**
* @var string|string[]|null
*/
public $manifestSrc = null;
public $manifestSrc;
/**
* Limits the kinds of plugins a page may invoke.
*
* @var string|string[]|null
*/
public $pluginTypes = null;
public $pluginTypes;
/**
* List of actions allowed.
*
* @var string|string[]|null
*/
public $sandbox = null;
public $sandbox;
}
......@@ -62,6 +62,7 @@ class Database extends Config
'username' => '',
'password' => '',
'database' => ':memory:',
/** @noRector StringClassNameToClassConstantRector */
'DBDriver' => 'SQLite3',
'DBPrefix' => 'db_', // Needed to ensure we're working correctly with prefixes live. DO NOT REMOVE FOR CI DEVS
'pConnect' => false,
......
......@@ -52,7 +52,7 @@ Events::on('pre_system', function () {
}
});
Events::on('login', function ($user) {
Events::on('login', function ($user): void {
helper('auth');
// set interact_as_actor_id value
......@@ -62,7 +62,7 @@ Events::on('login', function ($user) {
}
});
Events::on('logout', function ($user) {
Events::on('logout', function ($user): void {
helper('auth');
// remove user's interact_as_actor session
......@@ -75,7 +75,7 @@ Events::on('logout', function ($user) {
* --------------------------------------------------------------------
* Update episode metadata counts
*/
Events::on('on_note_add', function ($note) {
Events::on('on_note_add', function ($note): void {
if ($note->episode_id) {
model('EpisodeModel')
->where('id', $note->episode_id)
......@@ -87,7 +87,7 @@ Events::on('on_note_add', function ($note) {
cache()->deleteMatching("page_podcast#{$note->actor->podcast->id}*");
});
Events::on('on_note_remove', function ($note) {
Events::on('on_note_remove', function ($note): void {
if ($note->episode_id) {
model('EpisodeModel')
->where('id', $note->episode_id)
......@@ -106,7 +106,7 @@ Events::on('on_note_remove', function ($note) {
cache()->deleteMatching("page_note#{$note->id}*");
});
Events::on('on_note_reblog', function ($actor, $note) {
Events::on('on_note_reblog', function ($actor, $note): void {
if ($episodeId = $note->episode_id) {
model('EpisodeModel')
->where('id', $episodeId)
......@@ -125,7 +125,7 @@ Events::on('on_note_reblog', function ($actor, $note) {
}
});
Events::on('on_note_undo_reblog', function ($reblogNote) {
Events::on('on_note_undo_reblog', function ($reblogNote): void {
$note = $reblogNote->reblog_of_note;
if ($episodeId = $note->episode_id) {
model('EpisodeModel')
......@@ -147,21 +147,21 @@ Events::on('on_note_undo_reblog', function ($reblogNote) {
}
});
Events::on('on_note_reply', function ($reply) {
Events::on('on_note_reply', function ($reply): void {
$note = $reply->reply_to_note;
cache()->deleteMatching("page_podcast#{$note->actor->podcast->id}*");
cache()->deleteMatching("page_note#{$note->id}*");
});
Events::on('on_reply_remove', function ($reply) {
Events::on('on_reply_remove', function ($reply): void {
$note = $reply->reply_to_note;
cache()->deleteMatching("page_podcast#{$note->actor->podcast->id}*");
cache()->deleteMatching("page_note#{$note->id}*");
});
Events::on('on_note_favourite', function ($actor, $note) {
Events::on('on_note_favourite', function ($actor, $note): void {
if ($note->episode_id) {
model('EpisodeModel')
->where('id', $note->episode_id)
......@@ -176,7 +176,7 @@ Events::on('on_note_favourite', function ($actor, $note) {
}
});
Events::on('on_note_undo_favourite', function ($actor, $note) {
Events::on('on_note_undo_favourite', function ($actor, $note): void {
if ($note->episode_id) {
model('EpisodeModel')
->where('id', $note->episode_id)
......@@ -191,22 +191,22 @@ Events::on('on_note_undo_favourite', function ($actor, $note) {
}
});
Events::on('on_block_actor', function ($actorId) {
Events::on('on_block_actor', function ($actorId): void {
cache()->deleteMatching('page_podcast*');
cache()->deleteMatching('page_note*');
});
Events::on('on_unblock_actor', function ($actorId) {
Events::on('on_unblock_actor', function ($actorId): void {
cache()->deleteMatching('page_podcast*');
cache()->deleteMatching('page_note*');
});
Events::on('on_block_domain', function ($domainName) {
Events::on('on_block_domain', function ($domainName): void {
cache()->deleteMatching('page_podcast*');
cache()->deleteMatching('page_note*');
});
Events::on('on_unblock_domain', function ($domainName) {
Events::on('on_unblock_domain', function ($domainName): void {
cache()->deleteMatching('page_podcast*');
cache()->deleteMatching('page_note*');
});
......@@ -2,6 +2,10 @@
namespace Config;
use Myth\Auth\Filters\LoginFilter;
use Myth\Auth\Filters\RoleFilter;
use App\Filters\PermissionFilter;
use ActivityPub\Filters\ActivityPubFilter;
use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Filters\CSRF;
use CodeIgniter\Filters\DebugToolbar;
......@@ -19,10 +23,10 @@ class Filters extends BaseConfig
'csrf' => CSRF::class,
'toolbar' => DebugToolbar::class,