Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Server side code for video content player and Documentation for XAPI #12

Merged
merged 2 commits into from
Aug 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"label": "XAPI(Experience API)",
"position": 2,
"link": {
"type": "generated-index",
"description": "Pdf Library Introduction "
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
sidebar_position: 1
---
# Transferring Data from xAPI to SCORM Cloud

## Introduction

This guide provides a step-by-step approach for transferring data from an xAPI-based system to SCORM Cloud using JavaScript and the TinCanJS package. SCORM Cloud is a cloud-based service that supports SCORM and xAPI (Tin Can API) content. TinCanJS is a JavaScript library that facilitates communication with xAPI endpoints.

## Prerequisites

- **Node.js**: Ensure you have Node.js installed on your system.
- **NPM/Yarn**: Node package manager to install dependencies.
- **SCORM Cloud Account**: Access to SCORM Cloud credentials (endpoint, key, and secret).
- **TinCanJS Library**: JavaScript library for xAPI interactions.

## Setup

### 1. Install Dependencies

Start by setting up a new Node.js project and installing the required packages:

```bash
mkdir xapi-to-scorm
cd xapi-to-scorm
npm init -y
npm install tincanjs axios
```

### 2. Configure SCORM Cloud Credentials
Create a .env file in the root directory of your project to store SCORM Cloud credentials securely:
```bash
SCORM_CLOUD_ENDPOINT=https://cloud.scorm.com/lrs/VRIH0Z14DH/
SCORM_CLOUD_KEY=4gPlyMtlpy18FccetKM
SCORM_CLOUD_SECRET=vV4rzTZ8Q-yjnpakVIY
```
### 3. Create a JavaScript File for Data Transfer
Create a file transferData.js in the root directory of your project:
```bash
// Define the xAPI statements to be transferred
const statements = [
// Example xAPI statement
{
actor: {
mbox: "mailto:[email protected]",
name: "Example Learner"
},
verb: {
id: "http://adlnet.gov/expapi/verbs/completed",
display: { "en-US": "completed" }
},
object: {
id: "http://example.com/activities/example-activity",
objectType: "Activity",
name: { "en-US": "Example Activity" }
},
result: {
score: {
scaled: 0.9
}
},
timestamp: new Date().toISOString()
}
];
```
## Explanation
TinCanJS Configuration: Configures the library with SCORM Cloud credentials.
Statements Definition: Defines the xAPI statements that you wish to transfer.
Data Transfer Function: Sends the xAPI statements to the SCORM Cloud endpoint using Axios for HTTP requests.
Execution: Runs the data transfer script.
## Error Handling
Ensure valid SCORM Cloud credentials are provided in the .env file.
Check network connectivity and SCORM Cloud endpoint status.
Monitor the console output for successful or failed transfer messages.
## Conclusion
This documentation outlines the process of transferring data from an xAPI system to SCORM Cloud using JavaScript and the TinCanJS library. Adjust the xAPI statements as needed for your specific use case.
25 changes: 25 additions & 0 deletions Video_Player/Backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
env
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
150 changes: 150 additions & 0 deletions Video_Player/Backend/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const TinCan = require('tincanjs');
const jwt = require('jsonwebtoken');
const morgan = require('morgan');
const fs = require('fs');
const path = require('path');

const app = express();
const port = process.env.PORT || 5000;

const accessLogStream = fs.createWriteStream(path.join(__dirname, 'access.log'), { flags: 'a' });
app.use(morgan('combined', { stream: accessLogStream }));

app.use(bodyParser.json());

app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
next();
});


const authenticateJWT = (req, res, next) => {
const token = req.header('Authorization')?.split(' ')[1];

if (token) {
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) {
console.error('JWT verification failed:', err);
return res.status(403).send('Forbidden');
}
req.user = user;
next();
});
} else {
res.status(401).send('Unauthorized');
}
};

// // Route to Generate JWT Token (For Testing Purposes)
// app.post('/api/login', (req, res) => {
// const { username, email } = req.body;

// // Generate a JWT token
// const token = jwt.sign(
// { name: username, email: email },
// process.env.JWT_SECRET,
// { expiresIn: '1h' }
// );

// res.json({ token });
// });
app.post('/api/login', (req, res) => {
const { username, email } = req.body;
const token = jwt.sign(
{ name: username, email: email },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);

res.json({ token });
});

let lrs;
try {
lrs = new TinCan.LRS({
endpoint: process.env.LRS_ENDPOINT,
username: process.env.LRS_KEY,
password: process.env.LRS_SECRET,
allowFail: false
});
console.log("LRS object setup successfully.");
} catch (ex) {
console.error("Failed to setup LRS object: ", ex);
process.exit(1);
}


app.post('/api/video-metadata', authenticateJWT, (req, res) => {
const { videoId, title, description, duration, format } = req.body;

if (!videoId || !title || !duration || !format) {
console.error("Missing video metadata in request body");
return res.status(400).send("Missing video metadata in request body");
}


console.log("Received video metadata:", { videoId, title, description, duration, format });

const supportedFormats = ['mp4', 'avi', 'mov'];
if (!supportedFormats.includes(format)) {
console.error(`Unsupported video format: ${format}`);
return res.status(400).send(`Unsupported video format: ${format}`);
}

const statement = new TinCan.Statement({
actor: {
mbox: `mailto:${req.user.email}`,
name: req.user.name,
},
verb: {
id: "http://adlnet.gov/expapi/verbs/experienced",
display: { "en-US": "experienced" }
},
object: {
id: `http://example.com/videos/${videoId}`,
definition: {
name: { "en-US": title },
description: { "en-US": description }
}
},
result: {
duration: `PT${Math.floor(duration / 60)}M${duration % 60}S`
},
context: {
extensions: {
"http://example.com/metadata/format": format
}
}
});

console.log("Attempting to save video metadata statement:", statement);


lrs.saveStatement(statement, {
callback: function (err, xhr) {
if (err !== null) {
console.error("Failed to save statement:", err);
console.error("Error details:", xhr ? xhr.responseText : "No response text");
res.status(500).send("Failed to save video metadata");
} else {
console.log("Video metadata saved successfully to SCORM Cloud");
res.status(200).send("Video metadata saved successfully");
}
}
});
});

app.use((err, req, res, next) => {
console.error("An error occurred:", err.message);
res.status(500).send("Internal Server Error");
});

app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});

11 changes: 11 additions & 0 deletions Video_Player/Backend/models/Quiz.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const mongoose = require('mongoose');

const QuizSchema = new mongoose.Schema({
question: String,
type: { type: String, enum: ['multiple-choice', 'fill-in-the-blank'], required: true },
options: [String], // Only for multiple-choice questions
correctAnswer: String, // Correct option or answer
points: { type: Number, default: 4 }
});

const Quiz = mongoose.model('Quiz', QuizSchema);
Loading