-
Notifications
You must be signed in to change notification settings - Fork 2
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
Manage collaborators #24
base: main
Are you sure you want to change the base?
Changes from all commits
fd228a8
e01c768
4370ee2
b92f241
4af35d4
921416c
2e344d8
a6e3b6f
23a289e
37025cb
f8535d3
9d3c0f0
7bb1da4
9604626
522ecf5
8274279
b8f9d01
d111221
7146b7b
cacd407
a787704
9050ec6
9ffe8c0
1b5d369
782912f
81a4d99
e12e7ed
4bd006b
3349686
3a76a16
97317d8
85a90c7
0a5092f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import TPEN from "../TPEN/index.mjs" | ||
import { userMessage } from "../components/iiif-tools/index.mjs" | ||
|
||
export default class Project { | ||
|
||
TPEN = new TPEN() | ||
|
||
constructor(_id) { | ||
this._id = _id | ||
} | ||
|
||
async fetch() { | ||
const AUTH_TOKEN = TPEN.getAuthorization() ?? TPEN.login() | ||
try { | ||
return await fetch(`${this.TPEN.servicesURL}/project/${this._id}`, { | ||
method: 'GET', | ||
headers: { | ||
'Content-Type': 'application/json', | ||
'Authorization': `Bearer ${AUTH_TOKEN}` | ||
} | ||
}) | ||
.then(response => response.ok ? response : Promise.reject(response)) | ||
.then(response => response.json()) | ||
.then(data => Object.assign(this, data)) | ||
.catch(error => { throw error }) | ||
} catch (error) { | ||
return userMessage(`${error.status}: ${error.statusText}`) | ||
} | ||
} | ||
|
||
/** | ||
* Add a member to the project by email. | ||
* @param {String} email The email of the member to add. | ||
*/ | ||
async addMember(email) { | ||
try { | ||
const AUTH_TOKEN = TPEN.getAuthorization() ?? TPEN.login() | ||
const response = await fetch(`${this.TPEN.servicesURL}/project/${this._id}/invite-member`, { | ||
headers: { | ||
Authorization: `Bearer ${AUTH_TOKEN}`, | ||
'Content-Type': 'application/json', | ||
}, | ||
method: "POST", | ||
body: JSON.stringify({ email }), | ||
}) | ||
if (!response.ok) { | ||
const errorData = await response.json() | ||
throw new Error(errorData.message || `Failed to invite collaborator: ${response.statusText}`) | ||
} | ||
|
||
return await response.json() | ||
} catch (error) { | ||
userMessage(error.message) | ||
} | ||
} | ||
|
||
/** | ||
* Remove a member from the project by userId. | ||
* @param {String} userId The ID of the member to remove. | ||
*/ | ||
async removeMember(userId) { | ||
try { | ||
const token = TPEN.getAuthorization() ?? TPEN.login() | ||
const response = await fetch(`${this.TPEN.servicesURL}/project/${this._id}/remove-member`, { | ||
method: "POST", | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify({ userId }), | ||
}) | ||
if (!response.ok) { | ||
throw new Error(`Error removing member: ${response.status}`) | ||
} | ||
|
||
return await response | ||
} catch (error) { | ||
userMessage(error.message) | ||
} | ||
} | ||
getLabel() { | ||
return this.label ?? this.data?.label ?? this.metadata?.find(m => m.label === "title")?.value ?? "Untitled" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,37 @@ | ||
import { getUserFromToken } from "../components/iiif-tools/index.mjs" | ||
import { eventDispatcher } from "../TPEN/events.mjs" | ||
import TPEN from "../TPEN/index.mjs" | ||
/** Description: to use this class, initialize new class, set authentication token, then call required methods | ||
* | ||
*/ | ||
export default class User { | ||
#authentication | ||
baseURL = "https://dev.api.t-pen.org" | ||
TPEN = new TPEN() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should probably be #TPEN, unless we need it exposed outside this. |
||
|
||
#isTheAuthenticatedUser() { | ||
return this._id === getUserFromToken(TPEN.getAuthorization()) | ||
} | ||
constructor(_id) { | ||
this._id = _id | ||
// if (this.#authentication || this._id) this.getProfile() | ||
} | ||
|
||
/** | ||
* @param {any} token | ||
*/ | ||
set authentication(token) { | ||
let isNewToken = false; | ||
if (token != this.#authentication) { | ||
isNewToken = true | ||
} | ||
this.#authentication = token | ||
if (isNewToken) this.getProfile() | ||
} | ||
|
||
async getProfile() { | ||
if (!this.#authentication && !this._id) | ||
if (!this._id) | ||
throw Error("User ID is required") | ||
|
||
const serviceAPI = `${this.baseURL}/${this.#authentication ? "my/profile" : `user/:${this._id}` | ||
const serviceAPI = `${this.TPEN.servicesURL}/${this.#isTheAuthenticatedUser() ? "my/profile" : `user/:${this._id}` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this winds up calling TPEN.getAuthorization a lot, but it isn't broken, so.... |
||
}` | ||
const headers = this.#authentication | ||
? new Headers({ Authorization: `Bearer ${this.#authentication}` }) | ||
const headers = this.#isTheAuthenticatedUser() | ||
? new Headers({ Authorization: `Bearer ${TPEN.getAuthorization()}` }) | ||
: new Headers() | ||
await fetch(serviceAPI, { headers }) | ||
fetch(serviceAPI, { headers }) | ||
.then((response) => { | ||
if (!response.ok) Promise.reject(response) | ||
return response.json() | ||
}) | ||
.then((data) => { | ||
Object.assign(this, data) | ||
// the public user object has no displayName tag, it has a name instead, hence the check below | ||
this.displayName = data.profile.displayName ?? data.name ?? "Anonymous" | ||
this.displayName = data.profile?.displayName ?? data.name ?? "Anonymous" | ||
if (data._sub) { | ||
eventDispatcher.dispatch("tpen-user-loaded", this) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line (and the import above) is still needed and how #currentUser gets set in other modules, at the moment. |
||
} | ||
|
@@ -50,10 +41,10 @@ export default class User { | |
|
||
async getProjects() { | ||
const headers = new Headers({ | ||
Authorization: `Bearer ${this.#authentication}` | ||
Authorization: `Bearer ${TPEN.getAuthorization()}` | ||
}) | ||
|
||
return fetch(`${this.baseURL}/my/projects`, { headers }) | ||
return fetch(`${this.TPEN.servicesURL}/my/projects`, { headers }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a good change, I like getting baseURL out of there. |
||
.then((response) => { | ||
if (!response.ok) { | ||
return Promise.reject(response) | ||
|
@@ -91,14 +82,25 @@ export default class User { | |
<li> | ||
${project.title} | ||
<div class="manage"> | ||
<span>Resume</span> | ||
<span>Manage</span> | ||
<span class="resume-btn">Resume</span> | ||
<span class="manage-btn" data-project-id="${project._id}">Manage</span> | ||
</div> | ||
</li> | ||
` | ||
|
||
projectsList.insertAdjacentHTML("beforeend", projectTemplate) | ||
}) | ||
|
||
const manageButtons = document.querySelectorAll(".manage-btn") | ||
manageButtons.forEach((button) => { | ||
button.addEventListener("click", (event) => { | ||
|
||
const projectId = event.target.getAttribute("data-project-id") | ||
|
||
window.location.href = `/manage/?projectID=${projectId}` | ||
}) | ||
}) | ||
|
||
} else { | ||
projectsList.innerHTML = "No projects yet. Create one to get started" | ||
} | ||
|
@@ -107,6 +109,7 @@ export default class User { | |
const errorTemplate = ` | ||
<li> | ||
Error ${error.status ?? 500}: ${error.statusText ?? "Unknown Server error"} | ||
Error ${error.status ?? 500}: ${error.statusText ?? "Unknown Server error"} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. doubled line |
||
</li> | ||
` | ||
projectsList.insertAdjacentHTML("beforeend", errorTemplate) | ||
|
@@ -116,11 +119,11 @@ export default class User { | |
|
||
async updateRecord(data) { | ||
try { | ||
const response = await fetch(`${this.baseURL}/my/profile/update`, { | ||
const response = await fetch(`${this.TPEN.servicesURL}/my/profile/update`, { | ||
method: "PUT", | ||
headers: { | ||
"Content-Type": "application/json", | ||
Authorization: `Bearer ${this.#authentication}` | ||
Authorization: `Bearer ${TPEN.getAuthorization()}` | ||
}, | ||
body: JSON.stringify(data) | ||
}) | ||
|
@@ -152,4 +155,8 @@ export default class User { | |
const response = await this.updateRecord(data) | ||
return response | ||
} | ||
|
||
static fromToken(token) { | ||
return new User(getUserFromToken(token)) | ||
} | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This directory shouldn't be versioned
cubap marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,38 +1,124 @@ | ||
import checkUserAuthentication from "../utilities/checkUserAuthentication.mjs" | ||
|
||
|
||
/** | ||
* Project API Link for user interfaces to import. | ||
* | ||
* @author cubap | ||
* @type module | ||
* @version 0.0.1 | ||
*/ | ||
const baseURL = "https://dev.api.t-pen.org" | ||
|
||
export default class Project { | ||
constructor(projectID) { | ||
|
||
if (typeof projectID === "object") { | ||
// Assume configuration object | ||
this.data = projectID | ||
return | ||
} | ||
|
||
if (typeof projectID === "string") { | ||
this.id = projectID | ||
return | ||
} | ||
|
||
throw new Error("Project ID or configuration is required") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. preserving some sort of error may be great so we don't wind up with an empty Project. |
||
this.id = projectID | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe _id is better for consistency. |
||
} | ||
|
||
// new private function getById() | ||
|
||
/** | ||
* Load project information from TPEN Services about the project | ||
* for rendering user interface. | ||
* @param {String} projectID short hash id for TPEN project | ||
*/ | ||
|
||
async loadData() { | ||
return await fetch(`/project/${this.id}`) | ||
.then(response => response.json()) | ||
.then(project => this.data = project) | ||
.catch(err => new Promise.reject(err)) | ||
const TPEN_USER = await checkUserAuthentication() | ||
let token = TPEN_USER.authorization | ||
return await fetch(`${baseURL}/project/${this.id}`, { | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
}, | ||
}) | ||
.then(response => { | ||
if (!response.ok) { | ||
return Promise.reject(response) | ||
} | ||
const data = response.json() | ||
return this.data = data | ||
}) | ||
.catch(err => { throw err }) | ||
} | ||
|
||
async addMember(email) { | ||
let url = `${baseURL}/project/${this.id}/invite-member` | ||
|
||
try { | ||
const response = await fetch(url, { | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
'Content-Type': 'application/json', | ||
}, | ||
method: "POST", | ||
body: JSON.stringify({ email }), | ||
}) | ||
|
||
if (!response.ok) { | ||
const errorData = await response.json() | ||
throw new Error(errorData.message || `Failed to invite collaborator: ${response.statusText}`) | ||
} | ||
|
||
return await response.json() | ||
} catch (error) { | ||
throw error | ||
} | ||
} | ||
|
||
async removeMember(userId) { | ||
|
||
const url = `${baseURL}/project/${this.id}/remove-member` | ||
|
||
try { | ||
const response = await fetch(url, { | ||
method: "POST", | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
'Content-Type': 'application/json', | ||
}, | ||
body: JSON.stringify({ userId }) | ||
}) | ||
|
||
if (!response.ok) { | ||
throw new Error(`Error removing member: ${response.status}`) | ||
} | ||
|
||
const data = await response.json() | ||
return data | ||
|
||
|
||
} catch (error) { | ||
console.error('Error removing member:', error) | ||
throw error | ||
} | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
const removeButtons = document.querySelectorAll('.remove-button') | ||
|
||
removeButtons.forEach((button) => { | ||
|
||
|
||
button.addEventListener('click', async () => { | ||
const memberID = button.getAttribute("data-member-id") | ||
const memberName = button.getAttribute("data-member-name") | ||
|
||
const confirmed = confirm(`This action will remove ${memberName} from your project. Click 'OK' to continue?`) | ||
if (!confirmed) { | ||
return | ||
} | ||
|
||
|
||
}) | ||
}) | ||
} | ||
|
||
} | ||
|
||
// let projectID = "6602dd2314cd575343f513ba" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove these comments There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This file is already updated in Project/index.js. we may have to remove this completely |
||
// let projectObj = new Project(projectID) | ||
// const projectData = await projectObj.loadData() | ||
// console.log(projectData) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is still needed for non-module TPEN usage.