@learnpack/learnpack 4.0.10 → 4.0.13
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +16 -20
- package/lib/commands/audit.d.ts +6 -6
- package/lib/commands/audit.js +327 -327
- package/lib/commands/clean.d.ts +8 -8
- package/lib/commands/clean.js +22 -22
- package/lib/commands/download.d.ts +13 -13
- package/lib/commands/download.js +52 -52
- package/lib/commands/init.d.ts +9 -9
- package/lib/commands/init.js +127 -127
- package/lib/commands/login.d.ts +14 -14
- package/lib/commands/login.js +34 -34
- package/lib/commands/logout.d.ts +14 -14
- package/lib/commands/logout.js +34 -34
- package/lib/commands/publish.d.ts +11 -14
- package/lib/commands/publish.js +160 -82
- package/lib/commands/start.d.ts +7 -7
- package/lib/commands/start.js +252 -250
- package/lib/commands/test.d.ts +6 -6
- package/lib/commands/test.js +62 -62
- package/lib/index.d.ts +1 -1
- package/lib/index.js +4 -4
- package/lib/managers/config/allowed_files.d.ts +5 -5
- package/lib/managers/config/allowed_files.js +30 -30
- package/lib/managers/config/defaults.d.ts +47 -48
- package/lib/managers/config/defaults.js +51 -51
- package/lib/managers/config/exercise.d.ts +36 -36
- package/lib/managers/config/exercise.js +243 -236
- package/lib/managers/config/index.d.ts +3 -3
- package/lib/managers/config/index.js +464 -459
- package/lib/managers/file.d.ts +14 -14
- package/lib/managers/file.js +190 -184
- package/lib/managers/gitpod.d.ts +3 -3
- package/lib/managers/gitpod.js +67 -67
- package/lib/managers/server/index.d.ts +5 -6
- package/lib/managers/server/index.js +58 -58
- package/lib/managers/server/routes.d.ts +4 -4
- package/lib/managers/server/routes.js +228 -220
- package/lib/managers/session.d.ts +3 -3
- package/lib/managers/session.js +125 -125
- package/lib/managers/socket.d.ts +3 -3
- package/lib/managers/socket.js +188 -186
- package/lib/managers/telemetry.d.ts +74 -74
- package/lib/managers/telemetry.js +215 -214
- package/lib/managers/test.js +84 -84
- package/lib/models/action.d.ts +2 -2
- package/lib/models/action.js +2 -2
- package/lib/models/audit.d.ts +15 -15
- package/lib/models/audit.js +2 -2
- package/lib/models/config-manager.d.ts +21 -21
- package/lib/models/config-manager.js +2 -2
- package/lib/models/config.d.ts +86 -86
- package/lib/models/config.js +2 -2
- package/lib/models/counter.d.ts +11 -11
- package/lib/models/counter.js +2 -2
- package/lib/models/errors.d.ts +15 -15
- package/lib/models/errors.js +2 -2
- package/lib/models/exercise-obj.d.ts +29 -30
- package/lib/models/exercise-obj.js +2 -2
- package/lib/models/file.d.ts +5 -5
- package/lib/models/file.js +2 -2
- package/lib/models/findings.d.ts +17 -17
- package/lib/models/findings.js +2 -2
- package/lib/models/flags.d.ts +10 -10
- package/lib/models/flags.js +2 -2
- package/lib/models/front-matter.d.ts +11 -11
- package/lib/models/front-matter.js +2 -2
- package/lib/models/gitpod-data.d.ts +16 -16
- package/lib/models/gitpod-data.js +2 -2
- package/lib/models/language.d.ts +4 -4
- package/lib/models/language.js +2 -2
- package/lib/models/package.d.ts +7 -7
- package/lib/models/package.js +2 -2
- package/lib/models/plugin-config.d.ts +16 -16
- package/lib/models/plugin-config.js +2 -2
- package/lib/models/session.d.ts +31 -31
- package/lib/models/session.js +2 -2
- package/lib/models/socket.d.ts +37 -37
- package/lib/models/socket.js +2 -2
- package/lib/models/status.d.ts +1 -1
- package/lib/models/status.js +2 -2
- package/lib/models/success-types.d.ts +1 -1
- package/lib/models/success-types.js +2 -2
- package/lib/plugin/command/compile.d.ts +6 -6
- package/lib/plugin/command/compile.js +18 -18
- package/lib/plugin/command/test.d.ts +6 -6
- package/lib/plugin/command/test.js +25 -25
- package/lib/plugin/index.d.ts +27 -27
- package/lib/plugin/index.js +7 -7
- package/lib/plugin/plugin.d.ts +8 -8
- package/lib/plugin/plugin.js +68 -68
- package/lib/plugin/utils.d.ts +16 -16
- package/lib/plugin/utils.js +58 -58
- package/lib/ui/download.d.ts +5 -5
- package/lib/ui/download.js +62 -61
- package/lib/utils/BaseCommand.d.ts +8 -8
- package/lib/utils/BaseCommand.js +41 -41
- package/lib/utils/SessionCommand.d.ts +10 -10
- package/lib/utils/SessionCommand.js +43 -43
- package/lib/utils/api.d.ts +14 -14
- package/lib/utils/api.js +255 -255
- package/lib/utils/audit.d.ts +16 -16
- package/lib/utils/audit.js +303 -303
- package/lib/utils/checkNotInstalled.d.ts +8 -8
- package/lib/utils/checkNotInstalled.js +185 -181
- package/lib/utils/console.d.ts +12 -12
- package/lib/utils/console.js +19 -19
- package/lib/utils/errors.d.ts +17 -17
- package/lib/utils/errors.js +107 -100
- package/lib/utils/exercisesQueue.d.ts +9 -9
- package/lib/utils/exercisesQueue.js +38 -38
- package/lib/utils/fileQueue.d.ts +43 -43
- package/lib/utils/fileQueue.js +169 -169
- package/lib/utils/misc.d.ts +1 -1
- package/lib/utils/misc.js +24 -23
- package/lib/utils/osOperations.d.ts +5 -5
- package/lib/utils/osOperations.js +72 -72
- package/lib/utils/validators.d.ts +5 -5
- package/lib/utils/validators.js +16 -17
- package/lib/utils/watcher.d.ts +2 -2
- package/lib/utils/watcher.js +25 -25
- package/oclif.manifest.json +1 -1
- package/package.json +6 -4
- package/src/commands/publish.ts +181 -107
- package/src/managers/config/index.ts +5 -0
- package/src/managers/server/routes.ts +10 -0
- package/src/managers/session.ts +145 -145
package/oclif.manifest.json
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":"4.0.
|
1
|
+
{"version":"4.0.13","commands":{"audit":{"id":"audit","description":"learnpack audit is the command in charge of creating an auditory of the repository\n...\nlearnpack audit checks for the following information in a repository:\n 1. The configuration object has slug, repository and description. (Error)\n 2. The command learnpack clean has been run. (Error)\n 3. If a markdown or test file doesn't have any content. (Error)\n 4. The links are accessing to valid servers. (Error)\n 5. The relative images are working (If they have the shortest path to the image or if the images exists in the assets). (Error)\n 6. The external images are working (If they are pointing to a valid server). (Error)\n 7. The exercises directory names are valid. (Error)\n 8. If an exercise doesn't have a README file. (Error)\n 9. The exercises array (Of the config file) has content. (Error)\n 10. The exercses have the same translations. (Warning)\n 11. The .gitignore file exists. (Warning)\n 12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"clean":{"id":"clean","description":"Clean the configuration object\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[]},"download":{"id":"download","description":"Describe the command here\n...\nExtra documentation goes here\n","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"init":{"id":"init","description":"Create a new learning package: Book, Tutorial or Exercise","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"grading":{"name":"grading","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"login":{"id":"login","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"logout":{"id":"logout","description":"Describe the command here\n ...\n Extra documentation goes here\n ","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"package","description":"The unique string that identifies this package on learnpack","required":false,"hidden":false}]},"publish":{"id":"publish","description":"Builds the project by copying necessary files and directories into a zip file","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"show CLI help","allowNo":false}},"args":[]},"start":{"id":"start","description":"Runs a small server with all the exercise instructions","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{"port":{"name":"port","type":"option","char":"p","description":"server port"},"host":{"name":"host","type":"option","char":"h","description":"server host"},"disableGrading":{"name":"disableGrading","type":"boolean","char":"D","description":"disble grading functionality","allowNo":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Watch for file changes","allowNo":false},"editor":{"name":"editor","type":"option","char":"e","description":"[preview, extension]","options":["extension","preview"]},"version":{"name":"version","type":"option","char":"v","description":"E.g: 1.0.1"},"grading":{"name":"grading","type":"option","char":"g","description":"[isolated, incremental]","options":["isolated","incremental"]},"debug":{"name":"debug","type":"boolean","char":"d","description":"debugger mode for more verbage","allowNo":false}},"args":[]},"test":{"id":"test","description":"Test exercises","pluginName":"@learnpack/learnpack","pluginType":"core","aliases":[],"flags":{},"args":[{"name":"exerciseSlug","description":"The name of the exercise to test","required":false,"hidden":false}]}}}
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "@learnpack/learnpack",
|
3
3
|
"description": "Create, sell or download and take learning amazing learning packages",
|
4
|
-
"version": "4.0.
|
4
|
+
"version": "4.0.13",
|
5
5
|
"author": "Alejandro Sanchez @alesanchezr",
|
6
6
|
"contributors": [
|
7
7
|
{
|
@@ -22,6 +22,9 @@
|
|
22
22
|
"@oclif/plugin-help": "^3.1.0",
|
23
23
|
"@oclif/plugin-plugins": "^1.8.0",
|
24
24
|
"@oclif/plugin-warn-if-update-available": "^1.7.0",
|
25
|
+
"@types/archiver": "^6.0.2",
|
26
|
+
"archiver": "^7.0.1",
|
27
|
+
"axios": "^1.7.7",
|
25
28
|
"body-parser": "^1.19.0",
|
26
29
|
"chalk": "^4.1.0",
|
27
30
|
"chokidar": "^3.4.0",
|
@@ -36,7 +39,7 @@
|
|
36
39
|
"front-matter": "^4.0.2",
|
37
40
|
"moment": "^2.27.0",
|
38
41
|
"node-emoji": "^1.10.0",
|
39
|
-
"node-fetch": "^2.
|
42
|
+
"node-fetch": "^2.7.0",
|
40
43
|
"node-persist": "^3.1.0",
|
41
44
|
"prompts": "^2.3.2",
|
42
45
|
"shelljs": "^0.8.4",
|
@@ -56,7 +59,6 @@
|
|
56
59
|
"@types/mocha": "^5",
|
57
60
|
"@types/mock-fs": "^4.13.1",
|
58
61
|
"@types/node": "^10.17.60",
|
59
|
-
"@types/node-fetch": "^3.0.3",
|
60
62
|
"@types/node-persist": "^3.1.2",
|
61
63
|
"@types/prompts": "^2.0.14",
|
62
64
|
"@types/shelljs": "^0.8.9",
|
@@ -82,7 +84,7 @@
|
|
82
84
|
"pre-commit": "^1.2.2",
|
83
85
|
"prettier": "^2.4.1",
|
84
86
|
"ts-node": "^8",
|
85
|
-
"typescript": "
|
87
|
+
"typescript": "^5.6.2"
|
86
88
|
},
|
87
89
|
"engines": {
|
88
90
|
"node": ">=14.0.0"
|
package/src/commands/publish.ts
CHANGED
@@ -1,107 +1,181 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
import
|
4
|
-
import
|
5
|
-
import
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
static
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
const
|
47
|
-
if (
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
if (!
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
)
|
80
|
-
}
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
}
|
103
|
-
}
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
1
|
+
/* eslint-disable arrow-parens */
|
2
|
+
/* eslint-disable unicorn/no-array-for-each */
|
3
|
+
import { flags } from "@oclif/command"
|
4
|
+
import SessionCommand from "../utils/SessionCommand"
|
5
|
+
import SessionManager from "../managers/session"
|
6
|
+
import * as fs from "fs"
|
7
|
+
import * as path from "path"
|
8
|
+
import * as archiver from "archiver"
|
9
|
+
import axios from "axios"
|
10
|
+
import FormData = require("form-data")
|
11
|
+
import Console from "../utils/console"
|
12
|
+
|
13
|
+
// const RIGOBOT_HOST = "https://rigobot-test-cca7d841c9d8.herokuapp.com"
|
14
|
+
const RIGOBOT_HOST =
|
15
|
+
// "https://8000-charlytoc-rigobot-bmwdeam7cev.ws-us116.gitpod.io"
|
16
|
+
"https://rigobot.herokuapp.com"
|
17
|
+
const uploadZipEndpont = RIGOBOT_HOST + "/v1/learnpack/upload"
|
18
|
+
|
19
|
+
export default class BuildCommand extends SessionCommand {
|
20
|
+
static description =
|
21
|
+
"Builds the project by copying necessary files and directories into a zip file"
|
22
|
+
|
23
|
+
static flags = {
|
24
|
+
help: flags.help({ char: "h" }),
|
25
|
+
}
|
26
|
+
|
27
|
+
async init() {
|
28
|
+
const { flags } = this.parse(BuildCommand)
|
29
|
+
await this.initSession(flags)
|
30
|
+
}
|
31
|
+
|
32
|
+
async run() {
|
33
|
+
const buildDir = path.join(process.cwd(), "build")
|
34
|
+
const sessionPayload = await SessionManager.getPayload()
|
35
|
+
if (!sessionPayload || !sessionPayload.rigobot) {
|
36
|
+
Console.error(
|
37
|
+
"You must be logged in to upload a LearnPack packge, please run: \n$ learnpack login"
|
38
|
+
)
|
39
|
+
return
|
40
|
+
}
|
41
|
+
|
42
|
+
const rigoToken = sessionPayload.rigobot.key
|
43
|
+
// const rigoToken = "417d612d226a1606ad3a4e94b1881a9f0124b667"
|
44
|
+
|
45
|
+
// Read learn.json to get the slug
|
46
|
+
const learnJsonPath = path.join(process.cwd(), "learn.json")
|
47
|
+
if (!fs.existsSync(learnJsonPath)) {
|
48
|
+
this.error("learn.json not found")
|
49
|
+
}
|
50
|
+
|
51
|
+
const learnJson = JSON.parse(fs.readFileSync(learnJsonPath, "utf-8"))
|
52
|
+
|
53
|
+
const zipFilePath = path.join(process.cwd(), `${learnJson.slug}.zip`)
|
54
|
+
|
55
|
+
// Ensure build directory exists
|
56
|
+
if (!fs.existsSync(buildDir)) {
|
57
|
+
fs.mkdirSync(buildDir)
|
58
|
+
}
|
59
|
+
|
60
|
+
// Copy config.json
|
61
|
+
const configPath = path.join(process.cwd(), ".learn", "config.json")
|
62
|
+
if (fs.existsSync(configPath)) {
|
63
|
+
fs.copyFileSync(configPath, path.join(buildDir, "config.json"))
|
64
|
+
} else {
|
65
|
+
this.error("config.json not found")
|
66
|
+
}
|
67
|
+
|
68
|
+
// Copy .learn/assets directory
|
69
|
+
const assetsDir = path.join(process.cwd(), ".learn", "assets")
|
70
|
+
if (fs.existsSync(assetsDir)) {
|
71
|
+
this.copyDirectory(assetsDir, path.join(buildDir, ".learn", "assets"))
|
72
|
+
} else {
|
73
|
+
this.error(".learn/assets directory not found")
|
74
|
+
}
|
75
|
+
|
76
|
+
// Copy .learn/_app directory files to the same level as config.json
|
77
|
+
const appDir = path.join(process.cwd(), ".learn", "_app")
|
78
|
+
if (fs.existsSync(appDir)) {
|
79
|
+
this.copyDirectory(appDir, buildDir)
|
80
|
+
} else {
|
81
|
+
this.error(".learn/_app directory not found")
|
82
|
+
}
|
83
|
+
|
84
|
+
// Copy exercises directory
|
85
|
+
const exercisesDir = path.join(process.cwd(), "exercises")
|
86
|
+
const learnExercisesDir = path.join(process.cwd(), ".learn", "exercises")
|
87
|
+
|
88
|
+
if (fs.existsSync(exercisesDir)) {
|
89
|
+
this.copyDirectory(exercisesDir, path.join(buildDir, "exercises"))
|
90
|
+
} else if (fs.existsSync(learnExercisesDir)) {
|
91
|
+
this.copyDirectory(learnExercisesDir, path.join(buildDir, "exercises"))
|
92
|
+
} else {
|
93
|
+
this.error("exercises directory not found in either location")
|
94
|
+
}
|
95
|
+
|
96
|
+
// Copy learn.json
|
97
|
+
fs.copyFileSync(learnJsonPath, path.join(buildDir, "learn.json"))
|
98
|
+
|
99
|
+
// Create zip file
|
100
|
+
const output = fs.createWriteStream(zipFilePath)
|
101
|
+
const archive = archiver("zip", {
|
102
|
+
zlib: { level: 9 },
|
103
|
+
})
|
104
|
+
|
105
|
+
output.on("close", async () => {
|
106
|
+
this.log(
|
107
|
+
`Build completed: ${zipFilePath} (${archive.pointer()} total bytes)`
|
108
|
+
)
|
109
|
+
// Remove build directory after zip is created
|
110
|
+
this.removeDirectory(buildDir)
|
111
|
+
console.log("Zip file saved in project root")
|
112
|
+
|
113
|
+
const formData = new FormData()
|
114
|
+
formData.append("file", fs.createReadStream(zipFilePath))
|
115
|
+
formData.append("config", JSON.stringify(learnJson))
|
116
|
+
|
117
|
+
try {
|
118
|
+
const res = await axios.post(uploadZipEndpont, formData, {
|
119
|
+
headers: {
|
120
|
+
...formData.getHeaders(),
|
121
|
+
Authorization: `Token ${rigoToken}`,
|
122
|
+
},
|
123
|
+
})
|
124
|
+
console.log(res.data)
|
125
|
+
} catch (error) {
|
126
|
+
if (axios.isAxiosError(error)) {
|
127
|
+
if (error.response && error.response.status === 403) {
|
128
|
+
console.error("Error 403:", error.response.data.error)
|
129
|
+
} else if (error.response && error.response.status === 400) {
|
130
|
+
console.error(error.response.data.error)
|
131
|
+
} else {
|
132
|
+
console.error("Error uploading file:", error.message)
|
133
|
+
}
|
134
|
+
} else {
|
135
|
+
console.error("Error uploading file:", error)
|
136
|
+
}
|
137
|
+
}
|
138
|
+
})
|
139
|
+
|
140
|
+
archive.on("error", (err: any) => {
|
141
|
+
throw err
|
142
|
+
})
|
143
|
+
|
144
|
+
archive.pipe(output)
|
145
|
+
archive.directory(buildDir, false)
|
146
|
+
await archive.finalize()
|
147
|
+
}
|
148
|
+
|
149
|
+
copyDirectory(src: string, dest: string) {
|
150
|
+
if (!fs.existsSync(dest)) {
|
151
|
+
fs.mkdirSync(dest, { recursive: true })
|
152
|
+
}
|
153
|
+
|
154
|
+
const entries = fs.readdirSync(src, { withFileTypes: true })
|
155
|
+
|
156
|
+
for (const entry of entries) {
|
157
|
+
const srcPath = path.join(src, entry.name)
|
158
|
+
const destPath = path.join(dest, entry.name)
|
159
|
+
|
160
|
+
if (entry.isDirectory()) {
|
161
|
+
this.copyDirectory(srcPath, destPath)
|
162
|
+
} else {
|
163
|
+
fs.copyFileSync(srcPath, destPath)
|
164
|
+
}
|
165
|
+
}
|
166
|
+
}
|
167
|
+
|
168
|
+
removeDirectory(dir: string) {
|
169
|
+
if (fs.existsSync(dir)) {
|
170
|
+
fs.readdirSync(dir).forEach((file) => {
|
171
|
+
const currentPath = path.join(dir, file)
|
172
|
+
if (fs.lstatSync(currentPath).isDirectory()) {
|
173
|
+
this.removeDirectory(currentPath)
|
174
|
+
} else {
|
175
|
+
fs.unlinkSync(currentPath)
|
176
|
+
}
|
177
|
+
})
|
178
|
+
fs.rmdirSync(dir)
|
179
|
+
}
|
180
|
+
}
|
181
|
+
}
|
@@ -367,6 +367,11 @@ return true
|
|
367
367
|
Console.log(result.stdout)
|
368
368
|
throw InternalError(`Error installing ${language} exercise engine`)
|
369
369
|
},
|
370
|
+
logout: () => {
|
371
|
+
if (configObj.config) {
|
372
|
+
rmSync(configObj.config.dirPath + "/.session")
|
373
|
+
}
|
374
|
+
},
|
370
375
|
clean: () => {
|
371
376
|
if (configObj.config) {
|
372
377
|
if (configObj.config.outputPath) {
|
@@ -71,6 +71,16 @@ export default async function (
|
|
71
71
|
res.json(payload)
|
72
72
|
})
|
73
73
|
)
|
74
|
+
|
75
|
+
app.post(
|
76
|
+
"/logout",
|
77
|
+
jsonBodyParser,
|
78
|
+
withHandler(async (req: express.Request, res: express.Response) => {
|
79
|
+
SessionManager.destroy()
|
80
|
+
res.json({ message: "You've logged out from Breathecode and Rigobot" })
|
81
|
+
})
|
82
|
+
)
|
83
|
+
|
74
84
|
app.post(
|
75
85
|
"/set-rigobot-token",
|
76
86
|
jsonBodyParser,
|