Merge branch 'docker' into 'master'

Docker

See merge request tcit/eventos!16
This commit is contained in:
Thomas Citharel 2018-07-25 16:45:02 +02:00
commit 64245aaf32
58 changed files with 476 additions and 186 deletions

11
Dockerfile Normal file
View file

@ -0,0 +1,11 @@
FROM elixir:1.6
RUN apt-get update && apt-get install -y build-essential inotify-tools postgresql-client
RUN mix local.hex --force && mix local.rebar --force
COPY docker/entrypoint.sh /bin/entrypoint
WORKDIR /app
EXPOSE 4000

View file

@ -47,15 +47,14 @@ config :logger, :console, format: "[$level] $message\n", level: :debug
# in production as building large stacktraces may be expensive. # in production as building large stacktraces may be expensive.
config :phoenix, :stacktrace_depth, 20 config :phoenix, :stacktrace_depth, 20
config :eventos, Eventos.Mailer, config :eventos, Eventos.Mailer, adapter: Bamboo.LocalAdapter
adapter: Bamboo.LocalAdapter
# Configure your database # Configure your database
config :eventos, Eventos.Repo, config :eventos, Eventos.Repo,
adapter: Ecto.Adapters.Postgres, adapter: Ecto.Adapters.Postgres,
types: Eventos.PostgresTypes, types: Eventos.PostgresTypes,
username: "elixir", username: System.get_env("POSTGRES_USER") || "elixir",
password: "elixir", password: System.get_env("POSTGRES_PASSWORD") || "elixir",
database: "eventos_dev", database: System.get_env("POSTGRES_DATABASE") || "eventos_dev",
hostname: "localhost", hostname: System.get_env("POSTGRES_HOST") || "localhost",
pool_size: 10 pool_size: 10

38
docker-compose.yml Normal file
View file

@ -0,0 +1,38 @@
version: '3'
services:
postgres:
container_name: eventos_db
restart: unless-stopped
image: mdillon/postgis:10
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_DB: eventos_dev
front:
container_name: eventos_front
restart: unless-stopped
build: ./js
volumes:
- './js:/app/js'
ports:
- "80:8080"
entrypoint: entrypoint
api:
container_name: eventos_api
restart: unless-stopped
build: .
volumes:
- '.:/app'
ports:
- "4000:4000"
depends_on:
- postgres
environment:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DATABASE: eventos_dev
POSTGRES_HOST: postgres
entrypoint: entrypoint

22
docker/entrypoint.sh Executable file
View file

@ -0,0 +1,22 @@
#!/bin/bash
mix deps.get
# Wait for Postgres to become available.
until PGPASSWORD=$POSTGRES_PASSWORD psql -h postgres -U "postgres" -c '\q' 2>/dev/null; do
>&2 echo "Postgres is unavailable - sleeping"
sleep 1
done
echo "\nPostgres is available: continuing with database setup..."
# Potentially Set up the database
mix ecto.create
mix ecto.migrate
echo "\nTesting the installation..."
# "Proove" that install was successful by running the tests
mix test
echo "\n Launching Phoenix web server..."
iex -S mix phx.server

9
js/Dockerfile Normal file
View file

@ -0,0 +1,9 @@
FROM node:10
LABEL maintainer="tcit"
COPY docker/entrypoint.sh /bin/entrypoint
WORKDIR /app/js
EXPOSE 8080

5
js/docker/entrypoint.sh Executable file
View file

@ -0,0 +1,5 @@
#!/bin/bash
npm install
npm rebuild node-sass
npm run serve

View file

@ -9,22 +9,48 @@
enable-resize-watcher enable-resize-watcher
> >
<v-list dense> <v-list dense>
<v-list-tile avatar v-if="$store.state.user"> <v-list-group
<v-list-tile-avatar> value="false"
<img v-if="!getUser().actor.avatar_url" >
class="img-circle elevation-7 mb-1" <v-list-tile avatar v-if="$store.state.actor" slot="activator">
src="https://picsum.photos/125/125/" <v-list-tile-avatar>
> <img v-if="!$store.state.actor.avatar"
<img v-else class="img-circle elevation-7 mb-1"
class="img-circle elevation-7 mb-1" src="https://picsum.photos/125/125/"
:src="getUser().actor.avatar_url" >
> <img v-else
</v-list-tile-avatar> class="img-circle elevation-7 mb-1"
:src="$store.state.actor.avatar"
>
</v-list-tile-avatar>
<v-list-tile-content @click="$router.push({name: 'Account', params: { name: getUser().actor.username }})"> <v-list-tile-content @click="$router.push({name: 'Account', params: { name: $store.state.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>
<img
class="img-circle elevation-7 mb-1"
src="https://picsum.photos/125/125/"
>
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title>Autre identité</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile @click="$router.push({ name: 'Identities' })">
<v-list-tile-action>
<v-icon>group</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>Identities</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list-group>
<template v-for="(item, i) in items" v-if="showMenuItem(item.role)"> <template v-for="(item, i) in items" v-if="showMenuItem(item.role)">
<v-layout <v-layout
row row
@ -160,7 +186,7 @@ export default {
}, },
computed: { computed: {
displayed_name() { displayed_name() {
return this.$store.state.user.actor.display_name === null ? this.$store.state.user.actor.username : this.$store.state.user.actor.display_name return this.$store.state.actor.display_name === null ? this.$store.state.actor.username : this.$store.state.actor.display_name
}, },
} }
}; };

View file

@ -1,4 +1,5 @@
import { API_ORIGIN, API_PATH } from '../api/_entrypoint'; import { API_ORIGIN, API_PATH } from '../api/_entrypoint';
import { LOGIN_USER, LOAD_USER, CHANGE_ACTOR } from '../store/mutation-types';
// URL and endpoint constants // URL and endpoint constants
const LOGIN_URL = `${API_ORIGIN}${API_PATH}/login`; const LOGIN_URL = `${API_ORIGIN}${API_PATH}/login`;
@ -53,7 +54,7 @@ export default {
.then((response) => { .then((response) => {
console.log('We have a new token'); console.log('We have a new token');
this.authenticated = true; this.authenticated = true;
store.commit('LOGIN_USER', response); store.commit(LOGIN_USER, response);
localStorage.setItem('token', response.token); localStorage.setItem('token', response.token);
console.log("Let's try to auth again"); console.log("Let's try to auth again");
successHandler(); successHandler();
@ -104,9 +105,10 @@ export default {
}).then((response) => { }).then((response) => {
this.authenticated = true; this.authenticated = true;
console.log(response); console.log(response);
store.commit('LOAD_USER', response.data); store.commit(LOAD_USER, response.data);
store.commit(CHANGE_ACTOR, response.data.actors[0]);
return successHandler(); return successHandler();
}); });
}, },
// The object to be passed as a header for authenticated requests // The object to be passed as a header for authenticated requests

