Skip to content

Commit

Permalink
feature - make sharable links for owned bookmarks
Browse files Browse the repository at this point in the history
  • Loading branch information
adixchen committed Dec 4, 2022
1 parent 93eabfe commit c3949a4
Show file tree
Hide file tree
Showing 24 changed files with 195 additions and 27 deletions.
20 changes: 17 additions & 3 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"showdown": "^1.9.1",
"superagent": "^4.1.0",
"swagger-ui-express": "^4.3.0",
"uuid": "^9.0.0",
"yamljs": "^0.3.0"
},
"devDependencies": {
Expand Down
1 change: 1 addition & 0 deletions backend/src/model/bookmark.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const bookmarkSchema = new Schema({
shareableId: {type:String, select: false},
name: {type:String, required: true},
location: {type:String, required: true},
description: String,
Expand Down
17 changes: 13 additions & 4 deletions backend/src/routes/public/public-bookmarks.router.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ router.get('/', async (request, response, next) => {
const sort = request.query.sort;
const {page, limit} = PaginationQueryParamsHelper.getPageAndLimit(request);

if (searchText) {
if ( searchText ) {
const bookmarks = await publicBookmarksSearchService.findPublicBookmarks(searchText, page, limit, sort, searcMode);
response.send(bookmarks);
} else {
Expand All @@ -29,7 +29,7 @@ router.get('/', async (request, response, next) => {
*/
router.get('/', async (request, response, next) => {
const location = request.query.location;
if (location) {
if ( location ) {
const bookmarksForLocation = await PublicBookmarksService.getPublicBookmarkByLocation(location);

return response.send(bookmarksForLocation);
Expand All @@ -38,11 +38,20 @@ router.get('/', async (request, response, next) => {
}
});

/**
* Get Bookmark by shareableId
*/
router.get('/shared/:shareableId', async (request, response, next) => {
const shareableId = request.params.shareableId;
const sharedBookmark = await PublicBookmarksService.getBookmarkBySharableId(shareableId);

return response.json(sharedBookmark);
});

/**
* When no filter send latest public bookmarks
*/
router.get('/', async (request, response) => {

const {page, limit} = PaginationQueryParamsHelper.getPageAndLimit(request);
const bookmarks = await PublicBookmarksService.getLatestPublicBookmarks(page, limit);

Expand Down Expand Up @@ -71,7 +80,7 @@ router.get('/tagged/:tag', async (request, response) => {
/* GET title of bookmark given its url - might be moved to front-end */
router.get('/scrape', async function (request, response, next) {
const location = request.query.location;
if (location) {
if ( location ) {
const webpageData = await PublicBookmarksService.getScrapedDataForLocation(location);

return response.send(webpageData);
Expand Down
15 changes: 14 additions & 1 deletion backend/src/routes/public/public-bookmarks.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,20 @@ let getBookmarkById = async function (bookmarkId) {
return bookmark;
};

let getMostUsedPublicTags = async function (limit) {
/* GET bookmark of user by shareableId */
let getBookmarkBySharableId = async (shareableId) => {
const bookmark = await Bookmark.findOne({
shareableId: shareableId
}).select('+shareableId');

if ( !bookmark ) {
throw new NotFoundError(`Bookmark NOT_FOUND for shareableId: ${shareableId}`);
} else {
return bookmark;
}
};

let getMostUsedPublicTags = async function (limit) {
const aggregatedTags = await Bookmark.aggregate([
//first stage - filter
{
Expand Down Expand Up @@ -112,5 +124,6 @@ module.exports = {
getLatestPublicBookmarks: getLatestPublicBookmarks,
getBookmarksForTag: getBookmarksForTag,
getBookmarkById: getBookmarkById,
getBookmarkBySharableId: getBookmarkBySharableId,
getMostUsedPublicTags: getMostUsedPublicTags
};
10 changes: 10 additions & 0 deletions backend/src/routes/users/bookmarks/personal-bookmarks.router.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,16 @@ personalBookmarksRouter.get('/:bookmarkId', keycloak.protect(), async (request,
return response.status(HttpStatus.OK).send(bookmark);
});

/* GET sharableId for bookmark */
personalBookmarksRouter.get('/shareable/:bookmarkId', keycloak.protect(), async (request, response) => {
UserIdValidator.validateUserId(request);

const {userId, bookmarkId} = request.params;
const shareableId = await PersonalBookmarksService.getOrCreateSharableId(userId, bookmarkId);
const sharableIdResponse = {"shareableId": shareableId}
return response.json(sharableIdResponse);
});

/**
* full UPDATE via PUT - that is the whole document is required and will be updated
* the descriptionHtml parameter is only set in backend, if only does not come front-end (might be an API call)
Expand Down
32 changes: 31 additions & 1 deletion backend/src/routes/users/bookmarks/personal-bookmarks.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ const User = require('../../../model/user');
const NotFoundError = require('../../../error/not-found.error');

const BookmarkInputValidator = require('../../../common/bookmark-input.validator');
const {
v4: uuidv4,
} = require('uuid');

/**
* CREATE bookmark for user
Expand Down Expand Up @@ -191,6 +194,32 @@ let increaseOwnerVisitCount = async (userId, bookmarkId) => {
);
}

let getOrCreateShareableId = async (userId, bookmarkId) => {
const bookmark = await Bookmark.findOne(
{
_id: bookmarkId,
userId: userId
}).select('+shareableId');

if ( bookmark.shareableId ) {
return bookmark.shareableId
} else {
const uuid = uuidv4();
const updatedBookmark = await Bookmark.findOneAndUpdate(
{
_id: bookmarkId,
userId: userId
},
{
$set: {shareableId: uuid}
},
{new: true}
).select('+shareableId');

return updatedBookmark.shareableId;
}
}


/*
* DELETE bookmark for user
Expand Down Expand Up @@ -295,5 +324,6 @@ module.exports = {
deleteBookmarkByLocation: deleteBookmarkByLocation,
deletePrivateBookmarksByTag: deletePrivateBookmarksByTag,
getUserTagsAggregated: getUserTagsAggregated,
updateDisplayNameInBookmarks: updateDisplayNameInBookmarks
updateDisplayNameInBookmarks: updateDisplayNameInBookmarks,
getOrCreateSharableId: getOrCreateShareableId
};
1 change: 0 additions & 1 deletion frontend/src/app/core/auth/auth-guard.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import { KeycloakAuthGuard, KeycloakService } from 'keycloak-angular';
import { environment } from '../../../environments/environment';

@Injectable()
export class AuthGuard extends KeycloakAuthGuard {
Expand Down
1 change: 1 addition & 0 deletions frontend/src/app/core/model/bookmark.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface Bookmark {
_id?: string;
shareableId?: string;
name: string;
location: string;
description?: string;
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/app/core/personal-bookmarks.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ export class PersonalBookmarksService {
.pipe(shareReplay(1));
}

createOrGetShareableId(userId: string, bookmarkId: string): Observable<any> {
return this.httpClient
.get<string>(`${this.personalBookmarksApiBaseUrl}/${userId}/bookmarks/shareable/${bookmarkId}`)
.pipe(shareReplay(1));
}

increaseOwnerVisitCount(bookmark: Bookmark) {
return this.httpClient
.post(`${this.personalBookmarksApiBaseUrl}/${bookmark.userId}/bookmarks/${bookmark._id}/owner-visits/inc`, {},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { UserInfoStore } from '../../core/user/user-info.store';
import { ActivatedRoute } from '@angular/router';
import { Observable, of } from 'rxjs';
import { Bookmark } from '../../core/model/bookmark';
Expand All @@ -15,7 +14,6 @@ export class PublicBookmarkDetailsComponent implements OnInit {

constructor(
private publicBookmarksService: PublicBookmarksService,
private userInfoStore: UserInfoStore,
private route: ActivatedRoute) {
}

Expand Down
5 changes: 5 additions & 0 deletions frontend/src/app/public/bookmarks/public-bookmarks.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ export class PublicBookmarksService {
.get<Bookmark>(`${this.publicBookmarksApiBaseUrl}/${bookmarkId}`);
}

getSharedBookmarkBySharableId(shareableId: string): Observable<Bookmark> {
return this.httpClient
.get<Bookmark>(`${this.publicBookmarksApiBaseUrl}/shared/${shareableId}`);
}

getMostUsedPublicTags(limit: number): Observable<UsedTag[]> {
const params = new HttpParams()
.set('limit', limit.toString());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div *ngIf="(bookmark$ | async) as bookmark">
<app-bookmark-list-element
[bookmark]="bookmark"
[isDetailsPage]="true"
[showMoreText]="true">
</app-bookmark-list-element>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Component, OnInit } from '@angular/core';
import { Bookmark } from '../../../core/model/bookmark';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { PublicBookmarksService } from '../public-bookmarks.service';


@Component({
selector: 'app-bookmark-details',
templateUrl: './shareable-bookmark-details.component.html',
styleUrls: ['./shareable-bookmark-details.component.scss']
})
export class ShareableBookmarkDetailsComponent implements OnInit {

bookmark$: Observable<Bookmark>;
popup: string;

constructor(
private route: ActivatedRoute,
private publicBookmarksService: PublicBookmarksService
) {
}

ngOnInit() {
const shareableId = this.route.snapshot.paramMap.get('shareableId');
this.bookmark$ = this.publicBookmarksService.getSharedBookmarkBySharableId(shareableId);
}

}
8 changes: 8 additions & 0 deletions frontend/src/app/public/public-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import { ExtensionsPageComponent } from './extensions/extensions-page.component'
import { AboutComponent } from './about/about.component';
import { RegisterComponent } from './register/register.component';
import { PublicBookmarkDetailsComponent } from './bookmarks/public-bookmark-details.component';
import {
ShareableBookmarkDetailsComponent
} from './bookmarks/shareable-bookmark-details/shareable-bookmark-details.component';

const publicRoutes: Routes = [
{
Expand Down Expand Up @@ -125,6 +128,10 @@ const publicRoutes: Routes = [
}
]
},
{
path: 'bookmarks/shared/:shareableId',
component: ShareableBookmarkDetailsComponent
},
{
path: 'bookmarks/:id',
component: PublicBookmarkDetailsComponent,
Expand All @@ -138,6 +145,7 @@ const publicRoutes: Routes = [
}
]
},

{
path: 'users/:userId',
component: UserPublicProfileComponent,
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/app/public/public.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import { ExtensionsPageComponent } from './extensions/extensions-page.component'
import { AboutComponent } from './about/about.component';
import { RegisterComponent } from './register/register.component';
import { PublicBookmarkDetailsComponent } from './bookmarks/public-bookmark-details.component';
import {
ShareableBookmarkDetailsComponent
} from './bookmarks/shareable-bookmark-details/shareable-bookmark-details.component';

@NgModule({
declarations : [
Expand All @@ -41,6 +44,7 @@ import { PublicBookmarkDetailsComponent } from './bookmarks/public-bookmark-deta
PublicSnippetsComponent,
PublicBookmarkDetailsComponent,
SnippetTaggedComponent,
ShareableBookmarkDetailsComponent
],
imports: [
SharedModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ <h6 class="card-subtitle mb-2 text-muted url-under-title">
<button
class="btn btn-light btn-sm float-right"
title="Share via email or on social media"
(click)="shareBookmark(bookmark)"><i class="fas fa-share"></i> Share
(click)="shareBookmarkDialog(bookmark)"><i class="fas fa-share"></i> Share
</button>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ export class BookmarkListElementComponent extends TagFollowingBaseComponent impl
this.router.navigate(['/']);
}

shareBookmark(bookmark: Bookmark) {
shareBookmarkDialog(bookmark: Bookmark) {

const dialogConfig = new MatDialogConfig();

Expand All @@ -245,6 +245,9 @@ export class BookmarkListElementComponent extends TagFollowingBaseComponent impl
dialogConfig.minWidth = 380;
dialogConfig.data = {
bookmark: bookmark,
userIsLoggedIn: this.userIsLoggedIn,
userOwnsBookmark: this.bookmark.userId === this.userId,
userId: this.userId
};

const dialogRef = this.deleteDialog.open(SocialShareDialogComponent, dialogConfig);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ <h2 mat-dialog-title [innerHTML]="title"></h2>
<mat-expansion-panel
*ngFor="let bookmark of bookmarks | bookmarkFilter: filterText as filteredBookmarks; index as i"
[expanded]="filteredBookmarks.length === 1">

<mat-expansion-panel-header *ngIf="i<15">
<div class="p-3">
<h5 class="card-title">
Expand Down
Loading

0 comments on commit c3949a4

Please sign in to comment.