mirror of
https://github.com/lnbits/lnbits.git
synced 2025-06-28 09:40:59 +02:00
Table working
This commit is contained in:
parent
38c2270abf
commit
245a819f19
@ -6,6 +6,7 @@ from lnbits.helpers import urlsafe_short_hash
|
||||
|
||||
|
||||
async def create_jukebox(
|
||||
user: str,
|
||||
wallet: str,
|
||||
title: str,
|
||||
price: int,
|
||||
@ -19,11 +20,12 @@ async def create_jukebox(
|
||||
juke_id = urlsafe_short_hash()
|
||||
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)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO jukebox (id, user, title, wallet, sp_user, sp_secret, sp_access_token, sp_refresh_token, sp_device, sp_playlists, price)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
juke_id,
|
||||
user,
|
||||
title,
|
||||
wallet,
|
||||
sp_user,
|
||||
@ -40,12 +42,12 @@ async def create_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()])
|
||||
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
|
||||
|
||||
|
||||
@ -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,))
|
||||
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]:
|
||||
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):
|
||||
async def delete_jukebox(id: str):
|
||||
await db.execute(
|
||||
"""
|
||||
DELETE FROM jukebox WHERE id = ?
|
||||
""",
|
||||
(Jukebox, item_id),
|
||||
(id),
|
||||
)
|
||||
|
@ -6,6 +6,7 @@ async def m001_initial(db):
|
||||
"""
|
||||
CREATE TABLE jukebox (
|
||||
id TEXT PRIMARY KEY,
|
||||
user TEXT,
|
||||
title TEXT,
|
||||
wallet TEXT,
|
||||
sp_user TEXT NOT NULL,
|
||||
|
@ -9,6 +9,7 @@ from sqlite3 import Row
|
||||
|
||||
class Jukebox(NamedTuple):
|
||||
id: str
|
||||
user: str
|
||||
title: str
|
||||
wallet: str
|
||||
sp_user: str
|
||||
|
@ -2,20 +2,63 @@
|
||||
|
||||
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({
|
||||
el: '#vue',
|
||||
mixins: [windowMixin],
|
||||
data() {
|
||||
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,
|
||||
tokenFetched: true,
|
||||
devices: [],
|
||||
filter: '',
|
||||
jukebox: {},
|
||||
playlists: [],
|
||||
JukeboxLinks: [],
|
||||
step: 1,
|
||||
locationcbPath: "",
|
||||
locationcb: "",
|
||||
@ -27,20 +70,42 @@ new Vue({
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
printItems() {
|
||||
return this.jukebox.items.filter(({enabled}) => enabled)
|
||||
}
|
||||
|
||||
},
|
||||
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() {
|
||||
this.jukeboxDialog.data = {}
|
||||
this.jukeboxDialog.show = false
|
||||
this.step = 1
|
||||
},
|
||||
submitSpotify() {
|
||||
|
||||
self = this
|
||||
console.log(self.jukeboxDialog.data)
|
||||
self.jukeboxDialog.data.user = self.g.user.id
|
||||
self.requestAuthorization()
|
||||
this.$q.notify({
|
||||
spinner: true,
|
||||
@ -56,7 +121,6 @@ new Vue({
|
||||
.then(response => {
|
||||
if(response.data){
|
||||
var timerId = setInterval(function(){
|
||||
console.log(response.data)
|
||||
if(!self.jukeboxDialog.data.sp_user){
|
||||
clearInterval(timerId);
|
||||
}
|
||||
@ -68,15 +132,7 @@ new Vue({
|
||||
self.jukeboxDialog.data.sp_access_token = response.data.sp_access_token
|
||||
self.step = 3
|
||||
self.fetchAccessToken()
|
||||
|
||||
clearInterval(timerId)
|
||||
|
||||
// self.refreshPlaylists(response.data.sp_token)
|
||||
// self.$q.notify({
|
||||
// message: 'Success! App is now linked!',
|
||||
// timeout: 3000
|
||||
// })
|
||||
//set devices, playlists
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
@ -97,7 +153,7 @@ new Vue({
|
||||
url += '&redirect_uri=' + encodeURI(self.locationcbPath) + self.jukeboxDialog.data.sp_user
|
||||
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'
|
||||
console.log(url)
|
||||
|
||||
window.open(url)
|
||||
},
|
||||
openNewDialog() {
|
||||
@ -111,19 +167,16 @@ new Vue({
|
||||
},
|
||||
createJukebox(){
|
||||
self = this
|
||||
|
||||
this.jukeboxDialog.data.sp_playlists = this.jukeboxDialog.data.sp_playlists.join()
|
||||
LNbits.api.request(
|
||||
'PUT',
|
||||
'/jukebox/api/v1/jukebox/' + this.jukeboxDialog.data.sp_id,
|
||||
self.g.user.wallets[0].adminkey,
|
||||
self.jukeboxDialog.data
|
||||
)
|
||||
.then(response => {
|
||||
console.log(response.data)
|
||||
|
||||
})
|
||||
.catch(err => {
|
||||
LNbits.utils.notifyApiError(err)
|
||||
.then(function (response) {
|
||||
self.JukeboxLinks.push(mapJukebox(response.data))
|
||||
self.jukeboxDialog.show = false
|
||||
})
|
||||
},
|
||||
|
||||
@ -156,12 +209,11 @@ new Vue({
|
||||
xhr.send(body)
|
||||
xhr.onload = function() {
|
||||
let responseObj = JSON.parse(xhr.response)
|
||||
console.log(responseObj.devices[0])
|
||||
self.devices = []
|
||||
var i;
|
||||
for (i = 0; i < responseObj.devices.length; i++) {
|
||||
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('Authorization', 'Basic ' + btoa(this.jukeboxDialog.data.sp_user + ":" + this.jukeboxDialog.data.sp_secret))
|
||||
xhr.send(body)
|
||||
console.log(('Authorization', 'Basic ' + btoa(this.jukeboxDialog.data.sp_user + ":" + this.jukeboxDialog.data.sp_secret)))
|
||||
xhr.onload = function() {
|
||||
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_refresh_token = responseObj.refresh_token
|
||||
console.log(self.jukeboxDialog.data)
|
||||
self.refreshPlaylists()
|
||||
self.refreshDevices()
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
|
||||
var getJukeboxes = this.getJukeboxes
|
||||
getJukeboxes()
|
||||
this.selectedWallet = this.g.user.wallets[0]
|
||||
this.locationcbPath = String([
|
||||
window.location.protocol,
|
||||
@ -212,7 +263,6 @@ new Vue({
|
||||
window.location.host,
|
||||
'/jukebox/api/v1/jukebox/spotify/cb/'
|
||||
].join(''))
|
||||
console.log(this.locationcbPath)
|
||||
this.locationcb = this.locationcbPath
|
||||
}
|
||||
})
|
||||
|
@ -11,30 +11,36 @@
|
||||
@click="openNewDialog()"
|
||||
>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 %}
|
||||
|
||||
<q-table
|
||||
dense
|
||||
flat
|
||||
selection="multiple"
|
||||
:data="jukebox.items"
|
||||
dense
|
||||
:data="JukeboxLinks"
|
||||
row-key="id"
|
||||
no-data-label="No items for sale yet"
|
||||
:pagination="{rowsPerPage: 0}"
|
||||
:binary-state-sort="true"
|
||||
:columns="JukeboxTable.columns"
|
||||
:pagination.sync="JukeboxTable.pagination"
|
||||
:filter="filter"
|
||||
>
|
||||
<template v-slot:header="props">
|
||||
<q-tr :props="props">
|
||||
<q-th auto-width></q-th>
|
||||
<q-th auto-width>Name</q-th>
|
||||
<q-th auto-width>Description</q-th>
|
||||
<q-th auto-width>Image</q-th>
|
||||
<q-th auto-width>Price</q-th>
|
||||
<q-th auto-width></q-th>
|
||||
|
||||
<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-tr>
|
||||
</template>
|
||||
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props">
|
||||
<q-td auto-width>
|
||||
@ -42,44 +48,35 @@
|
||||
unelevated
|
||||
dense
|
||||
size="xs"
|
||||
:icon="props.row.enabled ? 'done' : 'block'"
|
||||
:color="props.row.enabled ? 'green' : ($q.dark.isActive ? 'grey-7' : 'grey-5')"
|
||||
icon="link"
|
||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
||||
type="a"
|
||||
@click="toggleItem(props.row.id)"
|
||||
:href="props.row.displayUrl"
|
||||
target="_blank"
|
||||
></q-btn>
|
||||
</q-td>
|
||||
<q-td auto-width class="text-center">{{ props.row.name }}</q-td>
|
||||
<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-tooltip> Jukebox link </q-tooltip>
|
||||
</q-btn>
|
||||
</q-td>
|
||||
<q-td auto-width>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
size="xs"
|
||||
@click="openUpdateDialog(props.row.id)"
|
||||
icon="edit"
|
||||
color="light-blue"
|
||||
></q-btn>
|
||||
<q-btn
|
||||
unelevated
|
||||
dense
|
||||
size="xs"
|
||||
icon="delete"
|
||||
color="negative"
|
||||
type="a"
|
||||
@click="deleteItem(props.row.id)"
|
||||
target="_blank"
|
||||
></q-btn>
|
||||
@click="deleteJukebox(props.row.id)"
|
||||
icon="cancel"
|
||||
color="pink"
|
||||
>
|
||||
<q-tooltip> Delete Jukebox </q-tooltip>
|
||||
</q-btn>
|
||||
</q-td>
|
||||
<q-td
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
:props="props"
|
||||
auto-width
|
||||
>
|
||||
<div v-if="col.name == 'id'"></div>
|
||||
<div v-else>{{ col.value }}</div>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
@ -253,9 +250,13 @@
|
||||
></q-select>
|
||||
<div class="row q-mt-md">
|
||||
<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
|
||||
>
|
||||
<q-btn v-else color="green-7" disable>Create Jukebox</q-btn>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<q-btn
|
||||
|
@ -19,7 +19,20 @@ from .models import Jukebox
|
||||
@jukebox_ext.route("/api/v1/jukebox", methods=["GET"])
|
||||
@api_check_wallet_key("invoice")
|
||||
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#####################
|
||||
@ -61,6 +74,7 @@ async def api_check_credentials_check(sp_id):
|
||||
@api_check_wallet_key("admin")
|
||||
@api_validate_post_request(
|
||||
schema={
|
||||
"user": {"type": "string", "empty": False, "required": True},
|
||||
"title": {"type": "string", "empty": False, "required": True},
|
||||
"wallet": {"type": "string", "empty": False, "required": True},
|
||||
"sp_user": {"type": "string", "empty": False, "required": True},
|
||||
@ -74,13 +88,27 @@ async def api_check_credentials_check(sp_id):
|
||||
)
|
||||
async def api_create_update_jukebox(item_id=None):
|
||||
if item_id:
|
||||
jukebox = await update_jukebox(**g.data)
|
||||
jukebox = await create_jukebox(**g.data)
|
||||
jukebox = await update_jukebox(item_id, **g.data)
|
||||
else:
|
||||
jukebox = await create_jukebox(**g.data)
|
||||
return jsonify(jukebox._asdict()), HTTPStatus.CREATED
|
||||
|
||||
|
||||
@jukebox_ext.route("/api/v1/jukebox/<juke_id>", methods=["DELETE"])
|
||||
@api_check_wallet_key("admin")
|
||||
async def api_delete_item(juke_id):
|
||||
shop = await delete_jukebox(juke_id)
|
||||
return "", HTTPStatus.NO_CONTENT
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user