Skip to content

Commit eccc6bc

Browse files
authored
fix: profile editor media sync (#947)
* fix: profile editor media sync * fix: address coderabbit comments * fix: app source initialization
1 parent a096cf6 commit eccc6bc

28 files changed

Lines changed: 627 additions & 148 deletions

File tree

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ PUBLIC_PROVISIONER_SHARED_SECRET="your-provisioner-shared-secret"
9292

9393
PUBLIC_ESIGNER_BASE_URL="http://localhost:3004"
9494
PUBLIC_FILE_MANAGER_BASE_URL="http://localhost:3005"
95-
PUBLIC_PROFILE_EDITOR_BASE_URL="http://localhost:3006"
95+
PUBLIC_PROFILE_EDITOR_BASE_URL=http://localhost:3007
9696

9797
DREAMSYNC_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/dreamsync
9898
VITE_DREAMSYNC_BASE_URL="http://localhost:8888"

infrastructure/dev-sandbox/src/routes/+page.svelte

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ interface Identity {
4949
w3id: string;
5050
uri: string;
5151
keyId: string;
52+
displayName?: string;
5253
bearerToken?: string;
5354
tokenExpiresAt?: number;
5455
}
@@ -468,6 +469,14 @@ async function doProvision() {
468469
identity.w3id,
469470
profile,
470471
);
472+
const provisionedKeyId = identity.keyId;
473+
const next = identities.map((id) =>
474+
id.keyId === provisionedKeyId
475+
? { ...id, displayName: profile.displayName }
476+
: id,
477+
);
478+
identities = next;
479+
saveIdentities(next);
471480
addLog("success", "UserProfile created", profile.displayName);
472481
} catch (e) {
473482
const msg = e instanceof Error ? e.message : String(e);
@@ -826,7 +835,7 @@ async function doSign() {
826835
<h2>Selected identity</h2>
827836
<select bind:value={selectedIndex}>
828837
{#each identities as id, i}
829-
<option value={i}>{id.w3id}</option>
838+
<option value={i}>{id.displayName ? `${id.displayName} (${id.w3id})` : id.w3id}</option>
830839
{/each}
831840
</select>
832841
</section>

platforms/file-manager/api/src/controllers/FileController.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ export class FileController {
453453
}
454454

455455
const { id } = req.params;
456-
const { displayName, description, folderId } = req.body;
456+
const { displayName, description, folderId, isPublic } = req.body;
457457

458458
const file = await this.fileService.updateFile(
459459
id,
@@ -473,6 +473,11 @@ export class FileController {
473473
.json({ error: "File not found or not authorized" });
474474
}
475475

476+
if (typeof isPublic === "boolean") {
477+
await this.fileService.setFilePublic(id, isPublic);
478+
file.isPublic = isPublic;
479+
}
480+
476481
res.json({
477482
id: file.id,
478483
name: file.name,
@@ -483,6 +488,7 @@ export class FileController {
483488
md5Hash: file.md5Hash,
484489
ownerId: file.ownerId,
485490
folderId: file.folderId,
491+
isPublic: file.isPublic,
486492
createdAt: file.createdAt,
487493
updatedAt: file.updatedAt,
488494
});
@@ -567,6 +573,41 @@ export class FileController {
567573
}
568574
};
569575

576+
/**
577+
* Serves a file publicly. Only files explicitly marked isPublic=true
578+
* are served; all others return 404.
579+
*/
580+
publicPreview = async (req: Request, res: Response) => {
581+
try {
582+
const { id } = req.params;
583+
const file = await this.fileService.getFileByIdPublic(id);
584+
585+
if (!file) {
586+
return res.status(404).json({ error: "File not found" });
587+
}
588+
589+
if (file.url) {
590+
return res.redirect(file.url);
591+
}
592+
593+
if (!file.data) {
594+
return res.status(410).json({ error: "File data unavailable" });
595+
}
596+
597+
res.setHeader("Content-Type", file.mimeType);
598+
res.setHeader(
599+
"Content-Disposition",
600+
`inline; filename="${file.name}"`,
601+
);
602+
res.setHeader("Content-Length", file.size.toString());
603+
res.setHeader("Cache-Control", "public, max-age=3600");
604+
res.send(file.data);
605+
} catch (error) {
606+
console.error("Error serving public file:", error);
607+
res.status(500).json({ error: "Failed to serve file" });
608+
}
609+
};
610+
570611
deleteFile = async (req: Request, res: Response) => {
571612
try {
572613
if (!req.user) {

platforms/file-manager/api/src/database/entities/File.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ export class File {
4444
@Column({ type: "text", nullable: true })
4545
url!: string | null;
4646

47+
@Column({ default: false })
48+
isPublic!: boolean;
49+
4750
@Column()
4851
ownerId!: string;
4952

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { MigrationInterface, QueryRunner } from "typeorm";
2+
3+
export class AddIsPublicToFiles1775700000000 implements MigrationInterface {
4+
name = 'AddIsPublicToFiles1775700000000'
5+
6+
public async up(queryRunner: QueryRunner): Promise<void> {
7+
await queryRunner.query(`ALTER TABLE "files" ADD "isPublic" boolean NOT NULL DEFAULT false`);
8+
}
9+
10+
public async down(queryRunner: QueryRunner): Promise<void> {
11+
await queryRunner.query(`ALTER TABLE "files" DROP COLUMN "isPublic"`);
12+
}
13+
}

platforms/file-manager/api/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ app.get("/api/auth/offer", authController.getOffer);
8080
app.post("/api/auth", authController.login);
8181
app.get("/api/auth/sessions/:id", authController.sseStream);
8282
app.post("/api/webhook", webhookController.handleWebhook);
83+
app.get("/api/public/files/:id", fileController.publicPreview);
8384

8485
// Protected routes (auth required)
8586
app.use(authMiddleware);

platforms/file-manager/api/src/services/FileService.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,20 @@ export class FileService {
114114
return savedFile;
115115
}
116116

117+
async getFileByIdPublic(id: string): Promise<File | null> {
118+
const file = await this.fileRepository.findOne({
119+
where: { id, isPublic: true },
120+
});
121+
if (!file || file.name === SOFT_DELETED_FILE_NAME) {
122+
return null;
123+
}
124+
return file;
125+
}
126+
127+
async setFilePublic(id: string, isPublic: boolean): Promise<void> {
128+
await this.fileRepository.update(id, { isPublic });
129+
}
130+
117131
async getFileById(id: string, userId: string): Promise<File | null> {
118132
const file = await this.fileRepository.findOne({
119133
where: { id },

platforms/marketplace/client/client/src/data/apps.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,5 +87,13 @@
8787
"category": "Storage",
8888
"logoUrl": "/emover.png",
8989
"url": "https://emover.w3ds.metastate.foundation"
90+
},
91+
{
92+
"id": "profile-editor",
93+
"name": "Profile Editor",
94+
"description": "Create and manage your professional profile across the W3DS ecosystem. Showcase your skills, experience, and credentials.",
95+
"category": "Identity",
96+
"logoUrl": "/profile-editor.png",
97+
"url": "https://profile-editor.w3ds.metastate.foundation"
9098
}
9199
]

platforms/marketplace/client/client/src/pages/app-detail.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ const appDetails: Record<string, { fullDescription: string; screenshots: string[
4646
"file-manager": {
4747
fullDescription: "File Manager is a decentralized file management system built on the Web 3.0 Data Space (W3DS) architecture. Organize, store, and share files with complete control over your data across the MetaState ecosystem.\n\nBuilt around the principle of data-platform separation, all your files are stored in your own sovereign eVault. Manage folders, organize documents, control access, and share files securely. Experience file management reimagined with privacy-first principles and complete data sovereignty.",
4848
screenshots: []
49+
},
50+
"profile-editor": {
51+
fullDescription: "Profile Editor is a professional profile management platform built on the Web 3.0 Data Space (W3DS) architecture. Create, edit, and share your professional profile across the entire MetaState ecosystem with a single source of truth.\n\nShowcase your work experience, education, skills, and social links. Upload your CV and video introduction. All your profile data is stored in your own sovereign eVault and automatically synced across every W3DS platform — update once, reflected everywhere.",
52+
screenshots: []
4953
}
5054
};
5155

platforms/pictique/api/src/controllers/WebhookController.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,13 @@ export class WebhookController {
4141
const mapping = Object.values(this.adapter.mapping).find(
4242
(m) => m.schemaId === schemaId
4343
);
44-
this.adapter.addToLockedIds(globalId);
4544

46-
if (!mapping) throw new Error();
45+
if (!mapping) {
46+
console.log(`[webhook] skipping unknown schema ${schemaId} for ${globalId}`);
47+
return res.status(200).send();
48+
}
49+
50+
this.adapter.addToLockedIds(globalId);
4751
const local = await this.adapter.fromGlobal({
4852
data: req.body.data,
4953
mapping,

0 commit comments

Comments
 (0)