Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b0c7c05ca | ||
|
|
a97560c1c7 | ||
|
|
00cb224fb3 | ||
|
|
6c3cac7381 | ||
|
|
b91f877045 | ||
|
|
b86a4216cf | ||
|
|
57fefdc9e2 | ||
|
|
358958c127 | ||
|
|
c205ae81e2 | ||
|
|
0cec6414ae | ||
|
|
43173a833d |
No files matched your search
@@ -1,7 +1,11 @@
|
|||||||
# TODO
|
# TODO
|
||||||
contact options on landing page
|
push offers
|
||||||
|
|
||||||
move want add to rpc
|
add last synced time to want
|
||||||
|
|
||||||
|
go over and reorganise event logic
|
||||||
|
|
||||||
|
contact options on landing page
|
||||||
|
|
||||||
skip unofficial release
|
skip unofficial release
|
||||||
|
|
||||||
@@ -9,10 +13,3 @@ status stepper
|
|||||||
|
|
||||||
queue up wantlist adds to gracefully handle rate limiting
|
queue up wantlist adds to gracefully handle rate limiting
|
||||||
|
|
||||||
push offers
|
|
||||||
|
|
||||||
listen / notify
|
|
||||||
on offers
|
|
||||||
notify admins
|
|
||||||
|
|
||||||
|
|
||||||
+2
-2
@@ -26,10 +26,10 @@ async function start() {
|
|||||||
discogs,
|
discogs,
|
||||||
api,
|
api,
|
||||||
events,
|
events,
|
||||||
|
notify,
|
||||||
} = createServices(log);
|
} = createServices(log);
|
||||||
|
|
||||||
// events.emit(Events.Sync);
|
await notify.listen();
|
||||||
// events.emit(Events.ListingsUpdated);
|
|
||||||
|
|
||||||
api.listen();
|
api.listen();
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+336
-4
@@ -23,7 +23,8 @@
|
|||||||
"koa-websocket": "^7.0.0",
|
"koa-websocket": "^7.0.0",
|
||||||
"koa-ws": "^1.0.1",
|
"koa-ws": "^1.0.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"passport-local": "^1.0.0"
|
"passport-local": "^1.0.0",
|
||||||
|
"pg-listen": "^1.7.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
@@ -697,6 +698,15 @@
|
|||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer-writer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bufferutil": {
|
"node_modules/bufferutil": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-1.1.0.tgz",
|
||||||
@@ -2892,6 +2902,12 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/packet-reader": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"node_modules/parse5": {
|
"node_modules/parse5": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz",
|
||||||
@@ -2991,6 +3007,129 @@
|
|||||||
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
|
||||||
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
|
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/pg": {
|
||||||
|
"version": "8.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz",
|
||||||
|
"integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-writer": "2.0.0",
|
||||||
|
"packet-reader": "1.0.0",
|
||||||
|
"pg-connection-string": "^2.5.0",
|
||||||
|
"pg-pool": "^3.5.2",
|
||||||
|
"pg-protocol": "^1.5.0",
|
||||||
|
"pg-types": "^2.1.0",
|
||||||
|
"pgpass": "1.x"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg-native": ">=3.0.1"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"pg-native": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-connection-string": {
|
||||||
|
"version": "2.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
|
||||||
|
"integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/pg-format": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-format/-/pg-format-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-YyKEF78pEA6wwTAqOUaHIN/rWpfzzIuMh9KdAhc3rSLQ/7zkRFcCgYBAEGatDstLyZw4g0s9SNICmaTGnBVeyw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-int8": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-listen": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-listen/-/pg-listen-1.7.0.tgz",
|
||||||
|
"integrity": "sha512-MKDwKLm4ryhy7iq1yw1K1MvUzBdTkaT16HZToddX9QaT8XSdt3Kins5mYH6DLECGFzFWG09VdXvWOIYogjXrsg==",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"pg-format": "^1.0.4",
|
||||||
|
"typed-emitter": "^0.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg": "7.x || 8.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-listen/node_modules/debug": {
|
||||||
|
"version": "4.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "2.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-listen/node_modules/ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
|
},
|
||||||
|
"node_modules/pg-pool": {
|
||||||
|
"version": "3.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz",
|
||||||
|
"integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==",
|
||||||
|
"peer": true,
|
||||||
|
"peerDependencies": {
|
||||||
|
"pg": ">=8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pg-protocol": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/pg-types": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"pg-int8": "1.0.1",
|
||||||
|
"postgres-array": "~2.0.0",
|
||||||
|
"postgres-bytea": "~1.0.0",
|
||||||
|
"postgres-date": "~1.0.4",
|
||||||
|
"postgres-interval": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/pgpass": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"split2": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
@@ -3003,6 +3142,45 @@
|
|||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postgres-array": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-bytea": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-date": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postgres-interval": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||||
|
"peer": true,
|
||||||
|
"dependencies": {
|
||||||
|
"xtend": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prisma": {
|
"node_modules/prisma": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.5.0.tgz",
|
||||||
@@ -3234,6 +3412,15 @@
|
|||||||
"source-map": "^0.6.0"
|
"source-map": "^0.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/split2": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==",
|
||||||
|
"peer": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/statuses": {
|
"node_modules/statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
@@ -3483,6 +3670,11 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typed-emitter": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-Tfay0l6gJMP5rkil8CzGbLthukn+9BN/VXWcABVFPjOoelJ+koW8BuPZYk+h/L+lEeIp1fSzVRiWRPIjKVjPdg=="
|
||||||
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "4.7.4",
|
"version": "4.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
|
||||||
@@ -3636,7 +3828,6 @@
|
|||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4"
|
"node": ">=0.4"
|
||||||
}
|
}
|
||||||
@@ -4276,6 +4467,12 @@
|
|||||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"buffer-writer": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"bufferutil": {
|
"bufferutil": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-1.1.0.tgz",
|
||||||
@@ -5891,6 +6088,12 @@
|
|||||||
"p-limit": "^3.0.2"
|
"p-limit": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packet-reader": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"parse5": {
|
"parse5": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz",
|
||||||
@@ -5962,12 +6165,131 @@
|
|||||||
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
|
||||||
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
|
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
|
||||||
},
|
},
|
||||||
|
"pg": {
|
||||||
|
"version": "8.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz",
|
||||||
|
"integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==",
|
||||||
|
"peer": true,
|
||||||
|
"requires": {
|
||||||
|
"buffer-writer": "2.0.0",
|
||||||
|
"packet-reader": "1.0.0",
|
||||||
|
"pg-connection-string": "^2.5.0",
|
||||||
|
"pg-pool": "^3.5.2",
|
||||||
|
"pg-protocol": "^1.5.0",
|
||||||
|
"pg-types": "^2.1.0",
|
||||||
|
"pgpass": "1.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pg-connection-string": {
|
||||||
|
"version": "2.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
|
||||||
|
"integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"pg-format": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-format/-/pg-format-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-YyKEF78pEA6wwTAqOUaHIN/rWpfzzIuMh9KdAhc3rSLQ/7zkRFcCgYBAEGatDstLyZw4g0s9SNICmaTGnBVeyw=="
|
||||||
|
},
|
||||||
|
"pg-int8": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"pg-listen": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-listen/-/pg-listen-1.7.0.tgz",
|
||||||
|
"integrity": "sha512-MKDwKLm4ryhy7iq1yw1K1MvUzBdTkaT16HZToddX9QaT8XSdt3Kins5mYH6DLECGFzFWG09VdXvWOIYogjXrsg==",
|
||||||
|
"requires": {
|
||||||
|
"debug": "^4.1.1",
|
||||||
|
"pg-format": "^1.0.4",
|
||||||
|
"typed-emitter": "^0.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"debug": {
|
||||||
|
"version": "4.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
|
"requires": {
|
||||||
|
"ms": "2.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pg-pool": {
|
||||||
|
"version": "3.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz",
|
||||||
|
"integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==",
|
||||||
|
"peer": true,
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
|
"pg-protocol": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"pg-types": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||||
|
"peer": true,
|
||||||
|
"requires": {
|
||||||
|
"pg-int8": "1.0.1",
|
||||||
|
"postgres-array": "~2.0.0",
|
||||||
|
"postgres-bytea": "~1.0.0",
|
||||||
|
"postgres-date": "~1.0.4",
|
||||||
|
"postgres-interval": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pgpass": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||||
|
"peer": true,
|
||||||
|
"requires": {
|
||||||
|
"split2": "^4.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"picomatch": {
|
"picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"postgres-array": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"postgres-bytea": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"postgres-date": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"postgres-interval": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||||
|
"peer": true,
|
||||||
|
"requires": {
|
||||||
|
"xtend": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"prisma": {
|
"prisma": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/prisma/-/prisma-4.5.0.tgz",
|
||||||
@@ -6138,6 +6460,12 @@
|
|||||||
"source-map": "^0.6.0"
|
"source-map": "^0.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"split2": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
"statuses": {
|
"statuses": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
@@ -6304,6 +6632,11 @@
|
|||||||
"mime-types": "~2.1.24"
|
"mime-types": "~2.1.24"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"typed-emitter": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-Tfay0l6gJMP5rkil8CzGbLthukn+9BN/VXWcABVFPjOoelJ+koW8BuPZYk+h/L+lEeIp1fSzVRiWRPIjKVjPdg=="
|
||||||
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "4.7.4",
|
"version": "4.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
|
||||||
@@ -6427,8 +6760,7 @@
|
|||||||
"xtend": {
|
"xtend": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"y18n": {
|
"y18n": {
|
||||||
"version": "5.0.8",
|
"version": "5.0.8",
|
||||||
|
|||||||
+2
-1
@@ -41,7 +41,8 @@
|
|||||||
"koa-websocket": "^7.0.0",
|
"koa-websocket": "^7.0.0",
|
||||||
"koa-ws": "^1.0.1",
|
"koa-ws": "^1.0.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"passport-local": "^1.0.0"
|
"passport-local": "^1.0.0",
|
||||||
|
"pg-listen": "^1.7.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"koa-passport": {
|
"koa-passport": {
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "listings" (
|
||||||
|
"id" BIGINT NOT NULL,
|
||||||
|
"seller" BIGINT NOT NULL,
|
||||||
|
"data" JSONB NOT NULL,
|
||||||
|
"wantId" UUID NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "listings_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "users" (
|
||||||
|
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"email" TEXT NOT NULL,
|
||||||
|
"password" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "wants" (
|
||||||
|
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"release" BIGINT NOT NULL,
|
||||||
|
"data" JSONB NOT NULL,
|
||||||
|
"userId" UUID NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "wants_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "offers" (
|
||||||
|
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||||
|
"status" TEXT NOT NULL,
|
||||||
|
"condition" TEXT NOT NULL DEFAULT '',
|
||||||
|
"item" MONEY NOT NULL,
|
||||||
|
"shipping" MONEY NOT NULL,
|
||||||
|
"total" MONEY NOT NULL,
|
||||||
|
"listingId" BIGINT NOT NULL,
|
||||||
|
"userId" UUID NOT NULL,
|
||||||
|
"wantId" UUID NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "offers_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "listings_seller_idx" ON "listings"("seller");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "wants_release_idx" ON "wants"("release");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "wants_userId_release_key" ON "wants"("userId", "release");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "offers_listingId_key" ON "offers"("listingId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "listings" ADD CONSTRAINT "listings_wantId_fkey" FOREIGN KEY ("wantId") REFERENCES "wants"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "wants" ADD CONSTRAINT "wants_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "offers" ADD CONSTRAINT "offers_listingId_fkey" FOREIGN KEY ("listingId") REFERENCES "listings"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "offers" ADD CONSTRAINT "offers_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "offers" ADD CONSTRAINT "offers_wantId_fkey" FOREIGN KEY ("wantId") REFERENCES "wants"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
CREATE OR REPLACE FUNCTION notify_users() RETURNS TRIGGER AS $$
|
||||||
|
DECLARE
|
||||||
|
record RECORD;
|
||||||
|
id UUID;
|
||||||
|
payload JSON;
|
||||||
|
BEGIN
|
||||||
|
IF (TG_OP = 'DELETE') THEN
|
||||||
|
id = OLD.id;
|
||||||
|
ELSE
|
||||||
|
id = NEW.id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
payload = json_build_object(
|
||||||
|
'table', TG_TABLE_NAME,
|
||||||
|
'action', TG_OP,
|
||||||
|
'id', id
|
||||||
|
);
|
||||||
|
|
||||||
|
PERFORM pg_notify('users.events', payload::text);
|
||||||
|
|
||||||
|
RETURN NULL;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION notify_listings() RETURNS TRIGGER AS $$
|
||||||
|
DECLARE
|
||||||
|
record RECORD;
|
||||||
|
id int8;
|
||||||
|
payload JSON;
|
||||||
|
BEGIN
|
||||||
|
IF (TG_OP = 'DELETE') THEN
|
||||||
|
id = OLD.id;
|
||||||
|
ELSE
|
||||||
|
id = NEW.id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
payload = json_build_object(
|
||||||
|
'table', TG_TABLE_NAME,
|
||||||
|
'action', TG_OP,
|
||||||
|
'id', id
|
||||||
|
);
|
||||||
|
|
||||||
|
PERFORM pg_notify('listings.events', payload::text);
|
||||||
|
|
||||||
|
RETURN NULL;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION notify_offers() RETURNS TRIGGER AS $$
|
||||||
|
DECLARE
|
||||||
|
record RECORD;
|
||||||
|
id UUID;
|
||||||
|
payload JSON;
|
||||||
|
BEGIN
|
||||||
|
IF (TG_OP = 'DELETE') THEN
|
||||||
|
id = OLD.id;
|
||||||
|
ELSE
|
||||||
|
id = NEW.id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
payload = json_build_object(
|
||||||
|
'table', TG_TABLE_NAME,
|
||||||
|
'action', TG_OP,
|
||||||
|
'id', id
|
||||||
|
);
|
||||||
|
|
||||||
|
PERFORM pg_notify('offers.events', payload::text);
|
||||||
|
|
||||||
|
RETURN NULL;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION notify_wants() RETURNS TRIGGER AS $$
|
||||||
|
DECLARE
|
||||||
|
record RECORD;
|
||||||
|
id UUID;
|
||||||
|
payload JSON;
|
||||||
|
BEGIN
|
||||||
|
IF (TG_OP = 'DELETE') THEN
|
||||||
|
id = OLD.id;
|
||||||
|
ELSE
|
||||||
|
id = NEW.id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
payload = json_build_object(
|
||||||
|
'table', TG_TABLE_NAME,
|
||||||
|
'action', TG_OP,
|
||||||
|
'id', id
|
||||||
|
);
|
||||||
|
|
||||||
|
PERFORM pg_notify('wants.events', payload::text);
|
||||||
|
|
||||||
|
RETURN NULL;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE OR REPLACE TRIGGER notify_wants_event
|
||||||
|
AFTER INSERT OR UPDATE OR DELETE ON wants
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE notify_wants();
|
||||||
|
|
||||||
|
CREATE OR REPLACE TRIGGER notify_listings_event
|
||||||
|
AFTER INSERT OR UPDATE OR DELETE ON listings
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE notify_listings();
|
||||||
|
|
||||||
|
CREATE OR REPLACE TRIGGER notify_offers_event
|
||||||
|
AFTER INSERT OR UPDATE OR DELETE ON offers
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE notify_offers();
|
||||||
|
|
||||||
|
CREATE OR REPLACE TRIGGER notify_users_event
|
||||||
|
AFTER INSERT OR UPDATE OR DELETE ON users
|
||||||
|
FOR EACH ROW EXECUTE PROCEDURE notify_users();
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (i.e. Git)
|
||||||
|
provider = "postgresql"
|
||||||
@@ -11,6 +11,11 @@ datasource db {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Listing {
|
model Listing {
|
||||||
|
|
||||||
|
// example listingId 2148851036
|
||||||
|
// works fine as a JS number
|
||||||
|
// but JS numbers are cast to int4 for pg by prisma
|
||||||
|
// becomes -2146116260
|
||||||
id BigInt @id
|
id BigInt @id
|
||||||
seller BigInt
|
seller BigInt
|
||||||
data Json
|
data Json
|
||||||
|
|||||||
+2
-11
@@ -63,16 +63,6 @@ export default function createApi(log: Log, pg: Pg, discogs: Discogs, offers: Of
|
|||||||
ctx.status = 200;
|
ctx.status = 200;
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/want', async function wantPost(ctx, next) {
|
|
||||||
ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
|
|
||||||
const { releaseId } = ctx.request.body;
|
|
||||||
const user = await pg.userFindId(ctx.state.user.id);
|
|
||||||
const release = await discogs.release(releaseId);
|
|
||||||
const want = await pg.wantCreate(release, user);
|
|
||||||
events.emit(Events.WantsAdded, [want]);
|
|
||||||
ctx.response.body = [want];
|
|
||||||
});
|
|
||||||
|
|
||||||
router.post('/want/discogs', async function wantListPost(ctx, next) {
|
router.post('/want/discogs', async function wantListPost(ctx, next) {
|
||||||
ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
|
ctx.assert(ctx.isAuthenticated(), 401, 'log in required');
|
||||||
const { username } = ctx.request.body;
|
const { username } = ctx.request.body;
|
||||||
@@ -109,7 +99,6 @@ export default function createApi(log: Log, pg: Pg, discogs: Discogs, offers: Of
|
|||||||
const ws = ctx.websocket;
|
const ws = ctx.websocket;
|
||||||
const rpc = createRpc(log, pg, ws, discogs, offers, events, ctx.state.user as User | null);
|
const rpc = createRpc(log, pg, ws, discogs, offers, events, ctx.state.user as User | null);
|
||||||
|
|
||||||
if (ctx.state.user) events.emit(Events.Connection, { ws, id: ctx.state.user.id });
|
|
||||||
ws.on('message', async function incoming(message) {
|
ws.on('message', async function incoming(message) {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(message.toString());
|
const parsed = JSON.parse(message.toString());
|
||||||
@@ -127,7 +116,9 @@ export default function createApi(log: Log, pg: Pg, discogs: Discogs, offers: Of
|
|||||||
log.info('ws disconnected', { user: ctx.state.user });
|
log.info('ws disconnected', { user: ctx.state.user });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (ctx.state.user) events.emit(Events.Connection, { rpc, id: ctx.state.user.id });
|
||||||
await rpc.onConnection();
|
await rpc.onConnection();
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error(e);
|
log.error(e);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ export default function createDiscogs(log: Log, pg: Pg) {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const additions = results.filter(r => r.status === 'fulfilled').map(r => r.value);
|
const additions = results.filter(r => r.status === 'fulfilled').map(r => r.value);
|
||||||
|
|
||||||
log.info('want list additions', additions);
|
log.info('want list additions', { additions: additions.length });
|
||||||
|
|
||||||
return additions;
|
return additions;
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-6
@@ -2,22 +2,26 @@ import { EventEmitter } from 'node:events';
|
|||||||
import { setTimeout } from 'timers/promises';
|
import { setTimeout } from 'timers/promises';
|
||||||
|
|
||||||
import type Log from 'bunyan';
|
import type Log from 'bunyan';
|
||||||
import type { Want } from '@prisma/client';
|
import type { Want, Offer } from '@prisma/client';
|
||||||
|
|
||||||
import { Pg } from './pg';
|
import { Pg } from './pg';
|
||||||
|
import { Rpc } from './rpc';
|
||||||
import { wantDetails } from './utils';
|
import { wantDetails } from './utils';
|
||||||
import routerAuth from './routers/router.auth';
|
import routerAuth from './routers/router.auth';
|
||||||
|
|
||||||
export enum Events {
|
export enum Events {
|
||||||
WantsAdded = 'WantsAdded',
|
WantsAdded = 'WantsAdded',
|
||||||
Sync = 'Sync',
|
Sync = 'Sync',
|
||||||
ListingsUpdated = 'ListingsUpdated',
|
|
||||||
Connection = 'Connection',
|
Connection = 'Connection',
|
||||||
|
|
||||||
|
ListingsUpdated = 'ListingsUpdated',
|
||||||
|
OffersUpdated = 'OffersUpdated',
|
||||||
|
WantsUpdated = 'WantsUpdated',
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function createEvents(log: Log, pg: Pg, discogs, offers) {
|
export default function createEvents(log: Log, pg: Pg, discogs, offers) {
|
||||||
const events = new EventEmitter({ captureRejections: true });
|
const events = new EventEmitter({ captureRejections: true });
|
||||||
const connections = new Map();
|
const connections = new Map<UUID, Rpc>();
|
||||||
|
|
||||||
async function populateListings(want: Want) {
|
async function populateListings(want: Want) {
|
||||||
try {
|
try {
|
||||||
@@ -40,7 +44,7 @@ export default function createEvents(log: Log, pg: Pg, discogs, offers) {
|
|||||||
async function updateWantListings(wants: Want[]) {
|
async function updateWantListings(wants: Want[]) {
|
||||||
for (const want of wants) {
|
for (const want of wants) {
|
||||||
const listings = await populateListings(want);
|
const listings = await populateListings(want);
|
||||||
log.info({ listings: listings.length, ...wantDetails(want) }, 'listings added');
|
log.info({ listings: listings.length, ...wantDetails(want) }, 'listings found');
|
||||||
events.emit(Events.ListingsUpdated);
|
events.emit(Events.ListingsUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,11 +68,30 @@ export default function createEvents(log: Log, pg: Pg, discogs, offers) {
|
|||||||
events.on(Events.ListingsUpdated, async function listingsUpdated() {
|
events.on(Events.ListingsUpdated, async function listingsUpdated() {
|
||||||
const updatedOffers = await offers.generate()
|
const updatedOffers = await offers.generate()
|
||||||
log.debug({ offers: updatedOffers.length }, 'offers up to date');
|
log.debug({ offers: updatedOffers.length }, 'offers up to date');
|
||||||
|
|
||||||
|
for (const [id, rpc] of connections) {
|
||||||
|
rpc.pushUserOffers();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
events.on(Events.Connection, async function onConnection({ ws, id }) {
|
events.on(Events.Connection, async function onConnection({ rpc, id }) {
|
||||||
connections.set(id, ws);
|
log.debug('new rpc connection', { id, rpc });
|
||||||
|
connections.set(id, rpc);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
events.on(Events.OffersUpdated, async function onOffersUpdated(offer: Offer) {
|
||||||
|
const rpc = connections.get(offer.userId);
|
||||||
|
if (!rpc) return null;
|
||||||
|
log.debug({ event: Events.OffersUpdated, rpc, offer });
|
||||||
|
return rpc.pushUserOffers();
|
||||||
|
});
|
||||||
|
|
||||||
|
events.on(Events.WantsUpdated, async function onWantsUpdated(want: Want) {
|
||||||
|
const rpc = connections.get(want.userId);
|
||||||
|
if (!rpc) return null;
|
||||||
|
log.debug({ event: Events.WantsUpdated, rpc, want });
|
||||||
|
return rpc.pushUserWants();
|
||||||
|
});
|
||||||
|
|
||||||
return events;
|
return events;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import createSubscriber from 'pg-listen'
|
||||||
|
import type Log from 'bunyan';
|
||||||
|
|
||||||
|
import { Events } from './events';
|
||||||
|
import { Pg } from './pg';
|
||||||
|
|
||||||
|
export default function createNotify(log: Log, pg: Pg, events) {
|
||||||
|
const OFFERS_CHANNEL = 'offers.events';
|
||||||
|
const USERS_CHANNEL = 'users.events';
|
||||||
|
const LISTINGS_CHANNEL = 'listings.events';
|
||||||
|
const WANTS_CHANNEL = 'wants.events';
|
||||||
|
|
||||||
|
const subscriber = createSubscriber({ connectionString: process.env.DATABASE_URL });
|
||||||
|
|
||||||
|
// pg has a hard limit of 8000B for the notification payload
|
||||||
|
// so we only receive the ids of the updated record
|
||||||
|
// and go fetch the whole thing to figure out who to notify
|
||||||
|
|
||||||
|
async function handleOffers(payload) {
|
||||||
|
const { action, id } = payload;
|
||||||
|
log.debug({ channel: OFFERS_CHANNEL, action, id });
|
||||||
|
if (action === 'DELETE') return null;
|
||||||
|
|
||||||
|
// const offer = await pg.offerFindId(id);
|
||||||
|
// events.emit(Events.OffersUpdated, offer);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleUsers(payload) {
|
||||||
|
const { action, id } = payload;
|
||||||
|
log.debug({ channel: USERS_CHANNEL, action, id });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleListings(payload) {
|
||||||
|
const { action, id } = payload;
|
||||||
|
log.debug({ channel: LISTINGS_CHANNEL, action, id });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleWants(payload) {
|
||||||
|
const { action, id } = payload;
|
||||||
|
// const want = await pg.offerFindId(id);
|
||||||
|
log.debug({ channel: WANTS_CHANNEL, action, id });
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriber.notifications.on(OFFERS_CHANNEL, handleOffers);
|
||||||
|
subscriber.notifications.on(USERS_CHANNEL, handleUsers);
|
||||||
|
subscriber.notifications.on(LISTINGS_CHANNEL, handleListings);
|
||||||
|
subscriber.notifications.on(WANTS_CHANNEL, handleWants);
|
||||||
|
|
||||||
|
async function listen() {
|
||||||
|
await subscriber.connect();
|
||||||
|
await Promise.all([
|
||||||
|
subscriber.listenTo(OFFERS_CHANNEL),
|
||||||
|
subscriber.listenTo(USERS_CHANNEL),
|
||||||
|
subscriber.listenTo(LISTINGS_CHANNEL),
|
||||||
|
subscriber.listenTo(WANTS_CHANNEL),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { listen };
|
||||||
|
}
|
||||||
+17
-11
@@ -24,6 +24,7 @@ export default function createPg(log: Log, prisma: PrismaClient) {
|
|||||||
return prisma.want.findMany({
|
return prisma.want.findMany({
|
||||||
where: { userId: user.id },
|
where: { userId: user.id },
|
||||||
include: { _count: { select: { offers: true }} },
|
include: { _count: { select: { offers: true }} },
|
||||||
|
orderBy: { offers: { _count: 'desc' } }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +98,12 @@ export default function createPg(log: Log, prisma: PrismaClient) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function wantFindId(id: UUID) {
|
||||||
|
return prisma.want.findFirstOrThrow({
|
||||||
|
where: { id },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function wantList(user: User) {
|
async function wantList(user: User) {
|
||||||
return prisma.want.findMany({
|
return prisma.want.findMany({
|
||||||
where: { userId: user.id },
|
where: { userId: user.id },
|
||||||
@@ -131,8 +138,8 @@ export default function createPg(log: Log, prisma: PrismaClient) {
|
|||||||
|
|
||||||
const protectedOfferStatuses = [Status.Confirmed, Status.Complete];
|
const protectedOfferStatuses = [Status.Confirmed, Status.Complete];
|
||||||
|
|
||||||
// can't delete a listing with an open offer
|
// delete all the offers where we don't want to keep them
|
||||||
// so delete those offers first
|
// for historical reasons
|
||||||
const { count: offersRemoved } = await prisma.offer.deleteMany({
|
const { count: offersRemoved } = await prisma.offer.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
wantId: want.id,
|
wantId: want.id,
|
||||||
@@ -145,7 +152,7 @@ export default function createPg(log: Log, prisma: PrismaClient) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (offersRemoved > 0) log.info({ offersRemoved }, 'open offers removed');
|
if (offersRemoved > 0) log.info({ offersRemoved }, 'pending offers removed');
|
||||||
|
|
||||||
// now we have to filter listingIds with confirmed offers
|
// now we have to filter listingIds with confirmed offers
|
||||||
// and get their listingIds to filter out
|
// and get their listingIds to filter out
|
||||||
@@ -154,23 +161,22 @@ export default function createPg(log: Log, prisma: PrismaClient) {
|
|||||||
wantId: want.id,
|
wantId: want.id,
|
||||||
status: {
|
status: {
|
||||||
in: protectedOfferStatuses,
|
in: protectedOfferStatuses,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
listingId: true,
|
listingId: true,
|
||||||
}
|
},
|
||||||
})).map(r => r.listingId);
|
})).map(r => r.listingId);
|
||||||
const removalIds = _.without(currentListingIds, ...confirmedListingIds);
|
const removalIds = currentListingIds.filter(id => confirmedListingIds.includes(id));
|
||||||
|
|
||||||
|
if (removalIds.length) log.info({ removalIds });
|
||||||
|
|
||||||
// finally we delete listings that aren't related
|
// finally we delete listings that aren't related
|
||||||
// to a historical offer
|
// to a historical offer
|
||||||
const { count } = await prisma.listing.deleteMany({
|
const { count } = await prisma.listing.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
wantId: want.id,
|
id: { in: removalIds },
|
||||||
id: {
|
},
|
||||||
notIn: currentListingIds,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
log.info({ count }, 'second stage open offers removed');
|
log.info({ count }, 'second stage open offers removed');
|
||||||
|
|||||||
+102
-77
@@ -12,6 +12,81 @@ import { Status } from './offers';
|
|||||||
import { isAdmin } from './utils';
|
import { isAdmin } from './utils';
|
||||||
import { Events } from './events';
|
import { Events } from './events';
|
||||||
|
|
||||||
|
export type Rpc = ReturnType<typeof createRpc>;
|
||||||
|
|
||||||
|
export enum RpcMessageType {
|
||||||
|
Ping = 'Ping',
|
||||||
|
Pong = 'Pong',
|
||||||
|
|
||||||
|
// points out user is anon
|
||||||
|
WhoDis = 'WhoDis',
|
||||||
|
|
||||||
|
Bundles = 'Bundles',
|
||||||
|
Offers = 'Offers',
|
||||||
|
List = 'List',
|
||||||
|
Wants = 'Wants',
|
||||||
|
User = 'User',
|
||||||
|
|
||||||
|
Error = 'Error',
|
||||||
|
|
||||||
|
OfferSamples = 'OfferSamples',
|
||||||
|
OfferStatusSet = 'OfferStatusSet',
|
||||||
|
|
||||||
|
AcpOfferStatusSet = 'AcpOfferStatusSet',
|
||||||
|
AcpOffers = 'AcpOffers',
|
||||||
|
AcpListingsSync = 'AcpListingsSync',
|
||||||
|
|
||||||
|
WantRm = 'WantRm',
|
||||||
|
WantAdd = 'WantAdd',
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrorMessage = [RpcMessageType.Error, string];
|
||||||
|
type PongMessage = [RpcMessageType.Pong, []];
|
||||||
|
type PingMessage = [RpcMessageType.Ping, []];
|
||||||
|
type WhoDisMessage = [RpcMessageType.WhoDis, []];
|
||||||
|
type UserMessage = [RpcMessageType.User, User];
|
||||||
|
type WantsMessage = [RpcMessageType.Wants, Want[]];
|
||||||
|
type OffersMessage = [RpcMessageType.Offers, Offer[]];
|
||||||
|
type OfferStatusSetMessage = [RpcMessageType.OfferStatusSet, Offer[]];
|
||||||
|
type OfferSamplesMessage = [RpcMessageType.OfferSamples, Offer[]];
|
||||||
|
|
||||||
|
type AcpOfferStatusSetMessage = [RpcMessageType.AcpOfferStatusSet, Offer[]];
|
||||||
|
type AcpOffersMessage = [RpcMessageType.AcpOffers, Offer[]];
|
||||||
|
type AcpListingsSyncMessage = [RpcMessageType.AcpListingsSync, []];
|
||||||
|
|
||||||
|
export type RpcMessage =
|
||||||
|
PingMessage |
|
||||||
|
PongMessage |
|
||||||
|
WhoDisMessage |
|
||||||
|
|
||||||
|
UserMessage |
|
||||||
|
|
||||||
|
WantsMessage |
|
||||||
|
|
||||||
|
OffersMessage |
|
||||||
|
OfferSamplesMessage |
|
||||||
|
OfferStatusSetMessage |
|
||||||
|
|
||||||
|
AcpOfferStatusSetMessage |
|
||||||
|
AcpOffersMessage |
|
||||||
|
AcpListingsSyncMessage |
|
||||||
|
|
||||||
|
ErrorMessage;
|
||||||
|
|
||||||
|
|
||||||
|
type OfferStatusSetParams = {
|
||||||
|
id: UUID,
|
||||||
|
status: Status,
|
||||||
|
};
|
||||||
|
|
||||||
|
type WantRmParams = {
|
||||||
|
id: UUID,
|
||||||
|
};
|
||||||
|
|
||||||
|
type WantAddParams = {
|
||||||
|
releaseId: number,
|
||||||
|
};
|
||||||
|
|
||||||
function checkMsgType(msgType: RpcMessageType): msgType is RpcMessageType {
|
function checkMsgType(msgType: RpcMessageType): msgType is RpcMessageType {
|
||||||
if (RpcMessageType[msgType]) {
|
if (RpcMessageType[msgType]) {
|
||||||
return true;
|
return true;
|
||||||
@@ -54,6 +129,18 @@ export default function createRpc(log: Log, pg: Pg, ws, discogs: Discogs, offers
|
|||||||
return [RpcMessageType.Wants, wants];
|
return [RpcMessageType.Wants, wants];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function wantAdd(params: WantAddParams): Promise<WantsMessage> {
|
||||||
|
const { releaseId } = params;
|
||||||
|
assert(releaseId, 'no id supplied');
|
||||||
|
|
||||||
|
const release = await discogs.release(releaseId);
|
||||||
|
const want = await pg.wantCreate(release, user);
|
||||||
|
events.emit(Events.WantsAdded, [want]);
|
||||||
|
|
||||||
|
const wants = await pg.userWants(user);
|
||||||
|
return [RpcMessageType.Wants, wants];
|
||||||
|
}
|
||||||
|
|
||||||
async function acpOfferStatusSet(params: OfferStatusSetParams): Promise<AcpOffersMessage> {
|
async function acpOfferStatusSet(params: OfferStatusSetParams): Promise<AcpOffersMessage> {
|
||||||
const { id, status } = params;
|
const { id, status } = params;
|
||||||
assert(isAdmin(user), 'not authorised');
|
assert(isAdmin(user), 'not authorised');
|
||||||
@@ -101,6 +188,8 @@ export default function createRpc(log: Log, pg: Pg, ws, discogs: Discogs, offers
|
|||||||
|
|
||||||
case RpcMessageType.WantRm:
|
case RpcMessageType.WantRm:
|
||||||
return wantRm(params);
|
return wantRm(params);
|
||||||
|
case RpcMessageType.WantAdd:
|
||||||
|
return wantAdd(params);
|
||||||
|
|
||||||
case RpcMessageType.AcpOfferStatusSet:
|
case RpcMessageType.AcpOfferStatusSet:
|
||||||
return acpOfferStatusSet(params);
|
return acpOfferStatusSet(params);
|
||||||
@@ -134,13 +223,8 @@ export default function createRpc(log: Log, pg: Pg, ws, discogs: Discogs, offers
|
|||||||
const userMsg: UserMessage = [RpcMessageType.User, user];
|
const userMsg: UserMessage = [RpcMessageType.User, user];
|
||||||
send(userMsg);
|
send(userMsg);
|
||||||
|
|
||||||
const userOffers = await offers.userOffers(user);
|
await pushUserOffers();
|
||||||
const offersMsg: OffersMessage = [RpcMessageType.Offers, userOffers];
|
await pushUserWants();
|
||||||
send(offersMsg);
|
|
||||||
|
|
||||||
const wants = await pg.userWants(user);
|
|
||||||
const wantsMsg: WantsMessage = [RpcMessageType.Wants, wants];
|
|
||||||
send(wantsMsg);
|
|
||||||
|
|
||||||
if (isAdmin(user)) return adminConnection();
|
if (isAdmin(user)) return adminConnection();
|
||||||
}
|
}
|
||||||
@@ -153,79 +237,20 @@ export default function createRpc(log: Log, pg: Pg, ws, discogs: Discogs, offers
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function pushUserOffers() {
|
||||||
|
send(await userOffers());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pushUserWants() {
|
||||||
|
send(await userWants());
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
receive,
|
receive,
|
||||||
send,
|
send,
|
||||||
onConnection,
|
onConnection,
|
||||||
|
|
||||||
|
pushUserOffers,
|
||||||
|
pushUserWants,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Rpc = ReturnType<typeof createRpc>;
|
|
||||||
|
|
||||||
export enum RpcMessageType {
|
|
||||||
Ping = 'Ping',
|
|
||||||
Pong = 'Pong',
|
|
||||||
|
|
||||||
// points out user is anon
|
|
||||||
WhoDis = 'WhoDis',
|
|
||||||
|
|
||||||
Bundles = 'Bundles',
|
|
||||||
Offers = 'Offers',
|
|
||||||
List = 'List',
|
|
||||||
Wants = 'Wants',
|
|
||||||
User = 'User',
|
|
||||||
|
|
||||||
Error = 'Error',
|
|
||||||
|
|
||||||
OfferSamples = 'OfferSamples',
|
|
||||||
OfferStatusSet = 'OfferStatusSet',
|
|
||||||
|
|
||||||
AcpOfferStatusSet = 'AcpOfferStatusSet',
|
|
||||||
AcpOffers = 'AcpOffers',
|
|
||||||
AcpListingsSync = 'AcpListingsSync',
|
|
||||||
|
|
||||||
WantRm = 'WantRm',
|
|
||||||
}
|
|
||||||
|
|
||||||
type ErrorMessage = [RpcMessageType.Error, string];
|
|
||||||
type PongMessage = [RpcMessageType.Pong, []];
|
|
||||||
type PingMessage = [RpcMessageType.Ping, []];
|
|
||||||
type WhoDisMessage = [RpcMessageType.WhoDis, []];
|
|
||||||
type UserMessage = [RpcMessageType.User, User];
|
|
||||||
type WantsMessage = [RpcMessageType.Wants, Want[]];
|
|
||||||
type OffersMessage = [RpcMessageType.Offers, Offer[]];
|
|
||||||
type OfferStatusSetMessage = [RpcMessageType.OfferStatusSet, Offer[]];
|
|
||||||
type OfferSamplesMessage = [RpcMessageType.OfferSamples, Offer[]];
|
|
||||||
|
|
||||||
type AcpOfferStatusSetMessage = [RpcMessageType.AcpOfferStatusSet, Offer[]];
|
|
||||||
type AcpOffersMessage = [RpcMessageType.AcpOffers, Offer[]];
|
|
||||||
type AcpListingsSyncMessage = [RpcMessageType.AcpListingsSync, []];
|
|
||||||
|
|
||||||
export type RpcMessage =
|
|
||||||
PingMessage |
|
|
||||||
PongMessage |
|
|
||||||
WhoDisMessage |
|
|
||||||
|
|
||||||
UserMessage |
|
|
||||||
|
|
||||||
WantsMessage |
|
|
||||||
|
|
||||||
OffersMessage |
|
|
||||||
OfferSamplesMessage |
|
|
||||||
OfferStatusSetMessage |
|
|
||||||
|
|
||||||
AcpOfferStatusSetMessage |
|
|
||||||
AcpOffersMessage |
|
|
||||||
AcpListingsSyncMessage |
|
|
||||||
|
|
||||||
ErrorMessage;
|
|
||||||
|
|
||||||
|
|
||||||
type OfferStatusSetParams = {
|
|
||||||
id: UUID,
|
|
||||||
status: Status,
|
|
||||||
};
|
|
||||||
|
|
||||||
type WantRmParams = {
|
|
||||||
id: UUID,
|
|
||||||
};
|
|
||||||
+4
-1
@@ -6,19 +6,22 @@ import createLog from './log';
|
|||||||
import createApi from './api';
|
import createApi from './api';
|
||||||
import createEvents from './events';
|
import createEvents from './events';
|
||||||
import createPg from './pg';
|
import createPg from './pg';
|
||||||
|
import createNotify from './notify';
|
||||||
|
|
||||||
export default function creates(log) {
|
export default function createServices(log) {
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
const pg = createPg(log, prisma);
|
const pg = createPg(log, prisma);
|
||||||
const discogs = createDiscogs(log, pg);
|
const discogs = createDiscogs(log, pg);
|
||||||
const offers = createOffers(log, pg);
|
const offers = createOffers(log, pg);
|
||||||
const events = createEvents(log, pg, discogs, offers);
|
const events = createEvents(log, pg, discogs, offers);
|
||||||
|
const notify = createNotify(log, pg, events);
|
||||||
const api = createApi(log, pg, discogs, offers, events);
|
const api = createApi(log, pg, discogs, offers, events);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
api,
|
api,
|
||||||
discogs,
|
discogs,
|
||||||
events,
|
events,
|
||||||
|
notify,
|
||||||
pg,
|
pg,
|
||||||
prisma,
|
prisma,
|
||||||
};
|
};
|
||||||
|
|||||||
+8
-6
@@ -9,7 +9,7 @@ import store, { wsState } from './store'
|
|||||||
|
|
||||||
import Nav from './components/nav';
|
import Nav from './components/nav';
|
||||||
|
|
||||||
import Offers from './routes/offers';
|
import Main from './routes/main';
|
||||||
import Acp from './routes/acp';
|
import Acp from './routes/acp';
|
||||||
import Login from './routes/login';
|
import Login from './routes/login';
|
||||||
import Search from './routes/search';
|
import Search from './routes/search';
|
||||||
@@ -18,7 +18,7 @@ import Info from './routes/info';
|
|||||||
|
|
||||||
if (window.location.host === 'matescrates.au') LogRocket.init('yh0dy3/mcrates');
|
if (window.location.host === 'matescrates.au') LogRocket.init('yh0dy3/mcrates');
|
||||||
|
|
||||||
const Main = () => {
|
const Mcrates = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
// i have no idea why this is needed,
|
// i have no idea why this is needed,
|
||||||
@@ -33,13 +33,15 @@ const Main = () => {
|
|||||||
const { ws } = store.getState();
|
const { ws } = store.getState();
|
||||||
if (!ws) return null;
|
if (!ws) return null;
|
||||||
if (e.url === '/acp') return ws.sendAcpOffers();
|
if (e.url === '/acp') return ws.sendAcpOffers();
|
||||||
|
if (e.url === '/') return ws.sendOffers();
|
||||||
|
if (e.url === '/list') return ws.sendWants();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main id="app">
|
<div id="mcrates">
|
||||||
<Nav />
|
<Nav />
|
||||||
<Router onChange={routeChange}>
|
<Router onChange={routeChange}>
|
||||||
<Route path="/" component={Offers} />
|
<Route path="/" component={Main} />
|
||||||
<Route path="/login" component={Login} mode="login" />
|
<Route path="/login" component={Login} mode="login" />
|
||||||
<Route path="/register" component={Login} mode="register" />
|
<Route path="/register" component={Login} mode="register" />
|
||||||
<Route path="/search" component={Search} />
|
<Route path="/search" component={Search} />
|
||||||
@@ -49,14 +51,14 @@ const Main = () => {
|
|||||||
{/* ACP */}
|
{/* ACP */}
|
||||||
<Route path="/acp" component={Acp} />
|
<Route path="/acp" component={Acp} />
|
||||||
</Router>
|
</Router>
|
||||||
</main>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<Main />
|
<Mcrates />
|
||||||
</Provider>
|
</Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,8 +49,12 @@ function OfferPriceBtn(offer) {
|
|||||||
? offer.status
|
? offer.status
|
||||||
: 'Accept';
|
: 'Accept';
|
||||||
|
|
||||||
|
const title = accepted
|
||||||
|
? 'Click to cancel'
|
||||||
|
: 'Click to accept this offer';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button disabled={confirmed} onClick={() => setStatus()} class={classes}>${offer.total} {offer.condition}</button>
|
<button disabled={confirmed} title={title} onClick={() => setStatus()} class={classes}>${offer.total} {offer.condition}</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,12 +62,16 @@ function OfferPriceBtn(offer) {
|
|||||||
export default function OfferGroup(group, i) {
|
export default function OfferGroup(group, i) {
|
||||||
const ws = useSelector((state: State) => state.ws);
|
const ws = useSelector((state: State) => state.ws);
|
||||||
|
|
||||||
|
const format = group.details.formats[0].text ? `${group.details.formats[0].text} - ` : '';
|
||||||
|
const descriptions = group.details.formats.map(f => f.descriptions.join(' ')).join(' ')
|
||||||
|
const detailText = `${format} ${descriptions}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="offer" key={i}>
|
<div class="offer" key={i} id={group.details.id}>
|
||||||
<section>
|
<section>
|
||||||
<h3>{group.details.artists_sort} - {group.details.title} <a href={group.details.uri} target="_blank"><ExternalLink /></a></h3>
|
<h3>{group.details.artists_sort} - {group.details.title} <a href={group.details.uri} target="_blank"><ExternalLink /></a></h3>
|
||||||
<div class='alt-text details'>
|
<div class='alt-text details'>
|
||||||
{group.details.year} - {group.details.formats.map(f => f.descriptions.join(' ')).join(' ')}
|
{group.details.year} - {detailText}
|
||||||
</div>
|
</div>
|
||||||
<div class='alt-text details'>Status: {group.status}</div>
|
<div class='alt-text details'>Status: {group.status}</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { h } from 'preact';
|
import { h } from 'preact';
|
||||||
import { useState, useCallback } from 'preact/hooks';
|
import { useState } from 'preact/hooks';
|
||||||
import { ExternalLink } from 'react-feather';
|
import { ExternalLink } from 'react-feather';
|
||||||
|
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { State } from './../store'
|
import { State } from './../store'
|
||||||
|
|
||||||
import http from './../http';
|
|
||||||
|
|
||||||
function Want(want) {
|
function Want(want) {
|
||||||
const r = want.data;
|
const r = want.data;
|
||||||
|
|
||||||
@@ -14,10 +12,13 @@ function Want(want) {
|
|||||||
const [confirm, setConfirm] = useState(false);
|
const [confirm, setConfirm] = useState(false);
|
||||||
|
|
||||||
function onClick() {
|
function onClick() {
|
||||||
if (confirm) return ws.sendWantRm(want.id);
|
if (confirm) {
|
||||||
|
setConfirm(false);
|
||||||
|
return ws.sendWantRm(want.id);
|
||||||
|
}
|
||||||
setTimeout(() => setConfirm(false), 2500);
|
setTimeout(() => setConfirm(false), 2500);
|
||||||
return setConfirm(true);
|
return setConfirm(true);
|
||||||
};
|
}
|
||||||
|
|
||||||
const text = confirm
|
const text = confirm
|
||||||
? 'confirm'
|
? 'confirm'
|
||||||
|
|||||||
@@ -33,15 +33,10 @@ api.interceptors.response.use(onSuccess, onError);
|
|||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
// TODO move to rpc
|
// TODO move to rpc
|
||||||
|
|
||||||
postWantSearch: async function postWantSearch(query) {
|
postWantSearch: async function postWantSearch(query) {
|
||||||
return api.post('/want/search', { query });
|
return api.post('/want/search', { query });
|
||||||
},
|
},
|
||||||
|
|
||||||
postWant: async function postWant(releaseId) {
|
|
||||||
return api.post('/want', { releaseId });
|
|
||||||
},
|
|
||||||
|
|
||||||
postDiscogs: async function postDiscogs(username) {
|
postDiscogs: async function postDiscogs(username) {
|
||||||
return api.post('/want/discogs', { username });
|
return api.post('/want/discogs', { username });
|
||||||
},
|
},
|
||||||
@@ -71,14 +66,6 @@ const actions = {
|
|||||||
password,
|
password,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
postOffers: async function postOffers(listingIds, shipping, baseShipping) {
|
|
||||||
return api.post('/offers', {
|
|
||||||
listingIds,
|
|
||||||
shipping,
|
|
||||||
baseShipping,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default actions;
|
export default actions;
|
||||||
@@ -10,7 +10,6 @@ import Want from './../components/want';
|
|||||||
|
|
||||||
const List = () => {
|
const List = () => {
|
||||||
const wants = useSelector((state: State) => state.wants);
|
const wants = useSelector((state: State) => state.wants);
|
||||||
const user = useSelector((state: State) => state.user);
|
|
||||||
|
|
||||||
if (!wants) {
|
if (!wants) {
|
||||||
return <div> loading... </div>;
|
return <div> loading... </div>;
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { h } from 'preact';
|
||||||
|
import { route } from 'preact-router';
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
|
import { State } from './../store';
|
||||||
|
import * as toast from './../toast';
|
||||||
|
|
||||||
|
import OfferGroup from './../components/offer.group';
|
||||||
|
|
||||||
|
const GroupNav = (want) => {
|
||||||
|
const href = want._count.offers ? `#${want.release}` : null;
|
||||||
|
return (
|
||||||
|
<div class=''>
|
||||||
|
<a href={href}>{want.data.artists_sort} - {want.data.title}</a>
|
||||||
|
<span>{want._count.offers}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function Home() {
|
||||||
|
const offers = useSelector((state: State) => state.offers);
|
||||||
|
const wants = useSelector((state: State) => state.wants);
|
||||||
|
|
||||||
|
if (offers && offers.length === 0) {
|
||||||
|
toast.info('no offers', 'You have no offers yet, search for some more items.');
|
||||||
|
route('/search');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!offers || !wants) return <div>loading...</div>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main >
|
||||||
|
<nav>
|
||||||
|
<div class='header'>
|
||||||
|
<span>Record</span>
|
||||||
|
<span>Offers</span>
|
||||||
|
</div>
|
||||||
|
{wants.map(GroupNav)}
|
||||||
|
</nav>
|
||||||
|
<div class={'offers'}>
|
||||||
|
{offers.map(OfferGroup)}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Home;
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { h } from 'preact';
|
|
||||||
import { route } from 'preact-router';
|
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
|
|
||||||
import { State } from './../store';
|
|
||||||
import * as toast from './../toast';
|
|
||||||
|
|
||||||
import OfferGroup from './../components/offer.group';
|
|
||||||
|
|
||||||
function Home() {
|
|
||||||
const offers = useSelector((state: State) => state.offers);
|
|
||||||
|
|
||||||
if (offers && offers.length === 0) {
|
|
||||||
toast.info('no offers', 'You have no offers yet, search for some more items.');
|
|
||||||
route('/search');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!offers) return <div>loading...</div>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class={'offers'}>
|
|
||||||
{offers.map(OfferGroup)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Home;
|
|
||||||
@@ -7,25 +7,22 @@ import { State } from './../store';
|
|||||||
|
|
||||||
import http from '../http';
|
import http from '../http';
|
||||||
|
|
||||||
function Result(r, wants) {
|
function Result(r, wants, ws) {
|
||||||
const [added, setAdded] = useState(false)
|
|
||||||
|
|
||||||
async function onClick() {
|
async function onClick() {
|
||||||
const [error, want] = await http.postWant(r.id);
|
ws.sendWantAdd(r.id);
|
||||||
if (error) return false;
|
|
||||||
setAdded(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const wanted = wants.some(w => w.release === r.id);
|
// releaseIds are bigints on server
|
||||||
|
const wanted = wants.some(w => BigInt(w.release) === BigInt(r.id));
|
||||||
|
|
||||||
const classes = `${added || wanted ? 'added' : ''}`;
|
const classes = `${wanted ? 'added' : ''}`;
|
||||||
const btnText = added
|
const btnText = wanted
|
||||||
? <Check />
|
? <Check />
|
||||||
: '+';
|
: '+';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article key={r.id}>
|
<article key={r.id}>
|
||||||
<button onClick={onClick} disabled={added} class={classes} >{btnText}</button>
|
<button onClick={onClick} disabled={wanted} class={classes} >{btnText}</button>
|
||||||
<div>
|
<div>
|
||||||
<h3>{r.title} <a href={`https://discogs.com${r.uri}`} target="_blank"><ExternalLink /></a></h3>
|
<h3>{r.title} <a href={`https://discogs.com${r.uri}`} target="_blank"><ExternalLink /></a></h3>
|
||||||
<h5 class='alt-text' >{r.year}</h5>
|
<h5 class='alt-text' >{r.year}</h5>
|
||||||
@@ -45,6 +42,7 @@ const User = () => {
|
|||||||
const [infoText, setInfoText] = useState('');
|
const [infoText, setInfoText] = useState('');
|
||||||
|
|
||||||
const wants = useSelector((state: State) => state.wants);
|
const wants = useSelector((state: State) => state.wants);
|
||||||
|
const ws = useSelector((state: State) => state.ws);
|
||||||
|
|
||||||
async function fetchResults() {
|
async function fetchResults() {
|
||||||
const [error, results] = await http.postWantSearch(query);
|
const [error, results] = await http.postWantSearch(query);
|
||||||
@@ -68,7 +66,8 @@ const User = () => {
|
|||||||
setInfoText(`Added ${wants.length} items to your list.`);
|
setInfoText(`Added ${wants.length} items to your list.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchRef = useRef<HTMLInputElement>()
|
const searchRef = useRef<HTMLInputElement>();
|
||||||
|
const resultsRef = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
searchRef.current.focus()
|
searchRef.current.focus()
|
||||||
@@ -85,7 +84,7 @@ const User = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const topEl = results.length
|
const topEl = results.length
|
||||||
? <button onClick={() => window.scrollTo(0, 0)}> top </button>
|
? <button onClick={() => resultsRef.current.scrollTo(0, 0)}> top </button>
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const infoEl = infoText
|
const infoEl = infoText
|
||||||
@@ -111,9 +110,9 @@ const User = () => {
|
|||||||
<hr />
|
<hr />
|
||||||
<p>Data provided by Discogs.</p>
|
<p>Data provided by Discogs.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class='results'>
|
<div class='results' ref={resultsRef}>
|
||||||
{infoEl}
|
{infoEl}
|
||||||
{results.map(r => Result(r, wants))}
|
{results.map(r => Result(r, wants, ws))}
|
||||||
{topEl}
|
{topEl}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Barlow';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(https://fonts.gstatic.com/s/barlow/v12/7cHpv4kjgoGqM7E_A8s52Hs.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Barlow';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(https://fonts.gstatic.com/s/barlow/v12/7cHpv4kjgoGqM7E_Ass52Hs.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Barlow';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url(https://fonts.gstatic.com/s/barlow/v12/7cHpv4kjgoGqM7E_DMs5.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
|
/* cyrillic-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Noto Sans Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
src: url(https://fonts.gstatic.com/s/notosansmono/v21/BngrUXNETWXI6LwhGYvaxZikqZqK6fBq6kPvUce2oAZcdthSBUsYck4-_FNJ09DdVXQQ.woff2) format('woff2');
|
||||||
|
unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
||||||
|
}
|
||||||
|
/* cyrillic */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Noto Sans Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
src: url(https://fonts.gstatic.com/s/notosansmono/v21/BngrUXNETWXI6LwhGYvaxZikqZqK6fBq6kPvUce2oAZcdthSBUsYck4-_FNJ09ndVXQQ.woff2) format('woff2');
|
||||||
|
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
||||||
|
}
|
||||||
|
/* greek-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Noto Sans Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
src: url(https://fonts.gstatic.com/s/notosansmono/v21/BngrUXNETWXI6LwhGYvaxZikqZqK6fBq6kPvUce2oAZcdthSBUsYck4-_FNJ09HdVXQQ.woff2) format('woff2');
|
||||||
|
unicode-range: U+1F00-1FFF;
|
||||||
|
}
|
||||||
|
/* greek */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Noto Sans Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
src: url(https://fonts.gstatic.com/s/notosansmono/v21/BngrUXNETWXI6LwhGYvaxZikqZqK6fBq6kPvUce2oAZcdthSBUsYck4-_FNJ097dVXQQ.woff2) format('woff2');
|
||||||
|
unicode-range: U+0370-03FF;
|
||||||
|
}
|
||||||
|
/* vietnamese */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Noto Sans Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
src: url(https://fonts.gstatic.com/s/notosansmono/v21/BngrUXNETWXI6LwhGYvaxZikqZqK6fBq6kPvUce2oAZcdthSBUsYck4-_FNJ09LdVXQQ.woff2) format('woff2');
|
||||||
|
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB;
|
||||||
|
}
|
||||||
|
/* latin-ext */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Noto Sans Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
src: url(https://fonts.gstatic.com/s/notosansmono/v21/BngrUXNETWXI6LwhGYvaxZikqZqK6fBq6kPvUce2oAZcdthSBUsYck4-_FNJ09PdVXQQ.woff2) format('woff2');
|
||||||
|
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
/* latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Noto Sans Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-stretch: 100%;
|
||||||
|
src: url(https://fonts.gstatic.com/s/notosansmono/v21/BngrUXNETWXI6LwhGYvaxZikqZqK6fBq6kPvUce2oAZcdthSBUsYck4-_FNJ093dVQ.woff2) format('woff2');
|
||||||
|
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||||
|
}
|
||||||
+11
-379
@@ -23,32 +23,6 @@ img {
|
|||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* latin */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Raleway';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: url(https://fonts.gstatic.com/s/raleway/v28/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVvaorCIPrE.woff2) format('woff2');
|
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Code Pro';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: url(https://fonts.gstatic.com/s/sourcecodepro/v21/HI_diYsKILxRpg3hIP6sJ7fM7PqPMcMnZFqUwX28DMyQtMlrTA.woff2) format('woff2');
|
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Noto Sans Mono';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-stretch: normal;
|
|
||||||
src: url(https://fonts.gstatic.com/s/notosansmono/v20/BngrUXNETWXI6LwhGYvaxZikqZqK6fBq6kPvUce2oAZcdthSBUsYck4-_FNJ093dVQ.woff2) format('woff2');
|
|
||||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
||||||
}
|
|
||||||
|
|
||||||
// stuff from UX laws
|
// stuff from UX laws
|
||||||
html {
|
html {
|
||||||
color:var(--base-font-color);
|
color:var(--base-font-color);
|
||||||
@@ -71,7 +45,7 @@ html {
|
|||||||
--medium-font-size: 1.777em;
|
--medium-font-size: 1.777em;
|
||||||
--large-font-size: 2.369em;
|
--large-font-size: 2.369em;
|
||||||
--xlarge-font-size: 3.157em;
|
--xlarge-font-size: 3.157em;
|
||||||
--base-font-family: 'Raleway', Arial, Sans-Serif;
|
--base-font-family: 'Barlow', Arial, Sans-Serif;
|
||||||
--secondary-font-family: 'Noto Sans Mono', monospace;
|
--secondary-font-family: 'Noto Sans Mono', monospace;
|
||||||
--base-font-color: var(--color-off-white);
|
--base-font-color: var(--color-off-white);
|
||||||
--secondary-font-color: var(--color-mid-grey);
|
--secondary-font-color: var(--color-mid-grey);
|
||||||
@@ -131,6 +105,11 @@ button, input, optgroup, select, textarea {
|
|||||||
line-height:var(--base-line-height);
|
line-height:var(--base-line-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button, [type=submit] {
|
||||||
|
padding: var(--spacing-half);
|
||||||
|
background: var(--background-1);
|
||||||
|
}
|
||||||
|
|
||||||
input:hover, textarea:hover {
|
input:hover, textarea:hover {
|
||||||
color:var(--base-font-color);
|
color:var(--base-font-color);
|
||||||
border:var(--hover-border)
|
border:var(--hover-border)
|
||||||
@@ -156,7 +135,7 @@ button, [type=button], [type=reset], [type=submit] {
|
|||||||
|
|
||||||
font-family: var(--secondary-font-family);
|
font-family: var(--secondary-font-family);
|
||||||
font-size: var(--small-font-size);
|
font-size: var(--small-font-size);
|
||||||
font-weight: bold;
|
// font-weight: bold;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -194,180 +173,6 @@ p {
|
|||||||
margin:0 0 var(--spacing)
|
margin:0 0 var(--spacing)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ok actual styles
|
|
||||||
|
|
||||||
body {
|
|
||||||
padding-top: calc(var(--spacing)/2);
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
padding: 0 var(--spacing-double) 0 var(--spacing-double);
|
|
||||||
}
|
|
||||||
|
|
||||||
button, [type=submit] {
|
|
||||||
padding: var(--spacing-half);
|
|
||||||
background: var(--background-1);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.landing {
|
|
||||||
display: grid;
|
|
||||||
|
|
||||||
grid-template-columns: 1fr 2fr;
|
|
||||||
grid-gap: calc(var(--spacing-double) * 2);
|
|
||||||
grid-template-areas:
|
|
||||||
"login swirl"
|
|
||||||
"login words";
|
|
||||||
|
|
||||||
.login {
|
|
||||||
grid-area: login;
|
|
||||||
button {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.swirl {
|
|
||||||
grid-area: swirl;
|
|
||||||
height: 15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.words {
|
|
||||||
grid-area: words;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
display: grid;
|
|
||||||
|
|
||||||
grid-template-areas:
|
|
||||||
"words swirl"
|
|
||||||
"words contact";
|
|
||||||
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
grid-template-rows: min-content 1fr;
|
|
||||||
|
|
||||||
section {
|
|
||||||
margin-bottom: var(--spacing);
|
|
||||||
}
|
|
||||||
|
|
||||||
.contact {
|
|
||||||
grid-area: contact;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.words {
|
|
||||||
grid-area: words;
|
|
||||||
}
|
|
||||||
|
|
||||||
.swirl {
|
|
||||||
grid-area: swirl;
|
|
||||||
height: 15rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.alt-text, .alt {
|
|
||||||
font-family: var(--secondary-font-family);
|
|
||||||
// font-size: var(--small-font-size);
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
article {
|
|
||||||
background-color: var(--background-1);
|
|
||||||
|
|
||||||
button {
|
|
||||||
border-color: var(--background-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 2fr;
|
|
||||||
|
|
||||||
.results {
|
|
||||||
display: grid;
|
|
||||||
padding-left: var(--spacing);
|
|
||||||
grid-gap: var(--spacing);
|
|
||||||
|
|
||||||
article {
|
|
||||||
display: grid;
|
|
||||||
padding: var(--spacing);
|
|
||||||
grid-template-columns: 1fr 4fr min-content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin-right: var(--spacing);
|
|
||||||
background: var(--background-2);
|
|
||||||
border-color: var(--background-1);
|
|
||||||
|
|
||||||
&.added {
|
|
||||||
background: var(--color-green);
|
|
||||||
cursor: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.list {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns:
|
|
||||||
1fr 1fr 1fr;
|
|
||||||
|
|
||||||
grid-gap: var(--spacing);
|
|
||||||
|
|
||||||
article {
|
|
||||||
display: grid;
|
|
||||||
|
|
||||||
// grid-template-rows:
|
|
||||||
// 1fr min-content min-content;
|
|
||||||
|
|
||||||
grid-template-areas:
|
|
||||||
"title img"
|
|
||||||
"details details"
|
|
||||||
"remove remove";
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin-top: var(--spacing-half);
|
|
||||||
grid-area: remove
|
|
||||||
}
|
|
||||||
|
|
||||||
.details {
|
|
||||||
grid-area: details;
|
|
||||||
}
|
|
||||||
|
|
||||||
padding: var(--spacing);
|
|
||||||
|
|
||||||
img {
|
|
||||||
justify-self: end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
margin-bottom: var(--spacing);
|
|
||||||
|
|
||||||
label, input {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
input, button {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
padding: var(--spacing-half);
|
|
||||||
margin-bottom: var(--spacing);
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-family: var(--secondary-font-family);
|
|
||||||
font-size: var(--small-font-size);
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin-bottom: var(--spacing-half);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: var(--secondary-font-color);
|
color: var(--secondary-font-color);
|
||||||
font-family: var(--secondary-font-family);
|
font-family: var(--secondary-font-family);
|
||||||
@@ -378,184 +183,11 @@ a {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nav {
|
.alt-text, .alt {
|
||||||
a {
|
|
||||||
font-size: var(--small-font-size);
|
|
||||||
margin-right: var(--spacing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
header {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr min-content;
|
|
||||||
padding-bottom: var(--spacing);
|
|
||||||
|
|
||||||
a {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bundles {
|
|
||||||
flex-flow: row;
|
|
||||||
|
|
||||||
.bundle {
|
|
||||||
margin-bottom: var(--spacing-double);
|
|
||||||
|
|
||||||
section {
|
|
||||||
margin-bottom: var(--spacing);
|
|
||||||
}
|
|
||||||
|
|
||||||
.items {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(5, 1fr);
|
|
||||||
|
|
||||||
div {
|
|
||||||
margin-right: var(--spacing);
|
|
||||||
margin-bottom: var(--spacing);
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
|
|
||||||
&.bold {
|
|
||||||
font-weight: bold;
|
|
||||||
background: var(--background-2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.offers {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
grid-gap: var(--spacing);
|
|
||||||
|
|
||||||
.offer {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr min-content;
|
|
||||||
grid-template-rows: min-content min-content;
|
|
||||||
grid-template-areas:
|
|
||||||
"details img"
|
|
||||||
"lines lines";
|
|
||||||
|
|
||||||
padding: var(--spacing);
|
|
||||||
background: var(--background-1);
|
|
||||||
|
|
||||||
.details {
|
|
||||||
font-size: var(--small-font-size);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status {
|
|
||||||
margin-top: var(--spacing-half);
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
|
||||||
grid-area: details;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
grid-area: img;
|
|
||||||
justify-self: end;
|
|
||||||
margin-left: var(--spacing-half);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lines {
|
|
||||||
grid-area: lines;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
column-gap: var(--spacing-half);
|
|
||||||
|
|
||||||
button {
|
|
||||||
margin-top: var(--spacing-half);
|
|
||||||
border-color: var(--background-2);
|
|
||||||
|
|
||||||
&.accepted {
|
|
||||||
background: var(--color-accepted);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.confirmed {
|
|
||||||
cursor: auto;
|
|
||||||
background: var(--color-confirmed);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.neg {
|
|
||||||
margin-left: var(--spacing-half);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.number {
|
|
||||||
font-family: var(--secondary-font-family);
|
font-family: var(--secondary-font-family);
|
||||||
|
// font-size: var(--small-font-size);
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.swirl {
|
@import 'fonts.less';
|
||||||
background-image: url("./../assets/swirl.fix.svg");
|
@import 'styles.less';
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
|
|
||||||
margin-bottom: var(--spacing);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width:900px) {
|
|
||||||
.landing {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
grid-template-areas:
|
|
||||||
"swirl"
|
|
||||||
"login"
|
|
||||||
"words";
|
|
||||||
}
|
|
||||||
|
|
||||||
.list {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.offers {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
// is necessary for some reason
|
|
||||||
grid-template-areas:
|
|
||||||
"swirl"
|
|
||||||
"contact"
|
|
||||||
"words";
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
padding: 0 var(--spacing) 0 var(--spacing);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width:1200px) {
|
|
||||||
img {
|
|
||||||
width: 5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ------------
|
|
||||||
// toasts
|
|
||||||
|
|
||||||
.iziToast {
|
|
||||||
border-radius: 0;
|
|
||||||
border-color: var(--base-border-color);
|
|
||||||
|
|
||||||
.iziToast-body {
|
|
||||||
font-family: var(--base-font-family);
|
|
||||||
font-size: var(--base-font-size);
|
|
||||||
}
|
|
||||||
|
|
||||||
.iziToast-title {
|
|
||||||
font-family: var(--secondary-font-family);
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,410 @@
|
|||||||
|
body {
|
||||||
|
padding-top: calc(var(--spacing)/2);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mcrates {
|
||||||
|
padding: 0 var(--spacing-double) var(--spacing) var(--spacing-double);
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
grid-template-areas:
|
||||||
|
"header header"
|
||||||
|
"main main"
|
||||||
|
"footer footer";
|
||||||
|
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: min-content 1fr min-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing {
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
grid-template-columns: 1fr 2fr;
|
||||||
|
grid-gap: calc(var(--spacing-double) * 2);
|
||||||
|
grid-template-areas:
|
||||||
|
"login swirl"
|
||||||
|
"login words";
|
||||||
|
|
||||||
|
.login {
|
||||||
|
grid-area: login;
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.swirl {
|
||||||
|
grid-area: swirl;
|
||||||
|
height: 15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.words {
|
||||||
|
grid-area: words;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
grid-template-areas:
|
||||||
|
"words swirl"
|
||||||
|
"words contact";
|
||||||
|
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-template-rows: min-content 1fr;
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin-bottom: var(--spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact {
|
||||||
|
grid-area: contact;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.words {
|
||||||
|
grid-area: words;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swirl {
|
||||||
|
grid-area: swirl;
|
||||||
|
height: 15rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
background-color: var(--background-1);
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-color: var(--background-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 2fr;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.results {
|
||||||
|
display: grid;
|
||||||
|
padding-left: var(--spacing);
|
||||||
|
grid-gap: var(--spacing);
|
||||||
|
|
||||||
|
article {
|
||||||
|
display: grid;
|
||||||
|
padding: var(--spacing);
|
||||||
|
grid-template-columns: 1fr 4fr min-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-right: var(--spacing);
|
||||||
|
background: var(--background-2);
|
||||||
|
border-color: var(--background-1);
|
||||||
|
|
||||||
|
&.added {
|
||||||
|
background: var(--color-green);
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns:
|
||||||
|
1fr 1fr 1fr;
|
||||||
|
|
||||||
|
grid-gap: var(--spacing);
|
||||||
|
overflow: scroll;
|
||||||
|
|
||||||
|
article {
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
grid-template-areas:
|
||||||
|
"title img"
|
||||||
|
"details details"
|
||||||
|
"remove remove";
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-top: var(--spacing-half);
|
||||||
|
grid-area: remove
|
||||||
|
}
|
||||||
|
|
||||||
|
.details {
|
||||||
|
grid-area: details;
|
||||||
|
}
|
||||||
|
|
||||||
|
padding: var(--spacing);
|
||||||
|
|
||||||
|
img {
|
||||||
|
justify-self: end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
margin-bottom: var(--spacing);
|
||||||
|
|
||||||
|
label, input {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
input, button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
padding: var(--spacing-half);
|
||||||
|
margin-bottom: var(--spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-family: var(--secondary-font-family);
|
||||||
|
font-size: var(--small-font-size);
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: var(--spacing-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
grid-area: header;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr min-content;
|
||||||
|
|
||||||
|
border-bottom: var(--base-border);
|
||||||
|
padding-bottom: var(--spacing-half);
|
||||||
|
margin-bottom: var(--spacing-half);
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-size: var(--small-font-size);
|
||||||
|
margin-right: var(--spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
grid-area: main;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas:
|
||||||
|
"nav content";
|
||||||
|
|
||||||
|
grid-template-columns:
|
||||||
|
1fr 1fr;
|
||||||
|
|
||||||
|
column-gap: var(--spacing);
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
nav {
|
||||||
|
// duck scrollbars
|
||||||
|
padding-right: var(--spacing-half);
|
||||||
|
|
||||||
|
grid-area: nav;
|
||||||
|
|
||||||
|
div {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns:
|
||||||
|
1fr min-content min-content;
|
||||||
|
|
||||||
|
column-gap: var(--spacing-half);
|
||||||
|
|
||||||
|
a {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offers {
|
||||||
|
grid-area: content;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bundles {
|
||||||
|
flex-flow: row;
|
||||||
|
|
||||||
|
.bundle {
|
||||||
|
margin-bottom: var(--spacing-double);
|
||||||
|
|
||||||
|
section {
|
||||||
|
margin-bottom: var(--spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
.items {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
|
||||||
|
div {
|
||||||
|
margin-right: var(--spacing);
|
||||||
|
margin-bottom: var(--spacing);
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
|
||||||
|
&.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
background: var(--background-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.offers {
|
||||||
|
display: grid;
|
||||||
|
// grid-template-columns: 1fr 1fr;
|
||||||
|
grid-gap: var(--spacing);
|
||||||
|
|
||||||
|
.offer {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr min-content;
|
||||||
|
grid-template-rows: min-content min-content;
|
||||||
|
grid-template-areas:
|
||||||
|
"details img"
|
||||||
|
"lines lines";
|
||||||
|
|
||||||
|
padding: var(--spacing);
|
||||||
|
background: var(--background-1);
|
||||||
|
|
||||||
|
.details {
|
||||||
|
font-size: var(--small-font-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
margin-top: var(--spacing-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
grid-area: details;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
grid-area: img;
|
||||||
|
justify-self: end;
|
||||||
|
margin-left: var(--spacing-half);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lines {
|
||||||
|
grid-area: lines;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
column-gap: var(--spacing-half);
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-top: var(--spacing-half);
|
||||||
|
border-color: var(--background-2);
|
||||||
|
|
||||||
|
&.accepted {
|
||||||
|
background: var(--color-accepted);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.confirmed {
|
||||||
|
cursor: auto;
|
||||||
|
background: var(--color-confirmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.neg {
|
||||||
|
margin-left: var(--spacing-half);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
font-family: var(--secondary-font-family);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.swirl {
|
||||||
|
background-image: url("./../assets/swirl.fix.svg");
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
|
||||||
|
margin-bottom: var(--spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width:900px) {
|
||||||
|
.landing {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-areas:
|
||||||
|
"swirl"
|
||||||
|
"login"
|
||||||
|
"words";
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offers {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
// is necessary for some reason
|
||||||
|
grid-template-areas:
|
||||||
|
"swirl"
|
||||||
|
"contact"
|
||||||
|
"words";
|
||||||
|
}
|
||||||
|
|
||||||
|
#mcrates {
|
||||||
|
padding: 0 var(--spacing) 0 var(--spacing);
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: 1fr 3fr;
|
||||||
|
grid-gap: var(--spacing-half);
|
||||||
|
grid-template-areas:
|
||||||
|
"nav"
|
||||||
|
"content";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width:1200px) {
|
||||||
|
img {
|
||||||
|
width: 5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ------------
|
||||||
|
// toasts
|
||||||
|
|
||||||
|
.iziToast {
|
||||||
|
border-radius: 0;
|
||||||
|
border-color: var(--base-border-color);
|
||||||
|
|
||||||
|
.iziToast-body {
|
||||||
|
font-family: var(--base-font-family);
|
||||||
|
font-size: var(--base-font-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
.iziToast-title {
|
||||||
|
font-family: var(--secondary-font-family);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
|
||||||
<% preact.headEnd %>
|
<% preact.headEnd %>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import toast from 'izitoast';
|
|||||||
export function info(title, message) {
|
export function info(title, message) {
|
||||||
toast.info({
|
toast.info({
|
||||||
title,
|
title,
|
||||||
message,
|
message: message || '',
|
||||||
position: 'bottomCenter',
|
position: 'bottomCenter',
|
||||||
color: '#1f2128',
|
color: '#1f2128',
|
||||||
titleColor: '#F4F1D0',
|
titleColor: '#F4F1D0',
|
||||||
|
|||||||
+11
-1
@@ -1,4 +1,7 @@
|
|||||||
import { wantsState, wsState, userState, offersState, acpOffersState } from './store'
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
|
import { wantsState, userState, offersState, acpOffersState } from './store'
|
||||||
|
import * as toast from './toast';
|
||||||
|
|
||||||
const SOCKET_URL = `${window.location.protocol === 'https:' ? 'wss://' : 'ws://'}${window.location.host}/api/ws`;
|
const SOCKET_URL = `${window.location.protocol === 'https:' ? 'wss://' : 'ws://'}${window.location.host}/api/ws`;
|
||||||
|
|
||||||
@@ -34,6 +37,10 @@ export default function createSocket(dispatch) {
|
|||||||
send(['WantRm', { id }]);
|
send(['WantRm', { id }]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sendWantAdd(releaseId) {
|
||||||
|
send(['WantAdd', { releaseId }]);
|
||||||
|
}
|
||||||
|
|
||||||
function sendOffers() {
|
function sendOffers() {
|
||||||
send(['Offers', {}]);
|
send(['Offers', {}]);
|
||||||
}
|
}
|
||||||
@@ -114,6 +121,7 @@ export default function createSocket(dispatch) {
|
|||||||
// Connection opened
|
// Connection opened
|
||||||
function onOpen() {
|
function onOpen() {
|
||||||
console.log('websocket connected', SOCKET_URL)
|
console.log('websocket connected', SOCKET_URL)
|
||||||
|
toast.info('connected', 'connected to server');
|
||||||
sendPing();
|
sendPing();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,8 +161,10 @@ export default function createSocket(dispatch) {
|
|||||||
|
|
||||||
sendOfferStatusSet,
|
sendOfferStatusSet,
|
||||||
sendWantRm,
|
sendWantRm,
|
||||||
|
sendWantAdd,
|
||||||
|
|
||||||
sendOffers,
|
sendOffers,
|
||||||
|
sendWants,
|
||||||
|
|
||||||
sendAcpOffers,
|
sendAcpOffers,
|
||||||
sendAcpOfferStatusSet,
|
sendAcpOfferStatusSet,
|
||||||
|
|||||||
Reference in new issue
Block a user