Skip to content

Commit

Permalink
Merge branch 'beta'
Browse files Browse the repository at this point in the history
  • Loading branch information
eteubert committed Dec 15, 2024
2 parents 5fbcc70 + f9c4355 commit 36bb1fe
Show file tree
Hide file tree
Showing 50 changed files with 1,529 additions and 3,029 deletions.
3 changes: 3 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
!/dist
vendor
vendor-prefixed
59 changes: 59 additions & 0 deletions .github/workflows/docker-image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# https://docs.github.com/en/actions/publishing-packages/publishing-docker-images#publishing-images-to-github-packages
name: Create and publish a Docker image

# Configures this workflow to run every time a change is pushed to the branch called `release`.
on:
push:
branches: ['beta', 'master']

# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

# There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu.
jobs:
build-and-push-image:
runs-on: ubuntu-latest
# Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job.
permissions:
contents: read
packages: write
attestations: write
id-token: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install devbox
uses: jetify-com/devbox-install-action@v0.11.0

- name: Bootstrap Podlove Publisher
run: devbox run bootstrap

- name: Build Podlove Publisher
run: devbox run build

- name: Log in to the Container registry
uses: docker/login-action@v3.2.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5.5.1
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

- name: Build and push Docker image
id: push
uses: docker/build-push-action@v6.1.0
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM wordpress:6-php8.1-apache

RUN apt-get update
RUN apt-get install zip default-mysql-client -y
RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && chmod +x wp-cli.phar && mv wp-cli.phar /usr/local/bin/wp

WORKDIR /var/www/html

COPY ./bin/docker-entry.sh /usr/local/bin/entry.sh
COPY ./bin/docker-setup.sh /usr/local/bin/setup.sh
COPY ./dist wp-content/plugins/podlove-podcasting-plugin-for-wordpress

ENTRYPOINT ["entry.sh"]
97 changes: 97 additions & 0 deletions bin/docker-entry.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
set -Eeuo pipefail

# Waiting for the MySQL server to start
HOST=$(echo $WORDPRESS_DB_HOST | cut -d: -f1)
PORT=$(echo $WORDPRESS_DB_HOST | cut -d: -f2)

until mysql -h $HOST -P $PORT -D $WORDPRESS_DB_NAME -u $WORDPRESS_DB_USER -p$WORDPRESS_DB_PASSWORD -e '\q'; do
>&2 echo "Mysql is unavailable - sleeping..."
sleep 2
done

uid="$(id -u)"
gid="$(id -g)"

if [ "$uid" = '0' ]; then
user="${APACHE_RUN_USER:-www-data}"
group="${APACHE_RUN_GROUP:-www-data}"

# strip off any '#' symbol ('#1000' is valid syntax for Apache)
pound='#'
user="${user#$pound}"
group="${group#$pound}"

if [ ! -e index.php ] && [ ! -e wp-includes/version.php ]; then
# if the directory exists and WordPress doesn't appear to be installed AND the permissions of it are root:root, let's chown it (likely a Docker-created directory)
if [ "$uid" = '0' ] && [ "$(stat -c '%u:%g' .)" = '0:0' ]; then
chown "$user:$group" .
fi

echo >&2 "WordPress not found in $PWD - copying now..."
if [ -n "$(find -mindepth 1 -maxdepth 1 -not -name wp-content)" ]; then
echo >&2 "WARNING: $PWD is not empty! (copying anyhow)"
fi
sourceTarArgs=(
--create
--file -
--directory /usr/src/wordpress
--owner "$user" --group "$group"
)
targetTarArgs=(
--extract
--file -
)
if [ "$uid" != '0' ]; then
# avoid "tar: .: Cannot utime: Operation not permitted" and "tar: .: Cannot change mode to rwxr-xr-x: Operation not permitted"
targetTarArgs+=( --no-overwrite-dir )
fi
# loop over "pluggable" content in the source, and if it already exists in the destination, skip it
# https://github.com/docker-library/wordpress/issues/506 ("wp-content" persisted, "akismet" updated, WordPress container restarted/recreated, "akismet" downgraded)
for contentPath in \
/usr/src/wordpress/.htaccess \
/usr/src/wordpress/wp-content/*/*/ \
; do
contentPath="${contentPath%/}"
[ -e "$contentPath" ] || continue
contentPath="${contentPath#/usr/src/wordpress/}" # "wp-content/plugins/akismet", etc.
if [ -e "$PWD/$contentPath" ]; then
echo >&2 "WARNING: '$PWD/$contentPath' exists! (not copying the WordPress version)"
sourceTarArgs+=( --exclude "./$contentPath" )
fi
done
tar "${sourceTarArgs[@]}" . | tar "${targetTarArgs[@]}"
echo >&2 "Complete! WordPress has been successfully copied to $PWD"
fi

