feat: add npm for js dependencies + move src/ files to root folder

- add node service in docker-compose.yml
- update .devcontainer Dockerfile by adding node, npm and vim
- init package.json for npm with tailwindcss, postcss, commitlint and commitizen as dev dependencies
- update default layout main header and footer
- replace CI's welcome_message.php with home.php listing all podcasts
- add AUTHORS.md file
- add docs folder in which to place castopod's technical documentation
This commit is contained in:
Yassine Doghri 2020-06-05 13:54:40 +00:00
parent 3bf9420b59
commit cbb83a6f30
129 changed files with 94753 additions and 1551 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -6,6 +6,11 @@ RUN apt-get update && apt-get install -y \
COPY --from=composer /usr/bin/composer /usr/bin/composer
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
RUN apt-get update && \
apt-get install -y nodejs
RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y git
apt-get install -y git vim

View File

@ -1,16 +1,18 @@
// 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",
"settings": {
"terminal.integrated.shell.linux": null
},
"extensions": [
"mikestead.dotenv",
"bmewburn.vscode-intelephense-client",
"streetsidesoftware.code-spell-checker",
"naumovs.color-highlight",
"heybourn.headwind"
]
}
"name": "Existing Dockerfile",
"dockerFile": "./Dockerfile",
"settings": {
"terminal.integrated.shell.linux": null
},
"extensions": [
"mikestead.dotenv",
"bmewburn.vscode-intelephense-client",
"streetsidesoftware.code-spell-checker",
"naumovs.color-highlight",
"heybourn.headwind",
"anish-m.ci-snippets2",
"wayou.vscode-todo-highlight"
]
}

137
.gitignore vendored
View File

