virtualfile-cli 1.0.0
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/LICENSE +21 -0
- package/README.md +222 -0
- package/client/commands/delete.js +21 -0
- package/client/commands/edit.js +35 -0
- package/client/commands/list.js +26 -0
- package/client/commands/read.js +24 -0
- package/client/commands/write.js +35 -0
- package/client/index.js +78 -0
- package/client/services/api.js +59 -0
- package/client/utils/editor/buffer.js +85 -0
- package/client/utils/editor/cursor.js +82 -0
- package/client/utils/editor/index.js +33 -0
- package/client/utils/editor/input.js +135 -0
- package/client/utils/editor/renderer.js +89 -0
- package/client/utils/editor/viewport.js +48 -0
- package/client/utils/logger.js +29 -0
- package/package.json +38 -0
- package/server/app.js +25 -0
- package/server/config/db.js +14 -0
- package/server/controllers/fileController.js +100 -0
- package/server/models/File.js +21 -0
- package/server/routes/fileRoutes.js +16 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sudesh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# ViewFile
|
|
2
|
+
|
|
3
|
+
ViewFile is a command-line interface (CLI) application that allows you to create, read, edit, and delete text files stored in the cloud. It provides a simple way to access your notes or text content from any machine without relying on local storage.
|
|
4
|
+
|
|
5
|
+
The project consists of a Node.js CLI client and a REST API backend, making it lightweight, fast, and easy to use from the terminal.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Create cloud-based text files
|
|
12
|
+
- Read files directly from the terminal
|
|
13
|
+
- Edit existing files using your default text editor
|
|
14
|
+
- Delete files
|
|
15
|
+
- Simple and intuitive command-line interface
|
|
16
|
+
- Cross-platform support (Windows and Linux)
|
|
17
|
+
- REST API powered backend
|
|
18
|
+
- Global CLI installation via npm
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
### Prerequisites
|
|
25
|
+
|
|
26
|
+
- Node.js 18 or later
|
|
27
|
+
- npm
|
|
28
|
+
|
|
29
|
+
### Install globally
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm install -g viewfile
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Verify the installation:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
vf --version
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
### Create a file
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
vf -w <filename>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Example:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
vf -w notes
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
This creates a new cloud file and opens your default editor to write its contents.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
### Read a file
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
vf -r <filename>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
vf -r notes
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Displays the contents of the specified file.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### Edit a file
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
vf -e <filename>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Example:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
vf -e notes
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Opens the existing file in your default editor. Changes are automatically saved back to the cloud.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
### Delete a file
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
vf -rm <filename>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Example:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
vf -rm notes
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Deletes the specified file from the server.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Command Reference
|
|
110
|
+
|
|
111
|
+
| Command | Description |
|
|
112
|
+
|---------|-------------|
|
|
113
|
+
| `vf -w <filename>` | Create a new file |
|
|
114
|
+
| `vf -r <filename>` | Read an existing file |
|
|
115
|
+
| `vf -e <filename>` | Edit an existing file |
|
|
116
|
+
| `vf -rm <filename>` | Delete a file |
|
|
117
|
+
| `vf --help` | Display help information |
|
|
118
|
+
| `vf --version` | Display the installed version |
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Example Workflow
|
|
123
|
+
|
|
124
|
+
Create a file:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
vf -w todo
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Read the file:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
vf -r todo
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Edit the file:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
vf -e todo
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Delete the file:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
vf -rm todo
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Use Cases
|
|
151
|
+
|
|
152
|
+
- Store quick notes accessible from anywhere
|
|
153
|
+
- Maintain terminal-based documentation
|
|
154
|
+
- Save code snippets
|
|
155
|
+
- Store TODO lists
|
|
156
|
+
- Keep configuration notes
|
|
157
|
+
- Manage text without depending on local files
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Technology Stack
|
|
162
|
+
|
|
163
|
+
### Client
|
|
164
|
+
|
|
165
|
+
- Node.js
|
|
166
|
+
- Axios
|
|
167
|
+
- Chalk
|
|
168
|
+
- Dotenv
|
|
169
|
+
|
|
170
|
+
### Server
|
|
171
|
+
|
|
172
|
+
- Node.js
|
|
173
|
+
- Express.js
|
|
174
|
+
- MongoDB Atlas
|
|
175
|
+
- Mongoose
|
|
176
|
+
|
|
177
|
+
### Deployment
|
|
178
|
+
|
|
179
|
+
- Render
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Project Structure
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
ViewFile/
|
|
187
|
+
├── client/
|
|
188
|
+
│ ├── commands/
|
|
189
|
+
│ ├── services/
|
|
190
|
+
│ ├── utils/
|
|
191
|
+
│ └── index.js
|
|
192
|
+
│
|
|
193
|
+
├── server/
|
|
194
|
+
│ ├── controllers/
|
|
195
|
+
│ ├── models/
|
|
196
|
+
│ ├── routes/
|
|
197
|
+
│ ├── config/
|
|
198
|
+
│ └── app.js
|
|
199
|
+
│
|
|
200
|
+
├── package.json
|
|
201
|
+
└── README.md
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Requirements
|
|
207
|
+
|
|
208
|
+
- Node.js 18+
|
|
209
|
+
- Internet connection
|
|
210
|
+
- npm
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
This project is licensed under the MIT License.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Author
|
|
221
|
+
|
|
222
|
+
Developed by Sudesh.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const { deleteFile } = require("../services/api");
|
|
2
|
+
const logger = require("../utils/logger");
|
|
3
|
+
|
|
4
|
+
async function remove(filename) {
|
|
5
|
+
if (!filename) {
|
|
6
|
+
logger.error("Usage: vf -rm <filename>");
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
logger.info(`Deleting "${filename}"...`);
|
|
12
|
+
|
|
13
|
+
await deleteFile(filename);
|
|
14
|
+
|
|
15
|
+
logger.success("File deleted successfully.");
|
|
16
|
+
} catch (err) {
|
|
17
|
+
logger.error(err.response?.data?.message || err.message);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = remove;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const openEditor = require("../utils/editor");
|
|
2
|
+
const {
|
|
3
|
+
getFile,
|
|
4
|
+
updateFile,
|
|
5
|
+
} = require("../services/api");
|
|
6
|
+
const logger = require("../utils/logger");
|
|
7
|
+
|
|
8
|
+
async function edit(filename) {
|
|
9
|
+
|
|
10
|
+
if (!filename) {
|
|
11
|
+
logger.error("Usage: vf -e <filename>");
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
|
|
17
|
+
const file = await getFile(filename);
|
|
18
|
+
|
|
19
|
+
logger.info("Opening editor...");
|
|
20
|
+
|
|
21
|
+
const updatedContent = await openEditor(
|
|
22
|
+
filename,
|
|
23
|
+
file.content
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
await updateFile(filename, updatedContent);
|
|
27
|
+
|
|
28
|
+
logger.success("File updated successfully.");
|
|
29
|
+
|
|
30
|
+
} catch (err) {
|
|
31
|
+
logger.error(err.response?.data?.message || err.message);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = edit;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
const { listFiles } = require("../services/api");
|
|
3
|
+
const logger = require("../utils/logger");
|
|
4
|
+
|
|
5
|
+
async function list() {
|
|
6
|
+
try {
|
|
7
|
+
const files = await listFiles();
|
|
8
|
+
|
|
9
|
+
if (files.length === 0) {
|
|
10
|
+
logger.warning("No files found.");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
logger.title(`Files (${files.length})`);
|
|
15
|
+
console.log(chalk.blue("─".repeat(60)));
|
|
16
|
+
|
|
17
|
+
files.forEach(file => {
|
|
18
|
+
console.log(`${chalk.green("📄")} ${chalk.white(file.filename)}`);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
} catch (err) {
|
|
22
|
+
logger.error(err.response?.data?.message || err.message);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = list;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
const { getFile } = require("../services/api");
|
|
3
|
+
const logger = require("../utils/logger");
|
|
4
|
+
|
|
5
|
+
async function read(filename) {
|
|
6
|
+
if (!filename) {
|
|
7
|
+
logger.error("Usage: vf -r <filename>");
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const file = await getFile(filename);
|
|
13
|
+
|
|
14
|
+
console.log(chalk.blue("─".repeat(60)));
|
|
15
|
+
logger.title(filename);
|
|
16
|
+
console.log(chalk.blue("─".repeat(60)));
|
|
17
|
+
console.log(file.content);
|
|
18
|
+
|
|
19
|
+
} catch (err) {
|
|
20
|
+
logger.error(err.response?.data?.message || err.message);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = read;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const openEditor = require("../utils/editor");
|
|
2
|
+
const {
|
|
3
|
+
fileExists,
|
|
4
|
+
createFile,
|
|
5
|
+
} = require("../services/api");
|
|
6
|
+
const logger = require("../utils/logger");
|
|
7
|
+
|
|
8
|
+
async function write(filename) {
|
|
9
|
+
if (!filename) {
|
|
10
|
+
logger.error("Usage: vf -w <filename>");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const exists = await fileExists(filename);
|
|
15
|
+
|
|
16
|
+
if (exists) {
|
|
17
|
+
logger.warning(`File "${filename}" already exists.`);
|
|
18
|
+
logger.info(`Use: vf -e ${filename}`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
logger.info("Opening editor...");
|
|
24
|
+
|
|
25
|
+
const content = await openEditor(filename);
|
|
26
|
+
|
|
27
|
+
await createFile(filename, content);
|
|
28
|
+
|
|
29
|
+
logger.success("File saved successfully.");
|
|
30
|
+
} catch (err) {
|
|
31
|
+
logger.error(err.message);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = write;
|
package/client/index.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
require("dotenv").config({ quiet: true });
|
|
3
|
+
const write = require("./commands/write");
|
|
4
|
+
const read = require("./commands/read");
|
|
5
|
+
const list = require("./commands/list");
|
|
6
|
+
const remove = require("./commands/delete");
|
|
7
|
+
const edit = require("./commands/edit");
|
|
8
|
+
const logger = require("./utils/logger");
|
|
9
|
+
|
|
10
|
+
const { version } = require("../package.json");
|
|
11
|
+
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
|
|
14
|
+
function showHelp() {
|
|
15
|
+
logger.title("ViewFile CLI");
|
|
16
|
+
console.log(`Version ${version}
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
vf -w <filename> Create a new file
|
|
20
|
+
vf -e <filename> Edit an existing file
|
|
21
|
+
vf -r <filename> Read a file
|
|
22
|
+
vf -rm <filename> Delete a file
|
|
23
|
+
|
|
24
|
+
Options:
|
|
25
|
+
--help Show this help message
|
|
26
|
+
--version Show CLI version
|
|
27
|
+
`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function main() {
|
|
31
|
+
|
|
32
|
+
if (args.length === 0) {
|
|
33
|
+
showHelp();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const command = args[0];
|
|
38
|
+
|
|
39
|
+
switch (command) {
|
|
40
|
+
|
|
41
|
+
case "-w":
|
|
42
|
+
await write(args[1]);
|
|
43
|
+
break;
|
|
44
|
+
|
|
45
|
+
case "-e":
|
|
46
|
+
await edit(args[1]);
|
|
47
|
+
break;
|
|
48
|
+
|
|
49
|
+
case "-r":
|
|
50
|
+
await read(args[1]);
|
|
51
|
+
break;
|
|
52
|
+
|
|
53
|
+
case "-rm":
|
|
54
|
+
await remove(args[1]);
|
|
55
|
+
break;
|
|
56
|
+
|
|
57
|
+
// case "ls":
|
|
58
|
+
// await list();
|
|
59
|
+
// break;
|
|
60
|
+
|
|
61
|
+
case "--help":
|
|
62
|
+
case "-h":
|
|
63
|
+
showHelp();
|
|
64
|
+
break;
|
|
65
|
+
|
|
66
|
+
case "--version":
|
|
67
|
+
case "-v":
|
|
68
|
+
console.log(`ViewFile CLI v${version}`);
|
|
69
|
+
break;
|
|
70
|
+
|
|
71
|
+
default:
|
|
72
|
+
logger.error(`Unknown command "${command}"`);
|
|
73
|
+
console.log();
|
|
74
|
+
logger.info('Run "vf --help" to see available commands.');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
main();
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const axios = require("axios");
|
|
2
|
+
|
|
3
|
+
const BASE_URL = process.env.API_URL || "https://viewfile-6xjx.onrender.com";
|
|
4
|
+
|
|
5
|
+
const API = `${BASE_URL}/files`;
|
|
6
|
+
|
|
7
|
+
async function createFile(filename, content) {
|
|
8
|
+
const res = await axios.post(API, {
|
|
9
|
+
filename,
|
|
10
|
+
content,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
return res.data;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function updateFile(filename, content) {
|
|
17
|
+
const res = await axios.put(`${API}/${filename}`, {
|
|
18
|
+
content,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return res.data;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function getFile(filename) {
|
|
25
|
+
const res = await axios.get(`${API}/${filename}`);
|
|
26
|
+
return res.data;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function fileExists(filename) {
|
|
30
|
+
try {
|
|
31
|
+
await axios.get(`${API}/${filename}`);
|
|
32
|
+
return true;
|
|
33
|
+
} catch (err) {
|
|
34
|
+
if (err.response?.status === 404) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function listFiles() {
|
|
43
|
+
const res = await axios.get(API);
|
|
44
|
+
return res.data;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function deleteFile(filename) {
|
|
48
|
+
const res = await axios.delete(`${API}/${filename}`);
|
|
49
|
+
return res.data;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
createFile,
|
|
54
|
+
updateFile,
|
|
55
|
+
getFile,
|
|
56
|
+
listFiles,
|
|
57
|
+
deleteFile,
|
|
58
|
+
fileExists
|
|
59
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
class Buffer {
|
|
2
|
+
|
|
3
|
+
constructor(initialContent = "") {
|
|
4
|
+
|
|
5
|
+
this.lines =
|
|
6
|
+
initialContent.length > 0
|
|
7
|
+
? initialContent.split("\n")
|
|
8
|
+
: [""];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
line(y) {
|
|
12
|
+
return this.lines[y];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
lineCount() {
|
|
16
|
+
return this.lines.length;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
insert(x, y, char) {
|
|
20
|
+
|
|
21
|
+
const line = this.lines[y];
|
|
22
|
+
|
|
23
|
+
this.lines[y] =
|
|
24
|
+
line.slice(0, x) +
|
|
25
|
+
char +
|
|
26
|
+
line.slice(x);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
deleteBackward(x, y) {
|
|
30
|
+
|
|
31
|
+
if (x > 0) {
|
|
32
|
+
|
|
33
|
+
const line = this.lines[y];
|
|
34
|
+
|
|
35
|
+
this.lines[y] =
|
|
36
|
+
line.slice(0, x - 1) +
|
|
37
|
+
line.slice(x);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
x: x - 1,
|
|
41
|
+
y
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (y > 0) {
|
|
46
|
+
|
|
47
|
+
const previousLength =
|
|
48
|
+
this.lines[y - 1].length;
|
|
49
|
+
|
|
50
|
+
this.lines[y - 1] += this.lines[y];
|
|
51
|
+
|
|
52
|
+
this.lines.splice(y, 1);
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
x: previousLength,
|
|
56
|
+
y: y - 1
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { x, y };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
newline(x, y) {
|
|
64
|
+
|
|
65
|
+
const current = this.lines[y];
|
|
66
|
+
|
|
67
|
+
const left = current.slice(0, x);
|
|
68
|
+
const right = current.slice(x);
|
|
69
|
+
|
|
70
|
+
this.lines[y] = left;
|
|
71
|
+
|
|
72
|
+
this.lines.splice(y + 1, 0, right);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
x: 0,
|
|
76
|
+
y: y + 1
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
toString() {
|
|
81
|
+
return this.lines.join("\n");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = Buffer;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
class Cursor {
|
|
2
|
+
|
|
3
|
+
constructor(buffer) {
|
|
4
|
+
this.buffer = buffer;
|
|
5
|
+
|
|
6
|
+
this.x = 0;
|
|
7
|
+
this.y = 0;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
loadToEnd() {
|
|
11
|
+
this.y = this.buffer.lineCount() - 1;
|
|
12
|
+
this.x = this.buffer.line(this.y).length;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
left() {
|
|
16
|
+
|
|
17
|
+
if (this.x > 0) {
|
|
18
|
+
this.x--;
|
|
19
|
+
}
|
|
20
|
+
else if (this.y > 0) {
|
|
21
|
+
this.y--;
|
|
22
|
+
this.x = this.buffer.line(this.y).length;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
right() {
|
|
27
|
+
|
|
28
|
+
if (this.x < this.buffer.line(this.y).length) {
|
|
29
|
+
this.x++;
|
|
30
|
+
}
|
|
31
|
+
else if (this.y < this.buffer.lineCount() - 1) {
|
|
32
|
+
this.y++;
|
|
33
|
+
this.x = 0;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
up() {
|
|
38
|
+
|
|
39
|
+
if (this.y > 0) {
|
|
40
|
+
this.y--;
|
|
41
|
+
|
|
42
|
+
this.x = Math.min(
|
|
43
|
+
this.x,
|
|
44
|
+
this.buffer.line(this.y).length
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
down() {
|
|
50
|
+
|
|
51
|
+
if (this.y < this.buffer.lineCount() - 1) {
|
|
52
|
+
this.y++;
|
|
53
|
+
|
|
54
|
+
this.x = Math.min(
|
|
55
|
+
this.x,
|
|
56
|
+
this.buffer.line(this.y).length
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
home() {
|
|
62
|
+
this.x = 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
end() {
|
|
66
|
+
this.x = this.buffer.line(this.y).length;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
set(x, y) {
|
|
70
|
+
this.x = x;
|
|
71
|
+
this.y = y;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
position() {
|
|
75
|
+
return {
|
|
76
|
+
x: this.x,
|
|
77
|
+
y: this.y
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = Cursor;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const { terminal: term } = require("terminal-kit");
|
|
2
|
+
|
|
3
|
+
const Buffer = require("./buffer");
|
|
4
|
+
const Cursor = require("./cursor");
|
|
5
|
+
const Viewport = require("./viewport");
|
|
6
|
+
const Renderer = require("./renderer");
|
|
7
|
+
const Input = require("./input");
|
|
8
|
+
|
|
9
|
+
async function openEditor(filename, initialContent = "") {
|
|
10
|
+
|
|
11
|
+
const buffer = new Buffer(initialContent);
|
|
12
|
+
|
|
13
|
+
const cursor = new Cursor(buffer);
|
|
14
|
+
cursor.loadToEnd();
|
|
15
|
+
|
|
16
|
+
const viewport = new Viewport(term, 5);
|
|
17
|
+
|
|
18
|
+
const renderer = new Renderer(
|
|
19
|
+
buffer,
|
|
20
|
+
cursor,
|
|
21
|
+
viewport
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const input = new Input(
|
|
25
|
+
buffer,
|
|
26
|
+
cursor,
|
|
27
|
+
renderer
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
return await input.start(filename);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports = openEditor;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
const { terminal: term } = require("terminal-kit");
|
|
2
|
+
|
|
3
|
+
class Input {
|
|
4
|
+
|
|
5
|
+
constructor(buffer, cursor, renderer) {
|
|
6
|
+
this.buffer = buffer;
|
|
7
|
+
this.cursor = cursor;
|
|
8
|
+
this.renderer = renderer;
|
|
9
|
+
|
|
10
|
+
this.renderScheduled = false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
scheduleRender(filename) {
|
|
14
|
+
|
|
15
|
+
if (this.renderScheduled) return;
|
|
16
|
+
|
|
17
|
+
this.renderScheduled = true;
|
|
18
|
+
|
|
19
|
+
setImmediate(() => {
|
|
20
|
+
this.renderer.draw(filename);
|
|
21
|
+
this.renderScheduled = false;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
start(filename) {
|
|
26
|
+
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
|
|
29
|
+
// Initial render
|
|
30
|
+
this.renderer.draw(filename);
|
|
31
|
+
|
|
32
|
+
term.grabInput(true);
|
|
33
|
+
|
|
34
|
+
const cleanup = () => {
|
|
35
|
+
|
|
36
|
+
term.removeListener("key", onKey);
|
|
37
|
+
term.grabInput(false);
|
|
38
|
+
|
|
39
|
+
if (typeof term.hideCursor === "function") {
|
|
40
|
+
term.hideCursor(false);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
term.styleReset();
|
|
44
|
+
term.clear();
|
|
45
|
+
term.moveTo(1, 1);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const onKey = (name) => {
|
|
49
|
+
|
|
50
|
+
switch (name) {
|
|
51
|
+
|
|
52
|
+
case "CTRL_S":
|
|
53
|
+
cleanup();
|
|
54
|
+
resolve(this.buffer.toString());
|
|
55
|
+
return;
|
|
56
|
+
|
|
57
|
+
case "ESCAPE":
|
|
58
|
+
cleanup();
|
|
59
|
+
reject(new Error("Cancelled"));
|
|
60
|
+
return;
|
|
61
|
+
|
|
62
|
+
case "LEFT":
|
|
63
|
+
this.cursor.left();
|
|
64
|
+
break;
|
|
65
|
+
|
|
66
|
+
case "RIGHT":
|
|
67
|
+
this.cursor.right();
|
|
68
|
+
break;
|
|
69
|
+
|
|
70
|
+
case "UP":
|
|
71
|
+
this.cursor.up();
|
|
72
|
+
break;
|
|
73
|
+
|
|
74
|
+
case "DOWN":
|
|
75
|
+
this.cursor.down();
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case "HOME":
|
|
79
|
+
this.cursor.home();
|
|
80
|
+
break;
|
|
81
|
+
|
|
82
|
+
case "END":
|
|
83
|
+
this.cursor.end();
|
|
84
|
+
break;
|
|
85
|
+
|
|
86
|
+
case "ENTER": {
|
|
87
|
+
|
|
88
|
+
const pos = this.buffer.newline(
|
|
89
|
+
this.cursor.x,
|
|
90
|
+
this.cursor.y
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
this.cursor.set(pos.x, pos.y);
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
case "BACKSPACE": {
|
|
98
|
+
|
|
99
|
+
const pos = this.buffer.deleteBackward(
|
|
100
|
+
this.cursor.x,
|
|
101
|
+
this.cursor.y
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
this.cursor.set(pos.x, pos.y);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
default:
|
|
109
|
+
|
|
110
|
+
if (name.length === 1) {
|
|
111
|
+
|
|
112
|
+
this.buffer.insert(
|
|
113
|
+
this.cursor.x,
|
|
114
|
+
this.cursor.y,
|
|
115
|
+
name
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
this.cursor.x++;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
this.scheduleRender(filename);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
term.on("key", onKey);
|
|
128
|
+
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = Input;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const { terminal: term } = require("terminal-kit");
|
|
2
|
+
|
|
3
|
+
class Renderer {
|
|
4
|
+
constructor(buffer, cursor, viewport) {
|
|
5
|
+
this.buffer = buffer;
|
|
6
|
+
this.cursor = cursor;
|
|
7
|
+
this.viewport = viewport;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
draw(filename) {
|
|
11
|
+
|
|
12
|
+
// Hide cursor while drawing
|
|
13
|
+
if (term.hideCursor) {
|
|
14
|
+
term.hideCursor(true);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
term.moveTo(1, 1);
|
|
18
|
+
|
|
19
|
+
// Some versions don't support eraseDisplayBelow()
|
|
20
|
+
if (typeof term.eraseDisplayBelow === "function") {
|
|
21
|
+
term.eraseDisplayBelow();
|
|
22
|
+
} else {
|
|
23
|
+
term.clear();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Header
|
|
27
|
+
term.bold.cyan("ViewFile\n");
|
|
28
|
+
|
|
29
|
+
term.gray("Editing: ");
|
|
30
|
+
term.green(filename);
|
|
31
|
+
term("\n");
|
|
32
|
+
|
|
33
|
+
term.gray("Ctrl+S Save | Esc Cancel | Arrows Move\n");
|
|
34
|
+
term("──────────────────────────────────────────────\n");
|
|
35
|
+
|
|
36
|
+
// Keep cursor visible
|
|
37
|
+
this.viewport.update(this.cursor.y);
|
|
38
|
+
|
|
39
|
+
const start = this.viewport.firstLine();
|
|
40
|
+
const end = this.viewport.lastLine(this.buffer);
|
|
41
|
+
|
|
42
|
+
// Draw only visible lines
|
|
43
|
+
for (let i = start; i < end; i++) {
|
|
44
|
+
|
|
45
|
+
const lineNumber = String(i + 1).padStart(4);
|
|
46
|
+
|
|
47
|
+
term.gray(lineNumber);
|
|
48
|
+
term.gray(" │ ");
|
|
49
|
+
|
|
50
|
+
term(this.buffer.line(i));
|
|
51
|
+
term("\n");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Fill remaining editor area
|
|
55
|
+
const drawn = end - start;
|
|
56
|
+
const remaining = Math.max(
|
|
57
|
+
0,
|
|
58
|
+
this.viewport.visibleHeight() - drawn - 1
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
for (let i = 0; i < remaining; i++) {
|
|
62
|
+
term.gray(" │\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Draw status bar at bottom
|
|
66
|
+
term.moveTo(1, term.height);
|
|
67
|
+
|
|
68
|
+
term.moveTo(1, term.height);
|
|
69
|
+
|
|
70
|
+
term.eraseLine();
|
|
71
|
+
|
|
72
|
+
term.inverse(
|
|
73
|
+
` Ln ${this.cursor.y + 1} | Col ${this.cursor.x + 1} | ${this.buffer.lineCount()} lines `
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
// Cursor position
|
|
77
|
+
term.moveTo(
|
|
78
|
+
this.cursor.x + 8,
|
|
79
|
+
this.viewport.screenY(this.cursor.y)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// Show cursor again
|
|
83
|
+
if (term.hideCursor) {
|
|
84
|
+
term.hideCursor(false);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = Renderer;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
class Viewport {
|
|
2
|
+
|
|
3
|
+
constructor(term, headerHeight = 6) {
|
|
4
|
+
|
|
5
|
+
this.term = term;
|
|
6
|
+
this.headerHeight = headerHeight;
|
|
7
|
+
|
|
8
|
+
this.scrollY = 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
visibleHeight() {
|
|
12
|
+
return this.term.height - this.headerHeight;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
update(cursorY) {
|
|
16
|
+
|
|
17
|
+
const visible = this.visibleHeight();
|
|
18
|
+
|
|
19
|
+
// Scroll up
|
|
20
|
+
if (cursorY < this.scrollY) {
|
|
21
|
+
this.scrollY = cursorY;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Scroll down
|
|
25
|
+
else if (cursorY >= this.scrollY + visible) {
|
|
26
|
+
this.scrollY = cursorY - visible + 1;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
firstLine() {
|
|
31
|
+
return this.scrollY;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
lastLine(buffer) {
|
|
35
|
+
|
|
36
|
+
return Math.min(
|
|
37
|
+
buffer.lineCount(),
|
|
38
|
+
this.scrollY + this.visibleHeight()
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
screenY(cursorY) {
|
|
43
|
+
return (cursorY - this.scrollY) + this.headerHeight;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = Viewport;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const chalk = require("chalk");
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
|
|
5
|
+
success(message) {
|
|
6
|
+
console.log(chalk.green(`✓ ${message}`));
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
error(message) {
|
|
10
|
+
console.log(chalk.red(`✗ ${message}`));
|
|
11
|
+
},
|
|
12
|
+
|
|
13
|
+
warning(message) {
|
|
14
|
+
console.log(chalk.yellow(`⚠ ${message}`));
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
info(message) {
|
|
18
|
+
console.log(chalk.cyan(`ℹ ${message}`));
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
title(message) {
|
|
22
|
+
console.log(chalk.bold.blue(message));
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
plain(message) {
|
|
26
|
+
console.log(message);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "virtualfile-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A terminal-based cloud text file storage CLI.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"vf": "client/index.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"start": "node server/app.js",
|
|
10
|
+
"dev": "nodemon server/app.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"cli",
|
|
14
|
+
"terminal",
|
|
15
|
+
"text",
|
|
16
|
+
"cloud",
|
|
17
|
+
"notes",
|
|
18
|
+
"nodejs"
|
|
19
|
+
],
|
|
20
|
+
"author": "Sudesh",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"type": "commonjs",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"axios": "^1.18.1",
|
|
25
|
+
"bcryptjs": "^3.0.3",
|
|
26
|
+
"chalk": "^4.1.2",
|
|
27
|
+
"cors": "^2.8.6",
|
|
28
|
+
"dotenv": "^17.4.2",
|
|
29
|
+
"express": "^5.2.1",
|
|
30
|
+
"jsonwebtoken": "^9.0.3",
|
|
31
|
+
"mongoose": "^9.7.4",
|
|
32
|
+
"ora": "^5.4.1",
|
|
33
|
+
"terminal-kit": "^3.1.4"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"nodemon": "^3.1.14"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/server/app.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const dotenv = require("dotenv");
|
|
3
|
+
const cors = require("cors");
|
|
4
|
+
|
|
5
|
+
const connectDB = require("./config/db");
|
|
6
|
+
const fileRoutes = require("./routes/fileRoutes");
|
|
7
|
+
|
|
8
|
+
dotenv.config();
|
|
9
|
+
|
|
10
|
+
connectDB();
|
|
11
|
+
|
|
12
|
+
const app = express();
|
|
13
|
+
|
|
14
|
+
app.use(cors());
|
|
15
|
+
app.use(express.json());
|
|
16
|
+
app.use("/files", fileRoutes);
|
|
17
|
+
app.get("/", (req, res) => {
|
|
18
|
+
res.send("ViewFile API Running...");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const PORT = process.env.PORT || 5000;
|
|
22
|
+
|
|
23
|
+
app.listen(PORT, () => {
|
|
24
|
+
console.log(`🚀 Server running on port ${PORT}`);
|
|
25
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const mongoose = require("mongoose");
|
|
2
|
+
|
|
3
|
+
const connectDB = async () => {
|
|
4
|
+
try {
|
|
5
|
+
await mongoose.connect(process.env.MONGO_URI);
|
|
6
|
+
console.log("MongoDB Connected");
|
|
7
|
+
} catch (error) {
|
|
8
|
+
console.error(" Database Connection Failed");
|
|
9
|
+
console.error(error.message);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
module.exports = connectDB;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const File = require("../models/File");
|
|
2
|
+
|
|
3
|
+
const createFile = async (req, res) => {
|
|
4
|
+
try {
|
|
5
|
+
const { filename, content } = req.body;
|
|
6
|
+
|
|
7
|
+
const existing = await File.findOne({ filename });
|
|
8
|
+
|
|
9
|
+
if (existing) {
|
|
10
|
+
return res.status(409).json({
|
|
11
|
+
message: "File already exists",
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const file = await File.create({
|
|
16
|
+
filename,
|
|
17
|
+
content,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
res.status(201).json(file);
|
|
21
|
+
|
|
22
|
+
} catch (error) {
|
|
23
|
+
res.status(500).json({
|
|
24
|
+
message: error.message,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getFile = async (req, res) => {
|
|
30
|
+
try {
|
|
31
|
+
const file = await File.findOne({ filename: req.params.filename });
|
|
32
|
+
|
|
33
|
+
if (!file) {
|
|
34
|
+
return res.status(404).json({ message: "File not found" });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
res.json(file);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
res.status(500).json({ message: error.message });
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const deleteFile = async (req, res) => {
|
|
44
|
+
try {
|
|
45
|
+
const file = await File.findOneAndDelete({
|
|
46
|
+
filename: req.params.filename,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!file) {
|
|
50
|
+
return res.status(404).json({ message: "File not found" });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
res.json({ message: "File deleted successfully" });
|
|
54
|
+
} catch (error) {
|
|
55
|
+
res.status(500).json({ message: error.message });
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const listFiles = async (req, res) => {
|
|
60
|
+
try {
|
|
61
|
+
const files = await File.find({}, "filename");
|
|
62
|
+
res.json(files);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
res.status(500).json({ message: error.message });
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const updateFile = async (req, res) => {
|
|
69
|
+
try {
|
|
70
|
+
|
|
71
|
+
const file = await File.findOne({
|
|
72
|
+
filename: req.params.filename,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!file) {
|
|
76
|
+
return res.status(404).json({
|
|
77
|
+
message: "File not found",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
file.content = req.body.content;
|
|
82
|
+
|
|
83
|
+
await file.save();
|
|
84
|
+
|
|
85
|
+
res.json(file);
|
|
86
|
+
|
|
87
|
+
} catch (error) {
|
|
88
|
+
res.status(500).json({
|
|
89
|
+
message: error.message,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
module.exports = {
|
|
95
|
+
createFile,
|
|
96
|
+
getFile,
|
|
97
|
+
deleteFile,
|
|
98
|
+
updateFile,
|
|
99
|
+
listFiles
|
|
100
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const mongoose = require("mongoose");
|
|
2
|
+
|
|
3
|
+
const fileSchema = new mongoose.Schema(
|
|
4
|
+
{
|
|
5
|
+
filename: {
|
|
6
|
+
type: String,
|
|
7
|
+
required: true,
|
|
8
|
+
trim: true,
|
|
9
|
+
unique: true
|
|
10
|
+
},
|
|
11
|
+
content: {
|
|
12
|
+
type: String,
|
|
13
|
+
required: true,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
timestamps: true,
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
module.exports = mongoose.model("File", fileSchema);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
|
|
4
|
+
const { createFile, getFile, deleteFile, listFiles, updateFile } = require("../controllers/fileController");
|
|
5
|
+
|
|
6
|
+
router.post("/", createFile);
|
|
7
|
+
|
|
8
|
+
router.put("/:filename", updateFile);
|
|
9
|
+
|
|
10
|
+
router.get("/", listFiles);
|
|
11
|
+
|
|
12
|
+
router.get("/:filename", getFile);
|
|
13
|
+
|
|
14
|
+
router.delete("/:filename", deleteFile);
|
|
15
|
+
|
|
16
|
+
module.exports = router;
|