feat: ♻️ Remake url #3

Merged
lgallet merged 11 commits from feat/refactoring into main 2024-07-01 15:55:10 +00:00
23 changed files with 242 additions and 164 deletions

View File

@ -1 +1,2 @@
.env
n8n_workflow.json

View File

@ -1,4 +1,5 @@
client_id=
client_secret=
n8n_webhook=
enviroment=dev
n8n_webhook_add_tracks=
n8n_webhook_create_playlist=
environment=production

2
.gitignore vendored
View File

@ -28,6 +28,8 @@
.idea/**/gradle.xml
.idea/**/libraries
.idea/
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using

8
.idea/.gitignore generated vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

12
.idea/dataSources.xml generated
View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="database.db" uuid="bc21304a-7893-4f0b-bc20-7e9bb19b7991">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:database.db</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

View File

@ -1,10 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="63" name="Python" />
</Languages>
</inspection_tool>
</profile>
</component>

View File

@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="file://$PROJECT_DIR$" libraries="{sweetalert2}" />
</component>
</project>

7
.idea/misc.xml generated
View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.12 (playlistCreator)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (playlistCreator)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated
View File

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/playlistCreator.iml" filepath="$PROJECT_DIR$/.idea/playlistCreator.iml" />
</modules>
</component>
</project>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="sweetalert2" level="application" />
</component>
</module>

7
.idea/sqldialects.xml generated
View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/main.py" dialect="GenericSQL" />
<file url="PROJECT" dialect="SQLite" />
</component>
</project>

6
.idea/vcs.xml generated
View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -1,5 +1,7 @@
FROM python:3.11-alpine
LABEL authors="louisgallet"
LABEL authors="Louis Gallet"
LABEL description="Make This Playlist is a web application that allows anyone to add music to a Spotify playlist without needing a Spotify account. The application is accessible at the following address: [Make This Playlist](https://makethisplaylist.louisgallet.fr)."
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

View File

