Merge pull request #753 from gradido/apollo-clicktipp-connector

Integration of the KlicktippAPI to the User management
This commit is contained in:
Hannes Heine 2021-09-16 15:51:48 +02:00 committed by GitHub
commit af8a722bd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 5962 additions and 60 deletions

View File

@ -345,7 +345,7 @@ jobs:
report_name: Coverage Frontend
type: lcov
result_path: ./coverage/lcov.info
min_coverage: 66
min_coverage: 67
token: ${{ github.token }}
##############################################################################

View File

@ -9,4 +9,9 @@ DB_HOST=localhost
DB_PORT=3306
DB_USER=root
DB_PASSWORD=
DB_DATABASE=gradido_community
DB_DATABASE=gradido_community
#KLICKTIPP_USER=
#KLICKTIPP_PASSWORD=
#KLICKTIPP_APIKEY_DE=
#KLICKTIPP_APIKEY_EN=
#KLICKTIPP=true

4563
backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,77 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { KlicktippConnector } from './klicktippConnector'
import CONFIG from '../config'
const klicktippConnector = new KlicktippConnector()
export const signIn = async (
email: string,
language: string,
firstName?: string,
lastName?: string,
): Promise<boolean> => {
const fields = {
fieldFirstName: firstName,
fieldLastName: lastName,
}
const apiKey = language === 'de' ? CONFIG.KLICKTIPP_APIKEY_DE : CONFIG.KLICKTIPP_APIKEY_EN
const result = await klicktippConnector.signin(apiKey, email, fields)
return result
}
export const signout = async (email: string, language: string): Promise<boolean> => {
const apiKey = language === 'de' ? CONFIG.KLICKTIPP_APIKEY_DE : CONFIG.KLICKTIPP_APIKEY_EN
const result = await klicktippConnector.signoff(apiKey, email)
return result
}
export const unsubscribe = async (email: string): Promise<boolean> => {
const isLogin = await loginKlicktippUser()
if (isLogin) {
return await klicktippConnector.unsubscribe(email)
}
throw new Error(`Could not unsubscribe ${email}`)
}
export const getKlickTippUser = async (email: string): Promise<any> => {
const isLogin = await loginKlicktippUser()
if (isLogin) {
const subscriberId = await klicktippConnector.subscriberSearch(email)
const result = await klicktippConnector.subscriberGet(subscriberId)
return result
}
return false
}
export const loginKlicktippUser = async (): Promise<boolean> => {
return await klicktippConnector.login(CONFIG.KLICKTIPP_USER, CONFIG.KLICKTIPP_PASSWORD)
}
export const logoutKlicktippUser = async (): Promise<boolean> => {
return await klicktippConnector.logout()
}
export const untagUser = async (email: string, tagId: string): Promise<boolean> => {
const isLogin = await loginKlicktippUser()
if (isLogin) {
return await klicktippConnector.untag(email, tagId)
}
return false
}
export const tagUser = async (email: string, tagIds: string): Promise<boolean> => {
const isLogin = await loginKlicktippUser()
if (isLogin) {
return await klicktippConnector.tag(email, tagIds)
}
return false
}
export const getKlicktippTagMap = async () => {
const isLogin = await loginKlicktippUser()
if (isLogin) {
return await klicktippConnector.tagIndex()
}
return ''
}

View File

