Skip to content

Commit 04e49e4

Browse files
committed
feat - add possibility to share private snippets
via shareable link, similar to bookmarks sharing
1 parent 0933791 commit 04e49e4

30 files changed

+361
-42
lines changed

backend/src/model/snippet.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const CodeSnippetSchema = new Schema({
1313
});
1414

1515
const snippetSchema = new Schema({
16+
shareableId: {type:String, select: false},
1617
title: {type:String, required: true},
1718
type: {type:String, required: true, default: 'snippet'},
1819
codeSnippets: [CodeSnippetSchema],

backend/src/routes/public/public-snippets.router.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@ const SnippetsSearchService = require('../../common/snippets-search.service');
66
const PublicSnippetsService = require('./public-snippets.service');
77

88
const PaginationQueryParamsHelper = require('../../common/pagination-query-params-helper');
9+
const PublicBookmarksService = require("./public-bookmarks.service");
10+
11+
/**
12+
* Get snippet by shareableId
13+
*/
14+
router.get('/shared/:shareableId', async (request, response) => {
15+
const shareableId = request.params.shareableId;
16+
const sharedSnippet = await PublicSnippetsService.getSnippetBySharableId(shareableId);
17+
18+
return response.json(sharedSnippet);
19+
});
920

1021
/**
1122
* Returns the with query text

backend/src/routes/public/public-snippets.service.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ let getMostUsedPublicTagsForSnippets = async function (limit) {
8181
},
8282

8383
//
84-
{ $limit : limit }
84+
{$limit: limit}
8585
]);
8686

8787
const usedTags = aggregatedTags.map(aggregatedTag => {
@@ -94,10 +94,24 @@ let getMostUsedPublicTagsForSnippets = async function (limit) {
9494
return usedTags;
9595
}
9696

97+
/* GET snippet of user by shareableId */
98+
let getSnippetBySharableId = async (shareableId) => {
99+
const snippet = await Snippet.findOne({
100+
shareableId: shareableId
101+
}).select('+shareableId');
102+
103+
if ( !snippet ) {
104+
throw new NotFoundError(`Snippet NOT_FOUND for shareableId: ${shareableId}`);
105+
} else {
106+
return snippet;
107+
}
108+
};
109+
97110

98111
module.exports = {
99112
getSnippetById: getSnippetById,
100113
getLatestPublicSnippets: getLatestPublicSnippets,
101114
getPublicSnippetsForTag: getPublicSnippetsForTag,
102-
getMostUsedPublicTagsForSnippets: getMostUsedPublicTagsForSnippets
115+
getMostUsedPublicTagsForSnippets: getMostUsedPublicTagsForSnippets,
116+
getSnippetBySharableId: getSnippetBySharableId
103117
};

backend/src/routes/users/bookmarks/personal-bookmarks.service.js

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const {
1313
*/
1414
let createBookmark = async function (userId, bookmark) {
1515
BookmarkInputValidator.validateBookmarkInput(userId, bookmark);
16+
bookmark.shareableId = undefined;
1617

1718
await BookmarkInputValidator.verifyPublicBookmarkExistenceOnCreation(bookmark);
1819

@@ -201,23 +202,28 @@ let getOrCreateShareableId = async (userId, bookmarkId) => {
201202
userId: userId
202203
}).select('+shareableId');
203204

204-
if ( bookmark.shareableId ) {
205-
return bookmark.shareableId
206-
} else {
207-
const uuid = uuidv4();
208-
const updatedBookmark = await Bookmark.findOneAndUpdate(
209-
{
210-
_id: bookmarkId,
211-
userId: userId
212-
},
213-
{
214-
$set: {shareableId: uuid}
215-
},
216-
{new: true}
217-
).select('+shareableId');
205+
if ( bookmark ) {
206+
if ( bookmark.shareableId ) {
207+
return bookmark.shareableId
208+
} else {
209+
const uuid = uuidv4();
210+
const updatedBookmark = await Bookmark.findOneAndUpdate(
211+
{
212+
_id: bookmarkId,
213+
userId: userId
214+
},
215+
{
216+
$set: {shareableId: uuid}
217+
},
218+
{new: true}
219+
).select('+shareableId');
218220

219-
return updatedBookmark.shareableId;
221+
return updatedBookmark.shareableId;
222+
}
223+
} else {
224+
throw new NotFoundError(`Bookmark NOT_FOUND the userId: ${userId} AND id: ${bookmarkId}`);
220225
}
226+
221227
}
222228

223229

backend/src/routes/users/snippets/personal-snippets.router.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,16 @@ personalSnippetsRouter.get('/export', keycloak.protect(), async (request, respon
6666
response.send(snippets);
6767
});
6868

69+
/* GET sharableId for bookmark */
70+
personalSnippetsRouter.get('/shareable/:snippetId', keycloak.protect(), async (request, response) => {
71+
UserIdValidator.validateUserId(request);
72+
73+
const {userId, snippetId} = request.params;
74+
const shareableId = await PersonalSnippetsService.getOrCreateShareableId(userId, snippetId);
75+
const sharableIdResponse = {"shareableId": shareableId}
76+
return response.json(sharableIdResponse);
77+
});
78+
6979
/**
7080
* Find personal snippets
7181
*/

backend/src/routes/users/snippets/personal-snippets.service.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ const Snippet = require('../../../model/snippet');
33
const NotFoundError = require('../../../error/not-found.error');
44

55
const SnippetInputValidator = require('./snippet-input.validator');
6+
const {v4: uuidv4} = require("uuid");
67

78
/**
89
* CREATE snippet for user
910
*/
1011
let createSnippet = async function (userId, snippetData) {
1112
SnippetInputValidator.validateSnippetInput(userId, snippetData);
12-
13+
snippetData.shareableId = undefined;
1314
const snippet = new Snippet(snippetData);
15+
1416
let newSnippet = await snippet.save();
1517

1618
return newSnippet;
@@ -138,6 +140,35 @@ let getUserSnippetTags = async (userId) => {
138140
return userTags;
139141
};
140142

143+
let getOrCreateShareableId = async (userId, snippetId) => {
144+
const snippet = await Snippet.findOne(
145+
{
146+
_id: snippetId,
147+
userId: userId
148+
}).select('+shareableId');
149+
150+
if ( snippet ) {
151+
if ( snippet.shareableId ) {
152+
return snippet.shareableId
153+
} else {
154+
const uuid = uuidv4();
155+
const updatedSnippet = await Snippet.findOneAndUpdate(
156+
{
157+
_id: snippetId,
158+
userId: userId
159+
},
160+
{
161+
$set: {shareableId: uuid}
162+
},
163+
{new: true}
164+
).select('+shareableId');
165+
166+
return updatedSnippet.shareableId;
167+
}
168+
} else {
169+
throw new NotFoundError(`Snippet NOT_FOUND the userId: ${userId} AND id: ${snippetId}`);
170+
}
171+
}
141172

142173
module.exports = {
143174
createSnippet: createSnippet,
@@ -148,4 +179,5 @@ module.exports = {
148179
getAllMySnippets: getAllMySnippets,
149180
updateSnippet: updateSnippet,
150181
deleteSnippetById: deleteSnippetById,
182+
getOrCreateShareableId: getOrCreateShareableId
151183
};

documentation/temp/to-do.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,14 @@
55
* add extensive integration tests for search
66
* verify "copy snippet" and show more for (longer snippets see https://www.codever.dev/?q=angular%20custom%20route&sd=my-snippets&page=1&tab=search-results)
77
* remove duplicate code in search services (OR search mode)
8+
*
9+
* verify for bookmarks
10+
<button *ngIf="snippet.public || (!snippet.public && snippet.userId === observables.userId)"
11+
class="btn btn-light btn-sm float-right"
12+
title="Share via email or on social media"
13+
(click)="shareSnippetDialog(snippet, observables.userIsLoggedIn, observables.userId)"><i class="fas fa-share"></i> Share
14+
</button>
15+
16+
* error userinfo in console network when NOT logged in http://localhost:4200/snippets/shared/0a77563c-afed-4b01-af83-52348653e844
17+
18+
* public snippets test not working - see console (results are present but not shown and public bookmarks are searched in...)

frontend/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,6 @@ Run `ng build` to build the project. The build artifacts will be stored in the `
4545

4646
I use an alias for that
4747
```shell
48-
alias bookmarks-build-aot='cd ~/projects/dev/personal/bookmarks/bookmarks.dev/frontend; rm -rf dist*; nvm use; npm run build:aot'
48+
alias codever-build-aot='cd ~/projects/dev/personal/codever/codever/frontend; rm -rf dist*; nvm use; npm run build:aot'
4949
```
5050

frontend/src/app/core/model/snippet.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export interface Snippet {
22
_id?: string;
3+
shareableId?: string;
34
type: string; // should always by 'snippet'
45
title: string;
56
codeSnippets: CodeSnippet[];

frontend/src/app/core/personal-snippets.service.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,10 @@ export class PersonalSnippetsService {
9797
return this.httpClient.get<Snippet[]>(`${this.personalSnippetsApiBaseUrl}/${userId}/snippets/export`)
9898
.pipe(shareReplay(1));
9999
}
100+
101+
createOrGetShareableId(userId: string, snippetId: string): Observable<any> {
102+
return this.httpClient
103+
.get<string>(`${this.personalSnippetsApiBaseUrl}/${userId}/snippets/shareable/${snippetId}`)
104+
.pipe(shareReplay(1));
105+
}
100106
}

0 commit comments

Comments
 (0)