Browse Source
feat: add newsletter (#204)
feat: add newsletter (#204)
* feat(newsletter): add newsletter service, endpoints, and UI components Add Newsletter service to support functionality for unfollowing newsletters Add Newsletter REST controller and routing Implement newsletter-related endpoints and methods in the User Service Create UnfollowRequest for the INewsletterService interface Add MyListNewsletterResponse to user's data fields Add newsletter validation Refactor JS components to support newsletter type and simplify recipient forms by moving logic to FormRecipient component Refactor window global constants to support newsletters Modify server to initialize and use the newsletter services Add UI component for listing newsletters Refactor existing components to use FormRecipient for recipient data input * chore: update documentation feat(openapi.yaml): add newsletter support with new paths and schemas docs(readme.md): update API endpoints including newsletter and images fix(openapi.yaml): correct duplicated summary text for user my newsletters * feat: update package name * feat: Update src/views/components/NewsletterList.js Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * feat: Update src/views/components/NewsletterList.js Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * feat: Update src/views/components/NewsletterList.js Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>pull/208/head v4.20.0
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 766 additions and 240 deletions
-
301docs/openapi.yaml
-
7readme.md
-
2src/cmd/root.go
-
11src/domains/newsletter/newsletter.go
-
4src/domains/user/account.go
-
1src/domains/user/user.go
-
32src/internal/rest/newsletter.go
-
13src/internal/rest/user.go
-
22src/pkg/whatsapp/whatsapp.go
-
32src/services/newsletter.go
-
14src/services/user.go
-
20src/validations/newsletter_validation.go
-
25src/views/components/AccountAvatar.js
-
24src/views/components/AccountUserInfo.js
-
26src/views/components/MessageDelete.js
-
26src/views/components/MessageReact.js
-
26src/views/components/MessageRevoke.js
-
26src/views/components/MessageUpdate.js
-
117src/views/components/NewsletterList.js
-
25src/views/components/SendAudio.js
-
26src/views/components/SendContact.js
-
26src/views/components/SendFile.js
-
26src/views/components/SendImage.js
-
26src/views/components/SendLocation.js
-
25src/views/components/SendMessage.js
-
26src/views/components/SendPoll.js
-
26src/views/components/SendVideo.js
-
52src/views/components/generic/FormRecipient.js
-
17src/views/index.html
@ -0,0 +1,11 @@ |
|||
package newsletter |
|||
|
|||
import "context" |
|||
|
|||
type INewsletterService interface { |
|||
Unfollow(ctx context.Context, request UnfollowRequest) (err error) |
|||
} |
|||
|
|||
type UnfollowRequest struct { |
|||
NewsletterID string `json:"newsletter_id" form:"newsletter_id"` |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
package rest |
|||
|
|||
import ( |
|||
domainNewsletter "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/newsletter" |
|||
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" |
|||
"github.com/gofiber/fiber/v2" |
|||
) |
|||
|
|||
type Newsletter struct { |
|||
Service domainNewsletter.INewsletterService |
|||
} |
|||
|
|||
func InitRestNewsletter(app *fiber.App, service domainNewsletter.INewsletterService) Newsletter { |
|||
rest := Newsletter{Service: service} |
|||
app.Post("/newsletter/unfollow", rest.Unfollow) |
|||
return rest |
|||
} |
|||
|
|||
func (controller *Newsletter) Unfollow(c *fiber.Ctx) error { |
|||
var request domainNewsletter.UnfollowRequest |
|||
err := c.BodyParser(&request) |
|||
utils.PanicIfNeeded(err) |
|||
|
|||
err = controller.Service.Unfollow(c.UserContext(), request) |
|||
utils.PanicIfNeeded(err) |
|||
|
|||
return c.JSON(utils.ResponseData{ |
|||
Status: 200, |
|||
Code: "SUCCESS", |
|||
Message: "Success unfollow newsletter", |
|||
}) |
|||
} |
|||
@ -0,0 +1,32 @@ |
|||
package services |
|||
|
|||
import ( |
|||
"context" |
|||
domainNewsletter "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/newsletter" |
|||
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" |
|||
"github.com/aldinokemal/go-whatsapp-web-multidevice/validations" |
|||
"go.mau.fi/whatsmeow" |
|||
) |
|||
|
|||
type newsletterService struct { |
|||
WaCli *whatsmeow.Client |
|||
} |
|||
|
|||
func NewNewsletterService(waCli *whatsmeow.Client) domainNewsletter.INewsletterService { |
|||
return &newsletterService{ |
|||
WaCli: waCli, |
|||
} |
|||
} |
|||
|
|||
func (service newsletterService) Unfollow(ctx context.Context, request domainNewsletter.UnfollowRequest) (err error) { |
|||
if err = validations.ValidateUnfollowNewsletter(ctx, request); err != nil { |
|||
return err |
|||
} |
|||
|
|||
JID, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.NewsletterID) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
return service.WaCli.UnfollowNewsletter(JID) |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
package validations |
|||
|
|||
import ( |
|||
"context" |
|||
domainNewsletter "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/newsletter" |
|||
pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" |
|||
validation "github.com/go-ozzo/ozzo-validation/v4" |
|||
) |
|||
|
|||
func ValidateUnfollowNewsletter(ctx context.Context, request domainNewsletter.UnfollowRequest) error { |
|||
err := validation.ValidateStructWithContext(ctx, &request, |
|||
validation.Field(&request.NewsletterID, validation.Required), |
|||
) |
|||
|
|||
if err != nil { |
|||
return pkgError.ValidationError(err.Error()) |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
@ -0,0 +1,117 @@ |
|||
export default { |
|||
name: 'ListNewsletter', |
|||
data() { |
|||
return { |
|||
newsletters: [] |
|||
} |
|||
}, |
|||
methods: { |
|||
async openModal() { |
|||
try { |
|||
this.dtClear() |
|||
await this.submitApi(); |
|||
$('#modalNewsletterList').modal('show'); |
|||
this.dtRebuild() |
|||
showSuccessInfo("Newsletters fetched") |
|||
} catch (err) { |
|||
showErrorInfo(err) |
|||
} |
|||
}, |
|||
dtClear() { |
|||
$('#account_newsletters_table').DataTable().destroy(); |
|||
}, |
|||
dtRebuild() { |
|||
$('#account_newsletters_table').DataTable({ |
|||
"pageLength": 100, |
|||
"reloadData": true, |
|||
}).draw(); |
|||
}, |
|||
async handleUnfollowNewsletter(newsletter_id) { |
|||
try { |
|||
const ok = confirm("Are you sure to leave this newsletter?"); |
|||
if (!ok) return; |
|||
|
|||
await this.unfollowNewsletterApi(newsletter_id); |
|||
this.dtClear() |
|||
await this.submitApi(); |
|||
this.dtRebuild() |
|||
showSuccessInfo("Success unfollow newsletter") |
|||
} catch (err) { |
|||
showErrorInfo(err) |
|||
} |
|||
}, |
|||
async unfollowNewsletterApi(newsletter_id) { |
|||
try { |
|||
let payload = { |
|||
newsletter_id: newsletter_id |
|||
}; |
|||
await window.http.post(`/newsletter/unfollow`, payload) |
|||
} catch (error) { |
|||
if (error.response) { |
|||
throw new Error(error.response.data.message); |
|||
} |
|||
throw new Error(error.message); |
|||
|
|||
} |
|||
}, |
|||
async submitApi() { |
|||
try { |
|||
let response = await window.http.get(`/user/my/newsletters`) |
|||
this.newsletters = response.data.results.data; |
|||
} catch (error) { |
|||
if (error.response) { |
|||
throw new Error(error.response.data.message); |
|||
} |
|||
throw new Error(error.message); |
|||
} |
|||
}, |
|||
formatDate: function (value) { |
|||
if (!value) return '' |
|||
if (isNaN(value)) return 'Invalid date'; |
|||
return moment.unix(value).format('LLL'); |
|||
} |
|||
}, |
|||
template: `
|
|||
<div class="green card" @click="openModal" style="cursor: pointer"> |
|||
<div class="content"> |
|||
<a class="ui green right ribbon label">Newsletter</a> |
|||
<div class="header">List Newsletters</div> |
|||
<div class="description"> |
|||
Display all your newsletters |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<!-- Modal AccountNewsletter --> |
|||
<div class="ui small modal" id="modalNewsletterList"> |
|||
<i class="close icon"></i> |
|||
<div class="header"> |
|||
My Newsletter List |
|||
</div> |
|||
<div class="content"> |
|||
<table class="ui celled table" id="account_newsletters_table"> |
|||
<thead> |
|||
<tr> |
|||
<th>Newsletter ID</th> |
|||
<th>Name</th> |
|||
<th>Role</th> |
|||
<th>Created At</th> |
|||
<th>Action</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody v-if="newsletters != null"> |
|||
<tr v-for="n in newsletters"> |
|||
<td>{{ n.id.split('@')[0] }}</td> |
|||
<td>{{ n.thread_metadata?.name?.text || 'N/A' }}</td> |
|||
<td>{{ n.viewer_metadata?.role || 'N/A' }}</td> |
|||
<td>{{ formatDate(n.thread_metadata?.creation_time) }}</td> |
|||
<td> |
|||
<button class="ui red tiny button" @click="handleUnfollowNewsletter(n.id)">Unfollow</button> |
|||
</td> |
|||
</tr> |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
</div> |
|||
`
|
|||
} |
|||
@ -0,0 +1,52 @@ |
|||
export default { |
|||
name: 'FormRecipient', |
|||
props: { |
|||
type: { |
|||
type: String, |
|||
required: true |
|||
}, |
|||
phone: { |
|||
type: String, |
|||
required: true |
|||
}, |
|||
}, |
|||
data() { |
|||
return { |
|||
recipientTypes: [] |
|||
}; |
|||
}, |
|||
computed: { |
|||
phone_id() { |
|||
return this.phone + this.type; |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.recipientTypes = [ |
|||
{ value: window.TYPEUSER, text: 'Private Message' }, |
|||
{ value: window.TYPEGROUP, text: 'Group Message' }, |
|||
{ value: window.TYPENEWSLETTER, text: 'Newsletter' } |
|||
]; |
|||
}, |
|||
methods: { |
|||
updateType(event) { |
|||
this.$emit('update:type', event.target.value); |
|||
}, |
|||
updatePhone(event) { |
|||
this.$emit('update:phone', event.target.value); |
|||
} |
|||
}, |
|||
template: `
|
|||
<div class="field"> |
|||
<label>Type</label> |
|||
<select name="type" @change="updateType" class="ui dropdown"> |
|||
<option v-for="type in recipientTypes" :value="type.value">{{ type.text }}</option> |
|||
</select> |
|||
</div> |
|||
|
|||
<div class="field"> |
|||
<label>Phone / Group ID</label> |
|||
<input :value="phone" aria-label="wa identifier" @input="updatePhone"> |
|||
<input :value="phone_id" disabled aria-label="whatsapp_id"> |
|||
</div> |
|||
`
|
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue