Move to GraphQL
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
7e137d1a1c
commit
b54dae7e15
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -23,3 +23,5 @@ priv/data/*
|
||||||
!priv/data/.gitkeep
|
!priv/data/.gitkeep
|
||||||
.vscode/
|
.vscode/
|
||||||
cover/
|
cover/
|
||||||
|
uploads/*
|
||||||
|
!uploads/.gitkeep
|
|
@ -1,4 +1,4 @@
|
||||||
image: elixir:1.7
|
image: tcitworld/mobilizon-ci
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- name: mdillon/postgis:10
|
- name: mdillon/postgis:10
|
||||||
|
@ -20,8 +20,7 @@ cache:
|
||||||
- .rebar3
|
- .rebar3
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- apt-get update
|
- cd js && npm install && npm run build && cd ../
|
||||||
- apt-get install -y build-essential postgresql-client git
|
|
||||||
- mix local.rebar --force
|
- mix local.rebar --force
|
||||||
- mix local.hex --force
|
- mix local.hex --force
|
||||||
- mix deps.get
|
- mix deps.get
|
||||||
|
|
|
@ -47,7 +47,7 @@ config :guardian, Guardian.DB,
|
||||||
# default
|
# default
|
||||||
schema_name: "guardian_tokens",
|
schema_name: "guardian_tokens",
|
||||||
# store all token types if not set
|
# store all token types if not set
|
||||||
token_types: ["refresh_token"],
|
# token_types: ["refresh_token"],
|
||||||
# default: 60 minutes
|
# default: 60 minutes
|
||||||
sweep_interval: 60
|
sweep_interval: 60
|
||||||
|
|
||||||
|
@ -59,3 +59,9 @@ config :geolix,
|
||||||
source: System.get_env("GEOLITE_CITIES_PATH") || "priv/data/GeoLite2-City.mmdb"
|
source: System.get_env("GEOLITE_CITIES_PATH") || "priv/data/GeoLite2-City.mmdb"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
config :arc,
|
||||||
|
storage: Arc.Storage.Local
|
||||||
|
|
||||||
|
config :email_checker,
|
||||||
|
validations: [EmailChecker.Check.Format]
|
||||||
|
|
5
docker/tests/Dockerfile
Normal file
5
docker/tests/Dockerfile
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
FROM elixir:latest
|
||||||
|
|
||||||
|
RUN apt-get update -yq && apt-get install -yq build-essential inotify-tools postgresql-client git curl gnupg
|
||||||
|
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash && apt-get install nodejs -yq
|
||||||
|
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
|
@ -1,7 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
'extends': [
|
extends: [
|
||||||
'plugin:vue/essential',
|
'plugin:vue/essential',
|
||||||
'@vue/airbnb'
|
'@vue/airbnb',
|
||||||
]
|
],
|
||||||
}
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
autoprefixer: {}
|
autoprefixer: {},
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
58
js/Makefile
Normal file
58
js/Makefile
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
# On OSX the PATH variable isn't exported unless "SHELL" is also set, see: http://stackoverflow.com/a/25506676
|
||||||
|
SHELL = /bin/bash
|
||||||
|
NODE_BINDIR = ./node_modules/.bin
|
||||||
|
export PATH := $(NODE_BINDIR):$(PATH)
|
||||||
|
|
||||||
|
# Where to find input files (it can be multiple paths).
|
||||||
|
INPUT_FILES = ./src
|
||||||
|
|
||||||
|
# Where to write the files generated by this makefile.
|
||||||
|
OUTPUT_DIR = ./src/i18n
|
||||||
|
|
||||||
|
# Available locales for the app.
|
||||||
|
LOCALES = en_US fr_FR
|
||||||
|
|
||||||
|
# Name of the generated .po files for each available locale.
|
||||||
|
LOCALE_FILES ?= $(patsubst %,$(OUTPUT_DIR)/locale/%/LC_MESSAGES/app.po,$(LOCALES))
|
||||||
|
|
||||||
|
GETTEXT_HTML_SOURCES = $(shell find $(INPUT_FILES) -name '*.vue' -o -name '*.html' 2> /dev/null)
|
||||||
|
GETTEXT_JS_SOURCES = $(shell find $(INPUT_FILES) -name '*.vue' -o -name '*.js')
|
||||||
|
|
||||||
|
# Makefile Targets
|
||||||
|
.PHONY: clean makemessages translations
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f /tmp/template.pot $(OUTPUT_DIR)/translations.json
|
||||||
|
|
||||||
|
makemessages: /tmp/template.pot
|
||||||
|
|
||||||
|
translations: ./$(OUTPUT_DIR)/translations.json
|
||||||
|
|
||||||
|
# Create a main .pot template, then generate .po files for each available language.
|
||||||
|
# Thanx to Systematic: https://github.com/Polyconseil/systematic/blob/866d5a/mk/main.mk#L167-L183
|
||||||
|
/tmp/template.pot: $(GETTEXT_HTML_SOURCES)
|
||||||
|
# `dir` is a Makefile built-in expansion function which extracts the directory-part of `$@`.
|
||||||
|
# `$@` is a Makefile automatic variable: the file name of the target of the rule.
|
||||||
|
# => `mkdir -p /tmp/`
|
||||||
|
mkdir -p $(dir $@)
|
||||||
|
which gettext-extract
|
||||||
|
# Extract gettext strings from templates files and create a POT dictionary template.
|
||||||
|
gettext-extract --attribute v-translate --quiet --output $@ $(GETTEXT_HTML_SOURCES)
|
||||||
|
# Extract gettext strings from JavaScript files.
|
||||||
|
xgettext --language=JavaScript --keyword=npgettext:1c,2,3 \
|
||||||
|
--from-code=utf-8 --join-existing --no-wrap \
|
||||||
|
--package-name=$(shell node -e "console.log(require('./package.json').name);") \
|
||||||
|
--package-version=$(shell node -e "console.log(require('./package.json').version);") \
|
||||||
|
--output $@ $(GETTEXT_JS_SOURCES)
|
||||||
|
# Generate .po files for each available language.
|
||||||
|
@for lang in $(LOCALES); do \
|
||||||
|
export PO_FILE=$(OUTPUT_DIR)/locale/$$lang/LC_MESSAGES/app.po; \
|
||||||
|
echo "msgmerge --update $$PO_FILE $@"; \
|
||||||
|
mkdir -p $$(dirname $$PO_FILE); \
|
||||||
|
[ -f $$PO_FILE ] && msgmerge --lang=$$lang --update $$PO_FILE $@ || msginit --no-translator --locale=$$lang --input=$@ --output-file=$$PO_FILE; \
|
||||||
|
msgattrib --no-wrap --no-obsolete -o $$PO_FILE $$PO_FILE; \
|
||||||
|
done;
|
||||||
|
|
||||||
|
$(OUTPUT_DIR)/translations.json: clean /tmp/template.pot
|
||||||
|
mkdir -p $(OUTPUT_DIR)
|
||||||
|
gettext-compile --output $@ $(LOCALE_FILES)
|
38
js/get_union_json.js
Normal file
38
js/get_union_json.js
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
fetch(`http://localhost:4000/graphiql`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
variables: {},
|
||||||
|
query: `
|
||||||
|
{
|
||||||
|
__schema {
|
||||||
|
types {
|
||||||
|
kind
|
||||||
|
name
|
||||||
|
possibleTypes {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then(result => result.json())
|
||||||
|
.then(result => {
|
||||||
|
// here we're filtering out any type information unrelated to unions or interfaces
|
||||||
|
const filteredData = result.data.__schema.types.filter(
|
||||||
|
type => type.possibleTypes !== null,
|
||||||
|
);
|
||||||
|
result.data.__schema.types = filteredData;
|
||||||
|
fs.writeFile('./fragmentTypes.json', JSON.stringify(result.data), err => {
|
||||||
|
if (err) {
|
||||||
|
console.error('Error writing fragmentTypes file', err);
|
||||||
|
} else {
|
||||||
|
console.log('Fragment types successfully extracted!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
2180
js/package-lock.json
generated
2180
js/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -6,22 +6,29 @@
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "vue-cli-service build --modern",
|
"build": "vue-cli-service build --modern",
|
||||||
"lint": "vue-cli-service lint",
|
"lint": "vue-cli-service lint",
|
||||||
"test:unit": "vue-cli-service test:unit",
|
"test:e2e": "vue-cli-service test:e2e",
|
||||||
"test:e2e": "vue-cli-service test:e2e"
|
"test:unit": "vue-cli-service test:unit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"apollo-absinthe-upload-link": "^1.4.0",
|
||||||
|
"apollo-cache-inmemory": "^1.3.6",
|
||||||
|
"apollo-link": "^1.2.3",
|
||||||
|
"apollo-link-http": "^1.5.5",
|
||||||
|
"easygettext": "^2.7.0",
|
||||||
|
"graphql-tag": "^2.9.0",
|
||||||
"material-design-icons": "^3.0.1",
|
"material-design-icons": "^3.0.1",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
"ngeohash": "^0.6.0",
|
"ngeohash": "^0.6.0",
|
||||||
"register-service-worker": "^1.4.1",
|
"register-service-worker": "^1.4.1",
|
||||||
"vue": "^2.5.17",
|
"vue": "^2.5.17",
|
||||||
|
"vue-apollo": "^3.0.0-beta.25",
|
||||||
|
"vue-gettext": "^2.1.1",
|
||||||
"vue-gravatar": "^1.2.1",
|
"vue-gravatar": "^1.2.1",
|
||||||
"vue-markdown": "^2.2.4",
|
"vue-markdown": "^2.2.4",
|
||||||
"vue-router": "^3.0.1",
|
"vue-router": "^3.0.1",
|
||||||
"vuetify": "^1.2.7",
|
"vuetify": "^1.3.1",
|
||||||
"vuetify-google-autocomplete": "^2.0.0-beta.5",
|
"vuetify-google-autocomplete": "^2.0.0-beta.5",
|
||||||
"vuex": "^3.0.1",
|
"vuex": "^3.0.1"
|
||||||
"vuex-i18n": "^1.10.5"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-babel": "^3.0.5",
|
"@vue/cli-plugin-babel": "^3.0.5",
|
||||||
|
@ -36,6 +43,7 @@
|
||||||
"dotenv-webpack": "^1.5.7",
|
"dotenv-webpack": "^1.5.7",
|
||||||
"node-sass": "^4.9.3",
|
"node-sass": "^4.9.3",
|
||||||
"sass-loader": "^7.1.0",
|
"sass-loader": "^7.1.0",
|
||||||
|
"vue-cli-plugin-apollo": "^0.17.1",
|
||||||
"vue-template-compiler": "^2.5.17"
|
"vue-template-compiler": "^2.5.17"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
|
|
|
@ -12,24 +12,24 @@
|
||||||
<v-list-group
|
<v-list-group
|
||||||
value="false"
|
value="false"
|
||||||
>
|
>
|
||||||
<v-list-tile avatar v-if="$store.state.actor" slot="activator">
|
<v-list-tile avatar v-if="actor" slot="activator">
|
||||||
<v-list-tile-avatar>
|
<v-list-tile-avatar>
|
||||||
<img v-if="!$store.state.actor.avatar"
|
<img v-if="!actor.avatar"
|
||||||
class="img-circle elevation-7 mb-1"
|
class="img-circle elevation-7 mb-1"
|
||||||
src="https://picsum.photos/125/125/"
|
src="https://picsum.photos/125/125/"
|
||||||
>
|
>
|
||||||
<img v-else
|
<img v-else
|
||||||
class="img-circle elevation-7 mb-1"
|
class="img-circle elevation-7 mb-1"
|
||||||
:src="$store.state.actor.avatar"
|
:src="actor.avatar"
|
||||||
>
|
>
|
||||||
</v-list-tile-avatar>
|
</v-list-tile-avatar>
|
||||||
|
|
||||||
<v-list-tile-content @click="$router.push({name: 'Account', params: { name: $store.state.actor.username }})">
|
<v-list-tile-content @click="$router.push({name: 'Account', params: { name: actor.username }})">
|
||||||
<v-list-tile-title>{{ this.displayed_name }}</v-list-tile-title>
|
<v-list-tile-title>{{ this.displayed_name }}</v-list-tile-title>
|
||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
|
|
||||||
<v-list-tile avatar v-if="$store.state.actor">
|
<v-list-tile avatar v-if="actor">
|
||||||
<v-list-tile-avatar>
|
<v-list-tile-avatar>
|
||||||
<img
|
<img
|
||||||
class="img-circle elevation-7 mb-1"
|
class="img-circle elevation-7 mb-1"
|
||||||
|
@ -93,11 +93,12 @@
|
||||||
<v-speed-dial
|
<v-speed-dial
|
||||||
v-model="fab"
|
v-model="fab"
|
||||||
bottom
|
bottom
|
||||||
fixed
|
|
||||||
right
|
right
|
||||||
|
fixed
|
||||||
direction="top"
|
direction="top"
|
||||||
|
open-on-hover
|
||||||
transition="scale-transition"
|
transition="scale-transition"
|
||||||
v-if="getUser()"
|
v-if="user"
|
||||||
>
|
>
|
||||||
<v-btn
|
<v-btn
|
||||||
slot="activator"
|
slot="activator"
|
||||||
|
@ -129,7 +130,12 @@
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-speed-dial>
|
</v-speed-dial>
|
||||||
<v-footer class="indigo" app>
|
<v-footer class="indigo" app>
|
||||||
<span class="white--text">© Thomas Citharel {{ new Date().getFullYear() }} - Made with Elixir, Phoenix & <a href="https://vuejs.org/">VueJS</a> & <a href="https://www.vuetifyjs.com/">Vuetify</a> with some love and some weeks</span>
|
<span
|
||||||
|
class="white--text"
|
||||||
|
v-translate="{
|
||||||
|
date: new Date().getFullYear(),
|
||||||
|
}">© The Mobilizon Contributors %{date} - Made with Elixir, Phoenix & <a href="https://vuejs.org/">VueJS</a> & <a href="https://www.vuetifyjs.com/">Vuetify</a> with some love and some weeks
|
||||||
|
</span>
|
||||||
</v-footer>
|
</v-footer>
|
||||||
<v-snackbar
|
<v-snackbar
|
||||||
:timeout="error.timeout"
|
:timeout="error.timeout"
|
||||||
|
@ -143,8 +149,9 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import gql from 'graphql-tag';
|
||||||
import NavBar from '@/components/NavBar';
|
import NavBar from '@/components/NavBar';
|
||||||
|
import { AUTH_USER_ACTOR, AUTH_USER_ID } from '@/constants';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
|
@ -155,11 +162,17 @@ export default {
|
||||||
return {
|
return {
|
||||||
drawer: false,
|
drawer: false,
|
||||||
fab: false,
|
fab: false,
|
||||||
user: false,
|
user: localStorage.getItem(AUTH_USER_ID),
|
||||||
items: [
|
items: [
|
||||||
{ icon: 'poll', text: 'Events', route: 'EventList', role: null },
|
{
|
||||||
{ icon: 'group', text: 'Groups', route: 'GroupList', role: null },
|
icon: 'poll', text: 'Events', route: 'EventList', role: null,
|
||||||
{ icon: 'content_copy', text: 'Categories', route: 'CategoryList', role: 'ROLE_ADMIN' },
|
},
|
||||||
|
{
|
||||||
|
icon: 'group', text: 'Groups', route: 'GroupList', role: null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'content_copy', text: 'Categories', route: 'CategoryList', role: 'ROLE_ADMIN',
|
||||||
|
},
|
||||||
{ icon: 'settings', text: 'Settings', role: 'ROLE_USER' },
|
{ icon: 'settings', text: 'Settings', role: 'ROLE_USER' },
|
||||||
{ icon: 'chat_bubble', text: 'Send feedback', role: 'ROLE_USER' },
|
{ icon: 'chat_bubble', text: 'Send feedback', role: 'ROLE_USER' },
|
||||||
{ icon: 'help', text: 'Help', role: null },
|
{ icon: 'help', text: 'Help', role: null },
|
||||||
|
@ -171,14 +184,15 @@ export default {
|
||||||
text: '',
|
text: '',
|
||||||
},
|
},
|
||||||
show_new_event_button: false,
|
show_new_event_button: false,
|
||||||
|
actor: localStorage.getItem(AUTH_USER_ACTOR),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showMenuItem(elem) {
|
showMenuItem(elem) {
|
||||||
return elem !== null && this.$store.state.user && this.$store.state.user.roles !== undefined ? this.$store.state.user.roles.includes(elem) : true;
|
return elem !== null && this.user && this.user.roles !== undefined ? this.user.roles.includes(elem) : true;
|
||||||
},
|
},
|
||||||
getUser() {
|
getUser() {
|
||||||
return this.$store.state.user === undefined ? false : this.$store.state.user;
|
return this.user === undefined ? false : this.user;
|
||||||
},
|
},
|
||||||
toggleDrawer() {
|
toggleDrawer() {
|
||||||
this.drawer = !this.drawer;
|
this.drawer = !this.drawer;
|
||||||
|
@ -186,9 +200,9 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
displayed_name() {
|
displayed_name() {
|
||||||
return this.$store.state.actor.display_name === null ? this.$store.state.actor.username : this.$store.state.actor.display_name
|
return this.actor.display_name === null ? this.actor.username : this.actor.display_name;
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
export default {
|
|
||||||
login(state, user) {
|
|
||||||
state.user = user.user;
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,29 +0,0 @@
|
||||||
import { API_ORIGIN, API_PATH } from './_entrypoint';
|
|
||||||
|
|
||||||
const jsonLdMimeType = 'application/json';
|
|
||||||
|
|
||||||
export default function eventFetch(url, store, optionsarg = {}) {
|
|
||||||
const options = optionsarg;
|
|
||||||
if (typeof options.headers === 'undefined') {
|
|
||||||
options.headers = new Headers();
|
|
||||||
}
|
|
||||||
if (options.headers.get('Accept') === null) {
|
|
||||||
options.headers.set('Accept', jsonLdMimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.body !== 'undefined' && !(options.body instanceof FormData) && options.headers.get('Content-Type') === null) {
|
|
||||||
options.headers.set('Content-Type', jsonLdMimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (store.state.user) {
|
|
||||||
options.headers.set('Authorization', `Bearer ${localStorage.getItem('token')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const link = url.includes(API_PATH) ? API_ORIGIN + url : API_ORIGIN + API_PATH + url;
|
|
||||||
|
|
||||||
return fetch(link, options).then((response) => {
|
|
||||||
if (response.ok) return response;
|
|
||||||
|
|
||||||
throw response.text();
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,120 +0,0 @@
|
||||||
import { API_ORIGIN, API_PATH } from '../api/_entrypoint';
|
|
||||||
import { LOGIN_USER, LOAD_USER, CHANGE_ACTOR } from '../store/mutation-types';
|
|
||||||
|
|
||||||
// URL and endpoint constants
|
|
||||||
const LOGIN_URL = `${API_ORIGIN}${API_PATH}/login`;
|
|
||||||
const SIGNUP_URL = `${API_ORIGIN}${API_PATH}/users/`;
|
|
||||||
const CHECK_AUTH = `${API_ORIGIN}${API_PATH}/user/`;
|
|
||||||
const REFRESH_TOKEN = `${API_ORIGIN}${API_PATH}/token/refresh`;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
|
|
||||||
// Send a request to the login URL and save the returned JWT
|
|
||||||
login(creds, success, error) {
|
|
||||||
fetch(LOGIN_URL, { method: 'POST', body: creds, headers: { 'Content-Type': 'application/json' } })
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status === 200) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
throw response.json();
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
localStorage.setItem('token', data.token);
|
|
||||||
// localStorage.setItem('refresh_token', data.refresh_token);
|
|
||||||
return success(data);
|
|
||||||
})
|
|
||||||
.catch(err => error(err));
|
|
||||||
},
|
|
||||||
|
|
||||||
signup(creds, success, error) {
|
|
||||||
fetch(SIGNUP_URL, { method: 'POST', body: creds, headers: { 'Content-Type': 'application/json' } })
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status === 200) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
throw response.json();
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
localStorage.setItem('token', data.token);
|
|
||||||
// localStorage.setItem('refresh_token', data.refresh_token);
|
|
||||||
|
|
||||||
return success(data);
|
|
||||||
}).catch(err => error(err));
|
|
||||||
},
|
|
||||||
refreshToken(store, successHandler, errorHandler) {
|
|
||||||
const refreshToken = localStorage.getItem('refresh_token');
|
|
||||||
console.log('We are refreshing the jwt token');
|
|
||||||
fetch(REFRESH_TOKEN, { method: 'POST', body: JSON.stringify({ refresh_token: refreshToken }), headers: { 'Content-Type': 'application/json' } })
|
|
||||||
.then((response) => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
return errorHandler('Error while authenticating');
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
console.log('We have a new token');
|
|
||||||
this.authenticated = true;
|
|
||||||
store.commit(LOGIN_USER, response);
|
|
||||||
localStorage.setItem('token', response.token);
|
|
||||||
console.log("Let's try to auth again");
|
|
||||||
successHandler();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// To log out, we just need to remove the token
|
|
||||||
logout(store) {
|
|
||||||
localStorage.removeItem('refresh_token');
|
|
||||||
localStorage.removeItem('token');
|
|
||||||
this.authenticated = false;
|
|
||||||
store.commit('LOGOUT_USER');
|
|
||||||
},
|
|
||||||
|
|
||||||
jwt_decode(token) {
|
|
||||||
const base64Url = token.split('.')[1];
|
|
||||||
const base64 = base64Url.replace('-', '+').replace('_', '/');
|
|
||||||
return JSON.parse(window.atob(base64));
|
|
||||||
},
|
|
||||||
|
|
||||||
getTokenExpirationDate(encodedToken) {
|
|
||||||
const token = this.jwt_decode(encodedToken);
|
|
||||||
if (!token.exp) { return null; }
|
|
||||||
|
|
||||||
const date = new Date(0);
|
|
||||||
date.setUTCSeconds(token.exp);
|
|
||||||
|
|
||||||
return date;
|
|
||||||
},
|
|
||||||
|
|
||||||
isTokenExpired(token) {
|
|
||||||
const expirationDate = this.getTokenExpirationDate(token);
|
|
||||||
return expirationDate < new Date();
|
|
||||||
},
|
|
||||||
|
|
||||||
getUser(store, successHandler, errorHandler) {
|
|
||||||
console.log('We are checking the auth');
|
|
||||||
this.token = localStorage.getItem('token');
|
|
||||||
const options = {};
|
|
||||||
options.headers = new Headers();
|
|
||||||
options.headers.set('Authorization', `Bearer ${this.token}`);
|
|
||||||
fetch(CHECK_AUTH, options)
|
|
||||||
.then((response) => {
|
|
||||||
if (response.ok) {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
return errorHandler('Error while authenticating');
|
|
||||||
}).then((response) => {
|
|
||||||
this.authenticated = true;
|
|
||||||
console.log(response);
|
|
||||||
store.commit(LOAD_USER, response.data);
|
|
||||||
store.commit(CHANGE_ACTOR, response.data.actors[0]);
|
|
||||||
return successHandler();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// The object to be passed as a header for authenticated requests
|
|
||||||
getAuthHeader() {
|
|
||||||
return {
|
|
||||||
Authorization: `Bearer ${localStorage.getItem('access_token')}`,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,216 +1,210 @@
|
||||||
<template>
|
<template>
|
||||||
<v-layout row>
|
<v-layout row>
|
||||||
<v-flex xs12 sm6 offset-sm3>
|
<v-flex xs12 sm6 offset-sm3>
|
||||||
<v-progress-circular v-if="loading" indeterminate color="primary"></v-progress-circular>
|
<ApolloQuery :query="FETCH_ACTOR" :variables="{ name }">
|
||||||
<v-card v-if="!loading">
|
<template slot-scope="{ result: { loading, error, data } }">
|
||||||
<v-img :src="actor.banner || 'https://picsum.photos/400/'" height="300px">
|
<v-progress-circular v-if="loading" indeterminate color="primary"></v-progress-circular>
|
||||||
<v-layout column class="media">
|
<v-card v-if="data">
|
||||||
<v-card-title>
|
<v-img :src="data.actor.banner || 'https://picsum.photos/400/'" height="300px">
|
||||||
<v-btn icon @click="$router.go(-1)">
|
<v-layout column class="media">
|
||||||
<v-icon>chevron_left</v-icon>
|
<v-card-title>
|
||||||
</v-btn>
|
<v-btn icon @click="$router.go(-1)">
|
||||||
<v-spacer></v-spacer>
|
<v-icon>chevron_left</v-icon>
|
||||||
<v-btn icon class="mr-3" v-if="$store.state.user && $store.state.actor.id === actor.id">
|
</v-btn>
|
||||||
<v-icon>edit</v-icon>
|
<v-spacer></v-spacer>
|
||||||
</v-btn>
|
<!-- <v-btn icon class="mr-3" v-if="actor.id === data.actor.id">
|
||||||
<v-menu bottom left>
|
<v-icon>edit</v-icon>
|
||||||
<v-btn icon slot="activator">
|
</v-btn> -->
|
||||||
<v-icon>more_vert</v-icon>
|
<v-menu bottom left>
|
||||||
</v-btn>
|
<v-btn icon slot="activator">
|
||||||
<v-list>
|
<v-icon>more_vert</v-icon>
|
||||||
<v-list-tile @click="logoutUser()" v-if="$store.state.user && $store.state.actor.id === actor.id">
|
</v-btn>
|
||||||
<v-list-tile-title>User logout</v-list-tile-title>
|
<v-list>
|
||||||
</v-list-tile>
|
<!-- <v-list-tile @click="logoutUser()" v-if="actor.id === data.actor.id">
|
||||||
<v-list-tile @click="deleteAccount()" v-if="$store.state.user && $store.state.actor.id === actor.id">
|
<v-list-tile-title>User logout</v-list-tile-title>
|
||||||
<v-list-tile-title>Delete</v-list-tile-title>
|
</v-list-tile>
|
||||||
</v-list-tile>
|
<v-list-tile @click="deleteAccount()" v-if="actor.id === data.actor.id">
|
||||||
</v-list>
|
<v-list-tile-title>Delete</v-list-tile-title>
|
||||||
</v-menu>
|
</v-list-tile> -->
|
||||||
</v-card-title>
|
</v-list>
|
||||||
<v-spacer></v-spacer>
|
</v-menu>
|
||||||
<div class="text-xs-center">
|
</v-card-title>
|
||||||
<v-avatar size="125px">
|
<v-spacer></v-spacer>
|
||||||
<img v-if="!actor.avatar"
|
<div class="text-xs-center">
|
||||||
class="img-circle elevation-7 mb-1"
|
<v-avatar size="125px">
|
||||||
src="https://picsum.photos/125/125/"
|
<img v-if="!data.actor.avatarUrl"
|
||||||
>
|
|
||||||
<img v-else
|
|
||||||
class="img-circle elevation-7 mb-1"
|
class="img-circle elevation-7 mb-1"
|
||||||
:src="actor.avatar"
|
src="https://picsum.photos/125/125/"
|
||||||
>
|
>
|
||||||
</v-avatar>
|
<img v-else
|
||||||
</div>
|
class="img-circle elevation-7 mb-1"
|
||||||
<v-container fluid grid-list-lg>
|
:src="data.actor.avatarUrl"
|
||||||
<v-layout row>
|
>
|
||||||
<v-flex xs7>
|
</v-avatar>
|
||||||
<div class="headline">{{ actor.display_name }}</div>
|
</div>
|
||||||
<div><span class="subheading">@{{ actor.username }}<span v-if="actor.domain">@{{ actor.domain }}</span></span></div>
|
<v-container fluid grid-list-lg>
|
||||||
<v-card-text v-if="actor.description" v-html="actor.description"></v-card-text>
|
<v-layout row>
|
||||||
|
<v-flex xs7>
|
||||||
|
<div class="headline">{{ data.actor.name }}</div>
|
||||||
|
<div><span class="subheading">@{{ data.actor.preferredUsername }}<span v-if="data.actor.domain">@{{ data.actor.domain }}</span></span></div>
|
||||||
|
<v-card-text v-if="data.actor.description" v-html="data.actor.description"></v-card-text>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-container>
|
||||||
|
</v-layout>
|
||||||
|
</v-img>
|
||||||
|
<v-list three-line>
|
||||||
|
<v-list-tile>
|
||||||
|
<v-list-tile-action>
|
||||||
|
<v-icon color="indigo">phone</v-icon>
|
||||||
|
</v-list-tile-action>
|
||||||
|
<v-list-tile-content>
|
||||||
|
<v-list-tile-title>(323) 555-6789</v-list-tile-title>
|
||||||
|
<v-list-tile-sub-title>Work</v-list-tile-sub-title>
|
||||||
|
</v-list-tile-content>
|
||||||
|
<v-list-tile-action>
|
||||||
|
<v-icon dark>chat</v-icon>
|
||||||
|
</v-list-tile-action>
|
||||||
|
</v-list-tile>
|
||||||
|
<v-divider inset></v-divider>
|
||||||
|
<v-list-tile>
|
||||||
|
<v-list-tile-action>
|
||||||
|
<v-icon color="indigo">mail</v-icon>
|
||||||
|
</v-list-tile-action>
|
||||||
|
<v-list-tile-content>
|
||||||
|
<v-list-tile-title>ali_connors@example.com</v-list-tile-title>
|
||||||
|
<v-list-tile-sub-title>Work</v-list-tile-sub-title>
|
||||||
|
</v-list-tile-content>
|
||||||
|
</v-list-tile>
|
||||||
|
<v-divider inset></v-divider>
|
||||||
|
<v-list-tile>
|
||||||
|
<v-list-tile-action>
|
||||||
|
<v-icon color="indigo">location_on</v-icon>
|
||||||
|
</v-list-tile-action>
|
||||||
|
<v-list-tile-content>
|
||||||
|
<v-list-tile-title>1400 Main Street</v-list-tile-title>
|
||||||
|
<v-list-tile-sub-title>Orlando, FL 79938</v-list-tile-sub-title>
|
||||||
|
</v-list-tile-content>
|
||||||
|
</v-list-tile>
|
||||||
|
</v-list>
|
||||||
|
<v-container fluid grid-list-md v-if="data.actor.participatingEvents && data.actor.participatingEvents.length > 0">
|
||||||
|
<v-subheader>Participated at</v-subheader>
|
||||||
|
<v-layout row wrap>
|
||||||
|
<v-flex v-for="event in data.actor.participatingEvents" :key="event.id">
|
||||||
|
<v-card>
|
||||||
|
<v-img
|
||||||
|
class="black--text"
|
||||||
|
height="200px"
|
||||||
|
src="https://picsum.photos/400/200/"
|
||||||
|
>
|
||||||
|
<v-container fill-height fluid>
|
||||||
|
<v-layout fill-height>
|
||||||
|
<v-flex xs12 align-end flexbox>
|
||||||
|
<span class="headline">{{ event.title }}</span>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-container>
|
||||||
|
</v-img>
|
||||||
|
<v-card-title>
|
||||||
|
<div>
|
||||||
|
<span class="grey--text">{{ event.startDate | formatDate }} à {{ event.location }}</span><br>
|
||||||
|
<p>{{ event.description }}</p>
|
||||||
|
<p v-if="event.organizer">Organisé par <router-link :to="{name: 'Account', params: {'id': event.organizer.id}}">{{ event.organizer.username }}</router-link></p>
|
||||||
|
</div>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn icon>
|
||||||
|
<v-icon>favorite</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn icon>
|
||||||
|
<v-icon>bookmark</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn icon>
|
||||||
|
<v-icon>share</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-layout>
|
<v-container fluid grid-list-md v-if="data.actor.organizedEvents && data.actor.organizedEvents.length > 0">
|
||||||
</v-img>
|
<v-subheader>Organized events</v-subheader>
|
||||||
<v-list three-line>
|
<v-layout row wrap>
|
||||||
<v-list-tile>
|
<v-flex v-for="event in data.actor.organizedEvents" :key="event.id" md6>
|
||||||
<v-list-tile-action>
|
<v-card>
|
||||||
<v-icon color="indigo">phone</v-icon>
|
<v-img
|
||||||
</v-list-tile-action>
|
height="200px"
|
||||||
<v-list-tile-content>
|
src="https://picsum.photos/400/200/"
|
||||||
<v-list-tile-title>(323) 555-6789</v-list-tile-title>
|
/>
|
||||||
<v-list-tile-sub-title>Work</v-list-tile-sub-title>
|
<v-card-title primary-title>
|
||||||
</v-list-tile-content>
|
<div>
|
||||||
<v-list-tile-action>
|
<router-link :to="{name: 'Event', params: {uuid: event.uuid}}">
|
||||||
<v-icon dark>chat</v-icon>
|
<div class="headline">{{ event.title }}</div>
|
||||||
</v-list-tile-action>
|
</router-link>
|
||||||
</v-list-tile>
|
<span class="grey--text" v-html="nl2br(event.description)"></span>
|
||||||
<v-divider inset></v-divider>
|
</div>
|
||||||
<v-list-tile>
|
</v-card-title>
|
||||||
<v-list-tile-action>
|
|
||||||
<v-icon color="indigo">mail</v-icon>
|
<!-- <v-card-title>
|
||||||
</v-list-tile-action>
|
<div>
|
||||||
<v-list-tile-content>
|
<span class="grey--text" v-if="event.addressType === 'physical'">{{ event.startDate }} à {{ event.location }}</span><br>
|
||||||
<v-list-tile-title>ali_connors@example.com</v-list-tile-title>
|
<p v-if="event.organizer">Organisé par <router-link :to="{name: 'Account', params: {'id': event.organizer.id}}">{{ event.organizer.username }}</router-link></p>
|
||||||
<v-list-tile-sub-title>Work</v-list-tile-sub-title>
|
</div>
|
||||||
</v-list-tile-content>
|
</v-card-title> -->
|
||||||
</v-list-tile>
|
<v-card-actions>
|
||||||
<v-divider inset></v-divider>
|
<v-spacer></v-spacer>
|
||||||
<v-list-tile>
|
<v-btn icon>
|
||||||
<v-list-tile-action>
|
<v-icon>favorite</v-icon>
|
||||||
<v-icon color="indigo">location_on</v-icon>
|
</v-btn>
|
||||||
</v-list-tile-action>
|
<v-btn icon>
|
||||||
<v-list-tile-content>
|
<v-icon>bookmark</v-icon>
|
||||||
<v-list-tile-title>1400 Main Street</v-list-tile-title>
|
</v-btn>
|
||||||
<v-list-tile-sub-title>Orlando, FL 79938</v-list-tile-sub-title>
|
<v-btn icon>
|
||||||
</v-list-tile-content>
|
<v-icon>share</v-icon>
|
||||||
</v-list-tile>
|
</v-btn>
|
||||||
</v-list>
|
</v-card-actions>
|
||||||
<v-container fluid grid-list-md v-if="actor.participatingEvents && actor.participatingEvents.length > 0">
|
</v-card>
|
||||||
<v-subheader>Participated at</v-subheader>
|
</v-flex>
|
||||||
<v-layout row wrap>
|
</v-layout>
|
||||||
<v-flex v-for="event in actor.participatingEvents" :key="event.id">
|
</v-container>
|
||||||
<v-card>
|
</v-card>
|
||||||
<v-card-media
|
</template>
|
||||||
class="black--text"
|
</ApolloQuery>
|
||||||
height="200px"
|
|
||||||
src="https://picsum.photos/400/200/"
|
|
||||||
>
|
|
||||||
<v-container fill-height fluid>
|
|
||||||
<v-layout fill-height>
|
|
||||||
<v-flex xs12 align-end flexbox>
|
|
||||||
<span class="headline">{{ event.title }}</span>
|
|
||||||
</v-flex>
|
|
||||||
</v-layout>
|
|
||||||
</v-container>
|
|
||||||
</v-card-media>
|
|
||||||
<v-card-title>
|
|
||||||
<div>
|
|
||||||
<span class="grey--text">{{ event.startDate | formatDate }} à {{ event.location }}</span><br>
|
|
||||||
<p>{{ event.description }}</p>
|
|
||||||
<p v-if="event.organizer">Organisé par <router-link :to="{name: 'Account', params: {'id': event.organizer.id}}">{{ event.organizer.username }}</router-link></p>
|
|
||||||
</div>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon>
|
|
||||||
<v-icon>favorite</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon>
|
|
||||||
<v-icon>bookmark</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon>
|
|
||||||
<v-icon>share</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</v-flex>
|
|
||||||
</v-layout>
|
|
||||||
</v-container>
|
|
||||||
<v-container fluid grid-list-md v-if="actor.organized_events && actor.organized_events.length > 0">
|
|
||||||
<v-subheader>Organized events</v-subheader>
|
|
||||||
<v-layout row wrap>
|
|
||||||
<v-flex v-for="event in actor.organized_events" :key="event.id">
|
|
||||||
<v-card>
|
|
||||||
<v-card-media
|
|
||||||
class="black--text"
|
|
||||||
height="200px"
|
|
||||||
src="https://picsum.photos/400/200/"
|
|
||||||
>
|
|
||||||
<v-container fill-height fluid>
|
|
||||||
<v-layout fill-height>
|
|
||||||
<v-flex xs12 align-end flexbox>
|
|
||||||
<span class="headline">{{ event.title }}</span>
|
|
||||||
</v-flex>
|
|
||||||
</v-layout>
|
|
||||||
</v-container>
|
|
||||||
</v-card-media>
|
|
||||||
<v-card-title>
|
|
||||||
<div>
|
|
||||||
<span class="grey--text">{{ event.startDate | formatDate }} à {{ event.location }}</span><br>
|
|
||||||
<p>{{ event.description }}</p>
|
|
||||||
<p v-if="event.organizer">Organisé par <router-link :to="{name: 'Account', params: {'id': event.organizer.id}}">{{ event.organizer.username }}</router-link></p>
|
|
||||||
</div>
|
|
||||||
</v-card-title>
|
|
||||||
<v-card-actions>
|
|
||||||
<v-spacer></v-spacer>
|
|
||||||
<v-btn icon>
|
|
||||||
<v-icon>favorite</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon>
|
|
||||||
<v-icon>bookmark</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<v-btn icon>
|
|
||||||
<v-icon>share</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-card>
|
|
||||||
</v-flex>
|
|
||||||
</v-layout>
|
|
||||||
</v-container>
|
|
||||||
</v-card>
|
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import eventFetch from '@/api/eventFetch';
|
import { FETCH_ACTOR } from '@/graphql/actor';
|
||||||
import auth from '@/auth';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Account',
|
name: 'Account',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
actor: null,
|
loading: true,
|
||||||
loading: true,
|
};
|
||||||
}
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
name: {
|
name: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true,
|
required: true,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.fetchData();
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
// call again the method if the route changes
|
// call again the method if the route changes
|
||||||
'$route': 'fetchData'
|
$route: 'fetchData',
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchData() {
|
|
||||||
eventFetch(`/actors/${this.name}`, this.$store)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then((response) => {
|
|
||||||
this.actor = response.data;
|
|
||||||
this.loading = false;
|
|
||||||
console.log('actor', this.actor);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
logoutUser() {
|
logoutUser() {
|
||||||
auth.logout(this.$store);
|
// TODO : implement logout
|
||||||
this.$router.push({ name: 'Home' });
|
this.$router.push({ name: 'Home' });
|
||||||
},
|
},
|
||||||
}
|
nl2br: function(text) {
|
||||||
}
|
return text.replace(/(?:\r\n|\r|\n)/g, '<br>');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
@click="$router.push({ name: 'Account', params: { name: actor.username } })"
|
@click="$router.push({ name: 'Account', params: { name: actor.username } })"
|
||||||
>
|
>
|
||||||
<v-list-tile-action>
|
<v-list-tile-action>
|
||||||
<v-icon v-if="$store.state.defaultActor === actor.username" color="pink">star</v-icon>
|
<v-icon v-if="defaultActor === actor.username" color="pink">star</v-icon>
|
||||||
</v-list-tile-action>
|
</v-list-tile-action>
|
||||||
|
|
||||||
<v-list-tile-content>
|
<v-list-tile-content>
|
||||||
|
@ -67,29 +67,26 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import eventFetch from "@/api/eventFetch";
|
|
||||||
import auth from "@/auth";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Identities",
|
name: 'Identities',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
actors: [],
|
actors: [],
|
||||||
newActor: {
|
newActor: {
|
||||||
preferred_username: "",
|
preferred_username: '',
|
||||||
summary: ""
|
summary: '',
|
||||||
},
|
},
|
||||||
loading: true,
|
loading: true,
|
||||||
showForm: false,
|
showForm: false,
|
||||||
rules: {
|
rules: {
|
||||||
required: value => !!value || "Required."
|
required: value => !!value || 'Required.',
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
username: {
|
username: {
|
||||||
status: false,
|
status: false,
|
||||||
msg: []
|
msg: [],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
@ -97,9 +94,9 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchData() {
|
fetchData() {
|
||||||
eventFetch(`/user`, this.$store)
|
eventFetch('/user', this.$store)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
this.actors = response.data.actors;
|
this.actors = response.data.actors;
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
});
|
||||||
|
@ -107,12 +104,12 @@ export default {
|
||||||
sendData() {
|
sendData() {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.showForm = false;
|
this.showForm = false;
|
||||||
eventFetch(`/actors`, this.$store, {
|
eventFetch('/actors', this.$store, {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
body: JSON.stringify({ actor: this.newActor })
|
body: JSON.stringify({ actor: this.newActor }),
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(response => {
|
.then((response) => {
|
||||||
this.actors.push(response.data);
|
this.actors.push(response.data);
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
});
|
});
|
||||||
|
@ -126,7 +123,7 @@ export default {
|
||||||
},
|
},
|
||||||
host() {
|
host() {
|
||||||
return `@${window.location.host}`;
|
return `@${window.location.host}`;
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -60,83 +60,88 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import { LOGIN_USER } from '@/store/mutation-types';
|
import Gravatar from 'vue-gravatar';
|
||||||
import auth from '@/auth/index';
|
import RegisterAvatar from './RegisterAvatar';
|
||||||
import Gravatar from 'vue-gravatar';
|
import { AUTH_TOKEN, AUTH_USER_ID, AUTH_USER_ACTOR } from '@/constants';
|
||||||
import RegisterAvatar from './RegisterAvatar';
|
import { LOGIN } from '@/graphql/auth';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
email: {
|
email: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
default: '',
|
default: '',
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
beforeCreate() {
|
||||||
|
if (this.user) {
|
||||||
|
this.$router.push('/');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
'v-gravatar': Gravatar,
|
||||||
|
avatar: RegisterAvatar,
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.credentials.email = this.email;
|
||||||
|
this.credentials.password = this.password;
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
credentials: {
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
},
|
},
|
||||||
password: {
|
validationSent: false,
|
||||||
type: String,
|
error: {
|
||||||
required: false,
|
show: false,
|
||||||
default: '',
|
text: '',
|
||||||
},
|
timeout: 3000,
|
||||||
},
|
field: {
|
||||||
beforeCreate() {
|
email: false,
|
||||||
if (this.$store.state.user) {
|
password: false,
|
||||||
this.$router.push('/');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
'v-gravatar': Gravatar,
|
|
||||||
'avatar': RegisterAvatar
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.credentials.email = this.email;
|
|
||||||
this.credentials.password = this.password;
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
credentials: {
|
|
||||||
email: '',
|
|
||||||
password: '',
|
|
||||||
},
|
},
|
||||||
validationSent: false,
|
|
||||||
error: {
|
|
||||||
show: false,
|
|
||||||
text: '',
|
|
||||||
timeout: 3000,
|
|
||||||
field: {
|
|
||||||
email: false,
|
|
||||||
password: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
required: value => !!value || 'Required.',
|
|
||||||
email: (value) => {
|
|
||||||
const pattern = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
|
||||||
return pattern.test(value) || 'Invalid e-mail.';
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
loginAction(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
auth.login(JSON.stringify(this.credentials), (data) => {
|
|
||||||
this.$store.commit(LOGIN_USER, data.user);
|
|
||||||
this.$router.push({ name: 'Home' });
|
|
||||||
}, (error) => {
|
|
||||||
Promise.resolve(error).then((errorMsg) => {
|
|
||||||
console.log(errorMsg);
|
|
||||||
this.error.show = true;
|
|
||||||
this.error.text = this.$t(errorMsg.display_error);
|
|
||||||
}).catch((e) => {
|
|
||||||
console.log(e);
|
|
||||||
this.error.show = true;
|
|
||||||
this.error.text = e.message;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
validEmail() {
|
rules: {
|
||||||
return this.rules.email(this.credentials.email) === true ? 'v-gravatar' : 'avatar';
|
required: value => !!value || 'Required.',
|
||||||
|
email: (value) => {
|
||||||
|
const pattern = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
|
return pattern.test(value) || 'Invalid e-mail.';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
loginAction(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: LOGIN,
|
||||||
|
variables: {
|
||||||
|
email: this.credentials.email,
|
||||||
|
password: this.credentials.password
|
||||||
|
}
|
||||||
|
}).then((result) => {
|
||||||
|
this.saveUserData(result.data);
|
||||||
|
this.$router.push({name: 'Home'});
|
||||||
|
}).catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
this.error.show = true;
|
||||||
|
this.error.text = e.message;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
validEmail() {
|
||||||
|
return this.rules.email(this.credentials.email) === true ? 'v-gravatar' : 'avatar';
|
||||||
|
},
|
||||||
|
saveUserData({login: login}) {
|
||||||
|
localStorage.setItem(AUTH_USER_ID, login.user.id);
|
||||||
|
localStorage.setItem(AUTH_USER_ACTOR, JSON.stringify(login.actor));
|
||||||
|
localStorage.setItem(AUTH_TOKEN, login.token);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -37,8 +37,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import fetchStory from '@/api/eventFetch';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PasswordReset',
|
name: 'PasswordReset',
|
||||||
props: {
|
props: {
|
||||||
|
@ -80,7 +78,7 @@ export default {
|
||||||
password_length: value => value.length > 6 || 'Password must be at least 6 caracters long',
|
password_length: value => value.length > 6 || 'Password must be at least 6 caracters long',
|
||||||
required: value => !!value || 'Required.',
|
required: value => !!value || 'Required.',
|
||||||
password_equal: value => value === this.credentials.password || 'Passwords must be the same',
|
password_equal: value => value === this.credentials.password || 'Passwords must be the same',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<v-tooltip bottom>
|
<v-tooltip bottom>
|
||||||
<v-btn
|
<v-btn
|
||||||
slot="activator"
|
slot="activator"
|
||||||
:to="{ name: 'Login', params: { email: this.credentials.email, password: this.credentials.password } }"
|
:to="{ name: 'Login', params: { email, password } }"
|
||||||
>
|
>
|
||||||
<!-- <v-icon large>login</v-icon> -->
|
<!-- <v-icon large>login</v-icon> -->
|
||||||
<span>Login</span>
|
<span>Login</span>
|
||||||
|
@ -21,22 +21,22 @@
|
||||||
<div class="text-xs-center">
|
<div class="text-xs-center">
|
||||||
<v-avatar size="80px">
|
<v-avatar size="80px">
|
||||||
<transition name="avatar">
|
<transition name="avatar">
|
||||||
<component :is="validEmail()" v-bind="{email: credentials.email}"></component>
|
<component :is="validEmail()" v-bind="{email}"></component>
|
||||||
<!-- <v-gravatar :email="credentials.email" default-img="mp" v-if="validEmail()"/>
|
<!-- <v-gravatar :email="credentials.email" default-img="mp" v-if="validEmail()"/>
|
||||||
<avatar v-else></avatar> -->
|
<avatar v-else></avatar> -->
|
||||||
</transition>
|
</transition>
|
||||||
</v-avatar>
|
</v-avatar>
|
||||||
</div>
|
</div>
|
||||||
<v-form @submit="registerAction" v-if="!validationSent">
|
<v-form @submit="submit()" v-if="!validationSent">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Username"
|
label="Username"
|
||||||
required
|
required
|
||||||
type="text"
|
type="text"
|
||||||
v-model="credentials.username"
|
v-model="username"
|
||||||
:rules="[rules.required]"
|
:rules="[rules.required]"
|
||||||
:error="this.state.username.status"
|
:error="state.username.status"
|
||||||
:error-messages="this.state.username.msg"
|
:error-messages="state.username.msg"
|
||||||
:suffix="this.host()"
|
:suffix="host()"
|
||||||
hint="You will be able to create more identities once registered"
|
hint="You will be able to create more identities once registered"
|
||||||
persistent-hint
|
persistent-hint
|
||||||
>
|
>
|
||||||
|
@ -46,30 +46,30 @@
|
||||||
required
|
required
|
||||||
type="email"
|
type="email"
|
||||||
ref="email"
|
ref="email"
|
||||||
v-model="credentials.email"
|
v-model="email"
|
||||||
:rules="[rules.required, rules.email]"
|
:rules="[rules.required, rules.email]"
|
||||||
:error="this.state.email.status"
|
:error="state.email.status"
|
||||||
:error-messages="this.state.email.msg"
|
:error-messages="state.email.msg"
|
||||||
>
|
>
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
<v-text-field
|
<v-text-field
|
||||||
label="Password"
|
label="Password"
|
||||||
required
|
required
|
||||||
:type="showPassword ? 'text' : 'password'"
|
:type="showPassword ? 'text' : 'password'"
|
||||||
v-model="credentials.password"
|
v-model="password"
|
||||||
:rules="[rules.required, rules.password_length]"
|
:rules="[rules.required, rules.password_length]"
|
||||||
:error="this.state.password.status"
|
:error="state.password.status"
|
||||||
:error-messages="this.state.password.msg"
|
:error-messages="state.password.msg"
|
||||||
:append-icon="showPassword ? 'visibility_off' : 'visibility'"
|
:append-icon="showPassword ? 'visibility_off' : 'visibility'"
|
||||||
@click:append="showPassword = !showPassword"
|
@click:append="showPassword = !showPassword"
|
||||||
>
|
>
|
||||||
</v-text-field>
|
</v-text-field>
|
||||||
<v-btn @click="registerAction" color="primary">Register</v-btn>
|
<v-btn @click="submit()" color="primary">Register</v-btn>
|
||||||
<router-link :to="{ name: 'ResendConfirmation', params: { email: credentials.email }}">Didn't receive the instructions ?</router-link>
|
<router-link :to="{ name: 'ResendConfirmation', params: { email }}">Didn't receive the instructions ?</router-link>
|
||||||
</v-form>
|
</v-form>
|
||||||
<div v-else>
|
<div v-if="validationSent">
|
||||||
<h2>{{ $t('registration.form.validation_sent', { email: credentials.email }) }}</h2>
|
<h2><translate>A validation email was sent to %{email}</translate></h2>
|
||||||
<b-alert show variant="info">{{ $t('registration.form.validation_sent_info') }}</b-alert>
|
<v-alert :value="true" type="info"><translate>Before you can login, you need to click on the link inside it to validate your account</translate></v-alert>
|
||||||
</div>
|
</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
@ -79,110 +79,101 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import auth from '@/auth/index';
|
import Gravatar from 'vue-gravatar';
|
||||||
import Gravatar from 'vue-gravatar';
|
import RegisterAvatar from './RegisterAvatar';
|
||||||
import RegisterAvatar from './RegisterAvatar';
|
import { CREATE_USER } from '@/graphql/user';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
email: {
|
default_email: {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
default: '',
|
default: '',
|
||||||
|
},
|
||||||
|
default_password: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
'v-gravatar': Gravatar,
|
||||||
|
avatar: RegisterAvatar,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
username: '',
|
||||||
|
email: this.default_email,
|
||||||
|
password: this.default_password,
|
||||||
|
error: {
|
||||||
|
show: false,
|
||||||
},
|
},
|
||||||
password: {
|
showPassword: false,
|
||||||
type: String,
|
validationSent: false,
|
||||||
required: false,
|
state: {
|
||||||
default: '',
|
email: {
|
||||||
|
status: false,
|
||||||
|
msg: [],
|
||||||
|
},
|
||||||
|
username: {
|
||||||
|
status: false,
|
||||||
|
msg: [],
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
status: false,
|
||||||
|
msg: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
rules: {
|
||||||
components: {
|
password_length: value => value.length > 6 || 'Password must be at least 6 caracters long',
|
||||||
'v-gravatar': Gravatar,
|
required: value => !!value || 'Required.',
|
||||||
'avatar': RegisterAvatar
|
email: (value) => {
|
||||||
},
|
const pattern = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
mounted() {
|
return pattern.test(value) || 'Invalid e-mail.';
|
||||||
this.credentials.email = this.email;
|
|
||||||
this.credentials.password = this.password;
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
credentials: {
|
|
||||||
username: '',
|
|
||||||
email: '',
|
|
||||||
password: '',
|
|
||||||
},
|
},
|
||||||
error: {
|
},
|
||||||
show: false,
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
resetState() {
|
||||||
|
this.state = {
|
||||||
|
email: {
|
||||||
|
status: false,
|
||||||
|
msg: '',
|
||||||
},
|
},
|
||||||
showPassword: false,
|
username: {
|
||||||
validationSent: false,
|
status: false,
|
||||||
state: {
|
msg: '',
|
||||||
email: {
|
|
||||||
status: false,
|
|
||||||
msg: [],
|
|
||||||
},
|
|
||||||
username: {
|
|
||||||
status: false,
|
|
||||||
msg: [],
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
status: false,
|
|
||||||
msg: [],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
rules: {
|
password: {
|
||||||
password_length: value => value.length > 6 || 'Password must be at least 6 caracters long',
|
status: false,
|
||||||
required: value => !!value || 'Required.',
|
msg: '',
|
||||||
email: (value) => {
|
|
||||||
const pattern = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
|
||||||
return pattern.test(value) || 'Invalid e-mail.';
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
host() {
|
||||||
registerAction(e) {
|
return `@${window.location.host}`;
|
||||||
this.resetState();
|
|
||||||
e.preventDefault();
|
|
||||||
auth.signup(JSON.stringify(this.credentials), (data) => {
|
|
||||||
console.log(data);
|
|
||||||
this.validationSent = true;
|
|
||||||
}, (error) => {
|
|
||||||
Promise.resolve(error).then((errormsg) => {
|
|
||||||
console.log(errormsg);
|
|
||||||
this.error.show = true;
|
|
||||||
Object.entries(errormsg.errors.user).forEach(([key, val]) => {
|
|
||||||
console.log(key);
|
|
||||||
console.log(val);
|
|
||||||
this.state[key] = { status: true, msg: val };
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
resetState() {
|
|
||||||
this.state = {
|
|
||||||
email: {
|
|
||||||
status: false,
|
|
||||||
msg: '',
|
|
||||||
},
|
|
||||||
username: {
|
|
||||||
status: false,
|
|
||||||
msg: '',
|
|
||||||
},
|
|
||||||
password: {
|
|
||||||
status: false,
|
|
||||||
msg: '',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
host() {
|
|
||||||
return `@${window.location.host}`;
|
|
||||||
},
|
|
||||||
validEmail() {
|
|
||||||
return this.rules.email(this.credentials.email) === true ? 'v-gravatar' : 'avatar';
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
validEmail() {
|
||||||
|
return this.rules.email(this.email) === true ? 'v-gravatar' : 'avatar';
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: CREATE_USER,
|
||||||
|
variables: {
|
||||||
|
email: this.email,
|
||||||
|
password: this.password,
|
||||||
|
username: this.username,
|
||||||
|
},
|
||||||
|
}).then((data) => {
|
||||||
|
console.log(data);
|
||||||
|
this.validationSent = true;
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.avatar-enter-active {
|
.avatar-enter-active {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'RegisterAvatar'
|
name: 'RegisterAvatar',
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -31,8 +31,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import fetchStory from '@/api/eventFetch';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ResendConfirmation',
|
name: 'ResendConfirmation',
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -31,8 +31,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import fetchStory from '@/api/eventFetch';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'SendPasswordReset',
|
name: 'SendPasswordReset',
|
||||||
props: {
|
props: {
|
||||||
|
@ -43,8 +41,8 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.credentials.email = this.email;
|
this.credentials.email = this.email;
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
credentials: {
|
credentials: {
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<v-container>
|
<v-container>
|
||||||
<h1 v-if="loading">{{ $t('registration.validation.process') }}</h1>
|
<h1 v-if="loading"><translate>Your account is being validated</translate></h1>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<div v-if="failed">
|
<div v-if="failed">
|
||||||
<v-alert :value="true" variant="danger">Error while validating account</v-alert>
|
<v-alert :value="true" variant="danger"><translate>Error while validating account</translate></v-alert>
|
||||||
</div>
|
</div>
|
||||||
<h1 v-else>{{ $t('registration.validation.finished') }}</h1>
|
<h1 v-else><translate>Your account has been validated</translate></h1>
|
||||||
</div>
|
</div>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import fetchStory from '@/api/eventFetch';
|
import { VALIDATE_USER } from '@/graphql/user';
|
||||||
import { LOGIN_USER } from '@/store/mutation-types';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Validate',
|
name: 'Validate',
|
||||||
|
@ -33,20 +32,27 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
validateAction() {
|
validateAction() {
|
||||||
fetchStory(`/users/validate/${this.token}`, this.$store).then((data) => {
|
this.$apollo.mutate({
|
||||||
|
mutation: VALIDATE_USER,
|
||||||
|
variables: {
|
||||||
|
token: this.token,
|
||||||
|
},
|
||||||
|
}).then((data) => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
localStorage.setItem('token', data.token);
|
console.log(data);
|
||||||
localStorage.setItem('refresh_token', data.refresh_token);
|
this.saveUserData(data.data);
|
||||||
this.$store.commit(LOGIN_USER, data.account);
|
this.$router.push({name: 'Home'});
|
||||||
this.$snotify.success(this.$t('registration.success.login', { username: data.account.username }));
|
}).catch((error) => {
|
||||||
this.$router.push({ name: 'Home' });
|
this.loading = false;
|
||||||
}).catch((err) => {
|
console.log(error);
|
||||||
Promise.resolve(err).then(() => {
|
this.failed = true;
|
||||||
this.failed = true;
|
|
||||||
this.loading = false;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
saveUserData({validateUser: login}) {
|
||||||
|
localStorage.setItem(AUTH_USER_ID, login.user.id);
|
||||||
|
localStorage.setItem(AUTH_USER_ACTOR, JSON.stringify(login.actor));
|
||||||
|
localStorage.setItem(AUTH_TOKEN, login.token);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,42 +1,87 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<v-container fluid fill-height>
|
||||||
<h3>Create a new category</h3>
|
<v-layout align-center justify-center>
|
||||||
<v-form>
|
<v-flex xs12 sm8 md4>
|
||||||
<v-text-field
|
<v-card class="elevation-12">
|
||||||
label="Name of the category"
|
<v-toolbar dark color="primary">
|
||||||
v-model="category.title"
|
<v-toolbar-title><translate>Create a new category</translate></v-toolbar-title>
|
||||||
:counter="100"
|
</v-toolbar>
|
||||||
required
|
<v-card-text>
|
||||||
></v-text-field>
|
<v-form>
|
||||||
</v-form>
|
<v-text-field
|
||||||
<v-btn color="primary" @click="create">Create category</v-btn>
|
:label="$gettext('Name of the category')"
|
||||||
</div>
|
v-model="title"
|
||||||
|
:counter="100"
|
||||||
|
required
|
||||||
|
></v-text-field>
|
||||||
|
<v-textarea
|
||||||
|
:label="$gettext('Description')"
|
||||||
|
v-model="description"
|
||||||
|
></v-textarea>
|
||||||
|
<v-flex xs12 class="text-xs-center text-sm-center text-md-center text-lg-center">
|
||||||
|
<v-img :src="image.url" height="150" v-if="image.url" aspect-ratio="1" contain/>
|
||||||
|
<v-text-field label="Select Image" @click='pickFile' v-model='image.name' prepend-icon='attach_file'></v-text-field>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
style="display: none"
|
||||||
|
ref="image"
|
||||||
|
accept="image/*"
|
||||||
|
@change="onFilePicked"
|
||||||
|
>
|
||||||
|
</v-flex>
|
||||||
|
<v-btn color="primary" @click="create"><translate>Create category</translate></v-btn>
|
||||||
|
</v-form>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import eventFetch from '@/api/eventFetch';
|
import { UPLOAD_PICTURE } from '@/graphql/upload';
|
||||||
|
import { CREATE_CATEGORY } from '@/graphql/category';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'create-category',
|
name: 'create-category',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
category: {
|
title: '',
|
||||||
title: '',
|
description: '',
|
||||||
},
|
image: {
|
||||||
};
|
url: '',
|
||||||
},
|
name: '',
|
||||||
methods: {
|
file: '',
|
||||||
create() {
|
|
||||||
const router = this.$router;
|
|
||||||
eventFetch('/categories', this.$store, { method: 'POST', body: JSON.stringify({ category: this.category }) })
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(() => {
|
|
||||||
this.loading = false;
|
|
||||||
router.push('/category')
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
create() {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: CREATE_CATEGORY,
|
||||||
|
variables: {
|
||||||
|
title: this.title,
|
||||||
|
description: this.description,
|
||||||
|
picture: this.$refs.image.files[0],
|
||||||
|
}
|
||||||
|
}).then((data) => {
|
||||||
|
console.log(data);
|
||||||
|
}).catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
pickFile () {
|
||||||
|
this.$refs.image.click ()
|
||||||
|
},
|
||||||
|
onFilePicked(e) {
|
||||||
|
const files = e.target.files;
|
||||||
|
if(files[0] === undefined || files[0].name.lastIndexOf('.') <= 0) {
|
||||||
|
console.error("File is incorrect")
|
||||||
|
}
|
||||||
|
this.image.name = files[0].name;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
<template>
|
<template>
|
||||||
<v-container>
|
<v-container>
|
||||||
<h1>Category List</h1>
|
<h1>Category List</h1>
|
||||||
|
|
||||||
<v-progress-circular v-if="loading" indeterminate color="primary"></v-progress-circular>
|
|
||||||
<v-container fluid grid-list-md class="grey lighten-4">
|
<v-container fluid grid-list-md class="grey lighten-4">
|
||||||
<v-layout row wrap v-if="!loading">
|
<v-progress-circular v-if="$apollo.loading" indeterminate color="primary"></v-progress-circular>
|
||||||
|
<v-layout row wrap v-else>
|
||||||
<v-flex xs12 sm6 md3 v-for="category in categories" :key="category.id">
|
<v-flex xs12 sm6 md3 v-for="category in categories" :key="category.id">
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-card-media v-if="category.image" :src="'/images/categories/' + category.image.name" height="200px">
|
<v-img v-if="category.picture.url" :src="HTTP_ENDPOINT + category.picture.url" height="200px">
|
||||||
</v-card-media>
|
</v-img>
|
||||||
<v-card-title primary-title>
|
<v-card-title primary-title>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="headline mb-0">{{ category.title }}</h3>
|
<h3 class="headline mb-0">{{ category.title }}</h3>
|
||||||
|
@ -16,8 +15,8 @@
|
||||||
</div>
|
</div>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-btn flat class="orange--text">Explore</v-btn>
|
<v-btn flat class="orange--text"><translate>Explore</translate></v-btn>
|
||||||
<v-btn flat class="red--text" v-on:click="deleteCategory(category.id)">Delete</v-btn>
|
<v-btn flat class="red--text" v-on:click="deleteCategory(category.id)"><translate>Delete</translate></v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
|
@ -32,40 +31,36 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import eventFetch from '@/api/eventFetch';
|
import { FETCH_CATEGORIES } from '@/graphql/category';
|
||||||
|
|
||||||
export default {
|
// TODO : remove this hardcode
|
||||||
name: 'Home',
|
|
||||||
data() {
|
|
||||||
return {
|
export default {
|
||||||
categories: [],
|
name: 'Home',
|
||||||
loading: true,
|
data() {
|
||||||
};
|
return {
|
||||||
|
categories: [],
|
||||||
|
loading: true,
|
||||||
|
HTTP_ENDPOINT: 'http://localhost:4000',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
apollo: {
|
||||||
|
categories: {
|
||||||
|
query: FETCH_CATEGORIES,
|
||||||
},
|
},
|
||||||
created() {
|
},
|
||||||
this.fetchData();
|
methods: {
|
||||||
},
|
deleteCategory(categoryId) {
|
||||||
methods: {
|
const router = this.$router;
|
||||||
fetchData() {
|
eventFetch(`/categories/${categoryId}`, this.$store, { method: 'DELETE' })
|
||||||
eventFetch('/categories', this.$store)
|
.then(() => {
|
||||||
.then(response => response.json())
|
this.categories = this.categories.filter(category => category.id !== categoryId);
|
||||||
.then((response) => {
|
|
||||||
this.loading = false;
|
|
||||||
this.categories = response.data;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
deleteCategory(categoryId) {
|
|
||||||
const router = this.$router;
|
|
||||||
eventFetch('/categories/' + categoryId, this.$store, {method: 'DELETE'})
|
|
||||||
.then(() => {
|
|
||||||
this.categories = this.categories.filter((category) => {
|
|
||||||
return category.id !== categoryId;
|
|
||||||
});
|
|
||||||
router.push('/category');
|
router.push('/category');
|
||||||
});
|
});
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
:counter="100"
|
:counter="100"
|
||||||
required
|
required
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
|
<v-date-picker v-model="event.begins_on">
|
||||||
|
</v-date-picker>
|
||||||
<v-radio-group v-model="event.location_type" row>
|
<v-radio-group v-model="event.location_type" row>
|
||||||
<v-radio label="Address" value="physical" off-icon="place"></v-radio>
|
<v-radio label="Address" value="physical" off-icon="place"></v-radio>
|
||||||
<v-radio label="Online" value="online" off-icon="link"></v-radio>
|
<v-radio label="Online" value="online" off-icon="link"></v-radio>
|
||||||
|
@ -64,134 +66,132 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// import Location from '@/components/Location';
|
// import Location from '@/components/Location';
|
||||||
import eventFetch from '@/api/eventFetch';
|
import VueMarkdown from 'vue-markdown';
|
||||||
import VueMarkdown from 'vue-markdown';
|
import { CREATE_EVENT, EDIT_EVENT } from '@/graphql/event';
|
||||||
|
import { FETCH_CATEGORIES } from '@/graphql/category';
|
||||||
|
import { AUTH_USER_ACTOR } from '@/constants';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'create-event',
|
name: 'create-event',
|
||||||
props: ['id'],
|
props: {
|
||||||
|
uuid: {
|
||||||
components: {
|
required: false,
|
||||||
/* Location,*/
|
type: String,
|
||||||
VueMarkdown,
|
|
||||||
},
|
},
|
||||||
data() {
|
},
|
||||||
return {
|
components: {
|
||||||
e1: 0,
|
/* Location, */
|
||||||
event: {
|
VueMarkdown,
|
||||||
title: null,
|
},
|
||||||
description: null,
|
data() {
|
||||||
begins_on: new Date(),
|
return {
|
||||||
ends_on: new Date(),
|
e1: 0,
|
||||||
seats: null,
|
event: {
|
||||||
physical_address: null,
|
title: null,
|
||||||
location_type: 'physical',
|
description: '',
|
||||||
online_address: null,
|
begins_on: (new Date()).toISOString().substr(0, 10),
|
||||||
tel_num: null,
|
ends_on: new Date(),
|
||||||
price: null,
|
seats: null,
|
||||||
category: null,
|
physical_address: null,
|
||||||
category_id: null,
|
location_type: 'physical',
|
||||||
tags: [],
|
online_address: null,
|
||||||
participants: [],
|
tel_num: null,
|
||||||
},
|
price: null,
|
||||||
categories: [],
|
category: null,
|
||||||
|
category_id: null,
|
||||||
tags: [],
|
tags: [],
|
||||||
tagsToSend: [],
|
participants: [],
|
||||||
tagsFetched: [],
|
},
|
||||||
};
|
categories: [],
|
||||||
|
tags: [],
|
||||||
|
tagsToSend: [],
|
||||||
|
tagsFetched: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// created() {
|
||||||
|
// if (this.uuid) {
|
||||||
|
// this.fetchEvent();
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
apollo: {
|
||||||
|
categories: {
|
||||||
|
query: FETCH_CATEGORIES,
|
||||||
},
|
},
|
||||||
created() {
|
},
|
||||||
if (this.id) {
|
methods: {
|
||||||
this.fetchEvent();
|
create() {
|
||||||
|
// this.event.seats = parseInt(this.event.seats, 10);
|
||||||
|
// this.tagsToSend.forEach((tag) => {
|
||||||
|
// this.event.tags.push({
|
||||||
|
// title: tag,
|
||||||
|
// // '@type': 'Tag',
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
const actor = JSON.parse(localStorage.getItem(AUTH_USER_ACTOR));
|
||||||
|
this.event.category_id = this.event.category;
|
||||||
|
this.event.organizer_actor_id = actor.id;
|
||||||
|
this.event.participants = [actor.id];
|
||||||
|
// this.event.price = parseFloat(this.event.price);
|
||||||
|
|
||||||
|
if (this.uuid === undefined) {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: CREATE_EVENT,
|
||||||
|
variables: {
|
||||||
|
title: this.event.title,
|
||||||
|
description: this.event.description,
|
||||||
|
organizerActorId: this.event.organizer_actor_id,
|
||||||
|
categoryId: this.event.category_id,
|
||||||
|
beginsOn: this.event.begins_on,
|
||||||
|
addressType: this.event.location_type,
|
||||||
|
}
|
||||||
|
}).then((data) => {
|
||||||
|
this.loading = false;
|
||||||
|
this.$router.push({ name: 'Event', params: { uuid: data.data.uuid } });
|
||||||
|
}).catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$apollo.mutate({
|
||||||
|
mutation: EDIT_EVENT,
|
||||||
|
}).then((data) => {
|
||||||
|
this.loading = false;
|
||||||
|
this.$router.push({ name: 'Event', params: { uuid: data.data.uuid } });
|
||||||
|
}).catch((error) => {
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.event.tags = [];
|
||||||
|
},
|
||||||
|
// fetchEvent() {
|
||||||
|
// eventFetch(`/events/${this.id}`, this.$store)
|
||||||
|
// .then(response => response.json())
|
||||||
|
// .then((data) => {
|
||||||
|
// this.loading = false;
|
||||||
|
// this.event = data;
|
||||||
|
// console.log(this.event);
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
getAddressData(addressData) {
|
||||||
|
if (addressData !== null) {
|
||||||
|
this.event.address = {
|
||||||
|
geom: {
|
||||||
|
data: {
|
||||||
|
latitude: addressData.latitude,
|
||||||
|
longitude: addressData.longitude,
|
||||||
|
},
|
||||||
|
type: 'point',
|
||||||
|
},
|
||||||
|
addressCountry: addressData.country,
|
||||||
|
addressLocality: addressData.locality,
|
||||||
|
addressRegion: addressData.administrative_area_level_1,
|
||||||
|
postalCode: addressData.postal_code,
|
||||||
|
streetAddress: `${addressData.street_number} ${addressData.route}`,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
},
|
||||||
this.fetchCategories();
|
};
|
||||||
this.fetchTags();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
create() {
|
|
||||||
// this.event.seats = parseInt(this.event.seats, 10);
|
|
||||||
// this.tagsToSend.forEach((tag) => {
|
|
||||||
// this.event.tags.push({
|
|
||||||
// title: tag,
|
|
||||||
// // '@type': 'Tag',
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
this.event.category_id = this.event.category;
|
|
||||||
this.event.organizer_actor_id = this.$store.state.actor.id;
|
|
||||||
this.event.participants = [this.$store.state.actor.id];
|
|
||||||
// this.event.price = parseFloat(this.event.price);
|
|
||||||
|
|
||||||
if (this.id === undefined) {
|
|
||||||
eventFetch('/events', this.$store, {method: 'POST', body: JSON.stringify({ event: this.event })})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then((data) => {
|
|
||||||
this.loading = false;
|
|
||||||
this.$router.push({name: 'Event', params: {uuid: data.data.uuid}});
|
|
||||||
}).catch((err) => {
|
|
||||||
Promise.resolve(err).then((err) => {
|
|
||||||
console.log('err creation', err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
eventFetch(`/events/${this.uuid}`, this.$store, {method: 'PUT', body: JSON.stringify(this.event)})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then((data) => {
|
|
||||||
this.loading = false;
|
|
||||||
this.$router.push({name: 'Event', params: {uuid: data.uuid}});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.event.tags = [];
|
|
||||||
},
|
|
||||||
fetchCategories() {
|
|
||||||
eventFetch('/categories', this.$store)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then((response) => {
|
|
||||||
this.loading = false;
|
|
||||||
this.categories = response.data;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fetchTags() {
|
|
||||||
eventFetch('/tags', this.$store)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then((response) => {
|
|
||||||
this.loading = false;
|
|
||||||
response.data.forEach((tag) => {
|
|
||||||
this.tagsFetched.push(tag.name);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fetchEvent() {
|
|
||||||
eventFetch(`/events/${this.id}`, this.$store)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then((data) => {
|
|
||||||
this.loading = false;
|
|
||||||
this.event = data;
|
|
||||||
console.log(this.event);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getAddressData: function (addressData) {
|
|
||||||
if (addressData !== null) {
|
|
||||||
this.event.address = {
|
|
||||||
geom: {
|
|
||||||
data: {
|
|
||||||
latitude: addressData.latitude,
|
|
||||||
longitude: addressData.longitude,
|
|
||||||
},
|
|
||||||
type: "point",
|
|
||||||
},
|
|
||||||
addressCountry: addressData.country,
|
|
||||||
addressLocality: addressData.locality,
|
|
||||||
addressRegion: addressData.administrative_area_level_1,
|
|
||||||
postalCode: addressData.postal_code,
|
|
||||||
streetAddress: `${addressData.street_number} ${addressData.route}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -98,29 +98,27 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import eventFetch from '@/api/eventFetch';
|
export default {
|
||||||
|
props: ['id'],
|
||||||
export default {
|
data() {
|
||||||
props: ['id'],
|
return {
|
||||||
data() {
|
loading: true,
|
||||||
return {
|
event: null,
|
||||||
loading: true,
|
};
|
||||||
event: null,
|
},
|
||||||
};
|
created() {
|
||||||
|
this.fetchData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchData() {
|
||||||
|
eventFetch(`/events/${this.id}`, this.$store)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
this.loading = false;
|
||||||
|
this.event = data;
|
||||||
|
console.log(this.event);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
created() {
|
},
|
||||||
this.fetchData();
|
};
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
fetchData() {
|
|
||||||
eventFetch(`/events/${this.id}`, this.$store)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then((data) => {
|
|
||||||
this.loading = false;
|
|
||||||
this.event = data;
|
|
||||||
console.log(this.event);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,239 +1,237 @@
|
||||||
<template>
|
<template>
|
||||||
<v-layout row>
|
<v-layout row>
|
||||||
<v-flex xs12 sm6 offset-sm3>
|
<v-flex xs12 sm6 offset-sm3>
|
||||||
<span v-if="error">Error : event not found</span>
|
<v-progress-circular v-if="$apollo.loading" indeterminate color="primary"></v-progress-circular>
|
||||||
<v-progress-circular v-if="loading" indeterminate color="primary"></v-progress-circular>
|
<div>{{ event }}</div>
|
||||||
<v-card v-if="!loading && !error">
|
<v-card v-if="event">
|
||||||
<v-img
|
<!-- <v-img
|
||||||
src="https://picsum.photos/600/400/"
|
src="https://picsum.photos/600/400/"
|
||||||
height="200px"
|
height="200px"
|
||||||
>
|
>
|
||||||
<v-container fill-height fluid>
|
<v-container fill-height fluid>
|
||||||
<v-layout fill-height>
|
<v-layout fill-height>
|
||||||
<v-flex xs12 align-end flexbox>
|
<v-flex xs12 align-end flexbox>
|
||||||
<v-card-title>
|
<v-card-title>
|
||||||
<v-btn icon @click="$router.go(-1)" class="white--text">
|
<v-btn icon @click="$router.go(-1)" class="white--text">
|
||||||
<v-icon>chevron_left</v-icon>
|
<v-icon>chevron_left</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn icon class="mr-3 white--text" v-if="actorIsOrganizer()" :to="{ name: 'EditEvent', params: {uuid: event.uuid}}">
|
||||||
|
<v-icon>edit</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-menu bottom left>
|
||||||
|
<v-btn icon slot="activator" class="white--text">
|
||||||
|
<v-icon>more_vert</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-list>
|
||||||
|
<v-list-tile @click="downloadIcsEvent()">
|
||||||
|
<v-list-tile-title>Download</v-list-tile-title>
|
||||||
|
</v-list-tile>
|
||||||
|
<v-list-tile @click="deleteEvent()" v-if="actorIsOrganizer()">
|
||||||
|
<v-list-tile-title>Delete</v-list-tile-title>
|
||||||
|
</v-list-tile>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
</v-card-title>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-container>
|
||||||
|
</v-img> -->
|
||||||
|
<v-container grid-list-md>
|
||||||
|
<v-layout row wrap>
|
||||||
|
<v-flex md10>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-btn icon class="mr-3 white--text" v-if="actorIsOrganizer()" :to="{ name: 'EditEvent', params: {id: event.id}}">
|
<span class="subheading grey--text">{{ event.begins_on | formatDay }}</span>
|
||||||
<v-icon>edit</v-icon>
|
<h1 class="display-1">{{ event.title }}</h1>
|
||||||
</v-btn>
|
<div>
|
||||||
<v-menu bottom left>
|
<!-- <router-link :to="{name: 'Account', params: { name: event.organizerActor.preferredUsername } }">
|
||||||
<v-btn icon slot="activator" class="white--text">
|
<v-avatar size="25px">
|
||||||
<v-icon>more_vert</v-icon>
|
<img class="img-circle elevation-7 mb-1"
|
||||||
</v-btn>
|
:src="event.organizer_actor.avatarUrl"
|
||||||
<v-list>
|
>
|
||||||
<v-list-tile @click="downloadIcsEvent()">
|
</v-avatar>
|
||||||
<v-list-tile-title>Download</v-list-tile-title>
|
</router-link> -->
|
||||||
|
<!-- <span v-if="event.organizerActor">Organisé par {{ event.organizerActor.name ? event.organizerActor.name : event.organizerActor.preferredUsername }}</span> -->
|
||||||
|
</div>
|
||||||
|
<!-- <p><router-link :to="{ name: 'Account', params: {id: event.organizer.id} }"><span class="grey--text">{{ event.organizer.username }}</span></router-link> organises {{ event.title }} <span v-if="event.address.addressLocality">in {{ event.address.addressLocality }}</span> on the {{ event.startDate | formatDate }}.</p> -->
|
||||||
|
<v-card-text v-if="event.description"><vue-markdown :source="event.description"></vue-markdown></v-card-text>
|
||||||
|
</v-flex>
|
||||||
|
<!-- <v-flex md2>
|
||||||
|
<p v-if="actorIsOrganizer()">
|
||||||
|
Vous êtes organisateur de cet événement.
|
||||||
|
</p>
|
||||||
|
<div v-else>
|
||||||
|
<p v-if="actorIsParticipant()">
|
||||||
|
Vous avez annoncé aller à cet événement.
|
||||||
|
</p>
|
||||||
|
<p v-else>Vous y allez ?
|
||||||
|
<span class="text--darken-2 grey--text">{{ event.participants.length }} personnes y vont.</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<v-card-actions v-if="!actorIsOrganizer()">
|
||||||
|
<v-btn v-if="!actorIsParticipant()" @click="joinEvent" color="success"><v-icon>check</v-icon> Join</v-btn>
|
||||||
|
<v-btn v-if="actorIsParticipant()" @click="leaveEvent" color="error">Leave</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-flex> -->
|
||||||
|
</v-layout>
|
||||||
|
</v-container>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
<v-container>
|
||||||
|
<v-layout row wrap>
|
||||||
|
<v-flex xs12 md4 order-md1>
|
||||||
|
<v-layout
|
||||||
|
column
|
||||||
|
fill-height
|
||||||
|
>
|
||||||
|
<v-list two-line>
|
||||||
|
<v-list-tile>
|
||||||
|
<v-list-tile-action>
|
||||||
|
<v-icon color="indigo">access_time</v-icon>
|
||||||
|
</v-list-tile-action>
|
||||||
|
|
||||||
|
<v-list-tile-content>
|
||||||
|
<v-list-tile-title>{{ event.begins_on | formatDate }}</v-list-tile-title>
|
||||||
|
<v-list-tile-sub-title>{{ event.ends_on | formatDate }}</v-list-tile-sub-title>
|
||||||
|
</v-list-tile-content>
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
<v-list-tile @click="deleteEvent()" v-if="actorIsOrganizer()">
|
|
||||||
<v-list-tile-title>Delete</v-list-tile-title>
|
<v-divider inset></v-divider>
|
||||||
|
|
||||||
|
<v-list-tile>
|
||||||
|
<v-list-tile-action>
|
||||||
|
<v-icon color="indigo">place</v-icon>
|
||||||
|
</v-list-tile-action>
|
||||||
|
|
||||||
|
<v-list-tile-content>
|
||||||
|
<v-list-tile-title><span v-if="event.address_type === 'physical'">
|
||||||
|
{{ event.physical_address.streetAddress }}
|
||||||
|
</span></v-list-tile-title>
|
||||||
|
<v-list-tile-sub-title>Mobile</v-list-tile-sub-title>
|
||||||
|
</v-list-tile-content>
|
||||||
</v-list-tile>
|
</v-list-tile>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-menu>
|
</v-layout>
|
||||||
</v-card-title>
|
</v-flex>
|
||||||
</v-flex>
|
<v-flex md8 xs12>
|
||||||
</v-layout>
|
<p>
|
||||||
</v-container>
|
<h2>Details</h2>
|
||||||
</v-img>
|
<vue-markdown :source="event.description" v-if="event.description" :toc-first-level="3"></vue-markdown>
|
||||||
<v-container grid-list-md>
|
</p>
|
||||||
<v-layout row wrap>
|
<v-subheader>Participants</v-subheader>
|
||||||
<v-flex md10>
|
<!-- <v-flex md2 v-for="participant in event.participants" :key="participant.actor.uuid">
|
||||||
<v-spacer></v-spacer>
|
<router-link :to="{name: 'Account', params: { name: participant.actor.preferredUsername }}">
|
||||||
<span class="subheading grey--text">{{ event.begins_on | formatDay }}</span>
|
<v-card>
|
||||||
<h1 class="display-1">{{ event.title }}</h1>
|
<v-avatar size="75px">
|
||||||
<div>
|
<img v-if="!participant.actor.avatarUrl"
|
||||||
<router-link :to="{name: 'Account', params: { name: event.organizer.username } }">
|
class="img-circle elevation-7 mb-1"
|
||||||
<v-avatar size="25px">
|
src="https://picsum.photos/125/125/"
|
||||||
<img class="img-circle elevation-7 mb-1"
|
>
|
||||||
:src="event.organizer.avatar"
|
<img v-else
|
||||||
>
|
class="img-circle elevation-7 mb-1"
|
||||||
</v-avatar>
|
:src="participant.actor.avatarUrl"
|
||||||
</router-link>
|
>
|
||||||
<span v-if="event.organizer">Organisé par {{ event.organizer.display_name ? event.organizer.display_name : event.organizer.username }}</span>
|
</v-avatar>
|
||||||
</div>
|
<v-card-title>
|
||||||
<!--<p><router-link :to="{ name: 'Account', params: {id: event.organizer.id} }"><span class="grey--text">{{ event.organizer.username }}</span></router-link> organises {{ event.title }} <span v-if="event.address.addressLocality">in {{ event.address.addressLocality }}</span> on the {{ event.startDate | formatDate }}.</p>
|
<span>{{ participant.actor.preferredUsername }}</span>
|
||||||
<v-card-text v-if="event.description"><vue-markdown :source="event.description"></vue-markdown></v-card-text>-->
|
</v-card-title>
|
||||||
</v-flex>
|
</v-card>
|
||||||
<v-flex md2>
|
</router-link>
|
||||||
<p v-if="actorIsOrganizer()">
|
</v-flex> -->
|
||||||
Vous êtes organisateur de cet événement.
|
</v-flex>
|
||||||
</p>
|
<span v-if="event.participants.length === 0">No participants yet.</span>
|
||||||
<div v-else>
|
|
||||||
<p v-if="actorIsParticipant()">
|
|
||||||
Vous avez annoncé aller à cet événement.
|
|
||||||
</p>
|
|
||||||
<p v-else>Vous y allez ?
|
|
||||||
<span class="text--darken-2 grey--text">{{ event.participants.length }} personnes y vont.</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<v-card-actions v-if="!actorIsOrganizer()">
|
|
||||||
<v-btn v-if="!actorIsParticipant()" @click="joinEvent" color="success"><v-icon>check</v-icon> Join</v-btn>
|
|
||||||
<v-btn v-if="actorIsParticipant()" @click="leaveEvent" color="error">Leave</v-btn>
|
|
||||||
</v-card-actions>
|
|
||||||
</v-flex>
|
|
||||||
</v-layout>
|
|
||||||
</v-container>
|
|
||||||
<v-divider></v-divider>
|
|
||||||
<v-container>
|
|
||||||
<v-layout row wrap>
|
|
||||||
<v-flex xs12 md4 order-md1>
|
|
||||||
<v-layout
|
|
||||||
column
|
|
||||||
fill-height
|
|
||||||
>
|
|
||||||
<v-list two-line>
|
|
||||||
<v-list-tile>
|
|
||||||
<v-list-tile-action>
|
|
||||||
<v-icon color="indigo">access_time</v-icon>
|
|
||||||
</v-list-tile-action>
|
|
||||||
|
|
||||||
<v-list-tile-content>
|
|
||||||
<v-list-tile-title>{{ event.begins_on | formatDate }}</v-list-tile-title>
|
|
||||||
<v-list-tile-sub-title>{{ event.ends_on | formatDate }}</v-list-tile-sub-title>
|
|
||||||
</v-list-tile-content>
|
|
||||||
</v-list-tile>
|
|
||||||
|
|
||||||
<v-divider inset></v-divider>
|
|
||||||
|
|
||||||
<v-list-tile>
|
|
||||||
<v-list-tile-action>
|
|
||||||
<v-icon color="indigo">place</v-icon>
|
|
||||||
</v-list-tile-action>
|
|
||||||
|
|
||||||
<v-list-tile-content>
|
|
||||||
<v-list-tile-title><span v-if="event.address_type === 'physical'">
|
|
||||||
{{ event.physical_address.streetAddress }}
|
|
||||||
</span></v-list-tile-title>
|
|
||||||
<v-list-tile-sub-title>Mobile</v-list-tile-sub-title>
|
|
||||||
</v-list-tile-content>
|
|
||||||
</v-list-tile>
|
|
||||||
</v-list>
|
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</v-flex>
|
</v-container>
|
||||||
<v-flex md8 xs12>
|
</v-card>
|
||||||
<p>
|
|
||||||
<h2>Details</h2>
|
|
||||||
<vue-markdown :source="event.description" v-if="event.description" :toc-first-level="3" />
|
|
||||||
</p>
|
|
||||||
<v-subheader>Participants</v-subheader>
|
|
||||||
<v-flex md2 v-for="actor in event.participants" :key="actor.uuid">
|
|
||||||
<router-link :to="{name: 'Account', params: { name: actor.username }}">
|
|
||||||
<v-avatar size="75px">
|
|
||||||
<img v-if="!actor.avatar"
|
|
||||||
class="img-circle elevation-7 mb-1"
|
|
||||||
src="https://picsum.photos/125/125/"
|
|
||||||
>
|
|
||||||
<img v-else
|
|
||||||
class="img-circle elevation-7 mb-1"
|
|
||||||
:src="actor.avatar"
|
|
||||||
>
|
|
||||||
</v-avatar>
|
|
||||||
</router-link>
|
|
||||||
<span>{{ actor.username }}</span>
|
|
||||||
</v-flex>
|
|
||||||
</v-flex>
|
|
||||||
<span v-if="event.participants.length === 0">No participants yet.</span>
|
|
||||||
</v-layout>
|
|
||||||
</v-container>
|
|
||||||
</v-card>
|
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import eventFetch from '@/api/eventFetch';
|
import VueMarkdown from 'vue-markdown';
|
||||||
import VueMarkdown from 'vue-markdown';
|
import { FETCH_EVENT } from '@/graphql/event';
|
||||||
|
import { LOGGED_ACTOR } from '@/graphql/actor';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
components: {
|
components: {
|
||||||
VueMarkdown,
|
VueMarkdown,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
loading: true,
|
event: {
|
||||||
error: false,
|
name: '',
|
||||||
event: {
|
slug: '',
|
||||||
name: '',
|
title: '',
|
||||||
slug: '',
|
uuid: this.uuid,
|
||||||
title: '',
|
description: '',
|
||||||
|
organizer: {
|
||||||
|
id: null,
|
||||||
|
username: null,
|
||||||
|
},
|
||||||
|
participants: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
apollo: {
|
||||||
|
event: {
|
||||||
|
query: FETCH_EVENT,
|
||||||
|
variables() {
|
||||||
|
return {
|
||||||
uuid: this.uuid,
|
uuid: this.uuid,
|
||||||
description: '',
|
};
|
||||||
organizer: {
|
|
||||||
id: null,
|
|
||||||
username: null,
|
|
||||||
},
|
|
||||||
participants: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
deleteEvent() {
|
|
||||||
const router = this.$router;
|
|
||||||
eventFetch(`/events/${this.uuid}`, this.$store, { method: 'DELETE' })
|
|
||||||
.then(() => router.push({'name': 'EventList'}));
|
|
||||||
},
|
},
|
||||||
fetchData() {
|
},
|
||||||
eventFetch(`/events/${this.uuid}`, this.$store)
|
// loggedActor: {
|
||||||
.then(response => response.json())
|
// query: LOGGED_ACTOR,
|
||||||
.then((data) => {
|
// }
|
||||||
this.loading = false;
|
},
|
||||||
this.event = data.data;
|
methods: {
|
||||||
console.log('event', this.event);
|
deleteEvent() {
|
||||||
}).catch((res) => {
|
const router = this.$router;
|
||||||
Promise.resolve(res).then((data) => {
|
eventFetch(`/events/${this.uuid}`, this.$store, { method: 'DELETE' })
|
||||||
console.log(data);
|
.then(() => router.push({ name: 'EventList' }));
|
||||||
this.error = true;
|
},
|
||||||
this.loading = false;
|
joinEvent() {
|
||||||
});
|
eventFetch(`/events/${this.uuid}/join`, this.$store, { method: 'POST' })
|
||||||
|
.then(response => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
console.log(data);
|
||||||
});
|
});
|
||||||
},
|
|
||||||
joinEvent() {
|
|
||||||
eventFetch(`/events/${this.uuid}/join`, this.$store, { method: 'POST' })
|
|
||||||
.then(response => response.json())
|
|
||||||
.then((data) => {
|
|
||||||
console.log(data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
leaveEvent() {
|
|
||||||
eventFetch(`/events/${this.uuid}/leave`, this.$store)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then((data) => {
|
|
||||||
console.log(data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
downloadIcsEvent() {
|
|
||||||
eventFetch(`/events/${this.uuid}/ics`, this.$store, {responseType: 'arraybuffer'})
|
|
||||||
.then((response) => response.text())
|
|
||||||
.then(response => {
|
|
||||||
const blob = new Blob([response],{type: 'text/calendar'});
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = window.URL.createObjectURL(blob);
|
|
||||||
link.download = `${this.event.title}.ics`;
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
document.body.removeChild(link);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
actorIsParticipant() {
|
|
||||||
return this.$store.state.actor && this.event.participants.map(participant => participant.id).includes(this.$store.state.actor.id) || this.actorIsOrganizer();
|
|
||||||
},
|
|
||||||
actorIsOrganizer() {
|
|
||||||
return this.$store.state.actor && this.$store.state.actor.id === this.event.organizer.id;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
props: {
|
leaveEvent() {
|
||||||
uuid: {
|
eventFetch(`/events/${this.uuid}/leave`, this.$store)
|
||||||
type: String,
|
.then(response => response.json())
|
||||||
required: true,
|
.then((data) => {
|
||||||
},
|
console.log(data);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
created() {
|
downloadIcsEvent() {
|
||||||
this.fetchData();
|
eventFetch(`/events/${this.uuid}/ics`, this.$store, { responseType: 'arraybuffer' })
|
||||||
|
.then(response => response.text())
|
||||||
|
.then((response) => {
|
||||||
|
const blob = new Blob([response], { type: 'text/calendar' });
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = window.URL.createObjectURL(blob);
|
||||||
|
link.download = `${this.event.title}.ics`;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
// actorIsParticipant() {
|
||||||
|
// return this.loggedActor && this.event.participants.map(participant => participant.actor.preferredUsername).includes(this.loggedActor.preferredUsername) || this.actorIsOrganizer();
|
||||||
|
// },
|
||||||
|
// actorIsOrganizer() {
|
||||||
|
// return this.loggedActor && this.loggedActor.preferredUsername === this.event.organizer.preferredUsername;
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
uuid: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
|
|
@ -54,85 +54,84 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ngeohash from 'ngeohash';
|
import ngeohash from 'ngeohash';
|
||||||
import VueMarkdown from 'vue-markdown';
|
import VueMarkdown from 'vue-markdown';
|
||||||
import eventFetch from '@/api/eventFetch';
|
import VCardTitle from 'vuetify/es5/components/VCard/VCardTitle';
|
||||||
import VCardTitle from "vuetify/es5/components/VCard/VCardTitle";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'EventList',
|
name: 'EventList',
|
||||||
components: {
|
components: {
|
||||||
VCardTitle,
|
VCardTitle,
|
||||||
VueMarkdown
|
VueMarkdown,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
events: [],
|
events: [],
|
||||||
loading: true,
|
loading: true,
|
||||||
locationChip: false,
|
locationChip: false,
|
||||||
locationText: '',
|
locationText: '',
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
props: ['location'],
|
props: ['location'],
|
||||||
created() {
|
created() {
|
||||||
this.fetchData(this.$router.currentRoute.params.location);
|
this.fetchData(this.$router.currentRoute.params.location);
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
locationChip(val) {
|
locationChip(val) {
|
||||||
if (val === false) {
|
if (val === false) {
|
||||||
this.$router.push({name: 'EventList'});
|
this.$router.push({ name: 'EventList' });
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
beforeRouteUpdate(to, from, next) {
|
},
|
||||||
this.fetchData(to.params.location);
|
beforeRouteUpdate(to, from, next) {
|
||||||
next();
|
this.fetchData(to.params.location);
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
geocode(lat, lon) {
|
||||||
|
console.log({ lat, lon });
|
||||||
|
console.log(ngeohash.encode(lat, lon, 10));
|
||||||
|
return ngeohash.encode(lat, lon, 10);
|
||||||
},
|
},
|
||||||
methods: {
|
fetchData(location) {
|
||||||
geocode(lat, lon) {
|
let queryString = '/events';
|
||||||
console.log({lat, lon});
|
if (location) {
|
||||||
console.log(ngeohash.encode(lat, lon, 10));
|
queryString += (`?geohash=${location}`);
|
||||||
return ngeohash.encode(lat, lon, 10);
|
const { latitude, longitude } = ngeohash.decode(location);
|
||||||
},
|
this.locationText = `${latitude.toString()} : ${longitude.toString()}`;
|
||||||
fetchData(location) {
|
}
|
||||||
let queryString = '/events';
|
this.locationChip = true;
|
||||||
if (location) {
|
eventFetch(queryString, this.$store)
|
||||||
queryString += ('?geohash=' + location);
|
.then(response => response.json())
|
||||||
const { latitude, longitude } = ngeohash.decode(location);
|
.then((response) => {
|
||||||
this.locationText = latitude.toString() + ' : ' + longitude.toString();
|
this.loading = false;
|
||||||
}
|
this.events = response.data;
|
||||||
this.locationChip = true;
|
console.log(this.events);
|
||||||
eventFetch(queryString, this.$store)
|
});
|
||||||
.then(response => response.json())
|
|
||||||
.then((response) => {
|
|
||||||
this.loading = false;
|
|
||||||
this.events = response.data;
|
|
||||||
console.log(this.events);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
deleteEvent(event) {
|
|
||||||
const router = this.$router;
|
|
||||||
eventFetch(`/events/${event.uuid}`, this.$store, {'method': 'DELETE'})
|
|
||||||
.then(() => router.push('/events'));
|
|
||||||
},
|
|
||||||
viewEvent(event) {
|
|
||||||
this.$router.push({ name: 'Event', params: { uuid: event.uuid } })
|
|
||||||
},
|
|
||||||
downloadIcsEvent(event) {
|
|
||||||
eventFetch(`/events/${event.uuid}/ics`, this.$store, {responseType: 'arraybuffer'})
|
|
||||||
.then((response) => response.text())
|
|
||||||
.then(response => {
|
|
||||||
const blob = new Blob([response],{type: 'text/calendar'});
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = window.URL.createObjectURL(blob);
|
|
||||||
link.download = `${event.title}.ics`;
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
document.body.removeChild(link);
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
deleteEvent(event) {
|
||||||
|
const router = this.$router;
|
||||||
|
eventFetch(`/events/${event.uuid}`, this.$store, { method: 'DELETE' })
|
||||||
|
.then(() => router.push('/events'));
|
||||||
|
},
|
||||||
|
viewEvent(event) {
|
||||||
|
this.$router.push({ name: 'Event', params: { uuid: event.uuid } });
|
||||||
|
},
|
||||||
|
downloadIcsEvent(event) {
|
||||||
|
eventFetch(`/events/${event.uuid}/ics`, this.$store, { responseType: 'arraybuffer' })
|
||||||
|
.then(response => response.text())
|
||||||
|
.then((response) => {
|
||||||
|
const blob = new Blob([response], { type: 'text/calendar' });
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = window.URL.createObjectURL(blob);
|
||||||
|
link.download = `${event.title}.ics`;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
|
|
@ -65,66 +65,65 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import eventFetch from '@/api/eventFetch';
|
import VueMarkdown from 'vue-markdown';
|
||||||
import VueMarkdown from 'vue-markdown';
|
import VuetifyGoogleAutocomplete from 'vuetify-google-autocomplete';
|
||||||
import VuetifyGoogleAutocomplete from 'vuetify-google-autocomplete';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'create-group',
|
name: 'create-group',
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
VueMarkdown,
|
VueMarkdown,
|
||||||
VuetifyGoogleAutocomplete,
|
VuetifyGoogleAutocomplete,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
e1: 0,
|
||||||
|
group: {
|
||||||
|
preferred_username: '',
|
||||||
|
name: '',
|
||||||
|
summary: '',
|
||||||
|
// category: null,
|
||||||
|
},
|
||||||
|
categories: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.fetchCategories();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
create() {
|
||||||
|
// this.group.organizer = "/accounts/" + this.$store.state.user.id;
|
||||||
|
|
||||||
|
eventFetch('/groups', this.$store, { method: 'POST', body: JSON.stringify({ group: this.group }) })
|
||||||
|
.then(response => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
this.loading = false;
|
||||||
|
this.$router.push({ path: 'Group', params: { id: data.id } });
|
||||||
|
});
|
||||||
},
|
},
|
||||||
data() {
|
fetchCategories() {
|
||||||
return {
|
eventFetch('/categories', this.$store)
|
||||||
e1: 0,
|
.then(response => response.json())
|
||||||
group: {
|
.then((data) => {
|
||||||
preferred_username: '',
|
this.loading = false;
|
||||||
name: '',
|
this.categories = data.data;
|
||||||
summary: '',
|
});
|
||||||
// category: null,
|
},
|
||||||
|
getAddressData(addressData) {
|
||||||
|
this.group.address = {
|
||||||
|
geo: {
|
||||||
|
latitude: addressData.latitude,
|
||||||
|
longitude: addressData.longitude,
|
||||||
},
|
},
|
||||||
categories: [],
|
addressCountry: addressData.country,
|
||||||
|
addressLocality: addressData.city,
|
||||||
|
addressRegion: addressData.administrative_area_level_1,
|
||||||
|
postalCode: addressData.postal_code,
|
||||||
|
streetAddress: `${addressData.street_number} ${addressData.route}`,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted() {
|
},
|
||||||
this.fetchCategories();
|
};
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
create() {
|
|
||||||
// this.group.organizer = "/accounts/" + this.$store.state.user.id;
|
|
||||||
|
|
||||||
eventFetch('/groups', this.$store, { method: 'POST', body: JSON.stringify({group: this.group}) })
|
|
||||||
.then(response => response.json())
|
|
||||||
.then((data) => {
|
|
||||||
this.loading = false;
|
|
||||||
this.$router.push({ path: 'Group', params: { id: data.id } });
|
|
||||||
});
|
|
||||||
},
|
|
||||||
fetchCategories() {
|
|
||||||
eventFetch('/categories', this.$store)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then((data) => {
|
|
||||||
this.loading = false;
|
|
||||||
this.categories = data.data;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getAddressData: function (addressData) {
|
|
||||||
this.group.address = {
|
|
||||||
geo: {
|
|
||||||
latitude: addressData.latitude,
|
|
||||||
longitude: addressData.longitude,
|
|
||||||
},
|
|
||||||
addressCountry: addressData.country,
|
|
||||||
addressLocality: addressData.city,
|
|
||||||
addressRegion: addressData.administrative_area_level_1,
|
|
||||||
postalCode: addressData.postal_code,
|
|
||||||
streetAddress: `${addressData.street_number} ${addressData.route}`,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -202,39 +202,37 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import eventFetch from '@/api/eventFetch';
|
export default {
|
||||||
|
name: 'Group',
|
||||||
export default {
|
data() {
|
||||||
name: 'Group',
|
return {
|
||||||
data() {
|
group: null,
|
||||||
return {
|
loading: true,
|
||||||
group: null,
|
};
|
||||||
loading: true,
|
},
|
||||||
}
|
props: {
|
||||||
},
|
name: {
|
||||||
props: {
|
type: String,
|
||||||
name: {
|
required: true,
|
||||||
type: String,
|
},
|
||||||
required: true,
|
},
|
||||||
}
|
created() {
|
||||||
},
|
this.fetchData();
|
||||||
created() {
|
},
|
||||||
this.fetchData();
|
watch: {
|
||||||
},
|
// call again the method if the route changes
|
||||||
watch: {
|
$route: 'fetchData',
|
||||||
// call again the method if the route changes
|
},
|
||||||
'$route': 'fetchData'
|
methods: {
|
||||||
},
|
fetchData() {
|
||||||
methods: {
|
eventFetch(`/actors/${this.name}`, this.$store)
|
||||||
fetchData() {
|
.then(response => response.json())
|
||||||
eventFetch(`/actors/${this.name}`, this.$store)
|
.then((response) => {
|
||||||
.then(response => response.json())
|
this.group = response.data;
|
||||||
.then((response) => {
|
this.loading = false;
|
||||||
this.group = response.data;
|
console.log(this.group);
|
||||||
this.loading = false;
|
});
|
||||||
console.log(this.group);
|
},
|
||||||
})
|
},
|
||||||
}
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -38,49 +38,47 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import eventFetch from '@/api/eventFetch';
|
export default {
|
||||||
|
name: 'GroupList',
|
||||||
export default {
|
data() {
|
||||||
name: 'GroupList',
|
return {
|
||||||
data() {
|
groups: [],
|
||||||
return {
|
loading: true,
|
||||||
groups: [],
|
};
|
||||||
loading: true,
|
},
|
||||||
};
|
created() {
|
||||||
|
this.fetchData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
username_with_domain(actor) {
|
||||||
|
return actor.username + (actor.domain === null ? '' : `@${actor.domain}`);
|
||||||
},
|
},
|
||||||
created() {
|
fetchData() {
|
||||||
this.fetchData();
|
eventFetch('/groups', this.$store)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
console.log(data);
|
||||||
|
this.loading = false;
|
||||||
|
this.groups = data.data;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
deleteGroup(group) {
|
||||||
username_with_domain(actor) {
|
const router = this.$router;
|
||||||
return actor.username + (actor.domain === null ? '' : `@${actor.domain}`)
|
eventFetch(`/groups/${this.username_with_domain(group)}`, this.$store, { method: 'DELETE' })
|
||||||
},
|
.then(response => response.json())
|
||||||
fetchData() {
|
.then(() => router.push('/groups'));
|
||||||
eventFetch('/groups', this.$store)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then((data) => {
|
|
||||||
console.log(data);
|
|
||||||
this.loading = false;
|
|
||||||
this.groups = data.data;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
deleteGroup(group) {
|
|
||||||
const router = this.$router;
|
|
||||||
eventFetch(`/groups/${this.username_with_domain(group)}`, this.$store, {'method': 'DELETE'})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(() => router.push('/groups'));
|
|
||||||
},
|
|
||||||
viewActor(actor) {
|
|
||||||
this.$router.push({ name: 'Group', params: { name: this.username_with_domain(actor) } })
|
|
||||||
},
|
|
||||||
joinGroup(group) {
|
|
||||||
const router = this.$router;
|
|
||||||
eventFetch(`/groups/${this.username_with_domain(group)}/join`, this.$store, { method: 'POST' })
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(() => router.push({ name: 'Group', params: { name: this.username_with_domain(group) } }));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
viewActor(actor) {
|
||||||
|
this.$router.push({ name: 'Group', params: { name: this.username_with_domain(actor) } });
|
||||||
|
},
|
||||||
|
joinGroup(group) {
|
||||||
|
const router = this.$router;
|
||||||
|
eventFetch(`/groups/${this.username_with_domain(group)}/join`, this.$store, { method: 'POST' })
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(() => router.push({ name: 'Group', params: { name: this.username_with_domain(group) } }));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
|
|
@ -5,14 +5,14 @@
|
||||||
src="https://picsum.photos/1200/900"
|
src="https://picsum.photos/1200/900"
|
||||||
dark
|
dark
|
||||||
height="300"
|
height="300"
|
||||||
v-if="$store.state.user === false"
|
v-if="!user"
|
||||||
>
|
>
|
||||||
<v-container fill-height>
|
<v-container fill-height>
|
||||||
<v-layout align-center>
|
<v-layout align-center>
|
||||||
<v-flex text-xs-center>
|
<v-flex text-xs-center>
|
||||||
<h1 class="display-3">Find events you like</h1>
|
<h1 class="display-3">Find events you like</h1>
|
||||||
<h2>Share it with Mobilizon</h2>
|
<h2>Share it with Mobilizon</h2>
|
||||||
<v-btn :to="{ name: 'Register' }">{{ $t("home.register") }}</v-btn>
|
<v-btn :to="{ name: 'Register' }"><translate>Register</translate></v-btn>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</v-container>
|
</v-container>
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
<v-flex xs12 sm8 offset-sm2>
|
<v-flex xs12 sm8 offset-sm2>
|
||||||
<v-layout row wrap>
|
<v-layout row wrap>
|
||||||
<v-flex xs12 sm6>
|
<v-flex xs12 sm6>
|
||||||
<h1>Welcome back {{ $store.state.actor.username }}</h1>
|
<h1><translate :translate-params="{username: actor.preferredUsername}">Welcome back %{username}</translate></h1>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
<v-flex xs12 sm6>
|
<v-flex xs12 sm6>
|
||||||
<v-layout align-center>
|
<v-layout align-center>
|
||||||
|
@ -33,11 +33,14 @@
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
|
<div v-if="$apollo.loading">
|
||||||
|
Still loading
|
||||||
|
</div>
|
||||||
<v-card v-if="events.length > 0">
|
<v-card v-if="events.length > 0">
|
||||||
<v-layout row wrap>
|
<v-layout row wrap>
|
||||||
<v-flex md4 v-for="event in events" :key="event.uuid">
|
<v-flex md4 v-for="event in events" :key="event.uuid">
|
||||||
<v-card :to="{ name: 'Event', params:{ uuid: event.uuid } }">
|
<v-card :to="{ name: 'Event', params:{ uuid: event.uuid } }">
|
||||||
<v-card-media v-if="!event.image"
|
<v-img v-if="!event.image"
|
||||||
class="white--text"
|
class="white--text"
|
||||||
height="200px"
|
height="200px"
|
||||||
src="https://picsum.photos/g/400/200/"
|
src="https://picsum.photos/g/400/200/"
|
||||||
|
@ -49,26 +52,26 @@
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</v-container>
|
</v-container>
|
||||||
</v-card-media>
|
</v-img>
|
||||||
<v-card-title primary-title>
|
<v-card-title primary-title>
|
||||||
<div>
|
<div>
|
||||||
<span class="grey--text">{{ event.begins_on | formatDay }}</span><br>
|
<span class="grey--text">{{ event.begins_on | formatDay }}</span><br>
|
||||||
<router-link :to="{name: 'Account', params: { name: event.organizer.username } }">
|
<router-link :to="{name: 'Account', params: { name: event.organizerActor.preferredUsername } }">
|
||||||
<v-avatar size="25px">
|
<v-avatar size="25px">
|
||||||
<img class="img-circle elevation-7 mb-1"
|
<img class="img-circle elevation-7 mb-1"
|
||||||
:src="event.organizer.avatar"
|
:src="event.organizerActor.avatarUrl"
|
||||||
>
|
>
|
||||||
</v-avatar>
|
</v-avatar>
|
||||||
</router-link>
|
</router-link>
|
||||||
<span v-if="event.organizer">Organisé par {{ event.organizer.display_name ? event.organizer.display_name : event.organizer.username }}</span>
|
<span v-if="event.organizerActor">Organisé par {{ event.organizerActor.name ? event.organizerActor.name : event.organizerActor.preferredUsername }}</span>
|
||||||
</div>
|
</div>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</v-card>
|
</v-card>
|
||||||
<v-alert v-else :value="true" type="info">
|
<v-alert v-else :value="true" type="error">
|
||||||
No events found nearby {{ ipLocation() }}
|
No events found
|
||||||
</v-alert>
|
</v-alert>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
|
@ -76,83 +79,75 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
import ngeohash from 'ngeohash';
|
import ngeohash from 'ngeohash';
|
||||||
import eventFetch from "../api/eventFetch";
|
import {AUTH_USER_ACTOR, AUTH_USER_ID} from '@/constants';
|
||||||
|
import { FETCH_EVENTS } from '@/graphql/event';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
gradient: 'to top right, rgba(63,81,181, .7), rgba(25,32,72, .7)',
|
gradient: 'to top right, rgba(63,81,181, .7), rgba(25,32,72, .7)',
|
||||||
user: null,
|
|
||||||
searchTerm: null,
|
searchTerm: null,
|
||||||
location_field: {
|
location_field: {
|
||||||
loading: false,
|
loading: false,
|
||||||
search: null,
|
search: null,
|
||||||
},
|
},
|
||||||
locations: [],
|
|
||||||
events: [],
|
events: [],
|
||||||
city: {name: null},
|
locations: [],
|
||||||
country: {name: null},
|
city: { name: null },
|
||||||
|
country: { name: null },
|
||||||
|
actor: JSON.parse(localStorage.getItem(AUTH_USER_ACTOR)),
|
||||||
|
user: localStorage.getItem(AUTH_USER_ID),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
created() {
|
apollo: {
|
||||||
this.fetchData();
|
events: {
|
||||||
|
query: FETCH_EVENTS,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
displayed_name() {
|
displayed_name() {
|
||||||
return this.$store.state.actor.display_name === null ? this.$store.state.actor.username : this.$store.state.actor.display_name
|
return this.actor.name === null ? this.actor.preferredUsername : this.actor.name;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchLocations() {
|
fetchLocations() {
|
||||||
eventFetch('/locations', this.$store)
|
eventFetch('/locations', this.$store)
|
||||||
.then((response) => (response.json()))
|
.then(response => (response.json()))
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
this.locations = response;
|
this.locations = response;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
fetchData() {
|
|
||||||
eventFetch('/events', this.$store)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then((response) => {
|
|
||||||
this.loading = false;
|
|
||||||
this.events = response.data;
|
|
||||||
this.city = response.city;
|
|
||||||
this.country = response.country;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
geoLocalize() {
|
geoLocalize() {
|
||||||
const router = this.$router;
|
const router = this.$router;
|
||||||
if (sessionStorage.getItem('City')) {
|
if (sessionStorage.getItem('City')) {
|
||||||
router.push({name: 'EventList', params: {location: localStorage.getItem('City')}})
|
router.push({ name: 'EventList', params: { location: localStorage.getItem('City') } });
|
||||||
} else {
|
} else {
|
||||||
navigator.geolocation.getCurrentPosition((pos) => {
|
navigator.geolocation.getCurrentPosition((pos) => {
|
||||||
const crd = pos.coords;
|
const crd = pos.coords;
|
||||||
|
|
||||||
const geohash = ngeohash.encode(crd.latitude, crd.longitude, 11);
|
const geohash = ngeohash.encode(crd.latitude, crd.longitude, 11);
|
||||||
sessionStorage.setItem('City', geohash);
|
sessionStorage.setItem('City', geohash);
|
||||||
router.push({name: 'EventList', params: {location: geohash}});
|
router.push({ name: 'EventList', params: { location: geohash } });
|
||||||
|
}, err => console.warn(`ERROR(${err.code}): ${err.message}`), {
|
||||||
}, (err) => console.warn(`ERROR(${err.code}): ${err.message}`), {
|
|
||||||
enableHighAccuracy: true,
|
enableHighAccuracy: true,
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
maximumAge: 0
|
maximumAge: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getAddressData: function (addressData) {
|
getAddressData(addressData) {
|
||||||
const geohash = ngeohash.encode(addressData.latitude, addressData.longitude, 11);
|
const geohash = ngeohash.encode(addressData.latitude, addressData.longitude, 11);
|
||||||
sessionStorage.setItem('City', geohash);
|
sessionStorage.setItem('City', geohash);
|
||||||
this.$router.push({name: 'EventList', params: {location: geohash}});
|
this.$router.push({ name: 'EventList', params: { location: geohash } });
|
||||||
},
|
},
|
||||||
viewEvent(event) {
|
viewEvent(event) {
|
||||||
this.$router.push({ name: 'Event', params: { uuid: event.uuid } })
|
this.$router.push({ name: 'Event', params: { uuid: event.uuid } });
|
||||||
},
|
},
|
||||||
ipLocation() {
|
ipLocation() {
|
||||||
return this.city.name ? this.city.name : this.country.name;
|
return this.city.name ? this.city.name : this.country.name;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -26,27 +26,27 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
description: 'Paris, France',
|
description: 'Paris, France',
|
||||||
center: { lat: 48.85, lng: 2.35 },
|
center: { lat: 48.85, lng: 2.35 },
|
||||||
markers: [],
|
markers: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
props: ['address'],
|
||||||
|
methods: {
|
||||||
|
setPlace(place) {
|
||||||
|
this.center = {
|
||||||
|
lat: place.geometry.location.lat(),
|
||||||
|
lng: place.geometry.location.lng(),
|
||||||
};
|
};
|
||||||
|
this.markers = [{
|
||||||
|
position: { lat: this.center.lat, lng: this.center.lng },
|
||||||
|
}];
|
||||||
|
this.$emit('input', place.formatted_address);
|
||||||
},
|
},
|
||||||
props: ['address'],
|
},
|
||||||
methods: {
|
};
|
||||||
setPlace(place) {
|
|
||||||
this.center = {
|
|
||||||
lat: place.geometry.location.lat(),
|
|
||||||
lng: place.geometry.location.lng(),
|
|
||||||
};
|
|
||||||
this.markers = [{
|
|
||||||
position: { lat: this.center.lat, lng: this.center.lng },
|
|
||||||
}];
|
|
||||||
this.$emit('input', place.formatted_address);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -12,33 +12,36 @@
|
||||||
</router-link>
|
</router-link>
|
||||||
</v-toolbar-title>
|
</v-toolbar-title>
|
||||||
<v-autocomplete
|
<v-autocomplete
|
||||||
:loading="searchElement.loading"
|
:loading="$apollo.loading"
|
||||||
flat
|
flat
|
||||||
solo-inverted
|
solo-inverted
|
||||||
prepend-icon="search"
|
prepend-icon="search"
|
||||||
label="Search"
|
:label="$gettext('Search')"
|
||||||
required
|
required
|
||||||
item-text="displayedText"
|
item-text="label"
|
||||||
class="hidden-sm-and-down"
|
class="hidden-sm-and-down"
|
||||||
:items="searchElement.items"
|
:items="items"
|
||||||
:search-input.sync="search"
|
:search-input.sync="searchText"
|
||||||
v-model="searchSelect"
|
v-model="model"
|
||||||
return-object
|
return-object
|
||||||
>
|
>
|
||||||
<template slot="item" slot-scope="data">
|
<template slot="item" slot-scope="data">
|
||||||
<template v-if="typeof data.item !== 'object'">
|
<!-- <div>{{ data }}</div> -->
|
||||||
<v-list-tile-content v-text="data.item"></v-list-tile-content>
|
<v-list-tile v-if="data.item.__typename === 'Event'">
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<v-list-tile-avatar>
|
<v-list-tile-avatar>
|
||||||
<img :src="data.item.avatar" v-if="data.item.avatar">
|
<v-icon>event</v-icon>
|
||||||
<v-icon v-else>event</v-icon>
|
</v-list-tile-avatar>
|
||||||
|
<v-list-tile-content v-text="data.item.label"></v-list-tile-content>
|
||||||
|
</v-list-tile>
|
||||||
|
<v-list-tile v-else-if="data.item.__typename === 'Actor'">
|
||||||
|
<v-list-tile-avatar>
|
||||||
|
<img :src="data.item.avatarUrl" v-if="data.item.avatarUrl">
|
||||||
|
<v-icon v-else>account_circle</v-icon>
|
||||||
</v-list-tile-avatar>
|
</v-list-tile-avatar>
|
||||||
<v-list-tile-content>
|
<v-list-tile-content>
|
||||||
<v-list-tile-title v-html="username_with_domain(data.item)"></v-list-tile-title>
|
<v-list-tile-title v-html="username_with_domain(data.item)"></v-list-tile-title>
|
||||||
<v-list-tile-sub-title v-html="data.item.type"></v-list-tile-sub-title>
|
|
||||||
</v-list-tile-content>
|
</v-list-tile-content>
|
||||||
</template>
|
</v-list-tile>
|
||||||
</template>
|
</template>
|
||||||
</v-autocomplete>
|
</v-autocomplete>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
|
@ -47,7 +50,7 @@
|
||||||
:close-on-content-click="false"
|
:close-on-content-click="false"
|
||||||
:nudge-width="200"
|
:nudge-width="200"
|
||||||
v-model="notificationMenu"
|
v-model="notificationMenu"
|
||||||
v-if="getUser()"
|
v-if="user"
|
||||||
>
|
>
|
||||||
<v-btn icon slot="activator">
|
<v-btn icon slot="activator">
|
||||||
<v-badge left color="red">
|
<v-badge left color="red">
|
||||||
|
@ -70,111 +73,94 @@
|
||||||
</v-list>
|
</v-list>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-btn flat @click="notificationMenu = false">Close</v-btn>
|
<v-btn flat @click="notificationMenu = false"><translate>Close</translate></v-btn>
|
||||||
<v-btn color="primary" flat @click="notificationMenu = false">Save</v-btn>
|
<v-btn color="primary" flat @click="notificationMenu = false"><translate>Save</translate></v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-menu>
|
</v-menu>
|
||||||
<v-btn v-if="!$store.state.user" :to="{ name: 'Login' }">Se connecter</v-btn>
|
<v-btn v-if="!user" :to="{ name: 'Login' }"><translate>Login</translate></v-btn>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import eventFetch from '@/api/eventFetch';
|
import {AUTH_USER_ACTOR, AUTH_USER_ID} from '@/constants';
|
||||||
|
import {SEARCH} from '@/graphql/search';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'NavBar',
|
name: 'NavBar',
|
||||||
props: {
|
props: {
|
||||||
toggleDrawer: {
|
toggleDrawer: {
|
||||||
type: Function,
|
type: Function,
|
||||||
required: true,
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
notificationMenu: false,
|
||||||
|
notifications: [
|
||||||
|
{ header: 'Coucou' },
|
||||||
|
{ title: "T'as une notification", subtitle: 'Et elle est cool' },
|
||||||
|
],
|
||||||
|
model: null,
|
||||||
|
search: [],
|
||||||
|
searchText: null,
|
||||||
|
searchSelect: null,
|
||||||
|
actor: localStorage.getItem(AUTH_USER_ACTOR),
|
||||||
|
user: localStorage.getItem(AUTH_USER_ID),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
apollo: {
|
||||||
|
search: {
|
||||||
|
query: SEARCH,
|
||||||
|
variables() {
|
||||||
|
return {
|
||||||
|
searchText: this.searchText,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
skip() {
|
||||||
|
return !this.searchText;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
},
|
||||||
return {
|
watch: {
|
||||||
notificationMenu: false,
|
model(val) {
|
||||||
notifications: [
|
switch(val.__typename) {
|
||||||
{header: 'Coucou'},
|
case 'Event':
|
||||||
{title: "T'as une notification", subtitle: 'Et elle est cool'},
|
this.$router.push({ name: 'Event', params: { uuid: val.uuid } });
|
||||||
],
|
break;
|
||||||
searchElement: {
|
case 'Actor':
|
||||||
loading: false,
|
this.$router.push({ name: 'Account', params: { name: this.username_with_domain(val) } });
|
||||||
items: [],
|
break;
|
||||||
},
|
|
||||||
search: null,
|
|
||||||
searchSelect: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
search (val) {
|
|
||||||
val && this.querySelections(val)
|
|
||||||
},
|
|
||||||
searchSelect(val) {
|
|
||||||
console.log('searchSelect', val);
|
|
||||||
if (val.type === 'Event') {
|
|
||||||
this.$router.push({name: 'Event', params: { uuid: val.uuid }});
|
|
||||||
} else if (val.type === 'Locality') {
|
|
||||||
this.$router.push({name: 'EventList', params: {location: val.geohash}});
|
|
||||||
} else {
|
|
||||||
this.$router.push({name: 'Account', params: { name : this.username_with_domain(val) }});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
},
|
||||||
displayed_name: function() {
|
computed: {
|
||||||
console.log('displayed name', this.$store.state.actor);
|
items() {
|
||||||
if (this.$store.state.actor) {
|
return this.search.map(searchEntry => {
|
||||||
return this.$store.state.actor.display_name === null ? this.$store.state.actor.username : this.$store.state.actor.display_name;
|
switch (searchEntry.__typename) {
|
||||||
|
case 'Actor':
|
||||||
|
searchEntry.label = searchEntry.preferredUsername;
|
||||||
|
break;
|
||||||
|
case 'Event':
|
||||||
|
searchEntry.label = searchEntry.title;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
return searchEntry;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
displayed_name() {
|
||||||
username_with_domain(actor) {
|
console.log('displayed name', this.actor);
|
||||||
if (actor.type !== 'Event') {
|
if (this.actor) {
|
||||||
return actor.username + (actor.domain === null ? '' : `@${actor.domain}`)
|
return this.actor.display_name === null ? this.actor.username : this.actor.display_name;
|
||||||
}
|
|
||||||
return actor.title;
|
|
||||||
},
|
|
||||||
getUser() {
|
|
||||||
return this.$store.state.user === undefined ? false : this.$store.state.user;
|
|
||||||
},
|
|
||||||
querySelections(searchTerm) {
|
|
||||||
this.searchElement.loading = true;
|
|
||||||
eventFetch(`/search/${searchTerm}`, this.$store)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then((results) => {
|
|
||||||
console.log('results');
|
|
||||||
console.log(results);
|
|
||||||
const accountResults = results.data.actors.map((result) => {
|
|
||||||
if (result.domain) {
|
|
||||||
result.displayedText = `${result.username}@${result.domain}`;
|
|
||||||
} else {
|
|
||||||
result.displayedText = result.username;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
|
|
||||||
const eventsResults = results.data.events.map((result) => {
|
|
||||||
result.displayedText = result.title;
|
|
||||||
return result;
|
|
||||||
});
|
|
||||||
// const cities = new Set();
|
|
||||||
// const placeResults = results.places.map((result) => {
|
|
||||||
// result.displayedText = result.addressLocality;
|
|
||||||
// return result;
|
|
||||||
// }).filter((result) => {
|
|
||||||
// if (cities.has(result.addressLocality)) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// cities.add(result.addressLocality);
|
|
||||||
// return true;
|
|
||||||
// });
|
|
||||||
this.searchElement.items = accountResults.concat(eventsResults);
|
|
||||||
this.searchElement.loading = false;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
methods: {
|
||||||
|
username_with_domain(actor) {
|
||||||
|
return actor.preferredUsername + (actor.domain === undefined ? '' : `@${actor.domain}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
3
js/src/constants.js
Normal file
3
js/src/constants.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export const AUTH_TOKEN = 'auth-token';
|
||||||
|
export const AUTH_USER_ID = 'auth-user-id';
|
||||||
|
export const AUTH_USER_ACTOR = 'auth-user-actor';
|
39
js/src/graphql/actor.js
Normal file
39
js/src/graphql/actor.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
|
export const FETCH_ACTOR = gql`
|
||||||
|
query($name:String!) {
|
||||||
|
actor(preferredUsername: $name) {
|
||||||
|
url,
|
||||||
|
outboxUrl,
|
||||||
|
inboxUrl,
|
||||||
|
followingUrl,
|
||||||
|
followersUrl,
|
||||||
|
sharedInboxUrl,
|
||||||
|
name,
|
||||||
|
domain,
|
||||||
|
summary,
|
||||||
|
preferredUsername,
|
||||||
|
suspended,
|
||||||
|
avatarUrl,
|
||||||
|
bannerUrl,
|
||||||
|
organizedEvents {
|
||||||
|
uuid,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
organizer_actor {
|
||||||
|
avatarUrl,
|
||||||
|
preferred_username,
|
||||||
|
name,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const LOGGED_ACTOR = gql`
|
||||||
|
query {
|
||||||
|
loggedActor {
|
||||||
|
avatarUrl,
|
||||||
|
preferredUsername,
|
||||||
|
}
|
||||||
|
}`;
|
16
js/src/graphql/auth.js
Normal file
16
js/src/graphql/auth.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
|
export const LOGIN = gql`
|
||||||
|
mutation Login($email: String!, $password: String!) {
|
||||||
|
login(email: $email, password: $password) {
|
||||||
|
token,
|
||||||
|
user {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
actor {
|
||||||
|
avatarUrl,
|
||||||
|
preferredUsername,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`;
|
29
js/src/graphql/category.js
Normal file
29
js/src/graphql/category.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
|
export const FETCH_CATEGORIES = gql`
|
||||||
|
query {
|
||||||
|
categories {
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
picture {
|
||||||
|
url,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const CREATE_CATEGORY = gql`
|
||||||
|
mutation createCategory($title: String!, $description: String!, $picture: Upload!) {
|
||||||
|
createCategory(title: $title, description: $description, picture: $picture) {
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
picture {
|
||||||
|
url,
|
||||||
|
url_thumbnail
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
`;
|
109
js/src/graphql/event.js
Normal file
109
js/src/graphql/event.js
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
|
export const FETCH_EVENT = gql`
|
||||||
|
query($uuid:UUID!) {
|
||||||
|
event(uuid: $uuid) {
|
||||||
|
uuid,
|
||||||
|
url,
|
||||||
|
local,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
begins_on,
|
||||||
|
ends_on,
|
||||||
|
state,
|
||||||
|
status,
|
||||||
|
public,
|
||||||
|
thumbnail,
|
||||||
|
large_image,
|
||||||
|
publish_at,
|
||||||
|
# address_type,
|
||||||
|
online_address,
|
||||||
|
phone,
|
||||||
|
organizerActor {
|
||||||
|
avatarUrl,
|
||||||
|
preferredUsername,
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
attributedTo {
|
||||||
|
avatarUrl,
|
||||||
|
preferredUsername,
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
participants {
|
||||||
|
actor {
|
||||||
|
avatarUrl,
|
||||||
|
preferredUsername,
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
role,
|
||||||
|
},
|
||||||
|
category {
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const FETCH_EVENTS = gql`
|
||||||
|
query {
|
||||||
|
events {
|
||||||
|
uuid,
|
||||||
|
url,
|
||||||
|
local,
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
begins_on,
|
||||||
|
ends_on,
|
||||||
|
state,
|
||||||
|
status,
|
||||||
|
public,
|
||||||
|
thumbnail,
|
||||||
|
large_image,
|
||||||
|
publish_at,
|
||||||
|
# address_type,
|
||||||
|
online_address,
|
||||||
|
phone,
|
||||||
|
organizerActor {
|
||||||
|
avatarUrl,
|
||||||
|
preferredUsername,
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
attributedTo {
|
||||||
|
avatarUrl,
|
||||||
|
preferredUsername,
|
||||||
|
name,
|
||||||
|
},
|
||||||
|
category {
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const CREATE_EVENT = gql`
|
||||||
|
mutation CreateEvent(
|
||||||
|
$title: String!,
|
||||||
|
$description: String!,
|
||||||
|
$organizerActorId: Int!,
|
||||||
|
$categoryId: Int!,
|
||||||
|
$beginsOn: DateTime!,
|
||||||
|
$addressType: AddressType!,
|
||||||
|
) {
|
||||||
|
createEvent(title: $title, description: $description, beginsOn: $beginsOn, organizerActorId: $organizerActorId, categoryId: $categoryId, addressType: $addressType) {
|
||||||
|
uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const EDIT_EVENT = gql`
|
||||||
|
mutation EditEvent(
|
||||||
|
$title: String!,
|
||||||
|
$description: String!,
|
||||||
|
$organizerActorId: Int!,
|
||||||
|
$categoryId: Int!,
|
||||||
|
) {
|
||||||
|
EditEvent(title: $title, description: $description, organizerActorId: $organizerActorId, categoryId: $categoryId) {
|
||||||
|
uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
1
js/src/graphql/fragmentTypes.json
Normal file
1
js/src/graphql/fragmentTypes.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"__schema":{"types":[{"possibleTypes":[{"name":"Event"},{"name":"Actor"}],"name":"SearchResult","kind":"UNION"}]}}
|
17
js/src/graphql/search.js
Normal file
17
js/src/graphql/search.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
|
export const SEARCH = gql`
|
||||||
|
query SearchEvents($searchText: String!) {
|
||||||
|
search(search: $searchText) {
|
||||||
|
...on Event {
|
||||||
|
title,
|
||||||
|
uuid,
|
||||||
|
__typename
|
||||||
|
},
|
||||||
|
...on Actor {
|
||||||
|
preferredUsername,
|
||||||
|
__typename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
10
js/src/graphql/upload.js
Normal file
10
js/src/graphql/upload.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
|
export const UPLOAD_PICTURE = gql`
|
||||||
|
mutation {
|
||||||
|
uploadPicture(file: "file") {
|
||||||
|
url,
|
||||||
|
url_thumbnail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
28
js/src/graphql/user.js
Normal file
28
js/src/graphql/user.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
|
export const CREATE_USER = gql`
|
||||||
|
mutation CreateUser($email: String!, $username: String!, $password: String!) {
|
||||||
|
createUser(email: $email, username: $username, password: $password) {
|
||||||
|
preferredUsername,
|
||||||
|
user {
|
||||||
|
email,
|
||||||
|
confirmationSentAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const VALIDATE_USER = gql`
|
||||||
|
mutation ValidateUser($token: String!) {
|
||||||
|
validateUser(token: $token) {
|
||||||
|
token,
|
||||||
|
user {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
actor {
|
||||||
|
avatarUrl,
|
||||||
|
preferredUsername,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
|
@ -1,15 +0,0 @@
|
||||||
export default {
|
|
||||||
home: {
|
|
||||||
welcome: 'Welcome on Mobilizon, {username}',
|
|
||||||
welcome_off: 'Welcome on Mobilizon',
|
|
||||||
events: 'Events',
|
|
||||||
groups: 'Groups',
|
|
||||||
login: 'Login',
|
|
||||||
register: 'Register',
|
|
||||||
},
|
|
||||||
event: {
|
|
||||||
list: {
|
|
||||||
title: "Your event list",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,20 +0,0 @@
|
||||||
export default {
|
|
||||||
home: {
|
|
||||||
welcome: 'Bienvenue sur Mobilizon, {username}!',
|
|
||||||
welcome_off: 'Bienvenue sur Mobilizon',
|
|
||||||
events: 'Événements',
|
|
||||||
groups: 'Groupes',
|
|
||||||
login: 'Se connecter',
|
|
||||||
register: "S'inscrire",
|
|
||||||
},
|
|
||||||
event: {
|
|
||||||
list: {
|
|
||||||
title: "Votre liste d'événements",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
session: {
|
|
||||||
error: {
|
|
||||||
bad_login: 'Erreur lors de la connexion : Votre nom d\'utilisateur ou votre mot de passe est incorrect',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,6 +0,0 @@
|
||||||
import en from './en';
|
|
||||||
import fr from './fr';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
en, fr,
|
|
||||||
};
|
|
30
js/src/i18n/locale/en_US/LC_MESSAGES/app.po
Normal file
30
js/src/i18n/locale/en_US/LC_MESSAGES/app.po
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# English translations for mobilizon package.
|
||||||
|
# Copyright (C) 2018 THE mobilizon'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the mobilizon package.
|
||||||
|
# Automatically generated, 2018.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: mobilizon 0.1.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2018-10-24 16:25+0200\n"
|
||||||
|
"PO-Revision-Date: 2018-10-24 16:25+0200\n"
|
||||||
|
"Last-Translator: Automatically generated\n"
|
||||||
|
"Language-Team: none\n"
|
||||||
|
"Language: en_US\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#: src/components/Account/Register.vue:70
|
||||||
|
msgid "A validation email was sent to %{email}"
|
||||||
|
msgstr "A validation email was sent to %{email}"
|
||||||
|
|
||||||
|
#: src/components/Account/Register.vue:71
|
||||||
|
msgid "Before you can login, you need to click on the link inside it to validate your account"
|
||||||
|
msgstr "Before you can login, you need to click on the link inside it to validate your account"
|
||||||
|
|
||||||
|
#: src/components/Home.vue:14
|
||||||
|
msgid "Register"
|
||||||
|
msgstr "Register"
|
30
js/src/i18n/locale/fr_FR/LC_MESSAGES/app.po
Normal file
30
js/src/i18n/locale/fr_FR/LC_MESSAGES/app.po
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# French translations for mobilizon package.
|
||||||
|
# Copyright (C) 2018 THE mobilizon'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the mobilizon package.
|
||||||
|
# Automatically generated, 2018.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: mobilizon 0.1.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2018-10-24 16:25+0200\n"
|
||||||
|
"PO-Revision-Date: 2018-10-24 16:25+0200\n"
|
||||||
|
"Last-Translator: Automatically generated\n"
|
||||||
|
"Language-Team: none\n"
|
||||||
|
"Language: fr_FR\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: src/components/Account/Register.vue:70
|
||||||
|
msgid "A validation email was sent to %{email}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/Account/Register.vue:71
|
||||||
|
msgid "Before you can login, you need to click on the link inside it to validate your account"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/Home.vue:14
|
||||||
|
msgid "Register"
|
||||||
|
msgstr "S'inscrire"
|
30
js/src/i18n/locale/fr_FR/LC_MESSAGES/app.po~
Normal file
30
js/src/i18n/locale/fr_FR/LC_MESSAGES/app.po~
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# French translations for mobilizon package.
|
||||||
|
# Copyright (C) 2018 THE mobilizon'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the mobilizon package.
|
||||||
|
# Automatically generated, 2018.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: mobilizon 0.1.0\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2018-10-24 16:25+0200\n"
|
||||||
|
"PO-Revision-Date: 2018-10-24 16:25+0200\n"
|
||||||
|
"Last-Translator: Automatically generated\n"
|
||||||
|
"Language-Team: none\n"
|
||||||
|
"Language: fr\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: src/components/Account/Register.vue:70
|
||||||
|
msgid "A validation email was sent to %{email}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/Account/Register.vue:71
|
||||||
|
msgid "Before you can login, you need to click on the link inside it to validate your account"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: src/components/Home.vue:14
|
||||||
|
msgid "Register"
|
||||||
|
msgstr "S'inscrire"
|
1
js/src/i18n/translations.json
Normal file
1
js/src/i18n/translations.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"en_US":{"A validation email was sent to %{email}":"A validation email was sent to %{email}","Before you can login, you need to click on the link inside it to validate your account":"Before you can login, you need to click on the link inside it to validate your account","Register":"Register"},"fr_FR":{"Register":"S'inscrire"}}
|
|
@ -5,60 +5,37 @@ import Vue from 'vue';
|
||||||
import VueMarkdown from 'vue-markdown';
|
import VueMarkdown from 'vue-markdown';
|
||||||
import Vuetify from 'vuetify';
|
import Vuetify from 'vuetify';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import VuexI18n from 'vuex-i18n';
|
import GetTextPlugin from 'vue-gettext';
|
||||||
import 'material-design-icons/iconfont/material-icons.css';
|
import 'material-design-icons/iconfont/material-icons.css';
|
||||||
import 'vuetify/dist/vuetify.min.css';
|
import 'vuetify/dist/vuetify.min.css';
|
||||||
import App from './App.vue';
|
import App from '@/App.vue';
|
||||||
import router from './router';
|
import router from '@/router';
|
||||||
import store from './store';
|
// import store from './store';
|
||||||
import translations from './i18n';
|
import translations from '@/i18n/translations.json';
|
||||||
import auth from './auth';
|
import { createProvider } from './vue-apollo';
|
||||||
|
|
||||||
Vue.config.productionTip = false;
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
Vue.use(VueMarkdown);
|
Vue.use(VueMarkdown);
|
||||||
Vue.use(Vuetify);
|
Vue.use(Vuetify);
|
||||||
let language = window.navigator.userLanguage || window.navigator.language;
|
const language = window.navigator.userLanguage || window.navigator.language;
|
||||||
moment.locale(language);
|
moment.locale(language);
|
||||||
|
|
||||||
Vue.filter('formatDate', value => (value ? moment(String(value)).format('LLLL') : null));
|
Vue.filter('formatDate', value => (value ? moment(String(value)).format('LLLL') : null));
|
||||||
Vue.filter('formatDay', value => (value ? moment(String(value)).format('LL') : null));
|
Vue.filter('formatDay', value => (value ? moment(String(value)).format('LL') : null));
|
||||||
|
|
||||||
if (!(language in translations)) {
|
Vue.use(GetTextPlugin, {
|
||||||
[language] = language.split('-', 1);
|
translations,
|
||||||
}
|
defaultLanguage: 'en_US',
|
||||||
|
|
||||||
Vue.use(VuexI18n.plugin, store);
|
|
||||||
|
|
||||||
Object.entries(translations).forEach((key) => {
|
|
||||||
Vue.i18n.add(key[0], key[1]);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Vue.i18n.set(language);
|
Vue.config.language = language.replace('-', '_');
|
||||||
Vue.i18n.fallback('en');
|
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
|
||||||
if (to.matched.some(record => record.meta.requiredAuth) && !store.state.user) {
|
|
||||||
next({
|
|
||||||
name: 'Login',
|
|
||||||
query: { redirect: to.fullPath },
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
auth.getUser(store, () => {}, (error) => {
|
|
||||||
console.warn(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('store', store);
|
|
||||||
|
|
||||||
/* eslint-disable no-new */
|
/* eslint-disable no-new */
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
router,
|
router,
|
||||||
store,
|
|
||||||
template: '<App/>',
|
template: '<App/>',
|
||||||
|
apolloProvider: createProvider(),
|
||||||
components: { App },
|
components: { App },
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
import Vue from 'vue';
|
|
||||||
import Vuex from 'vuex';
|
|
||||||
import { LOGIN_USER, LOGOUT_USER, LOAD_USER, CHANGE_ACTOR } from './mutation-types';
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
isLogged: !!localStorage.getItem('token'),
|
|
||||||
user: false,
|
|
||||||
actor: false,
|
|
||||||
defaultActor: localStorage.getItem('defaultActor') || null,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* eslint-disable */
|
|
||||||
const mutations = {
|
|
||||||
[LOGIN_USER](state, user) {
|
|
||||||
state.isLogged = true;
|
|
||||||
state.user = user;
|
|
||||||
},
|
|
||||||
|
|
||||||
[LOAD_USER](state, user) {
|
|
||||||
state.user = user;
|
|
||||||
},
|
|
||||||
|
|
||||||
[LOGOUT_USER](state) {
|
|
||||||
state.isLogged = false;
|
|
||||||
state.user = null;
|
|
||||||
},
|
|
||||||
|
|
||||||
[CHANGE_ACTOR](state, actor) {
|
|
||||||
state.actor = actor;
|
|
||||||
state.defaultActor = actor.username;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
/* eslint-enable */
|
|
||||||
|
|
||||||
Vue.use(Vuex);
|
|
||||||
const store = new Vuex.Store({ state, mutations });
|
|
||||||
|
|
||||||
store.subscribe((mutation, localState) => {
|
|
||||||
if (mutation === CHANGE_ACTOR) {
|
|
||||||
localStorage.setItem('defaultActor', localState.actor.username);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default store;
|
|
|
@ -1,4 +0,0 @@
|
||||||
export const LOGIN_USER = 'LOGIN_USER';
|
|
||||||
export const LOAD_USER = 'LOAD_USER';
|
|
||||||
export const LOGOUT_USER = 'LOGOUT_USER';
|
|
||||||
export const CHANGE_ACTOR = 'CHANGE_ACTOR';
|
|
135
js/src/vue-apollo.js
Normal file
135
js/src/vue-apollo.js
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
import Vue from 'vue';
|
||||||
|
import VueApollo from 'vue-apollo';
|
||||||
|
import { ApolloLink } from 'apollo-link';
|
||||||
|
import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory';
|
||||||
|
import { createLink } from 'apollo-absinthe-upload-link';
|
||||||
|
import { createApolloClient, restartWebsockets } from 'vue-cli-plugin-apollo/graphql-client';
|
||||||
|
import { AUTH_TOKEN } from './constants';
|
||||||
|
|
||||||
|
// Install the vue plugin
|
||||||
|
Vue.use(VueApollo);
|
||||||
|
|
||||||
|
// Http endpoint
|
||||||
|
const httpEndpoint = process.env.VUE_APP_GRAPHQL_HTTP || 'http://localhost:4000/api';
|
||||||
|
|
||||||
|
const fragmentMatcher = new IntrospectionFragmentMatcher({
|
||||||
|
introspectionQueryResultData: {
|
||||||
|
__schema: {
|
||||||
|
types: [
|
||||||
|
{
|
||||||
|
kind: 'UNION',
|
||||||
|
name: 'SearchResult',
|
||||||
|
possibleTypes: [
|
||||||
|
{ name: 'Event' },
|
||||||
|
{ name: 'Actor' },
|
||||||
|
],
|
||||||
|
}, // this is an example, put your INTERFACE and UNION kinds here!
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const cache = new InMemoryCache({ fragmentMatcher });
|
||||||
|
|
||||||
|
|
||||||
|
const authMiddleware = new ApolloLink((operation, forward) => {
|
||||||
|
// add the authorization to the headers
|
||||||
|
const token = localStorage.getItem(AUTH_TOKEN);
|
||||||
|
operation.setContext({
|
||||||
|
headers: {
|
||||||
|
authorization: token ? `Bearer ${token}` : null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return forward(operation);
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploadLink = createLink({
|
||||||
|
uri: httpEndpoint,
|
||||||
|
});
|
||||||
|
|
||||||
|
// const link = ApolloLink.from([
|
||||||
|
// uploadLink,
|
||||||
|
// authMiddleware,
|
||||||
|
// HttpLink,
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
const link = authMiddleware.concat(uploadLink);
|
||||||
|
|
||||||
|
// Config
|
||||||
|
const defaultOptions = {
|
||||||
|
// You can use `https` for secure connection (recommended in production)
|
||||||
|
httpEndpoint,
|
||||||
|
// You can use `wss` for secure connection (recommended in production)
|
||||||
|
// Use `null` to disable subscriptions
|
||||||
|
// wsEndpoint: process.env.VUE_APP_GRAPHQL_WS || 'ws://localhost:4000/graphql',
|
||||||
|
// LocalStorage token
|
||||||
|
tokenName: AUTH_TOKEN,
|
||||||
|
// Enable Automatic Query persisting with Apollo Engine
|
||||||
|
persisting: false,
|
||||||
|
// Use websockets for everything (no HTTP)
|
||||||
|
// You need to pass a `wsEndpoint` for this to work
|
||||||
|
websocketsOnly: false,
|
||||||
|
// Is being rendered on the server?
|
||||||
|
ssr: false,
|
||||||
|
cache,
|
||||||
|
link,
|
||||||
|
defaultHttpLink: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Call this in the Vue app file
|
||||||
|
export function createProvider(options = {}) {
|
||||||
|
// Create apollo client
|
||||||
|
const { apolloClient, wsClient } = createApolloClient({
|
||||||
|
...defaultOptions,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
apolloClient.wsClient = wsClient;
|
||||||
|
|
||||||
|
// Create vue apollo provider
|
||||||
|
const apolloProvider = new VueApollo({
|
||||||
|
defaultClient: apolloClient,
|
||||||
|
link,
|
||||||
|
cache,
|
||||||
|
connectToDevTools: true,
|
||||||
|
defaultOptions: {
|
||||||
|
$query: {
|
||||||
|
// fetchPolicy: 'cache-and-network',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errorHandler(error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('%cError', 'background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;', error.message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return apolloProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manually call this when user log in
|
||||||
|
export async function onLogin(apolloClient, token) {
|
||||||
|
if (typeof localStorage !== 'undefined' && token) {
|
||||||
|
localStorage.setItem(AUTH_TOKEN, token);
|
||||||
|
}
|
||||||
|
if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient);
|
||||||
|
try {
|
||||||
|
await apolloClient.resetStore();
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('%cError on cache reset (login)', 'color: orange;', e.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manually call this when user log out
|
||||||
|
export async function onLogout(apolloClient) {
|
||||||
|
if (typeof localStorage !== 'undefined') {
|
||||||
|
localStorage.removeItem(AUTH_TOKEN);
|
||||||
|
}
|
||||||
|
if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient);
|
||||||
|
try {
|
||||||
|
await apolloClient.resetStore();
|
||||||
|
} catch (e) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('%cError on cache reset (logout)', 'color: orange;', e.message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
env: {
|
env: {
|
||||||
mocha: true
|
mocha: true,
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'import/no-extraneous-dependencies': 'off'
|
'import/no-extraneous-dependencies': 'off',
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
|
@ -14,7 +14,7 @@ defmodule Mix.Tasks.CreateBot do
|
||||||
def run([email, name, summary, type, url]) do
|
def run([email, name, summary, type, url]) do
|
||||||
Mix.Task.run("app.start")
|
Mix.Task.run("app.start")
|
||||||
|
|
||||||
with {:ok, %User{} = user} <- Actors.find_by_email(email),
|
with {:ok, %User{} = user} <- Actors.get_user_by_email(email, true),
|
||||||
actor <- Actors.register_bot_account(%{name: name, summary: summary}),
|
actor <- Actors.register_bot_account(%{name: name, summary: summary}),
|
||||||
{:ok, %Bot{} = bot} <-
|
{:ok, %Bot{} = bot} <-
|
||||||
Actors.create_bot(%{
|
Actors.create_bot(%{
|
||||||
|
|
|
@ -11,6 +11,14 @@ defmodule Mobilizon.Actors do
|
||||||
|
|
||||||
alias Mobilizon.Service.ActivityPub
|
alias Mobilizon.Service.ActivityPub
|
||||||
|
|
||||||
|
def data() do
|
||||||
|
Dataloader.Ecto.new(Repo, query: &query/2)
|
||||||
|
end
|
||||||
|
|
||||||
|
def query(queryable, _params) do
|
||||||
|
queryable
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns the list of actors.
|
Returns the list of actors.
|
||||||
|
|
||||||
|
@ -42,6 +50,28 @@ defmodule Mobilizon.Actors do
|
||||||
Repo.get!(Actor, id)
|
Repo.get!(Actor, id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the associated actor for an user, either the default set one or the first found
|
||||||
|
"""
|
||||||
|
@spec get_actor_for_user(%Mobilizon.Actors.User{}) :: %Mobilizon.Actors.Actor{}
|
||||||
|
def get_actor_for_user(%Mobilizon.Actors.User{} = user) do
|
||||||
|
case user.default_actor_id do
|
||||||
|
nil -> get_first_actor_for_user(user)
|
||||||
|
actor_id -> get_actor!(actor_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the first actor found for an user
|
||||||
|
|
||||||
|
Useful when the user has not defined default actor
|
||||||
|
|
||||||
|
Raises `Ecto.NoResultsError` if no Actor is found for this ID
|
||||||
|
"""
|
||||||
|
defp get_first_actor_for_user(%Mobilizon.Actors.User{id: id} = _user) do
|
||||||
|
Repo.one!(from(a in Actor, where: a.user_id == ^id))
|
||||||
|
end
|
||||||
|
|
||||||
def get_actor_with_everything!(id) do
|
def get_actor_with_everything!(id) do
|
||||||
actor = Repo.get!(Actor, id)
|
actor = Repo.get!(Actor, id)
|
||||||
Repo.preload(actor, :organized_events)
|
Repo.preload(actor, :organized_events)
|
||||||
|
@ -162,10 +192,13 @@ defmodule Mobilizon.Actors do
|
||||||
Repo.all(User)
|
Repo.all(User)
|
||||||
end
|
end
|
||||||
|
|
||||||
def list_users_with_actors do
|
@doc """
|
||||||
users = Repo.all(User)
|
List users with their associated actors. No reason for that, so removed
|
||||||
Repo.preload(users, :actors)
|
"""
|
||||||
end
|
# def list_users_with_actors do
|
||||||
|
# users = Repo.all(User)
|
||||||
|
# Repo.preload(users, :actors)
|
||||||
|
# end
|
||||||
|
|
||||||
defp blank?(""), do: nil
|
defp blank?(""), do: nil
|
||||||
defp blank?(n), do: n
|
defp blank?(n), do: n
|
||||||
|
@ -226,6 +259,14 @@ defmodule Mobilizon.Actors do
|
||||||
Repo.preload(user, :actors)
|
Repo.preload(user, :actors)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec get_user_with_actor(integer()) :: %User{}
|
||||||
|
def get_user_with_actor(id) do
|
||||||
|
case Repo.get(User, id) do
|
||||||
|
nil -> {:error, "User with ID #{id} not found"}
|
||||||
|
user -> {:ok, Repo.preload(user, :actors)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def get_actor_by_url(url) do
|
def get_actor_by_url(url) do
|
||||||
Repo.get_by(Actor, url: url)
|
Repo.get_by(Actor, url: url)
|
||||||
end
|
end
|
||||||
|
@ -297,10 +338,17 @@ defmodule Mobilizon.Actors do
|
||||||
@doc """
|
@doc """
|
||||||
Find actors by their name or displayed name
|
Find actors by their name or displayed name
|
||||||
"""
|
"""
|
||||||
def find_actors_by_username_or_name(username) do
|
def find_actors_by_username_or_name(username, page \\ 1, limit \\ 10)
|
||||||
|
def find_actors_by_username_or_name("", page, limit), do: []
|
||||||
|
|
||||||
|
def find_actors_by_username_or_name(username, page, limit) do
|
||||||
|
start = (page - 1) * limit
|
||||||
|
|
||||||
Repo.all(
|
Repo.all(
|
||||||
from(
|
from(
|
||||||
a in Actor,
|
a in Actor,
|
||||||
|
limit: ^limit,
|
||||||
|
offset: ^start,
|
||||||
where:
|
where:
|
||||||
ilike(a.preferred_username, ^like_sanitize(username)) or
|
ilike(a.preferred_username, ^like_sanitize(username)) or
|
||||||
ilike(a.name, ^like_sanitize(username))
|
ilike(a.name, ^like_sanitize(username))
|
||||||
|
@ -340,19 +388,6 @@ defmodule Mobilizon.Actors do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
|
||||||
Get an user by email
|
|
||||||
"""
|
|
||||||
def find_by_email(email) do
|
|
||||||
case Repo.preload(Repo.get_by(User, email: email), :actors) do
|
|
||||||
nil ->
|
|
||||||
{:error, nil}
|
|
||||||
|
|
||||||
user ->
|
|
||||||
{:ok, user}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Authenticate user
|
Authenticate user
|
||||||
"""
|
"""
|
||||||
|
@ -390,31 +425,37 @@ defmodule Mobilizon.Actors do
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
actor =
|
with actor_changeset <-
|
||||||
Mobilizon.Actors.Actor.registration_changeset(%Mobilizon.Actors.Actor{}, %{
|
Mobilizon.Actors.Actor.registration_changeset(%Mobilizon.Actors.Actor{}, %{
|
||||||
preferred_username: username,
|
preferred_username: username,
|
||||||
domain: nil,
|
domain: nil,
|
||||||
keys: pem,
|
keys: pem,
|
||||||
avatar_url: avatar
|
avatar_url: avatar
|
||||||
})
|
}),
|
||||||
|
{:ok, %Mobilizon.Actors.Actor{id: id} = actor} <- Mobilizon.Repo.insert(actor_changeset),
|
||||||
user =
|
user_changeset <-
|
||||||
Mobilizon.Actors.User.registration_changeset(%Mobilizon.Actors.User{}, %{
|
Mobilizon.Actors.User.registration_changeset(%Mobilizon.Actors.User{}, %{
|
||||||
email: email,
|
email: email,
|
||||||
password: password
|
password: password,
|
||||||
})
|
default_actor_id: id
|
||||||
|
}),
|
||||||
actor_with_user = Ecto.Changeset.put_assoc(actor, :user, user)
|
{:ok, %Mobilizon.Actors.User{} = user} <- Mobilizon.Repo.insert(user_changeset) do
|
||||||
|
{:ok, Map.put(actor, :user, user)}
|
||||||
try do
|
else
|
||||||
Mobilizon.Repo.insert!(actor_with_user)
|
{:error, %Ecto.Changeset{} = changeset} ->
|
||||||
find_by_email(email)
|
handle_actor_user_changeset(changeset)
|
||||||
rescue
|
|
||||||
e in Ecto.InvalidChangesetError ->
|
|
||||||
{:error, e.changeset}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp handle_actor_user_changeset(changeset) do
|
||||||
|
changeset =
|
||||||
|
Ecto.Changeset.traverse_errors(changeset, fn
|
||||||
|
{msg, opts} -> msg
|
||||||
|
msg -> msg
|
||||||
|
end)
|
||||||
|
{:error, hd(Map.get(changeset, :email))}
|
||||||
|
end
|
||||||
|
|
||||||
def register_bot_account(%{name: name, summary: summary}) do
|
def register_bot_account(%{name: name, summary: summary}) do
|
||||||
key = :public_key.generate_key({:rsa, 2048, 65_537})
|
key = :public_key.generate_key({:rsa, 2048, 65_537})
|
||||||
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
|
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
|
||||||
|
@ -466,9 +507,16 @@ defmodule Mobilizon.Actors do
|
||||||
iex> get_user_by_email(user, wrong_email)
|
iex> get_user_by_email(user, wrong_email)
|
||||||
{:error, nil}
|
{:error, nil}
|
||||||
"""
|
"""
|
||||||
def get_user_by_email(email) do
|
def get_user_by_email(email, activated \\ nil) do
|
||||||
case Repo.get_by(User, email: email) do
|
query =
|
||||||
nil -> {:error, nil}
|
case activated do
|
||||||
|
nil -> from(u in User, where: u.email == ^email)
|
||||||
|
true -> from(u in User, where: u.email == ^email and not is_nil(u.confirmed_at))
|
||||||
|
false -> from(u in User, where: u.email == ^email and is_nil(u.confirmed_at))
|
||||||
|
end
|
||||||
|
|
||||||
|
case Repo.one(query) do
|
||||||
|
nil -> {:error, :user_not_found}
|
||||||
user -> {:ok, user}
|
user -> {:ok, user}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,7 @@ defmodule Mobilizon.Actors.Service.Activation do
|
||||||
|
|
||||||
alias Mobilizon.{Mailer, Repo, Actors.User, Actors}
|
alias Mobilizon.{Mailer, Repo, Actors.User, Actors}
|
||||||
alias Mobilizon.Email.User, as: UserEmail
|
alias Mobilizon.Email.User, as: UserEmail
|
||||||
|
alias Mobilizon.Actors.Service.Tools
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
@ -15,7 +16,8 @@ defmodule Mobilizon.Actors.Service.Activation do
|
||||||
"confirmation_sent_at" => nil,
|
"confirmation_sent_at" => nil,
|
||||||
"confirmation_token" => nil
|
"confirmation_token" => nil
|
||||||
}) do
|
}) do
|
||||||
{:ok, Repo.preload(user, :actors)}
|
Logger.info("User #{user.email} has been confirmed")
|
||||||
|
{:ok, user}
|
||||||
else
|
else
|
||||||
_err ->
|
_err ->
|
||||||
{:error, "Invalid token"}
|
{:error, "Invalid token"}
|
||||||
|
@ -23,8 +25,12 @@ defmodule Mobilizon.Actors.Service.Activation do
|
||||||
end
|
end
|
||||||
|
|
||||||
def resend_confirmation_email(%User{} = user, locale \\ "en") do
|
def resend_confirmation_email(%User{} = user, locale \\ "en") do
|
||||||
{:ok, user} = Actors.update_user(user, %{"confirmation_sent_at" => DateTime.utc_now()})
|
with :ok <- Tools.we_can_send_email(user, :confirmation_sent_at),
|
||||||
send_confirmation_email(user, locale)
|
{:ok, user} <- Actors.update_user(user, %{"confirmation_sent_at" => DateTime.utc_now()}) do
|
||||||
|
send_confirmation_email(user, locale)
|
||||||
|
Logger.info("Sent confirmation email again to #{user.email}")
|
||||||
|
{:ok, user.email}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_confirmation_email(%User{} = user, locale \\ "en") do
|
def send_confirmation_email(%User{} = user, locale \\ "en") do
|
||||||
|
|
|
@ -5,6 +5,7 @@ defmodule Mobilizon.Actors.Service.ResetPassword do
|
||||||
|
|
||||||
alias Mobilizon.{Mailer, Repo, Actors.User}
|
alias Mobilizon.{Mailer, Repo, Actors.User}
|
||||||
alias Mobilizon.Email.User, as: UserEmail
|
alias Mobilizon.Email.User, as: UserEmail
|
||||||
|
alias Mobilizon.Actors.Service.Tools
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Check that the provided token is correct and update provided password
|
Check that the provided token is correct and update provided password
|
||||||
|
@ -20,7 +21,7 @@ defmodule Mobilizon.Actors.Service.ResetPassword do
|
||||||
"reset_password_token" => nil
|
"reset_password_token" => nil
|
||||||
})
|
})
|
||||||
) do
|
) do
|
||||||
{:ok, Repo.preload(user, :actors)}
|
{:ok, user}
|
||||||
else
|
else
|
||||||
err ->
|
err ->
|
||||||
{:error, :invalid_token}
|
{:error, :invalid_token}
|
||||||
|
@ -32,11 +33,11 @@ defmodule Mobilizon.Actors.Service.ResetPassword do
|
||||||
"""
|
"""
|
||||||
@spec send_password_reset_email(User.t(), String.t()) :: tuple
|
@spec send_password_reset_email(User.t(), String.t()) :: tuple
|
||||||
def send_password_reset_email(%User{} = user, locale \\ "en") do
|
def send_password_reset_email(%User{} = user, locale \\ "en") do
|
||||||
with :ok <- we_can_send_email(user),
|
with :ok <- Tools.we_can_send_email(user, :reset_password_sent_at),
|
||||||
{:ok, %User{} = user_updated} <-
|
{:ok, %User{} = user_updated} <-
|
||||||
Repo.update(
|
Repo.update(
|
||||||
User.send_password_reset_changeset(user, %{
|
User.send_password_reset_changeset(user, %{
|
||||||
"reset_password_token" => random_string(30),
|
"reset_password_token" => Tools.random_string(30),
|
||||||
"reset_password_sent_at" => DateTime.utc_now()
|
"reset_password_sent_at" => DateTime.utc_now()
|
||||||
})
|
})
|
||||||
) do
|
) do
|
||||||
|
@ -50,28 +51,4 @@ defmodule Mobilizon.Actors.Service.ResetPassword do
|
||||||
{:error, reason} -> {:error, reason}
|
{:error, reason} -> {:error, reason}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec random_string(integer) :: String.t()
|
|
||||||
defp random_string(length) do
|
|
||||||
length
|
|
||||||
|> :crypto.strong_rand_bytes()
|
|
||||||
|> Base.url_encode64()
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec we_can_send_email(User.t()) :: boolean
|
|
||||||
defp we_can_send_email(%User{} = user) do
|
|
||||||
case user.reset_password_sent_at do
|
|
||||||
nil ->
|
|
||||||
:ok
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
case Timex.before?(Timex.shift(user.reset_password_sent_at, hours: 1), DateTime.utc_now()) do
|
|
||||||
true ->
|
|
||||||
:ok
|
|
||||||
|
|
||||||
false ->
|
|
||||||
{:error, :email_too_soon}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
27
lib/mobilizon/actors/service/tools.ex
Normal file
27
lib/mobilizon/actors/service/tools.ex
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
defmodule Mobilizon.Actors.Service.Tools do
|
||||||
|
alias Mobilizon.Actors.User
|
||||||
|
|
||||||
|
@spec we_can_send_email(User.t()) :: boolean
|
||||||
|
def we_can_send_email(%User{} = user, key \\ :reset_password_sent_at) do
|
||||||
|
case Map.get(user, key) do
|
||||||
|
nil ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
case Timex.before?(Timex.shift(Map.get(user, key), hours: 1), DateTime.utc_now()) do
|
||||||
|
true ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
false ->
|
||||||
|
{:error, :email_too_soon}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec random_string(integer) :: String.t()
|
||||||
|
def random_string(length) do
|
||||||
|
length
|
||||||
|
|> :crypto.strong_rand_bytes()
|
||||||
|
|> Base.url_encode64()
|
||||||
|
end
|
||||||
|
end
|
|
@ -12,6 +12,7 @@ defmodule Mobilizon.Actors.User do
|
||||||
field(:password, :string, virtual: true)
|
field(:password, :string, virtual: true)
|
||||||
field(:role, :integer, default: 0)
|
field(:role, :integer, default: 0)
|
||||||
has_many(:actors, Actor)
|
has_many(:actors, Actor)
|
||||||
|
field(:default_actor_id, :integer)
|
||||||
field(:confirmed_at, :utc_datetime)
|
field(:confirmed_at, :utc_datetime)
|
||||||
field(:confirmation_sent_at, :utc_datetime)
|
field(:confirmation_sent_at, :utc_datetime)
|
||||||
field(:confirmation_token, :string)
|
field(:confirmation_token, :string)
|
||||||
|
@ -27,6 +28,7 @@ defmodule Mobilizon.Actors.User do
|
||||||
|> cast(attrs, [
|
|> cast(attrs, [
|
||||||
:email,
|
:email,
|
||||||
:role,
|
:role,
|
||||||
|
:default_actor_id,
|
||||||
:password_hash,
|
:password_hash,
|
||||||
:confirmed_at,
|
:confirmed_at,
|
||||||
:confirmation_sent_at,
|
:confirmation_sent_at,
|
||||||
|
@ -49,7 +51,8 @@ defmodule Mobilizon.Actors.User do
|
||||||
struct
|
struct
|
||||||
|> changeset(params)
|
|> changeset(params)
|
||||||
|> cast(params, ~w(password)a, [])
|
|> cast(params, ~w(password)a, [])
|
||||||
|> validate_required([:email, :password])
|
|> validate_required([:email, :password, :default_actor_id])
|
||||||
|
|> validate_email()
|
||||||
|> validate_length(
|
|> validate_length(
|
||||||
:password,
|
:password,
|
||||||
min: 6,
|
min: 6,
|
||||||
|
@ -92,6 +95,19 @@ defmodule Mobilizon.Actors.User do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp validate_email(changeset) do
|
||||||
|
case changeset do
|
||||||
|
%Ecto.Changeset{valid?: true, changes: %{email: email}} ->
|
||||||
|
case EmailChecker.valid?(email) do
|
||||||
|
false -> add_error(changeset, :email, "Email doesn't fit required format")
|
||||||
|
_ -> changeset
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
changeset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp random_string(length) do
|
defp random_string(length) do
|
||||||
length
|
length
|
||||||
|> :crypto.strong_rand_bytes()
|
|> :crypto.strong_rand_bytes()
|
||||||
|
|
|
@ -16,7 +16,7 @@ defmodule Mobilizon.Email.User do
|
||||||
base_email()
|
base_email()
|
||||||
|> to(user.email)
|
|> to(user.email)
|
||||||
|> subject(
|
|> subject(
|
||||||
gettext("Peakweaver: Confirmation instructions for %{instance}", instance: instance_url)
|
gettext("Mobilizon: Confirmation instructions for %{instance}", instance: instance_url)
|
||||||
)
|
)
|
||||||
|> put_header("Reply-To", get_config(:reply_to))
|
|> put_header("Reply-To", get_config(:reply_to))
|
||||||
|> assign(:token, user.confirmation_token)
|
|> assign(:token, user.confirmation_token)
|
||||||
|
@ -32,7 +32,7 @@ defmodule Mobilizon.Email.User do
|
||||||
|> to(user.email)
|
|> to(user.email)
|
||||||
|> subject(
|
|> subject(
|
||||||
gettext(
|
gettext(
|
||||||
"Peakweaver: Reset your password on %{instance} instructions",
|
"Mobilizon: Reset your password on %{instance} instructions",
|
||||||
instance: instance_url
|
instance: instance_url
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,10 +5,11 @@ defmodule Mobilizon.Events.Category do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
alias Mobilizon.Events.Category
|
alias Mobilizon.Events.Category
|
||||||
|
use Arc.Ecto.Schema
|
||||||
|
|
||||||
schema "categories" do
|
schema "categories" do
|
||||||
field(:description, :string)
|
field(:description, :string)
|
||||||
field(:picture, :string)
|
field(:picture, MobilizonWeb.Uploaders.Category.Type)
|
||||||
field(:title, :string, null: false)
|
field(:title, :string, null: false)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
|
@ -17,7 +18,8 @@ defmodule Mobilizon.Events.Category do
|
||||||
@doc false
|
@doc false
|
||||||
def changeset(%Category{} = category, attrs) do
|
def changeset(%Category{} = category, attrs) do
|
||||||
category
|
category
|
||||||
|> cast(attrs, [:title, :description, :picture])
|
|> cast(attrs, [:title, :description])
|
||||||
|
|> cast_attachments(attrs, [:picture])
|
||||||
|> validate_required([:title])
|
|> validate_required([:title])
|
||||||
|> unique_constraint(:title)
|
|> unique_constraint(:title)
|
||||||
end
|
end
|
||||||
|
|
|
@ -86,7 +86,6 @@ defmodule Mobilizon.Events.Event do
|
||||||
|> validate_required([
|
|> validate_required([
|
||||||
:title,
|
:title,
|
||||||
:begins_on,
|
:begins_on,
|
||||||
:ends_on,
|
|
||||||
:organizer_actor_id,
|
:organizer_actor_id,
|
||||||
:category_id,
|
:category_id,
|
||||||
:url,
|
:url,
|
||||||
|
|
|
@ -6,23 +6,16 @@ defmodule Mobilizon.Events do
|
||||||
import Ecto.Query, warn: false
|
import Ecto.Query, warn: false
|
||||||
alias Mobilizon.Repo
|
alias Mobilizon.Repo
|
||||||
|
|
||||||
alias Mobilizon.Events.Event
|
alias Mobilizon.Events.{Event, Comment, Participant}
|
||||||
alias Mobilizon.Events.Comment
|
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Addresses.Address
|
alias Mobilizon.Addresses.Address
|
||||||
|
|
||||||
@doc """
|
def data() do
|
||||||
Returns the list of events.
|
Dataloader.Ecto.new(Mobilizon.Repo, query: &query/2)
|
||||||
|
end
|
||||||
|
|
||||||
## Examples
|
def query(queryable, _params) do
|
||||||
|
queryable
|
||||||
iex> list_events()
|
|
||||||
[%Event{}, ...]
|
|
||||||
|
|
||||||
"""
|
|
||||||
def list_events do
|
|
||||||
events = Repo.all(Event)
|
|
||||||
Repo.preload(events, [:organizer_actor])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_events_for_actor(%Actor{id: actor_id} = _actor, page \\ 1, limit \\ 10) do
|
def get_events_for_actor(%Actor{id: actor_id} = _actor, page \\ 1, limit \\ 10) do
|
||||||
|
@ -179,15 +172,47 @@ defmodule Mobilizon.Events do
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the list of events.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> list_events()
|
||||||
|
[%Event{}, ...]
|
||||||
|
|
||||||
|
"""
|
||||||
|
def list_events(page \\ 1, limit \\ 10) do
|
||||||
|
start = (page - 1) * limit
|
||||||
|
|
||||||
|
query =
|
||||||
|
from(e in Event,
|
||||||
|
limit: ^limit,
|
||||||
|
offset: ^start,
|
||||||
|
preload: [:organizer_actor]
|
||||||
|
)
|
||||||
|
|
||||||
|
Repo.all(query)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Find events by name
|
Find events by name
|
||||||
"""
|
"""
|
||||||
def find_events_by_name(name) when name == "", do: []
|
def find_events_by_name(name, page \\ 1, limit \\ 10)
|
||||||
|
def find_events_by_name("", page, limit), do: list_events(page, limit)
|
||||||
|
|
||||||
def find_events_by_name(name) do
|
def find_events_by_name(name, page, limit) do
|
||||||
name = String.trim(name)
|
name = String.trim(name)
|
||||||
events = Repo.all(from(a in Event, where: ilike(a.title, ^like_sanitize(name))))
|
start = (page - 1) * limit
|
||||||
Repo.preload(events, [:organizer_actor])
|
|
||||||
|
query =
|
||||||
|
from(e in Event,
|
||||||
|
limit: ^limit,
|
||||||
|
offset: ^start,
|
||||||
|
where: ilike(e.title, ^like_sanitize(name)),
|
||||||
|
preload: [:organizer_actor]
|
||||||
|
)
|
||||||
|
|
||||||
|
Repo.all(query)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -210,9 +235,16 @@ defmodule Mobilizon.Events do
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def create_event(attrs \\ %{}) do
|
def create_event(attrs \\ %{}) do
|
||||||
case %Event{} |> Event.changeset(attrs) |> Repo.insert() do
|
with {:ok, %Event{} = event} <- %Event{} |> Event.changeset(attrs) |> Repo.insert(),
|
||||||
{:ok, %Event{} = event} -> {:ok, Repo.preload(event, [:organizer_actor])}
|
{:ok, %Participant{} = _participant} <-
|
||||||
err -> err
|
%Participant{}
|
||||||
|
|> Participant.changeset(%{
|
||||||
|
actor_id: attrs.organizer_actor_id,
|
||||||
|
role: 4,
|
||||||
|
event_id: event.id
|
||||||
|
})
|
||||||
|
|> Repo.insert() do
|
||||||
|
{:ok, Repo.preload(event, [:organizer_actor])}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -475,6 +507,27 @@ defmodule Mobilizon.Events do
|
||||||
Repo.all(Participant)
|
Repo.all(Participant)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the list of participants for an event.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> list_participants_for_event(someuuid)
|
||||||
|
[%Participant{}, ...]
|
||||||
|
|
||||||
|
"""
|
||||||
|
def list_participants_for_event(uuid) do
|
||||||
|
Repo.all(
|
||||||
|
from(
|
||||||
|
p in Participant,
|
||||||
|
join: e in Event,
|
||||||
|
on: p.event_id == e.id,
|
||||||
|
where: e.uuid == ^uuid,
|
||||||
|
preload: [:actor]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single participant.
|
Gets a single participant.
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ defmodule MobilizonWeb.AuthPipeline do
|
||||||
module: MobilizonWeb.Guardian,
|
module: MobilizonWeb.Guardian,
|
||||||
error_handler: MobilizonWeb.AuthErrorHandler
|
error_handler: MobilizonWeb.AuthErrorHandler
|
||||||
|
|
||||||
plug(Guardian.Plug.VerifyHeader, claims: %{"typ" => "access"})
|
plug(Guardian.Plug.VerifyHeader, realm: "Bearer")
|
||||||
plug(Guardian.Plug.EnsureAuthenticated)
|
plug(Guardian.Plug.LoadResource, allow_blank: true)
|
||||||
plug(Guardian.Plug.LoadResource, ensure: true)
|
plug(MobilizonWeb.Context)
|
||||||
end
|
end
|
||||||
|
|
20
lib/mobilizon_web/context.ex
Normal file
20
lib/mobilizon_web/context.ex
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
defmodule MobilizonWeb.Context do
|
||||||
|
@behaviour Plug
|
||||||
|
|
||||||
|
import Plug.Conn
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
def init(opts) do
|
||||||
|
opts
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(conn, _) do
|
||||||
|
case Guardian.Plug.current_resource(conn) do
|
||||||
|
nil ->
|
||||||
|
conn
|
||||||
|
|
||||||
|
user ->
|
||||||
|
put_private(conn, :absinthe, %{context: %{current_user: user}})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,79 +0,0 @@
|
||||||
defmodule MobilizonWeb.ActorController do
|
|
||||||
@moduledoc """
|
|
||||||
Controller for Actors
|
|
||||||
"""
|
|
||||||
use MobilizonWeb, :controller
|
|
||||||
|
|
||||||
alias Mobilizon.Actors
|
|
||||||
alias Mobilizon.Actors.{Actor, User}
|
|
||||||
alias Mobilizon.Service.ActivityPub
|
|
||||||
|
|
||||||
action_fallback(MobilizonWeb.FallbackController)
|
|
||||||
|
|
||||||
def index(conn, _params) do
|
|
||||||
actors = Actors.list_actors()
|
|
||||||
render(conn, "index.json", actors: actors)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create(conn, %{"actor" => actor_params}) do
|
|
||||||
with %User{} = user <- Guardian.Plug.current_resource(conn),
|
|
||||||
actor_params <- Map.put(actor_params, "user_id", user.id),
|
|
||||||
actor_params <- Map.put(actor_params, "keys", keys_for_account()),
|
|
||||||
{:ok, %Actor{} = actor} <- Actors.create_actor(actor_params) do
|
|
||||||
conn
|
|
||||||
|> put_status(:created)
|
|
||||||
|> put_resp_header("location", actor_path(conn, :show, actor.preferred_username))
|
|
||||||
|> render("show_basic.json", actor: actor)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp keys_for_account() do
|
|
||||||
key = :public_key.generate_key({:rsa, 2048, 65_537})
|
|
||||||
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
|
|
||||||
|
|
||||||
[entry]
|
|
||||||
|> :public_key.pem_encode()
|
|
||||||
|> String.trim_trailing()
|
|
||||||
end
|
|
||||||
|
|
||||||
def show(conn, %{"name" => name}) do
|
|
||||||
with %Actor{} = actor <- Actors.get_actor_by_name_with_everything(name) do
|
|
||||||
render(conn, "show.json", actor: actor)
|
|
||||||
else
|
|
||||||
nil ->
|
|
||||||
send_resp(conn, :not_found, "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
|
|
||||||
def search(conn, %{"name" => name}) do
|
|
||||||
# find already saved accounts
|
|
||||||
case Actors.search(name) do
|
|
||||||
{:ok, actors} ->
|
|
||||||
render(conn, "index.json", actors: actors)
|
|
||||||
|
|
||||||
{:error, err} ->
|
|
||||||
json(conn, err)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update(conn, %{"name" => name, "actor" => actor_params}) do
|
|
||||||
actor = Actors.get_local_actor_by_name(name)
|
|
||||||
|
|
||||||
with {:ok, %Actor{} = actor} <- Actors.update_actor(actor, actor_params) do
|
|
||||||
render(conn, "show_basic.json", actor: actor)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# def delete(conn, %{"id" => id_str}) do
|
|
||||||
# {id, _} = Integer.parse(id_str)
|
|
||||||
# if Guardian.Plug.current_resource(conn).actor.id == id do
|
|
||||||
# actor = Actors.get_actor!(id)
|
|
||||||
# with {:ok, %Actor{}} <- Actors.delete_actor(actor) do
|
|
||||||
# send_resp(conn, :no_content, "")
|
|
||||||
# end
|
|
||||||
# else
|
|
||||||
# send_resp(conn, 401, "")
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
end
|
|
|
@ -1,78 +0,0 @@
|
||||||
defmodule MobilizonWeb.AddressController do
|
|
||||||
@moduledoc """
|
|
||||||
A controller for addresses
|
|
||||||
"""
|
|
||||||
|
|
||||||
use MobilizonWeb, :controller
|
|
||||||
|
|
||||||
alias Mobilizon.Addresses
|
|
||||||
alias Mobilizon.Addresses.Address
|
|
||||||
|
|
||||||
action_fallback(MobilizonWeb.FallbackController)
|
|
||||||
|
|
||||||
def index(conn, _params) do
|
|
||||||
addresses = Addresses.list_addresses()
|
|
||||||
render(conn, "index.json", addresses: addresses)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create(conn, %{"address" => address_params}) do
|
|
||||||
with {:ok, geom} <- Addresses.process_geom(address_params["geom"]) do
|
|
||||||
address_params = %{address_params | "geom" => geom}
|
|
||||||
|
|
||||||
with {:ok, %Address{} = address} <- Addresses.create_address(address_params) do
|
|
||||||
conn
|
|
||||||
|> put_status(:created)
|
|
||||||
|> put_resp_header("location", address_path(conn, :show, address))
|
|
||||||
|> render("show.json", address: address)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def process_geom(%{"type" => type, "data" => data}) do
|
|
||||||
import Logger
|
|
||||||
Logger.debug("Process geom")
|
|
||||||
Logger.debug(inspect(data))
|
|
||||||
Logger.debug(inspect(type))
|
|
||||||
types = [:point]
|
|
||||||
|
|
||||||
unless is_atom(type) do
|
|
||||||
type = String.to_existing_atom(type)
|
|
||||||
end
|
|
||||||
|
|
||||||
case type do
|
|
||||||
:point ->
|
|
||||||
%Geo.Point{coordinates: {data["latitude"], data["longitude"]}, srid: 4326}
|
|
||||||
|
|
||||||
nil ->
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def process_geom(nil) do
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def show(conn, %{"id" => id}) do
|
|
||||||
address = Addresses.get_address!(id)
|
|
||||||
render(conn, "show.json", address: address)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update(conn, %{"id" => id, "address" => address_params}) do
|
|
||||||
with {:ok, geom} <- Addresses.process_geom(address_params["geom"]) do
|
|
||||||
address = Addresses.get_address!(id)
|
|
||||||
address_params = %{address_params | "geom" => geom}
|
|
||||||
|
|
||||||
with {:ok, %Address{} = address} <- Addresses.update_address(address, address_params) do
|
|
||||||
render(conn, "show.json", address: address)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(conn, %{"id" => id}) do
|
|
||||||
address = Addresses.get_address!(id)
|
|
||||||
|
|
||||||
with {:ok, %Address{}} <- Addresses.delete_address(address) do
|
|
||||||
send_resp(conn, :no_content, "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,48 +0,0 @@
|
||||||
defmodule MobilizonWeb.BotController do
|
|
||||||
use MobilizonWeb, :controller
|
|
||||||
|
|
||||||
alias Mobilizon.Actors
|
|
||||||
alias Mobilizon.Actors.{Bot, Actor}
|
|
||||||
|
|
||||||
action_fallback(MobilizonWeb.FallbackController)
|
|
||||||
|
|
||||||
def index(conn, _params) do
|
|
||||||
bots = Actors.list_bots()
|
|
||||||
render(conn, "index.json", bots: bots)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create(conn, %{"bot" => bot_params}) do
|
|
||||||
with user <- Guardian.Plug.current_resource(conn),
|
|
||||||
bot_params <- Map.put(bot_params, "user_id", user.id),
|
|
||||||
%Actor{} = actor <-
|
|
||||||
Actors.register_bot_account(%{name: bot_params["name"], summary: bot_params["summary"]}),
|
|
||||||
bot_params <- Map.put(bot_params, "actor_id", actor.id),
|
|
||||||
{:ok, %Bot{} = bot} <- Actors.create_bot(bot_params) do
|
|
||||||
conn
|
|
||||||
|> put_status(:created)
|
|
||||||
|> put_resp_header("location", bot_path(conn, :show, bot))
|
|
||||||
|> render("show.json", bot: bot)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def show(conn, %{"id" => id}) do
|
|
||||||
bot = Actors.get_bot!(id)
|
|
||||||
render(conn, "show.json", bot: bot)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update(conn, %{"id" => id, "bot" => bot_params}) do
|
|
||||||
bot = Actors.get_bot!(id)
|
|
||||||
|
|
||||||
with {:ok, %Bot{} = bot} <- Actors.update_bot(bot, bot_params) do
|
|
||||||
render(conn, "show.json", bot: bot)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(conn, %{"id" => id}) do
|
|
||||||
bot = Actors.get_bot!(id)
|
|
||||||
|
|
||||||
with {:ok, %Bot{}} <- Actors.delete_bot(bot) do
|
|
||||||
send_resp(conn, :no_content, "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,46 +0,0 @@
|
||||||
defmodule MobilizonWeb.CategoryController do
|
|
||||||
@moduledoc """
|
|
||||||
Controller for Categories
|
|
||||||
"""
|
|
||||||
use MobilizonWeb, :controller
|
|
||||||
|
|
||||||
alias Mobilizon.Events
|
|
||||||
alias Mobilizon.Events.Category
|
|
||||||
|
|
||||||
action_fallback(MobilizonWeb.FallbackController)
|
|
||||||
|
|
||||||
def index(conn, _params) do
|
|
||||||
categories = Events.list_categories()
|
|
||||||
render(conn, "index.json", categories: categories)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create(conn, %{"category" => category_params}) do
|
|
||||||
with {:ok, %Category{} = category} <- Events.create_category(category_params) do
|
|
||||||
conn
|
|
||||||
|> put_status(:created)
|
|
||||||
|> put_resp_header("location", category_path(conn, :show, category))
|
|
||||||
|> render("show.json", category: category)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def show(conn, %{"id" => id}) do
|
|
||||||
category = Events.get_category!(id)
|
|
||||||
render(conn, "show.json", category: category)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update(conn, %{"id" => id, "category" => category_params}) do
|
|
||||||
category = Events.get_category!(id)
|
|
||||||
|
|
||||||
with {:ok, %Category{} = category} <- Events.update_category(category, category_params) do
|
|
||||||
render(conn, "show.json", category: category)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(conn, %{"id" => id}) do
|
|
||||||
category = Events.get_category!(id)
|
|
||||||
|
|
||||||
with {:ok, %Category{}} <- Events.delete_category(category) do
|
|
||||||
send_resp(conn, :no_content, "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,43 +0,0 @@
|
||||||
defmodule MobilizonWeb.CommentController do
|
|
||||||
use MobilizonWeb, :controller
|
|
||||||
|
|
||||||
alias Mobilizon.Events
|
|
||||||
alias Mobilizon.Events.Comment
|
|
||||||
|
|
||||||
action_fallback(MobilizonWeb.FallbackController)
|
|
||||||
|
|
||||||
def index(conn, _params) do
|
|
||||||
comments = Events.list_comments()
|
|
||||||
render(conn, "index.json", comments: comments)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create(conn, %{"comment" => comment_params}) do
|
|
||||||
with {:ok, %Comment{} = comment} <- Events.create_comment(comment_params) do
|
|
||||||
conn
|
|
||||||
|> put_status(:created)
|
|
||||||
|> put_resp_header("location", comment_path(conn, :show, comment))
|
|
||||||
|> render("show.json", comment: comment)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def show(conn, %{"uuid" => uuid}) do
|
|
||||||
comment = Events.get_comment_with_uuid!(uuid)
|
|
||||||
render(conn, "show.json", comment: comment)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update(conn, %{"uuid" => uuid, "comment" => comment_params}) do
|
|
||||||
comment = Events.get_comment_with_uuid!(uuid)
|
|
||||||
|
|
||||||
with {:ok, %Comment{} = comment} <- Events.update_comment(comment, comment_params) do
|
|
||||||
render(conn, "show.json", comment: comment)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(conn, %{"uuid" => uuid}) do
|
|
||||||
comment = Events.get_comment_with_uuid!(uuid)
|
|
||||||
|
|
||||||
with {:ok, %Comment{}} <- Events.delete_comment(comment) do
|
|
||||||
send_resp(conn, :no_content, "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,125 +0,0 @@
|
||||||
defmodule MobilizonWeb.EventController do
|
|
||||||
@moduledoc """
|
|
||||||
Controller for Events
|
|
||||||
"""
|
|
||||||
use MobilizonWeb, :controller
|
|
||||||
|
|
||||||
alias Mobilizon.Events
|
|
||||||
alias Mobilizon.Events.Event
|
|
||||||
alias Mobilizon.Export.ICalendar
|
|
||||||
|
|
||||||
require Logger
|
|
||||||
|
|
||||||
action_fallback(MobilizonWeb.FallbackController)
|
|
||||||
|
|
||||||
def index(conn, _params) do
|
|
||||||
ip = "88.161.154.97"
|
|
||||||
Logger.debug(inspect(Geolix.lookup(ip), pretty: true))
|
|
||||||
|
|
||||||
with %{
|
|
||||||
city: %Geolix.Adapter.MMDB2.Result.City{
|
|
||||||
city: city,
|
|
||||||
country: country,
|
|
||||||
location: %Geolix.Adapter.MMDB2.Record.Location{
|
|
||||||
latitude: latitude,
|
|
||||||
longitude: longitude
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} <- Geolix.lookup(ip) do
|
|
||||||
distance =
|
|
||||||
case city do
|
|
||||||
nil -> 500_000
|
|
||||||
_ -> 50_000
|
|
||||||
end
|
|
||||||
|
|
||||||
events = Events.find_close_events(longitude, latitude, distance)
|
|
||||||
|
|
||||||
render(
|
|
||||||
conn,
|
|
||||||
"index.json",
|
|
||||||
events: events,
|
|
||||||
coord: %{longitude: longitude, latitude: latitude, distance: distance},
|
|
||||||
city: city,
|
|
||||||
country: country
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def index_all(conn, _params) do
|
|
||||||
events = Events.list_events()
|
|
||||||
render(conn, "index_all.json", events: events)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create(conn, %{"event" => event_params}) do
|
|
||||||
event_params = process_event_address(event_params)
|
|
||||||
Logger.debug("creating event with")
|
|
||||||
Logger.debug(inspect(event_params))
|
|
||||||
|
|
||||||
with {:ok, %Event{} = event} <- Events.create_event(event_params) do
|
|
||||||
conn
|
|
||||||
|> put_status(:created)
|
|
||||||
|> put_resp_header("location", event_path(conn, :show, event.uuid))
|
|
||||||
|> render("show_simple.json", event: event)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp process_event_address(event) do
|
|
||||||
cond do
|
|
||||||
Map.has_key?(event, "address_type") && event["address_type"] !== :physical ->
|
|
||||||
event
|
|
||||||
|
|
||||||
Map.has_key?(event, "physical_address") ->
|
|
||||||
address = event["physical_address"]
|
|
||||||
geom = MobilizonWeb.AddressController.process_geom(address["geom"])
|
|
||||||
|
|
||||||
address =
|
|
||||||
case geom do
|
|
||||||
nil ->
|
|
||||||
address
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
%{address | "geom" => geom}
|
|
||||||
end
|
|
||||||
|
|
||||||
%{event | "physical_address" => address}
|
|
||||||
|
|
||||||
true ->
|
|
||||||
event
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def search(conn, %{"name" => name}) do
|
|
||||||
events = Events.find_events_by_name(name)
|
|
||||||
render(conn, "index.json", events: events)
|
|
||||||
end
|
|
||||||
|
|
||||||
def show(conn, %{"uuid" => uuid}) do
|
|
||||||
case Events.get_event_full_by_uuid(uuid) do
|
|
||||||
nil ->
|
|
||||||
send_resp(conn, 404, "")
|
|
||||||
|
|
||||||
event ->
|
|
||||||
render(conn, "show.json", event: event)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def export_to_ics(conn, %{"uuid" => uuid}) do
|
|
||||||
event = uuid |> Events.get_event_full_by_uuid() |> ICalendar.export_event()
|
|
||||||
send_resp(conn, 200, event)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update(conn, %{"uuid" => uuid, "event" => event_params}) do
|
|
||||||
event = Events.get_event_full_by_uuid(uuid)
|
|
||||||
|
|
||||||
with {:ok, %Event{} = event} <- Events.update_event(event, event_params) do
|
|
||||||
render(conn, "show_simple.json", event: event)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(conn, %{"uuid" => uuid}) do
|
|
||||||
with event <- Events.get_event_by_uuid(uuid),
|
|
||||||
{:ok, %Event{}} <- Events.delete_event(event) do
|
|
||||||
send_resp(conn, :no_content, "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,52 +0,0 @@
|
||||||
# defmodule MobilizonWeb.EventRequestController do
|
|
||||||
# @moduledoc """
|
|
||||||
# Controller for Event requests
|
|
||||||
# """
|
|
||||||
# use MobilizonWeb, :controller
|
|
||||||
#
|
|
||||||
# alias Mobilizon.Events
|
|
||||||
# alias Mobilizon.Events.{Event, Request}
|
|
||||||
#
|
|
||||||
# action_fallback MobilizonWeb.FallbackController
|
|
||||||
#
|
|
||||||
# def index_for_user(conn, _params) do
|
|
||||||
# actor = Guardian.Plug.current_resource(conn).actor
|
|
||||||
# requests = Events.list_requests_for_actor(actor)
|
|
||||||
# render(conn, "index.json", requests: requests)
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def create(conn, %{"request" => request_params}) do
|
|
||||||
# request_params = Map.put(request_params, "actor_id", Guardian.Plug.current_resource(conn).actor.id)
|
|
||||||
# with {:ok, %Request{} = request} <- Events.create_request(request_params) do
|
|
||||||
# conn
|
|
||||||
# |> put_status(:created)
|
|
||||||
# |> put_resp_header("location", event_request_path(conn, :show, request))
|
|
||||||
# |> render("show.json", request: request)
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def create_for_event(conn, %{"request" => request_params, "id" => event_id}) do
|
|
||||||
# request_params = Map.put(request_params, "event_id", event_id)
|
|
||||||
# create(conn, request_params)
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def show(conn, %{"id" => id}) do
|
|
||||||
# request = Events.get_request!(id)
|
|
||||||
# render(conn, "show.json", request: request)
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def update(conn, %{"id" => id, "request" => request_params}) do
|
|
||||||
# request = Events.get_request!(id)
|
|
||||||
#
|
|
||||||
# with {:ok, %Request{} = request} <- Events.update_request(request, request_params) do
|
|
||||||
# render(conn, "show.json", request: request)
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
# def delete(conn, %{"id" => id}) do
|
|
||||||
# request = Events.get_request!(id)
|
|
||||||
# with {:ok, %Request{}} <- Events.delete_request(request) do
|
|
||||||
# send_resp(conn, :no_content, "")
|
|
||||||
# end
|
|
||||||
# end
|
|
||||||
# end
|
|
|
@ -1,43 +0,0 @@
|
||||||
defmodule MobilizonWeb.FollowerController do
|
|
||||||
use MobilizonWeb, :controller
|
|
||||||
|
|
||||||
alias Mobilizon.Actors
|
|
||||||
alias Mobilizon.Actors.Follower
|
|
||||||
|
|
||||||
action_fallback(MobilizonWeb.FallbackController)
|
|
||||||
|
|
||||||
def index(conn, _params) do
|
|
||||||
followers = Actors.list_followers()
|
|
||||||
render(conn, "index.json", followers: followers)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create(conn, %{"follower" => follower_params}) do
|
|
||||||
with {:ok, %Follower{} = follower} <- Actors.create_follower(follower_params) do
|
|
||||||
conn
|
|
||||||
|> put_status(:created)
|
|
||||||
|> put_resp_header("location", follower_path(conn, :show, follower))
|
|
||||||
|> render("show.json", follower: follower)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def show(conn, %{"id" => id}) do
|
|
||||||
follower = Actors.get_follower!(id)
|
|
||||||
render(conn, "show.json", follower: follower)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update(conn, %{"id" => id, "follower" => follower_params}) do
|
|
||||||
follower = Actors.get_follower!(id)
|
|
||||||
|
|
||||||
with {:ok, %Follower{} = follower} <- Actors.update_follower(follower, follower_params) do
|
|
||||||
render(conn, "show.json", follower: follower)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(conn, %{"id" => id}) do
|
|
||||||
follower = Actors.get_follower!(id)
|
|
||||||
|
|
||||||
with {:ok, %Follower{}} <- Actors.delete_follower(follower) do
|
|
||||||
send_resp(conn, :no_content, "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,53 +0,0 @@
|
||||||
defmodule MobilizonWeb.GroupController do
|
|
||||||
@moduledoc """
|
|
||||||
Controller for Groups
|
|
||||||
"""
|
|
||||||
use MobilizonWeb, :controller
|
|
||||||
|
|
||||||
alias Mobilizon.Actors
|
|
||||||
alias Mobilizon.Actors.{Actor, Member}
|
|
||||||
|
|
||||||
action_fallback(MobilizonWeb.FallbackController)
|
|
||||||
|
|
||||||
def index(conn, _params) do
|
|
||||||
groups = Actors.list_groups()
|
|
||||||
render(conn, MobilizonWeb.ActorView, "index.json", actors: groups)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create(conn, %{"group" => group_params}) do
|
|
||||||
with {:ok, %Actor{} = group} <- Actors.create_group(group_params),
|
|
||||||
{:ok, %Member{} = member} <-
|
|
||||||
Actors.create_member(%{
|
|
||||||
"parent_id" => group.id,
|
|
||||||
"actor_id" => Actors.get_local_actor_by_name(group_params["actor_admin"]).id,
|
|
||||||
"role" => 2
|
|
||||||
}) do
|
|
||||||
conn
|
|
||||||
|> put_status(:created)
|
|
||||||
|> put_resp_header("location", actor_path(conn, :show, group))
|
|
||||||
|> render(MobilizonWeb.ActorView, "actor_basic.json", actor: group)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def join(conn, %{"name" => group_name, "actor_name" => actor_name}) do
|
|
||||||
with %Actor{} = group <- Actors.get_group_by_name(group_name),
|
|
||||||
%Actor{} = actor <- Actors.get_local_actor_by_name(actor_name),
|
|
||||||
{:ok, %Member{} = member} <-
|
|
||||||
Actors.create_member(%{"parent_id" => group.id, "actor_id" => actor.id}) do
|
|
||||||
conn
|
|
||||||
|> put_status(:created)
|
|
||||||
|> render(MobilizonWeb.MemberView, "member.json", member: member)
|
|
||||||
else
|
|
||||||
nil ->
|
|
||||||
conn
|
|
||||||
|> put_status(:not_found)
|
|
||||||
|> render(MobilizonWeb.ErrorView, "not_found.json",
|
|
||||||
details: "group or actor doesn't exist"
|
|
||||||
)
|
|
||||||
|
|
||||||
err ->
|
|
||||||
require Logger
|
|
||||||
Logger.debug(inspect(err))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,6 +0,0 @@
|
||||||
defmodule MobilizonWeb.InboxesController do
|
|
||||||
use MobilizonWeb, :controller
|
|
||||||
|
|
||||||
def create(conn) do
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,4 +1,4 @@
|
||||||
defmodule MobilizonWeb.NodeinfoController do
|
defmodule MobilizonWeb.NodeInfoController do
|
||||||
use MobilizonWeb, :controller
|
use MobilizonWeb, :controller
|
||||||
|
|
||||||
alias MobilizonWeb
|
alias MobilizonWeb
|
||||||
|
@ -11,7 +11,7 @@ defmodule MobilizonWeb.NodeinfoController do
|
||||||
links: [
|
links: [
|
||||||
%{
|
%{
|
||||||
rel: "http://nodeinfo.diaspora.software/ns/schema/2.0",
|
rel: "http://nodeinfo.diaspora.software/ns/schema/2.0",
|
||||||
href: MobilizonWeb.Router.Helpers.nodeinfo_url(MobilizonWeb.Endpoint, :nodeinfo, "2.0")
|
href: MobilizonWeb.Router.Helpers.node_info_url(MobilizonWeb.Endpoint, :nodeinfo, "2.0")
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -1,10 +0,0 @@
|
||||||
defmodule MobilizonWeb.OutboxesController do
|
|
||||||
use MobilizonWeb, :controller
|
|
||||||
|
|
||||||
def show(conn) do
|
|
||||||
actor = Guardian.Plug.current_resource(conn).actor
|
|
||||||
events = actor.events
|
|
||||||
|
|
||||||
render(conn, "index.json", events: events)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,18 +0,0 @@
|
||||||
defmodule MobilizonWeb.ParticipantController do
|
|
||||||
@moduledoc """
|
|
||||||
Controller for participants to an event
|
|
||||||
"""
|
|
||||||
use MobilizonWeb, :controller
|
|
||||||
|
|
||||||
alias Mobilizon.Events
|
|
||||||
|
|
||||||
def join(conn, %{"uuid" => uuid}) do
|
|
||||||
with event <- Events.get_event_by_uuid(uuid),
|
|
||||||
%{actor: actor} <- Guardian.Plug.current_resource(conn) do
|
|
||||||
participant =
|
|
||||||
Events.create_participant(%{"event_id" => event.id, "actor_id" => actor.id, "role" => 1})
|
|
||||||
|
|
||||||
render(conn, "participant.json", %{participant: participant})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,23 +0,0 @@
|
||||||
defmodule MobilizonWeb.SearchController do
|
|
||||||
@moduledoc """
|
|
||||||
Controller for Search
|
|
||||||
"""
|
|
||||||
use MobilizonWeb, :controller
|
|
||||||
|
|
||||||
alias Mobilizon.Events
|
|
||||||
alias Mobilizon.Actors
|
|
||||||
|
|
||||||
action_fallback(MobilizonWeb.FallbackController)
|
|
||||||
|
|
||||||
def search(conn, %{"name" => name}) do
|
|
||||||
events = Events.find_events_by_name(name)
|
|
||||||
# find already saved accounts
|
|
||||||
case Actors.search(name) do
|
|
||||||
{:ok, actors} ->
|
|
||||||
render(conn, "search.json", events: events, actors: actors)
|
|
||||||
|
|
||||||
{:error, err} ->
|
|
||||||
json(conn, err)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,56 +0,0 @@
|
||||||
defmodule MobilizonWeb.SessionController do
|
|
||||||
@moduledoc """
|
|
||||||
Controller for (event) Sessions
|
|
||||||
"""
|
|
||||||
use MobilizonWeb, :controller
|
|
||||||
|
|
||||||
alias Mobilizon.Events
|
|
||||||
alias Mobilizon.Events.Session
|
|
||||||
|
|
||||||
action_fallback(MobilizonWeb.FallbackController)
|
|
||||||
|
|
||||||
def index(conn, _params) do
|
|
||||||
sessions = Events.list_sessions()
|
|
||||||
render(conn, "index.json", sessions: sessions)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create(conn, %{"session" => session_params}) do
|
|
||||||
with {:ok, %Session{} = session} <- Events.create_session(session_params) do
|
|
||||||
conn
|
|
||||||
|> put_status(:created)
|
|
||||||
|> put_resp_header("location", session_path(conn, :show, session))
|
|
||||||
|> render("show.json", session: session)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def show(conn, %{"id" => id}) do
|
|
||||||
session = Events.get_session!(id)
|
|
||||||
render(conn, "show.json", session: session)
|
|
||||||
end
|
|
||||||
|
|
||||||
def show_sessions_for_event(conn, %{"uuid" => event_uuid}) do
|
|
||||||
sessions = Events.list_sessions_for_event(event_uuid)
|
|
||||||
render(conn, "index.json", sessions: sessions)
|
|
||||||
end
|
|
||||||
|
|
||||||
def show_sessions_for_track(conn, %{"id" => track}) do
|
|
||||||
sessions = Events.list_sessions_for_track(track)
|
|
||||||
render(conn, "index.json", sessions: sessions)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update(conn, %{"id" => id, "session" => session_params}) do
|
|
||||||
session = Events.get_session!(id)
|
|
||||||
|
|
||||||
with {:ok, %Session{} = session} <- Events.update_session(session, session_params) do
|
|
||||||
render(conn, "show.json", session: session)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(conn, %{"id" => id}) do
|
|
||||||
session = Events.get_session!(id)
|
|
||||||
|
|
||||||
with {:ok, %Session{}} <- Events.delete_session(session) do
|
|
||||||
send_resp(conn, :no_content, "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,46 +0,0 @@
|
||||||
defmodule MobilizonWeb.TagController do
|
|
||||||
@moduledoc """
|
|
||||||
Controller for Tags
|
|
||||||
"""
|
|
||||||
use MobilizonWeb, :controller
|
|
||||||
|
|
||||||
alias Mobilizon.Events
|
|
||||||
alias Mobilizon.Events.Tag
|
|
||||||
|
|
||||||
action_fallback(MobilizonWeb.FallbackController)
|
|
||||||
|
|
||||||
def index(conn, _params) do
|
|
||||||
tags = Events.list_tags()
|
|
||||||
render(conn, "index.json", tags: tags)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create(conn, %{"tag" => tag_params}) do
|
|
||||||
with {:ok, %Tag{} = tag} <- Events.create_tag(tag_params) do
|
|
||||||
conn
|
|
||||||
|> put_status(:created)
|
|
||||||
|> put_resp_header("location", tag_path(conn, :show, tag))
|
|
||||||
|> render("show.json", tag: tag)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def show(conn, %{"id" => id}) do
|
|
||||||
tag = Events.get_tag!(id)
|
|
||||||
render(conn, "show.json", tag: tag)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update(conn, %{"id" => id, "tag" => tag_params}) do
|
|
||||||
tag = Events.get_tag!(id)
|
|
||||||
|
|
||||||
with {:ok, %Tag{} = tag} <- Events.update_tag(tag, tag_params) do
|
|
||||||
render(conn, "show.json", tag: tag)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(conn, %{"id" => id}) do
|
|
||||||
tag = Events.get_tag!(id)
|
|
||||||
|
|
||||||
with {:ok, %Tag{}} <- Events.delete_tag(tag) do
|
|
||||||
send_resp(conn, :no_content, "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,46 +0,0 @@
|
||||||
defmodule MobilizonWeb.TrackController do
|
|
||||||
@moduledoc """
|
|
||||||
Controller for Tracks
|
|
||||||
"""
|
|
||||||
use MobilizonWeb, :controller
|
|
||||||
|
|
||||||
alias Mobilizon.Events
|
|
||||||
alias Mobilizon.Events.Track
|
|
||||||
|
|
||||||
action_fallback(MobilizonWeb.FallbackController)
|
|
||||||
|
|
||||||
def index(conn, _params) do
|
|
||||||
tracks = Events.list_tracks()
|
|
||||||
render(conn, "index.json", tracks: tracks)
|
|
||||||
end
|
|
||||||
|
|
||||||
def create(conn, %{"track" => track_params}) do
|
|
||||||
with {:ok, %Track{} = track} <- Events.create_track(track_params) do
|
|
||||||
conn
|
|
||||||
|> put_status(:created)
|
|
||||||
|> put_resp_header("location", track_path(conn, :show, track))
|
|
||||||
|> render("show.json", track: track)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def show(conn, %{"id" => id}) do
|
|
||||||
track = Events.get_track!(id)
|
|
||||||
render(conn, "show.json", track: track)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update(conn, %{"id" => id, "track" => track_params}) do
|
|
||||||
track = Events.get_track!(id)
|
|
||||||
|
|
||||||
with {:ok, %Track{} = track} <- Events.update_track(track, track_params) do
|
|
||||||
render(conn, "show.json", track: track)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(conn, %{"id" => id}) do
|
|
||||||
track = Events.get_track!(id)
|
|
||||||
|
|
||||||
with {:ok, %Track{}} <- Events.delete_track(track) do
|
|
||||||
send_resp(conn, :no_content, "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,148 +0,0 @@
|
||||||
defmodule MobilizonWeb.UserController do
|
|
||||||
@moduledoc """
|
|
||||||
Controller for Users
|
|
||||||
"""
|
|
||||||
use MobilizonWeb, :controller
|
|
||||||
|
|
||||||
alias Mobilizon.Actors
|
|
||||||
alias Mobilizon.Actors.User
|
|
||||||
alias Mobilizon.Repo
|
|
||||||
alias Mobilizon.Actors.Service.{Activation, ResetPassword}
|
|
||||||
|
|
||||||
action_fallback(MobilizonWeb.FallbackController)
|
|
||||||
|
|
||||||
def index(conn, _params) do
|
|
||||||
users = Actors.list_users_with_actors()
|
|
||||||
render(conn, "index.json", users: users)
|
|
||||||
end
|
|
||||||
|
|
||||||
def register(conn, %{"username" => username, "email" => email, "password" => password}) do
|
|
||||||
with {:ok, %User{} = user} <-
|
|
||||||
Actors.register(%{email: email, password: password, username: username}) do
|
|
||||||
Activation.send_confirmation_email(user, "locale")
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_status(:created)
|
|
||||||
|> render("confirmation.json", %{user: user})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def validate(conn, %{"token" => token}) do
|
|
||||||
with {:ok, %User{} = user} <- Activation.check_confirmation_token(token) do
|
|
||||||
{:ok, token, _claims} = MobilizonWeb.Guardian.encode_and_sign(user)
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_resp_header("location", user_path(conn, :show_current_actor))
|
|
||||||
|> render("show_with_token.json", %{user: user, token: token})
|
|
||||||
else
|
|
||||||
{:error, msg} ->
|
|
||||||
conn
|
|
||||||
|> put_status(:not_found)
|
|
||||||
|> json(%{"error" => msg})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@time_before_resend 3600
|
|
||||||
def resend_confirmation(conn, %{"email" => email}) do
|
|
||||||
with {:ok, %User{} = user} <- Actors.find_by_email(email),
|
|
||||||
false <- is_nil(user.confirmation_token),
|
|
||||||
true <-
|
|
||||||
Timex.before?(
|
|
||||||
Timex.shift(user.confirmation_sent_at, seconds: @time_before_resend),
|
|
||||||
DateTime.utc_now()
|
|
||||||
) do
|
|
||||||
Activation.resend_confirmation_email(user)
|
|
||||||
render(conn, "confirmation.json", %{user: user})
|
|
||||||
else
|
|
||||||
{:error, :not_found} ->
|
|
||||||
conn
|
|
||||||
|> put_status(:not_found)
|
|
||||||
|> json(%{"error" => "Unable to find an user with this email"})
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
conn
|
|
||||||
|> put_status(:not_found)
|
|
||||||
|> json(%{
|
|
||||||
"error" =>
|
|
||||||
"Unable to resend the validation token. Please wait a while before you can ask for resending token"
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def send_reset_password(conn, %{"email" => email}) do
|
|
||||||
with {:ok, %User{} = user} <- Actors.find_by_email(email),
|
|
||||||
{:ok, _} <- ResetPassword.send_password_reset_email(user) do
|
|
||||||
render(conn, "password_reset.json", %{user: user})
|
|
||||||
else
|
|
||||||
{:error, nil} ->
|
|
||||||
conn
|
|
||||||
|> put_status(:not_found)
|
|
||||||
|> json(%{"errors" => "Unable to find an user with this email"})
|
|
||||||
|
|
||||||
{:error, :email_too_soon} ->
|
|
||||||
conn
|
|
||||||
|> put_status(:not_found)
|
|
||||||
|> json(%{"errors" => "You requested a new reset password too early"})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def reset_password(conn, %{"password" => password, "token" => token}) do
|
|
||||||
with {:ok, %User{} = user} <- ResetPassword.check_reset_password_token(password, token) do
|
|
||||||
{:ok, token, _claims} = MobilizonWeb.Guardian.encode_and_sign(user)
|
|
||||||
render(conn, "show_with_token.json", %{user: user, token: token})
|
|
||||||
else
|
|
||||||
{:error, :invalid_token} ->
|
|
||||||
conn
|
|
||||||
|> put_status(:not_found)
|
|
||||||
|> json(%{"errors" => %{"token" => ["Wrong token for password reset"]}})
|
|
||||||
|
|
||||||
{:error, %Ecto.Changeset{} = changeset} ->
|
|
||||||
conn
|
|
||||||
|> put_status(:unprocessable_entity)
|
|
||||||
|> render(MobilizonWeb.ChangesetView, "error.json", changeset: changeset)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def show_current_actor(conn, _params) do
|
|
||||||
user =
|
|
||||||
conn
|
|
||||||
|> Guardian.Plug.current_resource()
|
|
||||||
|> Repo.preload(:actors)
|
|
||||||
|
|
||||||
render(conn, "show_simple.json", user: user)
|
|
||||||
end
|
|
||||||
|
|
||||||
# defp handle_changeset_errors(errors) do
|
|
||||||
# errors
|
|
||||||
# |> Enum.map(fn {field, detail} ->
|
|
||||||
# "#{field} " <> render_detail(detail)
|
|
||||||
# end)
|
|
||||||
# |> Enum.join()
|
|
||||||
# end
|
|
||||||
|
|
||||||
# defp render_detail({message, values}) do
|
|
||||||
# Enum.reduce(values, message, fn {k, v}, acc ->
|
|
||||||
# String.replace(acc, "%{#{k}}", to_string(v))
|
|
||||||
# end)
|
|
||||||
# end
|
|
||||||
|
|
||||||
# defp render_detail(message) do
|
|
||||||
# message
|
|
||||||
# end
|
|
||||||
|
|
||||||
def update(conn, %{"id" => id, "user" => user_params}) do
|
|
||||||
user = Actors.get_user!(id)
|
|
||||||
|
|
||||||
with {:ok, %User{} = user} <- Actors.update_user(user, user_params) do
|
|
||||||
render(conn, "show.json", user: user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete(conn, %{"id" => id}) do
|
|
||||||
user = Actors.get_user!(id)
|
|
||||||
|
|
||||||
with {:ok, %User{}} <- Actors.delete_user(user) do
|
|
||||||
send_resp(conn, :no_content, "")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,41 +0,0 @@
|
||||||
defmodule MobilizonWeb.UserSessionController do
|
|
||||||
@moduledoc """
|
|
||||||
Controller for user sessions
|
|
||||||
"""
|
|
||||||
use MobilizonWeb, :controller
|
|
||||||
alias Mobilizon.Actors.User
|
|
||||||
alias Mobilizon.Actors
|
|
||||||
|
|
||||||
def sign_in(conn, %{"email" => email, "password" => password}) do
|
|
||||||
with {:ok, %User{} = user} <- Actors.find_by_email(email),
|
|
||||||
{:ok, %User{} = _user} <- User.is_confirmed(user),
|
|
||||||
{:ok, token, _claims} <- Actors.authenticate(%{user: user, password: password}) do
|
|
||||||
# Render the token
|
|
||||||
render(conn, "token.json", %{token: token, user: user})
|
|
||||||
else
|
|
||||||
{:error, :not_found} ->
|
|
||||||
conn
|
|
||||||
|> put_status(401)
|
|
||||||
|> json(%{"error_msg" => "No such user", "display_error" => "session.error.bad_login"})
|
|
||||||
|
|
||||||
{:error, :unconfirmed} ->
|
|
||||||
conn
|
|
||||||
|> put_status(401)
|
|
||||||
|> json(%{
|
|
||||||
"error_msg" => "User is not activated",
|
|
||||||
"display_error" => "session.error.not_activated"
|
|
||||||
})
|
|
||||||
|
|
||||||
{:error, :unauthorized} ->
|
|
||||||
conn
|
|
||||||
|> put_status(401)
|
|
||||||
|> json(%{"error_msg" => "Bad login", "display_error" => "session.error.bad_login"})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def sign_out(conn, _params) do
|
|
||||||
conn
|
|
||||||
|> MobilizonWeb.Guardian.Plug.sign_out()
|
|
||||||
|> send_resp(204, "")
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -18,4 +18,8 @@ defmodule MobilizonWeb.WebFingerController do
|
||||||
_e -> send_resp(conn, 404, "Couldn't find user")
|
_e -> send_resp(conn, 404, "Couldn't find user")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def webfinger(conn, _) do
|
||||||
|
send_resp(conn, 400, "No query provided")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,6 +10,14 @@ defmodule MobilizonWeb.Endpoint do
|
||||||
#
|
#
|
||||||
# You should set gzip to true if you are running phoenix.digest
|
# You should set gzip to true if you are running phoenix.digest
|
||||||
# when deploying your static files in production.
|
# when deploying your static files in production.
|
||||||
|
|
||||||
|
plug(
|
||||||
|
Plug.Static,
|
||||||
|
at: "/uploads",
|
||||||
|
from: "./uploads",
|
||||||
|
gzip: false
|
||||||
|
)
|
||||||
|
|
||||||
plug(
|
plug(
|
||||||
Plug.Static,
|
Plug.Static,
|
||||||
at: "/",
|
at: "/",
|
||||||
|
|
26
lib/mobilizon_web/resolvers/actor.ex
Normal file
26
lib/mobilizon_web/resolvers/actor.ex
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
defmodule MobilizonWeb.Resolvers.Actor do
|
||||||
|
alias Mobilizon.Actors.Actor, as: ActorSchema
|
||||||
|
alias Mobilizon.Actors.User
|
||||||
|
alias Mobilizon.Actors
|
||||||
|
|
||||||
|
def find_actor(_parent, %{preferred_username: name}, _resolution) do
|
||||||
|
case Actors.get_actor_by_name_with_everything(name) do
|
||||||
|
nil ->
|
||||||
|
{:error, "Actor with name #{name} not found"}
|
||||||
|
|
||||||
|
actor ->
|
||||||
|
{:ok, actor}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the current actor for the currently logged-in user
|
||||||
|
"""
|
||||||
|
def get_current_actor(_parent, _args, %{context: %{current_user: user}}) do
|
||||||
|
{:ok, Actors.get_actor_for_user(user)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_current_actor(_parent, _args, _resolution) do
|
||||||
|
{:error, "You need to be logged-in to view current actor"}
|
||||||
|
end
|
||||||
|
end
|
37
lib/mobilizon_web/resolvers/category.ex
Normal file
37
lib/mobilizon_web/resolvers/category.ex
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
defmodule MobilizonWeb.Resolvers.Category do
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
def list_categories(_parent, _args, _resolution) do
|
||||||
|
categories =
|
||||||
|
Mobilizon.Events.list_categories()
|
||||||
|
|> Enum.map(fn category ->
|
||||||
|
urls = MobilizonWeb.Uploaders.Category.urls({category.picture, category})
|
||||||
|
Map.put(category, :picture, %{url: urls.original, url_thumbnail: urls.thumb})
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, categories}
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_category(_parent, %{title: title, picture: picture, description: description}, %{
|
||||||
|
context: %{current_user: user}
|
||||||
|
}) do
|
||||||
|
with {:ok, category} <-
|
||||||
|
Mobilizon.Events.create_category(%{
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
|
picture: picture
|
||||||
|
}),
|
||||||
|
urls <- MobilizonWeb.Uploaders.Category.urls({category.picture, category}) do
|
||||||
|
Logger.info("Created category " <> title)
|
||||||
|
{:ok, Map.put(category, :picture, %{url: urls.original, url_thumbnail: urls.thumb})}
|
||||||
|
else
|
||||||
|
{:error, %Ecto.Changeset{errors: errors} = _changeset} ->
|
||||||
|
# This is pretty ridiculous for changeset to error
|
||||||
|
errors =
|
||||||
|
Enum.into(errors, %{})
|
||||||
|
|> Enum.map(fn {key, {value, _}} -> Atom.to_string(key) <> ": " <> value end)
|
||||||
|
|
||||||
|
{:error, errors}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
54
lib/mobilizon_web/resolvers/event.ex
Normal file
54
lib/mobilizon_web/resolvers/event.ex
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
defmodule MobilizonWeb.Resolvers.Event do
|
||||||
|
def list_events(_parent, _args, _resolution) do
|
||||||
|
{:ok, Mobilizon.Events.list_events()}
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_event(_parent, %{uuid: uuid}, _resolution) do
|
||||||
|
case Mobilizon.Events.get_event_full_by_uuid(uuid) do
|
||||||
|
nil ->
|
||||||
|
{:error, "Event with UUID #{uuid} not found"}
|
||||||
|
|
||||||
|
event ->
|
||||||
|
{:ok, event}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def list_participants_for_event(_parent, %{uuid: uuid}, _resolution) do
|
||||||
|
{:ok, Mobilizon.Events.list_participants_for_event(uuid)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Search events by title
|
||||||
|
"""
|
||||||
|
def search_events(_parent, %{search: search, page: page, limit: limit}, _resolution) do
|
||||||
|
{:ok, Mobilizon.Events.find_events_by_name(search, page, limit)}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Search events and actors by title
|
||||||
|
"""
|
||||||
|
def search_events_and_actors(_parent, %{search: search, page: page, limit: limit}, _resolution) do
|
||||||
|
found =
|
||||||
|
Mobilizon.Events.find_events_by_name(search, page, limit) ++
|
||||||
|
Mobilizon.Actors.find_actors_by_username_or_name(search, page, limit)
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
Logger.debug(inspect(found))
|
||||||
|
{:ok, found}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
List participants for event (through an event request)
|
||||||
|
"""
|
||||||
|
def list_participants_for_event(%{uuid: uuid}, _args, _resolution) do
|
||||||
|
{:ok, Mobilizon.Events.list_participants_for_event(uuid)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_event(_parent, args, %{context: %{current_user: user}}) do
|
||||||
|
Mobilizon.Events.create_event(args)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_event(_parent, _args, _resolution) do
|
||||||
|
{:error, "You need to be logged-in to create events"}
|
||||||
|
end
|
||||||
|
end
|
2
lib/mobilizon_web/resolvers/upload.ex
Normal file
2
lib/mobilizon_web/resolvers/upload.ex
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
defmodule MobilizonWeb.Resolvers.Upload do
|
||||||
|
end
|
118
lib/mobilizon_web/resolvers/user.ex
Normal file
118
lib/mobilizon_web/resolvers/user.ex
Normal file
|
@ -0,0 +1,118 @@
|
||||||
|
defmodule MobilizonWeb.Resolvers.User do
|
||||||
|
alias Mobilizon.Actors.{User, Actor}
|
||||||
|
alias Mobilizon.Actors
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Find an user by it's ID
|
||||||
|
"""
|
||||||
|
def find_user(_parent, %{id: id}, _resolution) do
|
||||||
|
Actors.get_user_with_actor(id)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Return current logged-in user
|
||||||
|
"""
|
||||||
|
def get_current_user(_parent, _args, %{context: %{current_user: user}}) do
|
||||||
|
{:ok, user}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_current_user(_parent, _args, _resolution) do
|
||||||
|
{:error, "You need to be logged-in to view current user"}
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc """
|
||||||
|
Login an user. Returns a token and the user
|
||||||
|
"""
|
||||||
|
def login_user(_parent, %{email: email, password: password}, _resolution) do
|
||||||
|
with {:ok, %User{} = user} <- Actors.get_user_by_email(email, true),
|
||||||
|
{:ok, token, _} <- Actors.authenticate(%{user: user, password: password}),
|
||||||
|
%Actor{} = actor <- Actors.get_actor_for_user(user) do
|
||||||
|
{:ok, %{token: token, user: user, actor: actor}}
|
||||||
|
else
|
||||||
|
{:error, :user_not_found} ->
|
||||||
|
{:error, "User with email not found"}
|
||||||
|
|
||||||
|
{:error, :unauthorized} ->
|
||||||
|
{:error, "Impossible to authenticate"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc """
|
||||||
|
Register an user :
|
||||||
|
- create the user
|
||||||
|
- create the actor
|
||||||
|
- set the user's default_actor to the newly created actor
|
||||||
|
- send a validation email to the user
|
||||||
|
"""
|
||||||
|
@spec create_user_actor(any(), map(), any()) :: tuple()
|
||||||
|
def create_user_actor(_parent, args, _resolution) do
|
||||||
|
with {:ok, %Actor{user: user} = actor} <- Actors.register(args) do
|
||||||
|
Mobilizon.Actors.Service.Activation.send_confirmation_email(user)
|
||||||
|
{:ok, actor}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Validate an user, get it's actor and a token
|
||||||
|
"""
|
||||||
|
def validate_user(_parent, %{token: token}, _resolution) do
|
||||||
|
with {:ok, %User{} = user} <-
|
||||||
|
Mobilizon.Actors.Service.Activation.check_confirmation_token(token),
|
||||||
|
%Actor{} = actor <- Actors.get_actor_for_user(user),
|
||||||
|
{:ok, token, _} <- MobilizonWeb.Guardian.encode_and_sign(user) do
|
||||||
|
{:ok, %{token: token, user: user, actor: actor}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Send the confirmation email again.
|
||||||
|
We only do this to accounts unconfirmed
|
||||||
|
"""
|
||||||
|
def resend_confirmation_email(_parent, %{email: email, locale: locale}, _resolution) do
|
||||||
|
with {:ok, user} <- Actors.get_user_by_email(email, false),
|
||||||
|
{:ok, email} <- Mobilizon.Actors.Service.Activation.resend_confirmation_email(user, locale) do
|
||||||
|
{:ok, email}
|
||||||
|
else
|
||||||
|
{:error, :user_not_found} ->
|
||||||
|
{:error, "No user to validate with this email was found"}
|
||||||
|
{:error, :email_too_soon} ->
|
||||||
|
{:error, "You requested again a confirmation email too soon"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Send an email to reset the password from an user
|
||||||
|
"""
|
||||||
|
def send_reset_password(_parent, %{email: email, locale: locale}, _resolution) do
|
||||||
|
with {:ok, user} <- Actors.get_user_by_email(email, false),
|
||||||
|
{:ok, email} <- Mobilizon.Actors.Service.ResetPassword.send_password_reset_email(user, locale) do
|
||||||
|
{:ok, email}
|
||||||
|
else
|
||||||
|
{:error, :user_not_found} ->
|
||||||
|
{:error, "No user to validate with this email was found"}
|
||||||
|
{:error, :email_too_soon} ->
|
||||||
|
{:error, "You requested again a confirmation email too soon"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Reset the password from an user
|
||||||
|
"""
|
||||||
|
def reset_password(_parent, %{password: password, token: token}, _resolution) do
|
||||||
|
with {:ok, %User{} = user} <-
|
||||||
|
Mobilizon.Actors.Service.ResetPassword.check_reset_password_token(password, token),
|
||||||
|
%Actor{} = actor <- Actors.get_actor_for_user(user),
|
||||||
|
{:ok, token, _} <- MobilizonWeb.Guardian.encode_and_sign(user) do
|
||||||
|
{:ok, %{token: token, user: user, actor: actor}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Change an user default actor"
|
||||||
|
def change_default_actor(_parent, %{preferred_username: username}, %{
|
||||||
|
context: %{current_user: user}
|
||||||
|
}) do
|
||||||
|
with %Actor{id: id} <- Actors.get_local_actor_by_name(username) do
|
||||||
|
Actors.update_user(user, %{default_actor_id: id})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,8 +4,9 @@ defmodule MobilizonWeb.Router do
|
||||||
"""
|
"""
|
||||||
use MobilizonWeb, :router
|
use MobilizonWeb, :router
|
||||||
|
|
||||||
pipeline :api do
|
pipeline :graphql do
|
||||||
plug(:accepts, ["json"])
|
plug(:accepts, ["json"])
|
||||||
|
plug(MobilizonWeb.AuthPipeline)
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :well_known do
|
pipeline :well_known do
|
||||||
|
@ -17,11 +18,6 @@ defmodule MobilizonWeb.Router do
|
||||||
plug(MobilizonWeb.HTTPSignaturePlug)
|
plug(MobilizonWeb.HTTPSignaturePlug)
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :api_auth do
|
|
||||||
plug(:accepts, ["json"])
|
|
||||||
plug(MobilizonWeb.AuthPipeline)
|
|
||||||
end
|
|
||||||
|
|
||||||
pipeline :browser do
|
pipeline :browser do
|
||||||
plug(:accepts, ["html"])
|
plug(:accepts, ["html"])
|
||||||
plug(:fetch_session)
|
plug(:fetch_session)
|
||||||
|
@ -34,90 +30,21 @@ defmodule MobilizonWeb.Router do
|
||||||
plug(:accepts, ["html", "application/json"])
|
plug(:accepts, ["html", "application/json"])
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api", MobilizonWeb do
|
scope "/api" do
|
||||||
pipe_through(:api)
|
pipe_through(:graphql)
|
||||||
|
|
||||||
scope "/v1" do
|
forward("/", Absinthe.Plug, schema: MobilizonWeb.Schema)
|
||||||
post("/users", UserController, :register)
|
|
||||||
get("/users/validate/:token", UserController, :validate)
|
|
||||||
post("/users/resend", UserController, :resend_confirmation)
|
|
||||||
|
|
||||||
post("/users/password-reset/send", UserController, :send_reset_password)
|
|
||||||
post("/users/password-reset/post", UserController, :reset_password)
|
|
||||||
|
|
||||||
post("/login", UserSessionController, :sign_in)
|
|
||||||
get("/groups", GroupController, :index)
|
|
||||||
get("/events", EventController, :index)
|
|
||||||
get("/events/all", EventController, :index_all)
|
|
||||||
get("/events/search/:name", EventController, :search)
|
|
||||||
get("/events/:uuid/ics", EventController, :export_to_ics)
|
|
||||||
get("/events/:uuid/tracks", TrackController, :show_tracks_for_event)
|
|
||||||
get("/events/:uuid/sessions", SessionController, :show_sessions_for_event)
|
|
||||||
get("/events/:uuid", EventController, :show)
|
|
||||||
get("/comments/:uuid", CommentController, :show)
|
|
||||||
get("/bots/:id", BotController, :show)
|
|
||||||
get("/bots", BotController, :index)
|
|
||||||
|
|
||||||
get("/actors", ActorController, :index)
|
|
||||||
get("/actors/search/:name", ActorController, :search)
|
|
||||||
get("/actors/:name", ActorController, :show)
|
|
||||||
|
|
||||||
resources("/followers", FollowerController, except: [:new, :edit])
|
|
||||||
|
|
||||||
resources("/tags", TagController, only: [:index, :show])
|
|
||||||
resources("/categories", CategoryController, only: [:index, :show])
|
|
||||||
resources("/sessions", SessionController, only: [:index, :show])
|
|
||||||
resources("/tracks", TrackController, only: [:index, :show])
|
|
||||||
resources("/addresses", AddressController, only: [:index, :show])
|
|
||||||
|
|
||||||
get("/search/:name", SearchController, :search)
|
|
||||||
|
|
||||||
scope "/nodeinfo" do
|
|
||||||
pipe_through(:nodeinfo)
|
|
||||||
|
|
||||||
get("/:version", NodeinfoController, :nodeinfo)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Authentificated API
|
forward("/graphiql", Absinthe.Plug.GraphiQL, schema: MobilizonWeb.Schema)
|
||||||
scope "/api", MobilizonWeb do
|
|
||||||
pipe_through(:api_auth)
|
|
||||||
|
|
||||||
scope "/v1" do
|
|
||||||
get("/user", UserController, :show_current_actor)
|
|
||||||
post("/sign-out", UserSessionController, :sign_out)
|
|
||||||
resources("/users", UserController, except: [:new, :edit, :show])
|
|
||||||
post("/actors", ActorController, :create)
|
|
||||||
patch("/actors/:name", ActorController, :update)
|
|
||||||
post("/events", EventController, :create)
|
|
||||||
patch("/events/:uuid", EventController, :update)
|
|
||||||
put("/events/:uuid", EventController, :update)
|
|
||||||
delete("/events/:uuid", EventController, :delete)
|
|
||||||
post("/events/:uuid/join", ParticipantController, :join)
|
|
||||||
post("/comments", CommentController, :create)
|
|
||||||
patch("/comments/:uuid", CommentController, :update)
|
|
||||||
put("/comments/:uuid", CommentController, :update)
|
|
||||||
delete("/comments/:uuid", CommentController, :delete)
|
|
||||||
resources("/bots", BotController, except: [:new, :edit, :show, :index])
|
|
||||||
post("/groups", GroupController, :create)
|
|
||||||
post("/groups/:name/join", GroupController, :join)
|
|
||||||
resources("/members", MemberController)
|
|
||||||
resources("/sessions", SessionController, except: [:index, :show])
|
|
||||||
resources("/tracks", TrackController, except: [:index, :show])
|
|
||||||
get("/tracks/:id/sessions", SessionController, :show_sessions_for_track)
|
|
||||||
resources("/categories", CategoryController)
|
|
||||||
resources("/tags", TagController)
|
|
||||||
resources("/addresses", AddressController, except: [:index, :show])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
scope "/.well-known", MobilizonWeb do
|
scope "/.well-known", MobilizonWeb do
|
||||||
pipe_through(:well_known)
|
pipe_through(:well_known)
|
||||||
|
|
||||||
get("/host-meta", WebFingerController, :host_meta)
|
get("/host-meta", WebFingerController, :host_meta)
|
||||||
get("/webfinger", WebFingerController, :webfinger)
|
get("/webfinger", WebFingerController, :webfinger)
|
||||||
get("/nodeinfo", NodeinfoController, :schemas)
|
get("/nodeinfo", NodeInfoController, :schemas)
|
||||||
|
get("/nodeinfo/:version", NodeInfoController, :nodeinfo)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/", MobilizonWeb do
|
scope "/", MobilizonWeb do
|
||||||
|
@ -141,6 +68,7 @@ defmodule MobilizonWeb.Router do
|
||||||
scope "/", MobilizonWeb do
|
scope "/", MobilizonWeb do
|
||||||
pipe_through(:browser)
|
pipe_through(:browser)
|
||||||
|
|
||||||
|
forward("/uploads", UploadPlug)
|
||||||
get("/*path", PageController, :index)
|
get("/*path", PageController, :index)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
318
lib/mobilizon_web/schema.ex
Normal file
318
lib/mobilizon_web/schema.ex
Normal file
|
@ -0,0 +1,318 @@
|
||||||
|
defmodule MobilizonWeb.Schema do
|
||||||
|
use Absinthe.Schema
|
||||||
|
|
||||||
|
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
||||||
|
alias Mobilizon.{Actors, Events}
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
alias Mobilizon.Events.Event
|
||||||
|
|
||||||
|
import_types(MobilizonWeb.Schema.Custom.UUID)
|
||||||
|
import_types(Absinthe.Type.Custom)
|
||||||
|
import_types(Absinthe.Plug.Types)
|
||||||
|
|
||||||
|
# import_types(MobilizonWeb.Schema.EventTypes)
|
||||||
|
# import_types(MobilizonWeb.Schema.ActorTypes)
|
||||||
|
|
||||||
|
alias MobilizonWeb.Resolvers
|
||||||
|
|
||||||
|
@desc "An ActivityPub actor"
|
||||||
|
object :actor do
|
||||||
|
field(:url, :string)
|
||||||
|
field(:outbox_url, :string)
|
||||||
|
field(:inbox_url, :string)
|
||||||
|
field(:following_url, :string)
|
||||||
|
field(:followers_url, :string)
|
||||||
|
field(:shared_inbox_url, :string)
|
||||||
|
field(:type, :actor_type)
|
||||||
|
field(:name, :string)
|
||||||
|
field(:domain, :string)
|
||||||
|
field(:summary, :string)
|
||||||
|
field(:preferred_username, :string)
|
||||||
|
field(:keys, :string)
|
||||||
|
field(:manually_approves_followers, :boolean)
|
||||||
|
field(:suspended, :boolean)
|
||||||
|
field(:avatar_url, :string)
|
||||||
|
field(:banner_url, :string)
|
||||||
|
# field(:followers, list_of(:follower))
|
||||||
|
field :organized_events, list_of(:event) do
|
||||||
|
resolve(dataloader(Events))
|
||||||
|
end
|
||||||
|
|
||||||
|
# field(:memberships, list_of(:member))
|
||||||
|
field(:user, :user)
|
||||||
|
end
|
||||||
|
|
||||||
|
enum :actor_type do
|
||||||
|
value(:Person)
|
||||||
|
value(:Application)
|
||||||
|
value(:Group)
|
||||||
|
value(:Organization)
|
||||||
|
value(:Service)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "A local user of Mobilizon"
|
||||||
|
object :user do
|
||||||
|
field(:id, non_null(:id))
|
||||||
|
field(:email, non_null(:string))
|
||||||
|
# , resolve: dataloader(:actors))
|
||||||
|
field(:actors, non_null(list_of(:actor)))
|
||||||
|
field(:default_actor_id, non_null(:integer))
|
||||||
|
field(:confirmed_at, :datetime)
|
||||||
|
field(:confirmation_sent_at, :datetime)
|
||||||
|
field(:confirmation_token, :string)
|
||||||
|
field(:reset_password_sent_at, :datetime)
|
||||||
|
field(:reset_password_token, :string)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "A JWT and the associated user ID"
|
||||||
|
object :login do
|
||||||
|
field(:token, non_null(:string))
|
||||||
|
field(:user, non_null(:user))
|
||||||
|
field(:actor, non_null(:actor))
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "An event"
|
||||||
|
object :event do
|
||||||
|
field(:uuid, :uuid)
|
||||||
|
field(:url, :string)
|
||||||
|
field(:local, :boolean)
|
||||||
|
field(:title, :string)
|
||||||
|
field(:description, :string)
|
||||||
|
field(:begins_on, :datetime)
|
||||||
|
field(:ends_on, :datetime)
|
||||||
|
field(:state, :integer)
|
||||||
|
field(:status, :integer)
|
||||||
|
field(:public, :boolean)
|
||||||
|
field(:thumbnail, :string)
|
||||||
|
field(:large_image, :string)
|
||||||
|
field(:publish_at, :datetime)
|
||||||
|
field(:address_type, :address_type)
|
||||||
|
field(:online_address, :string)
|
||||||
|
field(:phone, :string)
|
||||||
|
|
||||||
|
field :organizer_actor, :actor do
|
||||||
|
resolve(dataloader(Actors))
|
||||||
|
end
|
||||||
|
|
||||||
|
field(:attributed_to, :actor)
|
||||||
|
# field(:tags, list_of(:tag))
|
||||||
|
field(:category, :category)
|
||||||
|
|
||||||
|
field(:participants, list_of(:participant),
|
||||||
|
resolve: &Resolvers.Event.list_participants_for_event/3
|
||||||
|
)
|
||||||
|
|
||||||
|
# field(:tracks, list_of(:track))
|
||||||
|
# field(:sessions, list_of(:session))
|
||||||
|
# field(:physical_address, :address)
|
||||||
|
|
||||||
|
field(:updated_at, :datetime)
|
||||||
|
field(:created_at, :datetime)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Represents a participant to an event"
|
||||||
|
object :participant do
|
||||||
|
# field(:event, :event, resolve: dataloader(Events))
|
||||||
|
# , resolve: dataloader(Actors)
|
||||||
|
field(:actor, :actor)
|
||||||
|
field(:role, :integer)
|
||||||
|
end
|
||||||
|
|
||||||
|
enum :address_type do
|
||||||
|
value(:physical)
|
||||||
|
value(:url)
|
||||||
|
value(:phone)
|
||||||
|
value(:other)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "A category"
|
||||||
|
object :category do
|
||||||
|
field(:id, :id)
|
||||||
|
field(:description, :string)
|
||||||
|
field(:picture, :picture)
|
||||||
|
field(:title, :string)
|
||||||
|
field(:updated_at, :datetime)
|
||||||
|
field(:created_at, :datetime)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "A picture"
|
||||||
|
object :picture do
|
||||||
|
field(:url, :string)
|
||||||
|
field(:url_thumbnail, :string)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "A search result"
|
||||||
|
union :search_result do
|
||||||
|
types([:event, :actor])
|
||||||
|
|
||||||
|
resolve_type(fn
|
||||||
|
%Actor{}, _ ->
|
||||||
|
:actor
|
||||||
|
|
||||||
|
%Event{}, _ ->
|
||||||
|
:event
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def context(ctx) do
|
||||||
|
loader =
|
||||||
|
Dataloader.new()
|
||||||
|
|> Dataloader.add_source(Actors, Actors.data())
|
||||||
|
|> Dataloader.add_source(Events, Events.data())
|
||||||
|
|
||||||
|
Map.put(ctx, :loader, loader)
|
||||||
|
end
|
||||||
|
|
||||||
|
def plugins do
|
||||||
|
[Absinthe.Middleware.Dataloader | Absinthe.Plugin.defaults()]
|
||||||
|
end
|
||||||
|
|
||||||
|
query do
|
||||||
|
@desc "Get all events"
|
||||||
|
field :events, list_of(:event) do
|
||||||
|
resolve(&Resolvers.Event.list_events/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Search through events and actors"
|
||||||
|
field :search, list_of(:search_result) do
|
||||||
|
arg(:search, non_null(:string))
|
||||||
|
arg(:page, :integer, default_value: 1)
|
||||||
|
arg(:limit, :integer, default_value: 10)
|
||||||
|
resolve(&Resolvers.Event.search_events_and_actors/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Get an event by uuid"
|
||||||
|
field :event, :event do
|
||||||
|
arg(:uuid, non_null(:uuid))
|
||||||
|
resolve(&Resolvers.Event.find_event/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Get all participants for an event uuid"
|
||||||
|
field :participants, list_of(:participant) do
|
||||||
|
arg(:uuid, non_null(:uuid))
|
||||||
|
resolve(&Resolvers.Event.list_participants_for_event/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Get an user"
|
||||||
|
field :user, :user do
|
||||||
|
arg(:id, non_null(:id))
|
||||||
|
resolve(&Resolvers.User.find_user/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Get the current user"
|
||||||
|
field :logged_user, :user do
|
||||||
|
resolve(&Resolvers.User.get_current_user/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Get the current actor for the logged-in user"
|
||||||
|
field :logged_actor, :actor do
|
||||||
|
resolve(&Resolvers.Actor.get_current_actor/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Get an actor"
|
||||||
|
field :actor, :actor do
|
||||||
|
arg(:preferred_username, non_null(:string))
|
||||||
|
resolve(&Resolvers.Actor.find_actor/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Get the list of categories
|
||||||
|
"""
|
||||||
|
field :categories, list_of(:category) do
|
||||||
|
resolve(&Resolvers.Category.list_categories/3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
mutation do
|
||||||
|
@desc "Create an event"
|
||||||
|
field :create_event, type: :event do
|
||||||
|
arg(:title, non_null(:string))
|
||||||
|
arg(:description, non_null(:string))
|
||||||
|
arg(:begins_on, non_null(:datetime))
|
||||||
|
arg(:ends_on, :datetime)
|
||||||
|
arg(:state, :integer)
|
||||||
|
arg(:status, :integer)
|
||||||
|
arg(:public, :boolean)
|
||||||
|
arg(:thumbnail, :string)
|
||||||
|
arg(:large_image, :string)
|
||||||
|
arg(:publish_at, :datetime)
|
||||||
|
arg(:address_type, non_null(:address_type))
|
||||||
|
arg(:online_address, :string)
|
||||||
|
arg(:phone, :string)
|
||||||
|
arg(:organizer_actor_id, non_null(:integer))
|
||||||
|
arg(:category_id, non_null(:integer))
|
||||||
|
|
||||||
|
resolve(&Resolvers.Event.create_event/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Create a category with a title, description and picture
|
||||||
|
"""
|
||||||
|
field :create_category, type: :category do
|
||||||
|
arg(:title, non_null(:string))
|
||||||
|
arg(:description, non_null(:string))
|
||||||
|
arg(:picture, non_null(:upload))
|
||||||
|
resolve(&Resolvers.Category.create_category/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Create an user (returns an actor)"
|
||||||
|
field :create_user, type: :actor do
|
||||||
|
arg(:email, non_null(:string))
|
||||||
|
arg(:password, non_null(:string))
|
||||||
|
arg(:username, non_null(:string))
|
||||||
|
|
||||||
|
resolve(&Resolvers.User.create_user_actor/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Validate an user after registration"
|
||||||
|
field :validate_user, type: :login do
|
||||||
|
arg(:token, non_null(:string))
|
||||||
|
resolve(&Resolvers.User.validate_user/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Resend registration confirmation token"
|
||||||
|
field :resend_confirmation_email, type: :string do
|
||||||
|
arg(:email, non_null(:string))
|
||||||
|
arg(:locale, :string, default_value: "en")
|
||||||
|
resolve(&Resolvers.User.resend_confirmation_email/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Send a link through email to reset user password
|
||||||
|
"""
|
||||||
|
field :send_reset_password, type: :string do
|
||||||
|
arg(:email, non_null(:string))
|
||||||
|
arg(:locale, :string, default_value: "en")
|
||||||
|
resolve(&Resolvers.User.send_reset_password/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Reset user password
|
||||||
|
"""
|
||||||
|
field :reset_password, type: :login do
|
||||||
|
arg(:token, non_null(:string))
|
||||||
|
arg(:password, non_null(:string))
|
||||||
|
arg(:locale, :string, default_value: "en")
|
||||||
|
resolve(&Resolvers.User.reset_password/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Login an user"
|
||||||
|
field :login, :login do
|
||||||
|
arg(:email, non_null(:string))
|
||||||
|
arg(:password, non_null(:string))
|
||||||
|
resolve(&Resolvers.User.login_user/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Change default actor for user"
|
||||||
|
field :change_default_actor, :user do
|
||||||
|
arg(:preferred_username, non_null(:string))
|
||||||
|
resolve(&Resolvers.User.change_default_actor/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "Upload a picture"
|
||||||
|
field :upload_picture, :picture do
|
||||||
|
arg(:file, non_null(:upload))
|
||||||
|
resolve(&Resolvers.Upload.upload_picture/3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
36
lib/mobilizon_web/schema/custom/UUID4.ex
Normal file
36
lib/mobilizon_web/schema/custom/UUID4.ex
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
defmodule MobilizonWeb.Schema.Custom.UUID do
|
||||||
|
@moduledoc """
|
||||||
|
The UUID4 scalar type allows UUID compliant strings to be passed in and out.
|
||||||
|
Requires `{ :ecto, ">= 0.0.0" }` package: https://github.com/elixir-ecto/ecto
|
||||||
|
"""
|
||||||
|
use Absinthe.Schema.Notation
|
||||||
|
|
||||||
|
alias Ecto.UUID
|
||||||
|
|
||||||
|
scalar :uuid, name: "UUID" do
|
||||||
|
description("""
|
||||||
|
The `UUID` scalar type represents UUID4 compliant string data, represented as UTF-8
|
||||||
|
character sequences. The UUID4 type is most often used to represent unique
|
||||||
|
human-readable ID strings.
|
||||||
|
""")
|
||||||
|
|
||||||
|
serialize(&encode/1)
|
||||||
|
parse(&decode/1)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec decode(Absinthe.Blueprint.Input.String.t()) :: {:ok, term()} | :error
|
||||||
|
@spec decode(Absinthe.Blueprint.Input.Null.t()) :: {:ok, nil}
|
||||||
|
defp decode(%Absinthe.Blueprint.Input.String{value: value}) do
|
||||||
|
UUID.cast(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp decode(%Absinthe.Blueprint.Input.Null{}) do
|
||||||
|
{:ok, nil}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp decode(_) do
|
||||||
|
:error
|
||||||
|
end
|
||||||
|
|
||||||
|
defp encode(value), do: value
|
||||||
|
end
|
15
lib/mobilizon_web/upload_plug.ex
Normal file
15
lib/mobilizon_web/upload_plug.ex
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule MobilizonWeb.UploadPlug do
|
||||||
|
use Plug.Builder
|
||||||
|
|
||||||
|
plug(Plug.Static,
|
||||||
|
at: "/",
|
||||||
|
from: {:mobilizon, "./uploads"}
|
||||||
|
)
|
||||||
|
|
||||||
|
# only: ~w(images robots.txt)
|
||||||
|
plug(:not_found)
|
||||||
|
|
||||||
|
def not_found(conn, _) do
|
||||||
|
send_resp(conn, 404, "not found")
|
||||||
|
end
|
||||||
|
end
|
50
lib/mobilizon_web/uploaders/avatar.ex
Normal file
50
lib/mobilizon_web/uploaders/avatar.ex
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
defmodule MobilizonWeb.Uploaders.Avatar do
|
||||||
|
use Arc.Definition
|
||||||
|
|
||||||
|
# Include ecto support (requires package arc_ecto installed):
|
||||||
|
# use Arc.Ecto.Definition
|
||||||
|
|
||||||
|
@versions [:original]
|
||||||
|
|
||||||
|
# To add a thumbnail version:
|
||||||
|
# @versions [:original, :thumb]
|
||||||
|
|
||||||
|
# Override the bucket on a per definition basis:
|
||||||
|
# def bucket do
|
||||||
|
# :custom_bucket_name
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Whitelist file extensions:
|
||||||
|
# def validate({file, _}) do
|
||||||
|
# ~w(.jpg .jpeg .gif .png) |> Enum.member?(Path.extname(file.file_name))
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Define a thumbnail transformation:
|
||||||
|
# def transform(:thumb, _) do
|
||||||
|
# {:convert, "-strip -thumbnail 250x250^ -gravity center -extent 250x250 -format png", :png}
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Override the persisted filenames:
|
||||||
|
# def filename(version, _) do
|
||||||
|
# version
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Override the storage directory:
|
||||||
|
# def storage_dir(version, {file, scope}) do
|
||||||
|
# "uploads/user/avatars/#{scope.id}"
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Provide a default URL if there hasn't been a file uploaded
|
||||||
|
# def default_url(version, scope) do
|
||||||
|
# "/images/avatars/default_#{version}.png"
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Specify custom headers for s3 objects
|
||||||
|
# Available options are [:cache_control, :content_disposition,
|
||||||
|
# :content_encoding, :content_length, :content_type,
|
||||||
|
# :expect, :expires, :storage_class, :website_redirect_location]
|
||||||
|
#
|
||||||
|
# def s3_object_headers(version, {file, scope}) do
|
||||||
|
# [content_type: MIME.from_path(file.file_name)]
|
||||||
|
# end
|
||||||
|
end
|
49
lib/mobilizon_web/uploaders/category.ex
Normal file
49
lib/mobilizon_web/uploaders/category.ex
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
defmodule MobilizonWeb.Uploaders.Category do
|
||||||
|
use Arc.Definition
|
||||||
|
use Arc.Ecto.Definition
|
||||||
|
|
||||||
|
# To add a thumbnail version:
|
||||||
|
@versions [:original, :thumb]
|
||||||
|
@extension_whitelist ~w(.jpg .jpeg .gif .png)
|
||||||
|
|
||||||
|
# Override the bucket on a per definition basis:
|
||||||
|
# def bucket do
|
||||||
|
# :custom_bucket_name
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Whitelist file extensions:
|
||||||
|
def validate({file, _}) do
|
||||||
|
file_extension = file.file_name |> Path.extname() |> String.downcase()
|
||||||
|
Enum.member?(@extension_whitelist, file_extension)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Define a thumbnail transformation:
|
||||||
|
def transform(:thumb, _) do
|
||||||
|
{:convert, "-strip -thumbnail 250x250^ -gravity center -extent 250x250 -format png", :png}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Override the persisted filenames:
|
||||||
|
def filename(version, {file, %{title: title}}) do
|
||||||
|
"#{title}_#{version}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# TODO : When we're sure creating a category is secured and made possible only for admins, use category name
|
||||||
|
# Override the storage directory:
|
||||||
|
def storage_dir(_, _) do
|
||||||
|
"uploads/categories/"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Provide a default URL if there hasn't been a file uploaded
|
||||||
|
# def default_url(version, scope) do
|
||||||
|
# "/images/avatars/default_#{version}.png"
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Specify custom headers for s3 objects
|
||||||
|
# Available options are [:cache_control, :content_disposition,
|
||||||
|
# :content_encoding, :content_length, :content_type,
|
||||||
|
# :expect, :expires, :storage_class, :website_redirect_location]
|
||||||
|
#
|
||||||
|
# def s3_object_headers(version, {file, scope}) do
|
||||||
|
# [content_type: MIME.from_path(file.file_name)]
|
||||||
|
# end
|
||||||
|
end
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue