pydorky 2.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/.devcontainer/devcontainer.json +17 -0
  2. package/.github/FUNDING.yml +15 -0
  3. package/.github/workflows/e2e-integration.yml +57 -0
  4. package/.github/workflows/publish.yml +24 -0
  5. package/.nvmrc +1 -0
  6. package/LICENSE +21 -0
  7. package/README.md +156 -0
  8. package/bin/index.js +19 -0
  9. package/bin/legacy.js +432 -0
  10. package/docs/doc#1 get-started/README.md +105 -0
  11. package/docs/doc#2 features-wishlist +33 -0
  12. package/docs/doc#2.5 python-port +31 -0
  13. package/docs/doc#3 the-correct-node-version +107 -0
  14. package/docs/doc#4 why-where-python +42 -0
  15. package/docs/doc#5 how-do-endpoints-cli-work +0 -0
  16. package/dorky-usage-aws.svg +1 -0
  17. package/dorky-usage-google-drive.svg +1 -0
  18. package/google-drive-credentials.json +16 -0
  19. package/openapi/openapi.yaml +257 -0
  20. package/package.json +46 -0
  21. package/python-client/README.md +19 -0
  22. package/python-client/dorky_client/__init__.py +3 -0
  23. package/python-client/dorky_client/client.py +32 -0
  24. package/python-client/pyproject.toml +13 -0
  25. package/python-client/tests/test_integration.py +20 -0
  26. package/rectdorky.png +0 -0
  27. package/server/index.js +193 -0
  28. package/server/package.json +12 -0
  29. package/todo/01-core-infrastructure.md +84 -0
  30. package/todo/02-storage-providers.md +104 -0
  31. package/todo/03-compression-formats.md +94 -0
  32. package/todo/04-python-client.md +126 -0
  33. package/todo/05-metadata-versioning.md +116 -0
  34. package/todo/06-performance-concurrency.md +130 -0
  35. package/todo/07-security-encryption.md +114 -0
  36. package/todo/08-developer-experience.md +175 -0
  37. package/todo/README.md +37 -0
  38. package/web-app/README.md +70 -0
  39. package/web-app/package-lock.json +17915 -0
  40. package/web-app/package.json +43 -0
  41. package/web-app/public/favicon.ico +0 -0
  42. package/web-app/public/index.html +43 -0
  43. package/web-app/public/logo192.png +0 -0
  44. package/web-app/public/logo512.png +0 -0
  45. package/web-app/public/manifest.json +25 -0
  46. package/web-app/public/robots.txt +3 -0
  47. package/web-app/src/App.css +23 -0
  48. package/web-app/src/App.js +84 -0
  49. package/web-app/src/App.test.js +8 -0
  50. package/web-app/src/PrivacyPolicy.js +26 -0
  51. package/web-app/src/TermsAndConditions.js +41 -0
  52. package/web-app/src/index.css +3 -0
  53. package/web-app/src/index.js +26 -0
  54. package/web-app/src/logo.svg +1 -0
  55. package/web-app/src/reportWebVitals.js +13 -0
  56. package/web-app/src/setupTests.js +5 -0
  57. package/web-app/tailwind.config.js +10 -0
