Monorepo for Aesthetic.Computer
aesthetic.computer
1#!/usr/bin/env node
2/**
3 * Update the 5 tool sketchbooks by @fifi
4 * - Fix description (no whistlegraph stamp)
5 * - Set inventory to 1
6 * - Ensure product_type is set
7 */
8
9import { readFileSync } from 'fs';
10import { fileURLToPath } from 'url';
11import { dirname, join } from 'path';
12
13const __dirname = dirname(fileURLToPath(import.meta.url));
14const envPath = join(__dirname, '../aesthetic-computer-vault/shop/.env');
15const envContent = readFileSync(envPath, 'utf-8');
16for (const line of envContent.split('\n')) {
17 if (line && !line.startsWith('#') && line.includes('=')) {
18 const [key, ...valueParts] = line.split('=');
19 process.env[key.trim()] = valueParts.join('=').trim();
20 }
21}
22
23const ACCESS_TOKEN = process.env.SHOPIFY_ADMIN_ACCESS_TOKEN;
24const BASE_URL = 'https://3pc8se-sj.myshopify.com/admin/api/2024-10';
25
26// The 5 tools product IDs
27const tools = [
28 { id: 10307732635829, title: 'I welcome new experiences every single day', code: '25.12.4.11.21' },
29 { id: 10307732668597, title: 'prompt.ac', code: '25.12.4.11.22' },
30 { id: 10307732701365, title: 'my songs', code: '25.12.4.11.23' },
31 { id: 10307732734133, title: 'pretty pictures', code: '25.12.4.11.24' },
32 { id: 10307732766901, title: 'play', code: '25.12.4.11.25' },
33];
34
35// Parse timecode to readable date/time string
36function formatDateFromCode(code) {
37 const [year, month, day, hour, minute] = code.split('.').map(Number);
38 const fullYear = 2000 + year;
39 const months = ['January', 'February', 'March', 'April', 'May', 'June',
40 'July', 'August', 'September', 'October', 'November', 'December'];
41 const monthName = months[month - 1];
42
43 // Ordinal suffix for day
44 const ordinal = (n) => {
45 if (n > 3 && n < 21) return 'th';
46 switch (n % 10) { case 1: return 'st'; case 2: return 'nd'; case 3: return 'rd'; default: return 'th'; }
47 };
48
49 // Format time as 12-hour with AM/PM
50 const period = hour >= 12 ? 'PM' : 'AM';
51 const hour12 = hour % 12 || 12;
52 const timeStr = `${hour12}:${minute.toString().padStart(2, '0')} ${period}`;
53
54 return `${monthName} ${day}${ordinal(day)}, ${fullYear} at ${timeStr}`;
55}
56
57// Generate description with time from code
58function makeDescription(code) {
59 const dateStr = formatDateFromCode(code);
60 return `<p>Sketchbook customized by <a href="https://aesthetic.computer/@fifi">@fifi</a></p>
61<p>Dated ${dateStr}</p>
62<p>This customized sketchbook ships from Los Angeles and includes a QR code.</p>
63<p>This Midori MD A5 Sketchbook comes with a clear plastic cover and has 48 pages.</p>`;
64}
65
66async function updateProduct(tool) {
67 const description = makeDescription(tool.code);
68
69 // Update product metadata
70 const res = await fetch(`${BASE_URL}/products/${tool.id}.json`, {
71 method: 'PUT',
72 headers: {
73 'X-Shopify-Access-Token': ACCESS_TOKEN,
74 'Content-Type': 'application/json',
75 },
76 body: JSON.stringify({
77 product: {
78 id: tool.id,
79 body_html: description,
80 product_type: 'Sketchbook',
81 tags: 'tool, sketchbook, fifi',
82 },
83 }),
84 });
85
86 const data = await res.json();
87 if (data.errors) {
88 console.log('❌ Error updating ' + tool.title + ':', JSON.stringify(data.errors));
89 return null;
90 }
91
92 console.log('✅ Updated: ' + tool.title);
93 console.log(' Product Type: ' + data.product.product_type);
94
95 // Get the variant ID to update inventory
96 const variantId = data.product.variants[0].id;
97 const inventoryItemId = data.product.variants[0].inventory_item_id;
98
99 console.log(' Variant ID: ' + variantId);
100 console.log(' Inventory Item ID: ' + inventoryItemId);
101
102 return { variantId, inventoryItemId };
103}
104
105async function setInventory(inventoryItemId, quantity = 1) {
106 // Try to set inventory level
107 const res = await fetch(`${BASE_URL}/inventory_levels/set.json`, {
108 method: 'POST',
109 headers: {
110 'X-Shopify-Access-Token': ACCESS_TOKEN,
111 'Content-Type': 'application/json',
112 },
113 body: JSON.stringify({
114 inventory_item_id: inventoryItemId,
115 available: quantity,
116 // We'll need the location_id - let's try without it first
117 }),
118 });
119
120 const data = await res.json();
121 if (data.errors) {
122 console.log(' ⚠️ Inventory update needs location_id:', JSON.stringify(data.errors));
123 return false;
124 }
125
126 console.log(' ✅ Inventory set to ' + quantity);
127 return true;
128}
129
130async function main() {
131 console.log('🛠️ Updating 5 tool sketchbooks by @fifi...\n');
132
133 for (const tool of tools) {
134 const result = await updateProduct(tool);
135 if (result) {
136 await setInventory(result.inventoryItemId, 1);
137 }
138 console.log('');
139 await new Promise(r => setTimeout(r, 500)); // Rate limit
140 }
141
142 console.log('✨ Product updates complete!');
143 console.log('\n⚠️ Note: To set inventory, you may need to:');
144 console.log(' 1. Add "read_locations" scope to the custom app');
145 console.log(' 2. Or set inventory manually in Shopify Admin');
146}
147
148main();