View file

@ -10,7 +10,7 @@
<v-icon>chevron_left</v-icon> <v-icon>chevron_left</v-icon>
</v-btn> </v-btn>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn icon class="mr-3" v-if="$store.state.user && $store.state.user.actor.id === actor.id"> <v-btn icon class="mr-3" v-if="$store.state.user && $store.state.actor.id === actor.id">
<v-icon>edit</v-icon> <v-icon>edit</v-icon>
</v-btn> </v-btn>
<v-menu bottom left> <v-menu bottom left>
@ -18,10 +18,10 @@
<v-icon>more_vert</v-icon> <v-icon>more_vert</v-icon>
</v-btn> </v-btn>
<v-list> <v-list>
<v-list-tile @click="logoutUser()" v-if="$store.state.user && $store.state.user.actor.id === actor.id"> <v-list-tile @click="logoutUser()" 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>User logout</v-list-tile-title>
</v-list-tile> </v-list-tile>
<v-list-tile @click="deleteAccount()" v-if="$store.state.user && $store.state.user.actor.id === actor.id"> <v-list-tile @click="deleteAccount()" v-if="$store.state.user && $store.state.actor.id === actor.id">
<v-list-tile-title>Delete</v-list-tile-title> <v-list-tile-title>Delete</v-list-tile-title>
</v-list-tile> </v-list-tile>
</v-list> </v-list>
@ -30,13 +30,13 @@
<v-spacer></v-spacer> <v-spacer></v-spacer>
<div class="text-xs-center"> <div class="text-xs-center">
<v-avatar size="125px"> <v-avatar size="125px">
<img v-if="!actor.avatar_url" <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="actor.avatar_url" :src="actor.avatar"
> >
</v-avatar> </v-avatar>
</div> </div>

View file

@ -0,0 +1,132 @@
<template>
<v-layout row>
<v-flex xs12 sm6 offset-sm3>
<v-progress-circular v-if="loading" indeterminate color="primary"></v-progress-circular>
<v-card v-if="!loading">
<v-toolbar dark color="primary">
<v-toolbar-title>Identities</v-toolbar-title>
</v-toolbar>
<v-card-text>
<v-list two-line>
<v-list-tile
v-for="actor in actors"
:key="actor.id"
avatar
@click="$router.push({ name: 'Account', params: { name: actor.username } })"
>
<v-list-tile-action>
<v-icon v-if="$store.state.defaultActor === actor.username" color="pink">star</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title v-text="actor.username"></v-list-tile-title>
<v-list-tile-sub-title v-if="actor.display_name" v-text="actor.display_name"></v-list-tile-sub-title>
</v-list-tile-content>
<v-list-tile-avatar>
<img :src="actor.avatar">
</v-list-tile-avatar>
</v-list-tile>
</v-list>
<v-divider v-if="showForm"></v-divider>
<v-form v-if="showForm">
<v-text-field
label="Username"
required
type="text"
v-model="newActor.preferred_username"
:rules="[rules.required]"
:error="this.state.username.status"
:error-messages="this.state.username.msg"
:suffix="this.host()"
hint="You will be able to create more identities once registered"
persistent-hint
>
</v-text-field>
<v-textarea
name="input-7-1"
label="Profile description"
hint="Will be displayed publicly on your profile"
></v-textarea>
</v-form>
<v-btn
color="pink"
dark
absolute
bottom
right
fab
@click="toggleForm()"
>
<v-icon>{{ showForm ? 'check' : 'add' }}</v-icon>
</v-btn>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</template>
<script>
import eventFetch from "@/api/eventFetch";
import auth from "@/auth";
export default {
name: "Identities",
data() {
return {
actors: [],
newActor: {
preferred_username: "",
summary: ""
},
loading: true,
showForm: false,
rules: {
required: value => !!value || "Required."
},
state: {
username: {
status: false,
msg: []
}
}
};
},
created() {
this.fetchData();
},
methods: {
fetchData() {
eventFetch(`/user`, this.$store)
.then(response => response.json())
.then(response => {
this.actors = response.data.actors;
this.loading = false;
});
},
sendData() {
this.loading = true;
this.showForm = false;
eventFetch(`/actors`, this.$store, {
method: "POST",
body: JSON.stringify({ actor: this.newActor })
})
.then(response => response.json())
.then(response => {
this.actors.push(response.data);
this.loading = false;
});
},
toggleForm() {
if (this.showForm === true) {
this.sendData();
} else {
this.showForm = true;
}
},
host() {
return `@${window.location.host}`;
}
}
};
</script>

View file

