Table working

This commit is contained in:
Ben Arc 2021-04-29 22:38:57 +01:00
parent 38c2270abf
commit 245a819f19
6 changed files with 178 additions and 93 deletions

View File

@ -6,6 +6,7 @@ from lnbits.helpers import urlsafe_short_hash
async def create_jukebox( async def create_jukebox(
user: str,
wallet: str, wallet: str,
title: str, title: str,
price: int, price: int,
@ -19,11 +20,12 @@ async def create_jukebox(
juke_id = urlsafe_short_hash() juke_id = urlsafe_short_hash()
result = await db.execute( result = await db.execute(
""" """
INSERT INTO jukebox (id, title, wallet, sp_user, sp_secret, sp_access_token, sp_refresh_token, sp_device, sp_playlists, price) INSERT INTO jukebox (id, user, title, wallet, sp_user, sp_secret, sp_access_token, sp_refresh_token, sp_device, sp_playlists, price)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", """,
( (
juke_id, juke_id,
user,
title, title,
wallet, wallet,
sp_user, sp_user,
@ -40,12 +42,12 @@ async def create_jukebox(
return jukebox return jukebox
async def update_jukebox(sp_user: str, **kwargs) -> Optional[Jukebox]: async def update_jukebox(id: str, **kwargs) -> Optional[Jukebox]:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
await db.execute( await db.execute(
f"UPDATE jukebox SET {q} WHERE sp_user = ?", (*kwargs.values(), sp_user) f"UPDATE jukebox SET {q} WHERE id = ?", (*kwargs.values(), id)
) )
row = await db.fetchone("SELECT * FROM jukebox WHERE sp_user = ?", (sp_user,)) row = await db.fetchone("SELECT * FROM jukebox WHERE id = ?", (id,))
return Jukebox(**row) if row else None return Jukebox(**row) if row else None
@ -58,16 +60,18 @@ async def get_jukebox_by_user(user: str) -> Optional[Jukebox]:
row = await db.fetchone("SELECT * FROM jukebox WHERE sp_user = ?", (user,)) row = await db.fetchone("SELECT * FROM jukebox WHERE sp_user = ?", (user,))
return Jukebox(**row) if row else None return Jukebox(**row) if row else None
async def get_jukeboxs(user: str) -> List[Jukebox]:
rows = await db.fetchall("SELECT * FROM jukebox WHERE user = ?", (user,))
for row in rows:
if not row.sp_playlists:
await delete_jukebox(row.id)
rows.remove(row)
return [Jukebox.from_row(row) for row in rows]
async def get_jukeboxs(id: str) -> Optional[Jukebox]: async def delete_jukebox(id: str):
rows = await db.fetchone("SELECT * FROM jukebox WHERE id = ?", (id,))
return [Jukebox(**row) for row in rows]
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 = ?
""", """,
(Jukebox, item_id), (id),
) )

View File

@ -6,6 +6,7 @@ async def m001_initial(db):
""" """
CREATE TABLE jukebox ( CREATE TABLE jukebox (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
user TEXT,
title TEXT, title TEXT,
wallet TEXT, wallet TEXT,
sp_user TEXT NOT NULL, sp_user TEXT NOT NULL,

View File

@ -9,6 +9,7 @@ from sqlite3 import Row
class Jukebox(NamedTuple): class Jukebox(NamedTuple):
id: str id: str
user: str
title: str title: str
wallet: str wallet: str
sp_user: str sp_user: str

View File

@ -2,20 +2,63 @@
Vue.component(VueQrcode.name, VueQrcode) Vue.component(VueQrcode.name, VueQrcode)
const pica = window.pica() var mapJukebox = obj => {
obj._data = _.clone(obj)
obj.device = obj.sp_device.split("-")[0]
playlists = obj.sp_playlists.split(",")
var i;
playlistsar = []
for (i = 0; i < playlists.length; i++) {
playlistsar.push(playlists[i].split("-")[0])
}
obj.playlist = playlistsar.join()
return obj
}
new Vue({ new Vue({
el: '#vue', el: '#vue',
mixins: [windowMixin], mixins: [windowMixin],
data() { data() {
return { return {
JukeboxTable: {
columns: [
{
name: 'title',
align: 'left',
label: 'Title',
field: 'title'
},
{
name: 'device',
align: 'left',
label: 'Device',
field: 'device'
},
{
name: 'playlist',
align: 'left',
label: 'Playlist',
field: 'playlist'
},
{
name: 'price',
align: 'left',
label: 'Price',
field: 'price'
},
],
pagination: {
rowsPerPage: 10
}
},
isPwd: true, isPwd: true,
tokenFetched: true, tokenFetched: true,
devices: [], devices: [],
filter: '',
jukebox: {}, jukebox: {},
playlists: [], playlists: [],
JukeboxLinks: [],
step: 1, step: 1,
locationcbPath: "", locationcbPath: "",
locationcb: "", locationcb: "",
@ -27,20 +70,42 @@ new Vue({
} }
}, },
computed: { computed: {
printItems() {
return this.jukebox.items.filter(({enabled}) => enabled)
}
}, },
methods: { methods: {
getJukeboxes(){
self = this
LNbits.api
.request('GET', '/jukebox/api/v1/jukebox', self.g.user.wallets[0].inkey)
.then(function (response) {
self.JukeboxLinks = response.data.map(mapJukebox)
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
},
deleteJukebox(juke_id){
self = this
LNbits.api
.request('DELETE', '/jukebox/api/v1/jukebox/' + juke_id, self.g.user.wallets[0].adminkey)
.then(function (response) {
self.JukeboxLinks = _.reject(self.JukeboxLinks, function (obj) {
return obj.id === juke_id
})
})
.catch(err => {
LNbits.utils.notifyApiError(err)
})
},
closeFormDialog() { closeFormDialog() {
this.jukeboxDialog.data = {} this.jukeboxDialog.data = {}
this.jukeboxDialog.show = false this.jukeboxDialog.show = false
this.step = 1 this.step = 1
}, },
submitSpotify() { submitSpotify() {
self = this self = this
console.log(self.jukeboxDialog.data) self.jukeboxDialog.data.user = self.g.user.id
self.requestAuthorization() self.requestAuthorization()
this.$q.notify({ this.$q.notify({
spinner: true, spinner: true,
@ -56,7 +121,6 @@ new Vue({
.then(response => { .then(response => {
if(response.data){ if(response.data){
var timerId = setInterval(function(){ var timerId = setInterval(function(){
console.log(response.data)
if(!self.jukeboxDialog.data.sp_user){ if(!self.jukeboxDialog.data.sp_user){
clearInterval(timerId); clearInterval(timerId);
} }
@ -68,15 +132,7 @@ new Vue({
self.jukeboxDialog.data.sp_access_token = response.data.sp_access_token self.jukeboxDialog.data.sp_access_token = response.data.sp_access_token
self.step = 3 self.step = 3
self.fetchAccessToken() self.fetchAccessToken()
clearInterval(timerId) clearInterval(timerId)
// self.refreshPlaylists(response.data.sp_token)
// self.$q.notify({
// message: 'Success! App is now linked!',
// timeout: 3000
// })
//set devices, playlists
} }
}) })
.catch(err => { .catch(err => {
@ -97,7 +153,7 @@ new Vue({
url += '&redirect_uri=' + encodeURI(self.locationcbPath) + self.jukeboxDialog.data.sp_user url += '&redirect_uri=' + encodeURI(self.locationcbPath) + self.jukeboxDialog.data.sp_user
url += "&show_dialog=true" url += "&show_dialog=true"
url += '&scope=user-read-private user-read-email user-modify-playback-state user-read-playback-position user-library-read streaming user-read-playback-state user-read-recently-played playlist-read-private' url += '&scope=user-read-private user-read-email user-modify-playback-state user-read-playback-position user-library-read streaming user-read-playback-state user-read-recently-played playlist-read-private'
console.log(url)
window.open(url) window.open(url)
}, },
openNewDialog() { openNewDialog() {
@ -111,19 +167,16 @@ new Vue({
}, },
createJukebox(){ createJukebox(){
self = this self = this
this.jukeboxDialog.data.sp_playlists = this.jukeboxDialog.data.sp_playlists.join()
LNbits.api.request( LNbits.api.request(
'PUT', 'PUT',
'/jukebox/api/v1/jukebox/' + this.jukeboxDialog.data.sp_id, '/jukebox/api/v1/jukebox/' + this.jukeboxDialog.data.sp_id,
self.g.user.wallets[0].adminkey, self.g.user.wallets[0].adminkey,
self.jukeboxDialog.data self.jukeboxDialog.data
) )
.then(response => { .then(function (response) {
console.log(response.data) self.JukeboxLinks.push(mapJukebox(response.data))
self.jukeboxDialog.show = false
})
.catch(err => {
LNbits.utils.notifyApiError(err)
}) })
}, },
@ -156,12 +209,11 @@ new Vue({
xhr.send(body) xhr.send(body)
xhr.onload = function() { xhr.onload = function() {
let responseObj = JSON.parse(xhr.response) let responseObj = JSON.parse(xhr.response)
console.log(responseObj.devices[0])
self.devices = [] self.devices = []
var i; var i;
for (i = 0; i < responseObj.devices.length; i++) { for (i = 0; i < responseObj.devices.length; i++) {
self.devices.push(responseObj.devices[i].name + "-" + responseObj.devices[i].id) self.devices.push(responseObj.devices[i].name + "-" + responseObj.devices[i].id)
console.log(responseObj.devices[i].name)
} }
} }
@ -191,20 +243,19 @@ new Vue({
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded') xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.setRequestHeader('Authorization', 'Basic ' + btoa(this.jukeboxDialog.data.sp_user + ":" + this.jukeboxDialog.data.sp_secret)) xhr.setRequestHeader('Authorization', 'Basic ' + btoa(this.jukeboxDialog.data.sp_user + ":" + this.jukeboxDialog.data.sp_secret))
xhr.send(body) xhr.send(body)
console.log(('Authorization', 'Basic ' + btoa(this.jukeboxDialog.data.sp_user + ":" + this.jukeboxDialog.data.sp_secret)))
xhr.onload = function() { xhr.onload = function() {
let responseObj = JSON.parse(xhr.response) let responseObj = JSON.parse(xhr.response)
alert(responseObj.access_token)
alert(responseObj.refresh_token)
self.jukeboxDialog.data.sp_access_token = responseObj.access_token self.jukeboxDialog.data.sp_access_token = responseObj.access_token
self.jukeboxDialog.data.sp_refresh_token = responseObj.refresh_token self.jukeboxDialog.data.sp_refresh_token = responseObj.refresh_token
console.log(self.jukeboxDialog.data)
self.refreshPlaylists() self.refreshPlaylists()
self.refreshDevices() self.refreshDevices()
} }
}, },
}, },
created() { created() {
var getJukeboxes = this.getJukeboxes
getJukeboxes()
this.selectedWallet = this.g.user.wallets[0] this.selectedWallet = this.g.user.wallets[0]
this.locationcbPath = String([ this.locationcbPath = String([
window.location.protocol, window.location.protocol,
@ -212,7 +263,6 @@ new Vue({
window.location.host, window.location.host,
'/jukebox/api/v1/jukebox/spotify/cb/' '/jukebox/api/v1/jukebox/spotify/cb/'
].join('')) ].join(''))
console.log(this.locationcbPath)
this.locationcb = this.locationcbPath this.locationcb = this.locationcbPath
} }
}) })

View File

@ -11,30 +11,36 @@
@click="openNewDialog()" @click="openNewDialog()"
>Add Spotify Jukebox</q-btn >Add Spotify Jukebox</q-btn
> >
<div class="row items-center no-wrap q-mb-md">
<h5 class="text-subtitle1 q-my-none">Items</h5>
</div>
{% raw %} {% raw %}
<q-table <q-table
dense
flat flat
selection="multiple" dense
:data="jukebox.items" :data="JukeboxLinks"
row-key="id" row-key="id"
no-data-label="No items for sale yet" :columns="JukeboxTable.columns"
:pagination="{rowsPerPage: 0}" :pagination.sync="JukeboxTable.pagination"
:binary-state-sort="true" :filter="filter"
> >
<template v-slot:header="props"> <template v-slot:header="props">
<q-tr :props="props"> <q-tr :props="props">
<q-th auto-width></q-th> <q-th auto-width></q-th>
<q-th auto-width>Name</q-th> <q-th auto-width></q-th>
<q-th auto-width>Description</q-th>
<q-th auto-width>Image</q-th> <q-th
<q-th auto-width>Price</q-th> v-for="col in props.cols"
:key="col.name"
:props="props"
auto-width
>
<div v-if="col.name == 'id'"></div>
<div v-else>{{ col.label }}</div>
</q-th>
<q-th auto-width></q-th> <q-th auto-width></q-th>
</q-tr> </q-tr>
</template> </template>
<template v-slot:body="props"> <template v-slot:body="props">
<q-tr :props="props"> <q-tr :props="props">
<q-td auto-width> <q-td auto-width>
@ -42,44 +48,35 @@
unelevated unelevated
dense dense
size="xs" size="xs"
:icon="props.row.enabled ? 'done' : 'block'" icon="link"
:color="props.row.enabled ? 'green' : ($q.dark.isActive ? 'grey-7' : 'grey-5')" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a" type="a"
@click="toggleItem(props.row.id)" :href="props.row.displayUrl"
target="_blank" target="_blank"
></q-btn> >
</q-td> <q-tooltip> Jukebox link </q-tooltip>
<q-td auto-width class="text-center">{{ props.row.name }}</q-td> </q-btn>
<q-td auto-width> {{ props.row.description }} </q-td>
<q-td class="text-center" auto-width>
<img
v-if="props.row.image"
:src="props.row.image"
style="height: 1.5em"
/>
</q-td>
<q-td class="text-center" auto-width>
{{ props.row.price }} {{ props.row.unit }}
</q-td> </q-td>
<q-td auto-width> <q-td auto-width>
<q-btn <q-btn
flat flat
dense dense
size="xs" size="xs"
@click="openUpdateDialog(props.row.id)" @click="deleteJukebox(props.row.id)"
icon="edit" icon="cancel"
color="light-blue" color="pink"
></q-btn> >
<q-btn <q-tooltip> Delete Jukebox </q-tooltip>
unelevated </q-btn>
dense </q-td>
size="xs" <q-td
icon="delete" v-for="col in props.cols"
color="negative" :key="col.name"
type="a" :props="props"
@click="deleteItem(props.row.id)" auto-width
target="_blank" >
></q-btn> <div v-if="col.name == 'id'"></div>
<div v-else>{{ col.value }}</div>
</q-td> </q-td>
</q-tr> </q-tr>
</template> </template>
@ -253,9 +250,13 @@
></q-select> ></q-select>
<div class="row q-mt-md"> <div class="row q-mt-md">
<div class="col-5"> <div class="col-5">
<q-btn color="green-7" @click="createJukebox" <q-btn
v-if="jukeboxDialog.data.sp_device != null && jukeboxDialog.data.sp_playlists != null"
color="green-7"
@click="createJukebox"
>Create Jukebox</q-btn >Create Jukebox</q-btn
> >
<q-btn v-else color="green-7" disable>Create Jukebox</q-btn>
</div> </div>
<div class="col-7"> <div class="col-7">
<q-btn <q-btn

View File

@ -19,7 +19,20 @@ 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():
jsonify([jukebox._asdict() for jukebox in await get_jukeboxs(g.wallet.id)]), try:
return (
jsonify(
[
{
**jukebox._asdict()
}
for jukebox in await get_jukeboxs(g.wallet.user)
]
),
HTTPStatus.OK,
)
except:
return "", HTTPStatus.NO_CONTENT
##################SPOTIFY AUTH##################### ##################SPOTIFY AUTH#####################
@ -61,6 +74,7 @@ async def api_check_credentials_check(sp_id):
@api_check_wallet_key("admin") @api_check_wallet_key("admin")
@api_validate_post_request( @api_validate_post_request(
schema={ schema={
"user": {"type": "string", "empty": False, "required": True},
"title": {"type": "string", "empty": False, "required": True}, "title": {"type": "string", "empty": False, "required": True},
"wallet": {"type": "string", "empty": False, "required": True}, "wallet": {"type": "string", "empty": False, "required": True},
"sp_user": {"type": "string", "empty": False, "required": True}, "sp_user": {"type": "string", "empty": False, "required": True},
@ -74,7 +88,8 @@ async def api_check_credentials_check(sp_id):
) )
async def api_create_update_jukebox(item_id=None): async def api_create_update_jukebox(item_id=None):
if item_id: if item_id:
jukebox = await update_jukebox(**g.data) jukebox = await update_jukebox(item_id, **g.data)
else:
jukebox = await create_jukebox(**g.data) jukebox = await create_jukebox(**g.data)
return jsonify(jukebox._asdict()), HTTPStatus.CREATED return jsonify(jukebox._asdict()), HTTPStatus.CREATED
@ -82,5 +97,18 @@ async def api_create_update_jukebox(item_id=None):
@jukebox_ext.route("/api/v1/jukebox/<juke_id>", methods=["DELETE"]) @jukebox_ext.route("/api/v1/jukebox/<juke_id>", methods=["DELETE"])
@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) await delete_jukebox(juke_id)
try:
return (
jsonify(
[
{
**jukebox._asdict()
}
for jukebox in await get_jukeboxs(g.wallet.user)
]
),
HTTPStatus.OK,
)
except:
return "", HTTPStatus.NO_CONTENT return "", HTTPStatus.NO_CONTENT