@ -0,0 +1,620 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import axios, { AxiosRequestConfig, Method } from 'axios'
export class KlicktippConnector {
private baseURL: string
private sessionName: string
private sessionId: string
private error: string
constructor(service?: string) {
this.baseURL = service !== undefined ? service : 'https://api.klicktipp.com'
this.sessionName = ''
this.sessionId = ''
}
/**
* Get last error
*
* @return string an error description of the last error
*/
getLastError(): string {
const result = this.error
return result
}
/**
* login
*
* @param username The login name of the user to login.
* @param password The password of the user.
* @return TRUE on success
*/
async login(username: string, password: string): Promise<boolean> {
if (!(username.length > 0 && password.length > 0)) {
throw new Error('Klicktipp Login failed: Illegal Arguments')
}
const res = await this.httpRequest('/account/login', 'POST', { username, password }, false)
if (!res.isAxiosError) {
this.sessionId = res.data.sessid
this.sessionName = res.data.session_name
return true
}
throw new Error(`Klicktipp Login failed: ${res.response.statusText}`)
}
/**
* Logs out the user currently logged in.
*
* @return TRUE on success
*/
async logout(): Promise<boolean> {
const res = await this.httpRequest('/account/logout', 'POST')
if (!res.isAxiosError) {
this.sessionId = ''
this.sessionName = ''
return true
}
throw new Error(`Klicktipp Logout failed: ${res.response.statusText}`)
}
/**
* Get all subscription processes (lists) of the logged in user. Requires to be logged in.
*
* @return A associative obeject <list id> => <list name>
*/
async subscriptionProcessIndex(): Promise<any> {
const res = await this.httpRequest('/list', 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscription process index failed: ${res.response.statusText}`)
}
/**
* Get subscription process (list) definition. Requires to be logged in.
*
* @param listid The id of the subscription process
*
* @return An object representing the Klicktipp subscription process.
*/
async subscriptionProcessGet(listid: string): Promise<any> {
if (!listid || listid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// retrieve
const res = await this.httpRequest(`/subscriber/${listid}`, 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscription process get failed: ${res.response.statusText}`)
}
/**
* Get subscription process (list) redirection url for given subscription.
*
* @param listid The id of the subscription process.
* @param email The email address of the subscriber.
*
* @return A redirection url as defined in the subscription process.
*/
async subscriptionProcessRedirect(listid: string, email: string): Promise<any> {
if (!listid || listid === '' || !email || email === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// update
const data = { listid, email }
const res = await this.httpRequest('/list/redirect', 'POST', data)
if (!res.isAxiosError) {
return res.data
}
throw new Error(
`Klicktipp Subscription process get redirection url failed: ${res.response.statusText}`,
)
}
/**
* Get all manual tags of the logged in user. Requires to be logged in.
*
* @return A associative object <tag id> => <tag name>
*/
async tagIndex(): Promise<any> {
const res = await this.httpRequest('/tag', 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Tag index failed: ${res.response.statusText}`)
}
/**
* Get a tag definition. Requires to be logged in.
*
* @param tagid The tag id.
*
* @return An object representing the Klicktipp tag object.
*/
async tagGet(tagid: string): Promise<any> {
if (!tagid || tagid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
const res = await this.httpRequest(`/tag/${tagid}`, 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Tag get failed: ${res.response.statusText}`)
}
/**
* Create a new manual tag. Requires to be logged in.
*
* @param name The name of the tag.
* @param text (optional) An additional description of the tag.
*
* @return The id of the newly created tag or false if failed.
*/
async tagCreate(name: string, text?: string): Promise<boolean> {
if (!name || name === '') {
throw new Error('Klicktipp Illegal Arguments')
}
const data = {
name,
text: text !== undefined ? text : '',
}
const res = await this.httpRequest('/tag', 'POST', data, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Tag creation failed: ${res.response.statusText}`)
}
/**
* Updates a tag. Requires to be logged in.
*
* @param tagid The tag id used to identify which tag to modify.
* @param name (optional) The new tag name. Set empty to leave it unchanged.
* @param text (optional) The new tag description. Set empty to leave it unchanged.
*
* @return TRUE on success
*/
async tagUpdate(tagid: string, name?: string, text?: string): Promise<boolean> {
if (!tagid || tagid === '' || (name === '' && text === '')) {
throw new Error('Klicktipp Illegal Arguments')
}
const data = {
name: name !== undefined ? name : '',
text: text !== undefined ? text : '',
}
const res = await this.httpRequest(`/tag/${tagid}`, 'PUT', data, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Tag update failed: ${res.response.statusText}`)
}
/**
* Deletes a tag. Requires to be logged in.
*
* @param tagid The user id of the user to delete.
*
* @return TRUE on success
*/
async tagDelete(tagid: string): Promise<boolean> {
if (!tagid || tagid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
const res = await this.httpRequest(`/tag/${tagid}`, 'DELETE')
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Tag deletion failed: ${res.response.statusText}`)
}
/**
* Get all contact fields of the logged in user. Requires to be logged in.
*
* @return A associative object <field id> => <field name>
*/
async fieldIndex(): Promise<any> {
const res = await this.httpRequest('/field', 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Field index failed: ${res.response.statusText}`)
}
/**
* Subscribe an email. Requires to be logged in.
*
* @param email The email address of the subscriber.
* @param listid (optional) The id subscription process.
* @param tagid (optional) The id of the manual tag the subscriber will be tagged with.
* @param fields (optional) Additional fields of the subscriber.
*
* @return An object representing the Klicktipp subscriber object.
*/
async subscribe(
email: string,
listid?: number,
tagid?: number,
fields?: any,
smsnumber?: string,
): Promise<any> {
if ((!email || email === '') && smsnumber === '') {
throw new Error('Illegal Arguments')
}
// subscribe
const data = {
email,
fields: fields !== undefined ? fields : {},
smsnumber: smsnumber !== undefined ? smsnumber : '',
listid: listid !== undefined ? listid : 0,
tagid: tagid !== undefined ? tagid : 0,
}
const res = await this.httpRequest('/subscriber', 'POST', data, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscription failed: ${res.response.statusText}`)
}
/**
* Unsubscribe an email. Requires to be logged in.
*
* @param email The email address of the subscriber.
*
* @return TRUE on success
*/
async unsubscribe(email: string): Promise<boolean> {
if (!email || email === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// unsubscribe
const data = { email }
const res = await this.httpRequest('/subscriber/unsubscribe', 'POST', data, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Unsubscription failed: ${res.response.statusText}`)
}
/**
* Tag an email. Requires to be logged in.
*
* @param email The email address of the subscriber.
* @param tagids an array of the manual tag(s) the subscriber will be tagged with.
*
* @return TRUE on success
*/
async tag(email: string, tagids: string): Promise<boolean> {
if (!email || email === '' || !tagids || tagids === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// tag
const data = {
email,
tagids,
}
const res = await this.httpRequest('/subscriber/tag', 'POST', data, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Tagging failed: ${res.response.statusText}`)
}
/**
* Untag an email. Requires to be logged in.
*
* @param mixed $email The email address of the subscriber.
* @param mixed $tagid The id of the manual tag that will be removed from the subscriber.
*
* @return TRUE on success.
*/
async untag(email: string, tagid: string): Promise<boolean> {
if (!email || email === '' || !tagid || tagid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// subscribe
const data = {
email,
tagid,
}
const res = await this.httpRequest('/subscriber/untag', 'POST', data, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Untagging failed: ${res.response.statusText}`)
}
/**
* Resend an autoresponder for an email address. Requires to be logged in.
*
* @param email A valid email address
* @param autoresponder An id of the autoresponder
*
* @return TRUE on success
*/
async resend(email: string, autoresponder: string): Promise<boolean> {
if (!email || email === '' || !autoresponder || autoresponder === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// resend/reset autoresponder
const data = { email, autoresponder }
const res = await this.httpRequest('/subscriber/resend', 'POST', data, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Resend failed: ${res.response.statusText}`)
}
/**
* Get all active subscribers. Requires to be logged in.
*
* @return An array of subscriber ids.
*/
async subscriberIndex(): Promise<[string]> {
const res = await this.httpRequest('/subscriber', 'GET', undefined, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscriber index failed: ${res.response.statusText}`)
}
/**
* Get subscriber information. Requires to be logged in.
*
* @param subscriberid The subscriber id.
*
* @return An object representing the Klicktipp subscriber.
*/
async subscriberGet(subscriberid: string): Promise<any> {
if (!subscriberid || subscriberid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// retrieve
const res = await this.httpRequest(`/subscriber/${subscriberid}`, 'GET', {}, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscriber get failed: ${res.response.statusText}`)
}
/**
* Get a subscriber id by email. Requires to be logged in.
*
* @param email The email address of the subscriber.
*
* @return The id of the subscriber. Use subscriber_get to get subscriber details.
*/
async subscriberSearch(email: string): Promise<any> {
if (!email || email === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// search
const data = { email }
const res = await this.httpRequest('/subscriber/search', 'POST', data, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp Subscriber search failed: ${res.response.statusText}`)
}
/**
* Get all active subscribers tagged with the given tag id. Requires to be logged in.
*
* @param tagid The id of the tag.
*
* @return An array with id -> subscription date of the tagged subscribers. Use subscriber_get to get subscriber details.
*/
async subscriberTagged(tagid: string): Promise<any> {
if (!tagid || tagid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// search
const data = { tagid }
const res = await this.httpRequest('/subscriber/tagged', 'POST', data, true)
if (!res.isAxiosError) {
return res.data
}
throw new Error(`Klicktipp subscriber tagged failed: ${res.response.statusText}`)
}
/**
* Updates a subscriber. Requires to be logged in.
*
* @param subscriberid The id of the subscriber to update.
* @param fields (optional) The fields of the subscriber to update
* @param newemail (optional) The new email of the subscriber to update
*
* @return TRUE on success
*/
async subscriberUpdate(
subscriberid: string,
fields?: any,
newemail?: string,
newsmsnumber?: string,
): Promise<boolean> {
if (!subscriberid || subscriberid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// update
const data = {
fields: fields !== undefined ? fields : {},
newemail: newemail !== undefined ? newemail : '',
newsmsnumber: newsmsnumber !== undefined ? newsmsnumber : '',
}
const res = await this.httpRequest(`/subscriber/${subscriberid}`, 'PUT', data, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Subscriber update failed: ${res.response.statusText}`)
}
/**
* Delete a subscribe. Requires to be logged in.
*
* @param subscriberid The id of the subscriber to update.
*
* @return TRUE on success.
*/
async subscriberDelete(subscriberid: string): Promise<boolean> {
if (!subscriberid || subscriberid === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// delete
const res = await this.httpRequest(`/subscriber/${subscriberid}`, 'DELETE', {}, true)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Subscriber deletion failed: ${res.response.statusText}`)
}
/**
* Subscribe an email. Requires an api key.
*
* @param apikey The api key (listbuildng configuration).
* @param email The email address of the subscriber.
* @param fields (optional) Additional fields of the subscriber.
*
* @return A redirection url as defined in the subscription process.
*/
async signin(apikey: string, email: string, fields?: any, smsnumber?: string): Promise<boolean> {
if (!apikey || apikey === '' || ((!email || email === '') && smsnumber === '')) {
throw new Error('Klicktipp Illegal Arguments')
}
// subscribe
const data = {
apikey,
email,
fields: fields !== undefined ? fields : {},
smsnumber: smsnumber !== undefined ? smsnumber : '',
}
const res = await this.httpRequest('/subscriber/signin', 'POST', data)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Subscription failed: ${res.response.statusText}`)
}
/**
* Untag an email. Requires an api key.
*
* @param apikey The api key (listbuildng configuration).
* @param email The email address of the subscriber.
*
* @return TRUE on success
*/
async signout(apikey: string, email: string): Promise<boolean> {
if (!apikey || apikey === '' || !email || email === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// untag
const data = { apikey, email }
const res = await this.httpRequest('/subscriber/signout', 'POST', data)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Untagging failed: ${res.response.statusText}`)
}
/**
* Unsubscribe an email. Requires an api key.
*
* @param apikey The api key (listbuildng configuration).
* @param email The email address of the subscriber.
*
* @return TRUE on success
*/
async signoff(apikey: string, email: string): Promise<boolean> {
if (!apikey || apikey === '' || !email || email === '') {
throw new Error('Klicktipp Illegal Arguments')
}
// unsubscribe
const data = { apikey, email }
const res = await this.httpRequest('/subscriber/signoff', 'POST', data)
if (!res.isAxiosError) {
return true
}
throw new Error(`Klicktipp Unsubscription failed: ${res.response.statusText}`)
}
async httpRequest(path: string, method?: Method, data?: any, usesession?: boolean): Promise<any> {
if (method === undefined) {
method = 'GET'
}
const options: AxiosRequestConfig = {
baseURL: this.baseURL,
method,
url: path,
data,
headers: {
'Content-Type': 'application/json',
Content: 'application/json',
Cookie:
usesession && this.sessionName !== '' ? `${this.sessionName}=${this.sessionId}` : '',
},
}
return axios(options)
.then((res) => res)
.catch((error) => error)
}
}

View File

@ -2,7 +2,7 @@
import { AuthChecker } from 'type-graphql'
import decode from '../jwt/decode'
import { apiGet } from '../apis/loginAPI'
import { apiGet } from '../apis/HttpRequest'
import CONFIG from '../config'
import encode from '../jwt/encode'

View File

@ -21,9 +21,18 @@ const database = {
DB_DATABASE: process.env.DB_DATABASE || 'gradido_community',
}
const klicktipp = {
KLICKTIPP: process.env.KLICKTIPP === 'true' || false,
KLICKTTIPP_API_URL: process.env.KLICKTIPP_API_URL || 'https://api.klicktipp.com',
KLICKTIPP_USER: process.env.KLICKTIPP_USER || 'gradido_test',
KLICKTIPP_PASSWORD: process.env.KLICKTIPP_PASSWORD || 'secret321',
KLICKTIPP_APIKEY_DE: process.env.KLICKTIPP_APIKEY_DE || 'SomeFakeKeyDE',
KLICKTIPP_APIKEY_EN: process.env.KLICKTIPP_APIKEY_EN || 'SomeFakeKeyEN',
}
// This is needed by graphql-directive-auth
process.env.APP_SECRET = server.JWT_SECRET
const CONFIG = { ...server, ...database }
const CONFIG = { ...server, ...database, ...klicktipp }
export default CONFIG

View File

@ -0,0 +1,10 @@
import { ArgsType, Field } from 'type-graphql'
@ArgsType()
export class SubscribeNewsletterArguments {
@Field(() => String)
email: string
@Field(() => String)
language: string
}

View File

@ -22,6 +22,9 @@ export class CreateUserArgs {
@Field(() => String)
password: string
@Field(() => String)
language: string
}
@ArgsType()

View File

@ -0,0 +1,29 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
@ObjectType()
export class CheckEmailResponse {
constructor(json: any) {
this.sessionId = json.session_id
this.email = json.user.email
this.language = json.user.language
this.firstName = json.user.first_name
this.lastName = json.user.last_name
}
@Field(() => Number)
sessionId: number
@Field(() => String)
email: string
@Field(() => String)
firstName: string
@Field(() => String)
lastName: string
@Field(() => String)
language: string
}

View File

@ -0,0 +1,13 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
@ObjectType()
export class KlickTipp {
constructor(json: any) {
this.newsletterState = json.status === 'Subscribed'
}
@Field(() => Boolean)
newsletterState: boolean
}

View File

@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { ObjectType, Field } from 'type-graphql'
import { KlickTipp } from './KlickTipp'
@ObjectType()
export class User {
@ -64,4 +65,7 @@ export class User {
@Field(() => ID)
publisherId: number
*/
@Field(() => KlickTipp)
klickTipp: KlickTipp
}

View File

@ -4,7 +4,7 @@
import { Resolver, Query, Ctx, Authorized } from 'type-graphql'
import CONFIG from '../../config'
import { Balance } from '../models/Balance'
import { apiGet } from '../../apis/loginAPI'
import { apiGet } from '../../apis/HttpRequest'
@Resolver()
export class BalanceResolver {

View File

@ -5,7 +5,7 @@ import { Resolver, Query, Args, Ctx, Authorized } from 'type-graphql'
import CONFIG from '../../config'
import { GdtEntryList } from '../models/GdtEntryList'
import { GdtTransactionSessionIdInput } from '../inputs/GdtInputs'
import { apiGet } from '../../apis/loginAPI'
import { apiGet } from '../../apis/HttpRequest'
@Resolver()
export class GdtResolver {

View File

@ -0,0 +1,40 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Query, Authorized, Arg, Mutation, Args } from 'type-graphql'
import {
getKlickTippUser,
getKlicktippTagMap,
unsubscribe,
signIn,
} from '../../apis/KlicktippController'
import { SubscribeNewsletterArguments } from '../inputs/KlickTippInputs'
@Resolver()
export class KlicktippResolver {
@Authorized()
@Query(() => String)
async getKlicktippUser(@Arg('email') email: string): Promise<string> {
return await getKlickTippUser(email)
}
@Authorized()
@Query(() => String)
async getKlicktippTagMap(): Promise<string> {
return await getKlicktippTagMap()
}
@Authorized()
@Mutation(() => Boolean)
async unsubscribeNewsletter(@Arg('email') email: string): Promise<boolean> {
return await unsubscribe(email)
}
@Authorized()
@Mutation(() => Boolean)
async subscribeNewsletter(
@Args() { email, language }: SubscribeNewsletterArguments,
): Promise<boolean> {
return await signIn(email, language)
}
}

View File

@ -5,7 +5,7 @@ import { Resolver, Query, Args, Authorized, Ctx } from 'type-graphql'
import CONFIG from '../../config'
import { TransactionList } from '../models/Transaction'
import { TransactionListInput, TransactionSendArgs } from '../inputs/TransactionInput'
import { apiGet, apiPost } from '../../apis/loginAPI'
import { apiGet, apiPost } from '../../apis/HttpRequest'
@Resolver()
export class TransactionResolver {

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Resolver, Query, Args, Arg, Authorized, Ctx } from 'type-graphql'
import { Resolver, Query, Args, Arg, Authorized, Ctx, UseMiddleware } from 'type-graphql'
import CONFIG from '../../config'
import { CheckUsernameResponse } from '../models/CheckUsernameResponse'
import { LoginViaVerificationCode } from '../models/LoginViaVerificationCode'
@ -16,11 +16,16 @@ import {
UnsecureLoginArgs,
UpdateUserInfosArgs,
} from '../inputs/LoginUserInput'
import { apiPost, apiGet } from '../../apis/loginAPI'
import { apiPost, apiGet } from '../../apis/HttpRequest'
import {
klicktippRegistrationMiddleware,
klicktippNewsletterStateMiddleware,
} from '../../middleware/klicktippMiddleware'
import { CheckEmailResponse } from '../models/CheckEmailResponse'
@Resolver()
export class UserResolver {
@Query(() => User)
@UseMiddleware(klicktippNewsletterStateMiddleware)
async login(@Args() { email, password }: UnsecureLoginArgs, @Ctx() context: any): Promise<User> {
email = email.trim().toLowerCase()
const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password })
@ -62,7 +67,9 @@ export class UserResolver {
}
@Query(() => String)
async create(@Args() { email, firstName, lastName, password }: CreateUserArgs): Promise<string> {
async create(
@Args() { email, firstName, lastName, password, language }: CreateUserArgs,
): Promise<string> {
const payload = {
email,
first_name: firstName,
@ -70,11 +77,13 @@ export class UserResolver {
password,
emailType: 2,
login_after_register: true,
language: language,
}
const result = await apiPost(CONFIG.LOGIN_API_URL + 'createUser', payload)
if (!result.success) {
throw new Error(result.data)
}
return 'success'
}
@ -88,7 +97,9 @@ export class UserResolver {
email_verification_code_type: 'resetPassword',
}
const response = await apiPost(CONFIG.LOGIN_API_URL + 'sendEmail', payload)
if (!response.success) throw new Error(response.data)
if (!response.success) {
throw new Error(response.data)
}
return new SendPasswordResetEmailResponse(response.data)
}
@ -103,8 +114,10 @@ export class UserResolver {
password,
}
const result = await apiPost(CONFIG.LOGIN_API_URL + 'resetPassword', payload)
if (!result.success) throw new Error(result.data)
return 'sucess'
if (!result.success) {
throw new Error(result.data)
}
return 'success'
}
@Authorized()
@ -151,4 +164,16 @@ export class UserResolver {
if (!response.success) throw new Error(response.data)
return new CheckUsernameResponse(response.data)
}
@Query(() => CheckEmailResponse)
@UseMiddleware(klicktippRegistrationMiddleware)
async checkEmail(@Arg('optin') optin: string): Promise<CheckEmailResponse> {
const result = await apiGet(
CONFIG.LOGIN_API_URL + 'loginViaEmailVerificationCode?emailVerificationCode=' + optin,
)
if (!result.success) {
throw new Error(result.data)
}
return new CheckEmailResponse(result.data)
}
}

View File

@ -15,6 +15,7 @@ import { UserResolver } from './graphql/resolvers/UserResolver'
import { BalanceResolver } from './graphql/resolvers/BalanceResolver'
import { GdtResolver } from './graphql/resolvers/GdtResolver'
import { TransactionResolver } from './graphql/resolvers/TransactionResolver'
import { KlicktippResolver } from './graphql/resolvers/KlicktippResolver'
import { isAuthorized } from './auth/auth'
@ -50,7 +51,7 @@ async function main() {
// const connection = await createConnection()
const schema = await buildSchema({
resolvers: [UserResolver, BalanceResolver, TransactionResolver, GdtResolver],
resolvers: [UserResolver, BalanceResolver, TransactionResolver, GdtResolver, KlicktippResolver],
authChecker: isAuthorized,
})

View File

@ -0,0 +1,34 @@
import { MiddlewareFn } from 'type-graphql'
import { signIn, getKlickTippUser } from '../apis/KlicktippController'
import { KlickTipp } from '../graphql/models/KlickTipp'
import CONFIG from '../config/index'
export const klicktippRegistrationMiddleware: MiddlewareFn = async (
// Only for demo
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
{ root, args, context, info },
next,
) => {
// Do Something here before resolver is called
const result = await next()
// Do Something here after resolver is completed
await signIn(result.email, result.language, result.firstName, result.lastName)
return result
}
export const klicktippNewsletterStateMiddleware: MiddlewareFn = async (
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
{ root, args, context, info },
next,
) => {
const result = await next()
let klickTipp = new KlickTipp({ status: 'Unsubscribed' })
if (CONFIG.KLICKTIPP) {
const klickTippUser = await getKlickTippUser(result.email)
if (klickTippUser) {
klickTipp = new KlickTipp(klickTippUser)
}
}
result.klickTipp = klickTipp
return result
}

View File

@ -4,7 +4,7 @@
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */

View File

@ -113,12 +113,6 @@ describe('SideBar', () => {
expect(wrapper.emitted('logout')).toEqual([[]])
})
})
describe('language-switch', () => {
it('has a language-switch button', () => {
expect(wrapper.find('div.language-switch').exists()).toBeTruthy()
})
})
})
})
})

