19 Commits

Author SHA1 Message Date
b1ba54194f remove jquery dependency
Some checks failed
release-tag / release-image (push) Has been cancelled
2024-07-03 17:06:25 +02:00
9ff28d99de Clean up
All checks were successful
release-tag / release-image (push) Successful in 5m8s
2024-07-03 12:16:00 +02:00
6955739841 Fix database errors
All checks were successful
release-tag / release-image (push) Successful in 6m17s
2024-07-03 11:57:01 +02:00
7f72c94b9e updaet action
All checks were successful
release-tag / release-image (push) Successful in 28m56s
2024-07-03 10:09:48 +02:00
2b29c22ea4 update action
Some checks failed
release-tag / release-image (push) Failing after 47s
2024-07-03 10:08:38 +02:00
abd90ed378 add gitea action build push image
Some checks failed
release-tag / release-image (push) Failing after 55s
2024-07-03 09:38:31 +02:00
5808b53071 remove github workflow 2024-07-03 09:30:33 +02:00
Matthias Weber
02cfa4b218 Create Dockerfile 2024-07-02 15:41:52 +02:00
Matthias Weber
cbadbcc706 Update docker-compose.yml 2024-07-02 15:40:19 +02:00
Matthias Weber
de06c1371f Rename Dockerfile to Dockerfile-dev 2024-07-02 15:39:21 +02:00
Matthias Weber
075ad83f6a Update docker-image.yml 2024-07-02 15:30:52 +02:00
Matthias Weber
d79b31fd46 Create docker-image.yml 2024-07-02 15:23:30 +02:00
matthias@matsewe.de
6cd77cca50 fix first run 2024-07-02 14:20:09 +02:00
matthias@matsewe.de
9e28915419 fix first run 2024-07-02 14:19:01 +02:00
matthias@matsewe.de
dfeb6d93c9 fix dockerfile 2024-07-02 14:13:25 +02:00
matthias@matsewe.de
39281e7e52 change sample compose to auto load 2024-07-02 13:58:49 +02:00
matthias@matsewe.de
b48a27b2a3 sample Docker/-compose file 2024-07-02 13:51:19 +02:00
Matthias Weber
dbaf0c5e4c Merge pull request #1 from matsewe/choriosity-ci
Corporate Identity, Slack OAuth, etc.
2024-07-02 13:19:12 +02:00
matthias@matsewe.de
e227792f08 Incorporate Choriosity CI 2024-07-02 11:59:10 +02:00
13 changed files with 263 additions and 110 deletions

View File

@@ -0,0 +1,53 @@
name: release-tag
on:
push
jobs:
release-image:
runs-on: ubuntu-latest
container:
image: catthehacker/ubuntu:act-latest
env:
DOCKER_ORG: matthias
DOCKER_LATEST: latest
RUNNER_TOOL_CACHE: /toolcache
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v2
with: # replace it with your local IP
config-inline: |
[registry."git.matsewe.de"]
http = true
insecure = true
- name: Login to DockerHub
uses: docker/login-action@v2
with:
registry: git.matsewe.de # replace it with your local IP
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Get Meta
id: meta
run: |
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
platforms: |
linux/amd64
push: true
tags: | # replace it with your local IP and tags
git.matsewe.de/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}
git.matsewe.de/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
__pycache__ __pycache__
.venv .venv
.vscode .vscode
.env
data

15
Dockerfile Normal file
View File

@@ -0,0 +1,15 @@
FROM python:3.11
WORKDIR /code
COPY ./requirements.txt /tmp/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /tmp/requirements.txt
COPY ./app /code/app
COPY ./static /code/static
COPY ./templates /code/templates
RUN echo "first_run" > "/tmp/first_run"
CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80"]

11
Dockerfile-dev Normal file
View File

@@ -0,0 +1,11 @@
FROM python:3.11
WORKDIR /code
COPY ./requirements.txt /tmp/requirements.txt
RUN pip install --no-cache-dir --upgrade -r /tmp/requirements.txt
RUN echo "first_run" > "/tmp/first_run"
CMD ["fastapi", "run", "app/main.py", "--proxy-headers", "--port", "80", "--reload"]

View File