wpEnvs=( "${!WORDPRESS_@}" )
if [ ! -s wp-config.php ] && [ "${#wpEnvs[@]}" -gt 0 ]; then
for wpConfigDocker in \
wp-config-docker.php \
/usr/src/wordpress/wp-config-docker.php \
; do
if [ -s "$wpConfigDocker" ]; then
echo >&2 "No 'wp-config.php' found in $PWD, but 'WORDPRESS_...' variables supplied; copying '$wpConfigDocker' (${wpEnvs[*]})"
# using "awk" to replace all instances of "put your unique phrase here" with a properly unique string (for AUTH_KEY and friends to have safe defaults if they aren't specified with environment variables)
awk '
/put your unique phrase here/ {
cmd = "head -c1m /dev/urandom | sha1sum | cut -d\\ -f1"
cmd | getline str
close(cmd)
gsub("put your unique phrase here", str)
}
{ print }
' "$wpConfigDocker" > wp-config.php
if [ "$uid" = '0' ]; then
# attempt to ensure that wp-config.php is owned by the run user
# could be on a filesystem that doesn't allow chown (like some NFS setups)
chown "$user:$group" wp-config.php || true
fi
break
fi
done
fi
fi

eval setup.sh
apache2-foreground
1 change: 1 addition & 0 deletions bin/docker-setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#!/usr/bin/env bash
68 changes: 68 additions & 0 deletions client/src/components/steps/Steps.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<template>
<ol role="list" class="divide-y divide-gray-300 rounded-md border border-gray-300 md:flex md:divide-y-0">
<li v-for="(step, stepIdx) in steps" :key="step.name" class="relative md:flex md:flex-1">
<p v-if="step.status === 'complete'" class="group flex w-full items-center">
<span class="flex items-center px-6 py-4 text-sm font-medium">
<span
class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-indigo-600 group-hover:bg-indigo-800 text-white">
<CheckIcon class="h-6 w-6 text-white" aria-hidden="true" />
</span>
<span class="ml-4 text-sm font-medium text-gray-900">{{ step.name }}</span>
</span>
</p>
<p v-else-if="step.status === 'current'" class="flex items-center px-6 py-4 text-sm font-medium"
aria-current="step">
<span
class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full border-2 border-indigo-600">
<span class="text-indigo-600">{{ step.id }}</span>
</span>
<span class="ml-4 text-sm font-medium text-indigo-600">{{ step.name }}</span>
</p>
<p v-else class="group flex items-center">
<span class="flex items-center px-6 py-4 text-sm font-medium">
<span
class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full border-2 border-gray-300 group-hover:border-gray-400">
<span class="text-gray-500 group-hover:text-gray-900">{{ step.id }}</span>
</span>
<span class="ml-4 text-sm font-medium text-gray-500 group-hover:text-gray-900">{{ step.name
}}</span>
</span>
</p>
<template v-if="stepIdx !== steps.length - 1">
<!-- Arrow separator for lg screens and up -->
<div class="absolute right-0 top-0 hidden h-full w-5 md:block" aria-hidden="true">
<svg class="h-full w-full text-gray-300" viewBox="0 0 22 80" fill="none" preserveAspectRatio="none">
<path d="M0 -2L20 40L0 82" vector-effect="non-scaling-stroke" stroke="currentcolor"
stroke-linejoin="round" />
</svg>
</div>
</template>
</li>
</ol>
</template>

<script lang="ts">
import { CheckIcon } from '@heroicons/vue/24/solid'
import { defineComponent, PropType } from 'vue'
export type StepStatus = 'complete' | 'current' | 'upcoming'
export interface Step {
id: Number,
name: string,
status: StepStatus
}
export default defineComponent({
components: {
CheckIcon,
},
props: {
steps: {
type: Array as PropType<Step[]>,
default: [],
}
}
})
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<div class="min-w-0 flex-1 px-2 md:grid md:gap-4">
<div class="flex-shrink-0">
<p class="truncate text-sm font-medium text-gray-900">
{{ data.realname }}
{{ data.realname || data.publicname }}
</p>
<p class="flex items-center text-sm text-gray-500">
<span class="truncate">{{ data.nickname }}</span>
Expand Down
38 changes: 38 additions & 0 deletions client/src/sagas/admin.sagas.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { fork, select, put, takeEvery } from 'redux-saga/effects'
import { PodloveApiClient } from '@lib/api'
import { createApi } from './api'
import * as adminStore from '@store/admin.store'
import { takeFirst } from './helper'
import { selectors } from '@store'

