commit
ce299e0f0e
|
|
@ -4,6 +4,7 @@ const prodEnv = require('./prod.env')
|
||||||
|
|
||||||
module.exports = merge(prodEnv, {
|
module.exports = merge(prodEnv, {
|
||||||
NODE_ENV: '"development"',
|
NODE_ENV: '"development"',
|
||||||
|
HEP_URL: JSON.stringify(process.env.HEP_URL),
|
||||||
MATOMO_HOST: JSON.stringify(process.env.MATOMO_HOST),
|
MATOMO_HOST: JSON.stringify(process.env.MATOMO_HOST),
|
||||||
MATOMO_SITE_ID: JSON.stringify(process.env.MATOMO_SITE_ID),
|
MATOMO_SITE_ID: JSON.stringify(process.env.MATOMO_SITE_ID),
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
module.exports = {
|
module.exports = {
|
||||||
NODE_ENV: '"production"',
|
NODE_ENV: '"production"',
|
||||||
|
HEP_URL: JSON.stringify(process.env.HEP_URL),
|
||||||
MATOMO_HOST: JSON.stringify(process.env.MATOMO_HOST),
|
MATOMO_HOST: JSON.stringify(process.env.MATOMO_HOST),
|
||||||
MATOMO_SITE_ID: JSON.stringify(process.env.MATOMO_SITE_ID),
|
MATOMO_SITE_ID: JSON.stringify(process.env.MATOMO_SITE_ID),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -2,8 +2,7 @@ describe('The Login Page', () => {
|
||||||
it('login and redirect to main page', () => {
|
it('login and redirect to main page', () => {
|
||||||
const username = 'test';
|
const username = 'test';
|
||||||
const password = 'test';
|
const password = 'test';
|
||||||
|
cy.visit('/beta-login');
|
||||||
cy.visit('/');
|
|
||||||
cy.login(username, password, true);
|
cy.login(username, password, true);
|
||||||
cy.get('body').contains('Neues Wissen erwerben');
|
cy.get('body').contains('Neues Wissen erwerben');
|
||||||
});
|
});
|
||||||
|
|
@ -12,7 +11,7 @@ describe('The Login Page', () => {
|
||||||
const username = '';
|
const username = '';
|
||||||
const password = 'test';
|
const password = 'test';
|
||||||
|
|
||||||
cy.visit('/');
|
cy.visit('/beta-login');
|
||||||
cy.login(username, password);
|
cy.login(username, password);
|
||||||
cy.get('[data-cy=email-local-errors]').contains('E-Mail ist ein Pflichtfeld');
|
cy.get('[data-cy=email-local-errors]').contains('E-Mail ist ein Pflichtfeld');
|
||||||
});
|
});
|
||||||
|
|
@ -21,7 +20,7 @@ describe('The Login Page', () => {
|
||||||
const username = 'test';
|
const username = 'test';
|
||||||
const password = '';
|
const password = '';
|
||||||
|
|
||||||
cy.visit('/');
|
cy.visit('/beta-login');
|
||||||
cy.login(username, password);
|
cy.login(username, password);
|
||||||
cy.get('[data-cy=password-local-errors]').contains('Passwort ist ein Pflichtfeld');
|
cy.get('[data-cy=password-local-errors]').contains('Passwort ist ein Pflichtfeld');
|
||||||
});
|
});
|
||||||
|
|
@ -30,23 +29,18 @@ describe('The Login Page', () => {
|
||||||
const username = 'test';
|
const username = 'test';
|
||||||
const password = '12345';
|
const password = '12345';
|
||||||
|
|
||||||
cy.visit('/');
|
cy.visit('/beta-login');
|
||||||
cy.login(username, password);
|
cy.login(username, password);
|
||||||
cy.get('[data-cy=login-error]').contains('Die E-Mail oder das Passwort ist falsch. Bitte versuchen Sie nochmals.');
|
cy.get('[data-cy=login-error]').contains('Die E-Mail oder das Passwort ist falsch. Bitte versuchen Sie nochmals.');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('redirect after login', () => {
|
it('logs out then logs in again', () => {
|
||||||
const username = 'test';
|
|
||||||
const password = 'test';
|
|
||||||
|
|
||||||
cy.visit('/book/topic/berufliche-grundbildung');
|
const user = 'rahel.cueni';
|
||||||
cy.login(username, password);
|
const pw = 'test'
|
||||||
cy.get('body').contains('Berufliche Grundbildung');
|
|
||||||
});
|
|
||||||
|
|
||||||
it.only('logs out then logs in again', () => {
|
|
||||||
cy.viewport('macbook-15');
|
cy.viewport('macbook-15');
|
||||||
cy.apolloLogin('rahel.cueni', 'test');
|
cy.apolloLogin(user, pw);
|
||||||
cy.visit('/me/my-class');
|
cy.visit('/me/my-class');
|
||||||
cy.get('[data-cy=header-user-widget]').should('exist').within(() => {
|
cy.get('[data-cy=header-user-widget]').should('exist').within(() => {
|
||||||
cy.get('[data-cy=user-widget-avatar]').should('exist').click();
|
cy.get('[data-cy=user-widget-avatar]').should('exist').click();
|
||||||
|
|
@ -54,7 +48,11 @@ describe('The Login Page', () => {
|
||||||
|
|
||||||
cy.get('[data-cy=logout]').click();
|
cy.get('[data-cy=logout]').click();
|
||||||
|
|
||||||
cy.login('rahel.cueni', 'test');
|
cy.get('[data-cy=email-input]').should('exist').within(() => {
|
||||||
|
cy.visit('/beta-login');
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.login(user, pw);
|
||||||
|
|
||||||
cy.get('[data-cy=header-user-widget]').should('exist').within(() => {
|
cy.get('[data-cy=header-user-widget]').should('exist').within(() => {
|
||||||
cy.get('[data-cy=user-widget-avatar]').should('exist').click();
|
cy.get('[data-cy=user-widget-avatar]').should('exist').click();
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { GraphQLError } from "graphql";
|
||||||
|
|
||||||
|
const schema = require('../fixtures/schema.json');
|
||||||
|
|
||||||
|
describe('Email Verifcation', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.server();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('forwards to homepage if confirmation key is correct', () => {
|
||||||
|
cy.viewport('macbook-15');
|
||||||
|
cy.mockGraphql({
|
||||||
|
schema: schema,
|
||||||
|
operations: {
|
||||||
|
Coupon: {
|
||||||
|
coupon: {
|
||||||
|
success: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cy.login('rahel.cueni', 'test', true)
|
||||||
|
cy.get('[data-cy="rooms-link"]').contains('Alle Räume anzeigen');
|
||||||
|
|
||||||
|
cy.visit('/license-activation');
|
||||||
|
cy.redeemCoupon('12345asfd');
|
||||||
|
cy.get('body').contains('Neues Wissen erwerben');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error if input is missing', () => {
|
||||||
|
cy.viewport('macbook-15');
|
||||||
|
cy.login('rahel.cueni', 'test', true)
|
||||||
|
cy.get('[data-cy="rooms-link"]').contains('Alle Räume anzeigen');
|
||||||
|
|
||||||
|
cy.visit('/license-activation');
|
||||||
|
cy.redeemCoupon('');
|
||||||
|
cy.get('[data-cy="coupon-local-errors"]').contains('Coupon ist ein Pflichtfeld.');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error if coupon input is wrong', () => {
|
||||||
|
cy.viewport('macbook-15');
|
||||||
|
cy.mockGraphql({
|
||||||
|
schema: schema,
|
||||||
|
operations: {
|
||||||
|
Coupon: new GraphQLError('invalid_coupon')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cy.login('rahel.cueni', 'test', true)
|
||||||
|
cy.get('[data-cy="rooms-link"]').contains('Alle Räume anzeigen');
|
||||||
|
|
||||||
|
cy.visit('/license-activation');
|
||||||
|
cy.redeemCoupon('12345asfd');
|
||||||
|
cy.get('[data-cy="coupon-remote-errors"]').contains('Der angegebene Coupon-Code ist ungültig.');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error if an error occures', () => {
|
||||||
|
cy.viewport('macbook-15');
|
||||||
|
cy.mockGraphql({
|
||||||
|
schema: schema,
|
||||||
|
operations: {
|
||||||
|
Coupon: new GraphQLError("unknown_error")
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cy.login('rahel.cueni', 'test', true)
|
||||||
|
cy.get('[data-cy="rooms-link"]').contains('Alle Räume anzeigen');
|
||||||
|
|
||||||
|
cy.visit('/license-activation');
|
||||||
|
cy.redeemCoupon('12345asfd');
|
||||||
|
cy.get('[data-cy="coupon-remote-errors"]').contains('Es ist ein Fehler aufgetreten. Bitte versuchen Sie es nochmals oder kontaktieren Sie den Administrator.');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
const schema = require('../fixtures/schema_public.json');
|
||||||
|
|
||||||
|
describe('Email Verifcation', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.server();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('forwards to homepage if confirmation key is correct', () => {
|
||||||
|
cy.viewport('macbook-15');
|
||||||
|
cy.mockGraphql({
|
||||||
|
schema: schema,
|
||||||
|
operations: {
|
||||||
|
Registration: {
|
||||||
|
registration: {
|
||||||
|
message: "success",
|
||||||
|
success: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.visit('/verify-email?confirmation=abcd1234&id=12');
|
||||||
|
|
||||||
|
// user should be logged in at that stage. As the cookie cannot be set at the right time
|
||||||
|
// we just check if the user gets redirected to the login page as we can't log her in
|
||||||
|
cy.url().should('include', 'hello?redirect=%2F');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error if key is incorrect', () => {
|
||||||
|
cy.viewport('macbook-15');
|
||||||
|
cy.mockGraphql({
|
||||||
|
schema: schema,
|
||||||
|
// endpoint: '/api/graphql'
|
||||||
|
operations: {
|
||||||
|
Registration: {
|
||||||
|
registration: {
|
||||||
|
message: "invalid_key",
|
||||||
|
success: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.visit('/verify-email?confirmation=abcd1234&id=12');
|
||||||
|
cy.get('[data-cy="code-nok-msg"]').contains('Der angegebene Verifizierungscode ist ungültig oder abgelaufen.');
|
||||||
|
cy.get('[data-cy="code-ok-msg"]').should('not.exist');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error if an error occured', () => {
|
||||||
|
cy.viewport('macbook-15');
|
||||||
|
cy.mockGraphql({
|
||||||
|
schema: schema,
|
||||||
|
// endpoint: '/api/graphql'
|
||||||
|
operations: {
|
||||||
|
Registration: {
|
||||||
|
registration: {
|
||||||
|
message: "unkown_error",
|
||||||
|
success: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.visit('/verify-email?confirmation=abcd1234&id=12');
|
||||||
|
cy.get('[data-cy="code-nok-msg"]').contains('Ein Fehler ist aufgetreten. Bitte kontaktieren Sie den Administrator.');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('forwards to coupon page if user has no valid license', () => {
|
||||||
|
cy.viewport('macbook-15');
|
||||||
|
cy.mockGraphql({
|
||||||
|
schema: schema,
|
||||||
|
// endpoint: '/api/graphql'
|
||||||
|
operations: {
|
||||||
|
Registration: {
|
||||||
|
registration: {
|
||||||
|
message: "no_valid_license",
|
||||||
|
success: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.visit('/verify-email?confirmation=abcd1234&id=12');
|
||||||
|
|
||||||
|
// user should be logged in at that stage. As the cookie cannot be set at the right time
|
||||||
|
// we just check if the user gets redirected to the coupon page as we can't log her in
|
||||||
|
cy.url().should('include', 'hello?redirect=%2Flicense-activation');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
describe('The Logged In Home Page', () => {
|
|
||||||
it('successfully loads', () => {
|
|
||||||
// todo: use graphql login
|
|
||||||
cy.visit('/');
|
|
||||||
cy.login('test', 'test');
|
|
||||||
|
|
||||||
cy.get('.block-title__title').should('contain', 'Inhalte')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
const schema = require('../fixtures/schema_public.json');
|
||||||
|
const isEmailAvailableUrl = '**/rest/deutsch/V1/customers/isEmailAvailable';
|
||||||
|
const checkPasswordUrl = '**/rest/deutsch/V1/integration/customer/token';
|
||||||
|
|
||||||
|
describe('Login', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.server();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works with valid email and password', () => {
|
||||||
|
cy.viewport('macbook-15');
|
||||||
|
|
||||||
|
cy.mockGraphql({
|
||||||
|
schema: schema,
|
||||||
|
operations: {
|
||||||
|
Login: variables => {
|
||||||
|
return {
|
||||||
|
login: {
|
||||||
|
errors: [],
|
||||||
|
message: "success",
|
||||||
|
success: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.route('POST', isEmailAvailableUrl, 'false');
|
||||||
|
cy.route({
|
||||||
|
method: 'POST',
|
||||||
|
url: checkPasswordUrl,
|
||||||
|
response: 'token12345ABCD+',
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.visit('/hello');
|
||||||
|
cy.checkEmailAvailable('feuz@aebi.ch');
|
||||||
|
|
||||||
|
cy.get('[data-cy="login-title"]').contains('Bitte geben Sie das passende Passwort ein');
|
||||||
|
cy.enterPassword('abcd1234');
|
||||||
|
// As we cannot set the cookie in the right manner, we just check for the absence of errors.
|
||||||
|
// In real world the user gets redirect to another page
|
||||||
|
cy.get('[data-cy="email-local-errors"]').should('not.exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error message if password is wrong', () => {
|
||||||
|
cy.viewport('macbook-15');
|
||||||
|
cy.route('POST', isEmailAvailableUrl, 'false');
|
||||||
|
cy.route({
|
||||||
|
method: 'POST',
|
||||||
|
status: 401,
|
||||||
|
response: {
|
||||||
|
message: "Sie haben sich nicht korrekt eingeloggt oder Ihr Konto ist vor\u00fcbergehend deaktiviert."
|
||||||
|
},
|
||||||
|
url: checkPasswordUrl
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.visit('/hello');
|
||||||
|
|
||||||
|
cy.checkEmailAvailable('feuz@aebi.ch');
|
||||||
|
cy.get('[data-cy="login-title"]').contains('Bitte geben Sie das passende Passwort ein');
|
||||||
|
|
||||||
|
cy.enterPassword('abcd1234');
|
||||||
|
cy.get('[data-cy="password-errors"]').contains('Sie haben sich nicht korrekt eingeloggt oder Ihr Konto ist vorübergehend deaktiviert.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error message if input is not an email address', () => {
|
||||||
|
cy.viewport('macbook-15');
|
||||||
|
cy.visit('/hello');
|
||||||
|
|
||||||
|
cy.checkEmailAvailable('feuzaebi.ch');
|
||||||
|
cy.get('[data-cy="email-local-errors"]').contains('Bitte geben Sie eine gülitge E-Mail an');
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
describe('The Regstration Page', () => {
|
|
||||||
// works locally, but not in pipelines.
|
|
||||||
// it('register user', () => {
|
|
||||||
|
|
||||||
// let timestamp = Math.round((new Date()).getTime() / 1000);
|
|
||||||
|
|
||||||
// const firstname = 'pesche';
|
|
||||||
// const lastname = 'peschemann';
|
|
||||||
// const email = `skillboxtest${timestamp}@iterativ.ch`;
|
|
||||||
// const licenseKey = 'c1fa2e2a-2e27-480d-8469-2e88414c4ad8';
|
|
||||||
|
|
||||||
// cy.visit('/register');
|
|
||||||
// cy.register(firstname, lastname, email, licenseKey);
|
|
||||||
// cy.get('.reset__heading').contains('Schauen Sie in Ihr Postfach');
|
|
||||||
// });
|
|
||||||
|
|
||||||
it('user sees error message if firstname is omitted', () => {
|
|
||||||
let timestamp = Math.round((new Date()).getTime() / 1000);
|
|
||||||
const firstname = '';
|
|
||||||
const lastname = 'peschemann';
|
|
||||||
const email = `skillboxtest${timestamp}@iterativ.ch`;
|
|
||||||
const licenseKey = 'c1fa2e2a-2e27-480d-8469-2e88414c4ad8';
|
|
||||||
|
|
||||||
cy.visit('/register');
|
|
||||||
cy.register(firstname, lastname, email, licenseKey);
|
|
||||||
cy.get('[data-cy="firstname-local-errors"]').contains('Vorname ist ein Pflichtfeld.');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('user sees error message if lastname is omitted', () => {
|
|
||||||
let timestamp = Math.round((new Date()).getTime() / 1000);
|
|
||||||
const firstname = 'pesche';
|
|
||||||
const lastname = '';
|
|
||||||
const email = `skillboxtest${timestamp}@iterativ.ch`;
|
|
||||||
const licenseKey = 'c1fa2e2a-2e27-480d-8469-2e88414c4ad8';
|
|
||||||
|
|
||||||
cy.visit('/register');
|
|
||||||
cy.register(firstname, lastname, email, licenseKey);
|
|
||||||
cy.get('[data-cy="lastname-local-errors"]').contains('Nachname ist ein Pflichtfeld.');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('user sees error message if email is omitted', () => {
|
|
||||||
let timestamp = Math.round((new Date()).getTime() / 1000);
|
|
||||||
const firstname = 'pesche';
|
|
||||||
const lastname = 'peschemann';
|
|
||||||
const email = ``;
|
|
||||||
const licenseKey = 'c1fa2e2a-2e27-480d-8469-2e88414c4ad8';
|
|
||||||
|
|
||||||
cy.visit('/register');
|
|
||||||
cy.register(firstname, lastname, email, licenseKey);
|
|
||||||
cy.get('[data-cy="email-local-errors"]').contains('E-Mail ist ein Pflichtfeld.');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('user sees error message if license is omitted', () => {
|
|
||||||
let timestamp = Math.round((new Date()).getTime() / 1000);
|
|
||||||
const firstname = 'pesche';
|
|
||||||
const lastname = 'peschemann';
|
|
||||||
const email = `skillboxtest${timestamp}@iterativ.ch`;
|
|
||||||
const licenseKey = '';
|
|
||||||
|
|
||||||
cy.visit('/register');
|
|
||||||
cy.register(firstname, lastname, email, licenseKey);
|
|
||||||
cy.get('[data-cy="licenseKey-local-errors"]').contains('Lizenz ist ein Pflichtfeld.');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('user sees error message if license key is wrong', () => {
|
|
||||||
let timestamp = Math.round((new Date()).getTime() / 1000);
|
|
||||||
const firstname = 'pesche';
|
|
||||||
const lastname = 'peschemann';
|
|
||||||
const email = `skillboxtest${timestamp}@iterativ.ch`;
|
|
||||||
const licenseKey = 'asdsafsadfsadfasdf';
|
|
||||||
|
|
||||||
cy.visit('/register');
|
|
||||||
cy.register(firstname, lastname, email, licenseKey);
|
|
||||||
cy.get('[data-cy="licenseKey-remote-errors"]').contains('Die angegebenen Lizenz ist unglültig');
|
|
||||||
});
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
const isEmailAvailableUrl = 'https://stage.hep-verlag.ch/rest/deutsch/V1/customers/isEmailAvailable';
|
||||||
|
const registerUrl = '/api/proxy/registration/';
|
||||||
|
|
||||||
|
let registrationResponse = {
|
||||||
|
id: 84215,
|
||||||
|
group_id: 1,
|
||||||
|
confirmation: "91cf39007547feae7e33778d89fc71db",
|
||||||
|
created_at: "2020-02-06 13:56:54",
|
||||||
|
updated_at: "2020-02-06 13:56:54",
|
||||||
|
created_in: "hep verlag",
|
||||||
|
email: "feuz@aebi.ch",
|
||||||
|
firstname: "Kari",
|
||||||
|
lastname: "Feuz",
|
||||||
|
prefix: "Herr",
|
||||||
|
gender: 1,
|
||||||
|
store_id: 1,
|
||||||
|
website_id: 1,
|
||||||
|
addresses: []
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Registration', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.viewport('macbook-15');
|
||||||
|
cy.server();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('works with valid data', () => {
|
||||||
|
cy.route('POST', isEmailAvailableUrl, "true");
|
||||||
|
cy.route('POST', registerUrl, registrationResponse);
|
||||||
|
|
||||||
|
cy.visit('/hello');
|
||||||
|
cy.checkEmailAvailable(registrationResponse.email);
|
||||||
|
|
||||||
|
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||||
|
cy.register(registrationResponse.gender, registrationResponse.firstname, registrationResponse.lastname, 'Weg 1', 'Bern', '3001', 'Abcd1234!', 'Abcd1234!');
|
||||||
|
cy.get('[data-cy="email-check"]').contains('Eine Email ist auf dem Weg, bitte überprüfen sie ihre E-mail Konto.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error if firstname is missing', () => {
|
||||||
|
cy.route('POST', isEmailAvailableUrl, "true");
|
||||||
|
cy.route('POST', registerUrl, registrationResponse);
|
||||||
|
|
||||||
|
cy.visit('/hello');
|
||||||
|
cy.checkEmailAvailable(registrationResponse.email);
|
||||||
|
|
||||||
|
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||||
|
cy.register(registrationResponse.gender, '', registrationResponse.lastname, 'Weg 1', 'Bern', '3001', 'Abcd1234!', 'Abcd1234!');
|
||||||
|
cy.get('[data-cy="firstname-local-errors"]').contains('Vorname ist ein Pflichtfeld');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error if lastname is missing', () => {
|
||||||
|
cy.route('POST', isEmailAvailableUrl, "true");
|
||||||
|
cy.route('POST', registerUrl, registrationResponse);
|
||||||
|
|
||||||
|
cy.visit('/hello');
|
||||||
|
cy.checkEmailAvailable(registrationResponse.email);
|
||||||
|
|
||||||
|
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||||
|
cy.register(registrationResponse.gender, registrationResponse.firstname, '', 'Weg 1', 'Bern', '3001', 'Abcd1234!', 'Abcd1234!');
|
||||||
|
cy.get('[data-cy="lastname-local-errors"]').contains('Nachname ist ein Pflichtfeld');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error if street is missing', () => {
|
||||||
|
cy.route('POST', isEmailAvailableUrl, 'true');
|
||||||
|
cy.route('POST', registerUrl, registrationResponse);
|
||||||
|
|
||||||
|
cy.visit('/hello');
|
||||||
|
cy.checkEmailAvailable(registrationResponse.email);
|
||||||
|
|
||||||
|
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||||
|
cy.register(registrationResponse.gender, registrationResponse.firstname, registrationResponse.lastname, '', 'Bern', '3001', 'Abcd1234!', 'Abcd1234!');
|
||||||
|
cy.get('[data-cy="street-local-errors"]').contains('Strasse ist ein Pflichtfeld');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error if city is missing', () => {
|
||||||
|
cy.route('POST', isEmailAvailableUrl, 'true');
|
||||||
|
cy.route('POST', registerUrl, registrationResponse);
|
||||||
|
|
||||||
|
cy.visit('/hello');
|
||||||
|
cy.checkEmailAvailable(registrationResponse.email);
|
||||||
|
|
||||||
|
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||||
|
cy.register(registrationResponse.gender, registrationResponse.firstname, registrationResponse.lastname, 'Weg 1', '', '3001', 'Abcd1234!', 'Abcd1234!');
|
||||||
|
cy.get('[data-cy="city-local-errors"]').contains('Ort ist ein Pflichtfeld');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error if postcode is missing', () => {
|
||||||
|
cy.route('POST', isEmailAvailableUrl, 'true');
|
||||||
|
cy.route('POST', registerUrl, registrationResponse);
|
||||||
|
|
||||||
|
cy.visit('/hello');
|
||||||
|
cy.checkEmailAvailable(registrationResponse.email);
|
||||||
|
|
||||||
|
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||||
|
cy.register(registrationResponse.gender, registrationResponse.firstname, registrationResponse.lastname, 'Weg 1', 'Bern', '', 'Abcd1234!', 'Abcd1234!');
|
||||||
|
cy.get('[data-cy="postcode-local-errors"]').contains('Postleitzahl ist ein Pflichtfeld');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error if password is missing', () => {
|
||||||
|
cy.route('POST', isEmailAvailableUrl, "true");
|
||||||
|
cy.route('POST', registerUrl, registrationResponse);
|
||||||
|
|
||||||
|
cy.visit('/hello');
|
||||||
|
cy.checkEmailAvailable(registrationResponse.email);
|
||||||
|
|
||||||
|
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||||
|
cy.register(registrationResponse.gender, registrationResponse.firstname, registrationResponse.lastname, 'Weg 1', 'Bern', '3001', '', 'Abcd1234!');
|
||||||
|
cy.get('[data-cy="password-local-errors"]').contains('Passwort ist ein Pflichtfeld');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error if passwords are not secure', () => {
|
||||||
|
cy.route('POST', isEmailAvailableUrl, "true");
|
||||||
|
cy.route('POST', registerUrl, registrationResponse);
|
||||||
|
|
||||||
|
cy.visit('/hello');
|
||||||
|
cy.checkEmailAvailable(registrationResponse.email);
|
||||||
|
|
||||||
|
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||||
|
cy.register(registrationResponse.gender, registrationResponse.firstname, registrationResponse.lastname, 'Weg 1', 'Bern', '3001', 'Abcd1234', 'Abcd1234');
|
||||||
|
cy.get('[data-cy="password-local-errors"]').contains('Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten und mindestens 8 Zeichen lang sein');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error if passwords are too short', () => {
|
||||||
|
cy.route('POST', isEmailAvailableUrl, "true");
|
||||||
|
cy.route('POST', registerUrl, registrationResponse);
|
||||||
|
|
||||||
|
cy.visit('/hello');
|
||||||
|
cy.checkEmailAvailable(registrationResponse.email);
|
||||||
|
|
||||||
|
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||||
|
cy.register(registrationResponse.gender, registrationResponse.firstname, registrationResponse.lastname, 'Weg 1', 'Bern', '3001', 'Abcd12!', 'Abcd12!');
|
||||||
|
cy.get('[data-cy="password-local-errors"]').contains('Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten und mindestens 8 Zeichen lang sein');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('displays error if passwords are not matching', () => {
|
||||||
|
cy.route('POST', isEmailAvailableUrl, "true");
|
||||||
|
cy.route('POST', registerUrl, registrationResponse);
|
||||||
|
|
||||||
|
cy.visit('/hello');
|
||||||
|
cy.checkEmailAvailable(registrationResponse.email);
|
||||||
|
|
||||||
|
cy.get('[data-cy="registration-title"]').contains('Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.');
|
||||||
|
cy.register(registrationResponse.gender, registrationResponse.firstname, registrationResponse.lastname, 'Weg 1', 'Bern', '3001', 'Abcd1234!', 'Abcd129999!');
|
||||||
|
cy.get('[data-cy="passwordConfirmation-local-errors"]').contains('Die Bestätigung von Passwort wiederholen stimmt nicht überein');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('redirects to hello if email is missing', () => {
|
||||||
|
cy.visit('/register');
|
||||||
|
cy.get('[data-cy="hello-title"]').contains('Wollen sie mySkillbox jetzt im Unterricht verwenden?');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
@ -3,8 +3,8 @@ describe('The Room Page', () => {
|
||||||
// todo: mock all the graphql queries and mutations
|
// todo: mock all the graphql queries and mutations
|
||||||
|
|
||||||
cy.viewport('macbook-15');
|
cy.viewport('macbook-15');
|
||||||
|
cy.apolloLogin('rahel.cueni', 'test');
|
||||||
cy.visit('/room/ein-historisches-festival');
|
cy.visit('/room/ein-historisches-festival');
|
||||||
cy.login('rahel.cueni', 'test');
|
|
||||||
|
|
||||||
cy.get('[data-cy=add-room-entry-button]').click();
|
cy.get('[data-cy=add-room-entry-button]').click();
|
||||||
cy.get('.add-content-element:first-of-type').click();
|
cy.get('.add-content-element:first-of-type').click();
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
describe('The Rooms Page', () => {
|
describe('The Rooms Page', () => {
|
||||||
// todo: mock all the graphql queries and mutations
|
// todo: mock all the graphql queries and mutations
|
||||||
it('goes to the rooms page', () => {
|
it('goes to the rooms page', () => {
|
||||||
|
cy.apolloLogin('nico.zickgraf', 'test');
|
||||||
cy.visit('/rooms');
|
cy.visit('/rooms');
|
||||||
cy.login('nico.zickgraf', 'test');
|
|
||||||
|
|
||||||
|
|
||||||
cy.get('[data-cy=add-room]').should('exist');
|
cy.get('[data-cy=add-room]').should('exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('add room should not exist for student', () => {
|
it('add room should not exist for student', () => {
|
||||||
|
cy.apolloLogin('rahel.cueni', 'test');
|
||||||
cy.visit('/rooms');
|
cy.visit('/rooms');
|
||||||
cy.login('rahel.cueni', 'test');
|
|
||||||
|
|
||||||
|
|
||||||
cy.get('[data-cy=add-room]').should('not.exist');
|
cy.get('[data-cy=add-room]').should('not.exist');
|
||||||
|
|
|
||||||
|
|
@ -29,16 +29,17 @@
|
||||||
// import 'cypress-graphql-mock';
|
// import 'cypress-graphql-mock';
|
||||||
import '@iam4x/cypress-graphql-mock';
|
import '@iam4x/cypress-graphql-mock';
|
||||||
|
|
||||||
|
|
||||||
Cypress.Commands.add('apolloLogin', (username, password) => {
|
Cypress.Commands.add('apolloLogin', (username, password) => {
|
||||||
const payload = {
|
const payload = {
|
||||||
'operationName': 'Login',
|
'operationName': 'BetaLogin',
|
||||||
'variables': {
|
'variables': {
|
||||||
'input': {
|
'input': {
|
||||||
'usernameInput': username,
|
'usernameInput': username,
|
||||||
'passwordInput': password
|
'passwordInput': password
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'query': 'mutation Login($input: LoginInput!) {\n login(input: $input) {\n success\n errors {\n field\n __typename\n }\n __typename\n }\n}\n'
|
'query': 'mutation BetaLogin($input: BetaLoginInput!) {\n betaLogin(input: $input) {\n success\n __typename\n }\n}\n'
|
||||||
};
|
};
|
||||||
|
|
||||||
cy.request({
|
cy.request({
|
||||||
|
|
@ -53,7 +54,7 @@ Cypress.Commands.add('apolloLogin', (username, password) => {
|
||||||
// todo: replace with apollo call
|
// todo: replace with apollo call
|
||||||
Cypress.Commands.add("login", (username, password, visitLogin = false) => {
|
Cypress.Commands.add("login", (username, password, visitLogin = false) => {
|
||||||
if (visitLogin) {
|
if (visitLogin) {
|
||||||
cy.visit('/login');
|
cy.visit('/beta-login');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (username != '') {
|
if (username != '') {
|
||||||
|
|
@ -110,22 +111,53 @@ Cypress.Commands.add('changePassword', (oldPassword, newPassword) => {
|
||||||
cy.get('[data-cy=change-password-button]').click();
|
cy.get('[data-cy=change-password-button]').click();
|
||||||
});
|
});
|
||||||
|
|
||||||
Cypress.Commands.add('register', (firstname, lastname, email, licenseKey) => {
|
Cypress.Commands.add('checkEmailAvailable', (email) => {
|
||||||
if (firstname != '') {
|
cy.get('[data-cy="email-input"]').type(email);
|
||||||
cy.get('[data-cy=firstname-input]').type(firstname);
|
cy.get('[data-cy="hello-button"]').click();
|
||||||
}
|
|
||||||
|
|
||||||
if (lastname != '') {
|
|
||||||
cy.get('[data-cy=lastname-input]').type(lastname);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (email != '') {
|
|
||||||
cy.get('[data-cy=email-input]').type(email);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (licenseKey != '') {
|
|
||||||
cy.get('[data-cy=licenseKey-input]').type(licenseKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
cy.get('[data-cy=register-button]').click();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('enterPassword', (password) => {
|
||||||
|
cy.get('[data-cy="password-input"]').type(password);
|
||||||
|
cy.get('[data-cy="login-button"]').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('register', (prefix, firstname, lastname, street, city, postcode, password, passwordConfirmation) => {
|
||||||
|
|
||||||
|
let selection = prefix === 1 ? 'Herr' : 'Frau';
|
||||||
|
|
||||||
|
cy.get('[data-cy="prefix-selection"]').select(selection);
|
||||||
|
|
||||||
|
if (firstname !== '') {
|
||||||
|
cy.get('[data-cy="firstname-input"]').type(firstname);
|
||||||
|
}
|
||||||
|
if (lastname !== '') {
|
||||||
|
cy.get('[data-cy="lastname-input"]').type(lastname);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (street !== '') {
|
||||||
|
cy.get('[data-cy="street-input"]').type(street);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (city !== '') {
|
||||||
|
cy.get('[data-cy="city-input"]').type(city);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (postcode !== '') {
|
||||||
|
cy.get('[data-cy="postcode-input"]').type(postcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password !== '') {
|
||||||
|
cy.get('[data-cy="password-input"]').type(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
cy.get('[data-cy="passwordConfirmation-input"]').type(passwordConfirmation);
|
||||||
|
cy.get('[data-cy="register-button"]').click();
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('redeemCoupon', coupon => {
|
||||||
|
if (coupon !== '') {
|
||||||
|
cy.get('[data-cy="coupon-input"]').type(coupon);
|
||||||
|
}
|
||||||
|
cy.get('[data-cy="coupon-button"]').click();
|
||||||
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2460,7 +2460,7 @@
|
||||||
},
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||||
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
|
@ -2492,7 +2492,7 @@
|
||||||
},
|
},
|
||||||
"onetime": {
|
"onetime": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
|
||||||
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
|
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
|
@ -9595,9 +9595,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"fstream": {
|
"fstream": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
|
||||||
"integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
|
"integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"graceful-fs": "^4.1.2",
|
"graceful-fs": "^4.1.2",
|
||||||
"inherits": "~2.0.0",
|
"inherits": "~2.0.0",
|
||||||
|
|
@ -9810,13 +9810,20 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"globule": {
|
"globule": {
|
||||||
"version": "1.2.1",
|
"version": "1.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/globule/-/globule-1.3.1.tgz",
|
||||||
"integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==",
|
"integrity": "sha512-OVyWOHgw29yosRHCHo7NncwR1hW5ew0W/UrvtwvjefVJeQ26q4/8r8FmPsSF1hJ93IgWkyv16pCTz6WblMzm/g==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"glob": "~7.1.1",
|
"glob": "~7.1.1",
|
||||||
"lodash": "~4.17.10",
|
"lodash": "~4.17.12",
|
||||||
"minimatch": "~3.0.2"
|
"minimatch": "~3.0.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"lodash": {
|
||||||
|
"version": "4.17.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||||
|
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"graceful-fs": {
|
"graceful-fs": {
|
||||||
|
|
@ -10425,9 +10432,9 @@
|
||||||
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
|
"integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o="
|
||||||
},
|
},
|
||||||
"in-publish": {
|
"in-publish": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.1.tgz",
|
||||||
"integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E="
|
"integrity": "sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ=="
|
||||||
},
|
},
|
||||||
"indent-string": {
|
"indent-string": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
|
|
@ -13334,7 +13341,7 @@
|
||||||
},
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
|
||||||
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
"integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
|
|
@ -13517,21 +13524,11 @@
|
||||||
"integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=",
|
"integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"lodash.assign": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
|
|
||||||
"integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc="
|
|
||||||
},
|
|
||||||
"lodash.camelcase": {
|
"lodash.camelcase": {
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||||
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
|
"integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY="
|
||||||
},
|
},
|
||||||
"lodash.clonedeep": {
|
|
||||||
"version": "4.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
|
||||||
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
|
|
||||||
},
|
|
||||||
"lodash.create": {
|
"lodash.create": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz",
|
||||||
|
|
@ -13576,11 +13573,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
|
||||||
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4="
|
"integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4="
|
||||||
},
|
},
|
||||||
"lodash.mergewith": {
|
|
||||||
"version": "4.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
|
|
||||||
"integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ=="
|
|
||||||
},
|
|
||||||
"lodash.once": {
|
"lodash.once": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
|
||||||
|
|
@ -14177,7 +14169,8 @@
|
||||||
"nan": {
|
"nan": {
|
||||||
"version": "2.10.0",
|
"version": "2.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
|
||||||
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
|
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"nanomatch": {
|
"nanomatch": {
|
||||||
"version": "1.2.13",
|
"version": "1.2.13",
|
||||||
|
|
@ -14465,9 +14458,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node-sass": {
|
"node-sass": {
|
||||||
"version": "4.9.2",
|
"version": "4.13.1",
|
||||||
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.1.tgz",
|
||||||
"integrity": "sha512-LdxoJLZutx0aQXHtWIYwJKMj+9pTjneTcLWJgzf2XbGu0q5pRNqW5QvFCEdm3mc5rJOdru/mzln5d0EZLacf6g==",
|
"integrity": "sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"async-foreach": "^0.1.3",
|
"async-foreach": "^0.1.3",
|
||||||
"chalk": "^1.1.1",
|
"chalk": "^1.1.1",
|
||||||
|
|
@ -14476,20 +14469,29 @@
|
||||||
"get-stdin": "^4.0.1",
|
"get-stdin": "^4.0.1",
|
||||||
"glob": "^7.0.3",
|
"glob": "^7.0.3",
|
||||||
"in-publish": "^2.0.0",
|
"in-publish": "^2.0.0",
|
||||||
"lodash.assign": "^4.2.0",
|
"lodash": "^4.17.15",
|
||||||
"lodash.clonedeep": "^4.3.2",
|
|
||||||
"lodash.mergewith": "^4.6.0",
|
|
||||||
"meow": "^3.7.0",
|
"meow": "^3.7.0",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"nan": "^2.10.0",
|
"nan": "^2.13.2",
|
||||||
"node-gyp": "^3.3.1",
|
"node-gyp": "^3.8.0",
|
||||||
"npmlog": "^4.0.0",
|
"npmlog": "^4.0.0",
|
||||||
"request": "2.87.0",
|
"request": "^2.88.0",
|
||||||
"sass-graph": "^2.2.4",
|
"sass-graph": "^2.2.4",
|
||||||
"stdout-stream": "^1.4.0",
|
"stdout-stream": "^1.4.0",
|
||||||
"true-case-path": "^1.0.2"
|
"true-case-path": "^1.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"ajv": {
|
||||||
|
"version": "6.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
|
||||||
|
"integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
|
||||||
|
"requires": {
|
||||||
|
"fast-deep-equal": "^3.1.1",
|
||||||
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
|
"json-schema-traverse": "^0.4.1",
|
||||||
|
"uri-js": "^4.2.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ansi-styles": {
|
"ansi-styles": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
|
||||||
|
|
@ -14516,10 +14518,80 @@
|
||||||
"which": "^1.2.9"
|
"which": "^1.2.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"fast-deep-equal": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
|
||||||
|
},
|
||||||
|
"har-validator": {
|
||||||
|
"version": "5.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
|
||||||
|
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
|
||||||
|
"requires": {
|
||||||
|
"ajv": "^6.5.5",
|
||||||
|
"har-schema": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"json-schema-traverse": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
|
||||||
|
},
|
||||||
|
"lodash": {
|
||||||
|
"version": "4.17.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||||
|
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||||
|
},
|
||||||
|
"nan": {
|
||||||
|
"version": "2.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
|
||||||
|
"integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
|
||||||
|
},
|
||||||
|
"oauth-sign": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"version": "2.88.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
||||||
|
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
|
||||||
|
"requires": {
|
||||||
|
"aws-sign2": "~0.7.0",
|
||||||
|
"aws4": "^1.8.0",
|
||||||
|
"caseless": "~0.12.0",
|
||||||
|
"combined-stream": "~1.0.6",
|
||||||
|
"extend": "~3.0.2",
|
||||||
|
"forever-agent": "~0.6.1",
|
||||||
|
"form-data": "~2.3.2",
|
||||||
|
"har-validator": "~5.1.3",
|
||||||
|
"http-signature": "~1.2.0",
|
||||||
|
"is-typedarray": "~1.0.0",
|
||||||
|
"isstream": "~0.1.2",
|
||||||
|
"json-stringify-safe": "~5.0.1",
|
||||||
|
"mime-types": "~2.1.19",
|
||||||
|
"oauth-sign": "~0.9.0",
|
||||||
|
"performance-now": "^2.1.0",
|
||||||
|
"qs": "~6.5.2",
|
||||||
|
"safe-buffer": "^5.1.2",
|
||||||
|
"tough-cookie": "~2.5.0",
|
||||||
|
"tunnel-agent": "^0.6.0",
|
||||||
|
"uuid": "^3.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"supports-color": {
|
"supports-color": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
|
||||||
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
|
"integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc="
|
||||||
|
},
|
||||||
|
"tough-cookie": {
|
||||||
|
"version": "2.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
|
||||||
|
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
|
||||||
|
"requires": {
|
||||||
|
"psl": "^1.1.28",
|
||||||
|
"punycode": "^2.1.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -17151,8 +17223,7 @@
|
||||||
"psl": {
|
"psl": {
|
||||||
"version": "1.7.0",
|
"version": "1.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz",
|
||||||
"integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==",
|
"integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"public-encrypt": {
|
"public-encrypt": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
|
|
@ -18592,9 +18663,9 @@
|
||||||
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
|
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
|
||||||
},
|
},
|
||||||
"stdout-stream": {
|
"stdout-stream": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz",
|
||||||
"integrity": "sha1-osfIWH5U2UJ+qe2zrD8s1SLfN4s=",
|
"integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"readable-stream": "^2.0.1"
|
"readable-stream": "^2.0.1"
|
||||||
}
|
}
|
||||||
|
|
@ -18805,12 +18876,12 @@
|
||||||
"integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI="
|
"integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI="
|
||||||
},
|
},
|
||||||
"tar": {
|
"tar": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz",
|
||||||
"integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
|
"integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"block-stream": "*",
|
"block-stream": "*",
|
||||||
"fstream": "^1.0.2",
|
"fstream": "^1.0.12",
|
||||||
"inherits": "2"
|
"inherits": "2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -19098,25 +19169,11 @@
|
||||||
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM="
|
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM="
|
||||||
},
|
},
|
||||||
"true-case-path": {
|
"true-case-path": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz",
|
||||||
"integrity": "sha1-fskRMJJHZsf1c74wIMNPj9/QDWI=",
|
"integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"glob": "^6.0.4"
|
"glob": "^7.1.2"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"glob": {
|
|
||||||
"version": "6.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
|
|
||||||
"integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
|
|
||||||
"requires": {
|
|
||||||
"inflight": "^1.0.4",
|
|
||||||
"inherits": "2",
|
|
||||||
"minimatch": "2 || 3",
|
|
||||||
"once": "^1.3.0",
|
|
||||||
"path-is-absolute": "^1.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tryer": {
|
"tryer": {
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@
|
||||||
"lodash": "^4.17.10",
|
"lodash": "^4.17.10",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"node-notifier": "^5.1.2",
|
"node-notifier": "^5.1.2",
|
||||||
"node-sass": "^4.9.2",
|
"node-sass": "^4.13.1",
|
||||||
"optimize-css-assets-webpack-plugin": "^3.2.0",
|
"optimize-css-assets-webpack-plugin": "^3.2.0",
|
||||||
"ora": "^1.2.0",
|
"ora": "^1.2.0",
|
||||||
"portfinder": "^1.0.13",
|
"portfinder": "^1.0.13",
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,16 @@ const writeLocalCache = cache => {
|
||||||
sidebar: {
|
sidebar: {
|
||||||
__typename: 'Sidebar',
|
__typename: 'Sidebar',
|
||||||
open: false
|
open: false
|
||||||
}
|
},
|
||||||
|
helloEmail: {
|
||||||
|
__typename: 'HelloEmail',
|
||||||
|
email: ''
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function (uri) {
|
export default function (uri, networkErrorCallback) {
|
||||||
const httpLink = createHttpLink({
|
const httpLink = createHttpLink({
|
||||||
// uri: process.env.NODE_ENV !== 'production' ? 'http://localhost:8000/api/graphql/' : '/api/graphql/',
|
// uri: process.env.NODE_ENV !== 'production' ? 'http://localhost:8000/api/graphql/' : '/api/graphql/',
|
||||||
uri,
|
uri,
|
||||||
|
|
@ -58,6 +62,11 @@ export default function (uri) {
|
||||||
});
|
});
|
||||||
|
|
||||||
const errorLink = onError(({response, operation, networkError, graphQLErrors}) => {
|
const errorLink = onError(({response, operation, networkError, graphQLErrors}) => {
|
||||||
|
if (networkError && networkErrorCallback) {
|
||||||
|
networkErrorCallback(networkError.statusCode);
|
||||||
|
return Observable.of();
|
||||||
|
}
|
||||||
|
|
||||||
if (graphQLErrors) {
|
if (graphQLErrors) {
|
||||||
graphQLErrors.forEach(({message, locations, path}) =>
|
graphQLErrors.forEach(({message, locations, path}) =>
|
||||||
console.log(
|
console.log(
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ fragment UserParts on UserNode {
|
||||||
firstName
|
firstName
|
||||||
lastName
|
lastName
|
||||||
avatarUrl
|
avatarUrl
|
||||||
|
expiryDate
|
||||||
lastModule {
|
lastModule {
|
||||||
id
|
id
|
||||||
slug
|
slug
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
query HelloEmail {
|
||||||
|
helloEmail @client {
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
mutation($helloEmail: String!) {
|
||||||
|
helloEmail(email: $helloEmail) @client
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
mutation BetaLogin($input: BetaLoginInput!) {
|
||||||
|
betaLogin(input: $input) {
|
||||||
|
success
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
mutation Login($input: LoginInput!) {
|
mutation Login($input: LoginInput!) {
|
||||||
login(input: $input) {
|
login(input: $input) {
|
||||||
success
|
success
|
||||||
errors {
|
message
|
||||||
field
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
mutation Coupon($input: CouponInput!){
|
||||||
|
coupon(input: $input) {
|
||||||
|
success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
mutation Registration($input: RegistrationInput!){
|
mutation Registration($input: RegistrationInput!) {
|
||||||
registration(input: $input) {
|
registration(input: $input) {
|
||||||
success
|
success
|
||||||
errors {
|
message
|
||||||
field
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import SCROLL_POSITION from '@/graphql/gql/local/scrollPosition.gql';
|
import SCROLL_POSITION from '@/graphql/gql/local/scrollPosition.gql';
|
||||||
|
import HELLO_EMAIL from '@/graphql/gql/local/helloEmail.gql';
|
||||||
import SIDEBAR from '@/graphql/gql/local/sidebar.gql';
|
import SIDEBAR from '@/graphql/gql/local/sidebar.gql';
|
||||||
|
|
||||||
export const resolvers = {
|
export const resolvers = {
|
||||||
|
|
@ -9,6 +10,12 @@ export const resolvers = {
|
||||||
cache.writeQuery({query: SCROLL_POSITION, data});
|
cache.writeQuery({query: SCROLL_POSITION, data});
|
||||||
return data.scrollPosition;
|
return data.scrollPosition;
|
||||||
},
|
},
|
||||||
|
helloEmail: (_, {email}, {cache}) => {
|
||||||
|
const data = cache.readQuery({query: HELLO_EMAIL});
|
||||||
|
data.helloEmail.email = email;
|
||||||
|
cache.writeQuery({query: HELLO_EMAIL, data});
|
||||||
|
return data.helloEmail;
|
||||||
|
},
|
||||||
toggleSidebar: (_, {open}, {cache}) => {
|
toggleSidebar: (_, {open}, {cache}) => {
|
||||||
const data = cache.readQuery({query: SIDEBAR});
|
const data = cache.readQuery({query: SIDEBAR});
|
||||||
data.sidebar.open = open;
|
data.sidebar.open = open;
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,24 @@
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
export const typeDefs = gql`
|
export const typeDefs = gql`
|
||||||
type ScrollPosition {
|
type ScrollPosition {
|
||||||
scrollTo: String!
|
scrollTo: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Sidebar {
|
type HelloEmail {
|
||||||
|
email: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type Sidebar {
|
||||||
open: Boolean!
|
open: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
scrollTo(scrollTo: String!): ScrollPosition
|
scrollTo(scrollTo: String!): ScrollPosition
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
helloEmail(email: String!): HelloEmail
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import * as axios from 'axios'
|
||||||
|
|
||||||
|
const hepBaseUrl = process.env.HEP_URL;
|
||||||
|
|
||||||
|
export function register(registrationData) {
|
||||||
|
return axios.post('/api/proxy/registration/', registrationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function login(username, password) {
|
||||||
|
return axios.post(`${hepBaseUrl}/rest/deutsch/V1/integration/customer/token`, {username, password});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function emailExists(email) {
|
||||||
|
return axios.post(`${hepBaseUrl}/rest/deutsch/V1/customers/isEmailAvailable`, {
|
||||||
|
customerEmail: email,
|
||||||
|
websiteId: 1
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,22 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="layout layout--public public">
|
<div class="layout layout--public public">
|
||||||
<logo class="public__logo"></logo>
|
<div class="public__logo">
|
||||||
<router-view class="layout__content"></router-view>
|
<logo></logo>
|
||||||
<footer class="layout__footer">Footer</footer>
|
</div>
|
||||||
|
<router-view class="public__content"></router-view>
|
||||||
|
<default-footer class="skillbox__footer public__footer footer"></default-footer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import Logo from '@/components/icons/Logo';
|
import Logo from '@/components/icons/Logo';
|
||||||
|
import DefaultFooter from '@/layouts/DefaultFooter';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {Logo},
|
components: {
|
||||||
|
Logo,
|
||||||
|
DefaultFooter
|
||||||
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -20,17 +25,46 @@ import Logo from '@/components/icons/Logo';
|
||||||
@import "@/styles/_mixins.scss";
|
@import "@/styles/_mixins.scss";
|
||||||
@import "@/styles/_default-layout.scss";
|
@import "@/styles/_default-layout.scss";
|
||||||
|
|
||||||
.public {
|
@mixin content-block {
|
||||||
|
padding-right: $medium-spacing;
|
||||||
|
padding-left: $medium-spacing;
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
padding-top: 4*$large-spacing;
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
width: 260px;
|
||||||
|
height: 43px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.public {
|
||||||
|
grid-template-areas: "h" "c" "f";
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
@include content-block();
|
||||||
|
margin-bottom: $large-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
&__logo {
|
&__logo {
|
||||||
position: relative;
|
@include content-block();
|
||||||
|
margin-top: $medium-spacing
|
||||||
|
}
|
||||||
|
|
||||||
width: 260px;
|
&__footer {
|
||||||
height: 43px;
|
background-color: $color-silver-light;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
padding-top: $large-spacing;
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
@include content-block();
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import router from './router'
|
||||||
import store from '@/store/index'
|
import store from '@/store/index'
|
||||||
import VueScrollTo from 'vue-scrollto';
|
import VueScrollTo from 'vue-scrollto';
|
||||||
import {Validator, install as VeeValidate} from 'vee-validate/dist/vee-validate.minimal.esm.js';
|
import {Validator, install as VeeValidate} from 'vee-validate/dist/vee-validate.minimal.esm.js';
|
||||||
import {required, min} from 'vee-validate/dist/rules.esm.js';
|
import {required, min, decimal, confirmed} from 'vee-validate/dist/rules.esm.js';
|
||||||
import veeDe from 'vee-validate/dist/locale/de';
|
import veeDe from 'vee-validate/dist/locale/de';
|
||||||
import {dateFilter} from './filters/date-filter';
|
import {dateFilter} from './filters/date-filter';
|
||||||
import autoGrow from '@/directives/auto-grow'
|
import autoGrow from '@/directives/auto-grow'
|
||||||
|
|
@ -45,8 +45,8 @@ if (process.env.MATOMO_HOST) {
|
||||||
Vue.directive('click-outside', clickOutside);
|
Vue.directive('click-outside', clickOutside);
|
||||||
Vue.directive('auto-grow', autoGrow);
|
Vue.directive('auto-grow', autoGrow);
|
||||||
|
|
||||||
const publicApolloClient = apolloClientFactory('/api/graphql-public/');
|
const publicApolloClient = apolloClientFactory('/api/graphql-public/', null);
|
||||||
const privateApolloClient = apolloClientFactory('/api/graphql/');
|
const privateApolloClient = apolloClientFactory('/api/graphql/', networkErrorCallback);
|
||||||
|
|
||||||
const apolloProvider = new VueApollo({
|
const apolloProvider = new VueApollo({
|
||||||
clients: {
|
clients: {
|
||||||
|
|
@ -57,6 +57,8 @@ const apolloProvider = new VueApollo({
|
||||||
|
|
||||||
Validator.extend('required', required);
|
Validator.extend('required', required);
|
||||||
Validator.extend('min', min);
|
Validator.extend('min', min);
|
||||||
|
Validator.extend('decimal', decimal);
|
||||||
|
Validator.extend('confirmed', confirmed);
|
||||||
|
|
||||||
const dict = {
|
const dict = {
|
||||||
custom: {
|
custom: {
|
||||||
|
|
@ -72,9 +74,10 @@ const dict = {
|
||||||
|
|
||||||
Validator.localize('de', veeDe)
|
Validator.localize('de', veeDe)
|
||||||
Validator.localize('de', dict)
|
Validator.localize('de', dict)
|
||||||
|
|
||||||
// https://github.com/baianat/vee-validate/issues/51
|
// https://github.com/baianat/vee-validate/issues/51
|
||||||
Validator.extend('strongPassword', {
|
Validator.extend('strongPassword', {
|
||||||
getMessage: field => 'Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten',
|
getMessage: field => 'Das Passwort muss Grossbuchstaben, Zahlen und Sonderzeichen beinhalten und mindestens 8 Zeichen lang sein',
|
||||||
validate: value => {
|
validate: value => {
|
||||||
const strongRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*?(),.":{}|<>+])(?=.{8,})/;
|
const strongRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*?(),.":{}|<>+])(?=.{8,})/;
|
||||||
return strongRegex.test(value);
|
return strongRegex.test(value);
|
||||||
|
|
@ -112,28 +115,43 @@ function unauthorizedAccess(to) {
|
||||||
return loginRequired(to) && getCookieValue('loginStatus') !== 'true';
|
return loginRequired(to) && getCookieValue('loginStatus') !== 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function redirectUsersWithoutValidLicense(to) {
|
||||||
|
return privateApolloClient.query({
|
||||||
|
query: ME_QUERY,
|
||||||
|
}).then(({data}) => data.me.expiryDate == null);
|
||||||
|
}
|
||||||
|
|
||||||
function redirectStudentsWithoutClass() {
|
function redirectStudentsWithoutClass() {
|
||||||
return privateApolloClient.query({
|
return privateApolloClient.query({
|
||||||
query: ME_QUERY,
|
query: ME_QUERY,
|
||||||
}).then(({data}) => data.me.schoolClasses.edges.length === 0 && data.me.permissions.length === 0);
|
}).then(({data}) => data.me.schoolClasses.edges.length === 0 && data.me.permissions.length === 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function networkErrorCallback(statusCode) {
|
||||||
|
if (statusCode === 402) {
|
||||||
|
router.push({name: 'licenseActivation'}, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
// handle logout
|
|
||||||
if (to.path === '/logout') {
|
if (to.path === '/logout') {
|
||||||
privateApolloClient.resetStore();
|
|
||||||
publicApolloClient.resetStore();
|
publicApolloClient.resetStore();
|
||||||
next({name: 'login'});
|
next({name: 'hello'});
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unauthorizedAccess(to)) {
|
if (unauthorizedAccess(to)) {
|
||||||
const redirectUrl = `/login?redirect=${to.path}`;
|
const redirectUrl = `/hello?redirect=${to.path}`;
|
||||||
next(redirectUrl);
|
next(redirectUrl);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (to.name !== 'join-class' && loginRequired(to) && await redirectStudentsWithoutClass()) {
|
if (to.name !== 'licenseActivation' && loginRequired(to) && await redirectUsersWithoutValidLicense()) {
|
||||||
|
next({name: 'licenseActivation'})
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((to.name !== 'join-class' && to.name !== 'licenseActivation') && loginRequired(to) && await redirectStudentsWithoutClass()) {
|
||||||
next({name: 'join-class'})
|
next({name: 'join-class'})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
<template>
|
||||||
|
<div class="login public-page">
|
||||||
|
<h1 class="login__title public-page__title">Melden Sie sich jetzt an</h1>
|
||||||
|
<form class="login__form login-form" novalidate @submit.prevent="validateBeforeSubmit">
|
||||||
|
<div class="login-form__field skillboxform-input">
|
||||||
|
<label for="email" class="skillboxform-input__label">E-Mail</label>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="text"
|
||||||
|
v-model="email"
|
||||||
|
v-validate="'required'"
|
||||||
|
data-vv-as="E-Mail"
|
||||||
|
:class="{ 'skillboxform-input__input--error': errors.has('email') }"
|
||||||
|
class="change-form__email skillbox-input skillboxform-input__input"
|
||||||
|
autocomplete="off"
|
||||||
|
data-cy="email-input"
|
||||||
|
/>
|
||||||
|
<small
|
||||||
|
v-if="errors.has('email') && submitted"
|
||||||
|
class="skillboxform-input__error"
|
||||||
|
data-cy="email-local-errors"
|
||||||
|
>{{ errors.first('email') }}</small>
|
||||||
|
<small
|
||||||
|
v-for="error in emailErrors"
|
||||||
|
:key="error"
|
||||||
|
class="skillboxform-input__error"
|
||||||
|
data-cy="email-remote-errors"
|
||||||
|
>{{ error }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="change-form__field skillboxform-input">
|
||||||
|
<label for="pw" class="skillboxform-input__label">Passwort</label>
|
||||||
|
<input
|
||||||
|
id="pw"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
data-vv-as="Passwort"
|
||||||
|
v-model="password"
|
||||||
|
v-validate="'required'"
|
||||||
|
:class="{ 'skillboxform-input__input--error': errors.has('password') }"
|
||||||
|
class="change-form__new skillbox-input skillboxform-input__input"
|
||||||
|
autocomplete="off"
|
||||||
|
data-cy="password-input"
|
||||||
|
/>
|
||||||
|
<small
|
||||||
|
v-if="errors.has('password') && submitted"
|
||||||
|
class="skillboxform-input__error"
|
||||||
|
data-cy="password-local-errors"
|
||||||
|
>{{ errors.first('password') }}</small>
|
||||||
|
<small
|
||||||
|
v-for="error in passwordErrors"
|
||||||
|
:key="error"
|
||||||
|
class="skillboxform-input__error"
|
||||||
|
data-cy="password-remote-errors"
|
||||||
|
>{{ error }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="skillboxform-input">
|
||||||
|
<small class="skillboxform-input__error" data-cy="login-error" v-if="loginError">{{loginError}}</small>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="button button--primary button--big actions__submit" data-cy="login-button">Anmelden</button>
|
||||||
|
<a class="actions__reset text-link" href="/accounts/password_reset/">Passwort vergessen?</a>
|
||||||
|
</div>
|
||||||
|
<div class="account-link">
|
||||||
|
<p class="account-link__text">Haben Sie noch kein Konto?</p>
|
||||||
|
<router-link class="account-link__link text-link" :to="{name: 'registration'}">Jetzt registrieren
|
||||||
|
</router-link>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BETA_LOGIN_MUTATION from '@/graphql/gql/mutations/betaLogin.gql';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
validateBeforeSubmit() {
|
||||||
|
this.$validator.validate().then(result => {
|
||||||
|
this.submitted = true;
|
||||||
|
let that = this;
|
||||||
|
if (result) {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
client: 'publicClient',
|
||||||
|
mutation: BETA_LOGIN_MUTATION,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
usernameInput: this.email,
|
||||||
|
passwordInput: this.password
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update(
|
||||||
|
store,
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
betaLogin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (betaLogin.success) {
|
||||||
|
const redirectUrl = that.$route.query.redirect ? that.$route.query.redirect : '/'
|
||||||
|
that.$router.push(redirectUrl);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(e);
|
||||||
|
that.loginError = 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie nochmals.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
const firstError = error.graphQLErrors[0];
|
||||||
|
switch (firstError.message) {
|
||||||
|
case 'invalid_credentials':
|
||||||
|
that.loginError = 'Die E-Mail oder das Passwort ist falsch. Bitte versuchen Sie nochmals.';
|
||||||
|
break;
|
||||||
|
case 'license_inactive':
|
||||||
|
that.loginError = 'Ihre Lizenz ist nicht mehr aktiv.';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
this.email = '';
|
||||||
|
this.password = '';
|
||||||
|
this.submitted = false;
|
||||||
|
this.$validator.reset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
emailErrors: [],
|
||||||
|
passwordErrors: [],
|
||||||
|
loginError: '',
|
||||||
|
submitted: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
@import "@/styles/_mixins.scss";
|
||||||
|
|
||||||
|
.text-link {
|
||||||
|
font-family: $sans-serif-font-family;
|
||||||
|
color: $color-brand;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&__reset {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: $large-spacing;
|
||||||
|
padding: 15px;
|
||||||
|
line-height: 19px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<template>
|
||||||
|
<div class="check-email">
|
||||||
|
<main class="check-email__content content" data-cy="email-check">
|
||||||
|
<p class="content__instructions">Eine Email ist auf dem Weg, bitte überprüfen sie ihre E-mail Konto.</p>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
|
||||||
|
.content__instructions {
|
||||||
|
margin-top: $medium-spacing;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
<template>
|
||||||
|
<div class="emailconfirmation public-page">
|
||||||
|
<h1 class="emailconfirmation__title public-page__title">Überprüfung der E-Mail Adresse</h1>
|
||||||
|
<p v-if="loading">Der Verifikationscode wird überprüft.</p>
|
||||||
|
<p v-if="showOkMessage" data-cy="code-ok-msg">Der Verifikationscode ist gültig. Sie werden weitergeleitet.</p>
|
||||||
|
<p v-if="showErrorMessage" data-cy="code-nok-msg">{{errorMessage}}</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import REGISTRATION_MUTATION from '@/graphql/gql/mutations/registration.gql';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
loading: true,
|
||||||
|
keyValid: false,
|
||||||
|
errorMessage: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
showOkMessage() {
|
||||||
|
return !this.loading && this.keyValid;
|
||||||
|
},
|
||||||
|
showErrorMessage() {
|
||||||
|
return !this.loading && !this.keyValid;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: REGISTRATION_MUTATION,
|
||||||
|
client: 'publicClient',
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
confirmationKey: this.$route.query.confirmation,
|
||||||
|
userId: this.$route.query.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchPolicy: 'no-cache'
|
||||||
|
}).then(({data}) => {
|
||||||
|
this.loading = false;
|
||||||
|
if (data.registration.success) {
|
||||||
|
this.keyValid = true;
|
||||||
|
|
||||||
|
if (data.registration.message === 'no_valid_license') {
|
||||||
|
this.$router.push({name: 'licenseActivation'});
|
||||||
|
} else {
|
||||||
|
this.$router.push('/');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (data.registration.message) {
|
||||||
|
case 'invalid_key':
|
||||||
|
this.errorMessage = 'Der angegebene Verifizierungscode ist ungültig oder abgelaufen.';
|
||||||
|
break;
|
||||||
|
case 'no_valid_license':
|
||||||
|
this.$router.push({name: 'licenseActivation'});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.errorMessage = 'Ein Fehler ist aufgetreten. Bitte kontaktieren Sie den Administrator.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.errorMessage = 'Ein Fehler ist aufgetreten. Bitte kontaktieren Sie den Administrator.'
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
@import "@/styles/_mixins.scss";
|
||||||
|
|
||||||
|
.text-link {
|
||||||
|
font-family: $sans-serif-font-family;
|
||||||
|
color: $color-brand;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
&__reset {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: $large-spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<div class="forgot-password public-page">
|
||||||
|
<header class="info-header">
|
||||||
|
<h1 class="forgot-password__title public-page__title" data-cy="forgot-password">Passwort vergessen?</h1>
|
||||||
|
</header>
|
||||||
|
<section class="forgot-password__section forgot-password__text">
|
||||||
|
<p class="forgot-info">Ihr Benutzerkonto wird durch den Hep Verlag verwaltet und deshalb können Sie das Passwort ausschliesslicht auf
|
||||||
|
<a class="hep-link" href="https://www.hep-verlag.ch">www.hep-verlag.ch</a> verwaltet werden.</p>
|
||||||
|
<p class="forgot-info">Melden Sie sich mit der gleichen E-Mail-Adresse und dem gleichen </p>
|
||||||
|
</section>
|
||||||
|
<section class="forgot-password__section forgot-password__link">
|
||||||
|
<a class="button button--primary button--big actions__submit" href="https://www.hep-verlag.ch">Hep Verlag Webseite besuchen</a>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
@import "@/styles/_mixins.scss";
|
||||||
|
|
||||||
|
.forgot-info {
|
||||||
|
font-family: $sans-serif-font-family;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-top: $large-spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.forgot-password__link {
|
||||||
|
margin-top: $large-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
<template>
|
||||||
|
<div class="hello public-page">
|
||||||
|
<h1 class="hello__title public-page__title" data-cy="hello-title">Wollen sie mySkillbox jetzt im Unterricht verwenden?</h1>
|
||||||
|
<form class="hello__form hello-form" novalidate @submit.prevent="validateBeforeSubmit">
|
||||||
|
<div class="hello-form__field skillboxform-input">
|
||||||
|
<label for="email" class="skillboxform-input__label">E-Mail</label>
|
||||||
|
<input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
v-model="email"
|
||||||
|
v-validate="'required'"
|
||||||
|
data-vv-as="E-Mail"
|
||||||
|
:class="{ 'skillboxform-input__input--error': errors.has('email') }"
|
||||||
|
class="change-form__email skillbox-input skillboxform-input__input"
|
||||||
|
autocomplete="off"
|
||||||
|
data-cy="email-input"
|
||||||
|
placeholder="E-Mail eingeben"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
<small
|
||||||
|
v-if="errors.has('email') && submitted"
|
||||||
|
class="skillboxform-input__error"
|
||||||
|
data-cy="email-local-errors"
|
||||||
|
>{{ errors.first('email') }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="button button--primary button--big actions__submit" data-cy="hello-button">Los geht's</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import {emailExists} from '../hep-client/index';
|
||||||
|
import HELLO_EMAIL_MUTATION from '@/graphql/gql/local/mutations/helloEmail.gql';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
validateBeforeSubmit() {
|
||||||
|
this.$validator.validate().then(result => {
|
||||||
|
this.submitted = true;
|
||||||
|
if (result) {
|
||||||
|
emailExists(this.email).then((response) => {
|
||||||
|
let redirectRouteName = 'login';
|
||||||
|
|
||||||
|
if (response.data) {
|
||||||
|
redirectRouteName = 'registration';
|
||||||
|
}
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: HELLO_EMAIL_MUTATION,
|
||||||
|
variables: {
|
||||||
|
helloEmail: this.email
|
||||||
|
}
|
||||||
|
}).then(() => this.$router.push({name: redirectRouteName, query: this.$route.query}));
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.registrationError = 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie es nochmals.';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
this.email = '';
|
||||||
|
this.submitted = false;
|
||||||
|
this.$validator.reset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
email: '',
|
||||||
|
submitted: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
@import "@/styles/_mixins.scss";
|
||||||
|
|
||||||
|
.text-link {
|
||||||
|
font-family: $sans-serif-font-family;
|
||||||
|
color: $color-brand;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
&__reset {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: $large-spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<a class="button button--primary button--big" data-cy="join-class" @click="joinClass(code)">Klasse beitreten</a>
|
<a class="button button--primary button--big" data-cy="join-class" @click="joinClass(code)">Klasse beitreten</a>
|
||||||
<a class="button button--big" data-cy="join-class-cancel" @click="cancel">Abbrechen</a>
|
<button class="button button--big" data-cy="join-class-cancel" @click="logout">Abmelden</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -31,6 +31,7 @@
|
||||||
<script>
|
<script>
|
||||||
import JOIN_CLASS_MUTATION from '@/graphql/gql/mutations/joinClass.gql';
|
import JOIN_CLASS_MUTATION from '@/graphql/gql/mutations/joinClass.gql';
|
||||||
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass';
|
import MY_SCHOOL_CLASS_QUERY from '@/graphql/gql/mySchoolClass';
|
||||||
|
import LOGOUT_MUTATION from '@/graphql/gql/mutations/logoutUser.gql';
|
||||||
|
|
||||||
import addSchoolClassMixin from '@/mixins/add-school-class';
|
import addSchoolClassMixin from '@/mixins/add-school-class';
|
||||||
|
|
||||||
|
|
@ -74,8 +75,12 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
cancel() {
|
logout() {
|
||||||
this.$router.go(-1);
|
this.$apollo.mutate({
|
||||||
|
mutation: LOGOUT_MUTATION,
|
||||||
|
}).then(({data}) => {
|
||||||
|
if (data.logout.success) { location.replace('/') }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
<template>
|
||||||
|
<div class="license-activation public-page">
|
||||||
|
<header class="info-header">
|
||||||
|
<p class="info-header__text small-emph">Für <span class="info-header__emph">{{me.email}}</span> haben wir keine gültige Lizenz gefunden</p>
|
||||||
|
</header>
|
||||||
|
<section class="coupon">
|
||||||
|
<form class="license-activation__form license-activation-form" novalidate @submit.prevent="validateBeforeSubmit">
|
||||||
|
<h2>Geben Sie einen Coupon-Code ein</h2>
|
||||||
|
<div class="change-form__field skillboxform-input">
|
||||||
|
<label for="coupon" class="skillboxform-input__label">Coupon-Code</label>
|
||||||
|
<input
|
||||||
|
id="coupon"
|
||||||
|
name="coupon"
|
||||||
|
type="coupon"
|
||||||
|
data-vv-as="Coupon"
|
||||||
|
v-model="coupon"
|
||||||
|
v-validate="'required'"
|
||||||
|
:class="{ 'skillboxform-input__input--error': errors.has('coupon') }"
|
||||||
|
class="change-form__new skillbox-input skillboxform-input__input"
|
||||||
|
autocomplete="off"
|
||||||
|
data-cy="coupon-input"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
<small
|
||||||
|
v-if="errors.has('coupon') && submitted"
|
||||||
|
class="skillboxform-input__error"
|
||||||
|
data-cy="coupon-local-errors"
|
||||||
|
>{{ errors.first('coupon') }}</small>
|
||||||
|
<small
|
||||||
|
v-for="error in couponErrors"
|
||||||
|
:key="error"
|
||||||
|
class="skillboxform-input__error"
|
||||||
|
data-cy="coupon-remote-errors"
|
||||||
|
>{{ error }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="button button--primary button--big actions__submit" data-cy="coupon-button">Coupon abschicken</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
<section class="get-license">
|
||||||
|
<h2>Oder, kaufen Sie eine Lizenz</h2>
|
||||||
|
<ul class="license-links">
|
||||||
|
<li class="license-links__item"><a :href="teacherEditionUrl" class="hep-link">mySkillobx für Lehrpersonen</a></li>
|
||||||
|
<li class="license-links__item"><a :href="studentEditionUrl" class="hep-link">mySkillobx für Lernende</a></li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import me from '@/mixins/me';
|
||||||
|
import REDEEM_COUPON from '@/graphql/gql/mutations/redeemCoupon.gql';
|
||||||
|
import ME_QUERY from '@/graphql/gql/meQuery.gql';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [me],
|
||||||
|
methods: {
|
||||||
|
validateBeforeSubmit() {
|
||||||
|
this.$validator.validate().then(result => {
|
||||||
|
this.submitted = true;
|
||||||
|
let that = this;
|
||||||
|
if (result) {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: REDEEM_COUPON,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
couponCode: this.coupon
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update(
|
||||||
|
store,
|
||||||
|
{
|
||||||
|
data: {coupon}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
if (coupon.success) {
|
||||||
|
that.couponErrors = [];
|
||||||
|
that.$apollo.query({
|
||||||
|
query: ME_QUERY,
|
||||||
|
fetchPolicy: 'network-only',
|
||||||
|
}).then(() => that.$router.push('/'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).catch(({message}) => {
|
||||||
|
if (message.indexOf('invalid_coupon') > -1) {
|
||||||
|
that.couponErrors = ['Der angegebene Coupon-Code ist ungültig.'];
|
||||||
|
} else {
|
||||||
|
that.couponErrors = ['Es ist ein Fehler aufgetreten. Bitte versuchen Sie es nochmals oder kontaktieren Sie den Administrator.'];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resetForm() {
|
||||||
|
this.coupon = '';
|
||||||
|
this.submitted = false;
|
||||||
|
this.$validator.reset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
coupon: '',
|
||||||
|
couponErrors: [],
|
||||||
|
loginError: '',
|
||||||
|
submitted: false,
|
||||||
|
me: {
|
||||||
|
email: ''
|
||||||
|
},
|
||||||
|
teacherEditionUrl: `${process.env.HEP_URL}/myskillbox-lehrpersonen`,
|
||||||
|
studentEditionUrl: `${process.env.HEP_URL}/myskillbox-fur-lernende`
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import "@/styles/_variables.scss";
|
||||||
|
@import "@/styles/_mixins.scss";
|
||||||
|
|
||||||
|
.text-link {
|
||||||
|
font-family: $sans-serif-font-family;
|
||||||
|
color: $color-brand;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
&__reset {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: $large-spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.get-license {
|
||||||
|
margin-top: $large-spacing
|
||||||
|
}
|
||||||
|
|
||||||
|
.license-links {
|
||||||
|
&__item {
|
||||||
|
margin-bottom: $medium-spacing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
@ -1,33 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="login public-page">
|
<div class="login public-page">
|
||||||
<h1 class="login__title public-page__title">Melden Sie sich jetzt an</h1>
|
<header class="info-header">
|
||||||
|
<p class="info-header__text small-emph">Super wir haben für <span class="info-header__emph">{{helloEmail.email}}</span> ein Hep Konto gefunden</p>
|
||||||
|
<h1 class="login__title public-page__title" data-cy="login-title">Bitte geben Sie das passende Passwort ein</h1>
|
||||||
|
</header>
|
||||||
<form class="login__form login-form" novalidate @submit.prevent="validateBeforeSubmit">
|
<form class="login__form login-form" novalidate @submit.prevent="validateBeforeSubmit">
|
||||||
<div class="login-form__field skillboxform-input">
|
|
||||||
<label for="email" class="skillboxform-input__label">E-Mail</label>
|
|
||||||
<input
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
type="text"
|
|
||||||
v-model="email"
|
|
||||||
v-validate="'required'"
|
|
||||||
data-vv-as="E-Mail"
|
|
||||||
:class="{ 'skillboxform-input__input--error': errors.has('email') }"
|
|
||||||
class="change-form__email skillbox-input skillboxform-input__input"
|
|
||||||
autocomplete="off"
|
|
||||||
data-cy="email-input"
|
|
||||||
/>
|
|
||||||
<small
|
|
||||||
v-if="errors.has('email') && submitted"
|
|
||||||
class="skillboxform-input__error"
|
|
||||||
data-cy="email-local-errors"
|
|
||||||
>{{ errors.first('email') }}</small>
|
|
||||||
<small
|
|
||||||
v-for="error in emailErrors"
|
|
||||||
:key="error"
|
|
||||||
class="skillboxform-input__error"
|
|
||||||
data-cy="email-remote-errors"
|
|
||||||
>{{ error }}</small>
|
|
||||||
</div>
|
|
||||||
<div class="change-form__field skillboxform-input">
|
<div class="change-form__field skillboxform-input">
|
||||||
<label for="pw" class="skillboxform-input__label">Passwort</label>
|
<label for="pw" class="skillboxform-input__label">Passwort</label>
|
||||||
<input
|
<input
|
||||||
|
|
@ -41,37 +18,29 @@
|
||||||
class="change-form__new skillbox-input skillboxform-input__input"
|
class="change-form__new skillbox-input skillboxform-input__input"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
data-cy="password-input"
|
data-cy="password-input"
|
||||||
|
tabindex="0"
|
||||||
/>
|
/>
|
||||||
<small
|
|
||||||
v-if="errors.has('password') && submitted"
|
|
||||||
class="skillboxform-input__error"
|
|
||||||
data-cy="password-local-errors"
|
|
||||||
>{{ errors.first('password') }}</small>
|
|
||||||
<small
|
<small
|
||||||
v-for="error in passwordErrors"
|
v-for="error in passwordErrors"
|
||||||
:key="error"
|
:key="error"
|
||||||
class="skillboxform-input__error"
|
class="skillboxform-input__error"
|
||||||
data-cy="password-remote-errors"
|
data-cy="password-errors"
|
||||||
>{{ error }}</small>
|
>{{ error }}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="skillboxform-input">
|
|
||||||
<small class="skillboxform-input__error" data-cy="login-error" v-if="loginError">{{loginError}}</small>
|
|
||||||
</div>
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="button button--primary button--big actions__submit" data-cy="login-button">Anmelden</button>
|
<button class="button button--primary button--big actions__submit" data-cy="login-button">Anmelden</button>
|
||||||
<a class="actions__reset text-link" href="/accounts/password_reset/">Passwort vergessen?</a>
|
<router-link class="button button--big actions__submit back-button" :to="{name: 'hello'}">Abbrechen</router-link>
|
||||||
</div>
|
<router-link class="actions__reset text-link" :to="{name: 'forgotPassword'}">Passwort vergessen?</router-link>
|
||||||
<div class="account-link">
|
|
||||||
<p class="account-link__text">Haben Sie noch kein Konto?</p>
|
|
||||||
<router-link class="account-link__link text-link" :to="{name: 'registration'}">Jetzt registrieren
|
|
||||||
</router-link>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
import HELLO_EMAIL from '@/graphql/gql/local/helloEmail.gql';
|
||||||
import LOGIN_MUTATION from '@/graphql/gql/mutations/login.gql';
|
import LOGIN_MUTATION from '@/graphql/gql/mutations/login.gql';
|
||||||
|
import {login} from '@/hep-client/index';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {},
|
components: {},
|
||||||
|
|
@ -80,44 +49,73 @@ export default {
|
||||||
validateBeforeSubmit() {
|
validateBeforeSubmit() {
|
||||||
this.$validator.validate().then(result => {
|
this.$validator.validate().then(result => {
|
||||||
this.submitted = true;
|
this.submitted = true;
|
||||||
let that = this;
|
|
||||||
if (result) {
|
if (result) {
|
||||||
this.$apollo.mutate({
|
login(this.helloEmail.email, this.password)
|
||||||
client: 'publicClient',
|
.then((response) => {
|
||||||
mutation: LOGIN_MUTATION,
|
if (response.status === 200) {
|
||||||
variables: {
|
this.mySkillboxLogin(response.data);
|
||||||
input: {
|
} else {
|
||||||
usernameInput: this.email,
|
this.passwordErrors = ['Es ist ein Fehler aufgetreten. Bitte versuchen Sie nochmals.'];
|
||||||
passwordInput: this.password
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fetchPolicy: 'no-cache',
|
|
||||||
}).then(({data: {login}}) => {
|
|
||||||
try {
|
|
||||||
if (login.success) {
|
|
||||||
const redirectUrl = that.$route.query.redirect ? that.$route.query.redirect : '/'
|
|
||||||
that.$router.push(redirectUrl);
|
|
||||||
} else {
|
|
||||||
const firstError = login.errors[0];
|
|
||||||
switch (firstError.field) {
|
|
||||||
case 'invalid_credentials':
|
|
||||||
that.loginError = 'Die E-Mail oder das Passwort ist falsch. Bitte versuchen Sie nochmals.';
|
|
||||||
break;
|
|
||||||
case 'license_inactive':
|
|
||||||
that.loginError = 'Ihre Lizenz ist nicht mehr aktiv.';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(e);
|
|
||||||
that.loginError = 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie nochmals.';
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
if (error.response.data.message && error.response.data.message === 'Sie haben sich nicht korrekt eingeloggt oder Ihr Konto ist vor\u00fcbergehend deaktiviert.') {
|
||||||
|
this.passwordErrors = ['Sie haben sich nicht korrekt eingeloggt oder Ihr Konto ist vorübergehend deaktiviert.'];
|
||||||
|
} else {
|
||||||
|
this.passwordErrors = ['Es ist ein Fehler aufgetreten. Bitte versuchen Sie nochmals.'];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
mySkillboxLogin(token) {
|
||||||
|
const that = this;
|
||||||
|
this.$apollo.mutate({
|
||||||
|
client: 'publicClient',
|
||||||
|
mutation: LOGIN_MUTATION,
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
tokenInput: token
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update(
|
||||||
|
store,
|
||||||
|
{
|
||||||
|
data: {
|
||||||
|
login
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
if (login.success) {
|
||||||
|
if (login.message === 'no_valid_license') {
|
||||||
|
that.$router.push({name: 'licenseActivation'})
|
||||||
|
} else {
|
||||||
|
const redirectUrl = that.$route.query.redirect ? that.$route.query.redirect : '/';
|
||||||
|
that.$router.push(redirectUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
that.passwordErrors = ['Es ist ein Fehler aufgetreten. Bitte versuchen Sie nochmals.'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(errors => {
|
||||||
|
const firstError = errors.graphQLErrors[0];
|
||||||
|
switch (firstError.message) {
|
||||||
|
case 'invalid_credentials':
|
||||||
|
that.passwordErrors = ['Die E-Mail oder das Passwort ist falsch. Bitte versuchen Sie nochmals.'];
|
||||||
|
break;
|
||||||
|
case 'email_not_verified':
|
||||||
|
that.passwordErrors = ['Bitte verifiziere zuerst deine E-Mail.'];
|
||||||
|
break;
|
||||||
|
case 'no_valid_license':
|
||||||
|
this.$router.push({name: 'licenseActivation'})
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
resetForm() {
|
resetForm() {
|
||||||
this.email = '';
|
|
||||||
this.password = '';
|
this.password = '';
|
||||||
this.submitted = false;
|
this.submitted = false;
|
||||||
this.$validator.reset();
|
this.$validator.reset();
|
||||||
|
|
@ -126,14 +124,22 @@ export default {
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
email: '',
|
|
||||||
password: '',
|
password: '',
|
||||||
emailErrors: [],
|
|
||||||
passwordErrors: [],
|
passwordErrors: [],
|
||||||
loginError: '',
|
|
||||||
submitted: false
|
submitted: false
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
|
||||||
|
apollo: {
|
||||||
|
helloEmail: {
|
||||||
|
query: HELLO_EMAIL,
|
||||||
|
result({data: {helloEmail}}) {
|
||||||
|
if (helloEmail.email === '') {
|
||||||
|
this.$router.push({name: 'hello'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -147,10 +153,20 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions {
|
.actions {
|
||||||
|
display: flex;
|
||||||
&__reset {
|
&__reset {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: $large-spacing;
|
margin-left: auto;
|
||||||
|
line-height: 19px;;
|
||||||
|
display: inline-block;
|
||||||
|
padding: $small-spacing;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: normal;
|
||||||
|
margin-left: $medium-spacing;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,28 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="registration public-page">
|
<div class="registration public-page">
|
||||||
<h1 class="registration__title public-page__title">Registrieren Sie ihr persönliches Konto.</h1>
|
<header class="info-header">
|
||||||
|
<p class="info-header__text small-emph">Für <span class="info-header__emph">{{helloEmail}}</span> haben wir kein Hep Konto gefunden.</p>
|
||||||
|
<h1 class="registration__title public-page__title" data-cy="registration-title">Damit Sie mySkillbox verwenden können, müssen Sie ein Konto erstellen.</h1>
|
||||||
|
</header>
|
||||||
<form class="registration__form registration-form" novalidate @submit.prevent="validateBeforeSubmit">
|
<form class="registration__form registration-form" novalidate @submit.prevent="validateBeforeSubmit">
|
||||||
<div class="registration-form__field skillboxform-input">
|
<div class="registration-form__field skillboxform-input">
|
||||||
|
<div class="registration-form__field skillboxform-input">
|
||||||
|
<label for="prefix" class="skillboxform-input__label">Anrede</label>
|
||||||
|
<select
|
||||||
|
id="prefix"
|
||||||
|
name="prefix"
|
||||||
|
v-model="prefix"
|
||||||
|
data-vv-as="Prefix"
|
||||||
|
v-validate="'required'"
|
||||||
|
:class="{ 'skillboxform-input__input--error': errors.has('prefix') }"
|
||||||
|
class="change-form__prefix skillbox-input skillboxform-input__input skillbox-dropdown"
|
||||||
|
autocomplete="off"
|
||||||
|
data-cy="prefix-selection"
|
||||||
|
>
|
||||||
|
<option value="Herr" selected>Herr</option>
|
||||||
|
<option value="Frau">Frau</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<label for="firstname" class="skillboxform-input__label">Vorname</label>
|
<label for="firstname" class="skillboxform-input__label">Vorname</label>
|
||||||
<input
|
<input
|
||||||
id="firstname"
|
id="firstname"
|
||||||
|
|
@ -55,74 +75,162 @@
|
||||||
>{{ error }}</small>
|
>{{ error }}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="change-form__field skillboxform-input">
|
<div class="change-form__field skillboxform-input">
|
||||||
<label for="email" class="skillboxform-input__label">E-Mail</label>
|
<label for="street" class="skillboxform-input__label">Strasse</label>
|
||||||
<input
|
<input
|
||||||
id="email"
|
id="street"
|
||||||
name="email"
|
name="street"
|
||||||
type="text"
|
type="text"
|
||||||
v-model="email"
|
v-model="street"
|
||||||
data-vv-as="E-Mail"
|
data-vv-as="Strasse"
|
||||||
v-validate="'required|email'"
|
v-validate="'required'"
|
||||||
:class="{ 'skillboxform-input__input--error': errors.has('email') }"
|
:class="{ 'skillboxform-input__input--error': errors.has('street') }"
|
||||||
class="change-form__new skillbox-input skillboxform-input__input"
|
class="change-form__new skillbox-input skillboxform-input__input"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
data-cy="email-input"
|
data-cy="street-input"
|
||||||
/>
|
/>
|
||||||
<small
|
<small
|
||||||
v-if="errors.has('email') && submitted"
|
v-if="errors.has('street') && submitted"
|
||||||
class="skillboxform-input__error"
|
class="skillboxform-input__error"
|
||||||
data-cy="email-local-errors"
|
data-cy="street-local-errors"
|
||||||
>{{ errors.first('email') }}</small>
|
>{{ errors.first('street') }}</small>
|
||||||
<small
|
<small
|
||||||
v-for="error in emailErrors"
|
v-for="error in streetErrors"
|
||||||
:key="error"
|
:key="error"
|
||||||
class="skillboxform-input__error"
|
class="skillboxform-input__error"
|
||||||
data-cy="email-remote-errors"
|
data-cy="street-remote-errors"
|
||||||
>{{ error }}</small>
|
>{{ error }}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="change-form__field skillboxform-input">
|
<div class="change-form__field skillboxform-input">
|
||||||
<label for="licenseKey" class="skillboxform-input__label">Lizenz</label>
|
<label for="city" class="skillboxform-input__label">Ort</label>
|
||||||
<input
|
<input
|
||||||
id="licenseKey"
|
id="city"
|
||||||
name="licenseKey"
|
name="city"
|
||||||
type="text"
|
type="text"
|
||||||
v-model="licenseKey"
|
v-model="city"
|
||||||
data-vv-as="Lizenz"
|
data-vv-as="Ort"
|
||||||
v-validate="'required'"
|
v-validate="'required'"
|
||||||
:class="{ 'skillboxform-input__input--error': errors.has('licenseKey') }"
|
:class="{ 'skillboxform-input__input--error': errors.has('city') }"
|
||||||
class="change-form__new skillbox-input skillboxform-input__input"
|
class="change-form__new skillbox-input skillboxform-input__input"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
data-cy="licenseKey-input"
|
data-cy="city-input"
|
||||||
/>
|
/>
|
||||||
<small
|
<small
|
||||||
v-if="errors.has('licenseKey') && submitted"
|
v-if="errors.has('city') && submitted"
|
||||||
class="skillboxform-input__error"
|
class="skillboxform-input__error"
|
||||||
data-cy="licenseKey-local-errors"
|
data-cy="city-local-errors"
|
||||||
>{{ errors.first('licenseKey') }}</small>
|
>{{ errors.first('city') }}</small>
|
||||||
<small
|
<small
|
||||||
v-for="error in licenseKeyErrors"
|
v-for="error in cityErrors"
|
||||||
:key="error"
|
:key="error"
|
||||||
class="skillboxform-input__error"
|
class="skillboxform-input__error"
|
||||||
data-cy="licenseKey-remote-errors"
|
data-cy="city-remote-errors"
|
||||||
>{{ error }}</small>
|
>{{ error }}</small>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="change-form__field skillboxform-input">
|
||||||
|
<label for="postcode" class="skillboxform-input__label">Postleitzahl</label>
|
||||||
|
<input
|
||||||
|
id="postcode"
|
||||||
|
name="postcode"
|
||||||
|
type="text"
|
||||||
|
v-model="postcode"
|
||||||
|
data-vv-as="Postleitzahl"
|
||||||
|
v-validate="'required'"
|
||||||
|
:class="{ 'skillboxform-input__input--error': errors.has('postcode') }"
|
||||||
|
class="change-form__new skillbox-input skillboxform-input__input"
|
||||||
|
autocomplete="off"
|
||||||
|
data-cy="postcode-input"
|
||||||
|
/>
|
||||||
|
<small
|
||||||
|
v-if="errors.has('postcode') && submitted"
|
||||||
|
class="skillboxform-input__error"
|
||||||
|
data-cy="postcode-local-errors"
|
||||||
|
>{{ errors.first('postcode') }}</small>
|
||||||
|
<small
|
||||||
|
v-for="error in postcodeErrors"
|
||||||
|
:key="error"
|
||||||
|
class="skillboxform-input__error"
|
||||||
|
data-cy="postcode-remote-errors"
|
||||||
|
>{{ error }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="change-form__field skillboxform-input">
|
||||||
|
<label for="password" class="skillboxform-input__label">Passwort</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="text"
|
||||||
|
v-model="password"
|
||||||
|
data-vv-as="Passwort"
|
||||||
|
v-validate="'required|strongPassword'"
|
||||||
|
:class="{ 'skillboxform-input__input--error': errors.has('password') && submitted }"
|
||||||
|
class="change-form__new skillbox-input skillboxform-input__input"
|
||||||
|
autocomplete="off"
|
||||||
|
data-cy="password-input"
|
||||||
|
ref="password"
|
||||||
|
/>
|
||||||
|
<small
|
||||||
|
v-if="errors.has('password') && submitted"
|
||||||
|
class="skillboxform-input__error"
|
||||||
|
data-cy="password-local-errors"
|
||||||
|
>{{ errors.first('password') }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="change-form__field skillboxform-input">
|
||||||
|
<label for="password2" class="skillboxform-input__label">Passwort wiederholen</label>
|
||||||
|
<input
|
||||||
|
id="passwordConfirmation"
|
||||||
|
name="passwordConfirmation"
|
||||||
|
type="text"
|
||||||
|
v-model="passwordConfirmation"
|
||||||
|
data-vv-as="Passwort wiederholen"
|
||||||
|
v-validate="'required|confirmed:password'"
|
||||||
|
:class="{ 'skillboxform-input__input--error': errors.has('passwordConfirmation') && submitted }"
|
||||||
|
class="change-form__new skillbox-input skillboxform-input__input"
|
||||||
|
autocomplete="off"
|
||||||
|
data-cy="passwordConfirmation-input"
|
||||||
|
/>
|
||||||
|
<small
|
||||||
|
v-if="errors.has('passwordConfirmation') && submitted"
|
||||||
|
class="skillboxform-input__error"
|
||||||
|
data-cy="passwordConfirmation-local-errors"
|
||||||
|
>{{ errors.first('passwordConfirmation') }}</small>
|
||||||
|
</div>
|
||||||
<div class="skillboxform-input">
|
<div class="skillboxform-input">
|
||||||
<small class="skillboxform-input__error" data-cy="registration-error" v-if="registrationError">{{registrationError}}</small>
|
<small class="skillboxform-input__error" data-cy="registration-error" v-if="registrationError">{{registrationError}}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="button button--primary button--big actions__submit" data-cy="register-button">Jetzt registrieren</button>
|
<button class="button button--primary button--big actions__submit" data-cy="register-button">Konto erstellen</button>
|
||||||
</div>
|
|
||||||
<div class="account-link">
|
|
||||||
<p class="account-link__text">Haben Sie ein Konto?</p>
|
|
||||||
<router-link class="account-link__link text-link" :to="{name: 'login'}">Jetzt anmelden
|
|
||||||
</router-link>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import REGISTRATION_MUTATION from '@/graphql/gql/mutations/registration.gql';
|
|
||||||
|
import {register} from '../hep-client/index'
|
||||||
|
import HELLO_EMAIL from '@/graphql/gql/local/helloEmail.gql';
|
||||||
|
|
||||||
|
function initialData() {
|
||||||
|
return {
|
||||||
|
prefix: 'Herr',
|
||||||
|
lastname: '',
|
||||||
|
firstname: '',
|
||||||
|
password: '',
|
||||||
|
passwordConfirmation: '',
|
||||||
|
street: '',
|
||||||
|
postcode: '',
|
||||||
|
city: '',
|
||||||
|
firstnameErrors: '',
|
||||||
|
lastnameErrors: '',
|
||||||
|
emailErrors: '',
|
||||||
|
passwordsErrors: [],
|
||||||
|
passwordErrors: [],
|
||||||
|
streetErrors: [],
|
||||||
|
cityErrors: [],
|
||||||
|
postcodeErrors: [],
|
||||||
|
registrationError: '',
|
||||||
|
submitted: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {},
|
components: {},
|
||||||
|
|
@ -131,73 +239,78 @@ export default {
|
||||||
validateBeforeSubmit() {
|
validateBeforeSubmit() {
|
||||||
this.$validator.validate().then(result => {
|
this.$validator.validate().then(result => {
|
||||||
this.submitted = true;
|
this.submitted = true;
|
||||||
let that = this;
|
|
||||||
if (result) {
|
if (result) {
|
||||||
this.$apollo.mutate({
|
const registrationData = {
|
||||||
client: 'publicClient',
|
customer: {
|
||||||
mutation: REGISTRATION_MUTATION,
|
prefix: this.prefix,
|
||||||
variables: {
|
email: this.helloEmail,
|
||||||
input: {
|
firstname: this.firstname,
|
||||||
firstnameInput: this.firstname,
|
lastname: this.lastname,
|
||||||
lastnameInput: this.lastname,
|
gender: this.prefix === 'Herr' ? 1 : 2,
|
||||||
emailInput: this.email,
|
addresses: [{
|
||||||
licenseKeyInput: this.licenseKey,
|
street: [this.street],
|
||||||
}
|
postcode: this.postcode,
|
||||||
|
city: this.city,
|
||||||
|
country_id: 'CH',
|
||||||
|
firstname: this.firstname,
|
||||||
|
lastname: this.lastname,
|
||||||
|
prefix: this.prefix,
|
||||||
|
default_shipping: true,
|
||||||
|
default_billing: true
|
||||||
|
}]
|
||||||
},
|
},
|
||||||
fetchPolicy: 'no-cache'
|
password: this.password
|
||||||
|
};
|
||||||
|
|
||||||
|
register(registrationData).then((response) => {
|
||||||
|
if (response.data.id && response.data.id > 0) {
|
||||||
|
this.$router.push({name: 'checkEmail'});
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.then(({data: {registration: { success, errors }}}) => {
|
.catch((error) => {
|
||||||
try {
|
console.warn(error);
|
||||||
if (success) {
|
if (error.response.data.message && error.response.data.message === 'Ein Kunde mit der gleichen E-Mail-Adresse existiert bereits in einer zugeordneten Website.') {
|
||||||
window.location.href = '/registration/set-password/done/';
|
this.emailErrors = ['Die angegebene E-Mail ist bereits registriert.'];
|
||||||
} else {
|
} else {
|
||||||
errors.forEach(function(error) {
|
this.registrationError = 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie nochmals.';
|
||||||
switch (error.field) {
|
|
||||||
case 'email':
|
|
||||||
that.emailErrors = ['Die angegebene E-Mail ist bereits registriert.'];
|
|
||||||
break;
|
|
||||||
case 'license_key':
|
|
||||||
that.licenseKeyErrors = ['Die angegebenen Lizenz ist unglültig'];
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(e);
|
|
||||||
that.registrationError = 'Es ist ein Fehler aufgetreten. Bitte versuchen Sie nochmals.';
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
resetForm() {
|
resetForm() {
|
||||||
this.email = '';
|
Object.assign(this.$data, initialData());
|
||||||
this.lastname = '';
|
|
||||||
this.firstname = '';
|
|
||||||
this.licenseKey = '';
|
|
||||||
this.firstnameErrors = '';
|
|
||||||
this.lastnameErrors = '';
|
|
||||||
this.emailErrors = '';
|
|
||||||
this.licenseKeyErrors = '';
|
|
||||||
this.registrationError = '';
|
|
||||||
this.submitted = false;
|
|
||||||
this.$validator.reset();
|
this.$validator.reset();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return Object.assign(
|
||||||
email: '',
|
{
|
||||||
lastname: '',
|
helloEmail: ''
|
||||||
firstname: '',
|
},
|
||||||
licenseKey: '',
|
initialData()
|
||||||
firstnameErrors: '',
|
);
|
||||||
lastnameErrors: '',
|
},
|
||||||
emailErrors: '',
|
|
||||||
licenseKeyErrors: '',
|
apollo: {
|
||||||
registrationError: '',
|
helloEmail: {
|
||||||
submitted: false
|
query: HELLO_EMAIL,
|
||||||
};
|
result({data}) {
|
||||||
}
|
if (data.helloEmail && data.helloEmail.email === '') {
|
||||||
|
this.$router.push({name: 'hello'});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update(data) {
|
||||||
|
return data.helloEmail.email;
|
||||||
|
},
|
||||||
|
error() {
|
||||||
|
console.log('error')
|
||||||
|
this.$router.push({name: 'hello'});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
class="start-sections__section"
|
class="start-sections__section"
|
||||||
title="Räume"
|
title="Räume"
|
||||||
subtitle="Beiträge mit der Klasse teilen"
|
subtitle="Beiträge mit der Klasse teilen"
|
||||||
|
data-cy="rooms-link"
|
||||||
link-text="Alle Räume anzeigen"
|
link-text="Alle Räume anzeigen"
|
||||||
route="/rooms"
|
route="/rooms"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="no-class public-page">
|
|
||||||
<h1 class="public-page__title">Sie sind keiner Klasse zugeteilt</h1>
|
|
||||||
<p>Sie können mySkillbox nur verwenden wenn Sie in einer Klasse zugeteilt sind. Aktuell kann Sie nur der mySkillbox-Support einer Klasse zuteilen.</p>
|
|
||||||
<button class="button button--primary button--big logout-button" @click="logout">Abmelden</button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import LOGOUT_MUTATION from '@/graphql/gql/mutations/logoutUser.gql';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
logout() {
|
|
||||||
this.$apollo.mutate({
|
|
||||||
mutation: LOGOUT_MUTATION,
|
|
||||||
}).then(({data}) => {
|
|
||||||
if (data.logout.success) { location.replace('/') }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@import "@/styles/_variables.scss";
|
|
||||||
|
|
||||||
.logout-button {
|
|
||||||
margin-top: $large-spacing;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -28,8 +28,13 @@ import surveyPage from '@/pages/survey'
|
||||||
import styleGuidePage from '@/pages/styleguide'
|
import styleGuidePage from '@/pages/styleguide'
|
||||||
import moduleRoom from '@/pages/moduleRoom'
|
import moduleRoom from '@/pages/moduleRoom'
|
||||||
import login from '@/pages/login'
|
import login from '@/pages/login'
|
||||||
|
import betaLogin from '@/pages/beta-login'
|
||||||
|
import hello from '@/pages/hello'
|
||||||
import registration from '@/pages/registration'
|
import registration from '@/pages/registration'
|
||||||
import waitForClass from '@/pages/waitForClass'
|
import checkEmail from '@/pages/check-email'
|
||||||
|
import emailVerification from '@/pages/email-verification'
|
||||||
|
import licenseActivation from '@/pages/license-activation'
|
||||||
|
import forgotPassword from '@/pages/forgot-password'
|
||||||
import joinClass from '@/pages/joinClass'
|
import joinClass from '@/pages/joinClass'
|
||||||
import oldClasses from '@/pages/oldClasses';
|
import oldClasses from '@/pages/oldClasses';
|
||||||
import createClass from '@/pages/createClass';
|
import createClass from '@/pages/createClass';
|
||||||
|
|
@ -53,6 +58,24 @@ const routes = [
|
||||||
public: true
|
public: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/hello',
|
||||||
|
name: 'hello',
|
||||||
|
component: hello,
|
||||||
|
meta: {
|
||||||
|
layout: 'public',
|
||||||
|
public: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/beta-login',
|
||||||
|
name: 'betaLogin',
|
||||||
|
component: betaLogin,
|
||||||
|
meta: {
|
||||||
|
layout: 'public',
|
||||||
|
public: true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/module/:slug',
|
path: '/module/:slug',
|
||||||
component: moduleBase,
|
component: moduleBase,
|
||||||
|
|
@ -124,7 +147,7 @@ const routes = [
|
||||||
{path: 'show-code', name: 'show-code', component: showCode, meta: {layout: 'simple'}},
|
{path: 'show-code', name: 'show-code', component: showCode, meta: {layout: 'simple'}},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{path: 'join-class', name: 'join-class', component: joinClass, meta: {layout: 'simple'}},
|
{path: 'join-class', name: 'join-class', component: joinClass, meta: {layout: 'public'}},
|
||||||
{
|
{
|
||||||
path: '/survey/:id',
|
path: '/survey/:id',
|
||||||
component: surveyPage,
|
component: surveyPage,
|
||||||
|
|
@ -142,10 +165,39 @@ const routes = [
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/no-class',
|
path: '/check-email',
|
||||||
component: waitForClass,
|
component: checkEmail,
|
||||||
name: 'noClass',
|
name: 'checkEmail',
|
||||||
meta: {layout: 'public'}
|
meta: {
|
||||||
|
public: true,
|
||||||
|
layout: 'public'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/verify-email',
|
||||||
|
component: emailVerification,
|
||||||
|
name: 'emailVerification',
|
||||||
|
meta: {
|
||||||
|
public: true,
|
||||||
|
layout: 'public'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/license-activation',
|
||||||
|
component: licenseActivation,
|
||||||
|
name: 'licenseActivation',
|
||||||
|
meta: {
|
||||||
|
layout: 'public'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/forgot-password',
|
||||||
|
component: forgotPassword,
|
||||||
|
name: 'forgotPassword',
|
||||||
|
meta: {
|
||||||
|
layout: 'public',
|
||||||
|
public: true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{path: '/styleguide', component: styleGuidePage},
|
{path: '/styleguide', component: styleGuidePage},
|
||||||
{path: '*', component: p404}
|
{path: '*', component: p404}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
.info-header {
|
||||||
|
margin-top: $large-spacing;
|
||||||
|
margin-bottom: $large-spacing;
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
font-family: $sans-serif-font-family;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__emph {
|
||||||
|
color: $color-brand;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -176,3 +176,12 @@
|
||||||
@mixin popover-defaults() {
|
@mixin popover-defaults() {
|
||||||
bottom: $popover-default-bottom;
|
bottom: $popover-default-bottom;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin content-block {
|
||||||
|
padding-right: $medium-spacing;
|
||||||
|
padding-left: $medium-spacing;
|
||||||
|
max-width: 800px;
|
||||||
|
min-width: 320px;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
&__label {
|
&__label {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__input {
|
&__input {
|
||||||
|
|
|
||||||
|
|
@ -75,3 +75,8 @@ input, textarea, select, button {
|
||||||
.inline-title {
|
.inline-title {
|
||||||
@include inline-title;
|
@include inline-title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hep-link {
|
||||||
|
font-family: $sans-serif-font-family;
|
||||||
|
color: $color-brand-dark;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
@import "public-page";
|
@import "public-page";
|
||||||
@import "student-submission";
|
@import "student-submission";
|
||||||
@import "module-activity";
|
@import "module-activity";
|
||||||
|
@import "info-header";
|
||||||
@import "book-subnavigation";
|
@import "book-subnavigation";
|
||||||
@import "simple-list";
|
@import "simple-list";
|
||||||
@import "widget-popover";
|
@import "widget-popover";
|
||||||
|
|
|
||||||
|
|
@ -8,5 +8,5 @@ module.exports = {
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ from assignments.schema.queries import AssignmentsQuery, StudentSubmissionQuery
|
||||||
from basicknowledge.queries import BasicKnowledgeQuery
|
from basicknowledge.queries import BasicKnowledgeQuery
|
||||||
from books.schema.mutations.main import BookMutations
|
from books.schema.mutations.main import BookMutations
|
||||||
from books.schema.queries import BookQuery
|
from books.schema.queries import BookQuery
|
||||||
|
from core.schema.mutations.coupon import CouponMutations
|
||||||
from core.schema.mutations.main import CoreMutations
|
from core.schema.mutations.main import CoreMutations
|
||||||
from notes.mutations import NoteMutations
|
from notes.mutations import NoteMutations
|
||||||
from objectives.mutations import ObjectiveMutations
|
from objectives.mutations import ObjectiveMutations
|
||||||
|
|
@ -36,7 +37,7 @@ class Query(UsersQuery, AllUsersQuery, ModuleRoomsQuery, RoomsQuery, ObjectivesQ
|
||||||
|
|
||||||
class Mutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, CoreMutations, PortfolioMutations,
|
class Mutation(BookMutations, RoomMutations, AssignmentMutations, ObjectiveMutations, CoreMutations, PortfolioMutations,
|
||||||
ProfileMutations, SurveyMutations, NoteMutations, RegistrationMutations, SpellCheckMutations,
|
ProfileMutations, SurveyMutations, NoteMutations, RegistrationMutations, SpellCheckMutations,
|
||||||
graphene.ObjectType):
|
CouponMutations, graphene.ObjectType):
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
debug = graphene.Field(DjangoDebug, name='_debug')
|
debug = graphene.Field(DjangoDebug, name='_debug')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,4 +12,16 @@ class Mutation(UserMutations, RegistrationMutations, graphene.ObjectType):
|
||||||
debug = graphene.Field(DjangoDebug, name='__debug')
|
debug = graphene.Field(DjangoDebug, name='__debug')
|
||||||
|
|
||||||
|
|
||||||
schema = graphene.Schema(mutation=Mutation)
|
# graphene neets some kind of schema in order to create a schema
|
||||||
|
class DummyQuery(object):
|
||||||
|
meaning_of_life = graphene.Int()
|
||||||
|
|
||||||
|
def resolve_meaning_of_life(self, info, **kwargs):
|
||||||
|
return 42
|
||||||
|
|
||||||
|
|
||||||
|
class Query(DummyQuery, graphene.ObjectType):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
schema = graphene.Schema(mutation=Mutation, query=Query)
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,26 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
from django.urls import include
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from graphene_django.views import GraphQLView
|
from graphene_django.views import GraphQLView
|
||||||
|
|
||||||
from api.schema_public import schema
|
from api.schema_public import schema
|
||||||
|
|
||||||
from core.views import PrivateGraphQLView
|
from core.views import PrivateGraphQLView, ConfirmationKeyDisplayView
|
||||||
|
|
||||||
app_name = 'api'
|
app_name = 'api'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^graphql-public', csrf_exempt(GraphQLView.as_view(schema=schema))),
|
url(r'^graphql-public', csrf_exempt(GraphQLView.as_view(schema=schema))),
|
||||||
url(r'^graphql', csrf_exempt(PrivateGraphQLView.as_view())),
|
url(r'^graphql', csrf_exempt(PrivateGraphQLView.as_view())),
|
||||||
|
|
||||||
|
# hep proxy
|
||||||
|
url(r'^proxy/', include('registration.urls', namespace="registration")),
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
urlpatterns += [url(r'^graphiql-public', csrf_exempt(GraphQLView.as_view(schema=schema, graphiql=True,
|
urlpatterns += [url(r'^graphiql-public', csrf_exempt(GraphQLView.as_view(schema=schema, graphiql=True,
|
||||||
pretty=True)))]
|
pretty=True)))]
|
||||||
urlpatterns += [url(r'^graphiql', csrf_exempt(PrivateGraphQLView.as_view(graphiql=True, pretty=True)))]
|
urlpatterns += [url(r'^graphiql', csrf_exempt(PrivateGraphQLView.as_view(graphiql=True, pretty=True)))]
|
||||||
|
urlpatterns += [url(r'^confirmation', ConfirmationKeyDisplayView.as_view(), name='confirmation_key_display')]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,228 @@
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
TEACHER_EDITION_DURATION = 365
|
||||||
|
STUDENT_EDITION_DURATION = 4*365
|
||||||
|
|
||||||
|
TEACHER_KEY = 'teacher'
|
||||||
|
STUDENT_KEY = 'student'
|
||||||
|
|
||||||
|
MYSKILLBOX_TEACHER_EDITION_ISBN = "978-3-0355-1823-8"
|
||||||
|
MYSKILLBOX_STUDENT_EDITION_ISBN = "978-3-0355-1397-4"
|
||||||
|
|
||||||
|
|
||||||
|
class HepClientException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class HepClientUnauthorizedException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class HepClient:
|
||||||
|
URL = settings.HEP_URL
|
||||||
|
WEBSITE_ID = 1
|
||||||
|
HEADERS = {
|
||||||
|
'accept': 'application/json',
|
||||||
|
'content-type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
def _call(self, url, method='get', data=None, additional_headers=None):
|
||||||
|
|
||||||
|
request_url = f'{self.URL}{url}'
|
||||||
|
|
||||||
|
if additional_headers:
|
||||||
|
headers = {**additional_headers, **self.HEADERS}
|
||||||
|
else:
|
||||||
|
headers = self.HEADERS
|
||||||
|
|
||||||
|
if method == 'post':
|
||||||
|
response = requests.post(request_url, json=data, headers=headers)
|
||||||
|
elif method == 'get':
|
||||||
|
if data:
|
||||||
|
response = requests.get(request_url, headers=headers, data=data)
|
||||||
|
else:
|
||||||
|
response = requests.get(request_url, headers=headers)
|
||||||
|
elif method == 'put':
|
||||||
|
response = requests.put(request_url, data=data)
|
||||||
|
|
||||||
|
# Todo handle 401 and most important network errors
|
||||||
|
if response.status_code == 401:
|
||||||
|
raise HepClientUnauthorizedException(response.status_code, response.json())
|
||||||
|
elif response.status_code != 200:
|
||||||
|
raise HepClientException(response.status_code, response.json())
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
def fetch_admin_token(self, admin_user, password):
|
||||||
|
response = self._call('/rest/deutsch/V1/integration/admin/token', 'post',
|
||||||
|
data={'username': admin_user, 'password': password})
|
||||||
|
return response.content.decode('utf-8')[1:-1]
|
||||||
|
|
||||||
|
def is_email_available(self, email):
|
||||||
|
response = self._call('/rest/deutsch/V1/customers/isEmailAvailable', method='post',
|
||||||
|
data={'customerEmail': email, 'websiteId': self.WEBSITE_ID})
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def is_email_verified(self, user_data):
|
||||||
|
return 'confirmation' not in user_data
|
||||||
|
|
||||||
|
def customer_verify_email(self, confirmation_key):
|
||||||
|
response = self._call('/rest/V1/customers/me', method='put', data={'confirmationKey': confirmation_key})
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def customer_create(self, customer_data):
|
||||||
|
response = self._call('/rest/deutsch/V1/customers', method='post', data=customer_data)
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def customer_token(self, username, password):
|
||||||
|
response = self._call('/rest/deutsch/V1/integration/customer/token', 'post',
|
||||||
|
data={'username': username, 'password': password})
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def customer_me(self, token):
|
||||||
|
response = self._call('/rest/V1/customers/me', additional_headers={'authorization': f'Bearer {token}'})
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def customer_activate(self, confirmation_key, user_id):
|
||||||
|
response = self._call(f'/customer/account/confirm/?back_url=&id={user_id}&key={confirmation_key}', method='get')
|
||||||
|
return response
|
||||||
|
|
||||||
|
def customers_search(self, admin_token, email):
|
||||||
|
response = self._call('/rest/V1/customers/search?searchCriteria[filterGroups][0][filters][0][field]='
|
||||||
|
f'email&searchCriteria[filterGroups][0][filters][0][value]={email}',
|
||||||
|
additional_headers={'authorization': f'Bearer {admin_token}'})
|
||||||
|
|
||||||
|
json_data = response.json()
|
||||||
|
if len(json_data['items']) > 0:
|
||||||
|
return json_data['items'][0]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def customers_by_id(self, admin_token, user_id):
|
||||||
|
response = self._call('/rest/V1/customers/{}'.format(user_id),
|
||||||
|
additional_headers={'authorization': f'Bearer {admin_token}'})
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def _customer_orders(self, admin_token, customer_id):
|
||||||
|
url = ('/rest/V1/orders/?searchCriteria[filterGroups][0][filters][0]['
|
||||||
|
f'field]=customer_id&searchCriteria[filterGroups][0][filters][0][value]={customer_id}')
|
||||||
|
|
||||||
|
response = self._call(url, additional_headers={'authorization': 'Bearer {}'.format(admin_token)})
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
def coupon_redeem(self, coupon, customer_id):
|
||||||
|
try:
|
||||||
|
response = self._call(f'/rest/deutsch/V1/coupon/{coupon}/customer/{customer_id}', method='put')
|
||||||
|
except HepClientException:
|
||||||
|
return None
|
||||||
|
|
||||||
|
response_data = response.json()
|
||||||
|
if response_data[0] == '201':
|
||||||
|
return None
|
||||||
|
|
||||||
|
return response_data[0]
|
||||||
|
|
||||||
|
def myskillbox_product_for_customer(self, admin_token, customer_id):
|
||||||
|
orders = self._customer_orders(admin_token, customer_id)
|
||||||
|
products = self._extract_myskillbox_products(orders)
|
||||||
|
|
||||||
|
if len(products) == 0:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return self._get_relevant_product(products)
|
||||||
|
|
||||||
|
def _extract_myskillbox_products(self, orders):
|
||||||
|
products = []
|
||||||
|
|
||||||
|
for order_item in orders['items']:
|
||||||
|
|
||||||
|
status = ''
|
||||||
|
if 'status' in order_item:
|
||||||
|
status = order_item['status']
|
||||||
|
|
||||||
|
for item in order_item['items']:
|
||||||
|
|
||||||
|
order_id = -1
|
||||||
|
if 'order_id' in item:
|
||||||
|
order_id = item['order_id']
|
||||||
|
|
||||||
|
if item['sku'] == MYSKILLBOX_TEACHER_EDITION_ISBN or \
|
||||||
|
item['sku'] == MYSKILLBOX_STUDENT_EDITION_ISBN:
|
||||||
|
|
||||||
|
product = {
|
||||||
|
'raw': item,
|
||||||
|
'activated': self._get_item_activation(order_item),
|
||||||
|
'status': status,
|
||||||
|
'order_id': order_id
|
||||||
|
}
|
||||||
|
|
||||||
|
if item['sku'] == MYSKILLBOX_TEACHER_EDITION_ISBN:
|
||||||
|
product['edition'] = TEACHER_KEY
|
||||||
|
|
||||||
|
else:
|
||||||
|
product['edition'] = STUDENT_KEY
|
||||||
|
|
||||||
|
products.append(product)
|
||||||
|
|
||||||
|
return products
|
||||||
|
|
||||||
|
def _get_item_activation(self, item):
|
||||||
|
if 'created_at' in item:
|
||||||
|
return datetime.strptime(item['created_at'], '%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
def _get_relevant_product(self, products):
|
||||||
|
|
||||||
|
def filter_valid_products(product):
|
||||||
|
|
||||||
|
if product['status'] != 'complete':
|
||||||
|
return False
|
||||||
|
|
||||||
|
if product['edition'] == TEACHER_KEY:
|
||||||
|
expiry_delta = product['activated'] + timedelta(TEACHER_EDITION_DURATION)
|
||||||
|
else:
|
||||||
|
expiry_delta = product['activated'] + timedelta(STUDENT_EDITION_DURATION)
|
||||||
|
|
||||||
|
if HepClient.is_product_active(expiry_delta, product['edition']):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
active_products = list(filter(filter_valid_products, products))
|
||||||
|
|
||||||
|
if len(active_products) == 0:
|
||||||
|
return None
|
||||||
|
elif len(active_products) == 1:
|
||||||
|
return active_products[0]
|
||||||
|
else:
|
||||||
|
return self._select_from_teacher_products(active_products)
|
||||||
|
|
||||||
|
def _select_from_teacher_products(self, active_products):
|
||||||
|
teacher_edition = None
|
||||||
|
|
||||||
|
# select first teacher product, as they are all valid it does not matter which one
|
||||||
|
for product in active_products:
|
||||||
|
if product['edition'] == TEACHER_KEY:
|
||||||
|
teacher_edition = product
|
||||||
|
break
|
||||||
|
|
||||||
|
# select a student product, as they are all valid it does not matter which one
|
||||||
|
if not teacher_edition:
|
||||||
|
return active_products[0]
|
||||||
|
|
||||||
|
return teacher_edition
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def is_product_active(expiry_date, edition):
|
||||||
|
if edition == TEACHER_KEY:
|
||||||
|
duration = TEACHER_EDITION_DURATION
|
||||||
|
else:
|
||||||
|
duration = STUDENT_EDITION_DURATION
|
||||||
|
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
|
return expiry_date >= now >= expiry_date - timedelta(days=duration)
|
||||||
|
|
@ -113,5 +113,3 @@ class Command(BaseCommand):
|
||||||
# now create all and rooms
|
# now create all and rooms
|
||||||
management.call_command('dummy_rooms', verbosity=0)
|
management.call_command('dummy_rooms', verbosity=0)
|
||||||
|
|
||||||
# create license
|
|
||||||
management.call_command('create_dummy_license', verbosity=0)
|
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,23 @@ import os
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
filename = 'schema.json'
|
schemas = [
|
||||||
|
{
|
||||||
|
'filename': 'schema.json',
|
||||||
|
'schema': 'api.schema.schema'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'filename': 'schema_public.json',
|
||||||
|
'schema': 'api.schema_public.schema'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
for schema in schemas:
|
||||||
|
self.create_schema(schema['filename'], schema['schema'])
|
||||||
|
|
||||||
|
def create_schema(self, filename, schema):
|
||||||
cypress_path = os.path.join(settings.BASE_DIR, '..', 'client', 'cypress', 'fixtures', filename)
|
cypress_path = os.path.join(settings.BASE_DIR, '..', 'client', 'cypress', 'fixtures', filename)
|
||||||
call_command('graphql_schema', schema='api.schema.schema', out=filename, indent=4)
|
call_command('graphql_schema', schema=schema, out=filename, indent=4)
|
||||||
with open(filename) as f:
|
with open(filename) as f:
|
||||||
initial_json = json.loads(f.read())
|
initial_json = json.loads(f.read())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
|
||||||
|
from core.models import AdminData
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
"Update admin token via cronjob"
|
||||||
|
AdminData.objects.update_admin_token()
|
||||||
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import models
|
||||||
|
from datetime import timedelta
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from core.hep_client import HepClient
|
||||||
|
|
||||||
|
DEFAULT_PK = 1
|
||||||
|
|
||||||
|
|
||||||
|
class AdminDataManager(models.Manager):
|
||||||
|
hep_client = HepClient()
|
||||||
|
|
||||||
|
def update_admin_token(self):
|
||||||
|
admin_token = self.hep_client.fetch_admin_token(settings.HEP_ADMIN_USER, settings.HEP_ADMIN_PASSWORD)
|
||||||
|
|
||||||
|
admin_data, created = self.get_or_create(pk=DEFAULT_PK)
|
||||||
|
admin_data.hep_admin_token = admin_token
|
||||||
|
admin_data.save()
|
||||||
|
return admin_data.hep_admin_token
|
||||||
|
|
||||||
|
def get_admin_token(self):
|
||||||
|
try:
|
||||||
|
admin_token = self.get(pk=DEFAULT_PK)
|
||||||
|
if admin_token.updated_at < timezone.now() + timedelta(hours=1):
|
||||||
|
admin_token = self.update_admin_token()
|
||||||
|
except self.model.DoesNotExist:
|
||||||
|
admin_token = self.update_admin_token()
|
||||||
|
|
||||||
|
return admin_token
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
|
import json
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import Http404, HttpResponsePermanentRedirect
|
from django.http import Http404, HttpResponsePermanentRedirect, HttpResponse
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.utils.deprecation import MiddlewareMixin
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
|
|
||||||
|
from core.utils import is_private_api_call_allowed
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from threading import local
|
from threading import local
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
@ -95,3 +98,13 @@ class UserLoggedInCookieMiddleWare(MiddlewareMixin):
|
||||||
#else if if no user and cookie remove user cookie, logout
|
#else if if no user and cookie remove user cookie, logout
|
||||||
response.delete_cookie(self.cookie_name)
|
response.delete_cookie(self.cookie_name)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class UserHasLicenseMiddleWare(MiddlewareMixin):
|
||||||
|
|
||||||
|
def process_response(self, request, response):
|
||||||
|
if request.path == '/api/graphql/':
|
||||||
|
if not is_private_api_call_allowed(request.user, request.body):
|
||||||
|
return HttpResponse(json.dumps({'errors': ['no active license']}), status=402)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Generated by Django 2.0.6 on 2020-02-05 13:56
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AdminData',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('hep_admin_token', models.CharField(max_length=100)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from core.managers import AdminDataManager
|
||||||
|
|
||||||
|
|
||||||
|
class AdminData(models.Model):
|
||||||
|
hep_admin_token = models.CharField(max_length=100, blank=False, null=False)
|
||||||
|
updated_at = models.DateTimeField(blank=False, null=True, auto_now=True)
|
||||||
|
|
||||||
|
objects = AdminDataManager()
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import graphene
|
||||||
|
from graphene import relay
|
||||||
|
|
||||||
|
from core.hep_client import HepClient, HepClientException
|
||||||
|
from users.user_signup_login_handler import check_and_create_licenses, create_role_for_user
|
||||||
|
|
||||||
|
|
||||||
|
class Coupon(relay.ClientIDMutation):
|
||||||
|
class Input:
|
||||||
|
coupon_code = graphene.String()
|
||||||
|
|
||||||
|
success = graphene.Boolean()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||||
|
coupon_code = kwargs.get('coupon_code').strip()
|
||||||
|
hep_client = HepClient()
|
||||||
|
|
||||||
|
try:
|
||||||
|
hep_id = info.context.user.hep_id
|
||||||
|
except AttributeError:
|
||||||
|
raise Exception('not_authenticated')
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = hep_client.coupon_redeem(coupon_code, hep_id)
|
||||||
|
except HepClientException:
|
||||||
|
raise Exception('unknown_error')
|
||||||
|
|
||||||
|
if not response:
|
||||||
|
raise Exception('invalid_coupon')
|
||||||
|
|
||||||
|
license, error_msg = check_and_create_licenses(hep_client, info.context.user)
|
||||||
|
|
||||||
|
# todo fail if no license
|
||||||
|
if error_msg:
|
||||||
|
raise Exception(error_msg)
|
||||||
|
|
||||||
|
create_role_for_user(info.context.user, license.for_role.key)
|
||||||
|
|
||||||
|
return cls(success=True)
|
||||||
|
|
||||||
|
|
||||||
|
class CouponMutations:
|
||||||
|
redeem_coupon = Coupon.Field()
|
||||||
|
|
@ -7,8 +7,10 @@
|
||||||
#
|
#
|
||||||
# Created on 22.10.18
|
# Created on 22.10.18
|
||||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||||
|
from core.schema.mutations.coupon import Coupon
|
||||||
from core.schema.mutations.logout import Logout
|
from core.schema.mutations.logout import Logout
|
||||||
|
|
||||||
|
|
||||||
class CoreMutations(object):
|
class CoreMutations(object):
|
||||||
logout = Logout.Field()
|
logout = Logout.Field()
|
||||||
|
coupon = Coupon.Field()
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,7 @@ MIDDLEWARE += [
|
||||||
'core.middleware.ThreadLocalMiddleware',
|
'core.middleware.ThreadLocalMiddleware',
|
||||||
'core.middleware.CommonRedirectMiddleware',
|
'core.middleware.CommonRedirectMiddleware',
|
||||||
'core.middleware.UserLoggedInCookieMiddleWare',
|
'core.middleware.UserLoggedInCookieMiddleWare',
|
||||||
|
'core.middleware.UserHasLicenseMiddleWare',
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'core.urls'
|
ROOT_URLCONF = 'core.urls'
|
||||||
|
|
@ -380,6 +381,12 @@ EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER")
|
||||||
EMAIL_USE_TLS = True
|
EMAIL_USE_TLS = True
|
||||||
EMAIL_USE_SSL = False
|
EMAIL_USE_SSL = False
|
||||||
|
|
||||||
|
ALLOW_BETA_LOGIN = True
|
||||||
|
|
||||||
|
# HEP
|
||||||
|
HEP_ADMIN_USER = os.environ.get("HEP_ADMIN_USER")
|
||||||
|
HEP_ADMIN_PASSWORD = os.environ.get("HEP_ADMIN_PASSWORD")
|
||||||
|
HEP_URL = os.environ.get("HEP_URL")
|
||||||
|
|
||||||
TASKBASE_USER = os.environ.get("TASKBASE_USER")
|
TASKBASE_USER = os.environ.get("TASKBASE_USER")
|
||||||
TASKBASE_PASSWORD = os.environ.get("TASKBASE_PASSWORD")
|
TASKBASE_PASSWORD = os.environ.get("TASKBASE_PASSWORD")
|
||||||
|
|
@ -391,3 +398,4 @@ TASKBASE_BASEURL = os.environ.get("TASKBASE_BASEURL")
|
||||||
TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner'
|
TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner'
|
||||||
TEST_OUTPUT_DIR = './test-reports/'
|
TEST_OUTPUT_DIR = './test-reports/'
|
||||||
TEST_OUTPUT_VERBOSE = 1
|
TEST_OUTPUT_VERBOSE = 1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,3 +15,5 @@ MIGRATION_MODULES = DisableMigrations()
|
||||||
SENDGRID_API_KEY = ""
|
SENDGRID_API_KEY = ""
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'
|
EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'
|
||||||
LOGIN_REDIRECT_URL = '/accounts/login/'
|
LOGIN_REDIRECT_URL = '/accounts/login/'
|
||||||
|
|
||||||
|
USE_LOCAL_REGISTRATION = False
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Confirmation</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a href="/verify-email?confirmation={{ confirmation_key }}&id={{ hep_id }}">Email bestätitgen</a>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"id": 49124,
|
||||||
|
"group_id": 1,
|
||||||
|
"default_billing": "47579",
|
||||||
|
"default_shipping": "47579",
|
||||||
|
"confirmation": "41b58ba6598a618095e8c70625d7f052",
|
||||||
|
"created_at": "2018-07-19 15:05:27",
|
||||||
|
"updated_at": "2019-11-26 17:04:29",
|
||||||
|
"created_in": "hep verlag",
|
||||||
|
"email": "1heptest19072018@mailinator.com",
|
||||||
|
"firstname": "Test",
|
||||||
|
"lastname": "Test",
|
||||||
|
"prefix": "Frau",
|
||||||
|
"gender": 2,
|
||||||
|
"store_id": 1,
|
||||||
|
"website_id": 1,
|
||||||
|
"addresses": [
|
||||||
|
{
|
||||||
|
"id": 47579,
|
||||||
|
"customer_id": 49124,
|
||||||
|
"region": {
|
||||||
|
"region_code": null,
|
||||||
|
"region": null,
|
||||||
|
"region_id": 0
|
||||||
|
},
|
||||||
|
"region_id": 0,
|
||||||
|
"country_id": "CH",
|
||||||
|
"street": [
|
||||||
|
"Test"
|
||||||
|
],
|
||||||
|
"telephone": "",
|
||||||
|
"postcode": "0000",
|
||||||
|
"city": "Test",
|
||||||
|
"firstname": "Test",
|
||||||
|
"lastname": "Test",
|
||||||
|
"prefix": "Frau",
|
||||||
|
"default_shipping": true,
|
||||||
|
"default_billing": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"id": 49124,
|
||||||
|
"group_id": 1,
|
||||||
|
"default_billing": "47579",
|
||||||
|
"default_shipping": "47579",
|
||||||
|
"created_at": "2018-07-19 15:05:27",
|
||||||
|
"updated_at": "2019-11-26 17:04:29",
|
||||||
|
"created_in": "hep verlag",
|
||||||
|
"email": "1heptest19072018@mailinator.com",
|
||||||
|
"firstname": "Test",
|
||||||
|
"lastname": "Test",
|
||||||
|
"prefix": "Frau",
|
||||||
|
"gender": 2,
|
||||||
|
"store_id": 1,
|
||||||
|
"website_id": 1,
|
||||||
|
"addresses": [
|
||||||
|
{
|
||||||
|
"id": 47579,
|
||||||
|
"customer_id": 49124,
|
||||||
|
"region": {
|
||||||
|
"region_code": null,
|
||||||
|
"region": null,
|
||||||
|
"region_id": 0
|
||||||
|
},
|
||||||
|
"region_id": 0,
|
||||||
|
"country_id": "CH",
|
||||||
|
"street": [
|
||||||
|
"Test"
|
||||||
|
],
|
||||||
|
"telephone": "",
|
||||||
|
"postcode": "0000",
|
||||||
|
"city": "Test",
|
||||||
|
"firstname": "Test",
|
||||||
|
"lastname": "Test",
|
||||||
|
"prefix": "Frau",
|
||||||
|
"default_shipping": true,
|
||||||
|
"default_billing": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,526 @@
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"base_currency_code": "CHF",
|
||||||
|
"base_discount_amount": 0,
|
||||||
|
"base_grand_total": 46,
|
||||||
|
"base_discount_tax_compensation_amount": 0,
|
||||||
|
"base_shipping_amount": 0,
|
||||||
|
"base_shipping_discount_amount": 0,
|
||||||
|
"base_shipping_incl_tax": 0,
|
||||||
|
"base_shipping_tax_amount": 0,
|
||||||
|
"base_subtotal": 44.88,
|
||||||
|
"base_subtotal_incl_tax": 46,
|
||||||
|
"base_tax_amount": 1.12,
|
||||||
|
"base_total_due": 46,
|
||||||
|
"base_to_global_rate": 1,
|
||||||
|
"base_to_order_rate": 1,
|
||||||
|
"billing_address_id": 83693,
|
||||||
|
"created_at": "2018-07-19 15:05:33",
|
||||||
|
"customer_email": "1heptest19072018@mailinator.com",
|
||||||
|
"customer_firstname": "Test",
|
||||||
|
"customer_gender": 2,
|
||||||
|
"customer_group_id": 4,
|
||||||
|
"customer_id": 49124,
|
||||||
|
"customer_is_guest": 0,
|
||||||
|
"customer_lastname": "Test",
|
||||||
|
"customer_note": "coupon",
|
||||||
|
"customer_note_notify": 1,
|
||||||
|
"customer_prefix": "Frau",
|
||||||
|
"discount_amount": 0,
|
||||||
|
"email_sent": 1,
|
||||||
|
"entity_id": 57612,
|
||||||
|
"global_currency_code": "CHF",
|
||||||
|
"grand_total": 46,
|
||||||
|
"discount_tax_compensation_amount": 0,
|
||||||
|
"increment_id": "1004614768",
|
||||||
|
"is_virtual": 1,
|
||||||
|
"order_currency_code": "CHF",
|
||||||
|
"protect_code": "71aedb",
|
||||||
|
"quote_id": 104401,
|
||||||
|
"shipping_amount": 0,
|
||||||
|
"shipping_discount_amount": 0,
|
||||||
|
"shipping_discount_tax_compensation_amount": 0,
|
||||||
|
"shipping_incl_tax": 0,
|
||||||
|
"shipping_tax_amount": 0,
|
||||||
|
"state": "complete",
|
||||||
|
"status": "complete",
|
||||||
|
"store_currency_code": "CHF",
|
||||||
|
"store_id": 1,
|
||||||
|
"store_name": "hep verlag\nhep verlag\nhep verlag",
|
||||||
|
"store_to_base_rate": 0,
|
||||||
|
"store_to_order_rate": 0,
|
||||||
|
"subtotal": 44.88,
|
||||||
|
"subtotal_incl_tax": 46,
|
||||||
|
"tax_amount": 1.12,
|
||||||
|
"total_due": 46,
|
||||||
|
"total_item_count": 1,
|
||||||
|
"total_qty_ordered": 1,
|
||||||
|
"updated_at": "2018-07-19 15:05:33",
|
||||||
|
"weight": 0,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"amount_refunded": 0,
|
||||||
|
"base_amount_refunded": 0,
|
||||||
|
"base_discount_amount": 0,
|
||||||
|
"base_discount_invoiced": 0,
|
||||||
|
"base_discount_tax_compensation_amount": 0,
|
||||||
|
"base_original_price": 46,
|
||||||
|
"base_price": 44.88,
|
||||||
|
"base_price_incl_tax": 46,
|
||||||
|
"base_row_invoiced": 0,
|
||||||
|
"base_row_total": 44.88,
|
||||||
|
"base_row_total_incl_tax": 46,
|
||||||
|
"base_tax_amount": 1.12,
|
||||||
|
"base_tax_invoiced": 0,
|
||||||
|
"created_at": "2018-07-19 15:05:33",
|
||||||
|
"discount_amount": 0,
|
||||||
|
"discount_invoiced": 0,
|
||||||
|
"discount_percent": 0,
|
||||||
|
"free_shipping": 0,
|
||||||
|
"discount_tax_compensation_amount": 0,
|
||||||
|
"is_qty_decimal": 0,
|
||||||
|
"is_virtual": 1,
|
||||||
|
"item_id": 80317,
|
||||||
|
"name": "Myskillbox Schüler Edition",
|
||||||
|
"no_discount": 0,
|
||||||
|
"order_id": 57612,
|
||||||
|
"original_price": 46,
|
||||||
|
"price": 44.88,
|
||||||
|
"price_incl_tax": 46,
|
||||||
|
"product_id": 8652,
|
||||||
|
"product_type": "virtual",
|
||||||
|
"qty_canceled": 0,
|
||||||
|
"qty_invoiced": 0,
|
||||||
|
"qty_ordered": 1,
|
||||||
|
"qty_refunded": 0,
|
||||||
|
"qty_shipped": 0,
|
||||||
|
"quote_item_id": 135166,
|
||||||
|
"row_invoiced": 0,
|
||||||
|
"row_total": 44.88,
|
||||||
|
"row_total_incl_tax": 46,
|
||||||
|
"sku": "978-3-0355-1397-4",
|
||||||
|
"store_id": 1,
|
||||||
|
"tax_amount": 1.12,
|
||||||
|
"tax_invoiced": 0,
|
||||||
|
"tax_percent": 2.5,
|
||||||
|
"updated_at": "2018-07-19 15:05:33",
|
||||||
|
"weight": 0.01
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"billing_address": {
|
||||||
|
"address_type": "billing",
|
||||||
|
"city": "Test",
|
||||||
|
"country_id": "CH",
|
||||||
|
"customer_address_id": 47579,
|
||||||
|
"email": "1heptest19072018@mailinator.com",
|
||||||
|
"entity_id": 83693,
|
||||||
|
"firstname": "Test",
|
||||||
|
"lastname": "Test",
|
||||||
|
"parent_id": 57612,
|
||||||
|
"postcode": "0000",
|
||||||
|
"prefix": "Frau",
|
||||||
|
"street": [
|
||||||
|
"Test"
|
||||||
|
],
|
||||||
|
"telephone": null
|
||||||
|
},
|
||||||
|
"payment": {
|
||||||
|
"account_status": null,
|
||||||
|
"additional_information": [
|
||||||
|
"Rechnung",
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"amount_ordered": 46,
|
||||||
|
"base_amount_ordered": 46,
|
||||||
|
"base_shipping_amount": 0,
|
||||||
|
"cc_last4": null,
|
||||||
|
"entity_id": 57612,
|
||||||
|
"method": "checkmo",
|
||||||
|
"parent_id": 57612,
|
||||||
|
"shipping_amount": 0,
|
||||||
|
"extension_attributes": []
|
||||||
|
},
|
||||||
|
"status_histories": [
|
||||||
|
{
|
||||||
|
"comment": "payed by couponcode",
|
||||||
|
"created_at": "2018-07-19 15:05:33",
|
||||||
|
"entity_id": 244885,
|
||||||
|
"entity_name": "order",
|
||||||
|
"is_customer_notified": null,
|
||||||
|
"is_visible_on_front": 0,
|
||||||
|
"parent_id": 57612,
|
||||||
|
"status": "complete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comment": "licence-coupon \"ebf81a59b968\"",
|
||||||
|
"created_at": "2018-07-19 15:05:33",
|
||||||
|
"entity_id": 244884,
|
||||||
|
"entity_name": "order",
|
||||||
|
"is_customer_notified": null,
|
||||||
|
"is_visible_on_front": 0,
|
||||||
|
"parent_id": 57612,
|
||||||
|
"status": "complete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comment": null,
|
||||||
|
"created_at": "2018-07-19 15:05:33",
|
||||||
|
"entity_id": 244883,
|
||||||
|
"entity_name": "order",
|
||||||
|
"is_customer_notified": 0,
|
||||||
|
"is_visible_on_front": 0,
|
||||||
|
"parent_id": 57612,
|
||||||
|
"status": "complete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comment": "Exported to ERP",
|
||||||
|
"created_at": "2018-07-19 15:05:33",
|
||||||
|
"entity_id": 244882,
|
||||||
|
"entity_name": "order",
|
||||||
|
"is_customer_notified": 0,
|
||||||
|
"is_visible_on_front": 0,
|
||||||
|
"parent_id": 57612,
|
||||||
|
"status": "complete"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extension_attributes": {
|
||||||
|
"shipping_assignments": [
|
||||||
|
{
|
||||||
|
"shipping": {
|
||||||
|
"total": {
|
||||||
|
"base_shipping_amount": 0,
|
||||||
|
"base_shipping_discount_amount": 0,
|
||||||
|
"base_shipping_incl_tax": 0,
|
||||||
|
"base_shipping_tax_amount": 0,
|
||||||
|
"shipping_amount": 0,
|
||||||
|
"shipping_discount_amount": 0,
|
||||||
|
"shipping_discount_tax_compensation_amount": 0,
|
||||||
|
"shipping_incl_tax": 0,
|
||||||
|
"shipping_tax_amount": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"amount_refunded": 0,
|
||||||
|
"base_amount_refunded": 0,
|
||||||
|
"base_discount_amount": 0,
|
||||||
|
"base_discount_invoiced": 0,
|
||||||
|
"base_discount_tax_compensation_amount": 0,
|
||||||
|
"base_original_price": 46,
|
||||||
|
"base_price": 44.88,
|
||||||
|
"base_price_incl_tax": 46,
|
||||||
|
"base_row_invoiced": 0,
|
||||||
|
"base_row_total": 44.88,
|
||||||
|
"base_row_total_incl_tax": 46,
|
||||||
|
"base_tax_amount": 1.12,
|
||||||
|
"base_tax_invoiced": 0,
|
||||||
|
"created_at": "2018-07-19 15:05:33",
|
||||||
|
"discount_amount": 0,
|
||||||
|
"discount_invoiced": 0,
|
||||||
|
"discount_percent": 0,
|
||||||
|
"free_shipping": 0,
|
||||||
|
"discount_tax_compensation_amount": 0,
|
||||||
|
"is_qty_decimal": 0,
|
||||||
|
"is_virtual": 1,
|
||||||
|
"item_id": 80317,
|
||||||
|
"name": "Gesellschaft Ausgabe A (eLehrmittel, Neuauflage)",
|
||||||
|
"no_discount": 0,
|
||||||
|
"order_id": 57612,
|
||||||
|
"original_price": 46,
|
||||||
|
"price": 44.88,
|
||||||
|
"price_incl_tax": 46,
|
||||||
|
"product_id": 8652,
|
||||||
|
"product_type": "virtual",
|
||||||
|
"qty_canceled": 0,
|
||||||
|
"qty_invoiced": 0,
|
||||||
|
"qty_ordered": 1,
|
||||||
|
"qty_refunded": 0,
|
||||||
|
"qty_shipped": 0,
|
||||||
|
"quote_item_id": 135166,
|
||||||
|
"row_invoiced": 0,
|
||||||
|
"row_total": 44.88,
|
||||||
|
"row_total_incl_tax": 46,
|
||||||
|
"sku": "978-3-0355-1082-9",
|
||||||
|
"store_id": 1,
|
||||||
|
"tax_amount": 1.12,
|
||||||
|
"tax_invoiced": 0,
|
||||||
|
"tax_percent": 2.5,
|
||||||
|
"updated_at": "2018-07-19 15:05:33",
|
||||||
|
"weight": 0.01
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"base_currency_code": "CHF",
|
||||||
|
"base_discount_amount": 0,
|
||||||
|
"base_grand_total": 24,
|
||||||
|
"base_discount_tax_compensation_amount": 0,
|
||||||
|
"base_shipping_amount": 0,
|
||||||
|
"base_shipping_discount_amount": 0,
|
||||||
|
"base_shipping_incl_tax": 0,
|
||||||
|
"base_shipping_tax_amount": 0,
|
||||||
|
"base_subtotal": 23.41,
|
||||||
|
"base_subtotal_incl_tax": 24,
|
||||||
|
"base_tax_amount": 0.59,
|
||||||
|
"base_total_due": 24,
|
||||||
|
"base_to_global_rate": 1,
|
||||||
|
"base_to_order_rate": 1,
|
||||||
|
"billing_address_id": 83696,
|
||||||
|
"created_at": "2018-07-19 15:19:00",
|
||||||
|
"customer_email": "1heptest19072018@mailinator.com",
|
||||||
|
"customer_firstname": "Test",
|
||||||
|
"customer_gender": 2,
|
||||||
|
"customer_group_id": 4,
|
||||||
|
"customer_id": 49124,
|
||||||
|
"customer_is_guest": 0,
|
||||||
|
"customer_lastname": "Test",
|
||||||
|
"customer_note": "coupon",
|
||||||
|
"customer_note_notify": 1,
|
||||||
|
"customer_prefix": "Frau",
|
||||||
|
"discount_amount": 0,
|
||||||
|
"email_sent": 1,
|
||||||
|
"entity_id": 57614,
|
||||||
|
"global_currency_code": "CHF",
|
||||||
|
"grand_total": 24,
|
||||||
|
"discount_tax_compensation_amount": 0,
|
||||||
|
"increment_id": "1004614770",
|
||||||
|
"is_virtual": 1,
|
||||||
|
"order_currency_code": "CHF",
|
||||||
|
"protect_code": "1a88e9",
|
||||||
|
"quote_id": 104403,
|
||||||
|
"shipping_amount": 0,
|
||||||
|
"shipping_discount_amount": 0,
|
||||||
|
"shipping_discount_tax_compensation_amount": 0,
|
||||||
|
"shipping_incl_tax": 0,
|
||||||
|
"shipping_tax_amount": 0,
|
||||||
|
"state": "complete",
|
||||||
|
"status": "complete",
|
||||||
|
"store_currency_code": "CHF",
|
||||||
|
"store_id": 1,
|
||||||
|
"store_name": "hep verlag\nhep verlag\nhep verlag",
|
||||||
|
"store_to_base_rate": 0,
|
||||||
|
"store_to_order_rate": 0,
|
||||||
|
"subtotal": 23.41,
|
||||||
|
"subtotal_incl_tax": 24,
|
||||||
|
"tax_amount": 0.59,
|
||||||
|
"total_due": 24,
|
||||||
|
"total_item_count": 1,
|
||||||
|
"total_qty_ordered": 1,
|
||||||
|
"updated_at": "2018-07-19 15:19:00",
|
||||||
|
"weight": 0,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"amount_refunded": 0,
|
||||||
|
"base_amount_refunded": 0,
|
||||||
|
"base_discount_amount": 0,
|
||||||
|
"base_discount_invoiced": 0,
|
||||||
|
"base_discount_tax_compensation_amount": 0,
|
||||||
|
"base_original_price": 24,
|
||||||
|
"base_price": 23.41,
|
||||||
|
"base_price_incl_tax": 24,
|
||||||
|
"base_row_invoiced": 0,
|
||||||
|
"base_row_total": 23.41,
|
||||||
|
"base_row_total_incl_tax": 24,
|
||||||
|
"base_tax_amount": 0.59,
|
||||||
|
"base_tax_invoiced": 0,
|
||||||
|
"created_at": "2018-07-19 15:19:00",
|
||||||
|
"discount_amount": 0,
|
||||||
|
"discount_invoiced": 0,
|
||||||
|
"discount_percent": 0,
|
||||||
|
"free_shipping": 0,
|
||||||
|
"discount_tax_compensation_amount": 0,
|
||||||
|
"is_qty_decimal": 0,
|
||||||
|
"is_virtual": 1,
|
||||||
|
"item_id": 80320,
|
||||||
|
"name": "Gesellschaft Ausgabe A, Arbeitsheft (eLehrmittel, Neuauflage)",
|
||||||
|
"no_discount": 0,
|
||||||
|
"order_id": 57614,
|
||||||
|
"original_price": 24,
|
||||||
|
"price": 23.41,
|
||||||
|
"price_incl_tax": 24,
|
||||||
|
"product_id": 8654,
|
||||||
|
"product_type": "virtual",
|
||||||
|
"qty_canceled": 0,
|
||||||
|
"qty_invoiced": 0,
|
||||||
|
"qty_ordered": 1,
|
||||||
|
"qty_refunded": 0,
|
||||||
|
"qty_shipped": 0,
|
||||||
|
"quote_item_id": 135169,
|
||||||
|
"row_invoiced": 0,
|
||||||
|
"row_total": 23.41,
|
||||||
|
"row_total_incl_tax": 24,
|
||||||
|
"sku": "978-3-0355-1185-7",
|
||||||
|
"store_id": 1,
|
||||||
|
"tax_amount": 0.59,
|
||||||
|
"tax_invoiced": 0,
|
||||||
|
"tax_percent": 2.5,
|
||||||
|
"updated_at": "2018-07-19 15:19:00",
|
||||||
|
"weight": 0.01
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"billing_address": {
|
||||||
|
"address_type": "billing",
|
||||||
|
"city": "Test",
|
||||||
|
"country_id": "CH",
|
||||||
|
"customer_address_id": 47579,
|
||||||
|
"email": "1heptest19072018@mailinator.com",
|
||||||
|
"entity_id": 83696,
|
||||||
|
"firstname": "Test",
|
||||||
|
"lastname": "Test",
|
||||||
|
"parent_id": 57614,
|
||||||
|
"postcode": "0000",
|
||||||
|
"prefix": "Frau",
|
||||||
|
"street": [
|
||||||
|
"Test"
|
||||||
|
],
|
||||||
|
"telephone": null
|
||||||
|
},
|
||||||
|
"payment": {
|
||||||
|
"account_status": null,
|
||||||
|
"additional_information": [
|
||||||
|
"Rechnung",
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"amount_ordered": 24,
|
||||||
|
"base_amount_ordered": 24,
|
||||||
|
"base_shipping_amount": 0,
|
||||||
|
"cc_last4": null,
|
||||||
|
"entity_id": 57614,
|
||||||
|
"method": "checkmo",
|
||||||
|
"parent_id": 57614,
|
||||||
|
"shipping_amount": 0,
|
||||||
|
"extension_attributes": []
|
||||||
|
},
|
||||||
|
"status_histories": [
|
||||||
|
{
|
||||||
|
"comment": "payed by couponcode",
|
||||||
|
"created_at": "2018-07-19 15:19:00",
|
||||||
|
"entity_id": 244890,
|
||||||
|
"entity_name": "order",
|
||||||
|
"is_customer_notified": null,
|
||||||
|
"is_visible_on_front": 0,
|
||||||
|
"parent_id": 57614,
|
||||||
|
"status": "complete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comment": "licence-coupon \"ece5e74a2b36\"",
|
||||||
|
"created_at": "2018-07-19 15:19:00",
|
||||||
|
"entity_id": 244889,
|
||||||
|
"entity_name": "order",
|
||||||
|
"is_customer_notified": null,
|
||||||
|
"is_visible_on_front": 0,
|
||||||
|
"parent_id": 57614,
|
||||||
|
"status": "complete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comment": null,
|
||||||
|
"created_at": "2018-07-19 15:19:00",
|
||||||
|
"entity_id": 244888,
|
||||||
|
"entity_name": "order",
|
||||||
|
"is_customer_notified": 0,
|
||||||
|
"is_visible_on_front": 0,
|
||||||
|
"parent_id": 57614,
|
||||||
|
"status": "complete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comment": "Exported to ERP",
|
||||||
|
"created_at": "2018-07-19 15:19:00",
|
||||||
|
"entity_id": 244887,
|
||||||
|
"entity_name": "order",
|
||||||
|
"is_customer_notified": 0,
|
||||||
|
"is_visible_on_front": 0,
|
||||||
|
"parent_id": 57614,
|
||||||
|
"status": "complete"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extension_attributes": {
|
||||||
|
"shipping_assignments": [
|
||||||
|
{
|
||||||
|
"shipping": {
|
||||||
|
"total": {
|
||||||
|
"base_shipping_amount": 0,
|
||||||
|
"base_shipping_discount_amount": 0,
|
||||||
|
"base_shipping_incl_tax": 0,
|
||||||
|
"base_shipping_tax_amount": 0,
|
||||||
|
"shipping_amount": 0,
|
||||||
|
"shipping_discount_amount": 0,
|
||||||
|
"shipping_discount_tax_compensation_amount": 0,
|
||||||
|
"shipping_incl_tax": 0,
|
||||||
|
"shipping_tax_amount": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"amount_refunded": 0,
|
||||||
|
"base_amount_refunded": 0,
|
||||||
|
"base_discount_amount": 0,
|
||||||
|
"base_discount_invoiced": 0,
|
||||||
|
"base_discount_tax_compensation_amount": 0,
|
||||||
|
"base_original_price": 24,
|
||||||
|
"base_price": 23.41,
|
||||||
|
"base_price_incl_tax": 24,
|
||||||
|
"base_row_invoiced": 0,
|
||||||
|
"base_row_total": 23.41,
|
||||||
|
"base_row_total_incl_tax": 24,
|
||||||
|
"base_tax_amount": 0.59,
|
||||||
|
"base_tax_invoiced": 0,
|
||||||
|
"created_at": "2018-07-19 15:19:00",
|
||||||
|
"discount_amount": 0,
|
||||||
|
"discount_invoiced": 0,
|
||||||
|
"discount_percent": 0,
|
||||||
|
"free_shipping": 0,
|
||||||
|
"discount_tax_compensation_amount": 0,
|
||||||
|
"is_qty_decimal": 0,
|
||||||
|
"is_virtual": 1,
|
||||||
|
"item_id": 80320,
|
||||||
|
"name": "Gesellschaft Ausgabe A, Arbeitsheft (eLehrmittel, Neuauflage)",
|
||||||
|
"no_discount": 0,
|
||||||
|
"order_id": 57614,
|
||||||
|
"original_price": 24,
|
||||||
|
"price": 23.41,
|
||||||
|
"price_incl_tax": 24,
|
||||||
|
"product_id": 8654,
|
||||||
|
"product_type": "virtual",
|
||||||
|
"qty_canceled": 0,
|
||||||
|
"qty_invoiced": 0,
|
||||||
|
"qty_ordered": 1,
|
||||||
|
"qty_refunded": 0,
|
||||||
|
"qty_shipped": 0,
|
||||||
|
"quote_item_id": 135169,
|
||||||
|
"row_invoiced": 0,
|
||||||
|
"row_total": 23.41,
|
||||||
|
"row_total_incl_tax": 24,
|
||||||
|
"sku": "978-3-0355-1185-7",
|
||||||
|
"store_id": 1,
|
||||||
|
"tax_amount": 0.59,
|
||||||
|
"tax_invoiced": 0,
|
||||||
|
"tax_percent": 2.5,
|
||||||
|
"updated_at": "2018-07-19 15:19:00",
|
||||||
|
"weight": 0.01
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"search_criteria": {
|
||||||
|
"filter_groups": [
|
||||||
|
{
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"field": "customer_id",
|
||||||
|
"value": "49124",
|
||||||
|
"condition_type": "eq"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"total_count": 2
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,526 @@
|
||||||
|
{
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"base_currency_code": "CHF",
|
||||||
|
"base_discount_amount": 0,
|
||||||
|
"base_grand_total": 46,
|
||||||
|
"base_discount_tax_compensation_amount": 0,
|
||||||
|
"base_shipping_amount": 0,
|
||||||
|
"base_shipping_discount_amount": 0,
|
||||||
|
"base_shipping_incl_tax": 0,
|
||||||
|
"base_shipping_tax_amount": 0,
|
||||||
|
"base_subtotal": 44.88,
|
||||||
|
"base_subtotal_incl_tax": 46,
|
||||||
|
"base_tax_amount": 1.12,
|
||||||
|
"base_total_due": 46,
|
||||||
|
"base_to_global_rate": 1,
|
||||||
|
"base_to_order_rate": 1,
|
||||||
|
"billing_address_id": 83693,
|
||||||
|
"created_at": "2018-07-19 15:05:33",
|
||||||
|
"customer_email": "1heptest19072018@mailinator.com",
|
||||||
|
"customer_firstname": "Test",
|
||||||
|
"customer_gender": 2,
|
||||||
|
"customer_group_id": 4,
|
||||||
|
"customer_id": 49124,
|
||||||
|
"customer_is_guest": 0,
|
||||||
|
"customer_lastname": "Test",
|
||||||
|
"customer_note": "coupon",
|
||||||
|
"customer_note_notify": 1,
|
||||||
|
"customer_prefix": "Frau",
|
||||||
|
"discount_amount": 0,
|
||||||
|
"email_sent": 1,
|
||||||
|
"entity_id": 57612,
|
||||||
|
"global_currency_code": "CHF",
|
||||||
|
"grand_total": 46,
|
||||||
|
"discount_tax_compensation_amount": 0,
|
||||||
|
"increment_id": "1004614768",
|
||||||
|
"is_virtual": 1,
|
||||||
|
"order_currency_code": "CHF",
|
||||||
|
"protect_code": "71aedb",
|
||||||
|
"quote_id": 104401,
|
||||||
|
"shipping_amount": 0,
|
||||||
|
"shipping_discount_amount": 0,
|
||||||
|
"shipping_discount_tax_compensation_amount": 0,
|
||||||
|
"shipping_incl_tax": 0,
|
||||||
|
"shipping_tax_amount": 0,
|
||||||
|
"state": "complete",
|
||||||
|
"status": "complete",
|
||||||
|
"store_currency_code": "CHF",
|
||||||
|
"store_id": 1,
|
||||||
|
"store_name": "hep verlag\nhep verlag\nhep verlag",
|
||||||
|
"store_to_base_rate": 0,
|
||||||
|
"store_to_order_rate": 0,
|
||||||
|
"subtotal": 44.88,
|
||||||
|
"subtotal_incl_tax": 46,
|
||||||
|
"tax_amount": 1.12,
|
||||||
|
"total_due": 46,
|
||||||
|
"total_item_count": 1,
|
||||||
|
"total_qty_ordered": 1,
|
||||||
|
"updated_at": "2018-07-19 15:05:33",
|
||||||
|
"weight": 0,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"amount_refunded": 0,
|
||||||
|
"base_amount_refunded": 0,
|
||||||
|
"base_discount_amount": 0,
|
||||||
|
"base_discount_invoiced": 0,
|
||||||
|
"base_discount_tax_compensation_amount": 0,
|
||||||
|
"base_original_price": 46,
|
||||||
|
"base_price": 44.88,
|
||||||
|
"base_price_incl_tax": 46,
|
||||||
|
"base_row_invoiced": 0,
|
||||||
|
"base_row_total": 44.88,
|
||||||
|
"base_row_total_incl_tax": 46,
|
||||||
|
"base_tax_amount": 1.12,
|
||||||
|
"base_tax_invoiced": 0,
|
||||||
|
"created_at": "2018-07-19 15:05:33",
|
||||||
|
"discount_amount": 0,
|
||||||
|
"discount_invoiced": 0,
|
||||||
|
"discount_percent": 0,
|
||||||
|
"free_shipping": 0,
|
||||||
|
"discount_tax_compensation_amount": 0,
|
||||||
|
"is_qty_decimal": 0,
|
||||||
|
"is_virtual": 1,
|
||||||
|
"item_id": 80317,
|
||||||
|
"name": "Myskillbox Lehreredition",
|
||||||
|
"no_discount": 0,
|
||||||
|
"order_id": 57612,
|
||||||
|
"original_price": 46,
|
||||||
|
"price": 44.88,
|
||||||
|
"price_incl_tax": 46,
|
||||||
|
"product_id": 8652,
|
||||||
|
"product_type": "virtual",
|
||||||
|
"qty_canceled": 0,
|
||||||
|
"qty_invoiced": 0,
|
||||||
|
"qty_ordered": 1,
|
||||||
|
"qty_refunded": 0,
|
||||||
|
"qty_shipped": 0,
|
||||||
|
"quote_item_id": 135166,
|
||||||
|
"row_invoiced": 0,
|
||||||
|
"row_total": 44.88,
|
||||||
|
"row_total_incl_tax": 46,
|
||||||
|
"sku": "978-3-0355-1823-8",
|
||||||
|
"store_id": 1,
|
||||||
|
"tax_amount": 1.12,
|
||||||
|
"tax_invoiced": 0,
|
||||||
|
"tax_percent": 2.5,
|
||||||
|
"updated_at": "2018-07-19 15:05:33",
|
||||||
|
"weight": 0.01
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"billing_address": {
|
||||||
|
"address_type": "billing",
|
||||||
|
"city": "Test",
|
||||||
|
"country_id": "CH",
|
||||||
|
"customer_address_id": 47579,
|
||||||
|
"email": "1heptest19072018@mailinator.com",
|
||||||
|
"entity_id": 83693,
|
||||||
|
"firstname": "Test",
|
||||||
|
"lastname": "Test",
|
||||||
|
"parent_id": 57612,
|
||||||
|
"postcode": "0000",
|
||||||
|
"prefix": "Frau",
|
||||||
|
"street": [
|
||||||
|
"Test"
|
||||||
|
],
|
||||||
|
"telephone": null
|
||||||
|
},
|
||||||
|
"payment": {
|
||||||
|
"account_status": null,
|
||||||
|
"additional_information": [
|
||||||
|
"Rechnung",
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"amount_ordered": 46,
|
||||||
|
"base_amount_ordered": 46,
|
||||||
|
"base_shipping_amount": 0,
|
||||||
|
"cc_last4": null,
|
||||||
|
"entity_id": 57612,
|
||||||
|
"method": "checkmo",
|
||||||
|
"parent_id": 57612,
|
||||||
|
"shipping_amount": 0,
|
||||||
|
"extension_attributes": []
|
||||||
|
},
|
||||||
|
"status_histories": [
|
||||||
|
{
|
||||||
|
"comment": "payed by couponcode",
|
||||||
|
"created_at": "2018-07-19 15:05:33",
|
||||||
|
"entity_id": 244885,
|
||||||
|
"entity_name": "order",
|
||||||
|
"is_customer_notified": null,
|
||||||
|
"is_visible_on_front": 0,
|
||||||
|
"parent_id": 57612,
|
||||||
|
"status": "complete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comment": "licence-coupon \"ebf81a59b968\"",
|
||||||
|
"created_at": "2018-07-19 15:05:33",
|
||||||
|
"entity_id": 244884,
|
||||||
|
"entity_name": "order",
|
||||||
|
"is_customer_notified": null,
|
||||||
|
"is_visible_on_front": 0,
|
||||||
|
"parent_id": 57612,
|
||||||
|
"status": "complete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comment": null,
|
||||||
|
"created_at": "2018-07-19 15:05:33",
|
||||||
|
"entity_id": 244883,
|
||||||
|
"entity_name": "order",
|
||||||
|
"is_customer_notified": 0,
|
||||||
|
"is_visible_on_front": 0,
|
||||||
|
"parent_id": 57612,
|
||||||
|
"status": "complete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comment": "Exported to ERP",
|
||||||
|
"created_at": "2018-07-19 15:05:33",
|
||||||
|
"entity_id": 244882,
|
||||||
|
"entity_name": "order",
|
||||||
|
"is_customer_notified": 0,
|
||||||
|
"is_visible_on_front": 0,
|
||||||
|
"parent_id": 57612,
|
||||||
|
"status": "complete"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extension_attributes": {
|
||||||
|
"shipping_assignments": [
|
||||||
|
{
|
||||||
|
"shipping": {
|
||||||
|
"total": {
|
||||||
|
"base_shipping_amount": 0,
|
||||||
|
"base_shipping_discount_amount": 0,
|
||||||
|
"base_shipping_incl_tax": 0,
|
||||||
|
"base_shipping_tax_amount": 0,
|
||||||
|
"shipping_amount": 0,
|
||||||
|
"shipping_discount_amount": 0,
|
||||||
|
"shipping_discount_tax_compensation_amount": 0,
|
||||||
|
"shipping_incl_tax": 0,
|
||||||
|
"shipping_tax_amount": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"amount_refunded": 0,
|
||||||
|
"base_amount_refunded": 0,
|
||||||
|
"base_discount_amount": 0,
|
||||||
|
"base_discount_invoiced": 0,
|
||||||
|
"base_discount_tax_compensation_amount": 0,
|
||||||
|
"base_original_price": 46,
|
||||||
|
"base_price": 44.88,
|
||||||
|
"base_price_incl_tax": 46,
|
||||||
|
"base_row_invoiced": 0,
|
||||||
|
"base_row_total": 44.88,
|
||||||
|
"base_row_total_incl_tax": 46,
|
||||||
|
"base_tax_amount": 1.12,
|
||||||
|
"base_tax_invoiced": 0,
|
||||||
|
"created_at": "2018-07-19 15:05:33",
|
||||||
|
"discount_amount": 0,
|
||||||
|
"discount_invoiced": 0,
|
||||||
|
"discount_percent": 0,
|
||||||
|
"free_shipping": 0,
|
||||||
|
"discount_tax_compensation_amount": 0,
|
||||||
|
"is_qty_decimal": 0,
|
||||||
|
"is_virtual": 1,
|
||||||
|
"item_id": 80317,
|
||||||
|
"name": "Gesellschaft Ausgabe A (eLehrmittel, Neuauflage)",
|
||||||
|
"no_discount": 0,
|
||||||
|
"order_id": 57612,
|
||||||
|
"original_price": 46,
|
||||||
|
"price": 44.88,
|
||||||
|
"price_incl_tax": 46,
|
||||||
|
"product_id": 8652,
|
||||||
|
"product_type": "virtual",
|
||||||
|
"qty_canceled": 0,
|
||||||
|
"qty_invoiced": 0,
|
||||||
|
"qty_ordered": 1,
|
||||||
|
"qty_refunded": 0,
|
||||||
|
"qty_shipped": 0,
|
||||||
|
"quote_item_id": 135166,
|
||||||
|
"row_invoiced": 0,
|
||||||
|
"row_total": 44.88,
|
||||||
|
"row_total_incl_tax": 46,
|
||||||
|
"sku": "978-3-0355-1082-9",
|
||||||
|
"store_id": 1,
|
||||||
|
"tax_amount": 1.12,
|
||||||
|
"tax_invoiced": 0,
|
||||||
|
"tax_percent": 2.5,
|
||||||
|
"updated_at": "2018-07-19 15:05:33",
|
||||||
|
"weight": 0.01
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"base_currency_code": "CHF",
|
||||||
|
"base_discount_amount": 0,
|
||||||
|
"base_grand_total": 24,
|
||||||
|
"base_discount_tax_compensation_amount": 0,
|
||||||
|
"base_shipping_amount": 0,
|
||||||
|
"base_shipping_discount_amount": 0,
|
||||||
|
"base_shipping_incl_tax": 0,
|
||||||
|
"base_shipping_tax_amount": 0,
|
||||||
|
"base_subtotal": 23.41,
|
||||||
|
"base_subtotal_incl_tax": 24,
|
||||||
|
"base_tax_amount": 0.59,
|
||||||
|
"base_total_due": 24,
|
||||||
|
"base_to_global_rate": 1,
|
||||||
|
"base_to_order_rate": 1,
|
||||||
|
"billing_address_id": 83696,
|
||||||
|
"created_at": "2018-07-19 15:19:00",
|
||||||
|
"customer_email": "1heptest19072018@mailinator.com",
|
||||||
|
"customer_firstname": "Test",
|
||||||
|
"customer_gender": 2,
|
||||||
|
"customer_group_id": 4,
|
||||||
|
"customer_id": 49124,
|
||||||
|
"customer_is_guest": 0,
|
||||||
|
"customer_lastname": "Test",
|
||||||
|
"customer_note": "coupon",
|
||||||
|
"customer_note_notify": 1,
|
||||||
|
"customer_prefix": "Frau",
|
||||||
|
"discount_amount": 0,
|
||||||
|
"email_sent": 1,
|
||||||
|
"entity_id": 57614,
|
||||||
|
"global_currency_code": "CHF",
|
||||||
|
"grand_total": 24,
|
||||||
|
"discount_tax_compensation_amount": 0,
|
||||||
|
"increment_id": "1004614770",
|
||||||
|
"is_virtual": 1,
|
||||||
|
"order_currency_code": "CHF",
|
||||||
|
"protect_code": "1a88e9",
|
||||||
|
"quote_id": 104403,
|
||||||
|
"shipping_amount": 0,
|
||||||
|
"shipping_discount_amount": 0,
|
||||||
|
"shipping_discount_tax_compensation_amount": 0,
|
||||||
|
"shipping_incl_tax": 0,
|
||||||
|
"shipping_tax_amount": 0,
|
||||||
|
"state": "complete",
|
||||||
|
"status": "complete",
|
||||||
|
"store_currency_code": "CHF",
|
||||||
|
"store_id": 1,
|
||||||
|
"store_name": "hep verlag\nhep verlag\nhep verlag",
|
||||||
|
"store_to_base_rate": 0,
|
||||||
|
"store_to_order_rate": 0,
|
||||||
|
"subtotal": 23.41,
|
||||||
|
"subtotal_incl_tax": 24,
|
||||||
|
"tax_amount": 0.59,
|
||||||
|
"total_due": 24,
|
||||||
|
"total_item_count": 1,
|
||||||
|
"total_qty_ordered": 1,
|
||||||
|
"updated_at": "2018-07-19 15:19:00",
|
||||||
|
"weight": 0,
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"amount_refunded": 0,
|
||||||
|
"base_amount_refunded": 0,
|
||||||
|
"base_discount_amount": 0,
|
||||||
|
"base_discount_invoiced": 0,
|
||||||
|
"base_discount_tax_compensation_amount": 0,
|
||||||
|
"base_original_price": 24,
|
||||||
|
"base_price": 23.41,
|
||||||
|
"base_price_incl_tax": 24,
|
||||||
|
"base_row_invoiced": 0,
|
||||||
|
"base_row_total": 23.41,
|
||||||
|
"base_row_total_incl_tax": 24,
|
||||||
|
"base_tax_amount": 0.59,
|
||||||
|
"base_tax_invoiced": 0,
|
||||||
|
"created_at": "2018-07-19 15:19:00",
|
||||||
|
"discount_amount": 0,
|
||||||
|
"discount_invoiced": 0,
|
||||||
|
"discount_percent": 0,
|
||||||
|
"free_shipping": 0,
|
||||||
|
"discount_tax_compensation_amount": 0,
|
||||||
|
"is_qty_decimal": 0,
|
||||||
|
"is_virtual": 1,
|
||||||
|
"item_id": 80320,
|
||||||
|
"name": "Gesellschaft Ausgabe A, Arbeitsheft (eLehrmittel, Neuauflage)",
|
||||||
|
"no_discount": 0,
|
||||||
|
"order_id": 57614,
|
||||||
|
"original_price": 24,
|
||||||
|
"price": 23.41,
|
||||||
|
"price_incl_tax": 24,
|
||||||
|
"product_id": 8654,
|
||||||
|
"product_type": "virtual",
|
||||||
|
"qty_canceled": 0,
|
||||||
|
"qty_invoiced": 0,
|
||||||
|
"qty_ordered": 1,
|
||||||
|
"qty_refunded": 0,
|
||||||
|
"qty_shipped": 0,
|
||||||
|
"quote_item_id": 135169,
|
||||||
|
"row_invoiced": 0,
|
||||||
|
"row_total": 23.41,
|
||||||
|
"row_total_incl_tax": 24,
|
||||||
|
"sku": "978-3-0355-1185-7",
|
||||||
|
"store_id": 1,
|
||||||
|
"tax_amount": 0.59,
|
||||||
|
"tax_invoiced": 0,
|
||||||
|
"tax_percent": 2.5,
|
||||||
|
"updated_at": "2018-07-19 15:19:00",
|
||||||
|
"weight": 0.01
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"billing_address": {
|
||||||
|
"address_type": "billing",
|
||||||
|
"city": "Test",
|
||||||
|
"country_id": "CH",
|
||||||
|
"customer_address_id": 47579,
|
||||||
|
"email": "1heptest19072018@mailinator.com",
|
||||||
|
"entity_id": 83696,
|
||||||
|
"firstname": "Test",
|
||||||
|
"lastname": "Test",
|
||||||
|
"parent_id": 57614,
|
||||||
|
"postcode": "0000",
|
||||||
|
"prefix": "Frau",
|
||||||
|
"street": [
|
||||||
|
"Test"
|
||||||
|
],
|
||||||
|
"telephone": null
|
||||||
|
},
|
||||||
|
"payment": {
|
||||||
|
"account_status": null,
|
||||||
|
"additional_information": [
|
||||||
|
"Rechnung",
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
],
|
||||||
|
"amount_ordered": 24,
|
||||||
|
"base_amount_ordered": 24,
|
||||||
|
"base_shipping_amount": 0,
|
||||||
|
"cc_last4": null,
|
||||||
|
"entity_id": 57614,
|
||||||
|
"method": "checkmo",
|
||||||
|
"parent_id": 57614,
|
||||||
|
"shipping_amount": 0,
|
||||||
|
"extension_attributes": []
|
||||||
|
},
|
||||||
|
"status_histories": [
|
||||||
|
{
|
||||||
|
"comment": "payed by couponcode",
|
||||||
|
"created_at": "2018-07-19 15:19:00",
|
||||||
|
"entity_id": 244890,
|
||||||
|
"entity_name": "order",
|
||||||
|
"is_customer_notified": null,
|
||||||
|
"is_visible_on_front": 0,
|
||||||
|
"parent_id": 57614,
|
||||||
|
"status": "complete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comment": "licence-coupon \"ece5e74a2b36\"",
|
||||||
|
"created_at": "2018-07-19 15:19:00",
|
||||||
|
"entity_id": 244889,
|
||||||
|
"entity_name": "order",
|
||||||
|
"is_customer_notified": null,
|
||||||
|
"is_visible_on_front": 0,
|
||||||
|
"parent_id": 57614,
|
||||||
|
"status": "complete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comment": null,
|
||||||
|
"created_at": "2018-07-19 15:19:00",
|
||||||
|
"entity_id": 244888,
|
||||||
|
"entity_name": "order",
|
||||||
|
"is_customer_notified": 0,
|
||||||
|
"is_visible_on_front": 0,
|
||||||
|
"parent_id": 57614,
|
||||||
|
"status": "complete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comment": "Exported to ERP",
|
||||||
|
"created_at": "2018-07-19 15:19:00",
|
||||||
|
"entity_id": 244887,
|
||||||
|
"entity_name": "order",
|
||||||
|
"is_customer_notified": 0,
|
||||||
|
"is_visible_on_front": 0,
|
||||||
|
"parent_id": 57614,
|
||||||
|
"status": "complete"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extension_attributes": {
|
||||||
|
"shipping_assignments": [
|
||||||
|
{
|
||||||
|
"shipping": {
|
||||||
|
"total": {
|
||||||
|
"base_shipping_amount": 0,
|
||||||
|
"base_shipping_discount_amount": 0,
|
||||||
|
"base_shipping_incl_tax": 0,
|
||||||
|
"base_shipping_tax_amount": 0,
|
||||||
|
"shipping_amount": 0,
|
||||||
|
"shipping_discount_amount": 0,
|
||||||
|
"shipping_discount_tax_compensation_amount": 0,
|
||||||
|
"shipping_incl_tax": 0,
|
||||||
|
"shipping_tax_amount": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"amount_refunded": 0,
|
||||||
|
"base_amount_refunded": 0,
|
||||||
|
"base_discount_amount": 0,
|
||||||
|
"base_discount_invoiced": 0,
|
||||||
|
"base_discount_tax_compensation_amount": 0,
|
||||||
|
"base_original_price": 24,
|
||||||
|
"base_price": 23.41,
|
||||||
|
"base_price_incl_tax": 24,
|
||||||
|
"base_row_invoiced": 0,
|
||||||
|
"base_row_total": 23.41,
|
||||||
|
"base_row_total_incl_tax": 24,
|
||||||
|
"base_tax_amount": 0.59,
|
||||||
|
"base_tax_invoiced": 0,
|
||||||
|
"created_at": "2018-07-19 15:19:00",
|
||||||
|
"discount_amount": 0,
|
||||||
|
"discount_invoiced": 0,
|
||||||
|
"discount_percent": 0,
|
||||||
|
"free_shipping": 0,
|
||||||
|
"discount_tax_compensation_amount": 0,
|
||||||
|
"is_qty_decimal": 0,
|
||||||
|
"is_virtual": 1,
|
||||||
|
"item_id": 80320,
|
||||||
|
"name": "Gesellschaft Ausgabe A, Arbeitsheft (eLehrmittel, Neuauflage)",
|
||||||
|
"no_discount": 0,
|
||||||
|
"order_id": 57614,
|
||||||
|
"original_price": 24,
|
||||||
|
"price": 23.41,
|
||||||
|
"price_incl_tax": 24,
|
||||||
|
"product_id": 8654,
|
||||||
|
"product_type": "virtual",
|
||||||
|
"qty_canceled": 0,
|
||||||
|
"qty_invoiced": 0,
|
||||||
|
"qty_ordered": 1,
|
||||||
|
"qty_refunded": 0,
|
||||||
|
"qty_shipped": 0,
|
||||||
|
"quote_item_id": 135169,
|
||||||
|
"row_invoiced": 0,
|
||||||
|
"row_total": 23.41,
|
||||||
|
"row_total_incl_tax": 24,
|
||||||
|
"sku": "978-3-0355-1185-7",
|
||||||
|
"store_id": 1,
|
||||||
|
"tax_amount": 0.59,
|
||||||
|
"tax_invoiced": 0,
|
||||||
|
"tax_percent": 2.5,
|
||||||
|
"updated_at": "2018-07-19 15:19:00",
|
||||||
|
"weight": 0.01
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"search_criteria": {
|
||||||
|
"filter_groups": [
|
||||||
|
{
|
||||||
|
"filters": [
|
||||||
|
{
|
||||||
|
"field": "customer_id",
|
||||||
|
"value": "49124",
|
||||||
|
"condition_type": "eq"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"total_count": 2
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
|
||||||
|
class MockResponse:
|
||||||
|
def __init__(self, status_code, data={}):
|
||||||
|
self.status_code = status_code
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
def json(self):
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
|
||||||
|
## Setup json data
|
||||||
|
|
||||||
|
def make_orders_valid(order_items):
|
||||||
|
for order_item in order_items['items']:
|
||||||
|
if 'created_at' in order_item:
|
||||||
|
yesterday = datetime.now() - timedelta(1)
|
||||||
|
order_item['created_at'] = datetime.strftime(yesterday, '%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
return order_items
|
||||||
|
|
||||||
|
# Load data
|
||||||
|
dir_path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
|
with open('{}/mock_data/valid_teacher_orders.json'.format(dir_path), 'r') as file:
|
||||||
|
valid_teacher_order_data = file.read()
|
||||||
|
|
||||||
|
with open('{}/mock_data/valid_student_orders.json'.format(dir_path), 'r') as file:
|
||||||
|
valid_student_order_data = file.read()
|
||||||
|
|
||||||
|
with open('{}/mock_data/me_data.json'.format(dir_path), 'r') as file:
|
||||||
|
me_data = file.read()
|
||||||
|
|
||||||
|
with open('{}/mock_data/email_not_confirmed_me.json'.format(dir_path), 'r') as file:
|
||||||
|
not_confirmed_email_me_data = file.read()
|
||||||
|
|
||||||
|
ME_DATA = json.loads(me_data)
|
||||||
|
NOT_CONFIRMED_ME = json.loads(not_confirmed_email_me_data)
|
||||||
|
|
||||||
|
valid_teacher_order_items = json.loads(valid_teacher_order_data)
|
||||||
|
VALID_TEACHERS_ORDERS = make_orders_valid(valid_teacher_order_items)
|
||||||
|
|
||||||
|
valid_student_order_items = json.loads(valid_student_order_data)
|
||||||
|
VALID_STUDENT_ORDERS = make_orders_valid(valid_student_order_items)
|
||||||
|
|
@ -20,7 +20,7 @@ class ApiAccessTestCase(TestCase):
|
||||||
self.assertEqual(response.url, '/login?next=/api/graphql/')
|
self.assertEqual(response.url, '/login?next=/api/graphql/')
|
||||||
|
|
||||||
def test_graphqlEndpoint_shouldBeAccessibleWithLogin(self):
|
def test_graphqlEndpoint_shouldBeAccessibleWithLogin(self):
|
||||||
user = UserFactory(username='admin')
|
UserFactory(username='admin')
|
||||||
|
|
||||||
c = Client()
|
c = Client()
|
||||||
c.login(username='admin', password='test')
|
c.login(username='admin', password='test')
|
||||||
|
|
@ -31,14 +31,11 @@ class ApiAccessTestCase(TestCase):
|
||||||
def test_publicGraphqlEndpoint_shouldBeAccessibleWithoutLogin(self):
|
def test_publicGraphqlEndpoint_shouldBeAccessibleWithoutLogin(self):
|
||||||
|
|
||||||
query= json.dumps({
|
query= json.dumps({
|
||||||
'operationName': 'Login',
|
'operationName': 'BetaLogin',
|
||||||
'query': '''
|
'query': '''
|
||||||
mutation Login($input: LoginInput!){
|
mutation BetaLogin($input: BetaLoginInput!){
|
||||||
login(input: $input) {
|
betaLogin(input: $input) {
|
||||||
success
|
success
|
||||||
errors {
|
|
||||||
field
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
''',
|
''',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# ITerativ GmbH
|
||||||
|
# http://www.iterativ.ch/
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020 ITerativ GmbH. All rights reserved.
|
||||||
|
#
|
||||||
|
# Created on 03.02.20
|
||||||
|
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from django.contrib.sessions.middleware import SessionMiddleware
|
||||||
|
from django.test import TestCase, RequestFactory
|
||||||
|
from graphene.test import Client
|
||||||
|
|
||||||
|
from api.schema import schema
|
||||||
|
from core.factories import UserFactory
|
||||||
|
from core.hep_client import HepClient
|
||||||
|
from core.tests.mock_hep_data_factory import MockResponse, VALID_TEACHERS_ORDERS
|
||||||
|
from users.models import License, Role, SchoolClass, UserRole
|
||||||
|
|
||||||
|
|
||||||
|
class CouponTests(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
Role.objects.create_default_roles()
|
||||||
|
|
||||||
|
self.user = UserFactory(username='aschi@iterativ.ch', email='aschi@iterativ.ch', hep_id=3)
|
||||||
|
Role.objects.create_default_roles()
|
||||||
|
self.teacher_role = Role.objects.get_default_teacher_role()
|
||||||
|
|
||||||
|
# adding session
|
||||||
|
request = RequestFactory().post('/')
|
||||||
|
middleware = SessionMiddleware()
|
||||||
|
middleware.process_request(request)
|
||||||
|
request.user = self.user
|
||||||
|
request.session.save()
|
||||||
|
self.client = Client(schema=schema, context_value=request)
|
||||||
|
|
||||||
|
def make_coupon_mutation(self, coupon_code, client):
|
||||||
|
mutation = '''
|
||||||
|
mutation Coupon($input: CouponInput!){
|
||||||
|
coupon(input: $input) {
|
||||||
|
success
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
return client.execute(mutation, variables={
|
||||||
|
'input': {
|
||||||
|
'couponCode': coupon_code
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
@patch.object(requests, 'put', return_value=MockResponse(200, data=['200', 'Coupon successfully redeemed']))
|
||||||
|
@patch.object(HepClient, '_customer_orders', return_value=VALID_TEACHERS_ORDERS)
|
||||||
|
@patch.object(HepClient, 'fetch_admin_token', return_value={'token': 'AABBCCDDEE**44566'})
|
||||||
|
def test_user_has_valid_coupon(self, admin_mock, orders_mock, response_mock):
|
||||||
|
result = self.make_coupon_mutation('COUPON--1234', self.client)
|
||||||
|
|
||||||
|
user_role_key = self.user.user_roles.get(user=self.user).role.key
|
||||||
|
self.assertEqual(user_role_key, Role.objects.TEACHER_KEY)
|
||||||
|
license = License.objects.get(licensee=self.user)
|
||||||
|
self.assertIsNotNone(license)
|
||||||
|
|
||||||
|
school_class = SchoolClass.objects.get(users__in=[self.user])
|
||||||
|
self.assertIsNotNone(school_class)
|
||||||
|
|
||||||
|
self.assertTrue(result.get('data').get('coupon').get('success'))
|
||||||
|
self.assertTrue(self.user.is_authenticated)
|
||||||
|
|
||||||
|
@patch.object(requests, 'put', return_value=MockResponse(200, data=['201', 'Invalid Coupon']))
|
||||||
|
def test_user_has_invalid_coupon(self, response_mock):
|
||||||
|
result = self.make_coupon_mutation('COUPON--1234', self.client)
|
||||||
|
|
||||||
|
self.assertEqual(result.get('errors')[0].get('message'), 'invalid_coupon')
|
||||||
|
|
||||||
|
@patch.object(requests, 'put', return_value=MockResponse(200, data=['201', 'Invalid Coupon']))
|
||||||
|
def test_unauthenticated_user_cannot_redeem(self, response_mock):
|
||||||
|
|
||||||
|
request = RequestFactory().post('/')
|
||||||
|
middleware = SessionMiddleware()
|
||||||
|
middleware.process_request(request)
|
||||||
|
request.session.save()
|
||||||
|
client = Client(schema=schema, context_value=request)
|
||||||
|
|
||||||
|
result = self.make_coupon_mutation('COUPON--1234', client)
|
||||||
|
|
||||||
|
self.assertEqual(result.get('errors')[0].get('message'), 'not_authenticated')
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
import json
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
from core.hep_client import HepClient, TEACHER_EDITION_DURATION
|
||||||
|
|
||||||
|
|
||||||
|
class HepClientTestCases(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.hep_client = HepClient()
|
||||||
|
self.now = datetime.now()
|
||||||
|
|
||||||
|
def test_has_no_valid_product(self):
|
||||||
|
products = [
|
||||||
|
{
|
||||||
|
'edition': 'teacher',
|
||||||
|
'raw': {},
|
||||||
|
'activated': self.now - timedelta(2*TEACHER_EDITION_DURATION),
|
||||||
|
'status': 'complete'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'edition': 'teacher',
|
||||||
|
'raw': {},
|
||||||
|
'activated': self.now - timedelta(3 * TEACHER_EDITION_DURATION),
|
||||||
|
'status': 'complete'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'edition': 'teacher',
|
||||||
|
'raw': {},
|
||||||
|
'activated': self.now - timedelta(4 * TEACHER_EDITION_DURATION),
|
||||||
|
'status': 'complete'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
relevant_product = self.hep_client._get_relevant_product(products)
|
||||||
|
self.assertIsNone(relevant_product)
|
||||||
|
|
||||||
|
def test_has_no_not_completed_product(self):
|
||||||
|
products = [
|
||||||
|
{
|
||||||
|
'edition': 'teacher',
|
||||||
|
'raw': {},
|
||||||
|
'activated': self.now - timedelta(7),
|
||||||
|
'status': 'not'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
relevant_product = self.hep_client._get_relevant_product(products)
|
||||||
|
self.assertIsNone(relevant_product)
|
||||||
|
|
||||||
|
def test_has_valid_product(self):
|
||||||
|
products = [
|
||||||
|
{
|
||||||
|
'edition': 'teacher',
|
||||||
|
'raw': {
|
||||||
|
'id': 0
|
||||||
|
},
|
||||||
|
'activated': self.now - timedelta(7),
|
||||||
|
'status': 'complete'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'edition': 'teacher',
|
||||||
|
'raw': {
|
||||||
|
'id': 1
|
||||||
|
},
|
||||||
|
'activated': self.now - timedelta(3 * TEACHER_EDITION_DURATION),
|
||||||
|
'status': 'complete'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'edition': 'teacher',
|
||||||
|
'raw': {
|
||||||
|
'id': 2
|
||||||
|
},
|
||||||
|
'activated': self.now - timedelta(4 * TEACHER_EDITION_DURATION),
|
||||||
|
'status': 'complete'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
relevant_product = self.hep_client._get_relevant_product(products)
|
||||||
|
self.assertEqual(relevant_product['raw']['id'], 0)
|
||||||
|
|
||||||
|
def test_has_multiple_valid_teacher_products_but_only_one_active(self):
|
||||||
|
products = [
|
||||||
|
{
|
||||||
|
'edition': 'teacher',
|
||||||
|
'raw': {
|
||||||
|
'id': 0
|
||||||
|
},
|
||||||
|
'activated': self.now - timedelta(7),
|
||||||
|
'status': 'complete'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'edition': 'teacher',
|
||||||
|
'raw': {
|
||||||
|
'id': 1
|
||||||
|
},
|
||||||
|
'activated': self.now - timedelta(3 * TEACHER_EDITION_DURATION),
|
||||||
|
'status': 'complete'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'edition': 'teacher',
|
||||||
|
'raw': {
|
||||||
|
'id': 2
|
||||||
|
},
|
||||||
|
'activated': self.now - timedelta(4 * TEACHER_EDITION_DURATION),
|
||||||
|
'status': 'complete'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
relevant_product = self.hep_client._get_relevant_product(products)
|
||||||
|
self.assertEqual(relevant_product['raw']['id'], 0)
|
||||||
|
|
||||||
|
def test_has_valid_student_and_teacher_edition(self):
|
||||||
|
products = [
|
||||||
|
{
|
||||||
|
'edition': 'student',
|
||||||
|
'raw': {
|
||||||
|
'id': 0
|
||||||
|
},
|
||||||
|
'activated': self.now - timedelta(7),
|
||||||
|
'status': 'complete'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'edition': 'teacher',
|
||||||
|
'raw': {
|
||||||
|
'id': 1
|
||||||
|
},
|
||||||
|
'activated': self.now - timedelta(7),
|
||||||
|
'status': 'complete'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
relevant_product = self.hep_client._select_from_teacher_products(products)
|
||||||
|
self.assertEqual(relevant_product['raw']['id'], 1)
|
||||||
|
|
||||||
|
def test_product_is_active(self):
|
||||||
|
|
||||||
|
expiry_date = self.now + timedelta(3)
|
||||||
|
|
||||||
|
is_active = HepClient.is_product_active(expiry_date, 'teacher')
|
||||||
|
self.assertTrue(is_active)
|
||||||
|
|
||||||
|
def test_product_is_not_active(self):
|
||||||
|
|
||||||
|
expiry_date = self.now - timedelta(3 * TEACHER_EDITION_DURATION)
|
||||||
|
|
||||||
|
is_active = HepClient.is_product_active(expiry_date, 'teacher')
|
||||||
|
self.assertFalse(is_active)
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase
|
||||||
from django.core import management
|
from django.core import management
|
||||||
|
|
||||||
from users.models import User, Role
|
from users.models import User, Role
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from core.factories import UserFactory
|
||||||
|
from core.utils import is_private_api_call_allowed
|
||||||
|
|
||||||
|
|
||||||
|
class MiddlewareTestCase(TestCase):
|
||||||
|
def test_user_with_license_can_see_private_api(self):
|
||||||
|
|
||||||
|
tomorrow = timezone.now() + timedelta(1)
|
||||||
|
user = UserFactory(username='aschiman@ch.ch')
|
||||||
|
user.license_expiry_date = tomorrow
|
||||||
|
|
||||||
|
body = b'"{mutation {\\n addRoom}"'
|
||||||
|
|
||||||
|
self.assertTrue(is_private_api_call_allowed(user, body))
|
||||||
|
|
||||||
|
def test_user_without_valid_license_cannot_see_private_api(self):
|
||||||
|
|
||||||
|
yesterday = timezone.now() - timedelta(1)
|
||||||
|
user = UserFactory(username='aschiman@ch.ch', hep_id=23)
|
||||||
|
user.license_expiry_date = yesterday
|
||||||
|
|
||||||
|
body = b'"{mutation {\\n addRoom}"'
|
||||||
|
|
||||||
|
self.assertFalse(is_private_api_call_allowed(user, body))
|
||||||
|
|
||||||
|
def test_logout_is_allowed_without_valid_license(self):
|
||||||
|
|
||||||
|
yesterday = timezone.now() - timedelta(1)
|
||||||
|
user = UserFactory(username='aschiman@ch.ch')
|
||||||
|
user.license_expiry_date = yesterday
|
||||||
|
|
||||||
|
body = b'"{mutation { logout {"'
|
||||||
|
|
||||||
|
self.assertTrue(is_private_api_call_allowed(user, body))
|
||||||
|
|
||||||
|
def test_me_query_is_allowed_without_valid_license(self):
|
||||||
|
|
||||||
|
yesterday = timezone.now() - timedelta(1)
|
||||||
|
user = UserFactory(username='aschiman@ch.ch')
|
||||||
|
user.license_expiry_date = yesterday
|
||||||
|
|
||||||
|
body = b'"{query { me {"'
|
||||||
|
|
||||||
|
self.assertTrue(is_private_api_call_allowed(user, body))
|
||||||
|
|
@ -2,41 +2,19 @@ from django.conf import settings
|
||||||
from django.conf.urls import url, include
|
from django.conf.urls import url, include
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.views import PasswordResetView
|
from django.urls import re_path
|
||||||
from django.urls import re_path, path
|
|
||||||
from django.views.generic import RedirectView
|
from django.views.generic import RedirectView
|
||||||
from wagtail.admin import urls as wagtailadmin_urls
|
from wagtail.admin import urls as wagtailadmin_urls
|
||||||
from wagtail.core import urls as wagtail_urls
|
from wagtail.core import urls as wagtail_urls
|
||||||
|
|
||||||
from core import views
|
from core import views
|
||||||
from core.views import LegacySetPasswordView, LegacySetPasswordDoneView, LegacySetPasswordConfirmView,\
|
|
||||||
LegacySetPasswordCompleteView, SetPasswordView, SetPasswordDoneView, SetPasswordConfirmView, SetPasswordCompleteView
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# django admin
|
# django admin
|
||||||
url(r'^guru/', admin.site.urls),
|
url(r'^guru/', admin.site.urls),
|
||||||
url(r'^statistics/', include('statistics.urls', namespace='statistics')),
|
url(r'^statistics/', include('statistics.urls', namespace='statistics')),
|
||||||
|
|
||||||
# legacy - will be removed
|
|
||||||
# forgot password
|
|
||||||
path('accounts/password_reset/',
|
|
||||||
PasswordResetView.as_view(html_email_template_name='registration/password_reset_email.html')),
|
|
||||||
path(r'accounts/', include('django.contrib.auth.urls')),
|
|
||||||
|
|
||||||
# set password
|
|
||||||
path('welcome/', LegacySetPasswordView.as_view(), name='set_password'),
|
|
||||||
path('set-password/done/', LegacySetPasswordDoneView.as_view(), name='set_password_done'),
|
|
||||||
path('set-password/<uidb64>/<token>/', LegacySetPasswordConfirmView.as_view(), name='set_password_confirm'),
|
|
||||||
path('set-password/complete/', LegacySetPasswordCompleteView.as_view(), name='set_password_complete'),
|
|
||||||
|
|
||||||
# set password upon registration
|
|
||||||
path('registration/welcome/', SetPasswordView.as_view(), name='registration_set_password'),
|
|
||||||
path('registration/set-password/done/', SetPasswordDoneView.as_view(), name='registration_set_password_done'),
|
|
||||||
path('registration/set-password/<uidb64>/<token>/', SetPasswordConfirmView.as_view(),
|
|
||||||
name='registration_set_password_confirm'),
|
|
||||||
path('registration/set-password/complete/', SetPasswordCompleteView.as_view(),
|
|
||||||
name='registration_set_password_complete'),
|
|
||||||
|
|
||||||
# wagtail
|
# wagtail
|
||||||
url(r'^cms/', include(wagtailadmin_urls)),
|
url(r'^cms/', include(wagtailadmin_urls)),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from api.utils import get_object
|
from api.utils import get_object
|
||||||
from users.models import SchoolClass
|
from users.models import SchoolClass
|
||||||
|
|
||||||
|
|
@ -18,3 +22,29 @@ def set_visible_for(block, visibility_list):
|
||||||
block.visible_for.remove(school_class)
|
block.visible_for.remove(school_class)
|
||||||
else:
|
else:
|
||||||
block.visible_for.add(school_class)
|
block.visible_for.add(school_class)
|
||||||
|
|
||||||
|
|
||||||
|
def is_private_api_call_allowed(user, body):
|
||||||
|
# logged in users should only be able to access all resources if they have a valid license
|
||||||
|
# logged in users without valid license have only access to logout, me & coupon mutations
|
||||||
|
|
||||||
|
body_unicode = body.decode('utf-8')
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not user.hep_id:
|
||||||
|
return True
|
||||||
|
except AttributeError:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# logout, me and coupon resources are always allowed. Even if the user has no valid license
|
||||||
|
if re.search(r"mutation\s*.*\s*logout\s*{", body_unicode) or re.search(r"query\s*.*\s*me\s*{", body_unicode)\
|
||||||
|
or re.search(r"mutation\s*Coupon", body_unicode):
|
||||||
|
return True
|
||||||
|
|
||||||
|
license_expiry = user.license_expiry_date
|
||||||
|
|
||||||
|
# all other resources are denied if the license is not valid
|
||||||
|
if license_expiry is None or license_expiry < timezone.now():
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,13 @@ from django.http.response import HttpResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||||
|
from django.views.generic import TemplateView
|
||||||
from graphene_django.views import GraphQLView
|
from graphene_django.views import GraphQLView
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from core.hep_client import HepClient
|
||||||
|
from core.models import AdminData
|
||||||
|
|
||||||
|
|
||||||
class PrivateGraphQLView(LoginRequiredMixin, GraphQLView):
|
class PrivateGraphQLView(LoginRequiredMixin, GraphQLView):
|
||||||
pass
|
pass
|
||||||
|
|
@ -26,50 +30,18 @@ def home(request):
|
||||||
return render(request, 'index.html', {})
|
return render(request, 'index.html', {})
|
||||||
|
|
||||||
|
|
||||||
class SetPasswordView(PasswordResetView):
|
class ConfirmationKeyDisplayView(TemplateView):
|
||||||
html_email_template_name = 'registration/registration_set_password_email.html'
|
template_name = 'confirmation_key.html'
|
||||||
subject_template_name = 'registration/registration_set_password_subject.txt'
|
|
||||||
success_url = reverse_lazy('registration_set_password_done')
|
|
||||||
template_name = 'registration/registration_set_password_form.html'
|
|
||||||
title = _('Password setzen')
|
|
||||||
|
|
||||||
|
def get_context_data(self, *args, **kwargs):
|
||||||
|
|
||||||
class SetPasswordDoneView(PasswordResetDoneView):
|
email = self.request.GET.get('email', '')
|
||||||
template_name = 'registration/registration_set_password_done.html'
|
|
||||||
title = _('Password setzen versandt')
|
|
||||||
|
|
||||||
|
hep_client = HepClient()
|
||||||
|
admin_token = AdminData.objects.get_admin_token()
|
||||||
|
hep_user = hep_client.customers_search(admin_token, email)
|
||||||
|
|
||||||
class SetPasswordConfirmView(PasswordResetConfirmView):
|
context = super().get_context_data(**kwargs)
|
||||||
success_url = reverse_lazy('registration_set_password_complete')
|
context['confirmation_key'] = hep_user['confirmation']
|
||||||
template_name = 'registration/registration_set_password_confirm.html'
|
context['hep_id'] = hep_user['id']
|
||||||
title = _('Gib ein Passwort ein')
|
return context
|
||||||
|
|
||||||
|
|
||||||
class SetPasswordCompleteView(PasswordResetCompleteView):
|
|
||||||
template_name = 'registration/registration_set_password_complete.html'
|
|
||||||
title = _('Passwort setzen erfolgreich')
|
|
||||||
|
|
||||||
|
|
||||||
# legacy
|
|
||||||
class LegacySetPasswordView(PasswordResetView):
|
|
||||||
html_email_template_name = 'registration/set_password_email.html'
|
|
||||||
subject_template_name = 'registration/set_password_subject.txt'
|
|
||||||
success_url = reverse_lazy('set_password_done')
|
|
||||||
template_name = 'registration/set_password_form.html'
|
|
||||||
title = _('Password setzen')
|
|
||||||
|
|
||||||
|
|
||||||
class LegacySetPasswordDoneView(PasswordResetDoneView):
|
|
||||||
template_name = 'registration/set_password_done.html'
|
|
||||||
title = _('Password setzen versandt')
|
|
||||||
|
|
||||||
|
|
||||||
class LegacySetPasswordConfirmView(PasswordResetConfirmView):
|
|
||||||
success_url = reverse_lazy('set_password_complete')
|
|
||||||
template_name = 'registration/set_password_confirm.html'
|
|
||||||
title = _('Gib ein Passwort ein')
|
|
||||||
|
|
||||||
|
|
||||||
class LegacySetPasswordCompleteView(PasswordResetCompleteView):
|
|
||||||
template_name = 'registration/set_password_complete.html'
|
|
||||||
title = _('Passwort setzen erfolgreich')
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# ITerativ GmbH
|
|
||||||
# http://www.iterativ.ch/
|
|
||||||
#
|
|
||||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
|
||||||
#
|
|
||||||
# Created on 2019-10-10
|
|
||||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
from registration.models import LicenseType, License
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(LicenseType)
|
|
||||||
class LicenseTypeAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('id', 'name', 'key', 'for_role', 'active')
|
|
||||||
list_filter = ('for_role', 'active')
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(License)
|
|
||||||
class LicenseAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('license_type', 'licensee')
|
|
||||||
list_filter = ('license_type', 'licensee')
|
|
||||||
raw_id_fields = ('licensee',)
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# ITerativ GmbH
|
|
||||||
# http://www.iterativ.ch/
|
|
||||||
#
|
|
||||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
|
||||||
#
|
|
||||||
# Created on 2019-10-08
|
|
||||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
|
||||||
import random
|
|
||||||
|
|
||||||
import factory
|
|
||||||
|
|
||||||
from registration.models import LicenseType, License
|
|
||||||
|
|
||||||
|
|
||||||
class LicenseTypeFactory(factory.django.DjangoModelFactory):
|
|
||||||
class Meta:
|
|
||||||
model = LicenseType
|
|
||||||
|
|
||||||
name = factory.Sequence(lambda n: 'license-{}'.format(n))
|
|
||||||
active = True
|
|
||||||
key = factory.Sequence(lambda n: "license-key-%03d" % n)
|
|
||||||
description = factory.Sequence(lambda n: "Some description %03d" % n)
|
|
||||||
|
|
||||||
|
|
||||||
class LicenseFactory(factory.django.DjangoModelFactory):
|
|
||||||
class Meta:
|
|
||||||
model = License
|
|
||||||
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# ITerativ GmbH
|
|
||||||
# http://www.iterativ.ch/
|
|
||||||
#
|
|
||||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
|
||||||
#
|
|
||||||
# Created on 2019-10-23
|
|
||||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# ITerativ GmbH
|
|
||||||
# http://www.iterativ.ch/
|
|
||||||
#
|
|
||||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
|
||||||
#
|
|
||||||
# Created on 2019-10-23
|
|
||||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# ITerativ GmbH
|
|
||||||
# http://www.iterativ.ch/
|
|
||||||
#
|
|
||||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
|
||||||
#
|
|
||||||
# Created on 2019-10-23
|
|
||||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.management import BaseCommand
|
|
||||||
|
|
||||||
from registration.models import LicenseType
|
|
||||||
from users.models import Role
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
|
|
||||||
try:
|
|
||||||
role = Role.objects.get(key=Role.objects.TEACHER_KEY)
|
|
||||||
except Role.DoesNotExist:
|
|
||||||
print("LicenseType requires that a Teacher Role exsits")
|
|
||||||
|
|
||||||
LicenseType.objects.create(name='dummy_license',
|
|
||||||
for_role=role,
|
|
||||||
active=True,
|
|
||||||
key='c1fa2e2a-2e27-480d-8469-2e88414c4ad8',
|
|
||||||
description='dummy license')
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Generated by Django 2.0.6 on 2020-02-04 13:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0010_schoolclass_code'),
|
||||||
|
('registration', '0002_auto_20191010_0905'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='licensetype',
|
||||||
|
name='for_role',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='license',
|
||||||
|
name='license_type',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='license',
|
||||||
|
name='expire_date',
|
||||||
|
field=models.DateField(null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='license',
|
||||||
|
name='for_role',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='users.Role'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='license',
|
||||||
|
name='raw',
|
||||||
|
field=models.TextField(default=''),
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='LicenseType',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# Generated by Django 2.1.15 on 2020-02-20 10:39
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('registration', '0003_auto_20200204_1331'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='license',
|
||||||
|
name='for_role',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='license',
|
||||||
|
name='licensee',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='License',
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# ITerativ GmbH
|
|
||||||
# http://www.iterativ.ch/
|
|
||||||
#
|
|
||||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
|
||||||
#
|
|
||||||
# Created on 2019-10-08
|
|
||||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
from users.managers import RoleManager
|
|
||||||
from users.models import Role, User
|
|
||||||
|
|
||||||
|
|
||||||
class LicenseType(models.Model):
|
|
||||||
|
|
||||||
name = models.CharField(_('License name'), max_length=255, blank=False, null=False)
|
|
||||||
for_role = models.ForeignKey(Role, blank=False, null=False, on_delete=models.CASCADE)
|
|
||||||
key = models.CharField(max_length=128, blank=False, null=False, unique=True)
|
|
||||||
active = models.BooleanField(_('License active'), default=False)
|
|
||||||
description = models.TextField(_('Description'), default="")
|
|
||||||
|
|
||||||
def is_teacher_license(self):
|
|
||||||
return self.for_role.key == RoleManager.TEACHER_KEY
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '%s - role: %s' % (self.name, self.for_role)
|
|
||||||
|
|
||||||
|
|
||||||
class License(models.Model):
|
|
||||||
license_type = models.ForeignKey(LicenseType, blank=False, null=False, on_delete=models.CASCADE)
|
|
||||||
licensee = models.ForeignKey(User, blank=False, null=True, on_delete=models.CASCADE)
|
|
||||||
|
|
@ -8,87 +8,60 @@
|
||||||
# Created on 2019-10-08
|
# Created on 2019-10-08
|
||||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||||
import graphene
|
import graphene
|
||||||
|
from django.contrib.auth import login
|
||||||
from graphene import relay
|
from graphene import relay
|
||||||
|
|
||||||
from core.views import SetPasswordView
|
from core.hep_client import HepClient, HepClientException
|
||||||
from registration.models import License
|
from core.models import AdminData
|
||||||
from registration.serializers import RegistrationSerializer
|
from users.user_signup_login_handler import handle_user_and_verify_products, UNKNOWN_ERROR, NO_VALID_LICENSE
|
||||||
from users.models import User, Role, UserRole, SchoolClass, SchoolClassMember
|
|
||||||
|
|
||||||
|
|
||||||
class PublicFieldError(graphene.ObjectType):
|
|
||||||
code = graphene.String()
|
|
||||||
|
|
||||||
|
|
||||||
class MutationError(graphene.ObjectType):
|
|
||||||
field = graphene.String()
|
|
||||||
errors = graphene.List(PublicFieldError)
|
|
||||||
|
|
||||||
|
|
||||||
class Registration(relay.ClientIDMutation):
|
class Registration(relay.ClientIDMutation):
|
||||||
class Input:
|
class Input:
|
||||||
firstname_input = graphene.String()
|
confirmation_key = graphene.String()
|
||||||
lastname_input = graphene.String()
|
user_id = graphene.Int()
|
||||||
email_input = graphene.String()
|
|
||||||
license_key_input = graphene.String()
|
|
||||||
|
|
||||||
success = graphene.Boolean()
|
success = graphene.Boolean()
|
||||||
errors = graphene.List(MutationError) # todo: change for consistency
|
message = graphene.String()
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||||
first_name = kwargs.get('firstname_input')
|
confirmation_key = kwargs.get('confirmation_key')
|
||||||
last_name = kwargs.get('lastname_input')
|
user_id = kwargs.get('user_id')
|
||||||
email = kwargs.get('email_input')
|
|
||||||
license_key = kwargs.get('license_key_input')
|
|
||||||
registration_data = {
|
|
||||||
'first_name': first_name,
|
|
||||||
'last_name': last_name,
|
|
||||||
'email': email,
|
|
||||||
'license_key': license_key,
|
|
||||||
}
|
|
||||||
|
|
||||||
serializer = RegistrationSerializer(data=registration_data)
|
hep_client = HepClient()
|
||||||
|
admin_token = AdminData.objects.get_admin_token()
|
||||||
|
|
||||||
if serializer.is_valid():
|
try:
|
||||||
user = User.objects.create_user_with_random_password(serializer.data['first_name'],
|
hep_client.customer_activate(confirmation_key, user_id)
|
||||||
serializer.data['last_name'],
|
user_data = hep_client.customers_by_id(admin_token, user_id)
|
||||||
serializer.data['email'])
|
# double check if user has verified his email. If the "confirmation" field is present, the email address
|
||||||
sb_license = License.objects.create(licensee=user, license_type=serializer.context['license_type'])
|
# is not verified.
|
||||||
|
if 'confirmation' in user_data:
|
||||||
|
return cls.return_fail_registration_msg('invalid_key')
|
||||||
|
except HepClientException:
|
||||||
|
return cls.return_fail_registration_msg('unknown_error')
|
||||||
|
|
||||||
if sb_license.license_type.is_teacher_license():
|
user, status_msg = handle_user_and_verify_products(user_data)
|
||||||
teacher_role = Role.objects.get(key=Role.objects.TEACHER_KEY)
|
|
||||||
UserRole.objects.get_or_create(user=user, role=teacher_role)
|
if user:
|
||||||
default_class_name = SchoolClass.generate_default_group_name(user=user)
|
login(info.context, user)
|
||||||
default_class = SchoolClass.objects.create(name=default_class_name)
|
|
||||||
SchoolClassMember.objects.create(
|
if status_msg:
|
||||||
user=user,
|
if status_msg == NO_VALID_LICENSE:
|
||||||
school_class=default_class
|
return cls(success=True, message=NO_VALID_LICENSE)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
student_role = Role.objects.get(key=Role.objects.STUDENT_KEY)
|
return cls.return_fail_registration_msg(status_msg)
|
||||||
UserRole.objects.get_or_create(user=user, role=student_role)
|
|
||||||
|
|
||||||
password_reset_view = SetPasswordView()
|
return cls(success=True, message='success')
|
||||||
password_reset_view.request = info.context
|
|
||||||
form = password_reset_view.form_class({'email': user.email})
|
|
||||||
|
|
||||||
if not form.is_valid():
|
@classmethod
|
||||||
return cls(success=False, errors=form.errors)
|
def return_fail_registration_msg(cls, message):
|
||||||
|
if message == UNKNOWN_ERROR:
|
||||||
|
raise Exception(message)
|
||||||
|
|
||||||
password_reset_view.form_valid(form)
|
return cls(success=False, message=message)
|
||||||
|
|
||||||
return cls(success=True)
|
|
||||||
|
|
||||||
errors = []
|
|
||||||
for key, value in serializer.errors.items():
|
|
||||||
error = MutationError(field=key, errors=[])
|
|
||||||
for field_error in serializer.errors[key]:
|
|
||||||
error.errors.append(PublicFieldError(code=field_error.code))
|
|
||||||
|
|
||||||
errors.append(error)
|
|
||||||
|
|
||||||
return cls(success=False, errors=errors)
|
|
||||||
|
|
||||||
|
|
||||||
class RegistrationMutations:
|
class RegistrationMutations:
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
#
|
|
||||||
# ITerativ GmbH
|
|
||||||
# http://www.iterativ.ch/
|
|
||||||
#
|
|
||||||
# Copyright (c) 2019 ITerativ GmbH. All rights reserved.
|
|
||||||
#
|
|
||||||
# Created on 2019-10-08
|
|
||||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from rest_framework import serializers
|
|
||||||
from rest_framework.fields import CharField, EmailField
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from registration.models import License, LicenseType
|
|
||||||
|
|
||||||
|
|
||||||
class RegistrationSerializer(serializers.Serializer):
|
|
||||||
first_name = CharField(allow_blank=False)
|
|
||||||
last_name = CharField(allow_blank=False)
|
|
||||||
email = EmailField(allow_blank=False)
|
|
||||||
license_key = CharField(allow_blank=False)
|
|
||||||
skillbox_license = None
|
|
||||||
|
|
||||||
def validate_email(self, value):
|
|
||||||
lower_email = value.lower()
|
|
||||||
# the email is used as username
|
|
||||||
if len(get_user_model().objects.filter(username=lower_email)) > 0:
|
|
||||||
raise serializers.ValidationError(_(u'Diese E-Mail ist bereits registriert'))
|
|
||||||
elif len(get_user_model().objects.filter(email=lower_email)) > 0:
|
|
||||||
raise serializers.ValidationError(_(u'Dieser E-Mail ist bereits registriert'))
|
|
||||||
else:
|
|
||||||
return lower_email
|
|
||||||
|
|
||||||
def validate_license_key(self, value):
|
|
||||||
license_types = LicenseType.objects.filter(key=value, active=True)
|
|
||||||
if len(license_types) == 0:
|
|
||||||
raise serializers.ValidationError(_(u'Die Lizenznummer ist ungültig'))
|
|
||||||
|
|
||||||
self.context['license_type'] = license_types[0] # Assuming there is just ONE license per key
|
|
||||||
return value
|
|
||||||
|
|
@ -7,32 +7,29 @@
|
||||||
#
|
#
|
||||||
# Created on 2019-10-08
|
# Created on 2019-10-08
|
||||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||||
from django.core import mail
|
from unittest.mock import patch
|
||||||
|
|
||||||
from django.contrib.sessions.middleware import SessionMiddleware
|
from django.contrib.sessions.middleware import SessionMiddleware
|
||||||
from django.test import TestCase, RequestFactory
|
from django.test import TestCase, RequestFactory
|
||||||
from graphene.test import Client
|
from graphene.test import Client
|
||||||
|
|
||||||
from api.schema import schema
|
from api.schema import schema
|
||||||
from registration.factories import LicenseTypeFactory, LicenseFactory
|
from core.hep_client import HepClient
|
||||||
from registration.models import License
|
from core.tests.mock_hep_data_factory import ME_DATA, VALID_TEACHERS_ORDERS
|
||||||
from users.managers import RoleManager
|
from users.models import License
|
||||||
from users.models import Role, User, UserRole, SchoolClass
|
from users.models import User, Role, SchoolClass
|
||||||
|
|
||||||
|
INVALID_KEY_ME = dict(ME_DATA)
|
||||||
|
|
||||||
|
INVALID_KEY_ME['confirmation'] = 'abddddddd'
|
||||||
|
|
||||||
|
|
||||||
class RegistrationTests(TestCase):
|
class RegistrationTests(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
||||||
self.teacher_role = Role.objects.create(key=Role.objects.TEACHER_KEY, name="Teacher Role")
|
|
||||||
self.student_role = Role.objects.create(key=Role.objects.STUDENT_KEY, name="Student Role")
|
|
||||||
|
|
||||||
self.teacher_license_type = LicenseTypeFactory(for_role=self.teacher_role)
|
|
||||||
self.student_license_type = LicenseTypeFactory(for_role=self.student_role)
|
|
||||||
|
|
||||||
self.teacher_license = LicenseFactory(license_type=self.teacher_license_type)
|
|
||||||
self.student_license = LicenseFactory(license_type=self.student_license_type)
|
|
||||||
|
|
||||||
request = RequestFactory().post('/')
|
request = RequestFactory().post('/')
|
||||||
|
|
||||||
|
Role.objects.create_default_roles()
|
||||||
|
|
||||||
self.email = 'sepp@skillbox.iterativ.ch'
|
self.email = 'sepp@skillbox.iterativ.ch'
|
||||||
self.first_name = 'Sepp'
|
self.first_name = 'Sepp'
|
||||||
self.last_name = 'Feuz'
|
self.last_name = 'Feuz'
|
||||||
|
|
@ -43,78 +40,62 @@ class RegistrationTests(TestCase):
|
||||||
request.session.save()
|
request.session.save()
|
||||||
self.client = Client(schema=schema, context_value=request)
|
self.client = Client(schema=schema, context_value=request)
|
||||||
|
|
||||||
def make_register_mutation(self, first_name, last_name, email, license_key):
|
def make_register_mutation(self, confirmation_key, user_id):
|
||||||
mutation = '''
|
mutation = '''
|
||||||
mutation Registration($input: RegistrationInput!){
|
mutation Registration($input: RegistrationInput!){
|
||||||
registration(input: $input) {
|
registration(input: $input) {
|
||||||
success
|
success
|
||||||
errors {
|
message
|
||||||
field
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
|
|
||||||
return self.client.execute(mutation, variables={
|
return self.client.execute(mutation, variables={
|
||||||
'input': {
|
'input': {
|
||||||
'firstnameInput': first_name,
|
'confirmationKey': confirmation_key,
|
||||||
'lastnameInput': last_name,
|
'userId': user_id
|
||||||
'emailInput': email,
|
|
||||||
'licenseKeyInput': license_key,
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
def _assert_user_registration(self, count, email, role_key):
|
@patch.object(HepClient, 'customer_activate', return_value="Response")
|
||||||
users = User.objects.filter(username=self.email)
|
@patch.object(HepClient, 'customers_by_id', return_value=ME_DATA)
|
||||||
self.assertEqual(len(users), count)
|
@patch.object(HepClient, 'myskillbox_product_for_customer', return_value=None)
|
||||||
user_roles = UserRole.objects.filter(user__email=email, role__key=role_key)
|
@patch.object(HepClient, 'fetch_admin_token', return_value=b'"AABBCCDDEE**44566"')
|
||||||
self.assertEqual(len(user_roles), count)
|
def test_user_can_register_with_valid_confirmation_key_and_no_license(self, admin_mock, customer_by_id_mock,
|
||||||
licenses = License.objects.filter(licensee__email=email, license_type__for_role__key=role_key)
|
product_mock, customer_mock):
|
||||||
self.assertEqual(len(licenses), count)
|
|
||||||
|
result = self.make_register_mutation('CONFIRMATION_KEY', 1)
|
||||||
|
|
||||||
def test_user_can_register_as_teacher(self):
|
|
||||||
self._assert_user_registration(0, self.email, RoleManager.TEACHER_KEY)
|
|
||||||
school_classes = SchoolClass.objects.filter(name__startswith='Meine Klasse')
|
|
||||||
self.assertEqual(len(school_classes), 0)
|
|
||||||
result = self.make_register_mutation(self.first_name, self.last_name, self.email, self.teacher_license_type.key)
|
|
||||||
self.assertTrue(result.get('data').get('registration').get('success'))
|
self.assertTrue(result.get('data').get('registration').get('success'))
|
||||||
self._assert_user_registration(1, self.email, RoleManager.TEACHER_KEY)
|
self.assertEqual(result.get('data').get('registration').get('message'), 'no_valid_license')
|
||||||
school_classes = SchoolClass.objects.filter(name__startswith='Meine Klasse')
|
|
||||||
self.assertEqual(len(school_classes), 1)
|
@patch.object(HepClient, 'customer_activate', return_value="Response")
|
||||||
user = User.objects.get(email=self.email)
|
@patch.object(HepClient, 'customers_by_id', return_value=INVALID_KEY_ME)
|
||||||
self.assertTrue(school_classes[0].is_user_in_schoolclass(user))
|
@patch.object(HepClient, 'fetch_admin_token', return_value=b'"AABBCCDDEE**44566"')
|
||||||
self.assertEqual(len(mail.outbox), 1)
|
def test_user_cannot_register_with_invalid_key(self, admin_mock, confirmation_mock, id_mock):
|
||||||
self.assertEqual(mail.outbox[0].subject, 'Myskillbox: E-Mail bestätigen und Passwort setzen')
|
|
||||||
|
result = self.make_register_mutation('CONFIRMATION_KEY', 1)
|
||||||
|
|
||||||
|
self.assertFalse(result.get('data').get('registration').get('success'))
|
||||||
|
self.assertEqual(result.get('data').get('registration').get('message'), 'invalid_key')
|
||||||
|
|
||||||
|
@patch.object(HepClient, '_customer_orders', return_value=VALID_TEACHERS_ORDERS)
|
||||||
|
@patch.object(HepClient, 'customer_activate', return_value="Response")
|
||||||
|
@patch.object(HepClient, 'customers_by_id', return_value=ME_DATA)
|
||||||
|
@patch.object(HepClient, 'fetch_admin_token', return_value=b'"AABBCCDDEE**44566"')
|
||||||
|
def test_teacher_can_register_with_remote_license(self, admin_mock, id_mock, activate_mock, orders_mock):
|
||||||
|
result = self.make_register_mutation('CONFIRMATION_KEY', 1)
|
||||||
|
|
||||||
|
user = User.objects.get(email=ME_DATA['email'])
|
||||||
|
|
||||||
|
user_role_key = user.user_roles.get(user=user).role.key
|
||||||
|
self.assertEqual(user_role_key, Role.objects.TEACHER_KEY)
|
||||||
|
|
||||||
|
license = License.objects.get(licensee=user)
|
||||||
|
self.assertEqual(license.for_role.key, Role.objects.TEACHER_KEY)
|
||||||
|
|
||||||
|
school_class = SchoolClass.objects.get(users__in=[user])
|
||||||
|
self.assertIsNotNone(school_class)
|
||||||
|
|
||||||
def test_user_can_register_as_student(self):
|
|
||||||
self._assert_user_registration(0, self.email, RoleManager.STUDENT_KEY)
|
|
||||||
result = self.make_register_mutation(self.first_name, self.last_name, self.email, self.student_license_type.key)
|
|
||||||
self.assertTrue(result.get('data').get('registration').get('success'))
|
self.assertTrue(result.get('data').get('registration').get('success'))
|
||||||
self._assert_user_registration(1, self.email, RoleManager.STUDENT_KEY)
|
self.assertTrue(user.is_authenticated)
|
||||||
|
|
||||||
def test_existing_user_cannot_register(self):
|
|
||||||
self._assert_user_registration(0, self.email, RoleManager.STUDENT_KEY)
|
|
||||||
self.make_register_mutation(self.first_name, self.last_name, self.email, self.student_license_type.key)
|
|
||||||
result = self.make_register_mutation(self.first_name, self.last_name, self.email, self.student_license_type.key)
|
|
||||||
self.assertEqual(result.get('data').get('registration').get('errors')[0].get('field'), 'email')
|
|
||||||
|
|
||||||
def test_existing_user_cannot_register_with_uppercase_email(self):
|
|
||||||
self._assert_user_registration(0, self.email, RoleManager.STUDENT_KEY)
|
|
||||||
self.make_register_mutation(self.first_name, self.last_name, self.email.upper(), self.student_license_type.key)
|
|
||||||
result = self.make_register_mutation(self.first_name, self.last_name, self.email, self.student_license_type.key)
|
|
||||||
self.assertEqual(result.get('data').get('registration').get('errors')[0].get('field'), 'email')
|
|
||||||
|
|
||||||
def test_user_cannot_register_if_firstname_is_missing(self):
|
|
||||||
result = self.make_register_mutation('', self.last_name, self.email, self.teacher_license_type.key)
|
|
||||||
self.assertEqual(result.get('data').get('registration').get('errors')[0].get('field'), 'first_name')
|
|
||||||
self.assertFalse(result.get('data').get('registration').get('success'))
|
|
||||||
|
|
||||||
def test_user_cannot_register_if_lastname_is_missing(self):
|
|
||||||
result = self.make_register_mutation(self.first_name, '', self.email, self.teacher_license_type.key)
|
|
||||||
self.assertEqual(result.get('data').get('registration').get('errors')[0].get('field'), 'last_name')
|
|
||||||
self.assertFalse(result.get('data').get('registration').get('success'))
|
|
||||||
|
|
||||||
def test_user_cannot_register_if_email_is_missing(self):
|
|
||||||
result = self.make_register_mutation(self.first_name, self.last_name, '', self.teacher_license_type.key)
|
|
||||||
self.assertEqual(result.get('data').get('registration').get('errors')[0].get('field'), 'email')
|
|
||||||
self.assertFalse(result.get('data').get('registration').get('success'))
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# ITerativ GmbH
|
||||||
|
# http://www.iterativ.ch/
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020 ITerativ GmbH. All rights reserved.
|
||||||
|
#
|
||||||
|
# Created on 25.02.20
|
||||||
|
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||||
|
import json
|
||||||
|
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from django.test import TestCase, Client
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from core.hep_client import HepClient
|
||||||
|
from core.tests.mock_hep_data_factory import MockResponse
|
||||||
|
|
||||||
|
RESPONSE = {
|
||||||
|
'id': 1234,
|
||||||
|
'confirmation': 'abdc1234',
|
||||||
|
'firstname': 'Pesche',
|
||||||
|
'lastname': 'Zubrüti',
|
||||||
|
'email': 'aschima@ch.ch',
|
||||||
|
'prefix': 'Herr',
|
||||||
|
'gender': 1,
|
||||||
|
'addresses': [
|
||||||
|
{
|
||||||
|
'country_id': 'CH',
|
||||||
|
'street': ['Weg 1'],
|
||||||
|
'postcode': '1234',
|
||||||
|
'city': 'Äussere Einöde',
|
||||||
|
'firstname': 'Pesche',
|
||||||
|
'lastname': 'Zubrüti',
|
||||||
|
'prefix': 'Herr',
|
||||||
|
'default_shipping': True,
|
||||||
|
'default_billing': True,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
DATA = {
|
||||||
|
'customer': {
|
||||||
|
'firstname': 'Pesche',
|
||||||
|
'lastname': 'Zubrüti',
|
||||||
|
'email': 'aschima@ch.ch',
|
||||||
|
'prefix': 'Herr',
|
||||||
|
'gender': 1,
|
||||||
|
'addresses': [
|
||||||
|
{
|
||||||
|
'country_id': 'CH',
|
||||||
|
'street': ['Weg 1'],
|
||||||
|
'postcode': '1234',
|
||||||
|
'city': 'Äussere Einöde',
|
||||||
|
'firstname': 'Pesche',
|
||||||
|
'lastname': 'Zubrüti',
|
||||||
|
'prefix': 'Herr',
|
||||||
|
'default_shipping': True,
|
||||||
|
'default_billing': True,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'password': '123454abasfd'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ProxyTest(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.client = Client()
|
||||||
|
|
||||||
|
@patch.object(HepClient, 'customer_create', return_value=RESPONSE)
|
||||||
|
def test_proxy_filters_confirmation_key(self, create_mock):
|
||||||
|
|
||||||
|
response = self.client.post(reverse('api:registration:proxy'), json.dumps(DATA), content_type="application/json")
|
||||||
|
found = 'confirmation' in response.json().keys()
|
||||||
|
self.assertFalse(found)
|
||||||
|
|
||||||
|
@patch.object(requests, 'post', return_value=MockResponse(400,
|
||||||
|
data={'message': 'Ein Kunde mit der gleichen E-Mail-Adresse existiert bereits in einer zugeordneten Website.'}))
|
||||||
|
def test_handles_400(self, create_mock):
|
||||||
|
|
||||||
|
response = self.client.post(reverse('api:registration:proxy'), json.dumps(DATA), content_type="application/json")
|
||||||
|
self.assertEquals(response.json()['message'], 'Ein Kunde mit der gleichen E-Mail-Adresse existiert bereits in einer zugeordneten Website.')
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
from django.conf.urls import url
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
|
||||||
|
from registration.view import RegistrationProxyView
|
||||||
|
|
||||||
|
app_name = 'registration'
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^registration/', csrf_exempt(RegistrationProxyView.as_view()), name="proxy"),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# ITerativ GmbH
|
||||||
|
# http://www.iterativ.ch/
|
||||||
|
#
|
||||||
|
# Copyright (c) 2020 ITerativ GmbH. All rights reserved.
|
||||||
|
#
|
||||||
|
# Created on 25.02.20
|
||||||
|
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||||
|
import json
|
||||||
|
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.views import View
|
||||||
|
|
||||||
|
from core.hep_client import HepClient, HepClientException
|
||||||
|
|
||||||
|
|
||||||
|
class RegistrationProxyView(View):
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
hep_client = HepClient()
|
||||||
|
data = json.loads(request.body)
|
||||||
|
data['customer']['group_id'] = 5
|
||||||
|
|
||||||
|
try:
|
||||||
|
hep_data = hep_client.customer_create(data)
|
||||||
|
except HepClientException as e:
|
||||||
|
return JsonResponse(e.args[1], status=e.args[0])
|
||||||
|
|
||||||
|
response_data = hep_data.copy()
|
||||||
|
del response_data['confirmation']
|
||||||
|
|
||||||
|
return JsonResponse(response_data)
|
||||||
|
|
||||||
|
|
@ -2,7 +2,7 @@ from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
|
|
||||||
from users.forms import CustomUserCreationForm, CustomUserChangeForm
|
from users.forms import CustomUserCreationForm, CustomUserChangeForm
|
||||||
from .models import User, SchoolClass, Role, UserRole, UserSetting
|
from .models import User, SchoolClass, Role, UserRole, UserSetting, License
|
||||||
|
|
||||||
|
|
||||||
class SchoolClassInline(admin.TabularInline):
|
class SchoolClassInline(admin.TabularInline):
|
||||||
|
|
@ -68,3 +68,10 @@ admin.site.register(User, CustomUserAdmin)
|
||||||
class UserSettingAdmin(admin.ModelAdmin):
|
class UserSettingAdmin(admin.ModelAdmin):
|
||||||
list_display = ('user', 'selected_class')
|
list_display = ('user', 'selected_class')
|
||||||
raw_id_fields = ('user', 'selected_class')
|
raw_id_fields = ('user', 'selected_class')
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(License)
|
||||||
|
class LicenseAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('licensee',)
|
||||||
|
list_filter = ('licensee',)
|
||||||
|
raw_id_fields = ('licensee',)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import random
|
import random
|
||||||
|
|
||||||
import factory
|
import factory
|
||||||
|
from users.models import SchoolClass, SchoolClassMember, License
|
||||||
from users.models import SchoolClass, SchoolClassMember
|
|
||||||
|
|
||||||
class_types = ['DA', 'KV', 'INF', 'EE']
|
class_types = ['DA', 'KV', 'INF', 'EE']
|
||||||
class_suffix = ['A', 'B', 'C', 'D', 'E']
|
class_suffix = ['A', 'B', 'C', 'D', 'E']
|
||||||
|
|
@ -30,3 +29,8 @@ class SchoolClassFactory(factory.django.DjangoModelFactory):
|
||||||
# A list of groups were passed in, use them
|
# A list of groups were passed in, use them
|
||||||
for user in extracted:
|
for user in extracted:
|
||||||
SchoolClassMember.objects.create(user=user, school_class=self, active=True)
|
SchoolClassMember.objects.create(user=user, school_class=self, active=True)
|
||||||
|
|
||||||
|
|
||||||
|
class LicenseFactory(factory.django.DjangoModelFactory):
|
||||||
|
class Meta:
|
||||||
|
model = License
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
from django.conf import settings
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
from django.conf import settings
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
|
||||||
|
from core.hep_client import HepClient
|
||||||
|
from core.models import AdminData
|
||||||
|
from users.models import User, License
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
"Update licenses via cronjob"
|
||||||
|
|
||||||
|
hep_client = HepClient()
|
||||||
|
|
||||||
|
admin_token = AdminData.objects.get_admin_token()
|
||||||
|
hep_users = User.objects.filter(hep_id__isnull=False)
|
||||||
|
|
||||||
|
for hep_user in hep_users:
|
||||||
|
product = hep_client.myskillbox_product_for_customer(admin_token, hep_user.hep_id)
|
||||||
|
|
||||||
|
if product and License.objects.filter(licensee=hep_user, order_id=product['order_id']).count() == 0:
|
||||||
|
license = License.objects.create_license_for_role(hep_user, product['activated'], product['raw'],
|
||||||
|
product['edition'], product['order_id'])
|
||||||
|
|
||||||
|
if license.is_valid():
|
||||||
|
hep_user.license_expiry_date = license.expire_date
|
||||||
|
hep_user.save()
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.apps import apps
|
||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import UserManager as DjangoUserManager
|
from django.contrib.auth.models import UserManager as DjangoUserManager
|
||||||
|
|
||||||
|
from core.hep_client import TEACHER_EDITION_DURATION, STUDENT_EDITION_DURATION
|
||||||
|
|
||||||
|
|
||||||
class RoleManager(models.Manager):
|
class RoleManager(models.Manager):
|
||||||
use_in_migrations = True
|
use_in_migrations = True
|
||||||
|
|
@ -66,14 +72,18 @@ class RoleManager(models.Manager):
|
||||||
|
|
||||||
|
|
||||||
class UserRoleManager(models.Manager):
|
class UserRoleManager(models.Manager):
|
||||||
def create_role_for_user(self, user, role_key):
|
def get_or_create_role_for_user(self, user, role_key):
|
||||||
from users.models import Role
|
from users.models import Role
|
||||||
try:
|
try:
|
||||||
role = Role.objects.get(key=role_key)
|
role = Role.objects.get(key=role_key)
|
||||||
except Role.DoesNotExist:
|
except Role.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return self._create_user_role(user, role)
|
try:
|
||||||
|
user_role = self.model.objects.get(role=role, user=user)
|
||||||
|
return user_role
|
||||||
|
except self.model.DoesNotExist:
|
||||||
|
return self._create_user_role(user, role)
|
||||||
|
|
||||||
def _create_user_role(self, user, role):
|
def _create_user_role(self, user, role):
|
||||||
user_role = self.model(user=user, role=role)
|
user_role = self.model(user=user, role=role)
|
||||||
|
|
@ -82,10 +92,66 @@ class UserRoleManager(models.Manager):
|
||||||
|
|
||||||
|
|
||||||
class UserManager(DjangoUserManager):
|
class UserManager(DjangoUserManager):
|
||||||
def create_user_with_random_password(self, first_name, last_name, email):
|
|
||||||
|
def _create_user_with_random_password_no_save(self, first_name, last_name, email):
|
||||||
user, created = self.model.objects.get_or_create(email=email, username=email)
|
user, created = self.model.objects.get_or_create(email=email, username=email)
|
||||||
user.first_name = first_name
|
user.first_name = first_name
|
||||||
user.last_name = last_name
|
user.last_name = last_name
|
||||||
user.set_password(self.model.objects.make_random_password())
|
# Todo: remove if not used
|
||||||
|
# user.set_password(self.model.objects.make_random_password())
|
||||||
|
user.set_unusable_password()
|
||||||
|
return user
|
||||||
|
|
||||||
|
def create_user_with_random_password(self, first_name, last_name, email):
|
||||||
|
user = self._create_user_with_random_password_no_save(first_name, last_name, email)
|
||||||
user.save()
|
user.save()
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
def create_user_from_hep(self, user_data):
|
||||||
|
try:
|
||||||
|
user = self.model.objects.get(email=user_data['email'])
|
||||||
|
user.set_unusable_password()
|
||||||
|
except self.model.DoesNotExist:
|
||||||
|
user = self._create_user_with_random_password_no_save( user_data['firstname'],
|
||||||
|
user_data['lastname'],
|
||||||
|
user_data['email'])
|
||||||
|
|
||||||
|
user.hep_id = user_data['id']
|
||||||
|
user.hep_group_id = user_data['group_id']
|
||||||
|
user.save()
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
class LicenseManager(models.Manager):
|
||||||
|
|
||||||
|
def create_license_for_role(self, licensee, activation_date, raw, role, order_id):
|
||||||
|
Role = apps.get_model('users', 'Role')
|
||||||
|
if role == 'teacher':
|
||||||
|
user_role = Role.objects.get_default_teacher_role()
|
||||||
|
expiry_date = activation_date + timedelta(TEACHER_EDITION_DURATION)
|
||||||
|
else:
|
||||||
|
user_role = Role.objects.get_default_student_role()
|
||||||
|
expiry_date = activation_date + timedelta(STUDENT_EDITION_DURATION)
|
||||||
|
|
||||||
|
new_license = self._create_license_for_role(licensee, expiry_date, raw, user_role, order_id)
|
||||||
|
new_license.licensee.license_expiry_date = new_license.expire_date
|
||||||
|
new_license.licensee.save()
|
||||||
|
|
||||||
|
return new_license
|
||||||
|
|
||||||
|
def _create_license_for_role(self, licensee, expiry_date, raw, role, order_id):
|
||||||
|
return self.create(licensee=licensee, expire_date=expiry_date, raw=raw, for_role=role, order_id=order_id)
|
||||||
|
|
||||||
|
def get_active_license_for_user(self, user):
|
||||||
|
licenses = self.filter(licensee=user, expire_date__gte=timezone.now()).order_by('-expire_date')
|
||||||
|
if len(licenses) == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
license = licenses[0]
|
||||||
|
|
||||||
|
# update license on user
|
||||||
|
user.license_expiry_date = license.expire_date
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
return license
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Generated by Django 2.1.15 on 2020-04-30 12:51
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('users', '0016_auto_20200304_1250'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='License',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('expire_date', models.DateField(null=True)),
|
||||||
|
('order_id', models.IntegerField(default=-1)),
|
||||||
|
('raw', models.TextField(default='')),
|
||||||
|
('for_role', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='users.Role')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='hep_group_id',
|
||||||
|
field=models.PositiveIntegerField(null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='hep_id',
|
||||||
|
field=models.PositiveIntegerField(null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='user',
|
||||||
|
name='license_expiry_date',
|
||||||
|
field=models.DateField(default=None, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='license',
|
||||||
|
name='licensee',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import re
|
import re
|
||||||
|
from datetime import datetime
|
||||||
import string
|
import string
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
|
@ -8,7 +9,8 @@ from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from users.managers import RoleManager, UserRoleManager, UserManager
|
from core.hep_client import HepClient
|
||||||
|
from users.managers import RoleManager, UserRoleManager, UserManager, LicenseManager
|
||||||
|
|
||||||
DEFAULT_SCHOOL_ID = 1
|
DEFAULT_SCHOOL_ID = 1
|
||||||
|
|
||||||
|
|
@ -17,6 +19,9 @@ class User(AbstractUser):
|
||||||
last_module = models.ForeignKey('books.Module', related_name='+', on_delete=models.SET_NULL, null=True)
|
last_module = models.ForeignKey('books.Module', related_name='+', on_delete=models.SET_NULL, null=True)
|
||||||
avatar_url = models.CharField(max_length=254, blank=True, default='')
|
avatar_url = models.CharField(max_length=254, blank=True, default='')
|
||||||
email = models.EmailField(_('email address'), unique=True)
|
email = models.EmailField(_('email address'), unique=True)
|
||||||
|
hep_id = models.PositiveIntegerField(null=True, blank=False)
|
||||||
|
hep_group_id = models.PositiveIntegerField(null=True, blank=False)
|
||||||
|
license_expiry_date = models.DateField(blank=False, null=True, default=None)
|
||||||
|
|
||||||
objects = UserManager()
|
objects = UserManager()
|
||||||
|
|
||||||
|
|
@ -66,11 +71,32 @@ class User(AbstractUser):
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def sync_with_hep_data(self, hep_data):
|
||||||
|
|
||||||
|
data_has_changed = False
|
||||||
|
|
||||||
|
if self.email != hep_data['email']:
|
||||||
|
self.email = hep_data['email']
|
||||||
|
self.username = hep_data['email']
|
||||||
|
data_has_changed = True
|
||||||
|
|
||||||
|
if self.first_name != hep_data['firstname']:
|
||||||
|
self.first_name = hep_data['firstname']
|
||||||
|
data_has_changed = True
|
||||||
|
|
||||||
|
if self.last_name != hep_data['lastname']:
|
||||||
|
self.last_name = hep_data['lastname']
|
||||||
|
data_has_changed = True
|
||||||
|
|
||||||
|
if data_has_changed:
|
||||||
|
self.save()
|
||||||
|
|
||||||
def set_selected_class(self, school_class):
|
def set_selected_class(self, school_class):
|
||||||
user_settings, created = UserSetting.objects.get_or_create(user=self)
|
user_settings, created = UserSetting.objects.get_or_create(user=self)
|
||||||
user_settings.selected_class = school_class
|
user_settings.selected_class = school_class
|
||||||
user_settings.save()
|
user_settings.save()
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full_name(self):
|
def full_name(self):
|
||||||
return self.get_full_name()
|
return self.get_full_name()
|
||||||
|
|
@ -111,6 +137,15 @@ class SchoolClass(models.Model):
|
||||||
|
|
||||||
return '{} {}'.format(prefix, index + 1)
|
return '{} {}'.format(prefix, index + 1)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_default_group_for_teacher(cls, user):
|
||||||
|
default_class_name = cls.generate_default_group_name()
|
||||||
|
default_class = cls.objects.create(name=default_class_name)
|
||||||
|
SchoolClassMember.objects.create(
|
||||||
|
user=user,
|
||||||
|
school_class=default_class
|
||||||
|
)
|
||||||
|
|
||||||
def is_user_in_schoolclass(self, user):
|
def is_user_in_schoolclass(self, user):
|
||||||
return user.is_superuser or user.school_classes.filter(pk=self.id).count() > 0
|
return user.is_superuser or user.school_classes.filter(pk=self.id).count() > 0
|
||||||
|
|
||||||
|
|
@ -201,7 +236,28 @@ class UserSetting(models.Model):
|
||||||
selected_class = models.ForeignKey(SchoolClass, blank=True, null=True, on_delete=models.CASCADE)
|
selected_class = models.ForeignKey(SchoolClass, blank=True, null=True, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
|
||||||
|
class License(models.Model):
|
||||||
|
for_role = models.ForeignKey(Role, blank=False, null=True, on_delete=models.CASCADE)
|
||||||
|
licensee = models.ForeignKey(User, blank=False, null=True, on_delete=models.CASCADE)
|
||||||
|
expire_date = models.DateField(blank=False, null=True,)
|
||||||
|
order_id = models.IntegerField(blank=False, null=False, default=-1)
|
||||||
|
raw = models.TextField(default='')
|
||||||
|
|
||||||
|
objects = LicenseManager()
|
||||||
|
|
||||||
|
def is_teacher_license(self):
|
||||||
|
return self.for_role.key == RoleManager.TEACHER_KEY
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
return HepClient.is_product_active(datetime(self.expire_date.year, self.expire_date.month, self.expire_date.day),
|
||||||
|
self.for_role.key)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f'License for role: {self.for_role}'
|
||||||
|
|
||||||
|
|
||||||
class SchoolClassMember(models.Model):
|
class SchoolClassMember(models.Model):
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
school_class = models.ForeignKey(SchoolClass, on_delete=models.CASCADE)
|
school_class = models.ForeignKey(SchoolClass, on_delete=models.CASCADE)
|
||||||
active = models.BooleanField(default=True)
|
active = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,50 +9,78 @@
|
||||||
# @author: chrigu <christian.cueni@iterativ.ch>
|
# @author: chrigu <christian.cueni@iterativ.ch>
|
||||||
|
|
||||||
import graphene
|
import graphene
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth import authenticate, login
|
from django.contrib.auth import authenticate, login
|
||||||
from graphene import relay
|
from graphene import relay
|
||||||
|
|
||||||
from registration.models import License
|
from core.hep_client import HepClient, HepClientUnauthorizedException, HepClientException
|
||||||
|
from users.user_signup_login_handler import handle_user_and_verify_products, UNKNOWN_ERROR, EMAIL_NOT_VERIFIED
|
||||||
|
|
||||||
|
|
||||||
class LoginError(graphene.ObjectType):
|
class BetaLogin(relay.ClientIDMutation):
|
||||||
field = graphene.String()
|
|
||||||
|
|
||||||
|
|
||||||
class Login(relay.ClientIDMutation):
|
|
||||||
class Input:
|
class Input:
|
||||||
username_input = graphene.String()
|
username_input = graphene.String()
|
||||||
password_input = graphene.String()
|
password_input = graphene.String()
|
||||||
|
|
||||||
success = graphene.Boolean()
|
success = graphene.Boolean()
|
||||||
errors = graphene.List(LoginError) # todo: change for consistency
|
message = graphene.String()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||||
|
if settings.ALLOW_BETA_LOGIN:
|
||||||
|
password = kwargs.get('password_input')
|
||||||
|
username = kwargs.get('username_input')
|
||||||
|
user = authenticate(username=username, password=password)
|
||||||
|
if user is None:
|
||||||
|
raise Exception('invalid_credentials')
|
||||||
|
login(info.context, user)
|
||||||
|
|
||||||
|
return cls(success=True, message='')
|
||||||
|
|
||||||
|
raise Exception('not_implemented')
|
||||||
|
|
||||||
|
|
||||||
|
class Login(relay.ClientIDMutation):
|
||||||
|
class Input:
|
||||||
|
token_input = graphene.String()
|
||||||
|
|
||||||
|
success = graphene.Boolean()
|
||||||
|
message = graphene.String()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def mutate_and_get_payload(cls, root, info, **kwargs):
|
def mutate_and_get_payload(cls, root, info, **kwargs):
|
||||||
|
|
||||||
user = authenticate(username=kwargs.get('username_input'), password=kwargs.get('password_input'))
|
hep_client = HepClient()
|
||||||
if user is None:
|
token = kwargs.get('token_input')
|
||||||
error = LoginError(field='invalid_credentials')
|
|
||||||
return cls(success=False, errors=[error])
|
|
||||||
|
|
||||||
user_license = None
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
user_license = License.objects.get(licensee=user)
|
user_data = hep_client.customer_me(token)
|
||||||
except License.DoesNotExist:
|
except HepClientUnauthorizedException:
|
||||||
# current users have no license, allow them to login
|
return cls.return_login_message('invalid_credentials')
|
||||||
pass
|
except HepClientException:
|
||||||
|
return cls.return_login_message(UNKNOWN_ERROR)
|
||||||
|
|
||||||
if user_license is not None and not user_license.license_type.active:
|
user, status_msg = handle_user_and_verify_products(user_data)
|
||||||
error = LoginError(field='license_inactive')
|
user.sync_with_hep_data(user_data)
|
||||||
return cls(success=False, errors=[error])
|
|
||||||
|
|
||||||
login(info.context, user)
|
if user and status_msg != EMAIL_NOT_VERIFIED:
|
||||||
return cls(success=True, errors=[])
|
login(info.context, user)
|
||||||
|
|
||||||
|
if status_msg:
|
||||||
|
return cls.return_login_message(status_msg)
|
||||||
|
|
||||||
|
return cls(success=True, message='success')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def return_login_message(cls, message):
|
||||||
|
if message == EMAIL_NOT_VERIFIED or message == UNKNOWN_ERROR or message == 'invalid_credentials':
|
||||||
|
raise Exception(message)
|
||||||
|
return cls(success=True, message=message)
|
||||||
|
|
||||||
|
|
||||||
class UserMutations:
|
class UserMutations:
|
||||||
login = Login.Field()
|
login = Login.Field()
|
||||||
|
beta_login = BetaLogin.Field()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue