feat: integrate blob storage and blob manage

This commit is contained in:
1721518185
2023-12-19 16:33:31 +08:00
parent 764fd4929f
commit f2254cfd0c
13 changed files with 214 additions and 156 deletions
@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import {Component, Input, OnInit} from '@angular/core';
import { NzMessageService } from 'ng-zorro-antd/message';
import { NzUploadChangeParam, NzUploadFile } from 'ng-zorro-antd/upload';
import { BlobServiceClient, ContainerClient } from '@azure/storage-blob';
@@ -10,10 +10,11 @@ import { NzModalService } from 'ng-zorro-antd/modal';
styleUrls: ['./file-upload.component.scss']
})
export class FileUploadComponent implements OnInit {
@Input() uniqueGameName: string | '' | undefined;
uploading = false;
fileList: NzUploadFile[] = [];
uploadTitle = '';
sasUrl = 'https://wecloudblob.blob.core.windows.net/?sv=2022-11-02&ss=bfqt&srt=sco&sp=rwdlacupiytfx&se=2023-12-18T18:13:02Z&st=2023-12-18T10:13:02Z&spr=https,http&sig=pMEPhuudBIztt%2FjWBY%2BWIeGqWHN6BdYMAzUmGSy7PUU%3D';
sasUrl = 'https://wecloudblob.blob.core.windows.net/games?si=all&sv=2022-11-02&sr=c&sig=YU%2BMkvJqCF8%2FKQkAH2orl8%2FVzHWhUpZydluUfHfFZK0%3D';
containerName = 'games';
blobServiceClient: BlobServiceClient;
containerClient: ContainerClient;
@@ -34,8 +35,13 @@ export class FileUploadComponent implements OnInit {
};
handleUpload(): void {
if (!this.uniqueGameName || this.uniqueGameName.trim() === '') {
this.$message.warning('Please select the media name before uploading.');
return;
}
this.modalService.confirm({
nzTitle: 'Confirm Upload',
nzTitle: 'Please Confirm Your Upload',
nzContent: `Title: ${this.uploadTitle}<br/>Number of files: ${this.fileList.length}`,
nzOnOk: () => this.onConfirmUpload()
});
@@ -43,13 +49,11 @@ export class FileUploadComponent implements OnInit {
async onConfirmUpload() {
this.uploading = true;
this.uploadTitle = ''; // Reset the title for the next upload
for (const file of this.fileList) {
const blob = new Blob([file as any], { type: file.type });
await this.uploadFileToAzure(this.uploadTitle, file.name, blob);
}
this.uploadTitle = '';
this.fileList = [];
this.uploading = false;
}
@@ -66,7 +70,8 @@ export class FileUploadComponent implements OnInit {
async uploadFileToAzure(uploadTitle: string, fileName: string, blob: Blob) {
try {
const newFileName = `${uploadTitle}_${fileName}`;
const username = localStorage.getItem('username') as any;
const newFileName = `${uploadTitle}_${username}_${this.uniqueGameName}_${fileName}`;
const blockBlobClient = this.containerClient.getBlockBlobClient(newFileName);
await blockBlobClient.uploadData(blob);
this.$message.success(`${fileName} uploaded successfully.`);
@@ -146,7 +146,7 @@
<!-- Insert the FileUploadComponent -->
<div nz-col [nzSpan]="24">
<app-file-upload></app-file-upload>
<app-file-upload [uniqueGameName]="uniqueGameName"></app-file-upload>
</div>
</div>
</form>
+7 -19
View File
@@ -16,6 +16,8 @@ export class ListComponent implements OnInit {
loading: boolean = true;
isVisible: boolean = false;
addForm!: FormGroup;
// the game name for upload media
uniqueGameName: string | '' | undefined;
addField: any = [
{
label: 'Name',
@@ -108,6 +110,10 @@ export class ListComponent implements OnInit {
Distributer: [null],
Developer: [null],
});
// @ts-ignore
this.addForm.get('Name').valueChanges.subscribe((value) => {
this.uniqueGameName = value;
});
this.getWegameList(true);
}
@@ -115,24 +121,7 @@ export class ListComponent implements OnInit {
return JSON.parse(localStorage.getItem('names') || '[]');
}
submitForm(): void {
if (this.addForm.valid) {
let params = { ...this.addForm.value };
this.apiService.post('new_game', params).subscribe(
(res: any) => {
this.isVisible = false;
this.getWegameList();
this.$message.success(`add success!`);
},
() => {}
);
} else {
Object.values(this.addForm.controls).forEach((control: any) => {
if (control.invalid) {
control.markAsDirty();
control.updateValueAndValidity({ onlySelf: true });
}
});
}
}
onSearch() {
@@ -179,7 +168,6 @@ export class ListComponent implements OnInit {
)[1];
});
this.total = total;
console.log('this.wegameList', this.wegameList);
if (names) {
localStorage.setItem('names', JSON.stringify(names));
}
@@ -26,7 +26,7 @@ export class MyUploadComponent implements OnInit {
}[] = [];
// Azure Storage SAS URL
sasUrl = 'https://wecloudblob.blob.core.windows.net/?sv=2022-11-02&ss=bfqt&srt=sco&sp=rwdlacupiytfx&se=2023-12-18T18:13:02Z&st=2023-12-18T10:13:02Z&spr=https,http&sig=pMEPhuudBIztt%2FjWBY%2BWIeGqWHN6BdYMAzUmGSy7PUU%3D';
// sasToken = 'si=asset&sv=2022-11-02&sr=c&sig=OA8DpIpkl1ak4ZG%2BA7BzMtIRbI6carOTxLtzAcHa9pg%3D'
containerName = 'games';
constructor(
private apiService: ApiService,
private $message: NzMessageService,
@@ -38,14 +38,6 @@ export class MyUploadComponent implements OnInit {
ngOnInit() {
this.username = localStorage.getItem('username') as any;
this.loadImagesFromAzure();
}
private extractSasToken(): string {
// Extract the SAS token from your sasUrl
const sasToken = this.sasUrl.split('?')[1];
return sasToken ? `?${sasToken}` : '';
}
previewImage(image: { url: string, type: string }) {
@@ -73,56 +65,20 @@ export class MyUploadComponent implements OnInit {
async deleteImage(index: number) {
const image = this.images[index];
const blobUrl = image.blobUrl; // Assuming this is the URL of the blob
const blobUrl = image.blobUrl;
const xmsVersion = '2022-11-02';
const headers = new HttpHeaders({
'x-ms-date': new Date().toUTCString(),
'x-ms-version': '2022-11-02' // Use the version of Azure Storage Service you are using
'x-ms-version': xmsVersion
});
try {
await this.http.delete(blobUrl, { headers }).toPromise();
// After the image is successfully deleted from Azure, remove it from the images array
//refresh the page
this.images.splice(index, 1);
} catch (error) {
console.error('Failed to delete blob:', error);
}
}
async loadImagesFromAzure() {
const containerUrl = 'https://medias.blob.core.windows.net/games';
const query = '?restype=container&comp=list'; // SAS token
const fullUrl = `${containerUrl}${query}`;
try {
const response = await lastValueFrom(this.http.get(fullUrl, { responseType: 'text' }));
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(response, 'text/xml');
const blobs = xmlDoc.getElementsByTagName('Blob');
this.images = [];
for (let i = 0; i < blobs.length; i++) {
const blob = blobs[i];
const name = blob.getElementsByTagName('Name')[0].textContent || '';
const title = JSON.parse(localStorage.getItem('gameInfo') as any).Title;
const lastModified = blob.getElementsByTagName('Last-Modified')[0].textContent || '';
if (!name || name.includes(this.username)) {
// const blobUrl = `${containerUrl}/${name}${this.extractSasToken()}}`;
const url = blob.getElementsByTagName('Url')[0].textContent || '';
const blobUrl = `${containerUrl}/${name}${this.extractSasToken()}`;
const picTitle = name.split('_')[0].replace('assets/', '');
// Assuming you want to display images
this.images.push({
url: url,
type: 'image/jpeg',
uploadTime: lastModified ,
blobUrl: blobUrl,
picTitle: picTitle
}); // Modify 'type' as needed
}
}
} catch (error) {
console.error('Error fetching blobs:', error);
}
}
}
+1 -15
View File
@@ -1,16 +1,2 @@
<h2>Upload Count({{images.length}})</h2>
<nz-list [nzDataSource]="images" nzBordered>
<nz-list-item *ngFor="let image of images; let i = index">
<nz-list-item-meta
[nzAvatar]="image.url"
[nzTitle]="'Upload by ' + username"
[nzDescription]="image.uploadTime">
<p>{{ image.picTitle }}</p>
<a [href]="image.url" target="_blank">{{ image.url }}</a>
</nz-list-item-meta>
<button nz-button nzType="primary" [ngStyle]="{ marginRight: '10px' }" (click)="previewImage(image)">Preview</button>
<button nz-button nzType="primary" [ngStyle]="{ marginRight: '10px' }" (click)="downloadImage(image.url)">Download</button>
<button nz-button nzType="primary" nzDanger (click)="deleteImage(i)">Delete</button>
</nz-list-item>
</nz-list>
<app-my-upload [filterCriteria]="username"></app-my-upload>
@@ -3,12 +3,14 @@ import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { SharedModule } from '../../shared/shared.module';
import { MyUploadComponent } from './myUpload.component';
import {UploadList} from "../uploadList/uploadList.module";
// import {FormatTimePipe} from "../../../pipe/formatTime.pipe";
@NgModule({
imports: [
CommonModule,
SharedModule,
UploadList,
RouterModule.forChild([
{
path: '',
@@ -0,0 +1,17 @@
<nz-row nzGutter="16">
<nz-col nzSpan="8" *ngFor="let item of images;let index = index">
<nz-card [nzHoverable]="true">
<nz-card-meta [title]="item.picTitle"></nz-card-meta>
<img *ngIf="item.type.startsWith('image')" [src]="item.url" alt="Uploaded Image" class="media"/>
<video *ngIf="item.type.startsWith('video')" [src]="item.url" controls class="media"></video>
<audio *ngIf="item.type.startsWith('audio')" [src]="item.url" controls class="media"></audio>
<nz-card-meta [nzTitle]="'Upload by ' + username" [nzDescription]="item.uploadTime| date:'medium'"></nz-card-meta>
</nz-card>
<button nz-button nzType="primary" [ngStyle]="{ marginRight: '8px' }" (click)="previewImage(item)">Preview</button>
<button nz-button nzType="primary" [ngStyle]="{ marginRight: '8px' }" (click)="downloadImage(item.url)">Download</button>
<button nz-button nzType="primary" nzDanger (click)="deleteImage(index)">Delete</button>
</nz-col>
</nz-row>
@@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { SharedModule } from '../../shared/shared.module';
import { MyUploadComponent} from "./uploadList";
// import {FormatTimePipe} from "../../../pipe/formatTime.pipe";
@NgModule({
imports: [
CommonModule,
SharedModule,
],
declarations: [MyUploadComponent],
exports: [MyUploadComponent],
})
export class UploadList {}
@@ -0,0 +1,17 @@
.music-search {
width: 500px;
margin: 0 auto;
display: block;
margin-bottom: 30px;
}
.nz-avatar {
width: 80px;
height: 80px;
}
.media {
max-width: 100%;
height: auto;
object-fit: cover;
}
@@ -0,0 +1,128 @@
import { Component, OnInit, Input } from '@angular/core';
import { NzMessageService } from 'ng-zorro-antd/message';
import { ApiService } from 'src/app/services/api.service';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { lastValueFrom } from 'rxjs';
import { NzModalService } from 'ng-zorro-antd/modal';
@Component({
selector: 'app-my-upload',
templateUrl: './uploadList.html',
styleUrls: ['./uploadList.scss']
})
export class MyUploadComponent implements OnInit {
@Input() filterCriteria: string | '' | undefined;
favoriteList: any = [];
loading: boolean = true;
username: any;
images: {
url: any,
type: any,
uploadTime: any,
blobUrl: any,
picTitle: any
}[] = [];
sasUrl = 'https://wecloudblob.blob.core.windows.net/games?si=all&sv=2022-11-02&sr=c&sig=YU%2BMkvJqCF8%2FKQkAH2orl8%2FVzHWhUpZydluUfHfFZK0%3D'
containerName = 'games';
constructor(
private apiService: ApiService,
private $message: NzMessageService,
private http: HttpClient,
private modalService: NzModalService
) {}
ngOnInit() {
console.log('filterCriteria!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
console.log(this.filterCriteria);
this.username = localStorage.getItem('username') as any;
this.loadImagesFromAzure();
}
private extractSasToken(): string {
const sasToken = this.sasUrl.split('?')[1];
return sasToken ? `?${sasToken}` : '';
}
previewImage(image: { url: string, type: string }) {
let content = '';
if (image.type.startsWith('image')) {
content = `<img src="${image.url}" alt="Image" style="width: 100%; max-height: 600px;">`;
} else if (image.type.startsWith('video')) {
content = `<video src="${image.url}" controls style="width: 100%; max-height: 600px;"></video>`;
} else if (image.type.startsWith('audio')) {
content = `<audio src="${image.url}" controls style="width: 100%; max-height: 600px;"></audio>`;
} else {
// Handle other types as needed
}
this.modalService.create({
nzTitle: 'Preview',
nzContent: content,
nzFooter: null,
nzWidth: '80%'
});
}
downloadImage(url: string) {
window.location.href = url;
}
async deleteImage(index: number) {
const image = this.images[index];
const blobUrl = image.blobUrl;
const headers = new HttpHeaders({
'x-ms-date': new Date().toUTCString(),
'x-ms-version': '2022-11-02'
});
try {
await this.http.delete(blobUrl, { headers }).toPromise();
this.images.splice(index, 1);
} catch (error) {
console.error('Failed to delete blob:', error);
}
}
async loadImagesFromAzure() {
const containerUrl = 'https://wecloudblob.blob.core.windows.net/games';
const query = '?restype=container&comp=list';
const fullUrl = `${containerUrl}${query}`;
let filter = this.filterCriteria
if (this.filterCriteria==='username'){
filter = this.username;
}
try {
const response = await lastValueFrom(this.http.get(fullUrl, { responseType: 'text' }));
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(response, 'text/xml');
const blobs = xmlDoc.getElementsByTagName('Blob');
this.images = [];
for (let i = 0; i < blobs.length; i++) {
const blob = blobs[i];
const name = blob.getElementsByTagName('Name')[0].textContent || '';
const lastModified = blob.getElementsByTagName('Last-Modified')[0].textContent || '';
if (!name || (filter && !name.includes(filter))) {
continue;
}
const url = blob.getElementsByTagName('Url')[0].textContent || '';
const blobUrl = `${containerUrl}/${name}${this.extractSasToken()}`;
const picTitle = name.split('_')[0].replace(`${this.containerName}/`, '');
this.images.push({
url: url,
type: 'image/jpeg',
uploadTime: lastModified,
blobUrl: blobUrl,
picTitle: picTitle
});
}
} catch (error) {
console.error('Error fetching blobs:', error);
}
}
}
@@ -21,22 +21,6 @@
border-bottom: 1px dashed var(--ant-primary-color);
"
></h2>
<button
nz-button
class="login-form-button login-form-margin"
[nzType]="'primary'"
(click)="onEdit()"
>
Edit Game Detail
</button>
<button
nz-button
nzType="default"
style="margin: 20px 0 20px 10px"
(click)="onDelete()"
>
Delete game
</button>
<button
nz-button
nzType="dashed"
@@ -78,38 +62,8 @@
<ng-template #elseBlock>
<nz-empty nzNotFoundImage="simple"></nz-empty>
</ng-template>
<nz-modal
[(nzVisible)]="isVisible"
nzTitle="Edit Wegame"
nzOkText="Ok"
nzCancelText="Cancel"
(nzOnOk)="submitForm()"
(nzOnCancel)="isVisible = false"
>
<ng-container *nzModalContent>
<form nz-form [formGroup]="editForm">
<div nz-row [nzGutter]="24">
<div nz-col [nzSpan]="24" *ngFor="let field of editField">
<nz-form-item>
<nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="name"
>{{ field.label }}
</nz-form-label>
<nz-form-control
[nzSm]="14"
[nzXs]="24"
[nzErrorTip]="'Please input ' + field.label!"
>
<input
nz-input
[formControlName]="field.value"
id="name"
[placeholder]="field.label"
/>
</nz-form-control>
</nz-form-item>
</div>
</div>
</form>
</ng-container>
</nz-modal>
<h2 style="margin-top: 40px; border-bottom: 1px solid #eee">Shared medias</h2>
<app-my-upload [filterCriteria]="movieInfo.Name"></app-my-upload>
</div>
@@ -56,7 +56,7 @@ export class MovieDetailsComponent implements OnInit {
) {}
ngOnInit(): void {
this.movieInfo = JSON.parse(localStorage.getItem('movieInfo') as any);
this.movieInfo = JSON.parse(localStorage.getItem('wegameInfo') as any);
this.editForm = this.fb.group({
Name: [null, [Validators.required]],
Distributor: [null, [Validators.required]],
@@ -69,19 +69,6 @@ export class MovieDetailsComponent implements OnInit {
});
}
onDelete() {
this.apiService.post('del_game', { _id: this.movieInfo._id }).subscribe(
(res: any) => {
this.$message.success(`Delete ${this.movieInfo.Name} Success!`);
this.router.navigate(['/layout/list'], {});
},
() => {}
);
}
onEdit() {
this.isVisible = true;
this.editForm.patchValue(this.movieInfo);
}
submitForm(): void {
if (this.editForm.valid) {
let params = { ...this.editForm.value, _id: this.movieInfo._id };
@@ -3,12 +3,13 @@ import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { SharedModule } from '../../shared/shared.module';
import { MovieDetailsComponent } from './wegame-details.component';
// import {FormatTimePipe} from "../../../pipe/formatTime.pipe";
import {UploadList} from "../uploadList/uploadList.module";
@NgModule({
imports: [
CommonModule,
SharedModule,
UploadList,
RouterModule.forChild([
{
path: '',