@@ -1,4 +1,4 @@
from fastapi import FastAPI, Request, Depends, Cookie, Security from fastapi import FastAPI, Request, Depends, Security
from app.routers import admin, songs, session from app.routers import admin, songs, session
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
@@ -6,26 +6,24 @@ from fastapi.templating import Jinja2Templates
from app.database import engine, Base, get_db, SessionLocal from app.database import engine, Base, get_db, SessionLocal
from app.crud import get_songs_and_vote_for_session, get_setting from app.crud import get_songs_and_vote_for_session, get_setting
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from typing import Annotated
from app.schemas import Song from app.schemas import Song
import json
import os import os
import asyncio import asyncio
from jose import JWTError, jwt
from app.security import get_current_user from app.security import get_current_user
from starlette.middleware import Middleware from starlette.middleware import Middleware
from starlette_context import context, plugins from starlette_context import plugins
from starlette_context.middleware import RawContextMiddleware from starlette_context.middleware import RawContextMiddleware
if os.path.isfile("first_run") and (os.environ.get("RELOAD_ON_FIRST_RUN").lower() == "true"): Base.metadata.create_all(engine)
if os.path.isfile("/tmp/first_run") and (os.environ.get("RELOAD_ON_FIRST_RUN", "").lower() == "true"):
print("First run ... load data") print("First run ... load data")
with SessionLocal() as db: with SessionLocal() as db:
asyncio.run(admin.create_upload_file(include_non_singable=True, db=db)) asyncio.run(admin.create_upload_file(include_non_singable=True, db=db))
os.remove("first_run") os.remove("/tmp/first_run")
# Base.metadata.create_all(engine)
middleware = [ middleware = [
Middleware( Middleware(

View File

@@ -1,8 +1,6 @@
from typing import Annotated from fastapi import HTTPException, status, Request
from fastapi import HTTPException, Cookie, status, Request
from fastapi.security import SecurityScopes from fastapi.security import SecurityScopes
from jose import JWTError, jwt from jose import JWTError
from pydantic import ValidationError from pydantic import ValidationError
import os import os
@@ -11,7 +9,7 @@ import os
# openssl rand -hex 32 # openssl rand -hex 32
scopes_db = { scopes_db = {
os.environ['ADMIN_EMAIL'] : ["admin"] os.environ.get('ADMIN_EMAIL', "") : ["admin"]
} }
credentials_exception = HTTPException( credentials_exception = HTTPException(
@@ -22,6 +20,10 @@ credentials_exception = HTTPException(
async def get_current_user( async def get_current_user(
security_scopes: SecurityScopes, request: Request security_scopes: SecurityScopes, request: Request
): ):
if os.environ.get("NO_LOGIN", "").lower() == "true":
return {"sub": "test"}
try: try:
username: str = request.headers.get("x-auth-request-user") # type: ignore username: str = request.headers.get("x-auth-request-user") # type: ignore
if username is None: if username is None:

21
docker-compose.yml Normal file
View File

@@ -0,0 +1,21 @@
version: "3.7"
services:
liederwahl-dev:
build:
context: .
dockerfile: Dockerfile-dev
container_name: liederwahl-dev
restart: unless-stopped
volumes:
- .:/code
- liederwahl-dev:/data
ports:
- 80:80
environment:
- LIST_URL=${LIST_URL}
- NO_LOGIN=true
- RELOAD_ON_FIRST_RUN=true
volumes:
liederwahl-dev:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,8 +1,55 @@
:root {
--color-white-100: white;
--color-white-90: #e6e6e6;
--color-white-80: #cccccc;
--color-white-70: #b3b3b3;
--color-white-10: #191919;
--color-choriosity-red: #be0519;
--color-choriosity-red--medium: #990514;
--color-choriosity-red--darker: #66030d;
--color-choriosity-red--rgba: 190, 5, 25;
--color-choriosity-red--light: #faeaea;
--color-background: #fffffa;
--color-tap: var(--color-choriosity-red--darker);
--color-callout-bg: var(--color-background);
}
@font-face {
font-family: 'Fira Sans';
src: url(FiraSans-Regular.woff2) format("woff2");
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Fira Sans';
src: url(FiraSans-Italic.woff2) format("woff2");
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: 'Fira Sans';
src: url(FiraSans-Medium.woff2) format("woff2");
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Fira Sans';
src: url(FiraSans-SemiBold.woff2) format("woff2");
font-weight: 600;
font-style: normal;
}
* { * {
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
padding: 0; padding: 0;
font-family: sans-serif; font-family: Fira Sans, Helvetica, Arial, sans-serif;
} }
.vote-buttons, .vote-buttons,
@@ -31,6 +78,7 @@
padding: 0.3em; padding: 0.3em;
margin-bottom: 0.7em; margin-bottom: 0.7em;
display: inline-block; display: inline-block;
font-size: 1.25rem;
} }
.clear { .clear {
@@ -38,13 +86,13 @@
} }
.song { .song {
background-color: #f0f0f0; background-color: var(--color-choriosity-red--light);
padding: 0.4em; padding: 0.4em;
border-radius: 0.5em; border-radius: 0.5em;
width: 30em; width: 30em;
font-family: sans-serif;
margin-bottom: 1rem; margin-bottom: 1rem;
margin-left: 0.5em; margin-left: 0.5em;
float: left;
} }
@@ -225,21 +273,28 @@
} }
h1 { h1 {
font-family: sans-serif;
padding: 0.1em; padding: 0.1em;
padding-left: 0.2em; padding-left: 0.2em;
margin-bottom: 1rem; margin-bottom: 1rem;
font-size: 1.5em; font-size: 1.25rem;
background-color: var(--color-choriosity-red);
color: white;
/*border-bottom: 0.3rem solid var(--color-choriosity-red--light);*/
clear: both;
}
#songs h1 .color {
border: 0.2rem solid color-mix(in srgb, hsl(calc(var(--hue) * 360), 100%, 40%) 50%, white);
background-color: hsl(calc(var(--hue) * 360), 100%, 40%);
border-radius: 0.5em;
margin-right: 1em;
} }
#songs h1 { #songs h1 {
background-color: color-mix(in srgb, hsl(calc(var(--hue) * 360), 100%, 40%) 50%, transparent); border-left: 0.3em solid white;
border-bottom: 0.3rem solid hsl(calc(var(--hue) * 360), 100%, 40%); box-shadow: -1em 0px 0px 0px hsl(calc(var(--hue) * 360), 100%, 40%);
} margin-left: 1em;
padding-left: 0.3em;
body>h1 {
background-color: color-mix(in srgb, hsl(0, 0%, 40%) 50%, transparent);
border-bottom: 0.3rem solid hsl(0, 0%, 40%);
} }
#yt-player, #yt-player,
@@ -279,3 +334,18 @@ body>h1 {
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
width: 1.5em; width: 1.5em;
} }
#title {
margin-top: 1em;
font-size: 2rem;
/*line-height: 2rem;*/
}
#title-logo {
margin-top: -0.7em;
margin-bottom: -0.8em;
vertical-align: middle;
height: 3em;
filter: drop-shadow(0 0 1px var(--color-choriosity-red--medium));
margin-right: 1em;
}

