Skip to content

Commit d4aec32

Browse files
mepriprithehabes
andauthored
TPEN28 Fix (#300)
* TPEN28 Fix * Update import28Router.js * service fix * function this * protocol * Update ProjectFactory.js * protocol * co-op fixing * change variables around page vs canvas * GET not POST --------- Co-authored-by: Bryan Haberberger <bryan.j.haberberger@slu.edu>
1 parent 70b2207 commit d4aec32

2 files changed

Lines changed: 166 additions & 0 deletions

File tree

classes/Project/ProjectFactory.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,99 @@ export default class ProjectFactory {
340340
}
341341
}
342342

343+
// We might add the Vault here to get the Manifest version 3
344+
static transformManifestUrl(url, protocol) {
345+
const parsedUrl = new URL(url)
346+
parsedUrl.protocol = protocol
347+
if (parsedUrl.pathname.endsWith("/manifest.json")) {
348+
parsedUrl.pathname = parsedUrl.pathname.replace(/\/manifest\.json$/, "")
349+
}
350+
parsedUrl.search = "?version=3"
351+
return parsedUrl.toString()
352+
}
353+
354+
static async importTPEN28(projectTPEN28Data, projectTPEN3Data, userToken, protocol) {
355+
if (!projectTPEN28Data || !projectTPEN3Data) {
356+
throw {
357+
status: 400,
358+
message: "Invalid project data"
359+
}
360+
}
361+
362+
const symbols = projectTPEN28Data.projectButtons.map(button => String.fromCharCode(button.key))
363+
if (symbols && symbols.length > 0) {
364+
const copiedHotkeys = new Hotkeys(projectTPEN3Data._id, symbols)
365+
await copiedHotkeys.create()
366+
}
367+
let projectTools = []
368+
try {
369+
projectTools = [...projectTPEN28Data.userTool, ...projectTPEN28Data.projectTool]
370+
}
371+
catch (err) {
372+
// Just in case the spread operator didn't end up making an array due to 'undefined' or something weird.
373+
projectTools = []
374+
}
375+
const toolList = projectTPEN3Data.tools.map((tool) => tool.value)
376+
const selectedTools = toolList.map((tool) => ({
377+
value: tool,
378+
state: projectTools.includes(tool),
379+
}))
380+
const project = new Project(projectTPEN3Data._id)
381+
if (selectedTools && selectedTools.length > 0) {
382+
await project.updateTools(selectedTools)
383+
}
384+
const allCanvases = projectTPEN3Data.layers[0].pages.map((page) => page.target)
385+
const allPagesIds = projectTPEN3Data.layers[0].pages.map((page) =>page.id.replace(/project\/([a-f0-9]+)/, `project/${projectTPEN3Data._id}`))
386+
let manifestUrl = projectTPEN3Data.manifest[0]
387+
manifestUrl = this.transformManifestUrl(manifestUrl, protocol)
388+
const responseManifest = await fetch(manifestUrl)
389+
if (!responseManifest.ok) {
390+
throw new Error(`Failed to fetch: ${responseManifest.statusText}`)
391+
}
392+
393+
const manifestJson = await responseManifest.json()
394+
const itemsByPage = {}
395+
manifestJson.items.map((item, index) => {
396+
const canvasId = item.id
397+
if (allCanvases.includes(canvasId)) {
398+
const annotations = item.annotations?.flatMap(
399+
(annotation) =>
400+
annotation.items?.flatMap((innerItems) => ({
401+
body: {
402+
type: innerItems.body?.type,
403+
format: innerItems.body?.format,
404+
value: innerItems.body?.value,
405+
},
406+
motivation: innerItems.motivation,
407+
target: innerItems.target,
408+
type: innerItems.type,
409+
})) || []
410+
) || []
411+
itemsByPage[allPagesIds[index]] = annotations
412+
}
413+
})
414+
415+
for (const [endpoint, annotations] of Object.entries(itemsByPage)) {
416+
try {
417+
const response = await fetch(`${endpoint}/line`, {
418+
method: "POST",
419+
headers: {
420+
"Content-Type": "application/json",
421+
Authorization: `Bearer ${userToken}`,
422+
},
423+
body: JSON.stringify(annotations),
424+
})
425+
if (!response.ok) {
426+
throw new Error(`Failed to import annotations: ${response.statusText}`)
427+
}
428+
} catch (error) {
429+
console.error("Error importing annotations:", error)
430+
}
431+
}
432+
433+
return projectTPEN3Data
434+
}
435+
343436
static async copyProject(projectId, creator) {
344437
if (!projectId) {
345438
throw {

project/import28Router.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { respondWithError } from "../utilities/shared.js"
33
import cookieParser from "cookie-parser"
44
import auth0Middleware from "../auth/index.js"
55
import cors from "cors"
6+
import validateURL from "../utilities/validateURL.js"
7+
import ProjectFactory from "../classes/Project/ProjectFactory.js"
68

79
function patchTokenFromQuery(req, res, next) {
810
if (!req.headers.authorization && req.cookies.userToken) {
@@ -79,4 +81,75 @@ router.route("/import28/:uid").get(
7981
respondWithError(res, 405, "Improper request method. Use GET instead")
8082
})
8183

84+
router.route("/import28/selectedproject/:selectedProjectId").get(
85+
cors(corsOptions),
86+
cookieParser(),
87+
patchTokenFromQuery,
88+
auth0Middleware(),
89+
async (req, res) => {
90+
const user = req.user
91+
const jsessionid = req.cookies?.JSESSIONID
92+
const selectedProjectId = req.params?.selectedProjectId
93+
94+
if (!user) return respondWithError(res, 401, "Unauthenticated request")
95+
if (!jsessionid) return respondWithError(res, 400, "Missing jsessionid in query")
96+
if (!selectedProjectId) return respondWithError(res, 400, "Missing selectedProjectId in query")
97+
98+
try {
99+
const importResponse = await fetch(
100+
`${process.env.TPEN28URL}/TPEN/getProjectTPENServlet?projectID=${selectedProjectId}`,
101+
{
102+
method: "GET",
103+
headers: {
104+
"Content-Type": "application/json; charset=utf-8",
105+
"Cookie": `JSESSIONID=${jsessionid}`
106+
},
107+
credentials: "include"
108+
}
109+
)
110+
.then(resp => {
111+
if(!resp.ok) throw resp
112+
return resp.json()
113+
})
114+
.catch(err => {throw err})
115+
116+
let parsedData = {}
117+
parsedData = Object.fromEntries(
118+
Object.entries(importResponse).map(([key, value]) => {
119+
try {
120+
return [key, JSON.parse(value)]
121+
} catch {
122+
return [key, value]
123+
}
124+
})
125+
)
126+
127+
const manifestURL = `${process.env.TPEN28URL}/TPEN/manifest/${selectedProjectId}`
128+
let checkURL = await validateURL(manifestURL)
129+
let importData
130+
if (!checkURL.valid)
131+
return res.status(checkURL.status).json({message: checkURL.message, resolvedPayload: checkURL.resolvedPayload})
132+
try {
133+
importData = await ProjectFactory.fromManifestURL(manifestURL, user.agent.split('/').pop(), true)
134+
} catch (error) {
135+
res.status(error.status ?? 500).json({
136+
status: error.status ?? 500,
137+
message: error.message,
138+
data: error.resolvedPayload
139+
})
140+
}
141+
142+
await ProjectFactory.importTPEN28(parsedData, importData, req.cookies.userToken, req.protocol)
143+
res.status(201).json({
144+
message: "Project imported successfully",
145+
project: { parsedData, importData }
146+
})
147+
} catch (error) {
148+
return respondWithError(res, error.status ?? 500, error.statusText ?? "Error fetching project data")
149+
}
150+
}
151+
).all((req, res) => {
152+
respondWithError(res, 405, "Improper request method. Use GET instead")
153+
})
154+
82155
export default router

0 commit comments

Comments
 (0)