@@ -0,0 +1,16 @@
1
+ {
2
+ "web": {
3
+ "client_id": "549624816041-j9tr8es6megehe18i0c04vmpno6q78ld.apps.googleusercontent.com",
4
+ "project_id": "dorky-445907",
5
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
6
+ "token_uri": "https://oauth2.googleapis.com/token",
7
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
8
+ "client_secret": "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",
9
+ "redirect_uris": [
10
+ "http://localhost:3000/oauth2callback"
11
+ ],
12
+ "javascript_origins": [
13
+ "http://localhost:3000"
14
+ ]
15
+ }
16
+ }
@@ -0,0 +1,257 @@
1
+ openapi: 3.0.1
2
+ info:
3
+ title: Pydorky Artifact Service
4
+ description: Language-agnostic HTTP service for artifact storage, streaming, metadata, idempotency, and versioning
5
+ version: 0.2.0
6
+ servers:
7
+ - url: http://localhost:3000
8
+ description: Local development server
9
+
10
+ paths:
11
+ /health:
12
+ get:
13
+ summary: Health check
14
+ operationId: health
15
+ tags:
16
+ - service
17
+ responses:
18
+ '200':
19
+ description: Service is healthy
20
+ content:
21
+ application/json:
22
+ schema:
23
+ type: object
24
+ properties:
25
+ status:
26
+ type: string
27
+ example: healthy
28
+ timestamp:
29
+ type: string
30
+ format: date-time
31
+
32
+ /artifacts:
33
+ post:
34
+ summary: Upload an artifact
35
+ operationId: uploadArtifact
36
+ tags:
37
+ - artifacts
38
+ requestBody:
39
+ required: true
40
+ content:
41
+ multipart/form-data:
42
+ schema:
43
+ type: object
44
+ properties:
45
+ file:
46
+ type: string
47
+ format: binary
48
+ description: The file to upload
49
+ metadata:
50
+ type: string
51
+ description: JSON metadata as string
52
+ idempotency_key:
53
+ type: string
54
+ description: Unique key to prevent duplicate uploads
55
+ required:
56
+ - file
57
+ responses:
58
+ '201':
59
+ description: Artifact created successfully
60
+ content:
61
+ application/json:
62
+ schema:
63
+ $ref: '#/components/schemas/Artifact'
64
+ '400':
65
+ description: Bad request (missing file, invalid metadata)
66
+ '409':
67
+ description: Conflict (artifact already exists with same idempotency key)
68
+
69
+ /artifacts/{id}:
70
+ get:
71
+ summary: Download an artifact
72
+ operationId: downloadArtifact
73
+ tags:
74
+ - artifacts
75
+ parameters:
76
+ - name: id
77
+ in: path
78
+ required: true
79
+ schema:
80
+ type: string
81
+ description: Artifact ID
82
+ - name: version
83
+ in: query
84
+ required: false
85
+ schema:
86
+ type: string
87
+ description: Specific version to download (optional)
88
+ responses:
89
+ '200':
90
+ description: Artifact stream
91
+ content:
92
+ application/octet-stream: {}
93
+ '404':
94
+ description: Artifact not found
95
+ '416':
96
+ description: Range not satisfiable
97
+
98
+ delete:
99
+ summary: Delete an artifact
100
+ operationId: deleteArtifact
101
+ tags:
102
+ - artifacts
103
+ parameters:
104
+ - name: id
105
+ in: path
106
+ required: true
107
+ schema:
108
+ type: string
109
+ responses:
110
+ '204':
111
+ description: Artifact deleted successfully
112
+ '404':
113
+ description: Artifact not found
114
+
115
+ /artifacts/{id}/metadata:
116
+ get:
117
+ summary: Get artifact metadata
118
+ operationId: getMetadata
119
+ tags:
120
+ - metadata
121
+ parameters:
122
+ - name: id
123
+ in: path
124
+ required: true
125
+ schema:
126
+ type: string
127
+ responses:
128
+ '200':
129
+ description: Artifact metadata
130
+ content:
131
+ application/json:
132
+ schema:
133
+ $ref: '#/components/schemas/Metadata'
134
+ '404':
135
+ description: Artifact not found
136
+
137
+ patch:
138
+ summary: Update artifact metadata
139
+ operationId: updateMetadata
140
+ tags:
141
+ - metadata
142
+ parameters:
143
+ - name: id
144
+ in: path
145
+ required: true
146
+ schema:
147
+ type: string
148
+ requestBody:
149
+ required: true
150
+ content:
151
+ application/json:
152
+ schema:
153
+ type: object
154
+ additionalProperties: true
155
+ responses:
156
+ '200':
157
+ description: Metadata updated
158
+ content:
159
+ application/json:
160
+ schema:
161
+ $ref: '#/components/schemas/Metadata'
162
+ '404':
163
+ description: Artifact not found
164
+
165
+ /artifacts/{id}/versions:
166
+ get:
167
+ summary: List all versions of an artifact
168
+ operationId: listVersions
169
+ tags:
170
+ - versioning
171
+ parameters:
172
+ - name: id
173
+ in: path
174
+ required: true
175
+ schema:
176
+ type: string
177
+ responses:
178
+ '200':
179
+ description: List of versions
180
+ content:
181
+ application/json:
182
+ schema:
183
+ type: array
184
+ items:
185
+ $ref: '#/components/schemas/Version'
186
+ '404':
187
+ description: Artifact not found
188
+
189
+ components:
190
+ schemas:
191
+ Artifact:
192
+ type: object
193
+ required:
194
+ - id
195
+ - url
196
+ properties:
197
+ id:
198
+ type: string
199
+ example: "1a2b3c"
200
+ url:
201
+ type: string
202
+ example: "/artifacts/1a2b3c"
203
+ metadata:
204
+ $ref: '#/components/schemas/Metadata'
205
+ version:
206
+ type: string
207
+ example: "1"
208
+
209
+ Metadata:
210
+ type: object
211
+ required:
212
+ - content_hash
213
+ - file_size
214
+ - last_updated
215
+ properties:
216
+ content_hash:
217
+ type: string
218
+ description: SHA256 hash of file content
219
+ example: "abc123def456..."
220
+ file_size:
221
+ type: integer
222
+ description: File size in bytes
223
+ example: 1024
224
+ compressed_size:
225
+ type: integer
226
+ description: Compressed file size (if applicable)
227
+ compression_type:
228
+ type: string
229
+ description: Compression algorithm used
230
+ enum: [gzip, lz4, brotli, null]
231
+ last_updated:
232
+ type: string
233
+ format: date-time
234
+ description: Last update timestamp (UTC ISO 8601)
235
+ uploaded_by:
236
+ type: string
237
+ description: User/identifier who uploaded
238
+ custom:
239
+ type: object
240
+ description: User-defined metadata
241
+ additionalProperties: true
242
+
243
+ Version:
244
+ type: object
245
+ required:
246
+ - version_id
247
+ - created_at
248
+ - metadata
249
+ properties:
250
+ version_id:
251
+ type: string
252
+ example: "1"
253
+ created_at:
254
+ type: string
255
+ format: date-time
256
+ metadata:
257
+ $ref: '#/components/schemas/Metadata'
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "pydorky",
3
+ "version": "2.1.8",
4
+ "description": "DevOps Records Keeper.",
5
+ "bin": {
6
+ "dorky": "bin/index.js"
7
+ },
8
+ "scripts": {
9
+ "start": "node bin/index.js",
10
+ "test": "mocha"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/Pratham-Jain-3903/pydorky.git"
15
+ },
16
+ "keywords": [
17
+ "DevOps",
18
+ "env",
19
+ "yaml",
20
+ "json"
21
+ ],
22
+ "author": "Pydorky contributors",
23
+ "license": "ISC",
24
+ "bugs": {
25
+ "url": "https://github.com/Pratham-Jain-3903/pydorky/issues"
26
+ },
27
+ "homepage": "https://github.com/Pratham-Jain-3903/pydorky#readme",
28
+ "engines": {
29
+ "node": ">=16"
30
+ },
31
+ "devDependencies": {
32
+ "tsc": "^2.0.4",
33
+ "typescript": "^5.5.4"
34
+ },
35
+ "dependencies": {
36
+ "@aws-sdk/client-s3": "^3.679.0",
37
+ "@google-cloud/local-auth": "^3.0.1",
38
+ "chalk": "^4.1.2",
39
+ "glob": "^13.0.0",
40
+ "googleapis": "^144.0.0",
41
+ "md5": "^2.3.0",
42
+ "mime-type": "^4.0.0",
43
+ "mime-types": "^2.1.35",
44
+ "yargs": "^17.7.2"
45
+ }
46
+ }
@@ -0,0 +1,19 @@
1
+ Dorky Python client
2
+
3
+ This is a minimal scaffold for the Dorky Python client. It implements a thin wrapper
4
+ around the Dorky HTTP API (see `openapi/openapi.yaml`).
5
+
6
+ Install (local editable):
7
+
8
+ ```bash
9
+ pip install -e python-client
10
+ ```
11
+
12
+ Usage example:
13
+
14
+ ```python
15
+ from dorky_client import DorkyClient
16
+
17
+ client = DorkyClient('http://localhost:3000')
18
+ client.upload('path/to/file.txt', metadata={'note':'example'})
19
+ ```
@@ -0,0 +1,3 @@
1
+ from .client import DorkyClient
2
+
3
+ __all__ = ["DorkyClient"]
@@ -0,0 +1,32 @@
1
+ import os
2
+ import requests
3
+ from typing import Optional, Dict
4
+
5
+ CHUNK_SIZE = 1024 * 64
6
+
7
+ class DorkyClient:
8
+ def __init__(self, base_url: str):
9
+ self.base_url = base_url.rstrip('/')
10
+
11
+ def upload(self, file_path: str, metadata: Optional[Dict]=None, idempotency_key: Optional[str]=None):
12
+ url = f"{self.base_url}/artifacts"
13
+ files = {'file': open(file_path, 'rb')}
14
+ data = {}
15
+ if metadata:
16
+ data['metadata'] = requests.utils.requote_uri(str(metadata))
17
+ if idempotency_key:
18
+ data['idempotency_key'] = idempotency_key
19
+ resp = requests.post(url, files=files, data=data)
20
+ resp.raise_for_status()
21
+ return resp.json()
22
+
23
+ def download(self, artifact_id: str, dest_path: str):
24
+ url = f"{self.base_url}/artifacts/{artifact_id}"
25
+ with requests.get(url, stream=True) as r:
26
+ r.raise_for_status()
27
+ os.makedirs(os.path.dirname(dest_path) or '.', exist_ok=True)
28
+ with open(dest_path, 'wb') as f:
29
+ for chunk in r.iter_content(chunk_size=CHUNK_SIZE):
30
+ if chunk:
31
+ f.write(chunk)
32
+ return dest_path
@@ -0,0 +1,13 @@
1
+ [project]
2
+ name = "pydorky-client"
3
+ version = "0.1.0"
4
+ description = "Pydorky Python client (minimal scaffold)"
5
+ readme = "README.md"
6
+ requires-python = ">=3.8"
7
+
8
+ [project.dependencies]
9
+ requests = "^2.28"
10
+
11
+ [build-system]
12
+ requires = ["setuptools>=61.0", "wheel"]
13
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,20 @@
1
+ import os
2
+ import tempfile
3
+
4
+ from dorky_client import DorkyClient
5
+
6
+
7
+ def test_upload_and_download(tmp_path):
8
+ client = DorkyClient('http://localhost:3000')
9
+
10
+ src = tmp_path / 'hello.txt'
11
+ src.write_bytes(b'hello-pydorky')
12
+
13
+ resp = client.upload(str(src), metadata={'test': 'integration'})
14
+ assert 'id' in resp
15
+ artifact_id = resp['id']
16
+
17
+ dest = tmp_path / 'out.txt'
18
+ client.download(artifact_id, str(dest))
19
+ assert dest.exists()
20
+ assert dest.read_bytes() == b'hello-pydorky'
package/rectdorky.png ADDED
Binary file
@@ -0,0 +1,193 @@
1
+ const express = require('express');
2
+ const multer = require('multer');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const crypto = require('crypto');
6
+
7
+ const upload = multer({ dest: 'tmp/' });
8
+ const app = express();
9
+ const PORT = process.env.PORT || 3000;
10
+
11
+ // In-memory storage for metadata and idempotency (in production: use database)
12
+ const artifactStore = new Map(); // id -> { path, metadata, versions }
13
+ const idempotencyStore = new Map(); // idempotency_key -> artifact_id
14
+
15
+ // Utility: compute SHA256 hash of file
16
+ function hashFile(filePath) {
17
+ const fileBuffer = fs.readFileSync(filePath);
18
+ return crypto.createHash('sha256').update(fileBuffer).digest('hex');
19
+ }
20
+
21
+ // Health check endpoint
22
+ app.get('/health', (req, res) => {
23
+ res.json({
24
+ status: 'healthy',
25
+ timestamp: new Date().toISOString()
26
+ });
27
+ });
28
+
29
+ // Serve OpenAPI spec
30
+ app.get('/openapi.yaml', (req, res) => {
31
+ res.sendFile(path.resolve(__dirname, '..', 'openapi', 'openapi.yaml'));
32
+ });
33
+
34
+ // POST /artifacts - Upload artifact with metadata and idempotency
35
+ app.post('/artifacts', upload.single('file'), (req, res) => {
36
+ if (!req.file) {
37
+ return res.status(400).json({ error: 'no file provided' });
38
+ }
39
+
40
+ const idempotencyKey = req.body.idempotency_key;
41
+
42
+ // Check idempotency: if same key uploaded before, return cached result
43
+ if (idempotencyKey && idempotencyStore.has(idempotencyKey)) {
44
+ const existingId = idempotencyStore.get(idempotencyKey);
45
+ const existing = artifactStore.get(existingId);
46
+ return res.status(409).json({
47
+ error: 'artifact already exists with this idempotency key',
48
+ artifact: {
49
+ id: existingId,
50
+ url: `/artifacts/${existingId}`,
51
+ metadata: existing.metadata
52
+ }
53
+ });
54
+ }
55
+
56
+ // Generate artifact ID
57
+ const id = Date.now().toString(36) + '_' + Math.random().toString(36).substr(2, 9);
58
+ const dest = path.resolve(__dirname, '..', 'data', id + '_' + req.file.originalname);
59
+
60
+ // Create data directory
61
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
62
+ fs.renameSync(req.file.path, dest);
63
+
64
+ // Compute file hash and size
65
+ const fileSize = fs.statSync(dest).size;
66
+ const contentHash = hashFile(dest);
67
+
68
+ // Parse custom metadata
69
+ let customMetadata = {};
70
+ try {
71
+ if (req.body.metadata) {
72
+ customMetadata = JSON.parse(req.body.metadata);
73
+ }
74
+ } catch (e) {
75
+ return res.status(400).json({ error: 'invalid metadata JSON' });
76
+ }
77
+
78
+ // Build complete metadata
79
+ const metadata = {
80
+ content_hash: contentHash,
81
+ file_size: fileSize,
82
+ compression_type: null,
83
+ compressed_size: fileSize,
84
+ last_updated: new Date().toISOString(),
85
+ uploaded_by: req.body.uploaded_by || 'anonymous',
86
+ custom: customMetadata
87
+ };
88
+
89
+ // Store artifact and metadata
90
+ artifactStore.set(id, {
91
+ path: dest,
92
+ metadata: metadata,
93
+ versions: [{ version_id: '1', created_at: metadata.last_updated, metadata: metadata }]
94
+ });
95
+
96
+ // Store idempotency mapping
97
+ if (idempotencyKey) {
98
+ idempotencyStore.set(idempotencyKey, id);
99
+ }
100
+
101
+ res.status(201).json({
102
+ id,
103
+ url: `/artifacts/${id}`,
104
+ metadata,
105
+ version: '1'
106
+ });
107
+ });
108
+
109
+ // GET /artifacts/{id} - Download artifact
110
+ app.get('/artifacts/:id', (req, res) => {
111
+ const id = req.params.id;
112
+ const artifact = artifactStore.get(id);
113
+
114
+ if (!artifact) {
115
+ // Fallback: check legacy data directory
116
+ const dir = path.resolve(__dirname, '..', 'data');
117
+ if (fs.existsSync(dir)) {
118
+ const file = fs.readdirSync(dir).find(f => f.startsWith(id + '_'));
119
+ if (file) {
120
+ return res.sendFile(path.resolve(dir, file));
121
+ }
122
+ }
123
+ return res.status(404).json({ error: 'artifact not found' });
124
+ }
125
+
126
+ res.sendFile(artifact.path);
127
+ });
128
+
129
+ // DELETE /artifacts/{id} - Delete artifact
130
+ app.delete('/artifacts/:id', (req, res) => {
131
+ const id = req.params.id;
132
+ const artifact = artifactStore.get(id);
133
+
134
+ if (!artifact) {
135
+ return res.status(404).json({ error: 'artifact not found' });
136
+ }
137
+
138
+ // Delete file from disk
139
+ if (fs.existsSync(artifact.path)) {
140
+ fs.unlinkSync(artifact.path);
141
+ }
142
+
143
+ // Remove from store
144
+ artifactStore.delete(id);
145
+
146
+ res.status(204).send();
147
+ });
148
+
149
+ // GET /artifacts/{id}/metadata - Get metadata
150
+ app.get('/artifacts/:id/metadata', (req, res) => {
151
+ const id = req.params.id;
152
+ const artifact = artifactStore.get(id);
153
+
154
+ if (!artifact) {
155
+ return res.status(404).json({ error: 'artifact not found' });
156
+ }
157
+
158
+ res.json(artifact.metadata);
159
+ });
160
+
161
+ // PATCH /artifacts/{id}/metadata - Update metadata
162
+ app.patch('/artifacts/:id/metadata', express.json(), (req, res) => {
163
+ const id = req.params.id;
164
+ const artifact = artifactStore.get(id);
165
+
166
+ if (!artifact) {
167
+ return res.status(404).json({ error: 'artifact not found' });
168
+ }
169
+
170
+ // Merge custom metadata
171
+ artifact.metadata.custom = { ...artifact.metadata.custom, ...req.body };
172
+ artifact.metadata.last_updated = new Date().toISOString();
173
+
174
+ res.json(artifact.metadata);
175
+ });
176
+
177
+ // GET /artifacts/{id}/versions - List all versions
178
+ app.get('/artifacts/:id/versions', (req, res) => {
179
+ const id = req.params.id;
180
+ const artifact = artifactStore.get(id);
181
+
182
+ if (!artifact) {
183
+ return res.status(404).json({ error: 'artifact not found' });
184
+ }
185
+
186
+ res.json(artifact.versions || []);
187
+ });
188
+
189
+ app.listen(PORT, () => {
190
+ console.log(`Dorky artifact service listening on port ${PORT}`);
191
+ console.log(`Health check: GET http://localhost:${PORT}/health`);
192
+ console.log(`OpenAPI spec: GET http://localhost:${PORT}/openapi.yaml`);
193
+ });
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "dorky-server-stub",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "start": "node index.js"
7
+ },
8
+ "dependencies": {
9
+ "express": "^4.18.2",
10
+ "multer": "^2.0.2"
11
+ }
12
+ }