diff --git a/juke-wizard/Sources/JukeWizard/JukeCloud.swift b/juke-wizard/Sources/JukeWizard/JukeCloud.swift index a22e631e9..77355a569 100644 --- a/juke-wizard/Sources/JukeWizard/JukeCloud.swift +++ b/juke-wizard/Sources/JukeWizard/JukeCloud.swift @@ -19,6 +19,7 @@ private struct JukeCloudPreparedUpload: Codable { let track: JukeCloudTrack } private struct JukeCloudDownload: Codable { let url: URL } +private struct JukeCloudPublished: Codable { let published: Bool } private struct JukeCloudError: Codable { let error: String } enum JukeCloudClientError: LocalizedError { @@ -96,6 +97,11 @@ final class JukeCloudClient { throw JukeCloudClientError.response( "Upload failed (\(status))\(detail.map { ": \($0)" } ?? ".")") } + let publishRequest = try request("POST", body: [ + "action": "publish", "key": prepared.track.key, + ]) + let (publishData, publishResponse) = try await session.data(for: publishRequest) + _ = try decode(JukeCloudPublished.self, data: publishData, response: publishResponse) return prepared.track } diff --git a/juke-wizard/bin/juke-cloud.mjs b/juke-wizard/bin/juke-cloud.mjs index e573ec302..5194b6669 100644 --- a/juke-wizard/bin/juke-cloud.mjs +++ b/juke-wizard/bin/juke-cloud.mjs @@ -16,6 +16,7 @@ function usage() { jukewizard cloud list [--json] jukewizard cloud push [...] jukewizard cloud pull [destination] + jukewizard cloud publish jukewizard cloud remove jukewizard cloud url `); } @@ -86,6 +87,7 @@ async function push(path) { bytes: stat.size, }); await uploadFile(prepared.uploadURL, absolute, prepared.headers, stat.size); + await api("POST", { action: "publish", key: prepared.track.key }); return prepared.track; } @@ -146,6 +148,12 @@ async function main() { console.log(`removed ${args[0]}`); return; } + if (command === "publish") { + if (!args[0]) throw new Error("Provide the cloud key shown by `jukewizard cloud list`."); + const published = await api("POST", { action: "publish", key: args[0] }); + console.log(published.command); + return; + } usage(); if (command && command !== "help" && command !== "--help") process.exitCode = 2; } diff --git a/system/netlify/functions/juke-cloud.mjs b/system/netlify/functions/juke-cloud.mjs index c6cea546e..9bdf74074 100644 --- a/system/netlify/functions/juke-cloud.mjs +++ b/system/netlify/functions/juke-cloud.mjs @@ -5,6 +5,7 @@ import { DeleteObjectCommand, GetObjectCommand, ListObjectsV2Command, + PutObjectAclCommand, PutObjectCommand, S3Client, } from "@aws-sdk/client-s3"; @@ -154,6 +155,21 @@ export async function handler(event) { return respond(200, { deleted: true, key: input.key }); } + if (input.action === "publish") { + if (!ownsKey(user.sub, input.key)) return respond(403, { error: "Track is outside your cloud library." }); + await client().send(new PutObjectAclCommand({ + Bucket: bucket, + Key: input.key, + ACL: "public-read", + })); + return respond(200, { + published: true, + key: input.key, + url: publicURL(input.key), + command: `play ${publicURL(input.key)}`, + }); + } + if (input.action !== "upload") return respond(400, { error: "Unknown action." }); const filename = safeTrackName(input.filename); const contentType = audioType(filename); @@ -168,7 +184,6 @@ export async function handler(event) { Key: key, ContentType: contentType, ContentDisposition: "inline", - ACL: "public-read", }), { expiresIn: 15 * 60 }); const track = trackFromObject({ Key: key, Size: bytes, LastModified: new Date() }); return respond(200, {