interface AdminData {
bannerHide: boolean | null
type: string | null
feedUrl: string | null
}

function* adminSaga() {
const apiClient: PodloveApiClient = yield createApi()
yield fork(initialize, apiClient)
yield takeEvery(adminStore.UPDATE_TYPE, save, apiClient)
}

function* initialize(api: PodloveApiClient) {
const { result }: { result: AdminData } = yield api.get('admin/onboarding')

if (result) {
yield put(adminStore.set(result))
}
}

function* save(api: PodloveApiClient, action: {type: string}) {
const typeValue: string = yield select(selectors.admin.type)

yield api.put('admin/onboarding', {type: typeValue})
}

export default function() {
return function* () {
yield takeFirst(adminStore.INIT, adminSaga)
}
}
43 changes: 43 additions & 0 deletions client/src/store/admin.store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { get } from 'lodash'
import { handleActions, createAction } from 'redux-actions'

export const INIT = 'podlove/publisher/admin/INIT'
export const SET = 'podlove/publisher/admin/SET'
export const UPDATE_TYPE = 'podlove/publisher/admin/UPDATE_TYPE'

export type State = {
bannerHide: boolean | null,
type: string | null,
feedUrl: string | null
}

export const initialState: State = {
bannerHide: null,
type: null,
feedUrl: null
}

export const init = createAction<void>(INIT)
export const set = createAction<Partial<State>>(SET)
export const update_type = createAction<string>(UPDATE_TYPE)

export const reducer = handleActions(
{
[SET]: (state: State, action: { payload: Partial<State> }): State => ({
bannerHide: get(action, ['payload', 'banner_hide'], state.bannerHide),
type: get(action, ['payload', 'type'], state.type),
feedUrl: get(action, ['payload', 'feedUrl'], state.feedUrl),
}),
[UPDATE_TYPE]: (state: State, action: { payload: string }): State => ({
...state,
type: action.payload
}),
},
initialState
)

export const selectors = {
bannerHide: (state: State) => state.bannerHide,
type: (state: State) => state.type,
feedUrl: (state: State) => state.feedUrl,
}
4 changes: 4 additions & 0 deletions client/src/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { State as progressState } from './progress.store'
import { State as mediafilesState } from './mediafiles.store'
import { State as relatedEpisodesState } from './relatedEpisodes.store'
import { State as showsState } from './shows.store'
import { State as adminState } from './admin.store'

import lifecycleSaga from '../sagas/lifecycle.sagas'
import podcastSaga from '../sagas/podcast.sagas'
Expand All @@ -37,6 +38,7 @@ import auphonicSaga from '../sagas/auphonic.sagas'
import mediafilesSaga from '../sagas/mediafiles.sagas'
import relatedEpisodesSaga from '../sagas/relatedEpisodes.sagas'
import showsSaga from '../sagas/shows.sagas'
import adminSaga from '../sagas/admin.sagas'

export interface State {
lifecycle: LifecycleState
Expand All @@ -53,6 +55,7 @@ export interface State {
mediafiles: mediafilesState
relatedEpisodes: relatedEpisodesState
shows: showsState
admin: adminState
}

const sagas = createSagaMiddleware()
Expand All @@ -72,5 +75,6 @@ sagas.run(auphonicSaga())
sagas.run(mediafilesSaga())
sagas.run(relatedEpisodesSaga())
sagas.run(showsSaga())
sagas.run(adminSaga())

export { selectors, sagas }
2 changes: 2 additions & 0 deletions client/src/store/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import * as progressStore from './progress.store'
import * as mediafilesStore from './mediafiles.store'
import * as relatedEpisodesStore from './relatedEpisodes.store'
import * as showsStore from './shows.store'
import * as adminStore from './admin.store'

export default combineReducers({
lifecycle: lifecycleStore.reducer,
Expand All @@ -29,4 +30,5 @@ export default combineReducers({
mediafiles: mediafilesStore.reducer,
relatedEpisodes: relatedEpisodesStore.reducer,
shows: showsStore.reducer,
admin: adminStore.reducer,
})
Loading

0 comments on commit 36bb1fe

Please sign in to comment.