@ -7,16 +7,24 @@ Make This Playlist is a web application that allows anyone to add music to a Spo
- Search for music on Spotify
- Add music to a playlist
## Requirements
- n8n account or instance
- n8n workflow (import [n8n workflow file](https://gitea.louisgallet.fr/lgallet/makethisplaylist/src/branch/main/n8n_workflow.json) into your account and set your Spotify credentials)
- Spotify Developer Account
## Installation
### Docker
You can install the application via Docker. To do this, execute the following command:
For the webhook URLs, make sure to remove the parameters, as they will be added directly by the application.
```bash
docker run -d \
-p 80:3000 \
-e client_id=SPOTIFY_CLIENT_ID \
-e client_secret=SPOTIFY_CLIENT_SECRET \
-e n8n_webhook=N8N_WEBHOOK_URL \
-e n8n_webhook_create_playlist=N8N_WEBHOOK_URL \
-e n8n_webhook_add_tracks=N8N_WEBHOOK_URL \
-e environment=production \
gitea.louisgallet.fr/lgallet/makethisplaylist:latest
```
@ -26,5 +34,6 @@ To install the application manually, clone the repository and install the depend
git clone https://gitea.louisgallet.fr/lgallet/makethisplaylist.git
cd makethisplaylist
pip install -r requirements.txt
cp .env.example .env
python main.py
```

View File

@ -41,7 +41,6 @@ def create_playlist():
data = requests.get((os.getenv("n8n_webhook_create_playlist") + "/" + str(roomid) + "/" + playlistName))
playlistID = data.json()['playlistID']
spotifyURL = data.json()['spotifyURL']
print(playlistID)
cursor.execute("INSERT INTO rooms (roomid, spotify_id, playlist_name, spotify_URL) VALUES (?, ?, ?, ?)",
(roomid, playlistID, playlistName, spotifyURL))
database.commit()
@ -54,7 +53,7 @@ def create_playlist():
return render_template("create.html", response='Request failed', comment=e, type="error")
@app.route('/search/<string:roomid>', methods=['GET', 'POST'])
def main_app(roomid):
def search(roomid):
if len(roomid) != 8 or not cursor.execute("SELECT * FROM rooms WHERE roomid = ?", (roomid,)).fetchone():
return main()
playlistName = cursor.execute("SELECT playlist_name FROM rooms WHERE roomid = ?", (roomid,)).fetchone()[0]
@ -69,16 +68,12 @@ def main_app(roomid):
# otherwise handle the GET request
return render_template("search.html", roomid=roomid, playlistName=playlistName, spotifyURL=spotifyURL)
@app.route('/add/<string:roomid>/<string:playlistID>/<string:trackid>', methods=['GET'])
@app.route('/add/room/<string:roomid>/playlist/<string:playlistID>/track/<string:trackid>', methods=['GET'])
def add_to_playlist(roomid, playlistID, trackid):
print(roomid)
print(playlistID)
print(trackid)
if not os.getenv("n8n_webhook_add_tracks"):
return render_template("add.html", response="No n8n webhook provided", comment="Please provide a n8n webhook in the .env file", type="error", roomid=roomid)
try:
data = requests.get(os.getenv("n8n_webhook_add_tracks") + "/" + playlistID + "/" + trackid)
print(data.json)
if data.json()['status'] == 'success':
return render_template("add.html", response="Track added to playlist successfully", comment="Enjoy the night \U0001f57a", type="success", roomid=roomid)

207
n8n_workflow.json Normal file
View File

@ -0,0 +1,207 @@
{
"name": "PlaylistCreator Workflows",
"nodes": [
{
"parameters": {
"content": "## Workflow to add track to a playlist\n\nThis webhook is called with the following parameters: `playlistid` and `trackid`, which are sent by the Flask application. This webhook will attempt to add the desired track to the playlist. We assume that the playlist exists and that the current user has the right to add a track to it. As for the `trackid`, it is guaranteed to be valid since it is retrieved from the Spotify API.",
"height": 212,
"width": 495
},
"id": "1c6779f6-ed75-457e-b19c-dd24268eb527",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
460,
120
]
},
{
"parameters": {
"resource": "playlist",
"id": "={{ $json.params.playlistid }}",
"trackID": "={{ $('Add Track Webhook').item.json.params.trackid }}",
"additionalFields": {}
},
"id": "6defaf1d-1c28-46d2-b31f-55b02cd8dda3",
"name": "Add track to the Spotify Playlist",
"type": "n8n-nodes-base.spotify",
"typeVersion": 1,
"position": [
680,
320
],
"alwaysOutputData": true,
"credentials": {
"spotifyOAuth2Api": {
"id": "ry4GegWcWUTXupsE",
"name": "Spotify account"
}
}
},
{
"parameters": {
"resource": "playlist",
"operation": "create",
"name": "={{ $json.params.playlistName }} - {{ $json.params.roomID }}",
"additionalFields": {
"description": "=Playlist created with Make This Playlist - https://makethisplaylist.louisgallet.fr. Add some music by entering the roomID: {{ $json.params.roomID }}",
"public": true
}
},
"id": "319efc18-903e-42d2-bb72-ce50b8bef1ee",
"name": "Create a Spotify playlist",
"type": "n8n-nodes-base.spotify",
"typeVersion": 1,
"position": [
660,
740
],
"credentials": {
"spotifyOAuth2Api": {
"id": "ry4GegWcWUTXupsE",
"name": "Spotify account"
}
}
},
{
"parameters": {
"content": "## Workflow to create a Spotify playlist\n\nThis webhook is called with the following parameters: `roomid` and `playlistid`, which are sent by the Flask application. This webhook will attempt to create the playlist on the associated Spotify account. We assume that the playlist name will be unique, as it uses the following format: `playlist name` - `roomid`.",
"height": 212,
"width": 495
},
"id": "972e1642-69f9-4458-b252-35b8446f4a15",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
480,
560
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "={\n \"playlistID\": \"{{ $json.id }}\", \n \"spotifyURL\": \"{{ $json.external_urls.spotify }}\"\n} ",
"options": {}
},
"id": "4223f8bf-0c4d-4184-8a50-bb3e9f1bb1fb",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
840,
740
]
},
{
"parameters": {
"respondWith": "json",
"responseBody": "{\n \"status\": \"success\"\n}",
"options": {}
},
"id": "296a009b-cfb2-4594-bedc-7362b5f0e7eb",
"name": "Respond to Webhook1",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.1,
"position": [
860,
320
]
},
{
"parameters": {
"path": "61f08b8c-5bca-4091-a934-da66d9ae09e7/:playlistid/:trackid",
"responseMode": "responseNode",
"options": {}
},
"id": "de91df67-33f9-4a6a-aaa4-17a272d54968",
"name": "Add Track Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
500,
320
],
"webhookId": "61f08b8c-5bca-4091-a934-da66d9ae09e7",
"notesInFlow": true
},
{
"parameters": {
"path": "b1447737-cf34-43d5-8d42-8fb29fbfa275/:roomID/:playlistName",
"responseMode": "responseNode",
"options": {}
},
"id": "2b8e9c76-786e-4784-aa8c-0789727b375b",
"name": "Create Playlist Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
480,
740
],
"webhookId": "b1447737-cf34-43d5-8d42-8fb29fbfa275"
}
],
"pinData": {},
"connections": {
"Create a Spotify playlist": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
},
"Add track to the Spotify Playlist": {
"main": [
[
{
"node": "Respond to Webhook1",
"type": "main",
"index": 0
}
]
]
},
"Add Track Webhook": {
"main": [
[
{
"node": "Add track to the Spotify Playlist",
"type": "main",
"index": 0
}
]
]
},
"Create Playlist Webhook": {
"main": [
[
{
"node": "Create a Spotify playlist",
"type": "main",
"index": 0
}
]
]
}
},
"active": true,
"settings": {
"executionOrder": "v1",
"timezone": "Europe/Paris",
"saveManualExecutions": true,
"callerPolicy": "workflowsFromSameOwner"
},
"versionId": "21bcacb5-a3ce-4355-ba98-bb3c865e2905",
"meta": {
"templateCredsSetupCompleted": true,
"instanceId": "2ebecfc6e81edd91c6988c37863cd68c7d8e130f8ce8a9ab9fe59ab24a8d3949"
},
"id": "kWG5BTxflXVL7fFm",
"tags": []
}

View File

@ -1,64 +1,6 @@
appnope==0.1.4
asttokens==2.4.1
attrs==23.2.0
backcall==0.2.0
beautifulsoup4==4.12.3
bleach==6.1.0
blinker==1.8.2
Bootstrap-Flask==2.4.0
certifi==2024.6.2
charset-normalizer==3.3.2
click==8.1.7
decorator==5.1.1
defusedxml==0.7.1
docopt==0.6.2
executing==2.0.1
fastjsonschema==2.20.0
Flask==3.0.3
idna==3.7
ipython==8.12.3
itsdangerous==2.2.0
jedi==0.19.1
Jinja2==3.1.4
jsonschema==4.22.0
jsonschema-specifications==2023.12.1
jupyter_client==8.6.2
jupyter_core==5.7.2
jupyterlab_pygments==0.3.0
MarkupSafe==2.1.5
matplotlib-inline==0.1.7
mistune==3.0.2
nbclient==0.10.0
nbconvert==7.16.4
nbformat==5.10.4
packaging==24.1
pandocfilters==1.5.1
parso==0.8.4
pexpect==4.9.0
pickleshare==0.7.5
pipreqs==0.5.0
platformdirs==4.2.2
prompt_toolkit==3.0.47
ptyprocess==0.7.0
pure-eval==0.2.2
Pygments==2.18.0
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
pyzmq==26.0.3
redis==5.0.6
referencing==0.35.1
requests==2.32.3
rpds-py==0.18.1
six==1.16.0
soupsieve==2.5
spotipy==2.24.0
stack-data==0.6.3
tinycss2==1.3.0
tornado==6.4.1
traitlets==5.14.3
urllib3==2.2.2
wcwidth==0.2.13
webencodings==0.5.1
Werkzeug==3.0.3
WTForms==3.1.2
yarg==0.1.9
spotipy~=2.24.0
python-dotenv~=1.0.1
Flask~=3.0.3
qrcode~=7.4.2
requests~=2.32.3
bootstrap-flask~=2.4.0

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Fête de la musique</title>
<title>Make this playlist</title>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
{{ bootstrap.load_css() }}
</head>

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Fête de la musique</title>
<title>Make this playlist</title>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
{{ bootstrap.load_css() }}
</head>

View File

@ -41,7 +41,7 @@
<h5 class="card-title"> {{ track[0] }} </h5>
<p class="card-text">{{ track[1] }} - {{ track[2] }}</p>
<audio controls><source src="{{ track[3] }}"></audio>
<form method="GET" action="/add/{{roomid}}/{{playlistID}}/{{track[5]}}">
<form method="GET" action="/add/room/{{roomid}}/playlist/{{playlistID}}/track/{{track[5]}}">
<input type="hidden" name="track" value="{{ track[5] }}">
<input type="submit" value="Add to the playlist" class="btn btn-primary">
</form>

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Fête de la musique</title>
<title>Make this playlist</title>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
{{ bootstrap.load_css() }}
</head>
@ -30,7 +30,7 @@
<input type="text" class="form-control" name="roomName" placeholder="Room name" />
<small id="roomNameHelp" class="form-text text-muted">This will be the playlist name</small>
</div>
<button type="submit" class="btn btn-success">Create (require a Spotify account)</button>
<button type="submit" class="btn btn-success">Create</button>
</form>
</div>
</div>

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Fête de la musique</title>
<title>Make this playlist - Search</title>
{{ bootstrap.load_css() }}
</head>
<body>