@cocreate/file 1.1.0 → 1.2.1

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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## [1.2.1](https://github.com/CoCreate-app/CoCreate-file/compare/v1.2.0...v1.2.1) (2023-06-10)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * Refactor authentication & update config file output format ([27da6f0](https://github.com/CoCreate-app/CoCreate-file/commit/27da6f06c832bb3e153074a96a42017f0f0a3c30))
7
+
8
+ # [1.2.0](https://github.com/CoCreate-app/CoCreate-file/compare/v1.1.0...v1.2.0) (2023-06-09)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * Update dependencies versions for [@cocreate](https://github.com/cocreate) libraries ([c86d68d](https://github.com/CoCreate-app/CoCreate-file/commit/c86d68d3ef7fa76e8f52867f7abecaa67bab8270))
14
+
15
+
16
+ ### Features
17
+
18
+ * client and server functions ([af50769](https://github.com/CoCreate-app/CoCreate-file/commit/af5076902607f52c51f5a4fcca4a4a5554617b92))
19
+
1
20
  # [1.1.0](https://github.com/CoCreate-app/CoCreate-file/compare/v1.0.0...v1.1.0) (2023-06-08)
2
21
 
3
22
 
@@ -1,9 +1,7 @@
1
1
  module.exports = {
2
- "config": {
3
- "organization_id": "",
4
- "key": "",
5
- "host": ""
6
- },
2
+ "organization_id": "",
3
+ "key": "",
4
+ "host": "",
7
5
  "sources": [
8
6
  {
9
7
  "collection": "files",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cocreate/file",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "A headless file uploader that uses HTML5 attributes for customization. Allows easy upload of files to server.",
5
5
  "keywords": [
6
6
  "file",
@@ -29,7 +29,7 @@
29
29
  "build": "NODE_ENV=production npx webpack --config webpack.config.js",
30
30
  "dev": "npx webpack --config webpack.config.js --watch",
31
31
  "docs": "node ./node_modules/@cocreate/docs/src/index.js",
32
- "postinstall": "node ./node_modules/@cocreate/cli/check-coc.js"
32
+ "postinstal": "node ./node_modules/@cocreate/cli/check-coc.js"
33
33
  },
34
34
  "repository": {
35
35
  "type": "git",
package/src/client.js ADDED
@@ -0,0 +1,318 @@
1
+ import observer from '@cocreate/observer';
2
+ import crud from '@cocreate/crud-client';
3
+ import action from '@cocreate/actions';
4
+ import render from '@cocreate/render';
5
+ import '@cocreate/element-prototype';
6
+
7
+ let inputs = new Map();
8
+
9
+ function init(elements) {
10
+ // Returns an array of elements.
11
+ if (!elements)
12
+ elements = document.querySelectorAll('[type="file"]')
13
+
14
+ // If elements is an array of elements returns an array of elements.
15
+ else if (!Array.isArray(elements))
16
+ elements = [elements]
17
+ for (let i = 0; i < elements.length; i++) {
18
+ if (elements[i].tagName !== 'INPUT') {
19
+ // TODO: create input and append to div if input dos not exist
20
+ }
21
+ elements[i].getValue = async () => await getSelectedFiles([elements[i]], true)
22
+
23
+ // elements[i].setValue = (value) => pickr.setColor(value);
24
+ if (elements[i].hasAttribute('directory')) {
25
+ if (window.showDirectoryPicker)
26
+ elements[i].addEventListener("click", selectDirectory);
27
+ else if ('webkitdirectory' in elements[i]) {
28
+ elements[i].webkitdirectory = true
29
+ elements[i].addEventListener("change", handleFileInputChange)
30
+ } else
31
+ console.error("Directory selection not supported in this browser.");
32
+ } else if (window.showOpenFilePicker)
33
+ elements[i].addEventListener("click", selectFile);
34
+ else
35
+ elements[i].addEventListener("change", handleFileInputChange);
36
+ }
37
+ }
38
+
39
+
40
+ function handleFileInputChange(event) {
41
+ const input = event.target;
42
+ const files = input.files;
43
+ let selected = inputs.get(input) || []
44
+ selected.push(...files)
45
+ inputs.set(input, selected);
46
+ console.log("Files selected:", files);
47
+ renderFiles(input)
48
+ }
49
+
50
+ async function selectFile(event) {
51
+ event.preventDefault()
52
+ const input = event.target;
53
+ let selected = inputs.get(input) || []
54
+ try {
55
+ const multiple = input.multiple
56
+ const selectedFiles = await window.showOpenFilePicker({ multiple });
57
+
58
+ for (const handle of selectedFiles) {
59
+ selected.push(handle)
60
+ }
61
+
62
+ if (selected.length) {
63
+ inputs.set(input, selected);
64
+ console.log("Files selected:", selected);
65
+ renderFiles(input)
66
+ }
67
+
68
+ } catch (error) {
69
+ if (error.name !== 'AbortError') {
70
+ console.error("Error selecting files:", error);
71
+ }
72
+ }
73
+ }
74
+
75
+ async function selectDirectory(event) {
76
+ event.preventDefault()
77
+ const input = event.target;
78
+ let selected = inputs.get(input) || []
79
+
80
+ try {
81
+ const handle = await window.showDirectoryPicker();
82
+ selected.push(handle)
83
+
84
+ if (selected.length) {
85
+ inputs.set(input, selected);
86
+ console.log("Directory selected:", selected);
87
+ renderFiles(input)
88
+ }
89
+ } catch (error) {
90
+ if (error.name !== 'AbortError') {
91
+ console.error("Error selecting directory:", error);
92
+ }
93
+ }
94
+ }
95
+
96
+ async function getNewFileHandle() {
97
+ // const options = {
98
+ // types: [
99
+ // {
100
+ // description: 'Text Files',
101
+ // accept: {
102
+ // 'text/plain': ['.txt'],
103
+ // },
104
+ // },
105
+ // ],
106
+ // };
107
+ const handle = await window.showSaveFilePicker(options);
108
+ return handle;
109
+ }
110
+
111
+ async function getSelectedFiles(fileInputs, isObject) {
112
+ const files = [];
113
+
114
+ if (!Array.isArray(fileInputs))
115
+ fileInputs = [fileInputs]
116
+
117
+ for (let input of fileInputs) {
118
+ const selected = inputs.get(input) || []
119
+
120
+ for (let i = 0; i < selected.length; i++) {
121
+ let file
122
+ if (selected[i] instanceof FileSystemDirectoryHandle) {
123
+ // The object is an instance of FileSystemFileHandle
124
+ const handles = await getSelectedDirectoryFiles(selected[i], selected[i].name)
125
+ for (let handle of handles) {
126
+ if (handle.kind === 'file')
127
+ file = await handle.getFile();
128
+ else if (handle.kind === 'directory')
129
+ file = { ...handle, name: handle.name }
130
+ else continue
131
+
132
+ if (isObject)
133
+ file = await readFile(file)
134
+
135
+ files.push(file)
136
+ }
137
+ } else {
138
+ if (selected[i] instanceof FileSystemFileHandle) {
139
+ // The object is an instance of FileSystemFileHandle
140
+ file = await selected[i].getFile();
141
+ } else {
142
+ // The object is not an instance of FileSystemFileHandle
143
+ console.log("It's not a FileSystemFileHandle object");
144
+ file = selected[i]
145
+ }
146
+
147
+ if (isObject)
148
+ file = await readFile(file)
149
+
150
+ files.push(file)
151
+ }
152
+
153
+ }
154
+ }
155
+
156
+ return files
157
+ }
158
+
159
+ async function getSelectedDirectoryFiles(handle, name) {
160
+ let files = [];
161
+ for await (const entry of handle.values()) {
162
+ entry.directory = '/' + name
163
+ entry.parentDirectory = name.split("/").pop();
164
+ entry.path = '/' + name + '/' + entry.name
165
+ if (!entry.webkitRelativePath)
166
+ entry.webkitRelativePath = name
167
+
168
+ if (entry.kind === 'file') {
169
+ files.push(entry);
170
+ } else if (entry.kind === 'directory') {
171
+ entry.type = 'text/directory'
172
+ files.push(entry);
173
+ const entries = await getSelectedDirectoryFiles(entry, name + '/' + entry.name);
174
+ files = files.concat(entries);
175
+ }
176
+ }
177
+ return files;
178
+ }
179
+
180
+ // This function reads the file and returns its src
181
+ function readFile(file) {
182
+ // Return a new promise that resolves the file object
183
+ return new Promise((resolve) => {
184
+ file["content-type"] = file.type
185
+
186
+ // Split the file type into an array
187
+ const fileType = file.type.split('/');
188
+ let readAs;
189
+
190
+ // Check if the file type is a directory
191
+ if (fileType[1] === 'directory') {
192
+ return resolve(file)
193
+ }
194
+ // Check if the file type is a image
195
+ else if (['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(fileType[1])
196
+ || fileType[0] === 'image') {
197
+ readAs = 'readAsDataURL';
198
+ }
199
+ // Check if the file type is a video
200
+ else if (['mp4', 'avi', 'mov', 'mpeg', 'flv'].includes(fileType[1])
201
+ || fileType[0] === 'video') {
202
+ readAs = 'readAsDataURL';
203
+ }
204
+ // Check if the file type is an audio
205
+ else if (['mp3', 'wav', 'wma', 'aac', 'ogg'].includes(fileType[1])
206
+ || fileType[0] === 'audio') { // updated condition
207
+ readAs = 'readAsDataURL';
208
+ }
209
+ // Check if the file type is a pdf
210
+ else if (fileType[1] === 'pdf') {
211
+ readAs = 'readAsDataURL';
212
+ }
213
+ // Check if the file type is a document
214
+ else if (['doc', 'msword', 'docx', 'xlsx', 'pptx'].includes(fileType[1])) {
215
+ readAs = 'readAsBinaryString';
216
+ }
217
+ // Otherwise, assume the file type is text
218
+ else {
219
+ readAs = 'readAsText';
220
+ }
221
+
222
+ // Create a FileReader instance to read the file
223
+ const reader = new FileReader();
224
+ // Read the file based on the file type
225
+ reader[readAs](file);
226
+ // When the file is loaded, resolve the file object
227
+ reader.onload = () => {
228
+ file.src = reader.result;
229
+ // If the file type is a document, convert it to base64 encoding
230
+ if (['doc', 'msword', 'docx', 'xlsx', 'pptx'].includes(fileType)) {
231
+ file.src = btoa(file.src);
232
+ }
233
+
234
+ // Resolve the file object
235
+ resolve(file);
236
+ };
237
+ });
238
+ }
239
+
240
+ async function fileAction(btn, params, action) {
241
+ const form = btn.closest('form')
242
+ let inputs = form.querySelectorAll('input[type="file"]')
243
+ let fileObjects = await getSelectedFiles(Array.from(inputs), true)
244
+
245
+ console.log('fileObjects', fileObjects)
246
+ document.dispatchEvent(new CustomEvent(action, {
247
+ detail: {}
248
+ }));
249
+
250
+ }
251
+
252
+ // may be best to use getValue() so form so inputtype files can be can be managed in forms
253
+ async function save(inputs, collection, document_id) {
254
+ let files = await getSelectedFiles(inputs, true)
255
+
256
+ let response = await crud.updateDocument({
257
+ collection,
258
+ document: files,
259
+ upsert: true
260
+ });
261
+
262
+ if (response && (!document_id || document_id !== response.document_id)) {
263
+ crud.setDocumentId(element, collection, response.document_id);
264
+ }
265
+
266
+ return response
267
+ }
268
+
269
+ async function getFiles(inputs) {
270
+ let files = await getSelectedFiles(inputs)
271
+ return files
272
+ }
273
+
274
+ async function getObjects(inputs) {
275
+ let objects = await getSelectedFiles(inputs, true)
276
+ return objects
277
+ }
278
+
279
+ function renderFiles(input) {
280
+ // TODO: support
281
+ let template_id = input.getAttribute('template_id')
282
+ if (template_id) {
283
+ // if data items are handle it will not yet have all the details
284
+ const data = inputs.get(input)
285
+ if (data.length) return
286
+ render.data({
287
+ selector: `[template='${template_id}']`,
288
+ data
289
+ });
290
+ }
291
+ }
292
+
293
+ observer.init({
294
+ name: 'CoCreateFileAddedNodes',
295
+ observe: ['addedNodes'],
296
+ target: 'input[type="file"]',
297
+ callback: mutation => init(mutation.target)
298
+
299
+ });
300
+
301
+ observer.init({
302
+ name: 'CoCreateFileAttributes',
303
+ observe: ['attributes'],
304
+ attributeName: ['type'],
305
+ target: 'input[type="file"]',
306
+ callback: mutation => init(mutation.target)
307
+ });
308
+
309
+ action.init({
310
+ name: "upload",
311
+ callback: (btn, params) => {
312
+ fileAction(btn, params, "upload")
313
+ }
314
+ })
315
+
316
+ init()
317
+
318
+ export default { getFiles, getObjects }
package/src/server.js ADDED
@@ -0,0 +1,354 @@
1
+ const crud = require('@cocreate/crud-client')
2
+ const cli = require('@cocreate/cli')
3
+ const mime = require('mime-types')
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+
8
+ module.exports = async function file(CoCreateConfig) {
9
+
10
+ let configFile = path.resolve(process.cwd(), 'CoCreate.config.js');
11
+ if (fs.existsSync(configFile)) {
12
+ CoCreateConfig = require(configFile);
13
+ } else {
14
+ console.log('CoCreate.config.js could not be found.')
15
+ process.exit()
16
+ }
17
+
18
+ let { directories, sources } = CoCreateConfig;
19
+ let config = await cli.config([
20
+ {
21
+ key: 'organization_id',
22
+ prompt: 'Enter your organization_id: '
23
+ }, {
24
+ key: 'host',
25
+ prompt: 'Enter the host: '
26
+ }, {
27
+ prompt: 'Choose an authentication option: \n1.key\n2.Sign In\n',
28
+ choices: {
29
+ '1': {
30
+ key: 'key',
31
+ prompt: 'Enter your key: '
32
+ },
33
+ '2': [
34
+ {
35
+ key: 'email',
36
+ prompt: 'Enter your email: '
37
+ },
38
+ {
39
+ key: 'password',
40
+ prompt: 'Enter your password: '
41
+ }
42
+ ]
43
+ }
44
+ }
45
+ ])
46
+
47
+ if (!config.organization_id || !config.host || !config.key && (!config.password || config.email)) {
48
+ console.log('One or more required config params could not be found')
49
+ process.exit()
50
+ }
51
+
52
+
53
+ crud.socket.create(config)
54
+ config.broadcast = false
55
+
56
+ if (config.email && config.password) {
57
+ let request = {
58
+ collection: 'users',
59
+ filter: {
60
+ query: [
61
+ { name: 'email', value: config.email, operator: '$eq' },
62
+ { name: 'password', value: config.password, operator: '$eq' }
63
+ ]
64
+ }
65
+ }
66
+
67
+ let response = await crud.socket.send('signIn', request)
68
+ let { success, token } = response;
69
+
70
+ if (success) {
71
+ console.log('succesful sign in')
72
+ // apply token to socket
73
+ } else {
74
+ console.log('The email or password you entered is incorrect')
75
+ process.exit()
76
+
77
+ }
78
+
79
+ }
80
+
81
+ console.log('Uploading files...')
82
+
83
+ /**
84
+ * Store files by config directories
85
+ **/
86
+ let errorLog = [];
87
+
88
+ async function runDirectories() {
89
+ for (const directory of directories) {
90
+ const entry = directory.entry
91
+ const exclude = directory.exclude
92
+ await runFiles(directory, entry, exclude)
93
+ }
94
+ return
95
+ }
96
+
97
+ async function runFiles(directory, entry, exclude, parentDirectory = '') {
98
+ let files = fs.readdirSync(entry);
99
+
100
+ for (let file of files) {
101
+ if (exclude && exclude.includes(file)) continue
102
+
103
+ let isDirectory = fs.existsSync(`${entry}/${file}`) && fs.lstatSync(`${entry}/${file}`).isDirectory();
104
+ let name = file
105
+ let source = ''
106
+ let directoryName = parentDirectory || '';
107
+ let parentDirectoryOnly = parentDirectory || '';
108
+ let index = parentDirectoryOnly.lastIndexOf('/') + 1
109
+ if (parentDirectoryOnly && index) {
110
+ parentDirectoryOnly = parentDirectoryOnly.substring(index)
111
+ }
112
+ let mimeType = mime.lookup(`${file}`)
113
+ let pathName = '';
114
+
115
+ if (!directoryName && directory.document && directory.document.directory)
116
+ directoryName = directory.document.directory.replace('{{directory}}', '').trim()
117
+ else if (!directoryName)
118
+ directoryName = '/'
119
+
120
+ if (exclude && exclude.includes(directoryName)) continue
121
+
122
+ if (directoryName.endsWith("/"))
123
+ pathName = directoryName + name
124
+ else if (directoryName)
125
+ pathName = directoryName + '/' + name
126
+ else
127
+ pathName = '/' + name
128
+
129
+ if (exclude && exclude.includes(pathName)) continue
130
+
131
+ if (isDirectory)
132
+ mimeType = "text/directory"
133
+ else
134
+ source = getSource(`${entry}/${file}`, mimeType)
135
+
136
+ let values = {
137
+ '{{name}}': name,
138
+ '{{source}}': source,
139
+ '{{directory}}': directoryName,
140
+ '{{parentDirectory}}': parentDirectoryOnly,
141
+ '{{path}}': pathName,
142
+ '{{content-type}}': mimeType
143
+ }
144
+
145
+ let document = { ...directory.document }
146
+ if (!document.name)
147
+ document.name = "{{name}}"
148
+ if (!document.src)
149
+ document.src = "{{source}}"
150
+ if (!document.directory)
151
+ document.directory = "/{{directory}}"
152
+ if (!document.parentDirectory)
153
+ document.parentDirectory = "{{parentDirectory}}"
154
+ if (!document.path)
155
+ document.path = "{{path}}"
156
+ if (!document["content-type"])
157
+ document["content-type"] = '{{content-type}}'
158
+ if (!document.public && document.public != false && document.public != 'false')
159
+ document.public = 'true'
160
+
161
+ let object = {
162
+ collection: directory.collection || 'files',
163
+ document
164
+ }
165
+ for (const key of Object.keys(directory.document)) {
166
+ if (typeof directory.document[key] == 'string') {
167
+
168
+ let variables = directory.document[key].match(/{{([A-Za-z0-9_.,\[\]\-\/ ]*)}}/g);
169
+ if (variables) {
170
+ for (let variable of variables) {
171
+ if (variable == '{{directory}}') {
172
+ if (parentDirectory)
173
+ object.document[key] = values[variable]
174
+ else
175
+ object.document[key] = object.document[key].replace(variable, '');
176
+ }
177
+ else if (isDirectory && variable == '{{source}}')
178
+ delete object.document[key]
179
+ else
180
+ object.document[key] = object.document[key].replace(variable, values[variable]);
181
+ }
182
+ }
183
+
184
+ }
185
+ }
186
+
187
+ if (!object.document._id)
188
+ object.filter = {
189
+ query: [{ name: 'path', value: pathName, operator: '$eq' }]
190
+ }
191
+
192
+ response = await runStore(object);
193
+ if (response.error)
194
+ errorLog.push(response.error)
195
+
196
+ if (isDirectory && pathName) {
197
+ let newEntry
198
+ if (entry.endsWith("/"))
199
+ newEntry = entry + name
200
+ else
201
+ newEntry = entry + '/' + name
202
+
203
+ await runFiles(directory, newEntry, exclude, pathName)
204
+ }
205
+ }
206
+ if (errorLog.length)
207
+ console.log(...errorLog)
208
+
209
+ }
210
+
211
+
212
+ function getSource(path, mimeType) {
213
+ let readType = 'utf8'
214
+ if (/^(image|audio|video)\/[-+.\w]+/.test(mimeType))
215
+ readType = 'base64'
216
+
217
+ let binary = fs.readFileSync(path);
218
+ let content = new Buffer.from(binary).toString(readType);
219
+
220
+ return content
221
+ }
222
+
223
+ /**
224
+ * Store files by config sources
225
+ **/
226
+ async function runSources() {
227
+ let updatedSources = [];
228
+
229
+ for (let i = 0; i < sources.length; i++) {
230
+ const { collection, document } = sources[i];
231
+
232
+ let source = { ...sources[i] };
233
+ let keys = new Map()
234
+ let response = {};
235
+
236
+ try {
237
+ if (collection) {
238
+ if (!document)
239
+ document = {};
240
+ else
241
+ for (const key of Object.keys(document)) {
242
+ if (typeof document[key] != 'string')
243
+ continue
244
+
245
+ let variables = document[key].match(/{{([A-Za-z0-9_.,\[\]\-\/ ]*)}}/g);
246
+ if (variables) {
247
+ keys.set(key, `${document[key]}`)
248
+ let value = ""
249
+ for (let variable of variables) {
250
+ let entry = /{{\s*([\w\W]+)\s*}}/g.exec(variable);
251
+ entry = entry[1].trim()
252
+ if (entry) {
253
+ if (!fs.existsSync(entry))
254
+ continue
255
+
256
+ let read_type = 'utf8'
257
+ let mime_type = mime.lookup(entry) || 'text/html';
258
+ if (/^(image|audio|video)\/[-+.\w]+/.test(mime_type)) {
259
+ read_type = 'base64'
260
+ }
261
+
262
+ let binary = fs.readFileSync(entry);
263
+ let content = new Buffer.from(binary).toString(read_type);
264
+ if (content)
265
+ value += content
266
+ // document[key] = document[key].replace(variable, content);
267
+ }
268
+ }
269
+ document[key] = value
270
+ }
271
+
272
+ }
273
+
274
+ let data = { collection, document }
275
+ if (!document._id && document.path)
276
+ data.filter = {
277
+ query: [{ name: 'path', value: document.path, operator: '$eq' }]
278
+ }
279
+
280
+ response = await runStore(data);
281
+ }
282
+ } catch (err) {
283
+ console.log(err)
284
+ process.exit()
285
+ }
286
+ if (response.document && response.document[0] && response.document[0]._id) {
287
+ for (const [key, value] of keys) {
288
+ source.document[key] = value
289
+ }
290
+ source.document._id = response.document[0]._id
291
+ } else {
292
+ console.log('_id could not be found')
293
+ process.exit()
294
+ }
295
+
296
+ updatedSources.push(source)
297
+ }
298
+
299
+ return updatedSources
300
+ }
301
+
302
+
303
+ async function runStore(data) {
304
+ try {
305
+ let response;
306
+ if (!data.document._id && !data.filter) {
307
+ response = await crud.createDocument({
308
+ ...config,
309
+ ...data
310
+ })
311
+ } else {
312
+ response = await crud.updateDocument({
313
+ ...config,
314
+ ...data,
315
+ upsert: true
316
+ })
317
+ }
318
+ if (response) {
319
+ return response;
320
+ }
321
+ } catch (err) {
322
+ console.log(err);
323
+ return null;
324
+ }
325
+ }
326
+
327
+ async function run() {
328
+ if (directories)
329
+ await runDirectories()
330
+
331
+ if (sources) {
332
+ let sources = await runSources()
333
+ let newConfig = { ...CoCreateConfig }
334
+ if (directories)
335
+ newConfig.directories = directories
336
+
337
+ newConfig.sources = sources
338
+
339
+ delete newConfig.url
340
+ delete newConfig.broadcast
341
+ const write_str = `module.exports = ${JSON.stringify(newConfig, null, 4)};`;
342
+
343
+ fs.writeFileSync(configFile, write_str);
344
+ }
345
+
346
+ console.log('upload complete!');
347
+
348
+ setTimeout(function () {
349
+ process.exit()
350
+ }, 2000)
351
+ }
352
+
353
+ run()
354
+ }