Merge branch 'basecamp:main' into main

This commit is contained in:
Dave Kimura
2025-12-02 16:08:26 -05:00
committed by GitHub
12 changed files with 285 additions and 15 deletions
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Features, Bug Reports, Questions
url: https://github.com/basecamp/fizzy/discussions/new/choose
about: Please use the discussions area to report issues or ask quest
+8
View File
@@ -0,0 +1,8 @@
---
name: Pre-Discussed and Approved Topics
about: |-
For topics already discussed and approved in the GitHub Discussions section.
---
** PLEASE START A DISCUSSION INSTEAD OF OPENING AN ISSUE **
** For more details see CONTRIBUTING.md **
+196
View File
@@ -0,0 +1,196 @@
name: Build and publish container image to GHCR
on:
push:
branches:
- main
tags:
- 'v*'
workflow_dispatch:
concurrency:
group: publish-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
packages: write
id-token: write
attestations: write
env:
IMAGE_DESCRIPTION: Fizzy is Kanban as it should be. Not as it has been.
SOURCE_URL: https://github.com/${{ github.repository }}
jobs:
build:
name: Build and push image (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-latest
platform: linux/amd64
arch: amd64
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
steps:
- name: Checkout
uses: actions/checkout@v5.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.11.1
- name: Log in to GHCR
uses: docker/login-action@v3.5.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Compute canonical image name (lowercase)
id: vars
shell: bash
run: |
set -eu
IMAGE_REF="${IMAGE_NAME:-$GITHUB_REPOSITORY}"
CANONICAL_IMAGE="${REGISTRY}/${IMAGE_REF,,}"
echo "canonical=${CANONICAL_IMAGE}" >> "$GITHUB_OUTPUT"
- name: Extract Docker metadata (tags, labels) with arch suffix
id: meta
uses: docker/metadata-action@v5.8.0
with:
images: ${{ steps.vars.outputs.canonical }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha,format=short,prefix=sha-
type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=semver,pattern={{major}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
flavor: |
latest=false
suffix=-${{ matrix.arch }}
labels: |
org.opencontainers.image.source=${{ env.SOURCE_URL }}
- name: Build and push (${{ matrix.platform }})
id: build
uses: docker/build-push-action@v6.18.0
with:
context: .
file: Dockerfile
build-args: |
OCI_SOURCE=${{ env.SOURCE_URL }}
OCI_DESCRIPTION=${{ env.IMAGE_DESCRIPTION }}
platforms: ${{ matrix.platform }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=${{ matrix.platform }}
cache-to: type=gha,scope=${{ matrix.platform }},mode=max
sbom: false
provenance: false
- name: Attest image provenance (per-arch)
if: github.event_name != 'pull_request'
uses: actions/attest-build-provenance@v3.0.0
with:
subject-name: ${{ steps.vars.outputs.canonical }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: false
manifest:
name: Create multi-arch manifest and sign
needs: build
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 20
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
steps:
- name: Set up Docker Buildx (for imagetools)
uses: docker/setup-buildx-action@v3.11.1
- name: Log in to GHCR
uses: docker/login-action@v3.5.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Compute canonical image name (lowercase)
id: vars
shell: bash
run: |
set -eu
IMAGE_REF="${IMAGE_NAME:-$GITHUB_REPOSITORY}"
CANONICAL_IMAGE="${REGISTRY}/${IMAGE_REF,,}"
echo "canonical=${CANONICAL_IMAGE}" >> "$GITHUB_OUTPUT"
- name: Compute base tags (no suffix)
id: meta
uses: docker/metadata-action@v5.8.0
with:
images: ${{ steps.vars.outputs.canonical }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha,format=short,prefix=sha-
type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=semver,pattern={{major}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
flavor: |
latest=false
labels: |
org.opencontainers.image.source=${{ env.SOURCE_URL }}
- name: Create multi-arch manifests
shell: bash
run: |
set -eu
tags="${{ steps.meta.outputs.tags }}"
echo "Creating manifests for tags:"
printf '%s\n' "$tags"
while IFS= read -r tag; do
[ -z "$tag" ] && continue
echo "Creating manifest for $tag"
src_tag="$tag"
if [[ "$tag" == *:latest && "${GITHUB_REF}" == refs/tags/* ]]; then
ref="${GITHUB_REF#refs/tags/}"
src_tag="${tag%:latest}:$ref"
fi
if [ -n "${IMAGE_DESCRIPTION:-}" ]; then
docker buildx imagetools create \
--tag "$tag" \
--annotation "index:org.opencontainers.image.description=${IMAGE_DESCRIPTION}" \
"${src_tag}-amd64"
else
docker buildx imagetools create \
--tag "$tag" \
"${src_tag}-amd64"
fi
done <<< "$tags"
- name: Install Cosign
uses: sigstore/cosign-installer@v3.9.2
- name: Cosign sign all tags (keyless OIDC)
shell: bash
run: |
set -eu
tags="${{ steps.meta.outputs.tags }}"
printf '%s\n' "$tags"
while IFS= read -r tag; do
[ -z "$tag" ] && continue
echo "Signing $tag"
cosign sign --yes "$tag"
done <<< "$tags"
+49
View File
@@ -0,0 +1,49 @@
# How to contribute to Fizzy
Fizzy uses GitHub
[discussions](https://github.com/basecamp/fizzy/discussions) to track
feature requests and questions, rather than [the issue
tracker](https://github.com/basecamp/fizzy/issues). If you're considering
opening an issue or pull request, please open a discussion instead.
Whenever a discussion leads to an actionable and well-understood task, we'll
move it to the issue tracker where it can be worked on.
This is a little different than how some other projects work, but it makes it
easier for us to triage and prioritise the work. It also means that the open
issues all represent agreed-upon tasks that are either being worked on, or are
ready to be worked on.
This should also make it easier to see what's in progress, and to find
something to work on if you'd like to do so.
## What this means in practice
### If you'd like to contribute to the code...
1. If you're interested in working on one of the open issues, please do! We are
grateful for the help!
2. You'll want to make sure someone else isn't already working on the same
issue. If they are, it will be tagged "in progress" and/or it should be clear
from the comments. When in doubt, you can always comment on the issue to ask.
3. Similarly, if you need any help or guidance on the issue, please comment on
the issue as you go, and we'll do our best to help.
4. When you have something ready for review or collaboration, open a PR.
### If you've found a bug...
1. If you don't have steps to reproduce the problem, or you're not certain it's a
bug, open a discussion.
2. If you have steps to reproduce, open an issue.
### If you have an idea for a feature...
1. Open a discussion.
### If you have a question, or are having trouble with configuration...
1. Open a discussion.
Hopefully this process makes it easier for everyone to be involved. Thanks for
helping! ❤️
+8 -1
View File
@@ -16,7 +16,7 @@ WORKDIR /rails
# Install base packages
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 && \
apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 libssl-dev && \
ln -s /usr/lib/$(uname -m)-linux-gnu/libjemalloc.so.2 /usr/local/lib/libjemalloc.so && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
@@ -59,6 +59,13 @@ RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
# Final stage for app image
FROM base
# Image metadata
ARG OCI_DESCRIPTION
LABEL org.opencontainers.image.description="${OCI_DESCRIPTION}"
ARG OCI_SOURCE
LABEL org.opencontainers.image.source="${OCI_SOURCE}"
LABEL org.opencontainers.image.licenses="O'Saasy"
# Run and own only the runtime files as a non-root user for security
RUN groupadd --system --gid 1000 rails && \
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash
+1 -2
View File
@@ -189,9 +189,8 @@
text-align: center;
kbd {
border: none;
box-shadow: none;
inset: 0.66em 0.33em auto auto;
line-height: 1.4;
opacity: 0.5;
position: absolute;
@@ -4,16 +4,12 @@ import { onNextEventLoopTick } from "helpers/timing_helpers"
export default class extends Controller {
static targets = [ "input" ]
submit(event) {
submit() {
onNextEventLoopTick(() => {
this.inputTarget.disabled = true
})
}
paste(event) {
onNextEventLoopTick(() => {
this.element.submit()
this.inputTarget.disabled = true
if (!this.inputTarget.disabled) {
this.element.submit()
this.inputTarget.disabled = true
}
})
}
}
+1
View File
@@ -3,5 +3,6 @@ module Board::Broadcastable
included do
broadcasts_refreshes
broadcasts_refreshes_to ->(board) { [ board.account, :all_boards ] }
end
end
+6
View File
@@ -24,10 +24,16 @@ class User < ApplicationRecord
transaction do
accesses.destroy_all
update! active: false, identity: nil
close_remote_connections
end
end
def setup?
name != identity.email_address
end
private
def close_remote_connections
ActionCable.server.remote_connections.where(current_user: self).disconnect(reconnect: false)
end
end
+1 -1
View File
@@ -3,5 +3,5 @@
<%= turbo_stream_from board %>
<% end %>
<% else %>
<%= turbo_stream_from :all_boards %>
<%= turbo_stream_from [ Current.account, :all_boards ] %>
<% end %>
+1 -1
View File
@@ -10,7 +10,7 @@
<%= form.text_field :code, required: true, class: "input center txt-align-enter txt-large",
autofocus: true, autocorrect: "off", autocapitalize: "off", spellcheck: "false", "data-1p-ignore": true,
autocomplete: "one-time-code", maxlength: "6", placeholder: "••••••", value: params[:code],
data: { magic_link_target: "input", action: "keydown.enter->magic-link#submit paste->magic-link#paste" } %>
data: { magic_link_target: "input", action: "keydown.enter->magic-link#submit paste->magic-link#submit" } %>
<% end %>
<p class="txt-small">The code you receive will work for <%= distance_of_time_in_words(MagicLink::EXPIRATION_TIME) %>.</p>
+4 -1
View File
@@ -25,7 +25,10 @@ class UserTest < ActiveSupport::TestCase
test "deactivate" do
assert_changes -> { users(:jz).active? }, from: true, to: false do
assert_changes -> { users(:jz).accesses.count }, from: 1, to: 0 do
users(:jz).deactivate
users(:jz).tap do |user|
user.stubs(:close_remote_connections).once
user.deactivate
end
end
end
end