@ -60,6 +60,7 @@
<script> <script>
import { LOGIN_USER } from '@/store/mutation-types';
import auth from '@/auth/index'; import auth from '@/auth/index';
import Gravatar from 'vue-gravatar'; import Gravatar from 'vue-gravatar';
import RegisterAvatar from './RegisterAvatar'; import RegisterAvatar from './RegisterAvatar';
@ -119,7 +120,7 @@
loginAction(e) { loginAction(e) {
e.preventDefault(); e.preventDefault();
auth.login(JSON.stringify(this.credentials), (data) => { auth.login(JSON.stringify(this.credentials), (data) => {
this.$store.commit('LOGIN_USER', data.user); this.$store.commit(LOGIN_USER, data.user);
this.$router.push({ name: 'Home' }); this.$router.push({ name: 'Home' });
}, (error) => { }, (error) => {
Promise.resolve(error).then((errorMsg) => { Promise.resolve(error).then((errorMsg) => {

View file

@ -1,17 +1,18 @@
<template> <template>
<b-container> <v-container>
<h1 v-if="loading">{{ $t('registration.validation.process') }}</h1> <h1 v-if="loading">{{ $t('registration.validation.process') }}</h1>
<div v-else> <div v-else>
<div v-if="failed"> <div v-if="failed">
<b-alert show variant="danger">{{ $t('registration.success.validation_failure') }}</b-alert> <v-alert :value="true" variant="danger">Error while validating account</v-alert>
</div> </div>
<h1 v-else>{{ $t('registration.validation.finished') }}</h1> <h1 v-else>{{ $t('registration.validation.finished') }}</h1>
</div> </div>
</b-container> </v-container>
</template> </template>
<script> <script>
import fetchStory from '@/api/eventFetch'; import fetchStory from '@/api/eventFetch';
import { LOGIN_USER } from '@/store/mutation-types';
export default { export default {
name: 'Validate', name: 'Validate',
@ -36,7 +37,7 @@ export default {
this.loading = false; this.loading = false;
localStorage.setItem('token', data.token); localStorage.setItem('token', data.token);
localStorage.setItem('refresh_token', data.refresh_token); localStorage.setItem('refresh_token', data.refresh_token);
this.$store.commit('LOGIN_USER', data.account); this.$store.commit(LOGIN_USER, data.account);
this.$snotify.success(this.$t('registration.success.login', { username: data.account.username })); this.$snotify.success(this.$t('registration.success.login', { username: data.account.username }));
this.$router.push({ name: 'Home' }); this.$router.push({ name: 'Home' });
}).catch((err) => { }).catch((err) => {

View file

@ -120,8 +120,8 @@
// }); // });
// }); // });
this.event.category_id = this.event.category; this.event.category_id = this.event.category;
this.event.organizer_actor_id = this.$store.state.user.actor.id; this.event.organizer_actor_id = this.$store.state.actor.id;
this.event.participants = [this.$store.state.user.actor.id]; this.event.participants = [this.$store.state.actor.id];
// this.event.price = parseFloat(this.event.price); // this.event.price = parseFloat(this.event.price);
if (this.id === undefined) { if (this.id === undefined) {

View file

@ -16,7 +16,7 @@
<v-icon>chevron_left</v-icon> <v-icon>chevron_left</v-icon>
</v-btn> </v-btn>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn icon class="mr-3 white--text" v-if="event.organizer.id === $store.state.user.actor.id" :to="{ name: 'EditEvent', params: {id: event.id}}"> <v-btn icon class="mr-3 white--text" v-if="actorIsOrganizer()" :to="{ name: 'EditEvent', params: {id: event.id}}">
<v-icon>edit</v-icon> <v-icon>edit</v-icon>
</v-btn> </v-btn>
<v-menu bottom left> <v-menu bottom left>
@ -27,7 +27,7 @@
<v-list-tile @click="downloadIcsEvent()"> <v-list-tile @click="downloadIcsEvent()">
<v-list-tile-title>Download</v-list-tile-title> <v-list-tile-title>Download</v-list-tile-title>
</v-list-tile> </v-list-tile>
<v-list-tile @click="deleteEvent()" v-if="$store.state.user.actor.id === event.organizer.id"> <v-list-tile @click="deleteEvent()" v-if="actorIsOrganizer()">
<v-list-tile-title>Delete</v-list-tile-title> <v-list-tile-title>Delete</v-list-tile-title>
</v-list-tile> </v-list-tile>
</v-list> </v-list>
@ -217,10 +217,10 @@
}) })
}, },
actorIsParticipant() { actorIsParticipant() {
return this.event.participants.map(participant => participant.id).includes(this.$store.state.user.actor.id) || this.actorIsOrganizer(); return this.$store.state.actor && this.event.participants.map(participant => participant.id).includes(this.$store.state.actor.id) || this.actorIsOrganizer();
}, },
actorIsOrganizer() { actorIsOrganizer() {
return this.$store.state.user.actor.id === this.event.organizer.id; return this.$store.state.actor && this.$store.state.actor.id === this.event.organizer.id;
} }
}, },
props: { props: {

View file

@ -11,7 +11,7 @@
<v-icon>chevron_left</v-icon> <v-icon>chevron_left</v-icon>
</v-btn> </v-btn>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<!--<v-btn icon class="mr-3" v-if="$store.state.user && $store.state.user.actor.id === actor.id">--> <!--<v-btn icon class="mr-3" v-if="$store.state.user && $store.state.actor.id === actor.id">-->
<!--<v-icon>edit</v-icon>--> <!--<v-icon>edit</v-icon>-->
<!--</v-btn>--> <!--</v-btn>-->
<v-btn icon> <v-btn icon>

View file

@ -16,11 +16,11 @@
</v-layout> </v-layout>
</v-container> </v-container>
</v-jumbotron> </v-jumbotron>
<v-layout> <v-layout v-else>
<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.user.actor.username }}</h1> <h1>Welcome back {{ $store.state.actor.username }}</h1>
</v-flex> </v-flex>
<v-flex xs12 sm6> <v-flex xs12 sm6>
<v-layout align-center> <v-layout align-center>
@ -51,7 +51,7 @@
</v-card-media> </v-card-media>
<v-card-title primary-title> <v-card-title primary-title>
<div> <div>
<span class="grey--text">{{ event.begins_on | formatDate }}</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.organizer.username } }">
<v-avatar size="25px"> <v-avatar size="25px">
<img class="img-circle elevation-7 mb-1" <img class="img-circle elevation-7 mb-1"
@ -100,8 +100,8 @@ export default {
this.fetchData(); this.fetchData();
}, },
computed: { computed: {
displayed_name: function() { displayed_name() {
return this.$store.state.user.actor.display_name === null ? this.$store.state.user.actor.username : this.$store.state.user.actor.display_name return this.$store.state.actor.display_name === null ? this.$store.state.actor.username : this.$store.state.actor.display_name
}, },
}, },
methods: { methods: {

View file

@ -75,8 +75,7 @@
</v-card-actions> </v-card-actions>
</v-card> </v-card>
</v-menu> </v-menu>
<v-btn flat @click="$router.push({name: 'Account', params: { name: getUser().actor.username }})" v-if="$store.state.user">{{ this.displayed_name }}</v-btn> <v-btn v-if="!$store.state.user" :to="{ name: 'Login' }">Se connecter</v-btn>
<v-btn v-else :to="{ name: 'Login' }">Se connecter</v-btn>
</v-toolbar> </v-toolbar>
</template> </template>
@ -123,7 +122,10 @@
}, },
computed: { computed: {
displayed_name: function() { displayed_name: function() {
return this.$store.state.user.actor.display_name === null ? this.$store.state.user.actor.username : this.$store.state.user.actor.display_name console.log('displayed name', this.$store.state.actor);
if (this.$store.state.actor) {
return this.$store.state.actor.display_name === null ? this.$store.state.actor.username : this.$store.state.actor.display_name;
}
}, },
}, },
methods: { methods: {

View file

@ -3,36 +3,21 @@
import Vue from 'vue'; import Vue from 'vue';
// import * as VueGoogleMaps from 'vue2-google-maps'; // import * as VueGoogleMaps from 'vue2-google-maps';
import VueMarkdown from 'vue-markdown'; import VueMarkdown from 'vue-markdown';
import VuetifyGoogleAutocomplete from 'vuetify-google-autocomplete';
import Vuetify from 'vuetify'; import Vuetify from 'vuetify';
import Vuex from 'vuex';
import moment from 'moment'; import moment from 'moment';
import VuexI18n from 'vuex-i18n'; import VuexI18n from 'vuex-i18n';
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'; import App from './App.vue';
import router from '@/router'; import router from './router';
import storeData from '@/store/index'; import store from './store';
import translations from '@/i18n/index'; import translations from './i18n';
import auth from '@/auth'; import auth from './auth';
Vue.config.productionTip = false; Vue.config.productionTip = false;
Vue.use(VuetifyGoogleAutocomplete, {
apiKey: 'AIzaSyBF37pw38j0giICt73TCAPNogc07Upe_Q4', // Can also be an object. E.g, for Google Maps Premium API, pass `{ client: <YOUR-CLIENT-ID> }`
});
/*Vue.use(VueGoogleMaps, {
load: {
key: 'AIzaSyBF37pw38j0giICt73TCAPNogc07Upe_Q4',
libraries: 'places',
installComponents: false,
},
});*/
Vue.use(VueMarkdown); Vue.use(VueMarkdown);
Vue.use(Vuetify); Vue.use(Vuetify);
Vue.use(Vuex);
let language = window.navigator.userLanguage || window.navigator.language; let language = window.navigator.userLanguage || window.navigator.language;
moment.locale(language); moment.locale(language);
@ -43,8 +28,6 @@ if (!(language in translations)) {
[language] = language.split('-', 1); [language] = language.split('-', 1);
} }
const store = new Vuex.Store(storeData);
Vue.use(VuexI18n.plugin, store); Vue.use(VuexI18n.plugin, store);
Object.entries(translations).forEach((key) => { Object.entries(translations).forEach((key) => {
@ -55,17 +38,21 @@ Vue.i18n.set(language);
Vue.i18n.fallback('en'); Vue.i18n.fallback('en');
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiredAuth) && store.state.user === undefined || store.state.user == null) { if (to.matched.some(record => record.meta.requiredAuth) && !store.state.user) {
next({ next({
name: 'Login', name: 'Login',
query: { redirect: to.fullPath } query: { redirect: to.fullPath },
}); });
} else { } else {
next(); next();
} }
}); });
auth.getUser(store, () => {}, () => {}); auth.getUser(store, () => {}, (error) => {
console.warn(error);
});
console.log('store', store);
/* eslint-disable no-new */ /* eslint-disable no-new */
new Vue({ new Vue({

View file

@ -18,6 +18,7 @@ import Account from '@/components/Account/Account';
import CreateGroup from '@/components/Group/Create'; import CreateGroup from '@/components/Group/Create';
import Group from '@/components/Group/Group'; import Group from '@/components/Group/Group';
import GroupList from '@/components/Group/GroupList'; import GroupList from '@/components/Group/GroupList';
import Identities from '../components/Account/Identities.vue';
Vue.use(Router); Vue.use(Router);
@ -110,6 +111,12 @@ const router = new Router({
props: true, props: true,
meta: { requiredAuth: false }, meta: { requiredAuth: false },
}, },
{
path: '/identities',
name: 'Identities',
component: Identities,
meta: { requiredAuth: true },
},
{ {
path: '/groups', path: '/groups',
name: 'GroupList', name: 'GroupList',

View file

@ -1,8 +1,12 @@
import { LOGIN_USER, LOGOUT_USER, LOAD_USER } from './mutation-types'; import Vue from 'vue';
import Vuex from 'vuex';
import { LOGIN_USER, LOGOUT_USER, LOAD_USER, CHANGE_ACTOR } from './mutation-types';
const state = { const state = {
isLogged: !!localStorage.getItem('token'), isLogged: !!localStorage.getItem('token'),
user: false, user: false,
actor: false,
defaultActor: localStorage.getItem('defaultActor') || null,
}; };
/* eslint-disable */ /* eslint-disable */
@ -20,7 +24,21 @@ const mutations = {
state.isLogged = false; state.isLogged = false;
state.user = null; state.user = null;
}, },
[CHANGE_ACTOR](state, actor) {
state.actor = actor;
state.defaultActor = actor.username;
}
}; };
/* eslint-enable */ /* eslint-enable */
export default { state, mutations }; 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;

View file

@ -1,3 +1,4 @@
export const LOGIN_USER = 'LOGIN_USER'; export const LOGIN_USER = 'LOGIN_USER';
export const LOAD_USER = 'LOAD_USER'; export const LOAD_USER = 'LOAD_USER';
export const LOGOUT_USER = 'LOGOUT_USER'; export const LOGOUT_USER = 'LOGOUT_USER';
export const CHANGE_ACTOR = 'CHANGE_ACTOR';

View file

@ -68,7 +68,7 @@ defmodule Eventos.Actors.Actor do
many_to_many :followers, Actor, join_through: Follower many_to_many :followers, Actor, join_through: Follower
has_many :organized_events, Event, [foreign_key: :organizer_actor_id] has_many :organized_events, Event, [foreign_key: :organizer_actor_id]
many_to_many :memberships, Actor, join_through: Member many_to_many :memberships, Actor, join_through: Member
has_one :user, User belongs_to :user, User
timestamps() timestamps()
end end
@ -76,14 +76,15 @@ defmodule Eventos.Actors.Actor do
@doc false @doc false
def changeset(%Actor{} = actor, attrs) do def changeset(%Actor{} = actor, attrs) do
actor actor
|> Ecto.Changeset.cast(attrs, [:url, :outbox_url, :inbox_url, :shared_inbox_url, :following_url, :followers_url, :type, :name, :domain, :summary, :preferred_username, :keys, :manually_approves_followers, :suspended, :avatar_url, :banner_url]) |> Ecto.Changeset.cast(attrs, [:url, :outbox_url, :inbox_url, :shared_inbox_url, :following_url, :followers_url, :type, :name, :domain, :summary, :preferred_username, :keys, :manually_approves_followers, :suspended, :avatar_url, :banner_url, :user_id])
|> put_change(:url, "#{EventosWeb.Endpoint.url()}/@#{attrs["prefered_username"]}")
|> validate_required([:preferred_username, :keys, :suspended, :url]) |> validate_required([:preferred_username, :keys, :suspended, :url])
|> unique_constraint(:prefered_username, name: :actors_preferred_username_domain_index) |> unique_constraint(:prefered_username, name: :actors_preferred_username_domain_index)
end end
def registration_changeset(%Actor{} = actor, attrs) do def registration_changeset(%Actor{} = actor, attrs) do
actor actor
|> Ecto.Changeset.cast(attrs, [:preferred_username, :domain, :name, :summary, :keys, :keys, :suspended, :url, :type, :avatar_url]) |> Ecto.Changeset.cast(attrs, [:preferred_username, :domain, :name, :summary, :keys, :keys, :suspended, :url, :type, :avatar_url, :user_id])
|> unique_constraint(:preferred_username, name: :actors_preferred_username_domain_index) |> unique_constraint(:preferred_username, name: :actors_preferred_username_domain_index)
|> put_change(:url, "#{EventosWeb.Endpoint.url()}/@#{attrs["prefered_username"]}") |> put_change(:url, "#{EventosWeb.Endpoint.url()}/@#{attrs["prefered_username"]}")
|> validate_required([:preferred_username, :keys, :suspended, :url, :type]) |> validate_required([:preferred_username, :keys, :suspended, :url, :type])

View file

@ -177,7 +177,7 @@ defmodule Eventos.Actors do
def list_users_with_actors do def list_users_with_actors do
users = Repo.all(User) users = Repo.all(User)
Repo.preload(users, :actor) Repo.preload(users, :actors)
end end
defp blank?(""), do: nil defp blank?(""), do: nil
@ -222,7 +222,7 @@ defmodule Eventos.Actors do
def get_user_with_actor!(id) do def get_user_with_actor!(id) do
user = Repo.get!(User, id) user = Repo.get!(User, id)
Repo.preload(user, :actor) Repo.preload(user, :actors)
end end
def get_actor_by_url(url) do def get_actor_by_url(url) do
@ -312,7 +312,7 @@ defmodule Eventos.Actors do
Get an user by email Get an user by email
""" """
def find_by_email(email) do def find_by_email(email) do
case Repo.preload(Repo.get_by(User, email: email), :actor) do case Repo.preload(Repo.get_by(User, email: email), :actors) do
nil -> nil ->
{:error, nil} {:error, nil}
user -> user ->

View file

@ -8,8 +8,9 @@ defmodule Eventos.Actors.Service.Activation do
@doc false @doc false
def check_confirmation_token(token) when is_binary(token) do def check_confirmation_token(token) when is_binary(token) do
with %User{} = user <- Repo.get_by(User, confirmation_token: token) do with %User{} = user <- Repo.get_by(User, confirmation_token: token),
Actors.update_user(user, %{"confirmed_at" => DateTime.utc_now(), "confirmation_sent_at" => nil, "confirmation_token" => nil}) {:ok, %User{} = user} <- Actors.update_user(user, %{"confirmed_at" => DateTime.utc_now(), "confirmation_sent_at" => nil, "confirmation_token" => nil}) do
{:ok, Repo.preload(user, :actors)}
else else
_err -> _err ->
{:error, "Invalid token"} {:error, "Invalid token"}

View file

@ -11,7 +11,7 @@ defmodule Eventos.Actors.User do
field :password_hash, :string field :password_hash, :string
field :password, :string, virtual: true field :password, :string, virtual: true
field :role, :integer, default: 0 field :role, :integer, default: 0
belongs_to :actor, Actor has_many :actors, Actor
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
@ -24,7 +24,7 @@ defmodule Eventos.Actors.User do
@doc false @doc false
def changeset(%User{} = user, attrs) do def changeset(%User{} = user, attrs) do
user user
|> cast(attrs, [:email, :role, :password_hash, :actor_id, :confirmed_at, :confirmation_sent_at, :confirmation_token, :reset_password_sent_at, :reset_password_token]) |> cast(attrs, [:email, :role, :password_hash, :confirmed_at, :confirmation_sent_at, :confirmation_token, :reset_password_sent_at, :reset_password_token])
|> validate_required([:email]) |> validate_required([:email])
|> unique_constraint(:email, [message: "registration.error.email_already_used"]) |> unique_constraint(:email, [message: "registration.error.email_already_used"])
|> validate_format(:email, ~r/@/) |> validate_format(:email, ~r/@/)

View file

@ -5,7 +5,7 @@ defmodule EventosWeb.ActorController do
use EventosWeb, :controller use EventosWeb, :controller
alias Eventos.Actors alias Eventos.Actors
alias Eventos.Actors.Actor alias Eventos.Actors.{Actor, User}
alias Eventos.Service.ActivityPub alias Eventos.Service.ActivityPub
action_fallback EventosWeb.FallbackController action_fallback EventosWeb.FallbackController
@ -15,6 +15,26 @@ defmodule EventosWeb.ActorController do
render(conn, "index.json", actors: actors) render(conn, "index.json", actors: actors)
end 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 def show(conn, %{"name" => name}) do
actor = Actors.get_actor_by_name_with_everything(name) actor = Actors.get_actor_by_name_with_everything(name)
render(conn, "show.json", actor: actor) render(conn, "show.json", actor: actor)

View file

@ -20,7 +20,7 @@ defmodule EventosWeb.GroupController do
conn conn
|> put_status(:created) |> put_status(:created)
|> put_resp_header("location", actor_path(conn, :show, group)) |> put_resp_header("location", actor_path(conn, :show, group))
|> render(EventosWeb.ActorView, "acccount_basic.json", actor: group) |> render(EventosWeb.ActorView, "actor_basic.json", actor: group)
end end
end end

View file

@ -92,7 +92,7 @@ defmodule EventosWeb.UserController do
def show_current_actor(conn, _params) do def show_current_actor(conn, _params) do
user = conn user = conn
|> Guardian.Plug.current_resource() |> Guardian.Plug.current_resource()
|> Repo.preload(:actor) |> Repo.preload(:actors)
render(conn, "show_simple.json", user: user) render(conn, "show_simple.json", user: user)
end end

View file

@ -76,6 +76,7 @@ defmodule EventosWeb.Router do
get "/user", UserController, :show_current_actor get "/user", UserController, :show_current_actor
post "/sign-out", UserSessionController, :sign_out post "/sign-out", UserSessionController, :sign_out
resources "/users", UserController, except: [:new, :edit, :show] resources "/users", UserController, except: [:new, :edit, :show]
post "/actors", ActorController, :create
patch "/actors/:name", ActorController, :update patch "/actors/:name", ActorController, :update
post "/events", EventController, :create post "/events", EventController, :create
patch "/events/:uuid", EventController, :update patch "/events/:uuid", EventController, :update

View file

@ -7,7 +7,7 @@ defmodule EventosWeb.ActorView do
alias Eventos.Actors alias Eventos.Actors
def render("index.json", %{actors: actors}) do def render("index.json", %{actors: actors}) do
%{data: render_many(actors, ActorView, "acccount_basic.json")} %{data: render_many(actors, ActorView, "actor_basic.json")}
end end
def render("show.json", %{actor: actor}) do def render("show.json", %{actor: actor}) do
@ -18,7 +18,7 @@ defmodule EventosWeb.ActorView do
%{data: render_one(actor, ActorView, "actor_basic.json")} %{data: render_one(actor, ActorView, "actor_basic.json")}
end end
def render("acccount_basic.json", %{actor: actor}) do def render("actor_basic.json", %{actor: actor}) do
%{id: actor.id, %{id: actor.id,
username: actor.preferred_username, username: actor.preferred_username,
domain: actor.domain, domain: actor.domain,

View file

@ -53,8 +53,8 @@ defmodule EventosWeb.EventView do
begins_on: event.begins_on, begins_on: event.begins_on,
ends_on: event.ends_on, ends_on: event.ends_on,
uuid: event.uuid, uuid: event.uuid,
organizer: render_one(event.organizer_actor, ActorView, "acccount_basic.json"), organizer: render_one(event.organizer_actor, ActorView, "actor_basic.json"),
participants: render_many(event.participants, ActorView, "acccount_basic.json"), participants: render_many(event.participants, ActorView, "actor_basic.json"),
physical_address: render_one(event.physical_address, AddressView, "address.json"), physical_address: render_one(event.physical_address, AddressView, "address.json"),
type: "Event", type: "Event",
address_type: event.address_type, address_type: event.address_type,

View file

@ -32,7 +32,7 @@ defmodule EventosWeb.GroupView do
description: group.description, description: group.description,
suspended: group.suspended, suspended: group.suspended,
url: group.url, url: group.url,
members: render_many(group.members, ActorView, "acccount_basic.json"), members: render_many(group.members, ActorView, "actor_basic.json"),
events: render_many(group.organized_events, EventView, "event_simple.json") events: render_many(group.organized_events, EventView, "event_simple.json")
} }
end end

View file

@ -16,8 +16,8 @@ defmodule EventosWeb.MemberView do
def render("member.json", %{member: member}) do def render("member.json", %{member: member}) do
%{ %{
role: member.role, role: member.role,
actor: render_one(member.actor, ActorView, "acccount_basic.json"), actor: render_one(member.actor, ActorView, "actor_basic.json"),
group: render_one(member.parent, ActorView, "acccount_basic.json") group: render_one(member.parent, ActorView, "actor_basic.json")
} }
end end
end end

View file

@ -9,7 +9,7 @@ defmodule EventosWeb.SearchView do
%{ %{
data: %{ data: %{
events: render_many(events, EventView, "event_simple.json"), events: render_many(events, EventView, "event_simple.json"),
actors: render_many(actors, ActorView, "acccount_basic.json"), actors: render_many(actors, ActorView, "actor_basic.json"),
} }
} }
end end

View file

@ -28,14 +28,14 @@ defmodule EventosWeb.UserView do
def render("user_simple.json", %{user: user}) do def render("user_simple.json", %{user: user}) do
%{id: user.id, %{id: user.id,
role: user.role, role: user.role,
actor: render_one(user.actor, ActorView, "acccount_basic.json") actors: render_many(user.actors, ActorView, "actor_basic.json")
} }
end end
def render("user.json", %{user: user}) do def render("user.json", %{user: user}) do
%{id: user.id, %{id: user.id,
role: user.role, role: user.role,
actor: render_one(user.actor, ActorView, "actor.json") actors: render_many(user.actors, ActorView, "actor.json")
} }
end end

19
mix.exs
View file

@ -6,13 +6,18 @@ defmodule Eventos.Mixfile do
app: :eventos, app: :eventos,
version: "0.0.1", version: "0.0.1",
elixir: "~> 1.4", elixir: "~> 1.4",
elixirc_paths: elixirc_paths(Mix.env), elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers, compilers: [:phoenix, :gettext] ++ Mix.compilers(),
start_permanent: Mix.env == :prod, start_permanent: Mix.env() == :prod,
aliases: aliases(), aliases: aliases(),
deps: deps(), deps: deps(),
test_coverage: [tool: ExCoveralls], test_coverage: [tool: ExCoveralls],
preferred_cli_env: ["coveralls": :test, "coveralls.detail": :test, "coveralls.post": :test, "coveralls.html": :test], preferred_cli_env: [
coveralls: :test,
"coveralls.detail": :test,
"coveralls.post": :test,
"coveralls.html": :test
],
name: "Eventos", name: "Eventos",
source_url: "https://framagit.org/tcit/eventos", source_url: "https://framagit.org/tcit/eventos",
homepage_url: "https://framagit.org/tcit/eventos", homepage_url: "https://framagit.org/tcit/eventos",
@ -32,7 +37,7 @@ defmodule Eventos.Mixfile do
# Specifies which paths to compile per environment. # Specifies which paths to compile per environment.
defp elixirc_paths(:test), do: ["lib", "test/support"] defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"] defp elixirc_paths(_), do: ["lib"]
# Specifies your project dependencies. # Specifies your project dependencies.
# #
@ -72,7 +77,7 @@ defmodule Eventos.Mixfile do
{:geolix, "~> 0.16"}, {:geolix, "~> 0.16"},
# Dev and test dependencies # Dev and test dependencies
{:phoenix_live_reload, "~> 1.0", only: :dev}, {:phoenix_live_reload, "~> 1.0", only: :dev},
{:ex_machina, "~> 2.1", only: :test}, {:ex_machina, "~> 2.2", only: [:dev, :test]},
{:credo, "~> 0.8", only: [:dev, :test], runtime: false}, {:credo, "~> 0.8", only: [:dev, :test], runtime: false},
{:excoveralls, "~> 0.8", only: :test}, {:excoveralls, "~> 0.8", only: :test},
{:ex_doc, "~> 0.16", only: :dev, runtime: false}, {:ex_doc, "~> 0.16", only: :dev, runtime: false},
@ -91,7 +96,7 @@ defmodule Eventos.Mixfile do
[ [
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
"ecto.reset": ["ecto.drop", "ecto.setup"], "ecto.reset": ["ecto.drop", "ecto.setup"],
"test": ["ecto.create --quiet", "ecto.migrate", "test"] test: ["ecto.create --quiet", "ecto.migrate", "test"]
] ]
end end
end end

View file

@ -2,15 +2,18 @@ defmodule Eventos.Repo.Migrations.Prerequites do
use Ecto.Migration use Ecto.Migration
def up do def up do
execute """ execute("""
CREATE TYPE datetimetz AS ( CREATE TYPE datetimetz AS (
dt timestamptz, dt timestamptz,
tz varchar tz varchar
); );
""" """)
execute("CREATE EXTENSION IF NOT EXISTS postgis")
end end
def down do def down do
execute "DROP TYPE IF EXISTS datetimetz;" execute("DROP TYPE IF EXISTS datetimetz;")
execute("DROP EXTENSION IF EXISTS postgis")
end end
end end

View file

@ -0,0 +1,23 @@
defmodule Eventos.Repo.Migrations.AllowMultipleAccountsForUser do
use Ecto.Migration
def up do
alter table(:actors) do
add :user_id, references(:users, on_delete: :delete_all), null: true
end
alter table(:users) do
remove :actor_id
end
end
def down do
alter table(:users) do
add :actor_id, references(:actors, on_delete: :delete_all), null: false
end
alter table(:actors) do
remove :user_id
end
end
end

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,2 +0,0 @@
!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var r=window.webpackJsonp;window.webpackJsonp=function(t,c,a){for(var u,i,f,s=0,l=[];s<t.length;s++)i=t[s],o[i]&&l.push(o[i][0]),o[i]=0;for(u in c)Object.prototype.hasOwnProperty.call(c,u)&&(e[u]=c[u]);for(r&&r(t,c,a);l.length;)l.shift()();if(a)for(s=0;s<a.length;s++)f=n(n.s=a[s]);return f};var t={},o={2:0};n.e=function(e){function r(){u.onerror=u.onload=null,clearTimeout(i);var n=o[e];0!==n&&(n&&n[1](new Error("Loading chunk "+e+" failed.")),o[e]=void 0)}var t=o[e];if(0===t)return new Promise(function(e){e()});if(t)return t[2];var c=new Promise(function(n,r){t=o[e]=[n,r]});t[2]=c;var a=document.getElementsByTagName("head")[0],u=document.createElement("script");u.type="text/javascript",u.charset="utf-8",u.async=!0,u.timeout=12e4,n.nc&&u.setAttribute("nonce",n.nc),u.src=n.p+"js/"+e+"."+{0:"0d63a19c6680451dd336",1:"29c4f33994925affb616"}[e]+".js";var i=setTimeout(r,12e4);return u.onerror=u.onload=r,a.appendChild(u),c},n.m=e,n.c=t,n.d=function(e,r,t){n.o(e,r)||Object.defineProperty(e,r,{configurable:!1,enumerable:!0,get:t})},n.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(r,"a",r),r},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="/",n.oe=function(e){throw console.error(e),e}}([]);
//# sourceMappingURL=manifest.881ff1dba0c9e5d0130f.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -37,7 +37,6 @@ defmodule Eventos.ActorsTest do
assert actor.domain == "some domain" assert actor.domain == "some domain"
assert actor.keys == "some keypair" assert actor.keys == "some keypair"
assert actor.suspended assert actor.suspended
assert actor.url == "some url"
assert actor.preferred_username == "some username" assert actor.preferred_username == "some username"
end end
@ -54,7 +53,6 @@ defmodule Eventos.ActorsTest do
assert actor.domain == "some updated domain" assert actor.domain == "some updated domain"
assert actor.keys == "some updated keys" assert actor.keys == "some updated keys"
refute actor.suspended refute actor.suspended
assert actor.url == "some updated url"
assert actor.preferred_username == "some updated username" assert actor.preferred_username == "some updated username"
end end

View file

@ -6,15 +6,29 @@ defmodule EventosWeb.ActorControllerTest do
alias Eventos.Actors alias Eventos.Actors
setup %{conn: conn} do setup %{conn: conn} do
actor = insert(:actor) user = insert(:user)
user = insert(:user, actor: actor) actor = insert(:actor, user: user)
{:ok, conn: conn, user: user} {:ok, conn: conn, user: user, actor: actor}
end end
key = :public_key.generate_key({:rsa, 2048, 65_537})
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
pem = [entry] |> :public_key.pem_encode() |> String.trim_trailing()
@create_attrs %{preferred_username: "otheridentity", summary: "This is my other identity", domain: nil, keys: pem, user: nil}
describe "index" do describe "index" do
test "lists all actors", %{conn: conn, user: user} do test "lists all actors", %{conn: conn, user: user, actor: actor} do
conn = get conn, actor_path(conn, :index) conn = get conn, actor_path(conn, :index)
assert hd(json_response(conn, 200)["data"])["username"] == user.actor.preferred_username assert hd(json_response(conn, 200)["data"])["username"] == actor.preferred_username
end
end
describe "create actor" do
test "from an existing user", %{conn: conn, user: user} do
conn = auth_conn(conn, user)
conn = post conn, actor_path(conn, :create), actor: @create_attrs
assert json_response(conn, 201)["data"]["username"] == @create_attrs.preferred_username
end end
end end
@ -41,9 +55,4 @@ defmodule EventosWeb.ActorControllerTest do
# assert response(conn, 200) # assert response(conn, 200)
# end # end
# end # end
defp create_actor(_) do
actor = insert(:actor)
{:ok, actor: actor}
end
end end

View file

@ -16,8 +16,8 @@ defmodule EventosWeb.AddressControllerTest do
end end
setup %{conn: conn} do setup %{conn: conn} do
actor = insert(:actor) user = insert(:user)
user = insert(:user, actor: actor) actor = insert(:actor, user: user)
{:ok, conn: conn, user: user} {:ok, conn: conn, user: user}
end end

View file

@ -11,8 +11,8 @@ defmodule EventosWeb.BotControllerTest do
@invalid_attrs %{source: nil, type: nil, name: nil} @invalid_attrs %{source: nil, type: nil, name: nil}
setup %{conn: conn} do setup %{conn: conn} do
actor = insert(:actor) user = insert(:user)
user = insert(:user, actor: actor) actor = insert(:actor, user: user)
{:ok, conn: put_req_header(conn, "accept", "application/json"), user: user} {:ok, conn: put_req_header(conn, "accept", "application/json"), user: user}
end end

View file

@ -16,8 +16,8 @@ defmodule EventosWeb.CategoryControllerTest do
end end
setup %{conn: conn} do setup %{conn: conn} do
actor = insert(:actor) user = insert(:user)
user = insert(:user, actor: actor) actor = insert(:actor, user: user)
{:ok, conn: conn, user: user} {:ok, conn: conn, user: user}
end end

View file

@ -11,15 +11,14 @@ defmodule EventosWeb.CommentControllerTest do
@invalid_attrs %{text: nil, url: nil} @invalid_attrs %{text: nil, url: nil}
setup %{conn: conn} do setup %{conn: conn} do
actor = insert(:actor) user = insert(:user)
user = insert(:user, actor: actor) actor = insert(:actor, user: user)
{:ok, conn: put_req_header(conn, "accept", "application/json"), user: user} {:ok, conn: put_req_header(conn, "accept", "application/json"), user: user, actor: actor}
end end
describe "create comment" do describe "create comment" do
test "renders comment when data is valid", %{conn: conn, user: user} do test "renders comment when data is valid", %{conn: conn, user: user, actor: actor} do
conn = auth_conn(conn, user) conn = auth_conn(conn, user)
actor = insert(:actor)
attrs = Map.merge(@create_attrs, %{actor_id: actor.id}) attrs = Map.merge(@create_attrs, %{actor_id: actor.id})
conn = post conn, comment_path(conn, :create), comment: attrs conn = post conn, comment_path(conn, :create), comment: attrs
assert %{"uuid" => uuid, "id" => id} = json_response(conn, 201)["data"] assert %{"uuid" => uuid, "id" => id} = json_response(conn, 201)["data"]
@ -43,9 +42,8 @@ defmodule EventosWeb.CommentControllerTest do
describe "update comment" do describe "update comment" do
setup [:create_comment] setup [:create_comment]
test "renders comment when data is valid", %{conn: conn, comment: %Comment{id: id, uuid: uuid} = comment, user: user} do test "renders comment when data is valid", %{conn: conn, comment: %Comment{id: id, uuid: uuid} = comment, user: user, actor: actor} do
conn = auth_conn(conn, user) conn = auth_conn(conn, user)
actor = insert(:actor)
attrs = Map.merge(@update_attrs, %{actor_id: actor.id}) attrs = Map.merge(@update_attrs, %{actor_id: actor.id})
conn = put conn, comment_path(conn, :update, uuid), comment: attrs conn = put conn, comment_path(conn, :update, uuid), comment: attrs
assert %{"uuid" => uuid, "id" => id} = json_response(conn, 200)["data"] assert %{"uuid" => uuid, "id" => id} = json_response(conn, 200)["data"]

View file

@ -21,9 +21,9 @@ defmodule EventosWeb.EventControllerTest do
end end
setup %{conn: conn} do setup %{conn: conn} do
actor = insert(:actor) user = insert(:user)
user = insert(:user, actor: actor) actor = insert(:actor, user: user)
{:ok, conn: conn, user: user} {:ok, conn: conn, user: user, actor: actor}
end end
describe "index" do describe "index" do
@ -34,8 +34,8 @@ defmodule EventosWeb.EventControllerTest do
end end
describe "create event" do describe "create event" do
test "renders event when data is valid", %{conn: conn, user: user} do test "renders event when data is valid", %{conn: conn, user: user, actor: actor} do
attrs = Map.put(@create_attrs, :organizer_actor_id, user.actor.id) attrs = Map.put(@create_attrs, :organizer_actor_id, actor.id)
attrs = Map.put(attrs, "physical_address", @create_address_attrs) attrs = Map.put(attrs, "physical_address", @create_address_attrs)
category = insert(:category) category = insert(:category)
@ -55,9 +55,9 @@ defmodule EventosWeb.EventControllerTest do
} = json_response(conn, 200)["data"] } = json_response(conn, 200)["data"]
end end
test "renders errors when data is invalid", %{conn: conn, user: user} do test "renders errors when data is invalid", %{conn: conn, user: user, actor: actor} do
conn = auth_conn(conn, user) conn = auth_conn(conn, user)
attrs = Map.put(@invalid_attrs, :organizer_actor_id, user.actor.id) attrs = Map.put(@invalid_attrs, :organizer_actor_id, actor.id)
attrs = Map.put(attrs, :address, @create_address_attrs) attrs = Map.put(attrs, :address, @create_address_attrs)
conn = post conn, event_path(conn, :create), event: attrs conn = post conn, event_path(conn, :create), event: attrs
assert json_response(conn, 422)["errors"] != %{} assert json_response(conn, 422)["errors"] != %{}
@ -78,10 +78,10 @@ defmodule EventosWeb.EventControllerTest do
describe "update event" do describe "update event" do
setup [:create_event] setup [:create_event]
test "renders event when data is valid", %{conn: conn, event: %Event{uuid: uuid} = event, user: user} do test "renders event when data is valid", %{conn: conn, event: %Event{uuid: uuid} = event, user: user, actor: actor} do
conn = auth_conn(conn, user) conn = auth_conn(conn, user)
address = address_fixture() address = address_fixture()
attrs = Map.put(@update_attrs, :organizer_actor_id, user.actor.id) attrs = Map.put(@update_attrs, :organizer_actor_id, actor.id)
attrs = Map.put(attrs, :address_id, address.id) attrs = Map.put(attrs, :address_id, address.id)
conn = put conn, event_path(conn, :update, uuid), event: attrs conn = put conn, event_path(conn, :update, uuid), event: attrs
assert %{"uuid" => uuid} = json_response(conn, 200)["data"] assert %{"uuid" => uuid} = json_response(conn, 200)["data"]
@ -97,9 +97,9 @@ defmodule EventosWeb.EventControllerTest do
} = json_response(conn, 200)["data"] } = json_response(conn, 200)["data"]
end end
test "renders errors when data is invalid", %{conn: conn, event: %Event{uuid: uuid} = event, user: user} do test "renders errors when data is invalid", %{conn: conn, event: %Event{uuid: uuid} = event, user: user, actor: actor} do
conn = auth_conn(conn, user) conn = auth_conn(conn, user)
attrs = Map.put(@invalid_attrs, :organizer_actor_id, user.actor.id) attrs = Map.put(@invalid_attrs, :organizer_actor_id, actor.id)
conn = put conn, event_path(conn, :update, uuid), event: attrs conn = put conn, event_path(conn, :update, uuid), event: attrs
assert json_response(conn, 422)["errors"] != %{} assert json_response(conn, 422)["errors"] != %{}
end end