@ -1,3 +1,140 @@
#-------------------------
# Operating Specific Junk Files
#-------------------------
# OS X
.DS_Store
.AppleDouble
.LSOverride
# OS X Thumbnails
._*
# Windows image file caches
Thumbs.db
ehthumbs.db
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# Linux
*~
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
#-------------------------
# Environment Files
#-------------------------
# These should never be under version control,
# as it poses a security risk.
.env
.vagrant
Vagrantfile
#-------------------------
# Temporary Files
#-------------------------
writable/cache/*
!writable/cache/index.html
writable/logs/*
!writable/logs/index.html
writable/session/*
!writable/session/index.html
writable/uploads/*
!writable/uploads/index.html
writable/debugbar/*
php_errors.log
#-------------------------
# User Guide Temp Files
#-------------------------
user_guide_src/build/*
user_guide_src/cilexer/build/*
user_guide_src/cilexer/dist/*
user_guide_src/cilexer/pycilexer.egg-info/*
#-------------------------
# Test Files
#-------------------------
tests/coverage*
# Don't save phpunit under version control.
phpunit
#-------------------------
# Composer
#-------------------------
vendor
#-------------------------
# IDE / Development Files
#-------------------------
# Modules Testing
_modules/*
# phpenv local config
.php-version
# Jetbrains editors (PHPStorm, etc)
.idea/
*.iml
# Netbeans
nbproject/
build/
nbbuild/
dist/
nbdist/
nbactions.xml
nb-configuration.xml
.nb-gradle/
# Sublime Text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
*.sublime-workspace
*.sublime-project
.phpintel
/api/
# Visual Studio Code
.vscode/
/results/
/phpunit*.xml
/.phpunit.*.cache
# Media files
public/media/*
# npm
yarn.lock
node_modules
# potcss generated file
public/index.css
#-------------------------
# Docker volumes
#-------------------------

4
AUTHORS.md Normal file
View File

@ -0,0 +1,4 @@
# Authors
[Benjamin Bellamy](https://code.podlibre.org/benjamin) <benjamin@podlibre.org>
[Yassine Doghri](https://code.podlibre.org/yassine) <yassine@podlibre.org>

View File

@ -1,6 +1,6 @@
FROM php:latest
COPY ./src /castopod
COPY . /castopod
WORKDIR /castopod
### Install CodeIgniter's server requirements

149
README.md
View File

@ -19,151 +19,6 @@ Moreover Podcasters can choose to publish on Castopod while keeping their existi
---
## Setup your development environment
## Documentation
Castopod is a web app based on the `php` framework [CodeIgniter 4](https://codeigniter.com).
To setup a dev environment, we use [Docker](https://www.docker.com/). A `docker-compose.yml` and `Dockerfile` are included in the project's root folder to help you kickstart your contribution.
> Know that you don't need any prior knowledge of Docker to follow the next steps. However, if you wish to use your own environment, feel free to do so!
### Prerequisites
0. Install [docker desktop](https://www.docker.com/products/docker-desktop).
1. Clone castopod project by running:
```bash
git clone https://code.podlibre.org/podlibre/castopod.git
```
2. Create a `./src/.env` file with the minimum required config to connect the app to the database:
```env
CI_ENVIRONMENT = development
database.default.hostname = mariadb
database.default.database = castopod
database.default.username = podlibre
database.default.password = castopod
```
> _NB._ You can tweak your environment by setting more environment variables. See the `./src/env` for examples or the [CodeIgniter4 User Guide](https://codeigniter.com/user_guide/index.html) for more info.
3. Add the repository you've cloned to docker desktop's `Settings` > `Resources` > `File Sharing`.
4. Install castopod's php dependencies
- The project's dependencies aren't included in the repository, you have to download them using the composer service defined in `docker-compose.yml`
```bash
docker-compose run --rm composer install --ignore-platform-reqs
```
### Start docker containers
Go to project's root folder and run:
```bash
# starts all services declared in docker-compose.yml file
# -d option starts the containers in the background
docker-compose up -d
# See all running processes (you should see 3 processes running)
docker ps
# Alternatively, you can check all processes (you should see composer with an Exited status)
docker ps -a
```
> The `docker-compose up -d` command will boot 3 containers in the background:
>
> - `castopod_app`: a php based container with codeigniter requirements installed
> - `castopod_mariadb`: a [mariadb](https://mariadb.org/) server for persistent data
> - `castopod_phpmyadmin`: a phpmyadmin server to visualize the mariadb database
>
> _NB._ `./mariadb`, `./phpmyadmin` folders will be mounted in the project's root directory to persist data and logs.
### Initialize and populate database
Build the database with the migrate command:
```bash
# loads the database schema during first migration
docker-compose run --rm app php spark migrate
```
Populate the database with the required data:
```bash
# Populates all categories
docker-compose run --rm app php spark db:seed CategorySeeder
docker-compose run --rm app php spark db:seed LanguageSeeder
```
### Start hacking
You're all set! Start working your magic by updating the project's files! Help yourself to the [CodeIgniter4 User Guide](https://codeigniter.com/user_guide/index.html) for more insights.
To see your changes, go to:
- [localhost:8080](http://localhost:8080/) for the castopod app
- [localhost:8888](http://localhost:8888/) for the phpmyadmin interface:
- **Username**: podlibre
- **Password**: castopod
---
### Going Further during development
#### Update app dependencies
You can update the project's dependencies using the `composer` service:
```bash
docker-compose run --rm composer update --ignore-platform-reqs
```
> _NB._ Composer commands look for the `composer.json` file to find castopod's php dependencies, all of which live in the `./src/vendor` folder. For more info, check out [Composer documentation](https://getcomposer.org/doc/).
#### Useful docker / docker-compose commands
```bash
# monitor the app container
docker logs --tail 50 --follow --timestamps castopod_app
# monitor the mariadb container
docker logs --tail 50 --follow --timestamps castopod_mariadb
# monitor the phpmyadmin container
docker logs --tail 50 --follow --timestamps castopod_phpmyadmin
# restart docker containers
docker-compose restart
# Destroy all containers, opposite of `up` command
docker-compose down
```
Check [docker](https://docs.docker.com/engine/reference/commandline/docker/) and [docker-compose](https://docs.docker.com/compose/reference/) documentations for more insights.
### Developing inside a Container
If you're working in VSCode, you can take advantage of the `./.devcontainer/` folder. It defines a development container with preinstalled VSCode extensions so you don't have to worry about them. The container will be loaded with php, composer and git:
1. Install the VSCode extension [Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
2. `Ctrl/Cmd + Shift + P` > `Open in container`
The VSCode window will reload inside the dev container.
You can check that the required packages are running in the console (`Terminal` > `New Terminal`):
```bash
php -v
composer -V
git version
```
For more info, see [VSCode Remote Containers](https://code.visualstudio.com/docs/remote/containers)
You can check castopod's documentation for [setting up a development environment](./docs/setup-development.md).

View File

@ -1,4 +1,6 @@
<?php namespace Config;
<?php
namespace Config;
use CodeIgniter\Config\BaseConfig;
@ -76,7 +78,7 @@ class App extends BaseConfig
| If false, no automatic detection will be performed.
|
*/
public $negotiateLocale = false;
public $negotiateLocale = true;
/*
|--------------------------------------------------------------------------

19
app/Controllers/Home.php Normal file
View File

@ -0,0 +1,19 @@
<?php
namespace App\Controllers;
use App\Models\PodcastModel;
class Home extends BaseController
{
public function index()
{
$model = new PodcastModel();
$data = ["podcasts" => $model->findAll()];
return view('home', $data);
}
//--------------------------------------------------------------------
}

View File

@ -8,5 +8,6 @@ class Language extends Entity
{
protected $casts = [
'code' => 'string',
'native_name' => 'string'
];
}

View File

@ -0,0 +1,3 @@
@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

6
app/Language/en/Home.php Normal file
View File

@ -0,0 +1,6 @@
<?
return [
"all_podcasts" => "All podcasts",
"no_podcast" => "No podcast found"
];

View File

@ -0,0 +1,141 @@
<?
return [
"create" => "Create a Podcast",
"form" => [
'title' => 'Title',
'name' => 'Name',
'description' => 'Description',
'episode_description_footer' => 'Episode description footer',
'image' => 'Image',
'language' => 'Language',
'category' => 'Category',
'explicit' => 'Explicit',
'author' => 'Author',
'owner_name' => 'Owner name',
'owner_email' => 'Owner email',
'type' => [
'label' => 'Type',
'episodic' => 'Episodic',
'serial' => 'Serial',
],
'copyright' => 'Copyright',
'block' => 'Block',
'complete' => 'Complete',
'custom_html_head' => 'Custom HTML code in <head/>',
'submit' => 'Create podcast',
],
'category_options' => [
'uncategorized' => 'uncategorized',
'arts' => 'Arts',
'business' => 'Business',
'comedy' => 'Comedy',
'education' => 'Education',
'fiction' => 'Fiction',
'government' => 'Government',
'health_and_fitness' => 'Health &amp Fitness',
'history' => 'History',
'kids_and_family' => 'Kids &amp Family',
'leisure' => 'Leisure',
'music' => 'Music',
'news' => 'News',
'religion_and_spirituality' => 'Religion &amp Spirituality',
'science' => 'Science',
'society_and_culture' => 'Society &amp Culture',
'sports' => 'Sports',
'technology' => 'Technology',
'true_crime' => 'True Crime',
'tv_and_film' => 'TV &amp Film',
'books' => 'Books',
'design' => 'Design',
'fashion_and_beauty' => 'Fashion &amp Beauty',
'food' => 'Food',
'performing_arts' => 'Performing Arts',
'visual_arts' => 'Visual Arts',
'careers' => 'Careers',
'entrepreneurship' => 'Entrepreneurship',
'investing' => 'Investing',
'management' => 'Management',
'marketing' => 'Marketing',
'non_profit' => 'Non-Profit',
'comedy_interviews' => 'Comedy Interviews',
'improv' => 'Improv',
'stand_up' => 'Stand-Up',
'courses' => 'Courses',
'how_to' => 'How To',
'language_learning' => 'Language Learning',
'self-improvement' => 'Self-Improvement',
'comedy_fiction' => 'Comedy Fiction',
'drama' => 'Drama',
'science_fiction' => 'Science Fiction',
'alternative_health' => 'Alternative Health',
'fitness' => 'Fitness',
'medicine' => 'Medicine',
'mental_health' => 'Mental Health',
'nutrition' => 'Nutrition',
'sexuality' => 'Sexuality',
'education_for_kids' => 'Education for Kids',
'parenting' => 'Parenting',
'pets_and_animals' => 'Pets &amp Animals',
'stories_for_kids' => 'Stories for Kids',
'animation_and_manga' => 'Animation &amp Manga',
'automotive' => 'Automotive',
'aviation' => 'Aviation',
'crafts' => 'Crafts',
'games' => 'Games',
'hobbies' => 'Hobbies',
'home_and_garden' => 'Home &amp Garden',
'video_games' => 'Video Games',
'music_commentary' => 'Music Commentary',
'music_history' => 'Music History',
'music_interviews' => 'Music Interviews',
'business_news' => 'Business News',
'daily_news' => 'Daily News',
'entertainment_news' => 'Entertainment News',
'news_commentary' => 'News Commentary',
'politics' => 'Politics',
'sports_news' => 'Sports News',
'tech_news' => 'Tech News',
'buddhism' => 'Buddhism',
'christianity' => 'Christianity',
'hinduism' => 'Hinduism',
'islam' => 'Islam',
'judaism' => 'Judaism',
'religion' => 'Religion',
'spirituality' => 'Spirituality',
'astronomy' => 'Astronomy',
'chemistry' => 'Chemistry',
'earth_sciences' => 'Earth Sciences',
'life_sciences' => 'Life Sciences',
'mathematics' => 'Mathematics',
'natural_sciences' => 'Natural Sciences',
'nature' => 'Nature',
'physics' => 'Physics',
'social_sciences' => 'Social Sciences',
'documentary' => 'Documentary',
'personal_journals' => 'Personal Journals',
'philosophy' => 'Philosophy',
'places_and_travel' => 'Places &amp Travel',
'relationships' => 'Relationships',
'baseball' => 'Baseball',
'basketball' => 'Basketball',
'cricket' => 'Cricket',
'fantasy_sports' => 'Fantasy Sports',
'football' => 'Football',
'golf' => 'Golf',
'hockey' => 'Hockey',
'rugby' => 'Rugby',
'running' => 'Running',
'soccer' => 'Soccer',
'swimming' => 'Swimming',
'tennis' => 'Tennis',
'volleyball' => 'Volleyball',
'wilderness' => 'Wilderness',
'wrestling' => 'Wrestling',
'after_shows' => 'After Shows',
'film_history' => 'Film History',
'film_interviews' => 'Film Interviews',
'film_reviews' => 'Film Reviews',
'tv_reviews' => 'TV Reviews',
],
];

View File

@ -10,7 +10,7 @@ class LanguageModel extends Model
protected $primaryKey = 'id';
protected $allowedFields = [
'code',
'code', 'native_name'
];
protected $returnType = 'App\Entities\Language';

View File

@ -1,4 +1,5 @@
<?php
use CodeIgniter\CLI\CLI;
CLI::error('ERROR: ' . $code);

View File

@ -1,17 +1,17 @@
An uncaught Exception was encountered
Type: <?= get_class($exception), "\n"; ?>
Message: <?= $message, "\n"; ?>
Filename: <?= $exception->getFile(), "\n"; ?>
Type: <?= get_class($exception), "\n"; ?>
Message: <?= $message, "\n"; ?>
Filename: <?= $exception->getFile(), "\n"; ?>
Line Number: <?= $exception->getLine(); ?>
<?php if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE === true): ?>
<?php if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE === true) : ?>
Backtrace:
<?php foreach ($exception->getTrace() as $error): ?>
<?php if (isset($error['file'])): ?>
<?php foreach ($exception->getTrace() as $error) : ?>
<?php if (isset($error['file'])) : ?>
<?= trim('-' . $error['line'] . ' - ' . $error['file'] . '::' . $error['function']) . "\n" ?>
<?php endif ?>
<?php endforeach ?>
<?php endif ?>
<?php endif ?>

View File

@ -5,16 +5,13 @@
var tabLinks = new Array();
var contentDivs = new Array();
function init ()
{
function init() {
// Grab the tab links and content divs from the page
var tabListItems = document.getElementById('tabs').childNodes;
console.log(tabListItems);
for (var i = 0; i < tabListItems.length; i ++)
{
if (tabListItems[i].nodeName == "LI")
{
for (var i = 0; i < tabListItems.length; i++) {
if (tabListItems[i].nodeName == "LI") {
var tabLink = getFirstChildWithTagName(tabListItems[i], 'A');
var id = getHash(tabLink.getAttribute('href'));
tabLinks[id] = tabLink;
@ -26,48 +23,40 @@ function init ()
// highlight the first tab
var i = 0;
for (var id in tabLinks)
{
for (var id in tabLinks) {
tabLinks[id].onclick = showTab;
tabLinks[id].onfocus = function () { this.blur() };
if (i == 0)
{
if (i == 0) {
tabLinks[id].className = 'active';
}
i ++;
i++;
}
// Hide all content divs except the first
var i = 0;
for (var id in contentDivs)
{
if (i != 0)
{
for (var id in contentDivs) {
if (i != 0) {
console.log(contentDivs[id]);
contentDivs[id].className = 'content hide';
}
i ++;
i++;
}
}
//--------------------------------------------------------------------
function showTab ()
{
function showTab() {
var selectedId = getHash(this.getAttribute('href'));
// Highlight the selected tab, and dim all others.
// Also show the selected content div, and hide all others.
for (var id in contentDivs)
{
if (id == selectedId)
{
for (var id in contentDivs) {
if (id == selectedId) {
tabLinks[id].className = 'active';
contentDivs[id].className = 'content';
}
else
{
else {
tabLinks[id].className = '';
contentDivs[id].className = 'content hide';
}
@ -79,12 +68,9 @@ function showTab ()
//--------------------------------------------------------------------
function getFirstChildWithTagName (element, tagName)
{
for (var i = 0; i < element.childNodes.length; i ++)
{
if (element.childNodes[i].nodeName == tagName)
{
function getFirstChildWithTagName(element, tagName) {
for (var i = 0; i < element.childNodes.length; i++) {
if (element.childNodes[i].nodeName == tagName) {
return element.childNodes[i];
}
}
@ -92,30 +78,25 @@ function getFirstChildWithTagName (element, tagName)
//--------------------------------------------------------------------
function getHash (url)
{
function getHash(url) {
var hashPos = url.lastIndexOf('#');
return url.substring(hashPos + 1);
}
//--------------------------------------------------------------------
function toggle (elem)
{
function toggle(elem) {
elem = document.getElementById(elem);
if (elem.style && elem.style['display'])
{
if (elem.style && elem.style['display']) {
// Only works with the "style" attr
var disp = elem.style['display'];
}
else if (elem.currentStyle)
{
else if (elem.currentStyle) {
// For MSIE, naturally
var disp = elem.currentStyle['display'];
}
else if (window.getComputedStyle)
{
else if (window.getComputedStyle) {
// For most other browsers
var disp = document.defaultView.getComputedStyle(elem, null).getPropertyValue('display');
}

View File

@ -0,0 +1,95 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>404 Page Not Found</title>
<style>
div.logo {
height: 200px;
width: 155px;
display: inline-block;
opacity: 0.08;
position: absolute;
top: 2rem;
left: 50%;
margin-left: -73px;
}
body {
height: 100%;
background: #fafafa;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #777;
font-weight: 300;
}
h1 {
font-weight: lighter;
letter-spacing: 0.8;
font-size: 3rem;
margin-top: 0;
margin-bottom: 0;
color: #222;
}
.wrap {
max-width: 1024px;
margin: 5rem auto;
padding: 2rem;
background: #fff;
text-align: center;
border: 1px solid #efefef;
border-radius: 0.5rem;
position: relative;
}
pre {
white-space: normal;
margin-top: 1.5rem;
}
code {
background: #fafafa;
border: 1px solid #efefef;
padding: 0.5rem 1rem;
border-radius: 5px;
display: block;
}
p {
margin-top: 1.5rem;
}
.footer {
margin-top: 2rem;
border-top: 1px solid #efefef;
padding: 1em 2em 0 2em;
font-size: 85%;
color: #999;
}
a:active,
a:link,
a:visited {
color: #dd4814;
}
</style>
</head>
<body>
<div class="wrap">
<h1>404 - File Not Found</h1>
<p>
<?php if (!empty($message) && $message !== '(null)') : ?>
<?= esc($message) ?>
<?php else : ?>
Sorry! Cannot seem to find the page you were looking for.
<?php endif ?>
</p>
</div>
</body>
</html>

View File

@ -1,6 +1,7 @@
<?php $error_id = uniqid('error', true); ?>
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex">
@ -14,6 +15,7 @@
<?= file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.js') ?>
</script>
</head>
<body onload="init()">
<!-- Header -->
@ -22,8 +24,7 @@
<h1><?= htmlspecialchars($title, ENT_SUBSTITUTE, 'UTF-8'), ($exception->getCode() ? ' #' . $exception->getCode() : '') ?></h1>
<p>
<?= $exception->getMessage() ?>
<a href="https://www.google.com/search?q=<?= urlencode($title . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $exception->getMessage())) ?>"
rel="noreferrer" target="_blank">search &rarr;</a>
<a href="https://www.google.com/search?q=<?= urlencode($title . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $exception->getMessage())) ?>" rel="noreferrer" target="_blank">search &rarr;</a>
</p>
</div>
</div>
@ -43,11 +44,11 @@
<ul class="tabs" id="tabs">
<li><a href="#backtrace">Backtrace</a></li>
<li><a href="#server">Server</a></li>
<li><a href="#request">Request</a></li>
<li><a href="#response">Response</a></li>
<li><a href="#files">Files</a></li>
<li><a href="#memory">Memory</a></li>
<li><a href="#server">Server</a></li>
<li><a href="#request">Request</a></li>
<li><a href="#response">Response</a></li>
<li><a href="#files">Files</a></li>
<li><a href="#memory">Memory</a></li>
</li>
</ul>
@ -57,71 +58,69 @@
<div class="content" id="backtrace">
<ol class="trace">
<?php foreach ($trace as $index => $row) : ?>
<?php foreach ($trace as $index => $row) : ?>
<li>
<p>
<!-- Trace info -->
<?php if (isset($row['file']) && is_file($row['file'])) :?>
<?php
if (isset($row['function']) && in_array($row['function'], ['include', 'include_once', 'require', 'require_once']))
{
echo $row['function'] . ' ' . static::cleanPath($row['file']);
}
else
{
echo static::cleanPath($row['file']) . ' : ' . $row['line'];
}
?>
<?php else : ?>
{PHP internal code}
<?php endif; ?>
<!-- Class/Method -->
<?php if (isset($row['class'])) : ?>
&nbsp;&nbsp;&mdash;&nbsp;&nbsp;<?= $row['class'] . $row['type'] . $row['function'] ?>
<?php if (! empty($row['args'])) : ?>
<?php $args_id = $error_id . 'args' . $index ?>
( <a href="#" onclick="return toggle('<?= $args_id ?>');">arguments</a> )
<div class="args" id="<?= $args_id ?>">
<table cellspacing="0">
<?php
$params = null;
// Reflection by name is not available for closure function
if (substr( $row['function'], -1 ) !== '}')
{
$mirror = isset( $row['class'] ) ? new \ReflectionMethod( $row['class'], $row['function'] ) : new \ReflectionFunction( $row['function'] );
$params = $mirror->getParameters();
}
foreach ($row['args'] as $key => $value) : ?>
<tr>
<td><code><?= htmlspecialchars(isset($params[$key]) ? '$' . $params[$key]->name : "#$key", ENT_SUBSTITUTE, 'UTF-8') ?></code></td>
<td><pre><?= print_r($value, true) ?></pre></td>
</tr>
<?php endforeach ?>
</table>
</div>
<li>
<p>
<!-- Trace info -->
<?php if (isset($row['file']) && is_file($row['file'])) : ?>
<?php
if (isset($row['function']) && in_array($row['function'], ['include', 'include_once', 'require', 'require_once'])) {
echo $row['function'] . ' ' . static::cleanPath($row['file']);
} else {
echo static::cleanPath($row['file']) . ' : ' . $row['line'];
}
?>
<?php else : ?>
()
{PHP internal code}
<?php endif; ?>
<!-- Class/Method -->
<?php if (isset($row['class'])) : ?>
&nbsp;&nbsp;&mdash;&nbsp;&nbsp;<?= $row['class'] . $row['type'] . $row['function'] ?>
<?php if (!empty($row['args'])) : ?>
<?php $args_id = $error_id . 'args' . $index ?>
( <a href="#" onclick="return toggle('<?= $args_id ?>');">arguments</a> )
<div class="args" id="<?= $args_id ?>">
<table cellspacing="0">
<?php
$params = null;
// Reflection by name is not available for closure function
if (substr($row['function'], -1) !== '}') {
$mirror = isset($row['class']) ? new \ReflectionMethod($row['class'], $row['function']) : new \ReflectionFunction($row['function']);
$params = $mirror->getParameters();
}
foreach ($row['args'] as $key => $value) : ?>
<tr>
<td><code><?= htmlspecialchars(isset($params[$key]) ? '$' . $params[$key]->name : "#$key", ENT_SUBSTITUTE, 'UTF-8') ?></code></td>
<td>
<pre><?= print_r($value, true) ?></pre>
</td>
</tr>
<?php endforeach ?>
</table>
</div>
<?php else : ?>
()
<?php endif; ?>
<?php endif; ?>
<?php if (!isset($row['class']) && isset($row['function'])) : ?>
&nbsp;&nbsp;&mdash;&nbsp;&nbsp; <?= $row['function'] ?>()
<?php endif; ?>
</p>
<!-- Source? -->
<?php if (isset($row['file']) && is_file($row['file']) && isset($row['class'])) : ?>
<div class="source">
<?= static::highlightFile($row['file'], $row['line']) ?>
</div>
<?php endif; ?>
</li>
<?php if (! isset($row['class']) && isset($row['function'])) : ?>
&nbsp;&nbsp;&mdash;&nbsp;&nbsp; <?= $row['function'] ?>()
<?php endif; ?>
</p>
<!-- Source? -->
<?php if (isset($row['file']) && is_file($row['file']) && isset($row['class'])) : ?>
<div class="source">
<?= static::highlightFile($row['file'], $row['line']) ?>
</div>
<?php endif; ?>
</li>
<?php endforeach; ?>
<?php endforeach; ?>
</ol>
</div>
@ -129,8 +128,7 @@
<!-- Server -->
<div class="content" id="server">
<?php foreach (['_SERVER', '_SESSION'] as $var) : ?>
<?php if (empty($GLOBALS[$var]) || ! is_array($GLOBALS[$var]))
{
<?php if (empty($GLOBALS[$var]) || !is_array($GLOBALS[$var])) {
continue;
} ?>
@ -144,18 +142,18 @@
</tr>
</thead>
<tbody>
<?php foreach ($GLOBALS[$var] as $key => $value) : ?>
<tr>
<td><?= htmlspecialchars($key, ENT_IGNORE, 'UTF-8') ?></td>
<td>
<?php if (is_string($value)) : ?>
<?= htmlspecialchars($value, ENT_SUBSTITUTE, 'UTF-8') ?>
<?php else: ?>
<?= '<pre>' . print_r($value, true) ?>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php foreach ($GLOBALS[$var] as $key => $value) : ?>
<tr>
<td><?= htmlspecialchars($key, ENT_IGNORE, 'UTF-8') ?></td>
<td>
<?php if (is_string($value)) : ?>
<?= htmlspecialchars($value, ENT_SUBSTITUTE, 'UTF-8') ?>
<?php else : ?>
<?= '<pre>' . print_r($value, true) ?>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
@ -163,7 +161,7 @@
<!-- Constants -->
<?php $constants = get_defined_constants(true); ?>
<?php if (! empty($constants['user'])) : ?>
<?php if (!empty($constants['user'])) : ?>
<h3>Constants</h3>
<table>
@ -174,18 +172,18 @@
</tr>
</thead>
<tbody>
<?php foreach ($constants['user'] as $key => $value) : ?>
<tr>
<td><?= htmlspecialchars($key, ENT_IGNORE, 'UTF-8') ?></td>
<td>
<?php if (! is_array($value) && ! is_object($value)) : ?>
<?= htmlspecialchars($value, ENT_SUBSTITUTE, 'UTF-8') ?>
<?php else: ?>
<?= '<pre>' . print_r($value, true) ?>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php foreach ($constants['user'] as $key => $value) : ?>
<tr>
<td><?= htmlspecialchars($key, ENT_IGNORE, 'UTF-8') ?></td>
<td>
<?php if (!is_array($value) && !is_object($value)) : ?>
<?= htmlspecialchars($value, ENT_SUBSTITUTE, 'UTF-8') ?>
<?php else : ?>
<?= '<pre>' . print_r($value, true) ?>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
@ -232,8 +230,7 @@
<?php $empty = true; ?>
<?php foreach (['_GET', '_POST', '_COOKIE'] as $var) : ?>
<?php if (empty($GLOBALS[$var]) || ! is_array($GLOBALS[$var]))
{
<?php if (empty($GLOBALS[$var]) || !is_array($GLOBALS[$var])) {
continue;
} ?>
@ -249,18 +246,18 @@
</tr>
</thead>
<tbody>
<?php foreach ($GLOBALS[$var] as $key => $value) : ?>
<tr>
<td><?= htmlspecialchars($key, ENT_IGNORE, 'UTF-8') ?></td>
<td>
<?php if (! is_array($value) && ! is_object($value)) : ?>
<?= htmlspecialchars($value, ENT_SUBSTITUTE, 'UTF-8') ?>
<?php else: ?>
<?= '<pre>' . print_r($value, true) ?>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php foreach ($GLOBALS[$var] as $key => $value) : ?>
<tr>
<td><?= htmlspecialchars($key, ENT_IGNORE, 'UTF-8') ?></td>
<td>
<?php if (!is_array($value) && !is_object($value)) : ?>
<?= htmlspecialchars($value, ENT_SUBSTITUTE, 'UTF-8') ?>
<?php else : ?>
<?= '<pre>' . print_r($value, true) ?>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
@ -275,7 +272,7 @@
<?php endif; ?>
<?php $headers = $request->getHeaders(); ?>
<?php if (! empty($headers)) : ?>
<?php if (!empty($headers)) : ?>
<h3>Headers</h3>
@ -287,22 +284,20 @@
</tr>
</thead>
<tbody>
<?php foreach ($headers as $value) : ?>
<?php if (empty($value))
{
continue;
} ?>
<?php if (! is_array($value))
{
$value = [$value];
} ?>
<?php foreach ($value as $h) : ?>
<tr>
<td><?= esc($h->getName(), 'html') ?></td>
<td><?= esc($h->getValueLine(), 'html') ?></td>
</tr>
<?php foreach ($headers as $value) : ?>
<?php if (empty($value)) {
continue;
} ?>
<?php if (!is_array($value)) {
$value = [$value];
} ?>
<?php foreach ($value as $h) : ?>
<tr>
<td><?= esc($h->getName(), 'html') ?></td>
<td><?= esc($h->getValueLine(), 'html') ?></td>
</tr>
<?php endforeach; ?>
<?php endforeach; ?>
<?php endforeach; ?>
</tbody>
</table>
@ -311,8 +306,8 @@
<!-- Response -->
<?php
$response = \Config\Services::response();
$response->setStatusCode(http_response_code());
$response = \Config\Services::response();
$response->setStatusCode(http_response_code());
?>
<div class="content" id="response">
<table>
@ -323,7 +318,7 @@
</table>
<?php $headers = $response->getHeaders(); ?>
<?php if (! empty($headers)) : ?>
<?php if (!empty($headers)) : ?>
<?php natsort($headers) ?>
<h3>Headers</h3>
@ -336,12 +331,12 @@
</tr>
</thead>
<tbody>
<?php foreach ($headers as $name => $value) : ?>
<tr>
<td><?= esc($name, 'html') ?></td>
<td><?= esc($response->getHeaderLine($name), 'html') ?></td>
</tr>
<?php endforeach; ?>
<?php foreach ($headers as $name => $value) : ?>
<tr>
<td><?= esc($name, 'html') ?></td>
<td><?= esc($response->getHeaderLine($name), 'html') ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
@ -353,9 +348,9 @@
<?php $files = get_included_files(); ?>
<ol>
<?php foreach ($files as $file) :?>
<li><?= htmlspecialchars( static::cleanPath($file), ENT_SUBSTITUTE, 'UTF-8') ?></li>
<?php endforeach ?>
<?php foreach ($files as $file) : ?>
<li><?= htmlspecialchars(static::cleanPath($file), ENT_SUBSTITUTE, 'UTF-8') ?></li>
<?php endforeach ?>
</ol>
</div>
@ -381,7 +376,7 @@
</div>
</div> <!-- /tab-content -->
</div> <!-- /tab-content -->
</div> <!-- /container -->
@ -390,7 +385,7 @@
<p>
Displayed at <?= date('H:i:sa') ?> &mdash;
PHP: <?= phpversion() ?> &mdash;
PHP: <?= phpversion() ?> &mdash;
CodeIgniter: <?= \CodeIgniter\CodeIgniter::CI_VERSION ?>
</p>
@ -398,4 +393,5 @@
</div>
</body>
</html>
</html>

View File

@ -1,5 +1,6 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<meta name="robots" content="noindex">
@ -10,8 +11,8 @@
<?= preg_replace('#[\r\n\t ]+#', ' ', file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.css')) ?>
</style>
</head>
<body>
<body>
<div class="container text-center">
<h1 class="headline">Whoops!</h1>
@ -19,7 +20,6 @@
<p class="lead">We seem to have hit a snag. Please try again later...</p>
</div>
</body>
</html>
</html>

22
app/Views/home.php Normal file
View File

@ -0,0 +1,22 @@
<?= $this->extend('layouts/default') ?>
<?= $this->section('content') ?>
<h1 class="mb-2 text-xl"><?= lang("Home.all_podcasts") ?> (<?= count($podcasts) ?>)</h1>
<section class="flex flex-wrap">
<?php if ($podcasts) : ?>
<?php foreach ($podcasts as $podcast) : ?>
<a href="/@<?= $podcast->name ?>">
<article class="w-48 p-2 mb-4 mr-4 border shadow-sm hover:bg-gray-100 hover:shadow">
<img alt="<?= $podcast->title ?>" src="<?= $podcast->image ?>" class="object-cover w-full h-40 mb-2" />
<h2 class="font-semibold leading-tight"><?= $podcast->title ?></h2>
<p class="text-gray-600">@<?= $podcast->name ?></p>
</article>
</a>
<?php endforeach ?>
<?php else : ?>
<p class="italic"><?= lang("Home.no_podcast") ?></p>
<?php endif ?>
</section>
<?= $this->endSection() ?>

87170
app/Views/index.css Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Castopod</title>
<meta name="description" content="Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience.">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" type="image/png" href="/favicon.ico" />
<link rel="stylesheet" href="/index.css">
</head>
<body class="flex flex-col min-h-screen mx-auto">
<header class="border-b">
<div class="container flex items-center justify-between px-2 py-4 mx-auto">
<a href="/" class="text-2xl">Castopod</a>
<nav>
<a class="px-4 py-2 border hover:bg-gray-100" href="/podcasts/create">New podcast</a>
</nav>
</div>
</header>
<main class="container flex-1 px-4 py-10 mx-auto">
<?= $this->renderSection('content') ?>
</main>
<footer class="container px-2 py-4 mx-auto text-sm text-right border-t">
Powered by <a class="underline hover:no-underline" href="https://code.podlibre.org/podlibre/castopod">Castopod</a>, a <a class="underline hover:no-underline" href="https://podlibre.org/">Podlibre</a> initiative.
</footer>
</body>

View File

@ -0,0 +1,110 @@
<?= $this->extend('layouts/default') ?>
<?= $this->section('content') ?>
<h1 class="mb-6 text-xl"><?= lang("Podcasts.create") ?></h1>
<!-- <div class="mb-8">
\Config\Services::validation()->listErrors()
</div> -->
<?= form_open_multipart('podcasts/create', ["method" => "post", "class" => "flex flex-col max-w-md"]) ?>
<?= csrf_field() ?>
<div class="flex flex-col mb-4">
<label for="title"><?= lang("Podcasts.form.title") ?></label>
<input type="text" class="form-input" id="title" name="title" required />
</div>
<div class="flex flex-col mb-4">
<label for="name"><?= lang("Podcasts.form.name") ?></label>
<input type="text" class="form-input" id="name" name="name" required />
</div>
<div class="flex flex-col mb-4">
<label for="description"><?= lang("Podcasts.form.description") ?></label>
<textarea class="form-textarea" id="description" name="description" required></textarea>
</div>
<div class="flex flex-col mb-4">
<label for="episode_description_footer"><?= lang("Podcasts.form.episode_description_footer") ?></label>
<textarea class="form-textarea" id="episode_description_footer" name="episode_description_footer"></textarea>
</div>
<div class="flex flex-col mb-4">
<label for="image"><?= lang("Podcasts.form.image") ?></label>
<input type="file" class="form-input" id="image" name="image" required />
</div>
<div class="flex flex-col mb-4">
<label for="language"><?= lang("Podcasts.form.language") ?></label>
<select id="language" name="language" autocomplete="off" class="form-select" required>
<?php foreach ($languages as $language) : ?>
<option <?= ($language->code == $browser_lang) ? "selected='selected'" : "" ?> value="<?= $language->code ?>"><?= $language->native_name ?></option>
<?php endforeach ?>
</select>
</div>
<div class="flex flex-col mb-4">
<label for="category"><?= lang("Podcasts.form.category") ?></label>
<select id="category" name="category" class="form-select" required>
<?php foreach ($categories as $category) : ?>
<option value="<?= $category->code ?>"><?= lang("Podcasts.category_options." . $category->code) ?></option>
<?php endforeach ?>
</select>
</div>
<div class="inline-flex items-center mb-4">
<input type="checkbox" id="explicit" name="explicit" class="form-checkbox" />
<label for="explicit" class="pl-2"><?= lang("Podcasts.form.explicit") ?></label>
</div>
<div class="flex flex-col mb-4">
<label for="author"><?= lang("Podcasts.form.author") ?></label>
<input type="text" class="form-input" id="author" name="author" />
</div>
<div class="flex flex-col mb-4">
<label for="owner_name"><?= lang("Podcasts.form.owner_name") ?></label>
<input type="text" class="form-input" id="owner_name" name="owner_name" />
</div>
<div class="flex flex-col mb-4">
<label for="owner_email"><?= lang("Podcasts.form.owner_email") ?></label>
<input type="email" class="form-input" id="owner_email" name="owner_email" required />
</div>
<fieldset class="mb-4">
<legend><?= lang("Podcasts.form.type.label") ?></legend>
<input type="radio" class="form-radio" value="episodic" id="episodic" name="type" checked="checked" />
<label for="episodic"><?= lang("Podcasts.form.type.episodic") ?></label><br />
<input type="radio" class="form-radio" value="serial" id="serial" name="type" />
<label for="serial"><?= lang("Podcasts.form.type.serial") ?></label><br />
</fieldset>
<div class="flex flex-col mb-4">
<label for="copyright"><?= lang("Podcasts.form.copyright") ?></label>
<input type="text" class="form-input" id="copyright" name="copyright" />
</div>
<div class="inline-flex items-center mb-4">
<input type="checkbox" id="block" name="block" class="form-checkbox" />
<label for="block" class="pl-2"><?= lang("Podcasts.form.block") ?></label>
</div>
<div class="inline-flex items-center mb-4">
<input type="checkbox" id="complete" name="complete" class="form-checkbox" />
<label for="complete" class="pl-2"><?= lang("Podcasts.form.complete") ?></label>
</div>
<div class="flex flex-col mb-4">
<label for="custom_html_head"><?= esc(lang("Podcasts.form.custom_html_head")) ?></label>
<textarea class="form-textarea" id="custom_html_head" name="custom_html_head"></textarea>
</div>
<button type="submit" name="submit" class="self-end px-4 py-2 bg-gray-200"><?= lang("Podcasts.form.submit") ?></button>
<?= form_close() ?>
<?= $this->endSection() ?>

View File

@ -0,0 +1,7 @@
<?= $this->extend('layouts/default') ?>
<?= $this->section('content') ?>
<h1 class="text-xl"><?= $podcast->title ?></h1>
<img src="<?= base_url($podcast->image) ?>" alt="Podcast cover" />
<?= $this->endSection() ?>

View File

@ -1,11 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>403 Forbidden</title>
</head>
<body>
<p>Directory access is forbidden.</p>
<p>Directory access is forbidden.</p>
</body>
</html>
</html>

View File

1
commitlint.config.js Normal file
View File

@ -0,0 +1 @@
module.exports = { extends: ['@commitlint/config-conventional'] }

1887
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@ services:
ports:
- 8080:8080
volumes:
- ./src:/castopod
- .:/castopod
depends_on:
- mariadb
networks:
@ -21,7 +21,7 @@ services:
mariadb:
image: mariadb:latest
container_name: "castopod_mariadb"
container_name: castopod_mariadb
ports:
- 3306:3306
volumes:
@ -36,7 +36,7 @@ services:
phpmyadmin:
image: phpmyadmin/phpmyadmin:latest
container_name: "castopod_phpmyadmin"
container_name: castopod_phpmyadmin
environment:
PMA_HOST: mariadb
PMA_PORT: 3306
@ -51,13 +51,22 @@ services:
composer:
image: composer:latest
container_name: composer
container_name: castopod_composer
volumes:
- ./src:/var/www/html
- .:/var/www/html
working_dir: /var/www/html
networks:
- castopod
node:
image: node:lts
container_name: castopod_node
volumes:
- .:/usr/src/app
working_dir: /usr/src/app
networks:
- castopod
volumes:
mariadb:
phpmyadmin:

194
docs/setup-development.md Normal file
View File

@ -0,0 +1,194 @@
# Setup your development environment <!-- omit in toc -->
## Table of contents <!-- omit in toc -->
- [Introduction](#introduction)
- [Prerequisites](#prerequisites)
- [Start docker containers](#start-docker-containers)
- [Initialize and populate database](#initialize-and-populate-database)
- [Install/Update app dependencies](#installupdate-app-dependencies)
- [Start hacking](#start-hacking)
- [Going Further](#going-further)
- [Useful docker / docker-compose commands](#useful-docker--docker-compose-commands)
- [Developing inside a Container](#developing-inside-a-container)
## Introduction
Castopod is a web app based on the `php` framework [CodeIgniter 4](https://codeigniter.com).
To setup a dev environment, we use [Docker](https://www.docker.com/). A `docker-compose.yml` and `Dockerfile` are included in the project's root folder to help you kickstart your contribution.
> Know that you don't need any prior knowledge of Docker to follow the next steps. However, if you wish to use your own environment, feel free to do so!
## Prerequisites
0. Install [docker desktop](https://www.docker.com/products/docker-desktop).
1. Clone castopod project by running:
```bash
git clone https://code.podlibre.org/podlibre/castopod.git
```
2. Create a `.env` file with the minimum required config to connect the app to the database:
```ini
CI_ENVIRONMENT = development
database.default.hostname = mariadb
database.default.database = castopod
database.default.username = podlibre
database.default.password = castopod
```
> _NB._ You can tweak your environment by setting more environment variables in your custom `.env` file. See the `env` for examples or the [CodeIgniter4 User Guide](https://codeigniter.com/user_guide/index.html) for more info.
3. Add the repository you've cloned to docker desktop's `Settings` > `Resources` > `File Sharing`.
4. Install castopod's php dependencies
> The project's php dependencies aren't included in the repository, you have to download them using the composer service defined in `docker-compose.yml`
```bash
docker-compose run --rm composer install --ignore-platform-reqs
```
5. Install castopod's js dependencies
> The project's js dependencies aren't included in the repository, you have to download them using the node service defined in `docker-compose.yml`
```bash
docker-compose run --rm node npm install
```
6. Build styles using postcss
> To generate the `public/index.css` file, you must run the following command.
```bash
docker-compose run --rm node npm run build:css
```
## Start docker containers
Go to project's root folder and run:
```bash
# starts all services declared in docker-compose.yml file
# -d option starts the containers in the background
docker-compose up -d
# See all running processes (you should see 3 processes running)
docker ps
# Alternatively, you can check all processes (you should see composer with an Exited status)
docker ps -a
```
> The `docker-compose up -d` command will boot 3 containers in the background:
>
> - `castopod_app`: a php based container with codeigniter requirements installed
> - `castopod_mariadb`: a [mariadb](https://mariadb.org/) server for persistent data
> - `castopod_phpmyadmin`: a phpmyadmin server to visualize the mariadb database
>
> _NB._ `./mariadb`, `./phpmyadmin` folders will be mounted in the project's root directory to persist data and logs.
## Initialize and populate database
Build the database with the migrate command:
```bash
# loads the database schema during first migration
docker-compose run --rm app php spark migrate
```
Populate the database with the required data:
```bash
# Populates all categories
docker-compose run --rm app php spark db:seed CategorySeeder
docker-compose run --rm app php spark db:seed LanguageSeeder
```
## Install/Update app dependencies
Castopod uses `composer` to manage php dependencies and `npm` to manage javascript dependencies.
You can install / update the project's dependencies using both `composer` and `node` services:
```bash
# install php dependencies
docker-compose run --rm composer update --ignore-platform-reqs
# update php dependencies
docker-compose run --rm composer update --ignore-platform-reqs
```
> _NB._ composer commands look for the `composer.json` file to find castopod's php dependencies, all of which live in the `vendor/` folder. For more info, check out [Composer documentation](https://getcomposer.org/doc/).
```bash
# install js dependencies
docker-compose run --rm node npm install
# update js dependencies
docker-compose run --rm node npm update
```
> _NB._ npm commands look for the `package.json` file to find castopod's js dependencies, all of which live in the `node_modules/` folder. For more info, check out [NPM documentation](https://docs.npmjs.com/).
## Start hacking
You're all set! Start working your magic by updating the project's files! Help yourself to the [CodeIgniter4 User Guide](https://codeigniter.com/user_guide/index.html) for more insights.
To see your changes, go to:
- [localhost:8080](http://localhost:8080/) for the castopod app
- [localhost:8888](http://localhost:8888/) for the phpmyadmin interface:
- **Username**: podlibre
- **Password**: castopod
---
## Going Further
### Useful docker / docker-compose commands
```bash
# monitor the app container
docker logs --tail 50 --follow --timestamps castopod_app
# monitor the mariadb container
docker logs --tail 50 --follow --timestamps castopod_mariadb
# monitor the phpmyadmin container
docker logs --tail 50 --follow --timestamps castopod_phpmyadmin
# restart docker containers
docker-compose restart
# Destroy all containers, opposite of `up` command
docker-compose down
```
Check [docker](https://docs.docker.com/engine/reference/commandline/docker/) and [docker-compose](https://docs.docker.com/compose/reference/) documentations for more insights.
## Developing inside a Container
If you're working in VSCode, you can take advantage of the `./.devcontainer/` folder. It defines a development container with preinstalled VSCode extensions so you don't have to worry about them. The container will be loaded with php, composer and git:
1. Install the VSCode extension [Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
2. `Ctrl/Cmd + Shift + P` > `Open in container`
The VSCode window will reload inside the dev container.
You can check that the required packages are running in the console (`Terminal` > `New Terminal`):
```bash
php -v
composer -V
git version
```
For more info, see [VSCode Remote Containers](https://code.visualstudio.com/docs/remote/containers)

View File

4625
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

35
package.json Normal file
View File

@ -0,0 +1,35 @@
{
"name": "castopod",
"version": "0.0.0-development",
"description": "Castopod is an open-source hosting platform made for podcasters who want engage and interact with their audience.",
"private": true,
"license": "AGPL-3.0-or-later",
"repository": {
"type": "git",
"url": "https://code.podlibre.org/podlibre/castopod.git"
},
"scripts": {
"build:css": "postcss app/Javascript/assets/styles/index.css -o app/Views/index.css",
"watch:css": "postcss app/Javascript/assets/styles/index.css -o app/Views/index.css -w",
"commit": "git-cz"
},
"devDependencies": {
"@commitlint/cli": "^8.3.5",
"@commitlint/config-conventional": "^8.3.4",
"@tailwindcss/custom-forms": "^0.2.1",
"cz-conventional-changelog": "^3.2.0",
"husky": "^4.2.5",
"postcss-cli": "^7.1.1",
"tailwindcss": "^1.4.6"
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
}
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
]
}

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

View File

@ -1,51 +0,0 @@
name: PHPUnit
on:
pull_request:
branches:
- develop
jobs:
main:
name: Build and test
strategy:
matrix:
php-versions: ['7.2', '7.3', '7.4']
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.message, '[ci skip]')"
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@master
with:
php-version: ${{ matrix.php-versions }}
tools: composer, pecl, phpunit
extensions: intl, json, mbstring, mysqlnd, xdebug, xml, sqlite3
coverage: xdebug
- name: Get composer cache directory
id: composer-cache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache composer dependencies
uses: actions/cache@v1
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --no-progress --no-suggest --no-interaction --prefer-dist --optimize-autoloader
# To prevent rate limiting you may need to supply an OAuth token in Settings > Secrets
# env:
# https://getcomposer.org/doc/articles/troubleshooting.md#api-rate-limit-and-oauth-tokens
# COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }}
- name: Test with phpunit
run: vendor/bin/phpunit --coverage-text

130
src/.gitignore vendored
View File

@ -1,130 +0,0 @@
#-------------------------
# Operating Specific Junk Files
#-------------------------
# OS X
.DS_Store
.AppleDouble
.LSOverride
# OS X Thumbnails
._*
# Windows image file caches
Thumbs.db
ehthumbs.db
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# Linux
*~
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
#-------------------------
# Environment Files
#-------------------------
# These should never be under version control,
# as it poses a security risk.
.env
.vagrant
Vagrantfile
#-------------------------
# Temporary Files
#-------------------------
writable/cache/*
!writable/cache/index.html
writable/logs/*
!writable/logs/index.html
writable/session/*
!writable/session/index.html
writable/uploads/*
!writable/uploads/index.html
writable/debugbar/*
php_errors.log
#-------------------------
# User Guide Temp Files
#-------------------------
user_guide_src/build/*
user_guide_src/cilexer/build/*
user_guide_src/cilexer/dist/*
user_guide_src/cilexer/pycilexer.egg-info/*
#-------------------------
# Test Files
#-------------------------
tests/coverage*
# Don't save phpunit under version control.
phpunit
#-------------------------
# Composer
#-------------------------
vendor/
composer.lock
#-------------------------
# IDE / Development Files
#-------------------------
# Modules Testing
_modules/*
# phpenv local config
.php-version
# Jetbrains editors (PHPStorm, etc)
.idea/
*.iml
# Netbeans
nbproject/
build/
nbbuild/
dist/
nbdist/
nbactions.xml
nb-configuration.xml
.nb-gradle/
# Sublime Text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
*.sublime-workspace
*.sublime-project
.phpintel
/api/
# Visual Studio Code
.vscode/
/results/
/phpunit*.xml
/.phpunit.*.cache
# Media files
public/media/*

Some files were not shown because too many files have changed in this diff Show More