View File

@@ -27,37 +27,9 @@
{% endif %} {% endif %}
<script src="https://open.spotify.com/embed/iframe-api/v1" async></script> <script src="https://open.spotify.com/embed/iframe-api/v1" async></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
const session_id = "{{ session_id }}"; const session_id = "{{ session_id }}";
function activate_session() {
$.ajax({
url: "/session/" + session_id,
method: "PUT"
})
}
function deactivate_session() {
$.ajax({
url: "/session/" + session_id,
method: "DELETE"
})
}
$(window).on("load", activate_session);
$(window).on("beforeunload", deactivate_session);
$(window).on("unload", deactivate_session);
$(window).on("pagehide", deactivate_session);
$(document).on('visibilitychange', function () {
if (document.visibilityState == 'hidden') {
deactivate_session();
} else {
activate_session()
}
});
var spotify_embed_controller; var spotify_embed_controller;
window.onSpotifyIframeApiReady = (IFrameAPI) => { window.onSpotifyIframeApiReady = (IFrameAPI) => {
@@ -75,12 +47,15 @@
var is_playing = -1; var is_playing = -1;
function stop() { function stop() {
$("#song-" + is_playing + " .cover-container .overlay img").attr("src", "/static/play.svg"); if (is_playing !== -1) {
document.querySelector("#song-" + is_playing + " .cover-container .overlay img").setAttribute("src", "/static/play.svg");
}
document.querySelector("#yt-player").style.display = "none";
document.querySelector("#spotify-player").style.display = "none";
document.querySelector("#close-player").style.display = "none";
document.querySelector("#yt-player").innerHTML = "";
$("#yt-player").css("display", "none");
$("#spotify-player").css("display", "none");
$("#close-player").css("display", "none");
$("#yt-player").html("");
spotify_embed_controller.pause(); spotify_embed_controller.pause();
is_playing = -1; is_playing = -1;
} }
@@ -93,12 +68,13 @@
is_playing = song_id; is_playing = song_id;
$("#song-" + song_id + " .cover-container .overlay img").attr("src", "/static/stop.svg"); document.querySelector("#song-" + song_id + " .cover-container .overlay img").setAttribute("src", "/static/stop.svg");
document.querySelector("#yt-player").style.display = "flex";
document.querySelector("#close-player").style.display = "block";
$("#yt-player").css("display", "flex");
$("#close-player").css("display", "block");
iframe_code = '<iframe src="https://www.youtube.com/embed/' + yt_id + '?autoplay=1" title="" width="640" height="360" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowFullScreen></iframe>'; iframe_code = '<iframe src="https://www.youtube.com/embed/' + yt_id + '?autoplay=1" title="" width="640" height="360" frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowFullScreen></iframe>';
$("#yt-player").html(iframe_code); document.querySelector("#yt-player").innerHTML = iframe_code;
} }
} }
@@ -110,10 +86,10 @@
is_playing = song_id; is_playing = song_id;
$("#song-" + song_id + " .cover-container .overlay img").attr("src", "/static/stop.svg"); document.querySelector("#song-" + song_id + " .cover-container .overlay img").setAttribute("src", "/static/stop.svg");
$("#spotify-player").css("display", "flex"); document.querySelector("#spotify-player").style.display = "flex";
$("#close-player").css("display", "block"); document.querySelector("#close-player").style.display = "block";
spotify_embed_controller.loadUri("spotify:track:" + spfy_id); spotify_embed_controller.loadUri("spotify:track:" + spfy_id);
spotify_embed_controller.play(); spotify_embed_controller.play();
} }
@@ -124,38 +100,42 @@
window.open(url, '_blank').focus(); window.open(url, '_blank').focus();
} }
function vote(song_id, vote) { async function vote(song_id, vote) {
$.ajax({ response = await fetch(
url: "/songs/" + song_id + "/vote?" + $.param({ session_id: session_id, vote: vote }), "/songs/" + song_id + "/vote?session_id=" + encodeURIComponent(session_id) + "&vote=" + vote, {
method: "POST", method: 'POST',
success: function (data, textStatus) { headers: {
'Content-Type': 'application/json'
}
});
no_button = $("#song-" + song_id).find(".button-no") if (response.ok) {
yes_button = $("#song-" + song_id).find(".button-yes") no_button = document.querySelector("#song-" + song_id + " .button-no");
neutral_button = $("#song-" + song_id).find(".button-neutral") yes_button = document.querySelector("#song-" + song_id + " .button-yes");
neutral_button = document.querySelector("#song-" + song_id + " .button-neutral");
no_button.removeClass("selected") no_button.classList.remove("selected")
yes_button.removeClass("selected") yes_button.classList.remove("selected")
neutral_button.removeClass("selected") neutral_button.classList.remove("selected")
switch (vote) { switch (vote) {
case 0: case 0:
neutral_button.addClass("selected") neutral_button.classList.add("selected")
{% if veto_mode %} {% if veto_mode %}
$("#song-" + song_id).removeClass("not_singable") document.querySelector("#song-" + song_id).classList.remove("not_singable")
{% endif %} {% endif %}
break; break;
case 1: case 1:
yes_button.addClass("selected") yes_button.classList.add("selected")
{% if veto_mode %} {% if veto_mode %}
$("#song-" + song_id).removeClass("not_singable") document.querySelector("#song-" + song_id).classList.remove("not_singable")
{% endif %} {% endif %}
break; break;
case -1: case -1:
no_button.addClass("selected") no_button.classList.add("selected")
{% if veto_mode %} {% if veto_mode %}
$("#song-" + song_id).addClass("not_singable") document.querySelector("#song-" + song_id).classList.add("not_singable")
{% endif %} {% endif %}
break; break;
default: default:
@@ -163,20 +143,19 @@
} }
} }
});
} }
{% if veto_mode %} {% if veto_mode %}
function updateComment(song_id, el) { async function updateComment(song_id, el) {
comment = el.value comment = el.value
$.ajax({
url: "/songs/" + song_id + "/comment?" + $.param({ session_id: session_id, comment: comment }), response = await fetch(
method: "POST" "/songs/" + song_id + "/comment?session_id=" + encodeURIComponent(session_id) + "&comment=" + encodeURIComponent(comment), {
}) method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
} }
{% endif %} {% endif %}
</script> </script>
@@ -189,8 +168,10 @@
abgeben. abgeben.
</div> </div>
{% else %} {% else %}
<h1>Hallo :)</h1> <h1 id="title"><img id="title-logo" src="https://choriosity.de/assets/images/logo-choriosity_weiss.svg"> Liederwahl
<div class="text">Du kannst die Liederwahl jederzeit unterbrechen und zu einem späteren Zeitpunkt weitermachen. </h1>
<div class="text">Du kannst die Liederwahl jederzeit unterbrechen und zu einem späteren Zeitpunkt weitermachen. Mit
einem Klick auf das Thumbnail kannst du das Lied abspielen.
</div> </div>
{% endif %} {% endif %}
<div id="songs"> <div id="songs">