View file

@ -16,8 +16,8 @@ defmodule EventosWeb.SessionControllerTest do
end end
setup %{conn: conn} do setup %{conn: conn} do
actor = insert(:actor) user = insert(:user)
user = insert(:user, actor: actor) actor = insert(:actor, user: user)
event = insert(:event, organizer_actor: actor) event = insert(:event, organizer_actor: actor)
{:ok, conn: conn, user: user, event: event} {:ok, conn: conn, user: user, event: event}
end end

View file

@ -16,8 +16,8 @@ defmodule EventosWeb.TagControllerTest do
end end
setup %{conn: conn} do setup %{conn: conn} do
actor = insert(:actor) user = insert(:user)
user = insert(:user, actor: actor) actor = insert(:actor, user: user)
{:ok, conn: conn, user: user} {:ok, conn: conn, user: user}
end end

View file

@ -16,8 +16,8 @@ defmodule EventosWeb.TrackControllerTest do
end end
setup %{conn: conn} do setup %{conn: conn} do
actor = insert(:actor) user = insert(:user)
user = insert(:user, actor: actor) actor = insert(:actor, user: user)
event = insert(:event, organizer_actor: actor) event = insert(:event, organizer_actor: actor)
{:ok, conn: conn, user: user, event: event} {:ok, conn: conn, user: user, event: event}
end end

View file

@ -16,8 +16,8 @@ defmodule EventosWeb.UserControllerTest do
end end
setup %{conn: conn} do setup %{conn: conn} do
actor = insert(:actor) user = insert(:user)
user = insert(:user, actor: actor) actor = insert(:actor, user: user)
{:ok, conn: conn, user: user} {:ok, conn: conn, user: user}
end end

View file

@ -10,7 +10,6 @@ defmodule Eventos.Factory do
password_hash: "Jane Smith", password_hash: "Jane Smith",
email: sequence(:email, &"email-#{&1}@example.com"), email: sequence(:email, &"email-#{&1}@example.com"),
role: 0, role: 0,
actor: build(:actor)
} }
end end
@ -25,7 +24,8 @@ defmodule Eventos.Factory do
preferred_username: preferred_username, preferred_username: preferred_username,
domain: nil, domain: nil,
keys: pem, keys: pem,
url: EventosWeb.Endpoint.url() <> "/@#{preferred_username}" url: EventosWeb.Endpoint.url() <> "/@#{preferred_username}",
user: nil,
} }
end end