View File

@ -64,23 +64,18 @@
</a>
</li>
</ul>
<div class="mt-5 ml-4">
<language-switch />
</div>
</div>
</div>
</nav>
</template>
<script>
import NavbarToggleButton from '@/components/NavbarToggleButton'
import LanguageSwitch from '@/components/LanguageSwitch.vue'
import VueQrcode from 'vue-qrcode'
export default {
name: 'sidebar',
components: {
NavbarToggleButton,
LanguageSwitch,
VueQrcode,
},
props: {

View File

@ -0,0 +1,13 @@
import gql from 'graphql-tag'
export const subscribeNewsletter = gql`
mutation($email: String!, $language: String!) {
subscribeNewsletter(email: $email, language: $language)
}
`
export const unsubscribeNewsletter = gql`
mutation($email: String!) {
unsubscribeNewsletter(email: $email)
}
`

View File

@ -9,6 +9,9 @@ export const login = gql`
lastName
language
description
klickTipp {
newsletterState
}
}
}
`
@ -92,8 +95,20 @@ export const transactionsQuery = gql`
`
export const resgisterUserQuery = gql`
query($firstName: String!, $lastName: String!, $email: String!, $password: String!) {
create(email: $email, firstName: $firstName, lastName: $lastName, password: $password)
query(
$firstName: String!
$lastName: String!
$email: String!
$password: String!
$language: String!
) {
create(
email: $email
firstName: $firstName
lastName: $lastName
password: $password
language: $language
)
}
`
@ -136,3 +151,12 @@ export const listGDTEntriesQuery = gql`
}
}
`
export const checkEmailQuery = gql`
query($optin: String!) {
checkEmail(optin: $optin) {
email
sessionId
}
}
`

View File

@ -120,6 +120,12 @@
},
"select_language": "Bitte wähle eine Sprache für die App und Newsletter",
"send": "Senden",
"setting": {
"changeNewsletter": "Newsletter Status ändern",
"newsletter": "Newsletter",
"newsletterFalse": "Du bist aus Newslettersystem ausgetragen.",
"newsletterTrue": "Du bist im Newslettersystem eingetraten."
},
"signup": "Registrieren",
"site": {
"404": {
@ -127,6 +133,10 @@
"ooops": "Ooops!",
"text": "Seite nicht gefunden. Aber keine Sorge, wir haben noch viele andere Seiten zum Erkunden"
},
"checkEmail": {
"errorText": "Email konnte nicht verifiziert werden.",
"title": "Email wird verifiziert"
},
"login": {
"community": "Tausend Dank, weil du bei uns bist!",
"forgot_pwd": "Passwort vergessen?",
@ -162,6 +172,7 @@
"uppercase": "Ein Großbuchstabe erforderlich."
},
"thx": {
"checkEmail": "Deine Email würde erfolgreich verifiziert.",
"email": "Wir haben dir eine eMail gesendet.",
"register": "Du bist jetzt regisriert.",
"reset": "Dein Passwort wurde geändert.",

View File

@ -120,6 +120,12 @@
},
"select_language": "Please choose a language for the app and newsletter",
"send": "Send",
"setting": {
"changeNewsletter": "Newsletter status change",
"newsletter": "Newsletter",
"newsletterFalse": "You are unsubscribed from newsletter system.",
"newsletterTrue": "You are subscribed to newsletter system."
},
"signup": "Sign up",
"site": {
"404": {
@ -127,6 +133,10 @@
"ooops": "Ooops!",
"text": "Page not found. Do not worry though, we have plenty of other pages to explore"
},
"checkEmail": {
"errorText": "Could not verify the email.",
"title": "Verifing email"
},
"login": {
"community": "A thousand thanks for being with us!",
"forgot_pwd": "Forgot password?",
@ -162,6 +172,7 @@
"uppercase": "One uppercase letter required."
},
"thx": {
"checkEmail": "Your email has been successfully verified.",
"email": "We have sent you an email.",
"register": "You are registred now.",
"reset": "Your password has been changed.",

View File

@ -52,6 +52,10 @@ const routes = [
path: '/reset/:optin',
component: () => import('../views/Pages/ResetPassword.vue'),
},
{
path: '/checkEmail/:optin',
component: () => import('../views/Pages/CheckEmail.vue'),
},
{ path: '*', component: NotFound },
]

View File

@ -26,6 +26,9 @@ export const mutations = {
token: (state, token) => {
state.token = token
},
newsletterState: (state, newsletterState) => {
state.newsletterState = newsletterState
},
}
export const actions = {
@ -36,6 +39,7 @@ export const actions = {
commit('firstName', data.firstName)
commit('lastName', data.lastName)
commit('description', data.description)
commit('newsletterState', data.klickTipp.newsletterState)
},
logout: ({ commit, state }) => {
commit('token', null)
@ -44,6 +48,7 @@ export const actions = {
commit('firstName', '')
commit('lastName', '')
commit('description', '')
commit('newsletterState', null)
localStorage.clear()
},
}
@ -62,6 +67,7 @@ export const store = new Vuex.Store({
username: '',
description: '',
token: null,
newsletterState: null,
},
getters: {},
// Syncronous mutation of the state

View File

@ -1,6 +1,15 @@
import { mutations, actions } from './store'
const { language, email, token, username, firstName, lastName, description } = mutations
const {
language,
email,
token,
username,
firstName,
lastName,
description,
newsletterState,
} = mutations
const { login, logout } = actions
describe('Vuex store', () => {
@ -60,6 +69,14 @@ describe('Vuex store', () => {
expect(state.description).toEqual('Nickelbrille')
})
})
describe('newsletterState', () => {
it('sets the state of newsletterState', () => {
const state = { newsletterState: null }
newsletterState(state, true)
expect(state.newsletterState).toEqual(true)
})
})
})
describe('actions', () => {
@ -73,11 +90,14 @@ describe('Vuex store', () => {
firstName: 'Peter',
lastName: 'Lustig',
description: 'Nickelbrille',
klickTipp: {
newsletterState: true,
},
}
it('calls seven commits', () => {
login({ commit, state }, commitedData)
expect(commit).toHaveBeenCalledTimes(6)
expect(commit).toHaveBeenCalledTimes(7)
})
it('commits email', () => {
@ -109,6 +129,11 @@ describe('Vuex store', () => {
login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(6, 'description', 'Nickelbrille')
})
it('commits newsletterState', () => {
login({ commit, state }, commitedData)
expect(commit).toHaveBeenNthCalledWith(7, 'newsletterState', true)
})
})
describe('logout', () => {
@ -117,7 +142,7 @@ describe('Vuex store', () => {
it('calls six commits', () => {
logout({ commit, state })
expect(commit).toHaveBeenCalledTimes(6)
expect(commit).toHaveBeenCalledTimes(7)
})
it('commits token', () => {
@ -150,6 +175,11 @@ describe('Vuex store', () => {
expect(commit).toHaveBeenNthCalledWith(6, 'description', '')
})
it('commits newsletterState', () => {
logout({ commit, state })
expect(commit).toHaveBeenNthCalledWith(7, 'newsletterState', null)
})
// how to get this working?
it.skip('calls localStorage.clear()', () => {
const clearStorageMock = jest.fn()

View File

@ -117,10 +117,6 @@ describe('DashboardLayoutGdd', () => {
)
})
it('has a locale switch', () => {
expect(wrapper.find('div.language-switch').exists()).toBeTruthy()
})
it('has a logout button', () => {
expect(wrapper.findAll('ul').at(3).text()).toBe('logout')
})

View File

@ -0,0 +1,105 @@
import { mount, RouterLinkStub } from '@vue/test-utils'
import CheckEmail from './CheckEmail'
const localVue = global.localVue
const apolloQueryMock = jest.fn().mockRejectedValue({ message: 'error' })
const toasterMock = jest.fn()
const routerPushMock = jest.fn()
describe('CheckEmail', () => {
let wrapper
const mocks = {
$i18n: {
locale: 'en',
},
$t: jest.fn((t) => t),
$route: {
params: {
optin: '123',
},
},
$toasted: {
error: toasterMock,
},
$router: {
push: routerPushMock,
},
$loading: {
show: jest.fn(() => {
return { hide: jest.fn() }
}),
},
$apollo: {
query: apolloQueryMock,
},
}
const stubs = {
RouterLink: RouterLinkStub,
}
const Wrapper = () => {
return mount(CheckEmail, { localVue, mocks, stubs })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('calls the checkEmail when created', async () => {
expect(apolloQueryMock).toBeCalledWith(
expect.objectContaining({ variables: { optin: '123' } }),
)
})
describe('No valid optin', () => {
it('toasts an error when no valid optin is given', () => {
expect(toasterMock).toHaveBeenCalledWith('error')
})
it('has a message suggesting to contact the support', () => {
expect(wrapper.find('div.header').text()).toContain('checkEmail.title')
expect(wrapper.find('div.header').text()).toContain('checkEmail.errorText')
})
})
describe('is authenticated', () => {
beforeEach(() => {
apolloQueryMock.mockResolvedValue({
data: {
checkEmail: {
sessionId: 1,
email: 'user@example.org',
language: 'de',
},
},
})
})
it.skip('Has sessionId from API call', async () => {
await wrapper.vm.$nextTick()
expect(wrapper.vm.sessionId).toBe(1)
})
describe('Register header', () => {
it('has a welcome message', async () => {
expect(wrapper.find('div.header').text()).toContain('checkEmail.title')
})
})
describe('links', () => {
it('has a link "Back"', async () => {
expect(wrapper.findAllComponents(RouterLinkStub).at(0).text()).toEqual('back')
})
it('links to /login when clicking "Back"', async () => {
expect(wrapper.findAllComponents(RouterLinkStub).at(0).props().to).toBe('/Login')
})
})
})
})
})

View File

@ -0,0 +1,72 @@
<template>
<div class="checkemail-form">
<b-container>
<div class="header p-4" ref="header">
<div class="header-body text-center mb-7">
<b-row class="justify-content-center">
<b-col xl="5" lg="6" md="8" class="px-2">
<h1>{{ $t('checkEmail.title') }}</h1>
<div class="pb-4" v-if="!pending">
<span v-if="!authenticated">
{{ $t('checkEmail.errorText') }}
</span>
</div>
</b-col>
</b-row>
</div>
</div>
</b-container>
<b-container class="mt--8 p-1">
<b-row>
<b-col class="text-center py-lg-4">
<router-link to="/Login" class="mt-3">{{ $t('back') }}</router-link>
</b-col>
</b-row>
</b-container>
</div>
</template>
<script>
import { checkEmailQuery } from '../../graphql/queries'
export default {
name: 'CheckEmail',
data() {
return {
authenticated: false,
sessionId: null,
email: null,
pending: true,
}
},
methods: {
async authenticate() {
const loader = this.$loading.show({
container: this.$refs.header,
})
const optin = this.$route.params.optin
this.$apollo
.query({
query: checkEmailQuery,
variables: {
optin: optin,
},
})
.then((result) => {
this.authenticated = true
this.sessionId = result.data.checkEmail.sessionId
this.email = result.data.checkEmail.email
this.$router.push('/thx/checkEmail')
})
.catch((error) => {
this.$toasted.error(error.message)
})
loader.hide()
this.pending = false
},
},
mounted() {
this.authenticate()
},
}
</script>
<style></style>

View File

@ -1,7 +1,11 @@
<template>
<b-card class="bg-transparent">
<div class="w-100 text-center">
<vue-qrcode :value="$store.state.email" type="image/png"></vue-qrcode>
<vue-qrcode
v-if="$store.state.email"
:value="$store.state.email"
type="image/png"
></vue-qrcode>
</div>
<div class="card-profile-stats d-flex justify-content-center mt-md-5">

View File

@ -12,38 +12,42 @@
</b-row>
</div>
<b-container>
<div>
<b-form @keyup.prevent="loadSubmitButton">
<b-row class="mb-3">
<b-col class="col-12 col-lg-3 col-md-12 col-sm-12 text-md-left text-lg-right">
<small>{{ $t('form.firstname') }}</small>
<b-col class="col-12">
<small>
<b>{{ $t('form.firstname') }}</b>
</small>
</b-col>
<b-col v-if="showUserData" class="h2 col-sm-10 col-md-9">
<b-col v-if="showUserData" class="col-12">
{{ form.firstName }}
</b-col>
<b-col v-else class="col-md-9 col-sm-10">
<b-col v-else class="col-12">
<b-input type="text" v-model="form.firstName"></b-input>
</b-col>
</b-row>
<b-row class="mb-3">
<b-col class="col-12 col-lg-3 col-md-12 col-sm-12 text-md-left text-lg-right">
<small>{{ $t('form.lastname') }}</small>
<b-col class="col-12">
<small>
<b>{{ $t('form.lastname') }}</b>
</small>
</b-col>
<b-col v-if="showUserData" class="h2 col-sm-10 col-md-9">
<b-col v-if="showUserData" class="col-12">
{{ form.lastName }}
</b-col>
<b-col v-else class="col-md-9 col-sm-10">
<b-col v-else class="col-12">
<b-input type="text" v-model="form.lastName"></b-input>
</b-col>
</b-row>
<b-row class="mb-3" v-show="false">
<b-col class="col-12 col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<b-col class="col-12">
<small>{{ $t('form.description') }}</small>
</b-col>
<b-col v-if="showUserData" class="col-sm-10 col-md-9">
<b-col v-if="showUserData" class="col-12">
{{ form.description }}
</b-col>
<b-col v-else class="col-sm-10 col-md-9">
<b-col v-else class="col-12">
<b-textarea rows="3" max-rows="6" v-model="form.description"></b-textarea>
</b-col>
</b-row>
@ -64,7 +68,7 @@
</b-col>
</b-row>
</b-form>
</b-container>
</div>
</b-card>
</template>
<script>

View File

@ -18,10 +18,12 @@
<div v-if="showLanguage">
<b-row class="mb-3">
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('language') }}</small>
<b-col class="col-12">
<small>
<b>{{ $t('language') }}</b>
</small>
</b-col>
<b-col class="h2 col-md-9 col-sm-10">{{ $store.state.language }}</b-col>
<b-col class="col-12">{{ $store.state.language }}</b-col>
</b-row>
</div>
@ -29,18 +31,26 @@
<div>
<b-form @submit.stop.prevent="handleSubmit(onSubmit)">
<b-row class="mb-2">
<b-col class="col-lg-3 col-md-10 col-sm-10 text-md-left text-lg-right">
<small>{{ $t('language') }}</small>
<b-col class="col-12">
<small>
<b>{{ $t('language') }}</b>
</small>
</b-col>
<b-col class="col-md-9 col-sm-10">
<b-col class="col-12">
<language-switch-select @update-language="updateLanguage" :language="language" />
</b-col>
</b-row>
<b-row class="text-right">
<b-col>
<div class="text-right">
<b-button type="submit" variant="primary" class="mt-4">
<div class="text-right" ref="submitButton">
<b-button
:variant="loading ? 'default' : 'success'"
@click="onSubmit"
type="submit"
class="mt-4"
:disabled="loading"
>
{{ $t('form.save') }}
</b-button>
</div>
@ -62,15 +72,22 @@ export default {
return {
showLanguage: true,
language: '',
loading: true,
}
},
methods: {
updateLanguage(e) {
this.language = e
if (this.language !== this.$store.state.language) {
this.loading = false
} else {
this.loading = true
}
},
cancelEdit() {
this.showLanguage = true
},
async onSubmit() {
this.$apollo
.query({

View File

@ -0,0 +1,98 @@
import { mount } from '@vue/test-utils'
import UserCardNewsletter from './UserCard_Newsletter'
import { unsubscribeNewsletter } from '../../../graphql/mutations'
const localVue = global.localVue
const mockAPIcall = jest.fn()
const toastErrorMock = jest.fn()
const toastSuccessMock = jest.fn()
const storeCommitMock = jest.fn()
const newsletterStateMock = jest.fn().mockReturnValue(true)
describe('UserCard_Newsletter', () => {
let wrapper
const mocks = {
$t: jest.fn((t) => t),
$store: {
state: {
language: 'de',
email: 'peter@lustig.de',
newsletterState: newsletterStateMock,
},
commit: storeCommitMock,
},
$toasted: {
success: toastSuccessMock,
error: toastErrorMock,
},
$apollo: {
mutate: mockAPIcall,
},
}
const Wrapper = () => {
return mount(UserCardNewsletter, { localVue, mocks })
}
describe('mount', () => {
beforeEach(() => {
wrapper = Wrapper()
})
it('renders the component', () => {
expect(wrapper.find('div#formusernewsletter').exists()).toBeTruthy()
})
it('has an edit BFormCheckbox switch', () => {
expect(wrapper.find('.Test-BFormCheckbox').exists()).toBeTruthy()
})
describe('unsubscribe with sucess', () => {
beforeEach(() => {
mockAPIcall.mockResolvedValue({
data: {
unsubscribeNewsletter: true,
},
})
wrapper.find('input').trigger('change')
})
it('calls the unsubscribe mutation', () => {
expect(mockAPIcall).toBeCalledWith({
mutation: unsubscribeNewsletter,
variables: {
email: 'peter@lustig.de',
},
})
})
it('updates the store', () => {
expect(storeCommitMock).toBeCalledWith('newsletterState', false)
})
it('toasts a success message', () => {
expect(toastSuccessMock).toBeCalledWith('setting.newsletterFalse')
})
})
describe('unsubscribe with server error', () => {
beforeEach(() => {
mockAPIcall.mockRejectedValue({
message: 'Ouch',
})
wrapper.find('input').trigger('change')
})
it('resets the newsletterState', () => {
expect(wrapper.vm.newsletterState).toBeTruthy()
})
it('toasts an error message', () => {
expect(toastErrorMock).toBeCalledWith('Ouch')
})
})
})
})

View File

@ -0,0 +1,64 @@
<template>
<b-card
id="formusernewsletter"
class="bg-transparent"
style="background-color: #ebebeba3 !important"
>
<div>
<b-row class="mb-3">
<b-col class="mb-2 col-12">
<small>
<b>{{ $t('setting.newsletter') }}</b>
</small>
</b-col>
<b-col class="col-12">
<b-form-checkbox
class="Test-BFormCheckbox"
v-model="newsletterState"
name="check-button"
switch
@change="onSubmit"
>
{{ newsletterState ? $t('setting.newsletterTrue') : $t('setting.newsletterFalse') }}
</b-form-checkbox>
</b-col>
</b-row>
</div>
</b-card>
</template>
<script>
import { subscribeNewsletter, unsubscribeNewsletter } from '../../../graphql/mutations'
export default {
name: 'FormUserNewsletter',
data() {
return {
newsletterState: this.$store.state.newsletterState,
}
},
methods: {
async onSubmit() {
this.$apollo
.mutate({
mutation: this.newsletterState ? subscribeNewsletter : unsubscribeNewsletter,
variables: {
email: this.$store.state.email,
language: this.newsletterState ? this.$store.state.language : undefined,
},
})
.then(() => {
this.$store.commit('newsletterState', this.newsletterState)
this.$toasted.success(
this.newsletterState
? this.$t('setting.newsletterTrue')
: this.$t('setting.newsletterFalse'),
)
})
.catch((error) => {
this.newsletterState = this.$store.state.newsletterState
this.$toasted.error(error.message)
})
},
},
}
</script>

View File

@ -6,6 +6,8 @@
<form-user-passwort />
<hr />
<form-user-language />
<hr />
<form-user-newsletter />
</div>
</template>
<script>
@ -13,6 +15,7 @@ import UserCard from './UserProfile/UserCard.vue'
import FormUserData from './UserProfile/UserCard_FormUserData.vue'
import FormUserPasswort from './UserProfile/UserCard_FormUserPasswort.vue'
import FormUserLanguage from './UserProfile/UserCard_Language.vue'
import FormUserNewsletter from './UserProfile/UserCard_Newsletter.vue'
export default {
components: {
@ -20,6 +23,7 @@ export default {
FormUserData,
FormUserPasswort,
FormUserLanguage,
FormUserNewsletter,
},
props: {
balance: { type: Number, default: 0 },

View File

@ -31,6 +31,11 @@ const textFields = {
button: 'site.login.signin',
linkTo: '/overview',
},
checkEmail: {
subtitle: 'site.thx.checkEmail',
button: 'login',
linkTo: '/login',
},
}
export default {

View File

@ -6,6 +6,7 @@
#include "../SingletonManager/EmailManager.h"
#include "../SingletonManager/SessionManager.h"
#include "../SingletonManager/LanguageManager.h"
#include "../tasks/AuthenticatedEncryptionCreateKeyTask.h"
@ -17,6 +18,8 @@ Poco::JSON::Object* JsonCreateUser::handle(Poco::Dynamic::Var params)
std::string password;
std::string username;
std::string description;
std::string language;
bool login_after_register = false;
int emailType;
int group_id = 1;
@ -42,6 +45,7 @@ Poco::JSON::Object* JsonCreateUser::handle(Poco::Dynamic::Var params)
auto group_id_obj = paramJsonObject->get("group_id");
auto username_obj = paramJsonObject->get("username");
auto description_obj = paramJsonObject->get("description");
auto language_obj = paramJsonObject->get("language");
if(!group_id_obj.isEmpty()) {
group_id_obj.convert(group_id);
@ -52,6 +56,9 @@ Poco::JSON::Object* JsonCreateUser::handle(Poco::Dynamic::Var params)
if (!description_obj.isEmpty()) {
description_obj.convert(description);
}
if (!language_obj.isEmpty()) {
language_obj.convert(language);
}
if ((ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_PASSWORD_REQUESTS)) {
paramJsonObject->get("password").convert(password);
}
@ -96,15 +103,20 @@ Poco::JSON::Object* JsonCreateUser::handle(Poco::Dynamic::Var params)
group_was_not_set = true;
}
user = controller::User::create(email, first_name, last_name, group_id);
auto user_model = user->getModel();
if (username.size() > 3) {
if (user->isUsernameAlreadyUsed(username)) {
return stateError("username already in use");
}
user->getModel()->setUsername(username);
user_model->setUsername(username);
}
if (description.size() > 3) {
user->getModel()->setDescription(description);
user_model->setDescription(description);
}
if (LanguageManager::languageFromString(language) != LANG_NULL) {
user_model->setLanguageKey(language);
}
auto userModel = user->getModel();
Session* session = nullptr;