Connects to spotify

This commit is contained in:
Ben Arc 2021-04-28 12:04:47 +01:00
parent 3f8890def7
commit dc10a0f52b
9 changed files with 395 additions and 353 deletions

View file

@ -8,6 +8,5 @@ jukebox_ext: Blueprint = Blueprint(
"jukebox", __name__, static_folder="static", template_folder="templates" "jukebox", __name__, static_folder="static", template_folder="templates"
) )
from .views_api import * # noqa from .views_api import * # noqa
from .views import * # noqa from .views import * # noqa

View file

@ -1,5 +1,5 @@
{ {
"name": "Jukebox", "name": "SpotifyJukebox",
"short_description": "Spotify jukebox middleware", "short_description": "Spotify jukebox middleware",
"icon": "audiotrack", "icon": "audiotrack",
"contributors": ["benarc"] "contributors": ["benarc"]

View file

@ -2,32 +2,69 @@ from typing import List, Optional
from . import db from . import db
from .models import Jukebox from .models import Jukebox
from lnbits.helpers import urlsafe_short_hash
async def create_update_jukebox(wallet_id: str) -> int: async def create_jukebox(
wallet: str,
title: str,
price: int,
sp_user: str,
sp_secret: str,
sp_token: Optional[str] = "",
sp_device: Optional[str] = "",
sp_playlists: Optional[str] = "",
) -> Jukebox:
juke_id = urlsafe_short_hash() juke_id = urlsafe_short_hash()
result = await db.execute( result = await db.execute(
""" """
INSERT INTO jukebox (id, wallet, user, secret, token, playlists) INSERT INTO jukebox (id, title, wallet, sp_user, sp_secret, sp_token, sp_device, sp_playlists, price)
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", """,
(juke_id, wallet_id, "", "", "", ""), (
juke_id,
title,
wallet,
sp_user,
sp_secret,
sp_token,
sp_device,
sp_playlists,
int(price),
),
) )
return result._result_proxy.lastrowid jukebox = await get_jukebox(juke_id)
assert jukebox, "Newly created Jukebox couldn't be retrieved"
return jukebox
async def update_jukebox(sp_user: str, **kwargs) -> Optional[Jukebox]:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
await db.execute(
f"UPDATE jukebox SET {q} WHERE sp_user = ?", (*kwargs.values(), sp_user)
)
row = await db.fetchone("SELECT * FROM jukebox WHERE sp_user = ?", (sp_user,))
return Jukebox(**row) if row else None
async def get_jukebox(id: str) -> Optional[Jukebox]: async def get_jukebox(id: str) -> Optional[Jukebox]:
row = await db.fetchone("SELECT * FROM jukebox WHERE id = ?", (id,)) row = await db.fetchone("SELECT * FROM jukebox WHERE id = ?", (id,))
return Shop(**dict(row)) if row else None return Jukebox(**row) if row else None
async def get_jukebox_by_user(user: str) -> Optional[Jukebox]:
row = await db.fetchone("SELECT * FROM jukebox WHERE sp_user = ?", (user,))
return Jukebox(**row) if row else None
async def get_jukeboxs(id: str) -> Optional[Jukebox]: async def get_jukeboxs(id: str) -> Optional[Jukebox]:
row = await db.fetchone("SELECT * FROM jukebox WHERE id = ?", (id,)) rows = await db.fetchone("SELECT * FROM jukebox WHERE id = ?", (id,))
return Shop(**dict(row)) if row else None return [Jukebox(**row) for row in rows]
async def delete_jukebox(shop: int, item_id: int): async def delete_jukebox(shop: int, item_id: int):
await db.execute( await db.execute(
""" """
DELETE FROM jukebox WHERE id = ? DELETE FROM jukebox WHERE id = ?
""", """,
(shop, item_id), (Jukebox, item_id),
) )

View file

@ -5,12 +5,15 @@ async def m001_initial(db):
await db.execute( await db.execute(
""" """
CREATE TABLE jukebox ( CREATE TABLE jukebox (
id INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT PRIMARY KEY,
wallet TEXT NOT NULL, title TEXT,
user TEXT NOT NULL, wallet TEXT,
secret TEXT NOT NULL, sp_user TEXT NOT NULL,
token TEXT NOT NULL, sp_secret TEXT NOT NULL,
playlists TEXT NOT NULL sp_token TEXT,
sp_device TEXT,
sp_playlists TEXT,
price INTEGER
); );
""" """
) )

View file

@ -6,14 +6,18 @@ from quart import url_for
from typing import NamedTuple, Optional, List, Dict from typing import NamedTuple, Optional, List, Dict
from sqlite3 import Row from sqlite3 import Row
class Jukebox(NamedTuple): class Jukebox(NamedTuple):
id: int id: str
title: str
wallet: str wallet: str
user: str sp_user: str
secret: str sp_secret: str
token: str sp_token: str
playlists: str sp_device: str
sp_playlists: str
price: int
@classmethod @classmethod
def from_row(cls, row: Row) -> "Charges": def from_row(cls, row: Row) -> "Jukebox":
return cls(**dict(row)) return cls(**dict(row))

View file

@ -4,28 +4,25 @@ Vue.component(VueQrcode.name, VueQrcode)
const pica = window.pica() const pica = window.pica()
const defaultItemData = {
unit: 'sat'
}
new Vue({ new Vue({
el: '#vue', el: '#vue',
mixins: [windowMixin], mixins: [windowMixin],
data() { data() {
return { return {
selectedWallet: null, isPwd: true,
confirmationMethod: 'wordlist', tokenFetched: true,
wordlistTainted: false, device: [],
jukebox: { jukebox: {},
method: null, playlists: [],
wordlist: [], step: 1,
items: [] locationcbPath: "",
}, jukeboxDialog: {
itemDialog: {
show: false, show: false,
data: {...defaultItemData}, data: {}
units: ['sat'] },
} spotifyDialog: false
} }
}, },
computed: { computed: {
@ -34,183 +31,130 @@ new Vue({
} }
}, },
methods: { methods: {
openNewDialog() { closeFormDialog() {
this.itemDialog.show = true this.jukeboxDialog.data = {}
this.itemDialog.data = {...defaultItemData} this.jukeboxDialog.show = false
this.step = 1
}, },
openUpdateDialog(itemId) { submitSpotify() {
this.itemDialog.show = true
let item = this.jukebox.items.find(item => item.id === itemId) self = this
this.itemDialog.data = item console.log(self.jukeboxDialog.data)
}, self.requestAuthorization()
imageAdded(file) { this.$q.notify({
let blobURL = URL.createObjectURL(file) spinner: true,
let image = new Image() message: 'Fetching token',
image.src = blobURL timeout: 4000
image.onload = async () => {
let canvas = document.createElement('canvas')
canvas.setAttribute('width', 100)
canvas.setAttribute('height', 100)
await pica.resize(image, canvas, {
quality: 0,
alpha: true,
unsharpAmount: 95,
unsharpRadius: 0.9,
unsharpThreshold: 70
}) })
this.itemDialog.data.image = canvas.toDataURL() LNbits.api.request(
this.itemDialog = {...this.itemDialog} 'POST',
} '/jukebox/api/v1/jukebox/',
}, self.g.user.wallets[0].adminkey,
imageCleared() { self.jukeboxDialog.data
this.itemDialog.data.image = null
this.itemDialog = {...this.itemDialog}
},
disabledAddItemButton() {
return (
!this.itemDialog.data.name ||
this.itemDialog.data.name.length === 0 ||
!this.itemDialog.data.price ||
!this.itemDialog.data.description ||
!this.itemDialog.data.unit ||
this.itemDialog.data.unit.length === 0
)
},
changedWallet(wallet) {
this.selectedWallet = wallet
this.loadShop()
},
loadShop() {
LNbits.api
.request('GET', '/jukebox/api/v1/jukebox', this.selectedWallet.inkey)
.then(response => {
this.jukebox = response.data
this.confirmationMethod = response.data.method
this.wordlistTainted = false
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
},
async setMethod() {
try {
await LNbits.api.request(
'PUT',
'/jukebox/api/v1/jukebox/method',
this.selectedWallet.inkey,
{method: this.confirmationMethod, wordlist: this.jukebox.wordlist}
)
} catch (err) {
LNbits.utils.notifyApiError(err)
return
}
this.$q.notify({
message:
`Method set to ${this.confirmationMethod}.` +
(this.confirmationMethod === 'wordlist' ? ' Counter reset.' : ''),
timeout: 700
})
this.loadShop()
},
async sendItem() {
let {id, name, image, description, price, unit} = this.itemDialog.data
const data = {
name,
description,
image,
price,
unit
}
try {
if (id) {
await LNbits.api.request(
'PUT',
'/jukebox/api/v1/jukebox/items/' + id,
this.selectedWallet.inkey,
data
)
} else {
await LNbits.api.request(
'POST',
'/jukebox/api/v1/jukebox/items',
this.selectedWallet.inkey,
data
)
this.$q.notify({
message: `Item '${this.itemDialog.data.name}' added.`,
timeout: 700
})
}
} catch (err) {
LNbits.utils.notifyApiError(err)
return
}
this.loadShop()
this.itemDialog.show = false
this.itemDialog.data = {...defaultItemData}
},
toggleItem(itemId) {
let item = this.jukebox.items.find(item => item.id === itemId)
item.enabled = !item.enabled
LNbits.api
.request(
'PUT',
'/jukebox/api/v1/jukebox/items/' + itemId,
this.selectedWallet.inkey,
item
) )
.then(response => { .then(response => {
this.$q.notify({ if(response.data){
message: `Item ${item.enabled ? 'enabled' : 'disabled'}.`, var timerId = setInterval(function(){
timeout: 700 if(!self.jukeboxDialog.data.sp_user){
}) clearInterval(timerId);
this.jukebox.items = this.jukebox.items }
}) LNbits.api
.catch(err => { .request('GET', '/jukebox/api/v1/jukebox/spotify/' + self.jukeboxDialog.data.sp_user, self.g.user.wallets[0].inkey)
LNbits.utils.notifyApiError(err)
})
},
deleteItem(itemId) {
LNbits.utils
.confirmDialog('Are you sure you want to delete this item?')
.onOk(() => {
LNbits.api
.request(
'DELETE',
'/jukebox/api/v1/jukebox/items/' + itemId,
this.selectedWallet.inkey
)
.then(response => { .then(response => {
this.$q.notify({ if(response.data.sp_token){
message: `Item deleted.`, console.log(response.data.sp_token)
timeout: 700
self.step = 3
clearInterval(timerId);
self.refreshPlaylists()
self.$q.notify({
message: 'Success! App is now linked!',
timeout: 3000
}) })
this.jukebox.items.splice( //set devices, playlists
this.jukebox.items.findIndex(item => item.id === itemId), }
1
)
}) })
.catch(err => { .catch(err => {
LNbits.utils.notifyApiError(err) LNbits.utils.notifyApiError(err)
}) })
}, 3000)
}
}) })
} .catch(err => {
LNbits.utils.notifyApiError(err)
})
},
requestAuthorization(){
self = this
let url = 'https://accounts.spotify.com/authorize'
url += '?scope=user-modify-playback-state%20user-read-playback-position'
url += '%20user-library-read%20streaming%20user-read-playback-state'
url += '%20user-read-recently-played%20playlist-read-private&response_type=code'
url += '&redirect_uri=' + encodeURIComponent(self.locationcbPath) + self.jukeboxDialog.data.sp_user
url += '&client_id=' + self.jukeboxDialog.data.sp_user
url += '&show_dialog=true'
console.log(url)
window.open(url)
},
openNewDialog() {
this.jukeboxDialog.show = true
this.jukeboxDialog.data = {}
},
openUpdateDialog(itemId) {
this.jukeboxDialog.show = true
let item = this.jukebox.items.find(item => item.id === itemId)
this.jukeboxDialog.data = item
},
callApi(method, url, body, callback){
let xhr = new XMLHttpRequest()
xhr.open(method, url, true)
xhr.setRequestHeader('Content-Type', 'application/json')
xhr.setRequestHeader('Authorization', 'Bearer ' + self.jukeboxDialog.data.sp_token)
xhr.send(body)
xhr.onload = callback
},
refreshPlaylists(){
console.log("sdfvasdv")
callApi( "GET", "https://api.spotify.com/v1/me/playlists", null, handlePlaylistsResponse )
},
handlePlaylistsResponse(){
console.log("data")
if ( this.status == 200 ){
var data = JSON.parse(this.responseText)
console.log(data)
}
else if ( this.status == 401 ){
refreshAccessToken()
}
else {
console.log(this.responseText)
alert(this.responseText)
}
},
refreshAccessToken(){
refresh_token = localStorage.getItem("refresh_token")
let body = "grant_type=refresh_token"
body += "&refresh_token=" + self.jukeboxDialog.data.sp_token
body += "&client_id=" + self.jukeboxDialog.data.sp_user
callAuthorizationApi(body)
},
callAuthorizationApi(body){
let xhr = new XMLHttpRequest()
xhr.open("POST", TOKEN, true)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.setRequestHeader('Authorization', 'Basic ' + btoa(self.jukeboxDialog.data.sp_user + ":" + self.jukeboxDialog.data.sp_secret))
xhr.send(body)
xhr.onload = handleAuthorizationResponse
},
}, },
created() { created() {
this.selectedWallet = this.g.user.wallets[0] this.selectedWallet = this.g.user.wallets[0]
this.loadShop() this.locationcbPath = String([
window.location.protocol,
LNbits.api '//',
.request('GET', '/jukebox/api/v1/currencies') window.location.host,
.then(response => { '/jukebox/api/v1/jukebox/spotify/cb/'
this.itemDialog = {...this.itemDialog, units: ['sat', ...response.data]} ].join(''))
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
} }
}) })

View file

@ -1,42 +1,20 @@
<q-expansion-item <q-expansion-item
group="extras" group="extras"
icon="swap_vertical_circle" icon="swap_vertical_circle"
label="How to use" label="About"
:content-inset-level="0.5" :content-inset-level="0.5"
> >
<q-card> <q-card>
<q-card-section> <q-card-section>
<ol> To use this extension you need a Spotify client ID and client secret. You
<li>Register items.</li> get these by creating an app in the Spotify developers dashboard
<li> <a href="https://developer.spotify.com/dashboard/applications">here </a>
Print QR codes and paste them on your store, your menu, somewhere, <br /><br />Select the playlists you want people to be able to pay for,
somehow. share the frontend page, profit :) <br /><br />
</li> Made by, <a href="https://twitter.com/arcbtc">benarc</a>. Inspired by,
<li> <a href="https://twitter.com/pirosb3/status/1056263089128161280"
Clients scan the QR codes and get information about the items plus the >pirosb3</a
price on their phones directly (they must have internet) >.
</li>
<li>
Once they decide to pay, they'll get an invoice on their phones
automatically
</li>
<li>
When the payment is confirmed, a confirmation code will be issued for
them.
</li>
</ol>
<p>
The confirmation codes are words from a predefined sequential word list.
Each new payment bumps the words sequence by 1. So you can check the
confirmation codes manually by just looking at them.
</p>
<p>
For example, if your wordlist is
<code>[apple, banana, coconut]</code> the first purchase will be
<code>apple</code>, the second <code>banana</code> and so on. When it
gets to the end it starts from the beginning again.
</p>
<p>Powered by LNURL-pay.</p>
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-expansion-item> </q-expansion-item>

View file

@ -4,15 +4,15 @@
<div class="col-12 col-md-7 q-gutter-y-md"> <div class="col-12 col-md-7 q-gutter-y-md">
<q-card> <q-card>
<q-card-section> <q-card-section>
<q-btn
unelevated
color="green-7"
class="q-ma-lg"
@click="openNewDialog()"
>Add Spotify Jukebox</q-btn
>
<div class="row items-center no-wrap q-mb-md"> <div class="row items-center no-wrap q-mb-md">
<div class="col"> <h5 class="text-subtitle1 q-my-none">Items</h5>
<h5 class="text-subtitle1 q-my-none">Items</h5>
</div>
<div class="col q-ml-lg">
<q-btn unelevated color="deep-purple" @click="openNewDialog()"
>Add jukebox</q-btn
>
</div>
</div> </div>
{% raw %} {% raw %}
<q-table <q-table
@ -101,107 +101,170 @@
</q-card> </q-card>
</div> </div>
<q-dialog v-model="itemDialog.show"> <q-dialog v-model="jukeboxDialog.show" position="top" @hide="closeFormDialog">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card"> <q-card class="q-pa-md q-pt-lg q-mt-md" style="width: 100%">
<q-card-section> <q-stepper
<h5 v-model="step"
class="q-ma-none" active-color="green-7"
v-if="itemDialog.data.id" inactive-color="green-10"
v-text="itemDialog.data.name" vertical
></h5> animated
<h5 class="q-ma-none q-mb-xl" v-else>Adding a new item</h5> >
<q-step
<q-responsive v-if="itemDialog.data.id" :ratio="1"> :name="1"
<qrcode title="Pick wallet, price"
:value="itemDialog.data.lnurl" icon="account_balance_wallet"
:options="{width: 800}" :done="step > 1"
class="rounded-borders" >
></qrcode>
</q-responsive>
<div v-if="itemDialog.data.id" class="row q-gutter-sm justify-center">
<q-btn
outline
color="grey"
@click="copyText(itemDialog.data.lnurl, 'LNURL copied to clipboard!')"
class="q-mb-lg"
>Copy LNURL</q-btn
>
</div>
<q-form @submit="sendItem" class="q-gutter-md">
<q-input <q-input
filled filled
class="q-pt-md"
dense dense
v-model.trim="itemDialog.data.name" v-model.trim="jukeboxDialog.data.title"
type="text" label="Jukebox name"
label="Item name"
></q-input>
<q-input
filled
dense
v-model.trim="itemDialog.data.description"
type="text"
label="Brief description"
></q-input>
<q-file
filled
dense
capture="environment"
accept="image/jpeg, image/png"
:max-file-size="3*1024**2"
label="Small image (optional)"
clearable
@input="imageAdded"
@clear="imageCleared"
>
<template v-if="itemDialog.data.image" v-slot:before>
<img style="height: 1em" :src="itemDialog.data.image" />
</template>
<template v-if="itemDialog.data.image" v-slot:append>
<q-icon
name="cancel"
@click.stop.prevent="imageCleared"
class="cursor-pointer"
/>
</template>
</q-file>
<q-input
filled
dense
v-model.number="itemDialog.data.price"
type="number"
min="1"
:label="`Item price (${itemDialog.data.unit})`"
></q-input> ></q-input>
<q-select <q-select
class="q-pb-md q-pt-md"
filled filled
dense dense
v-model="itemDialog.data.unit" emit-value
type="text" v-model="jukeboxDialog.data.wallet"
label="Unit" :options="g.user.walletOptions"
:options="itemDialog.units" label="Wallet to use"
></q-select> ></q-select>
<q-input
<div class="row q-mt-lg"> filled
<div class="col q-ml-lg"> dense
v-model.trim="jukeboxDialog.data.price"
type="number"
max="1440"
label="Price per track"
class="q-pb-lg"
>
</q-input>
<div class="row">
<div class="col-4">
<q-btn <q-btn
unelevated v-if="jukeboxDialog.data.title != null && jukeboxDialog.data.price != null && jukeboxDialog.data.wallet != null"
color="deep-purple" color="green-7"
:disable="disabledAddItemButton()" @click="step = 2"
type="submit" >Continue</q-btn
> >
{% raw %}{{ itemDialog.data.id ? 'Update' : 'Add' }}{% endraw %} <q-btn v-else color="green-7" disable>Continue</q-btn>
Item
</q-btn>
</div> </div>
<div class="col q-ml-lg"> <div class="col-8">
<q-btn v-close-popup flat color="grey" class="q-ml-auto" <q-btn
color="green-7"
class="float-right"
@click="closeFormDialog"
>Cancel</q-btn >Cancel</q-btn
> >
</div> </div>
</div> </div>
</q-form>
</q-card-section> <br />
</q-step>
<q-step :name="2" title="Add api keys" icon="vpn_key" :done="step > 2">
To use this extension you need a Spotify client ID and client secret.
You get these by creating an app in the Spotify developers dashboard
<a
target="_blank"
href="https://developer.spotify.com/dashboard/applications"
>here</a
>. <br />
In the app go to edit-settings, set the redirect URI to this link
(replacing the CLIENT-ID with your own) {% raw %}{{ locationcbPath
}}CLIENT-ID{% endraw %}
<q-input
filled
class="q-pb-md q-pt-md"
dense
v-model.trim="jukeboxDialog.data.sp_user"
label="Client ID"
></q-input>
<q-input
dense
v-model="jukeboxDialog.data.sp_secret"
filled
:type="isPwd ? 'password' : 'text'"
label="Client secret"
>
<template #append>
<q-icon
:name="isPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="isPwd = !isPwd"
></q-icon>
</template>
</q-input>
<div class="row q-mt-md">
<div class="col-4">
<q-btn
v-if="jukeboxDialog.data.sp_secret != null && jukeboxDialog.data.sp_user != null && tokenFetched"
color="green-7"
@click="submitSpotify"
>Get token</q-btn
>
<q-btn v-else color="green-7" disable color="green-7"
>Get token</q-btn
>
</div>
<div class="col-8">
<q-btn
color="green-7"
class="float-right"
@click="closeFormDialog"
>Cancel</q-btn
>
</div>
</div>
<br />
</q-step>
<q-step
:name="3"
title="Select playlists"
icon="queue_music"
active-color="green-8"
:done="step > 3"
>
<q-select
class="q-pb-md q-pt-md"
filled
dense
emit-value
v-model="jukeboxDialog.data.sp_device"
:options="device"
label="Device jukebox will play to"
></q-select>
<q-select
class="q-pb-md"
filled
dense
emit-value
v-model="jukeboxDialog.data.sp_playlists"
:options="playlists"
label="Playlists available to the jukebox"
></q-select>
<div class="row q-mt-md">
<div class="col-5">
<q-btn color="green-7" @click="step = 2">Create Jukebox</q-btn>
</div>
<div class="col-7">
<q-btn
color="green-7"
class="float-right"
@click="closeFormDialog"
>Cancel</q-btn
>
</div>
</div>
</q-step>
</q-stepper>
</q-card> </q-card>
</q-dialog> </q-dialog>
</div> </div>

View file

@ -1,13 +1,15 @@
from quart import g, jsonify from quart import g, jsonify, request
from http import HTTPStatus from http import HTTPStatus
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl # type: ignore from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl # type: ignore
from lnbits.decorators import api_check_wallet_key, api_validate_post_request from lnbits.decorators import api_check_wallet_key, api_validate_post_request
import httpx
from . import jukebox_ext from . import jukebox_ext
from .crud import ( from .crud import (
create_update_jukebox, create_jukebox,
update_jukebox,
get_jukebox, get_jukebox,
get_jukebox_by_user,
get_jukeboxs, get_jukeboxs,
delete_jukebox, delete_jukebox,
) )
@ -17,33 +19,45 @@ from .models import Jukebox
@jukebox_ext.route("/api/v1/jukebox", methods=["GET"]) @jukebox_ext.route("/api/v1/jukebox", methods=["GET"])
@api_check_wallet_key("invoice") @api_check_wallet_key("invoice")
async def api_get_jukeboxs(): async def api_get_jukeboxs():
jukebox = await get_jukeboxs(g.wallet.id) jsonify([jukebox._asdict() for jukebox in await get_jukeboxs(g.wallet.id)]),
return (
jsonify(
{ ##################SPOTIFY AUTH#####################
jukebox._asdict()
}
), @jukebox_ext.route("/api/v1/jukebox/spotify/cb/<sp_user>/", methods=["GET"])
HTTPStatus.OK, async def api_check_credentials_callbac(sp_user):
jukebox = await get_jukebox_by_user(sp_user)
jukebox = await update_jukebox(
sp_user=sp_user, sp_secret=jukebox.sp_secret, sp_token=request.args.get('code')
) )
return "<h1>Success!</h1><h2>You can close this window</h2>"
#websocket get spotify crap @jukebox_ext.route("/api/v1/jukebox/spotify/<sp_user>", methods=["GET"])
@api_check_wallet_key("invoice")
async def api_check_credentials_check(sp_user):
jukebox = await get_jukebox_by_user(sp_user)
return jsonify(jukebox._asdict()), HTTPStatus.CREATED
@jukebox_ext.route("/api/v1/jukebox/items", methods=["POST"])
@jukebox_ext.route("/api/v1/jukebox/items/<item_id>", methods=["PUT"]) @jukebox_ext.route("/api/v1/jukebox/", methods=["POST"])
@jukebox_ext.route("/api/v1/jukebox/<item_id>", methods=["PUT"])
@api_check_wallet_key("admin") @api_check_wallet_key("admin")
@api_validate_post_request( @api_validate_post_request(
schema={ schema={
"wallet": {"type": "string", "empty": False}, "title": {"type": "string", "empty": False, "required": True},
"user": {"type": "string", "empty": False}, "wallet": {"type": "string", "empty": False, "required": True},
"secret": {"type": "string", "required": False}, "sp_user": {"type": "string", "empty": False, "required": True},
"token": {"type": "string", "required": True}, "sp_secret": {"type": "string", "required": True},
"playlists": {"type": "string", "required": True}, "sp_token": {"type": "string", "required": False},
"sp_device": {"type": "string", "required": False},
"sp_playlists": {"type": "string", "required": False},
"price": {"type": "string", "required": True},
} }
) )
async def api_create_update_jukebox(item_id=None): async def api_create_update_jukebox(item_id=None):
jukebox = await create_update_jukebox(g.wallet.id, **g.data) print(g.data)
jukebox = await create_jukebox(**g.data)
return jsonify(jukebox._asdict()), HTTPStatus.CREATED return jsonify(jukebox._asdict()), HTTPStatus.CREATED
@ -51,4 +65,4 @@ async def api_create_update_jukebox(item_id=None):
@api_check_wallet_key("admin") @api_check_wallet_key("admin")
async def api_delete_item(juke_id): async def api_delete_item(juke_id):
shop = await delete_jukebox(juke_id) shop = await delete_jukebox(juke_id)
return "", HTTPStatus.NO_CONTENT return "", HTTPStatus.NO_CONTENT