SaveTalkAsync(Talk talk)
25 | => SaveEntityAsync(talk);
26 | }
27 | }
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/shared/city-select/city-name.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from "@angular/core";
2 | import { City } from "./enums";
3 |
4 | @Pipe({ name: "cityName" })
5 | export class CityNamePipe implements PipeTransform {
6 | public transform(city: City): string {
7 | switch (city) {
8 | case City.Spb:
9 | return "Санкт-Петербург";
10 | case City.Msk:
11 | return "Москва";
12 | case City.Sar:
13 | return "Саратов";
14 | case City.Kry:
15 | return "Красноярск";
16 | case City.Kzn:
17 | return "Казань";
18 | case City.Nsk:
19 | return "Новосибирск";
20 | case City.Nnv:
21 | return "Нижний Новгород";
22 | case City.Ufa:
23 | return "Уфа";
24 | }
25 |
26 | const exhaustingCheck: never = city;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/DevActivator/Views/Shared/Error.cshtml:
--------------------------------------------------------------------------------
1 | @{
2 | ViewData["Title"] = "Error";
3 | }
4 |
5 | Error.
6 | An error occurred while processing your request.
7 |
8 | @if (!string.IsNullOrEmpty((string)ViewData["RequestId"]))
9 | {
10 |
11 | Request ID: @ViewData["RequestId"]
12 |
13 | }
14 |
15 | Development Mode
16 |
17 | Swapping to Development environment will display more detailed information about the error that occurred.
18 |
19 |
20 | Development environment should not be enabled in deployed applications , as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development , and restarting the application.
21 |
22 |
--------------------------------------------------------------------------------
/DevActivator.Meetups.DAL/Providers/VenueProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using DevActivator.Common.BL.Config;
4 | using DevActivator.Common.DAL;
5 | using DevActivator.Meetups.BL.Entities;
6 | using DevActivator.Meetups.BL.Interfaces;
7 | using DevActivator.Meetups.DAL.Config;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace DevActivator.Meetups.DAL.Providers
11 | {
12 | public class VenueProvider : BaseProvider, IVenueProvider
13 | {
14 | public VenueProvider(ILogger l, Settings s) : base(l, s, VenueConfig.DirectoryName)
15 | {
16 | }
17 |
18 | public Task> GetAllVenuesAsync()
19 | => GetAllAsync();
20 |
21 | public Task GetVenueOrDefaultAsync(string venueId)
22 | => GetEntityByIdAsync(venueId);
23 |
24 | public Task SaveVenueAsync(Venue venue)
25 | => SaveEntityAsync(venue);
26 | }
27 | }
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/components/talk-list/talk-list.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 | import { API_ENDPOINTS, HttpService } from "@dotnetru/core";
3 | import { IAutocompleteRow } from "@dotnetru/shared/autocomplete";
4 | import { BehaviorSubject, Observable } from "rxjs";
5 | import { filter } from "rxjs/operators";
6 |
7 | @Injectable()
8 | export class TalkListService {
9 | private _talks$: BehaviorSubject = new BehaviorSubject([]);
10 |
11 | public get talks$(): Observable {
12 | return this._talks$.pipe(filter((x) => x.length > 0));
13 | }
14 |
15 | constructor(
16 | private _httpService: HttpService,
17 | ) { }
18 |
19 | public fetchTalks(): void {
20 | this._httpService.get(
21 | API_ENDPOINTS.getTalksUrl,
22 | (talks: IAutocompleteRow[]) => this._talks$.next(talks),
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/search/search.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
6 |
8 |
9 |
11 |
12 |
14 |
15 |
17 |
18 |
21 |
22 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { DebugElement } from "@angular/core";
2 | import { async, ComponentFixture, TestBed } from "@angular/core/testing";
3 | import { } from "jasmine";
4 | import { AppComponent } from "./app.component";
5 |
6 | describe("AppComponent", () => {
7 | let app: AppComponent;
8 | let de: DebugElement;
9 | let fixture: ComponentFixture;
10 |
11 | beforeEach(async(() => {
12 | TestBed.configureTestingModule({
13 | declarations: [
14 | AppComponent,
15 | ],
16 | });
17 |
18 | fixture = TestBed.createComponent(AppComponent);
19 | app = fixture.componentInstance;
20 | de = fixture.debugElement;
21 | }));
22 |
23 | it("should create the app", async(() => {
24 | expect(app).toBeDefined();
25 | }));
26 |
27 | it(`should have as title 'app'`, async(() => {
28 | expect(app.title).toEqual("app");
29 | }));
30 | });
31 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/components/venue-list/venue-list.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 | import { API_ENDPOINTS, HttpService } from "@dotnetru/core";
3 | import { IAutocompleteRow } from "@dotnetru/shared/autocomplete";
4 | import { BehaviorSubject, Observable } from "rxjs";
5 | import { filter } from "rxjs/operators";
6 |
7 | @Injectable()
8 | export class VenueListService {
9 | private _venues$: BehaviorSubject = new BehaviorSubject([]);
10 |
11 | public get venues$(): Observable {
12 | return this._venues$.pipe(filter((x) => x.length > 0));
13 | }
14 |
15 | constructor(
16 | private _httpService: HttpService,
17 | ) { }
18 |
19 | public fetchVenues(): void {
20 | this._httpService.get(
21 | API_ENDPOINTS.getVenuesUrl,
22 | (venues: IAutocompleteRow[]) => this._venues$.next(venues),
23 | );
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/DevActivator.Meetups.DAL/Providers/FriendProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using DevActivator.Common.BL.Config;
4 | using DevActivator.Common.DAL;
5 | using DevActivator.Meetups.BL.Entities;
6 | using DevActivator.Meetups.BL.Interfaces;
7 | using DevActivator.Meetups.DAL.Config;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace DevActivator.Meetups.DAL.Providers
11 | {
12 | public class FriendProvider : BaseProvider, IFriendProvider
13 | {
14 | public FriendProvider(ILogger l, Settings s) : base(l, s, FriendConfig.DirectoryName)
15 | {
16 | }
17 |
18 | public Task> GetAllFriendsAsync()
19 | => GetAllAsync();
20 |
21 | public Task GetFriendOrDefaultAsync(string friendId)
22 | => GetEntityByIdAsync(friendId);
23 |
24 | public Task SaveFriendAsync(Friend friend)
25 | => SaveEntityAsync(friend);
26 | }
27 | }
--------------------------------------------------------------------------------
/DevActivator.Meetups.DAL/Providers/MeetupProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using DevActivator.Common.BL.Config;
4 | using DevActivator.Common.DAL;
5 | using DevActivator.Meetups.BL.Entities;
6 | using DevActivator.Meetups.BL.Interfaces;
7 | using DevActivator.Meetups.DAL.Config;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace DevActivator.Meetups.DAL.Providers
11 | {
12 | public class MeetupProvider : BaseProvider, IMeetupProvider
13 | {
14 | public MeetupProvider(ILogger l, Settings s) : base(l, s, MeetupConfig.DirectoryName)
15 | {
16 | }
17 |
18 | public Task> GetAllMeetupsAsync()
19 | => GetAllAsync();
20 |
21 | public Task GetMeetupOrDefaultAsync(string meetupId)
22 | => GetEntityByIdAsync(meetupId);
23 |
24 | public Task SaveMeetupAsync(Meetup meetup)
25 | => SaveEntityAsync(meetup);
26 | }
27 | }
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/components/friend-list/friend-list.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 | import { API_ENDPOINTS, HttpService } from "@dotnetru/core";
3 | import { BehaviorSubject, Observable } from "rxjs";
4 | import { filter } from "rxjs/operators";
5 |
6 | import { IAutocompleteRow } from "@dotnetru/shared/autocomplete";
7 |
8 | @Injectable()
9 | export class FriendListService {
10 | private _friends$: BehaviorSubject = new BehaviorSubject([]);
11 |
12 | public get friends$(): Observable {
13 | return this._friends$.pipe(filter((x) => x.length > 0));
14 | }
15 |
16 | constructor(
17 | private _httpService: HttpService,
18 | ) { }
19 |
20 | public fetchFriends(): void {
21 | this._httpService.get(
22 | API_ENDPOINTS.getFriendsUrl,
23 | (friends: IAutocompleteRow[]) => this._friends$.next(friends),
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/components/meetup-list/meetup-list.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 | import { API_ENDPOINTS, HttpService } from "@dotnetru/core";
3 | import { BehaviorSubject, Observable } from "rxjs";
4 | import { filter } from "rxjs/operators";
5 |
6 | import { IAutocompleteRow } from "@dotnetru/shared/autocomplete";
7 |
8 | @Injectable()
9 | export class MeetupListService {
10 | private _meetups$: BehaviorSubject = new BehaviorSubject([]);
11 |
12 | public get meetups$(): Observable {
13 | return this._meetups$.pipe(filter((x) => x.length > 0));
14 | }
15 |
16 | constructor(
17 | private _httpService: HttpService,
18 | ) { }
19 |
20 | public fetchMeetups(): void {
21 | this._httpService.get(
22 | API_ENDPOINTS.getMeetupsUrl,
23 | (meetups: IAutocompleteRow[]) => this._meetups$.next(meetups),
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/DevActivator.Meetups.DAL/Providers/SpeakerProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using DevActivator.Common.BL.Config;
4 | using DevActivator.Common.DAL;
5 | using DevActivator.Meetups.BL.Entities;
6 | using DevActivator.Meetups.BL.Interfaces;
7 | using DevActivator.Meetups.DAL.Config;
8 | using Microsoft.Extensions.Logging;
9 |
10 | namespace DevActivator.Meetups.DAL.Providers
11 | {
12 | public class SpeakerProvider : BaseProvider, ISpeakerProvider
13 | {
14 | public SpeakerProvider(ILogger l, Settings s) : base(l, s, SpeakerConfig.DirectoryName)
15 | {
16 | }
17 |
18 | public Task> GetAllSpeakersAsync()
19 | => GetAllAsync();
20 |
21 | public Task GetSpeakerOrDefaultAsync(string speakerId)
22 | => GetEntityByIdAsync(speakerId);
23 |
24 | public Task SaveSpeakerAsync(Speaker speaker)
25 | => SaveEntityAsync(speaker);
26 | }
27 | }
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/components/speaker-list/speaker-list.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 | import { API_ENDPOINTS, HttpService } from "@dotnetru/core";
3 | import { BehaviorSubject, Observable } from "rxjs";
4 | import { filter } from "rxjs/operators";
5 |
6 | import { IAutocompleteRow } from "@dotnetru/shared/autocomplete";
7 |
8 | @Injectable()
9 | export class SpeakerListService {
10 | private _speakers$: BehaviorSubject = new BehaviorSubject([]);
11 |
12 | public get speakers$(): Observable {
13 | return this._speakers$.pipe(filter((x) => x.length > 0));
14 | }
15 |
16 | constructor(
17 | private _httpService: HttpService,
18 | ) { }
19 |
20 | public fetchSpeakers(): void {
21 | this._httpService.get(
22 | API_ENDPOINTS.getSpeakersUrl,
23 | (speakers: IAutocompleteRow[]) => this._speakers$.next(speakers),
24 | );
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/search/search.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from "@angular/core";
2 | import { RouterModule } from "@angular/router";
3 | import { FriendListModule } from "@dotnetru/friend-list";
4 | import { MeetupListModule } from "@dotnetru/meetup-list";
5 | import { SpeakerListModule } from "@dotnetru/speaker-list";
6 | import { TalkListModule } from "@dotnetru/talk-list";
7 | import { VenueListModule } from "@dotnetru/venue-list";
8 |
9 | import { SearchPageComponent } from "./search.component";
10 |
11 | @NgModule({
12 | declarations: [
13 | SearchPageComponent,
14 | ],
15 | exports: [
16 | SearchPageComponent,
17 | ],
18 | imports: [
19 | RouterModule.forChild([
20 | { path: "search", component: SearchPageComponent },
21 | ]),
22 |
23 | FriendListModule,
24 | MeetupListModule,
25 | SpeakerListModule,
26 | TalkListModule,
27 | VenueListModule,
28 | ],
29 | })
30 | export class SearchPageModule { }
31 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/test/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/0.13/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '.',
7 | frameworks: ['jasmine'],
8 | files: [
9 | '../../wwwroot/dist/vendor.js',
10 | './boot-tests.ts'
11 | ],
12 | preprocessors: {
13 | './boot-tests.ts': ['webpack']
14 | },
15 | reporters: ['progress'],
16 | port: 9876,
17 | colors: true,
18 | logLevel: config.LOG_INFO,
19 | autoWatch: true,
20 | browsers: ['Chrome'],
21 | mime: { 'application/javascript': ['ts','tsx'] },
22 | singleRun: false,
23 | webpack: require('../../webpack.config.js')().filter(config => config.target !== 'node'), // Test against client bundle, because tests run in a browser
24 | webpackMiddleware: { stats: 'errors-only' }
25 | });
26 | };
27 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/timepad/interfaces.ts:
--------------------------------------------------------------------------------
1 | import { IFriend } from "../friend-editor/interfaces";
2 | import { Community } from "../meetup-editor/enums";
3 | import { IApiSession, ISession } from "../meetup-editor/interfaces";
4 | import { ISpeaker } from "../speaker-editor/interfaces";
5 | import { ITalk } from "../talk-editor/interfaces";
6 | import { IVenue } from "../venue-editor/interfaces";
7 |
8 | export interface IMap {
9 | [key: string]: T;
10 | }
11 |
12 | export interface ICompositeMeetup {
13 | id: string | undefined;
14 | name: string | undefined;
15 | communityId: Community | undefined;
16 | venue: IVenue | undefined;
17 | sessions: ISession[];
18 | talks: IMap;
19 | speakers: IMap;
20 | friends: IFriend[];
21 | }
22 |
23 | export type IApiCompositeMeetup = ICompositeMeetup & {
24 | sessions: IApiSession[];
25 | };
26 |
27 | export interface IRandomConcatModel {
28 | name: string | undefined;
29 | communityId: string | undefined;
30 | venueId: string | undefined;
31 | friendIds: string[];
32 | sessions: ISession[];
33 | talkIds: string[];
34 | speakerIds: string[];
35 | }
36 |
--------------------------------------------------------------------------------
/DevActivator.Meetups.Tests/ProviderTests/SpeakerProviderTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading.Tasks;
4 | using DevActivator.Common.BL.Config;
5 | using DevActivator.Meetups.DAL.Providers;
6 | using DevActivator.Meetups.BL.Extensions;
7 | using DevActivator.Meetups.BL.Interfaces;
8 | using Xunit;
9 |
10 | namespace DevActivator.Meetups.Tests.ProviderTests
11 | {
12 | public class SpeakerProviderTests
13 | {
14 | [Fact]
15 | public async Task Test1()
16 | {
17 | // prepare
18 | var settings = new Settings {AuditRepoDirectory = "/Users/alex-mbp/repos/Audit"};
19 | ISpeakerProvider speakerProvider = new SpeakerProvider(null, settings);
20 |
21 | // test
22 | var speakers = await speakerProvider.GetAllSpeakersAsync();
23 | Assert.NotNull(speakers);
24 | Assert.NotEmpty(speakers);
25 |
26 | var speaker = speakers.First();
27 |
28 | var lastUpdateDate = speaker.GetLastUpdateDate(settings);
29 | var date = DateTime.Parse(lastUpdateDate);
30 | Assert.False(DateTime.MinValue == date);
31 | }
32 | }
33 | }
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/components/talk-list/talk-list.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from "@angular/common";
2 | import { NgModule } from "@angular/core";
3 | import { ReactiveFormsModule } from "@angular/forms";
4 | import {
5 | MatAutocompleteModule,
6 | MatButtonModule,
7 | MatFormFieldModule,
8 | MatIconModule,
9 | MatInputModule,
10 | } from "@angular/material";
11 | import { AutocompleteModule } from "@dotnetru/shared/autocomplete";
12 |
13 | import { TalkListComponent } from "./talk-list.component";
14 | import { TalkListService } from "./talk-list.service";
15 |
16 | @NgModule({
17 | declarations: [
18 | TalkListComponent,
19 | ],
20 | entryComponents: [
21 | TalkListComponent,
22 | ],
23 | exports: [
24 | TalkListComponent,
25 | ],
26 | imports: [
27 | CommonModule,
28 | ReactiveFormsModule,
29 |
30 | AutocompleteModule,
31 |
32 | MatAutocompleteModule,
33 | MatButtonModule,
34 | MatFormFieldModule,
35 | MatIconModule,
36 | MatInputModule,
37 | ],
38 | providers: [
39 | TalkListService,
40 | ],
41 | })
42 | export class TalkListModule { }
43 |
--------------------------------------------------------------------------------
/DevActivator/Controllers/TalkController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using DevActivator.Meetups.BL.Interfaces;
4 | using DevActivator.Meetups.BL.Models;
5 | using Microsoft.AspNetCore.Mvc;
6 |
7 | namespace DevActivator.Controllers
8 | {
9 | [Route("api/[controller]")]
10 | public class TalkController : Controller
11 | {
12 | private readonly ITalkService _talkService;
13 |
14 | public TalkController(ITalkService talkService)
15 | {
16 | _talkService = talkService;
17 | }
18 |
19 | [HttpGet("[action]")]
20 | public Task> GetTalks()
21 | => _talkService.GetAllTalksAsync();
22 |
23 | [HttpGet("[action]/{talkId}")]
24 | public Task GetTalk(string talkId)
25 | => _talkService.GetTalkAsync(talkId);
26 |
27 | [HttpPost("[action]")]
28 | public Task AddTalk([FromBody] TalkVm talk)
29 | => _talkService.AddTalkAsync(talk);
30 |
31 | [HttpPost("[action]")]
32 | public Task UpdateTalk([FromBody] TalkVm talk)
33 | => _talkService.UpdateTalkAsync(talk);
34 | }
35 | }
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/components/venue-list/venue-list.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from "@angular/common";
2 | import { NgModule } from "@angular/core";
3 | import { ReactiveFormsModule } from "@angular/forms";
4 | import {
5 | MatAutocompleteModule,
6 | MatButtonModule,
7 | MatFormFieldModule,
8 | MatIconModule,
9 | MatInputModule,
10 | } from "@angular/material";
11 | import { AutocompleteModule } from "@dotnetru/shared/autocomplete";
12 |
13 | import { VenueListComponent } from "./venue-list.component";
14 | import { VenueListService } from "./venue-list.service";
15 |
16 | @NgModule({
17 | declarations: [
18 | VenueListComponent,
19 | ],
20 | entryComponents: [
21 | VenueListComponent,
22 | ],
23 | exports: [
24 | VenueListComponent,
25 | ],
26 | imports: [
27 | CommonModule,
28 | ReactiveFormsModule,
29 |
30 | AutocompleteModule,
31 |
32 | MatAutocompleteModule,
33 | MatButtonModule,
34 | MatFormFieldModule,
35 | MatIconModule,
36 | MatInputModule,
37 | ],
38 | providers: [
39 | VenueListService,
40 | ],
41 | })
42 | export class VenueListModule { }
43 |
--------------------------------------------------------------------------------
/DevActivator.Meetups.BL/Helpers/ShellHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 |
4 | namespace DevActivator.Meetups.BL.Helpers
5 | {
6 | // todo: move from BL
7 | public static class ShellHelper
8 | {
9 | public static string Bash(this string command)
10 | {
11 | var escapedArgs = command;
12 |
13 | var process = new Process
14 | {
15 | StartInfo = new ProcessStartInfo
16 | {
17 | FileName = "/bin/bash",
18 | Arguments = $"-c \"{escapedArgs}\"",
19 | RedirectStandardOutput = true,
20 | UseShellExecute = false,
21 | CreateNoWindow = true,
22 | }
23 | };
24 |
25 | string result;
26 | try
27 | {
28 | process.Start();
29 | result = process.StandardOutput.ReadToEnd();
30 | process.WaitForExit();
31 | }
32 | catch (Exception)
33 | {
34 | result = "bash error";
35 | }
36 |
37 | return result.TrimEnd('\r', '\n');
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/components/friend-list/friend-list.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from "@angular/common";
2 | import { NgModule } from "@angular/core";
3 | import { ReactiveFormsModule } from "@angular/forms";
4 | import {
5 | MatAutocompleteModule,
6 | MatButtonModule,
7 | MatFormFieldModule,
8 | MatIconModule,
9 | MatInputModule,
10 | } from "@angular/material";
11 | import { AutocompleteModule } from "@dotnetru/shared/autocomplete";
12 |
13 | import { FriendListComponent } from "./friend-list.component";
14 | import { FriendListService } from "./friend-list.service";
15 |
16 | @NgModule({
17 | declarations: [
18 | FriendListComponent,
19 | ],
20 | entryComponents: [
21 | FriendListComponent,
22 | ],
23 | exports: [
24 | FriendListComponent,
25 | ],
26 | imports: [
27 | CommonModule,
28 | ReactiveFormsModule,
29 |
30 | AutocompleteModule,
31 |
32 | MatAutocompleteModule,
33 | MatButtonModule,
34 | MatFormFieldModule,
35 | MatIconModule,
36 | MatInputModule,
37 | ],
38 | providers: [
39 | FriendListService,
40 | ],
41 | })
42 | export class FriendListModule { }
43 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/components/meetup-list/meetup-list.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from "@angular/common";
2 | import { NgModule } from "@angular/core";
3 | import { ReactiveFormsModule } from "@angular/forms";
4 | import {
5 | MatAutocompleteModule,
6 | MatButtonModule,
7 | MatFormFieldModule,
8 | MatIconModule,
9 | MatInputModule,
10 | } from "@angular/material";
11 | import { AutocompleteModule } from "@dotnetru/shared/autocomplete";
12 |
13 | import { MeetupListComponent } from "./meetup-list.component";
14 | import { MeetupListService } from "./meetup-list.service";
15 |
16 | @NgModule({
17 | declarations: [
18 | MeetupListComponent,
19 | ],
20 | entryComponents: [
21 | MeetupListComponent,
22 | ],
23 | exports: [
24 | MeetupListComponent,
25 | ],
26 | imports: [
27 | CommonModule,
28 | ReactiveFormsModule,
29 |
30 | AutocompleteModule,
31 |
32 | MatAutocompleteModule,
33 | MatButtonModule,
34 | MatFormFieldModule,
35 | MatIconModule,
36 | MatInputModule,
37 | ],
38 | providers: [
39 | MeetupListService,
40 | ],
41 | })
42 | export class MeetupListModule { }
43 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/components/speaker-list/speaker-list.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from "@angular/common";
2 | import { NgModule } from "@angular/core";
3 | import { ReactiveFormsModule } from "@angular/forms";
4 | import {
5 | MatAutocompleteModule,
6 | MatButtonModule,
7 | MatFormFieldModule,
8 | MatIconModule,
9 | MatInputModule,
10 | } from "@angular/material";
11 | import { AutocompleteModule } from "@dotnetru/shared/autocomplete";
12 |
13 | import { SpeakerListComponent } from "./speaker-list.component";
14 | import { SpeakerListService } from "./speaker-list.service";
15 |
16 | @NgModule({
17 | declarations: [
18 | SpeakerListComponent,
19 | ],
20 | entryComponents: [
21 | SpeakerListComponent,
22 | ],
23 | exports: [
24 | SpeakerListComponent,
25 | ],
26 | imports: [
27 | CommonModule,
28 | ReactiveFormsModule,
29 |
30 | AutocompleteModule,
31 |
32 | MatAutocompleteModule,
33 | MatButtonModule,
34 | MatFormFieldModule,
35 | MatIconModule,
36 | MatInputModule,
37 | ],
38 | providers: [
39 | SpeakerListService,
40 | ],
41 | })
42 | export class SpeakerListModule { }
43 |
--------------------------------------------------------------------------------
/DevActivator/Controllers/VenueController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using DevActivator.Meetups.BL.Interfaces;
4 | using DevActivator.Meetups.BL.Models;
5 | using Microsoft.AspNetCore.Mvc;
6 |
7 | namespace DevActivator.Controllers
8 | {
9 | [Route("api/[controller]")]
10 | public class VenueController : Controller
11 | {
12 | private readonly IVenueService _venueService;
13 |
14 | public VenueController(IVenueService venueService)
15 | {
16 | _venueService = venueService;
17 | }
18 |
19 | [HttpGet("[action]")]
20 | public Task> GetVenues()
21 | => _venueService.GetAllVenuesAsync();
22 |
23 | [HttpGet("[action]/{venueId}")]
24 | public Task GetVenue(string venueId)
25 | => _venueService.GetVenueAsync(venueId);
26 |
27 | [HttpPost("[action]")]
28 | public Task AddVenue([FromBody] VenueVm venue)
29 | => _venueService.AddVenueAsync(venue);
30 |
31 | [HttpPost("[action]")]
32 | public Task UpdateVenue([FromBody] VenueVm venue)
33 | => _venueService.UpdateVenueAsync(venue);
34 | }
35 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | ### STAGE 1: Build ###
2 |
3 | # We label our stage as ‘builder’
4 | FROM node:10-alpine as builder
5 |
6 | COPY ./DevActivator/package.json ./DevActivator/npm-shrinkwrap.json ./
7 |
8 | ## Storing node modules on a separate layer will prevent unnecessary npm installs at each build
9 |
10 | RUN npm ci && mkdir /ng-app && mv ./node_modules ./ng-app
11 |
12 | WORKDIR /ng-app
13 |
14 | COPY DevActivator .
15 |
16 | ## Run tslint
17 |
18 | RUN npm run lint
19 |
20 | ## Build the angular app in production mode and store the artifacts in dist folder
21 |
22 | RUN npm run prod
23 |
24 | ### STAGE 2: Setup ###
25 |
26 | FROM nginx:alpine
27 |
28 | # COPY nginx/nginx.conf /etc/nginx/nginx.conf
29 | COPY nginx/default.conf /etc/nginx/conf.d/
30 | RUN rm -rf /usr/share/nginx/html/*
31 |
32 | # ## From ‘builder’ stage copy over the artifacts in dist folder to default nginx public folder
33 | COPY ./DevActivator/wwwroot /usr/share/nginx/html
34 | COPY ./nginx/index.html /usr/share/nginx/html
35 | COPY --from=builder /ng-app/wwwroot/dist /usr/share/nginx/html
36 |
37 | CMD ["nginx", "-g", "daemon off;"]
38 |
39 | # * docker image build -t dotnetru/devactivator:latest .
40 | # * docker run -p 3000:80 --rm dotnetru/devactivator:latest
41 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | image: Ubuntu
2 |
3 | version: 0.1.0.{build}
4 |
5 | # Only clone the current branch and no history
6 | clone_depth: 1
7 |
8 | pull_requests:
9 | # Do not increment build number for pull requests
10 | do_not_increment_build_number: true
11 |
12 | environment:
13 | # DOCKER_BUILDKIT: 1
14 | DOCKER_PASS:
15 | secure: 7xusxxMFy9iURl0Ktuzri5XyT3/NDKlsU3kkfJ+Sq90=
16 |
17 | cache:
18 | - /var/run/docker.sock -> Dockerfile, local.Dockerfile
19 |
20 | skip_commits:
21 | files:
22 | - README.md
23 |
24 | build:
25 | verbosity: quiet
26 |
27 | build_script:
28 | - ps: docker image build -f local.Dockerfile -t dotnetru/devactivator-local:latest -t dotnetru/devactivator-local:$env:APPVEYOR_BUILD_VERSION .
29 | - ps: docker image build -t dotnetru/devactivator:latest -t dotnetru/devactivator:$env:APPVEYOR_BUILD_VERSION .
30 |
31 | test: off
32 |
33 | deploy_script:
34 | - ps: $env:DOCKER_PASS | docker login --username dotnetrucd --password-stdin
35 | - ps: docker push dotnetru/devactivator-local:latest
36 | - ps: docker push dotnetru/devactivator-local:$env:APPVEYOR_BUILD_VERSION
37 | - ps: docker push dotnetru/devactivator:latest
38 | - ps: docker push dotnetru/devactivator:$env:APPVEYOR_BUILD_VERSION
39 |
--------------------------------------------------------------------------------
/DevActivator.Meetups.BL/Extensions/FriendExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using DevActivator.Meetups.BL.Entities;
3 |
4 | namespace DevActivator.Meetups.BL.Extensions
5 | {
6 | public static class FriendExtensions
7 | {
8 | public static Friend EnsureIsValid(this Friend friend)
9 | {
10 | // todo: implement full validation
11 | if (string.IsNullOrWhiteSpace(friend.Name))
12 | {
13 | throw new FormatException(nameof(friend.Name));
14 | }
15 |
16 | if (string.IsNullOrWhiteSpace(friend.Url))
17 | {
18 | throw new FormatException(nameof(friend.Url));
19 | }
20 |
21 | if (string.IsNullOrWhiteSpace(friend.Description))
22 | {
23 | throw new FormatException(nameof(friend.Description));
24 | }
25 |
26 | return friend;
27 | }
28 |
29 | public static Friend Extend(this Friend original, Friend friend)
30 | => new Friend
31 | {
32 | Id = original.Id,
33 | Name = friend.Name,
34 | Url = friend.Url,
35 | Description = friend.Description
36 | };
37 | }
38 | }
--------------------------------------------------------------------------------
/DevActivator/Controllers/MeetupController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using DevActivator.Meetups.BL.Interfaces;
4 | using DevActivator.Meetups.BL.Models;
5 | using Microsoft.AspNetCore.Mvc;
6 |
7 | namespace DevActivator.Controllers
8 | {
9 | [Route("api/[controller]")]
10 | public class MeetupController : Controller
11 | {
12 | private readonly IMeetupService _meetupService;
13 |
14 | public MeetupController(IMeetupService meetupService)
15 | {
16 | _meetupService = meetupService;
17 | }
18 |
19 | [HttpGet("[action]")]
20 | public Task> GetMeetups()
21 | => _meetupService.GetAllMeetupsAsync();
22 |
23 | [HttpGet("[action]/{meetupId}")]
24 | public Task GetMeetup(string meetupId)
25 | => _meetupService.GetMeetupAsync(meetupId);
26 |
27 | [HttpPost("[action]")]
28 | public Task AddMeetup([FromBody] MeetupVm meetup)
29 | => _meetupService.AddMeetupAsync(meetup);
30 |
31 | [HttpPost("[action]")]
32 | public Task UpdateMeetup([FromBody] MeetupVm meetup)
33 | => _meetupService.UpdateMeetupAsync(meetup);
34 | }
35 | }
--------------------------------------------------------------------------------
/DevActivator/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@dotnetru/core": [
6 | "./ClientApp/app/core/index"
7 | ],
8 | "@dotnetru/shared/*": [
9 | "./ClientApp/app/shared/*/index",
10 | "./ClientApp/app/shared/*"
11 | ],
12 | "@dotnetru/pages/*": [
13 | "./ClientApp/app/pages/*/index",
14 | "./ClientApp/app/pages/*"
15 | ],
16 | "@dotnetru/*": [
17 | "./ClientApp/app/components/*/index",
18 | "./ClientApp/app/components/*"
19 | ]
20 | },
21 | "module": "es2015",
22 | "moduleResolution": "node",
23 | "target": "es5",
24 | "sourceMap": true,
25 | "experimentalDecorators": true,
26 | "emitDecoratorMetadata": true,
27 | "skipDefaultLibCheck": true,
28 | "skipLibCheck": true, // Workaround for https://github.com/angular/angular/issues/17863. Remove this if you upgrade to a fixed version of Angular.
29 | "strict": true,
30 | "noImplicitAny": true,
31 | "lib": [
32 | "es6",
33 | "dom"
34 | ],
35 | "types": [
36 | "webpack-env"
37 | ]
38 | },
39 | "exclude": [
40 | "bin",
41 | "node_modules"
42 | ],
43 | "atom": {
44 | "rewriteTsconfig": false
45 | }
46 | }
--------------------------------------------------------------------------------
/DevActivator/Controllers/FriendController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using DevActivator.Meetups.BL.Entities;
4 | using DevActivator.Meetups.BL.Interfaces;
5 | using DevActivator.Meetups.BL.Models;
6 | using Microsoft.AspNetCore.Mvc;
7 |
8 | namespace DevActivator.Controllers
9 | {
10 | [Route("api/[controller]")]
11 | public class FriendController : Controller
12 | {
13 | private readonly IFriendService _friendService;
14 |
15 | public FriendController(IFriendService friendService)
16 | {
17 | _friendService = friendService;
18 | }
19 |
20 | [HttpGet("[action]")]
21 | public Task> GetFriends()
22 | => _friendService.GetAllFriendsAsync();
23 |
24 | [HttpGet("[action]/{friendId}")]
25 | public Task GetFriend(string friendId)
26 | => _friendService.GetFriendAsync(friendId);
27 |
28 | [HttpPost("[action]")]
29 | public Task AddFriend([FromBody] Friend friend)
30 | => _friendService.AddFriendAsync(friend);
31 |
32 | [HttpPost("[action]")]
33 | public Task UpdateFriend([FromBody] Friend friend)
34 | => _friendService.UpdateFriendAsync(friend);
35 | }
36 | }
--------------------------------------------------------------------------------
/DevActivator/Controllers/SpeakerController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using DevActivator.Meetups.BL.Interfaces;
4 | using DevActivator.Meetups.BL.Models;
5 | using Microsoft.AspNetCore.Mvc;
6 |
7 | namespace DevActivator.Controllers
8 | {
9 | [Route("api/[controller]")]
10 | public class SpeakerController : Controller
11 | {
12 | private readonly ISpeakerService _speakerService;
13 |
14 | public SpeakerController(ISpeakerService speakerService)
15 | {
16 | _speakerService = speakerService;
17 | }
18 |
19 | [HttpGet("[action]")]
20 | public Task> GetSpeakers()
21 | => _speakerService.GetAllSpeakersAsync();
22 |
23 | [HttpGet("[action]/{speakerId}")]
24 | public Task GetSpeaker(string speakerId)
25 | => _speakerService.GetSpeakerAsync(speakerId);
26 |
27 | [HttpPost("[action]")]
28 | public Task AddSpeaker([FromBody] SpeakerVm speaker)
29 | => _speakerService.AddSpeakerAsync(speaker);
30 |
31 | [HttpPost("[action]")]
32 | public Task UpdateSpeaker([FromBody] SpeakerVm speaker)
33 | => _speakerService.UpdateSpeakerAsync(speaker);
34 | }
35 | }
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/timepad/timepad.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | display: block;
3 | height: 100%;
4 | }
5 |
6 | .save-action {
7 | height: 36px;
8 | padding-bottom: 4px;
9 | display: flex;
10 | justify-content: flex-end;
11 | max-width: 640px;
12 | width: 100%;
13 | margin: 0 auto;
14 | }
15 |
16 | .container {
17 | max-width: 640px;
18 | margin: 0 auto;
19 | height: calc(100% - 40px);
20 | overflow: auto;
21 | }
22 |
23 | .mat-list-item {
24 | height: auto !important;
25 | padding: 12px 0 !important;
26 | }
27 |
28 | .session-content,
29 | .friend-content {
30 | display: flex;
31 | align-items: center;
32 | padding: 8px 0;
33 | justify-content: space-between;
34 | }
35 |
36 | .nowrap {
37 | white-space: nowrap;
38 | }
39 |
40 | .session-description {
41 | padding-left: 8px;
42 | }
43 |
44 | h3,
45 | p {
46 | white-space: pre-wrap;
47 | font-family: Roboto, "Helvetica Neue", sans-serif;
48 | }
49 |
50 | .about {
51 | display: flex;
52 | }
53 |
54 | .avatar {
55 | max-width: 200px;
56 | }
57 |
58 | .mat-expansion-panel-header-title {
59 | flex-direction: column;
60 | }
61 |
62 | .handle {
63 | padding-right: 4px;
64 | }
65 |
66 | .header {
67 | height: 40px;
68 | display: flex;
69 | align-items: center;
70 | }
--------------------------------------------------------------------------------
/DevActivator.Meetups.Tests/ProviderTests/MeetupProviderTests.cs:
--------------------------------------------------------------------------------
1 | using System.Threading.Tasks;
2 | using DevActivator.Common.BL.Config;
3 | using DevActivator.Meetups.BL.Entities;
4 | using DevActivator.Meetups.BL.Interfaces;
5 | using DevActivator.Meetups.DAL.Providers;
6 | using Xunit;
7 |
8 | namespace DevActivator.Meetups.Tests.ProviderTests
9 | {
10 | public class MeetupProviderTests
11 | {
12 | [Fact]
13 | public async Task MeetupFriendIdsDeserializationSucceed()
14 | {
15 | // prepare
16 | var talk = await GetTestMeetupAsync();
17 |
18 | // test
19 | Assert.Equal(2, talk.FriendIds.Count);
20 | }
21 |
22 | [Fact]
23 | public async Task MeetupSessionsDeserializationSucceed()
24 | {
25 | // prepare
26 | var talk = await GetTestMeetupAsync();
27 |
28 | // test
29 | Assert.Equal(2, talk.Sessions.Count);
30 | }
31 |
32 | private Task GetTestMeetupAsync(string testMeetupId = "SpbDotNet-30")
33 | {
34 | var settings = new Settings {AuditRepoDirectory = "/Users/alex-mbp/repos/Audit"};
35 | IMeetupProvider talkProvider = new MeetupProvider(null, settings);
36 | return talkProvider.GetMeetupOrDefaultAsync(testMeetupId);
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/components/navmenu/navmenu.component.html:
--------------------------------------------------------------------------------
1 |
3 | fiber_new
4 | Создать
5 |
6 |
7 |
9 | search
10 | Поиск
11 |
12 |
13 |
15 | developer_mode
16 | В разработке
17 |
18 |
19 |
20 |
22 |
24 | Встречу
25 |
26 |
28 | person
29 | Докладчика
30 |
31 |
33 | speaker_notes
34 | Доклад
35 |
36 |
38 | account_balance
39 | Площадку
40 |
41 |
43 | loyalty
44 | Друга
45 |
46 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/test/boot-tests.ts:
--------------------------------------------------------------------------------
1 | // Load required polyfills and testing libraries
2 | import "reflect-metadata";
3 | import "zone.js";
4 | import "zone.js/dist/long-stack-trace-zone";
5 | import "zone.js/dist/proxy.js";
6 | import "zone.js/dist/sync-test";
7 | // tslint:disable-next-line:ordered-imports
8 | import "zone.js/dist/jasmine-patch";
9 | // tslint:disable-next-line:ordered-imports
10 | import "zone.js/dist/async-test";
11 | import "zone.js/dist/fake-async-test";
12 | // tslint:disable-next-line:ordered-imports
13 | import * as testing from "@angular/core/testing";
14 | import * as testingBrowser from "@angular/platform-browser-dynamic/testing";
15 |
16 | // There's no typing for the `__karma__` variable. Just declare it as any
17 | declare var __karma__: any;
18 | declare var require: any;
19 |
20 | // Prevent Karma from running prematurely
21 | // tslint:disable-next-line:only-arrow-functions
22 | __karma__.loaded = function() { /* ignore */ };
23 |
24 | // First, initialize the Angular testing environment
25 | testing.getTestBed().initTestEnvironment(
26 | testingBrowser.BrowserDynamicTestingModule, testingBrowser.platformBrowserDynamicTesting());
27 |
28 | // Then we find all the tests
29 | const context = require.context("../", true, /\.spec\.ts$/);
30 |
31 | // And load the modules
32 | context.keys().map(context);
33 |
34 | // Finally, start Karma to run the tests
35 | __karma__.start();
36 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/friend-editor/friend-editor.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from "@angular/common";
2 | import { NgModule } from "@angular/core";
3 | import { FormsModule, ReactiveFormsModule } from "@angular/forms";
4 | import { MatButtonModule, MatFormFieldModule, MatIconModule, MatInputModule } from "@angular/material";
5 | import { RouterModule } from "@angular/router";
6 | import { CoreModule } from "@dotnetru/core";
7 | import { FileDialogModule } from "@dotnetru/shared/file-dialog";
8 |
9 | import { FriendEditorComponent } from "./friend-editor.component";
10 | import { FriendImageUrlPipe } from "./pipes";
11 |
12 | @NgModule({
13 | declarations: [
14 | FriendEditorComponent,
15 | FriendImageUrlPipe,
16 | ],
17 | entryComponents: [
18 | FriendEditorComponent,
19 | ],
20 | exports: [
21 | FriendEditorComponent,
22 | FriendImageUrlPipe,
23 | ],
24 | imports: [
25 | RouterModule.forChild([
26 | { path: "friend-creator", component: FriendEditorComponent },
27 | { path: "friend-editor/:friendId", component: FriendEditorComponent },
28 | ]),
29 |
30 | CommonModule,
31 | FormsModule,
32 | ReactiveFormsModule,
33 |
34 | MatButtonModule,
35 | MatFormFieldModule,
36 | MatIconModule,
37 | MatInputModule,
38 |
39 | CoreModule,
40 | FileDialogModule,
41 | ],
42 | })
43 | export class FriendEditorModule {
44 | }
45 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/boot.browser.ts:
--------------------------------------------------------------------------------
1 | import "reflect-metadata";
2 | import "zone.js";
3 | // tslint:disable-next-line:ordered-imports
4 | import { enableProdMode, NgModuleRef } from "@angular/core";
5 | import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
6 | import { AppModule } from "./app/app.browser.module";
7 |
8 | if (module.hot) {
9 | module.hot.accept();
10 | module.hot.dispose(() => {
11 | // Before restarting the app, we create a new root element and dispose the old one
12 | const oldRootElem = document.querySelector("app");
13 | const newRootElem = document.createElement("app");
14 | oldRootElem!.parentNode!.insertBefore(newRootElem, oldRootElem);
15 | modulePromise.then((appModule: NgModuleRef) => {
16 | appModule.destroy();
17 | oldRootElem!.innerHTML = "";
18 | const elements: HTMLCollectionOf = document.getElementsByClassName("cdk-overlay-container");
19 | // tslint:disable-next-line:prefer-for-of
20 | for (let i = 0; i < elements.length; i++) {
21 | elements[i].innerHTML = "";
22 | }
23 | });
24 | });
25 | } else {
26 | enableProdMode();
27 | }
28 |
29 | // Note: @ng-tools/webpack looks for the following expression when performing production
30 | // builds. Don't change how this line looks, otherwise you may break tree-shaking.
31 | const modulePromise = platformBrowserDynamic().bootstrapModule(AppModule);
32 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/talk-editor/talk-editor-dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from "@angular/core";
2 | import { MatDialog, MatDialogRef } from "@angular/material";
3 | import { ActivatedRoute, Router } from "@angular/router";
4 | import { LayoutService } from "@dotnetru/core";
5 | import { ITalk } from "./interfaces";
6 | import { TalkEditorComponent } from "./talk-editor.component";
7 | import { TalkEditorService } from "./talk-editor.service";
8 |
9 | @Component({
10 | changeDetection: ChangeDetectionStrategy.OnPush,
11 | providers: [TalkEditorService],
12 | selector: "mtp-talk-editor-dialog",
13 | styleUrls: ["./talk-editor.component.css"],
14 | templateUrl: "./talk-editor.component.html",
15 | })
16 | export class TalkEditorDialogComponent extends TalkEditorComponent {
17 | constructor(
18 | private _dialogRef: MatDialogRef,
19 | talkEditorService: TalkEditorService,
20 | layoutService: LayoutService,
21 | dialog: MatDialog,
22 | activatedRoute: ActivatedRoute,
23 | router: Router,
24 | changeDetectorRef: ChangeDetectorRef,
25 | ) {
26 | super(talkEditorService, layoutService, dialog, activatedRoute, router, changeDetectorRef);
27 | this.isDialog = true;
28 | }
29 |
30 | public save(cb?: (talk: ITalk) => void): void {
31 | super.save((talk: ITalk) => {
32 | this._dialogRef.close(talk);
33 | });
34 | }
35 |
36 | public close(): void {
37 | this._dialogRef.close();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/speaker-editor/speaker-editor-dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from "@angular/core";
2 | import { MatDialogRef } from "@angular/material";
3 | import { ActivatedRoute, Router } from "@angular/router";
4 | import { LayoutService } from "@dotnetru/core";
5 | import { ISpeaker } from "./interfaces";
6 | import { SpeakerEditorComponent } from "./speaker-editor.component";
7 | import { SpeakerEditorService } from "./speaker-editor.service";
8 |
9 | @Component({
10 | changeDetection: ChangeDetectionStrategy.OnPush,
11 | providers: [SpeakerEditorService],
12 | selector: "mtp-speaker-editor-dialog",
13 | styleUrls: ["./speaker-editor.component.css"],
14 | templateUrl: "./speaker-editor.component.html",
15 | })
16 | export class SpeakerEditorDialogComponent extends SpeakerEditorComponent {
17 | constructor(
18 | private _dialogRef: MatDialogRef,
19 | speakerEditorService: SpeakerEditorService,
20 | layoutService: LayoutService,
21 | activatedRoute: ActivatedRoute,
22 | router: Router,
23 | changeDetectorRef: ChangeDetectorRef,
24 | ) {
25 | super(speakerEditorService, layoutService, activatedRoute, router, changeDetectorRef);
26 | this.isDialog = true;
27 | }
28 |
29 | public save(cb?: (speaker: ISpeaker) => void): void {
30 | super.save((speaker: ISpeaker) => {
31 | this._dialogRef.close(speaker);
32 | });
33 | }
34 |
35 | public close(): void {
36 | this._dialogRef.close();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/DevActivator.Meetups.BL/Extensions/VenueExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using DevActivator.Common.BL.Extensions;
3 | using DevActivator.Meetups.BL.Entities;
4 | using DevActivator.Meetups.BL.Models;
5 |
6 | namespace DevActivator.Meetups.BL.Extensions
7 | {
8 | public static class VenueExtensions
9 | {
10 | public static VenueVm EnsureIsValid(this VenueVm venue)
11 | {
12 | // todo: implement full validation
13 | if (string.IsNullOrWhiteSpace(venue.Name))
14 | {
15 | throw new FormatException(nameof(venue.Name));
16 | }
17 |
18 | if (string.IsNullOrWhiteSpace(venue.Address))
19 | {
20 | throw new FormatException(nameof(venue.Address));
21 | }
22 |
23 | if (venue.City != venue.Id.GetCity())
24 | {
25 | throw new FormatException(nameof(venue.City));
26 | }
27 |
28 | return venue;
29 | }
30 |
31 |
32 | public static VenueVm ToVm(this Venue venue)
33 | => new VenueVm
34 | {
35 | Id = venue.Id,
36 | City = venue.Id.GetCity(),
37 | Name = venue.Name,
38 | Address = venue.Address,
39 | MapUrl = venue.MapUrl
40 | };
41 |
42 | public static Venue Extend(this Venue original, VenueVm venue)
43 | => new Venue
44 | {
45 | Id = original.Id,
46 | Name = venue.Name,
47 | Address = venue.Address,
48 | MapUrl = venue.MapUrl
49 | };
50 | }
51 | }
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/core/layout.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 | import { BehaviorSubject, Observable, Subject } from "rxjs";
3 |
4 | import { IMessage } from "./interfaces";
5 |
6 | @Injectable()
7 | export class LayoutService {
8 | private _messages$: Subject = new Subject();
9 | private _progress$: BehaviorSubject = new BehaviorSubject(false);
10 |
11 | public get messages$(): Observable {
12 | return this._messages$.pipe();
13 | }
14 |
15 | public get progress$(): Observable {
16 | return this._progress$.pipe();
17 | }
18 |
19 | public showInfo(text: string): void {
20 | const message: IMessage = {
21 | duration: 3000,
22 | severity: "info",
23 | text,
24 | };
25 | this._messages$.next(message);
26 | }
27 |
28 | public showWarning(text: string): void {
29 | const message: IMessage = {
30 | duration: 10000,
31 | severity: "warn",
32 | text,
33 | };
34 | this._messages$.next(message);
35 | }
36 |
37 | public showError(text: string): void {
38 | const message: IMessage = {
39 | duration: 60000,
40 | severity: "error",
41 | text,
42 | };
43 | this._messages$.next(message);
44 | }
45 |
46 | public showProgress(percentage: number = 0): void {
47 | this._progress$.next(percentage || true);
48 | }
49 |
50 | public hideProgress(): void {
51 | this._progress$.next(false);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/venue-editor/venue-editor.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from "@angular/common";
2 | import { NgModule } from "@angular/core";
3 | import { FormsModule, ReactiveFormsModule } from "@angular/forms";
4 | import { MatButtonModule, MatFormFieldModule, MatIconModule, MatInputModule } from "@angular/material";
5 | import { RouterModule } from "@angular/router";
6 | import { CoreModule } from "@dotnetru/core";
7 | import { CitySelectModule } from "@dotnetru/shared/city-select";
8 | import { SpeakerListModule } from "@dotnetru/speaker-list";
9 |
10 | import { VenueEditorDialogComponent } from "./venue-editor-dialog.component";
11 | import { VenueEditorComponent } from "./venue-editor.component";
12 |
13 | @NgModule({
14 | declarations: [
15 | VenueEditorComponent,
16 | VenueEditorDialogComponent,
17 | ],
18 | entryComponents: [
19 | VenueEditorComponent,
20 | VenueEditorDialogComponent,
21 | ],
22 | exports: [
23 | VenueEditorComponent,
24 | VenueEditorDialogComponent,
25 | ],
26 | imports: [
27 | RouterModule.forChild([
28 | { path: "venue-creator", component: VenueEditorComponent },
29 | { path: "venue-editor/:venueId", component: VenueEditorComponent },
30 | ]),
31 |
32 | CommonModule,
33 | FormsModule,
34 | ReactiveFormsModule,
35 |
36 | MatButtonModule,
37 | MatFormFieldModule,
38 | MatIconModule,
39 | MatInputModule,
40 |
41 | CoreModule,
42 | CitySelectModule,
43 | SpeakerListModule,
44 | ],
45 | })
46 | export class VenueEditorModule { }
47 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/speaker-editor/speaker-editor.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from "@angular/common";
2 | import { NgModule } from "@angular/core";
3 | import { FormsModule, ReactiveFormsModule } from "@angular/forms";
4 | import { MatButtonModule, MatFormFieldModule, MatIconModule, MatInputModule } from "@angular/material";
5 | import { RouterModule } from "@angular/router";
6 | import { CoreModule } from "@dotnetru/core";
7 | import { FileDialogModule } from "@dotnetru/shared/file-dialog";
8 |
9 | import { SpeakerImageUrlPipe } from "./pipes";
10 | import { SpeakerEditorDialogComponent } from "./speaker-editor-dialog.component";
11 | import { SpeakerEditorComponent } from "./speaker-editor.component";
12 |
13 | @NgModule({
14 | declarations: [
15 | SpeakerEditorComponent,
16 | SpeakerEditorDialogComponent,
17 | SpeakerImageUrlPipe,
18 | ],
19 | entryComponents: [
20 | SpeakerEditorComponent,
21 | SpeakerEditorDialogComponent,
22 | ],
23 | exports: [
24 | SpeakerEditorComponent,
25 | SpeakerImageUrlPipe,
26 | ],
27 | imports: [
28 | RouterModule.forChild([
29 | { path: "speaker-creator", component: SpeakerEditorComponent },
30 | { path: "speaker-editor/:speakerId", component: SpeakerEditorComponent },
31 | ]),
32 |
33 | CommonModule,
34 | FormsModule,
35 | ReactiveFormsModule,
36 |
37 | MatButtonModule,
38 | MatFormFieldModule,
39 | MatIconModule,
40 | MatInputModule,
41 |
42 | CoreModule,
43 | FileDialogModule,
44 | ],
45 | })
46 | export class SpeakerEditorModule { }
47 |
--------------------------------------------------------------------------------
/.teamcity/patches/buildTypes/Build.kts:
--------------------------------------------------------------------------------
1 | package patches.buildTypes
2 |
3 | import jetbrains.buildServer.configs.kotlin.v2018_2.*
4 | import jetbrains.buildServer.configs.kotlin.v2018_2.buildFeatures.PullRequests
5 | import jetbrains.buildServer.configs.kotlin.v2018_2.buildFeatures.commitStatusPublisher
6 | import jetbrains.buildServer.configs.kotlin.v2018_2.buildFeatures.pullRequests
7 | import jetbrains.buildServer.configs.kotlin.v2018_2.ui.*
8 |
9 | /*
10 | This patch script was generated by TeamCity on settings change in UI.
11 | To apply the patch, change the buildType with id = 'Build'
12 | accordingly, and delete the patch script.
13 | */
14 | changeBuildType(RelativeId("Build")) {
15 | vcs {
16 |
17 | check(cleanCheckout == false) {
18 | "Unexpected option value: cleanCheckout = $cleanCheckout"
19 | }
20 | cleanCheckout = true
21 | }
22 |
23 | features {
24 | add {
25 | pullRequests {
26 | provider = github {
27 | authType = token {
28 | token = "credentialsJSON:c97a1c72-0f46-4979-a3de-93abed177766"
29 | }
30 | filterAuthorRole = PullRequests.GitHubRoleFilter.MEMBER
31 | }
32 | }
33 | }
34 | add {
35 | commitStatusPublisher {
36 | publisher = github {
37 | githubUrl = "https://api.github.com"
38 | authType = personalToken {
39 | token = "credentialsJSON:c97a1c72-0f46-4979-a3de-93abed177766"
40 | }
41 | }
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/talk-editor/talk-editor.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from "@angular/common";
2 | import { NgModule } from "@angular/core";
3 | import { FormsModule, ReactiveFormsModule } from "@angular/forms";
4 | import { MatButtonModule, MatCardModule, MatFormFieldModule, MatIconModule, MatInputModule } from "@angular/material";
5 | import { RouterModule } from "@angular/router";
6 | import { CoreModule } from "@dotnetru/core";
7 | import { SpeakerEditorModule } from "@dotnetru/pages/speaker-editor";
8 | import { SpeakerListModule } from "@dotnetru/speaker-list";
9 |
10 | import { TalkEditorDialogComponent } from "./talk-editor-dialog.component";
11 | import { TalkEditorComponent } from "./talk-editor.component";
12 |
13 | @NgModule({
14 | declarations: [
15 | TalkEditorComponent,
16 | TalkEditorDialogComponent,
17 | ],
18 | entryComponents: [
19 | TalkEditorComponent,
20 | TalkEditorDialogComponent,
21 | ],
22 | exports: [
23 | TalkEditorComponent,
24 | TalkEditorDialogComponent,
25 | ],
26 | imports: [
27 | RouterModule.forChild([
28 | { path: "talk-creator", component: TalkEditorComponent },
29 | { path: "talk-editor/:talkId", component: TalkEditorComponent },
30 | ]),
31 |
32 | CommonModule,
33 | FormsModule,
34 | ReactiveFormsModule,
35 |
36 | MatButtonModule,
37 | MatCardModule,
38 | MatFormFieldModule,
39 | MatIconModule,
40 | MatInputModule,
41 |
42 | CoreModule,
43 | SpeakerListModule,
44 | SpeakerEditorModule,
45 | ],
46 | })
47 | export class TalkEditorModule { }
48 |
--------------------------------------------------------------------------------
/DevActivator.Meetups.BL/Services/CachedTalkService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using DevActivator.Common.BL.Caching;
5 | using DevActivator.Meetups.BL.Interfaces;
6 | using DevActivator.Meetups.BL.Models;
7 |
8 | namespace DevActivator.Meetups.BL.Services
9 | {
10 | public class CachedTalkService : ITalkService
11 | {
12 | private readonly ICache _cache;
13 | private readonly ITalkService _talkService;
14 |
15 | public CachedTalkService(ICache cache, ITalkService talkService)
16 | {
17 | _cache = cache;
18 | _talkService = talkService;
19 | }
20 |
21 | public Task> GetAllTalksAsync()
22 | => _cache.GetOrCreateAsync(nameof(GetAllTalksAsync),
23 | cacheEntry =>
24 | {
25 | cacheEntry.SlidingExpiration = TimeSpan.FromMinutes(10);
26 | return _talkService.GetAllTalksAsync();
27 | }
28 | );
29 |
30 | public Task GetTalkAsync(string talkId)
31 | => _talkService.GetTalkAsync(talkId);
32 |
33 | public async Task AddTalkAsync(TalkVm talk)
34 | {
35 | var result = await _talkService.AddTalkAsync(talk).ConfigureAwait(false);
36 |
37 | _cache.Remove(nameof(GetAllTalksAsync));
38 |
39 | return result;
40 | }
41 |
42 | public async Task UpdateTalkAsync(TalkVm talk)
43 | {
44 | var result = await _talkService.UpdateTalkAsync(talk).ConfigureAwait(false);
45 |
46 | _cache.Remove(nameof(GetAllTalksAsync));
47 |
48 | return result;
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/DevActivator.Meetups.BL/Services/CachedVenueService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using DevActivator.Common.BL.Caching;
5 | using DevActivator.Meetups.BL.Interfaces;
6 | using DevActivator.Meetups.BL.Models;
7 |
8 | namespace DevActivator.Meetups.BL.Services
9 | {
10 | public class CachedVenueService : IVenueService
11 | {
12 | private readonly ICache _cache;
13 | private readonly IVenueService _venueService;
14 |
15 | public CachedVenueService(ICache cache, IVenueService venueService)
16 | {
17 | _cache = cache;
18 | _venueService = venueService;
19 | }
20 |
21 | public Task> GetAllVenuesAsync()
22 | => _cache.GetOrCreateAsync(nameof(GetAllVenuesAsync),
23 | cacheEntry =>
24 | {
25 | cacheEntry.SlidingExpiration = TimeSpan.FromMinutes(10);
26 | return _venueService.GetAllVenuesAsync();
27 | }
28 | );
29 |
30 | public Task GetVenueAsync(string venueId)
31 | => _venueService.GetVenueAsync(venueId);
32 |
33 | public async Task AddVenueAsync(VenueVm venue)
34 | {
35 | var result = await _venueService.AddVenueAsync(venue).ConfigureAwait(false);
36 |
37 | _cache.Remove(nameof(GetAllVenuesAsync));
38 |
39 | return result;
40 | }
41 |
42 | public async Task UpdateVenueAsync(VenueVm venue)
43 | {
44 | var result = await _venueService.UpdateVenueAsync(venue).ConfigureAwait(false);
45 |
46 | _cache.Remove(nameof(GetAllVenuesAsync));
47 |
48 | return result;
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/shared/city-select/city-select.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, Input } from "@angular/core";
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";
3 |
4 | import { CITIES } from "./constants";
5 | import { City } from "./enums";
6 |
7 | @Component({
8 | changeDetection: ChangeDetectionStrategy.OnPush,
9 | providers: [
10 | { provide: NG_VALUE_ACCESSOR, useExisting: CitySelectComponent, multi: true },
11 | ],
12 | selector: "mtp-city-select",
13 | styleUrls: ["./city-select.component.css"],
14 | templateUrl: "./city-select.component.html",
15 | })
16 | export class CitySelectComponent implements ControlValueAccessor {
17 | public CITIES: City[] = CITIES;
18 |
19 | @Input()
20 | public set readonly(newValue: boolean) {
21 | if (newValue === true) {
22 | this.CITIES = [this._value];
23 | } else {
24 | this.CITIES = CITIES;
25 | }
26 | }
27 |
28 | @Input()
29 | public get value(): City { return this._value; }
30 | public set value(newValue: City) {
31 | if (newValue && newValue !== this._value) {
32 | this._value = newValue;
33 | this._changed.forEach((f) => f(newValue));
34 | }
35 | }
36 |
37 | private _value: City = City.Spb;
38 | private _changed = new Array<(value: City) => void>();
39 | private _touched = new Array<() => void>();
40 |
41 | public touch() {
42 | this._touched.forEach((f) => f());
43 | }
44 |
45 | public writeValue(newValue: City): void {
46 | this._value = newValue;
47 | }
48 |
49 | public registerOnChange(fn: any): void {
50 | this._changed.push(fn);
51 | }
52 |
53 | public registerOnTouched(fn: any): void {
54 | this._touched.push(fn);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/shared/file-dialog/file-dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, Input, Output, ViewChild } from "@angular/core";
2 |
3 | import { FileService } from "./file.service";
4 | import { IAcceptedFile, IRejectedFile, IVerifiedFiles } from "./interfaces";
5 |
6 | @Component({
7 | changeDetection: ChangeDetectionStrategy.OnPush,
8 | providers: [FileService],
9 | selector: "mtp-file-dialog",
10 | templateUrl: "./file-dialog.component.html",
11 | })
12 | export class FileDialogComponent {
13 | @ViewChild("inputFile") public nativeInputFile?: ElementRef;
14 |
15 | @Input() public disabled: boolean = false;
16 | @Input() public accept: string = "";
17 | @Input() public multiple: boolean = true;
18 | @Input() public maxFileSize: number = 0;
19 |
20 | @Output() public readonly filesAccepted: EventEmitter = new EventEmitter();
21 | @Output() public readonly filesRejected: EventEmitter = new EventEmitter();
22 |
23 | constructor(
24 | private _fileService: FileService,
25 | ) { }
26 |
27 | public onNativeInputFileSelect(event: Event): void {
28 | this._fileService.configure(this.accept.split(",").filter((x) => x !== ""), this.maxFileSize);
29 | const result: IVerifiedFiles = this._fileService.verifyFiles(event);
30 |
31 | this.filesAccepted.emit(result.acceptedFiles);
32 | this.filesRejected.emit(result.rejectedFiles);
33 |
34 | if (this.nativeInputFile) {
35 | this.nativeInputFile.nativeElement.value = "";
36 | }
37 |
38 | this.preventAndStopEventPropagation(event);
39 | }
40 |
41 | public uploadFile(): void {
42 | this.nativeInputFile!.nativeElement.click();
43 | }
44 |
45 | private preventAndStopEventPropagation(event: Event): void {
46 | event.preventDefault();
47 | event.stopPropagation();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/timepad/timepad.module.ts:
--------------------------------------------------------------------------------
1 | import { DragDropModule } from "@angular/cdk/drag-drop";
2 | import { CommonModule } from "@angular/common";
3 | import { NgModule } from "@angular/core";
4 | import { FormsModule } from "@angular/forms";
5 | import {
6 | MatButtonModule,
7 | MatExpansionModule,
8 | MatIconModule,
9 | MatInputModule,
10 | MatListModule,
11 | MatSelectModule,
12 | } from "@angular/material";
13 | import { RouterModule } from "@angular/router";
14 | import { CoreModule } from "@dotnetru/core";
15 | import { FriendListModule } from "@dotnetru/friend-list";
16 | import { FriendEditorModule } from "@dotnetru/pages/friend-editor";
17 | import { MeetupEditorModule } from "@dotnetru/pages/meetup-editor";
18 | import { SpeakerEditorModule } from "@dotnetru/pages/speaker-editor";
19 | import { TalkEditorModule } from "@dotnetru/pages/talk-editor";
20 | import { VenueEditorModule } from "@dotnetru/pages/venue-editor";
21 | import { VenueListModule } from "@dotnetru/venue-list";
22 |
23 | import { TimepadComponent } from "./timepad.component";
24 |
25 | @NgModule({
26 | declarations: [
27 | TimepadComponent,
28 | ],
29 | exports: [
30 | TimepadComponent,
31 | ],
32 | imports: [
33 | CommonModule,
34 | FormsModule,
35 |
36 | RouterModule.forChild([
37 | { path: "timepad", component: TimepadComponent },
38 | { path: "timepad/:meetupId", component: TimepadComponent },
39 | ]),
40 |
41 | CoreModule,
42 |
43 | DragDropModule,
44 | MatButtonModule,
45 | MatExpansionModule,
46 | MatIconModule,
47 | MatInputModule,
48 | MatListModule,
49 | MatSelectModule,
50 |
51 | FriendListModule,
52 | FriendEditorModule,
53 | MeetupEditorModule,
54 | SpeakerEditorModule,
55 | TalkEditorModule,
56 | VenueEditorModule,
57 | VenueListModule,
58 | ],
59 | })
60 | export class TimepadModule { }
61 |
--------------------------------------------------------------------------------
/local.Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/core/sdk:2.1 AS build-env
2 |
3 | ################################################################
4 | # Install node.js v10.x
5 | #
6 | RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - \
7 | && apt update \
8 | && apt install -y nodejs
9 |
10 | ################################################################
11 | # Cache layer with package.json for node_modules
12 | #
13 | ADD ./DevActivator/package.json ./DevActivator/npm-shrinkwrap.json ./tmp/
14 | RUN cd /tmp \
15 | && npm i npm@latest -g \
16 | && npm ci
17 |
18 | ################################################################
19 | # Cache layer with NuGet.Config and *.csproj for nuget packages
20 | #
21 | COPY ./DevActivator.Common.BL/DevActivator.Common.BL.csproj ./app/DevActivator.Common.BL/
22 | COPY ./DevActivator.Common.DAL/DevActivator.Common.DAL.csproj ./app/DevActivator.Common.DAL/
23 |
24 | COPY ./DevActivator.Meetups.BL/DevActivator.Meetups.BL.csproj ./app/DevActivator.Meetups.BL/
25 | COPY ./DevActivator.Meetups.DAL/DevActivator.Meetups.DAL.csproj ./app/DevActivator.Meetups.DAL/
26 | COPY ./DevActivator.Meetups.Tests/DevActivator.Meetups.Tests.csproj ./app/DevActivator.Meetups.Tests/
27 |
28 | COPY ./DevActivator/DevActivator.csproj ./app/DevActivator/
29 | COPY ./ElectronNetAngular.sln ./app/
30 |
31 | RUN cd /app \
32 | && dotnet restore \
33 | && mkdir -p /home/app \
34 | && cp -a /app /home
35 |
36 | ################################################################
37 | # Copy everything else and build
38 | #
39 | COPY . /home/app
40 | RUN cp -a /tmp/node_modules /home/app/DevActivator
41 |
42 | WORKDIR /home/app/DevActivator
43 | RUN dotnet publish -c Debug -o out --no-restore --no-dependencies
44 |
45 | ################################################################
46 | # Build runtime image
47 | #
48 | FROM mcr.microsoft.com/dotnet/core/aspnet:2.1
49 | WORKDIR /home/app
50 | COPY --from=build-env /home/app/DevActivator/out .
51 |
52 | ENTRYPOINT ["dotnet", "DevActivator.dll"]
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/components/toolbar/toolbar.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from "@angular/core";
2 | import { MatDrawer, MatSnackBar, MatSnackBarConfig } from "@angular/material";
3 | import { IMessage, LayoutService } from "@dotnetru/core";
4 |
5 | @Component({
6 | changeDetection: ChangeDetectionStrategy.OnPush,
7 | selector: "mtp-toolbar",
8 | styleUrls: ["./toolbar.component.css"],
9 | templateUrl: "./toolbar.component.html",
10 | })
11 | export class ToolbarComponent implements OnInit {
12 | @Input() public drawer?: MatDrawer;
13 |
14 | public percentage: number = 0;
15 | public showProgressBar: boolean = false;
16 |
17 | constructor(
18 | private _snackBar: MatSnackBar,
19 | private _layoutService: LayoutService,
20 | private _changeDetectorRef: ChangeDetectorRef,
21 | ) { }
22 |
23 | public getProgressMode = (): string => {
24 | return !this.percentage
25 | ? "indeterminate"
26 | : (this.percentage < 100 ? "determinate" : "query");
27 | }
28 |
29 | public ngOnInit(): void {
30 | this._layoutService.messages$.subscribe((message: IMessage) => {
31 | const snackBarConfig: MatSnackBarConfig = {
32 | direction: "ltr",
33 | duration: message.duration,
34 | horizontalPosition: "center",
35 | panelClass: `snack-bar-${message.severity}`,
36 | politeness: "assertive",
37 | verticalPosition: "top",
38 | };
39 | this._snackBar.open(message.text, "Закрыть", snackBarConfig);
40 | });
41 |
42 | this._layoutService.progress$.subscribe((progress: boolean | number) => {
43 | this.showProgressBar = !!progress;
44 | this.percentage = this.showProgressBar && typeof progress === "number" ? progress : 0;
45 | this._changeDetectorRef.detectChanges();
46 | });
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/search/search.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component } from "@angular/core";
2 | import { Router } from "@angular/router";
3 | import { IAutocompleteRow } from "@dotnetru/shared/autocomplete";
4 |
5 | @Component({
6 | changeDetection: ChangeDetectionStrategy.OnPush,
7 | selector: "mtp-search",
8 | templateUrl: "./search.component.html",
9 | })
10 | export class SearchPageComponent {
11 | constructor(
12 | private _router: Router,
13 | ) { }
14 |
15 | public onSpeakerSelected(speaker: IAutocompleteRow): void {
16 | this._router.navigateByUrl(`speaker-editor/${speaker.id}`);
17 | }
18 |
19 | public addSpeaker(): void {
20 | this._router.navigateByUrl(`speaker-creator`);
21 | }
22 |
23 | public onTalkSelected(talk: IAutocompleteRow): void {
24 | this._router.navigateByUrl(`talk-editor/${talk.id}`);
25 | }
26 |
27 | public addTalk(): void {
28 | this._router.navigateByUrl(`talk-creator`);
29 | }
30 |
31 | public onVenueSelected(venueId: string): void {
32 | this._router.navigateByUrl(`venue-editor/${venueId}`);
33 | }
34 |
35 | public addVenue(): void {
36 | this._router.navigateByUrl(`venue-creator`);
37 | }
38 |
39 | public onFriendSelected(friendId: string): void {
40 | this._router.navigateByUrl(`friend-editor/${friendId}`);
41 | }
42 |
43 | public addFriend(): void {
44 | this._router.navigateByUrl(`friend-creator`);
45 | }
46 |
47 | public onMeetupSelected(meetup: IAutocompleteRow): void {
48 | this._router.navigateByUrl(`meetup-editor/${meetup.id}`);
49 | }
50 |
51 | public addMeetup(): void {
52 | this._router.navigateByUrl(`meetup-creator`);
53 | }
54 |
55 | public addTimepad(): void {
56 | this._router.navigateByUrl(`timepad`);
57 | }
58 |
59 | public onTimepadSelected(meetup: IAutocompleteRow): void {
60 | this._router.navigateByUrl(`timepad/${meetup.id}`);
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/shared/file-dialog/file.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 |
3 | import { RejectionReason } from "./enums";
4 | import { IVerifiedFiles } from "./interfaces";
5 |
6 | @Injectable()
7 | export class FileService {
8 | private _supportedFileTypes: string[] = [];
9 | private _maximumFileSizeInBytes: number = 0;
10 |
11 | public configure(supportFileFormats: string[], maximumFileSize: number): void {
12 | this._supportedFileTypes = supportFileFormats;
13 | this._maximumFileSizeInBytes = maximumFileSize;
14 | console.warn(this._supportedFileTypes);
15 | }
16 |
17 | public verifyFiles(event: Event): IVerifiedFiles {
18 | const result: IVerifiedFiles = {
19 | acceptedFiles: [],
20 | rejectedFiles: [],
21 | };
22 |
23 | const target: EventTarget | null = event.target || event.srcElement;
24 | if (!(target instanceof HTMLInputElement)) {
25 | return result;
26 | }
27 |
28 | if (target.files === null || target.files.length === 0) {
29 | return result;
30 | }
31 |
32 | for (const currentFile of Array.from(target.files)) {
33 | if (this._supportedFileTypes.length > 0) {
34 | const fileTypeIndex: number = this._supportedFileTypes.indexOf(currentFile.type);
35 | if (fileTypeIndex === -1) {
36 | result.rejectedFiles.push({ file: currentFile, reason: RejectionReason.FileType });
37 | continue;
38 | }
39 | }
40 |
41 | if (this._maximumFileSizeInBytes > 0) {
42 | if (this._maximumFileSizeInBytes < currentFile.size) {
43 | result.rejectedFiles.push({ file: currentFile, reason: RejectionReason.FileSize });
44 | continue;
45 | }
46 | }
47 |
48 | result.acceptedFiles.push({ file: currentFile });
49 | }
50 |
51 | return result;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/DevActivator.Meetups.BL/Extensions/TalkExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using DevActivator.Meetups.BL.Entities;
4 | using DevActivator.Meetups.BL.Models;
5 |
6 | namespace DevActivator.Meetups.BL.Extensions
7 | {
8 | public static class TalkExtensions
9 | {
10 | public static TalkVm EnsureIsValid(this TalkVm talk)
11 | {
12 | // todo: implement full validation
13 | if (string.IsNullOrWhiteSpace(talk.Title))
14 | {
15 | throw new FormatException(nameof(talk.Title));
16 | }
17 |
18 | if (string.IsNullOrWhiteSpace(talk.Description))
19 | {
20 | throw new FormatException(nameof(talk.Description));
21 | }
22 |
23 | if (talk.SpeakerIds == null || talk.SpeakerIds.Count == 0 ||
24 | talk.SpeakerIds.Any(x => string.IsNullOrWhiteSpace(x.SpeakerId)))
25 | {
26 | throw new FormatException(nameof(talk.SpeakerIds));
27 | }
28 |
29 | return talk;
30 | }
31 |
32 | public static TalkVm ToVm(this Talk talk)
33 | => new TalkVm
34 | {
35 | Id = talk.Id,
36 | SpeakerIds = talk.SpeakerIds.Select(x => new SpeakerReference {SpeakerId = x}).ToList(),
37 | Title = talk.Title,
38 | Description = talk.Description,
39 | CodeUrl = talk.CodeUrl,
40 | SlidesUrl = talk.SlidesUrl,
41 | VideoUrl = talk.VideoUrl
42 | };
43 |
44 | public static Talk Extend(this Talk original, TalkVm talk)
45 | => new Talk
46 | {
47 | Id = original.Id,
48 | SpeakerIds = talk.SpeakerIds.Select(x => x.SpeakerId).ToList(),
49 | Title = talk.Title,
50 | Description = talk.Description,
51 | CodeUrl = talk.CodeUrl,
52 | SlidesUrl = talk.SlidesUrl,
53 | VideoUrl = talk.VideoUrl
54 | };
55 | }
56 | }
--------------------------------------------------------------------------------
/DevActivator.Meetups.BL/Services/CachedMeetupService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using DevActivator.Common.BL.Caching;
5 | using DevActivator.Meetups.BL.Interfaces;
6 | using DevActivator.Meetups.BL.Models;
7 |
8 | namespace DevActivator.Meetups.BL.Services
9 | {
10 | public class CachedMeetupService : IMeetupService
11 | {
12 | private readonly ICache _cache;
13 | private readonly IMeetupService _meetupService;
14 |
15 | public CachedMeetupService(ICache cache, IMeetupService meetupServiceImplementation)
16 | {
17 | _cache = cache;
18 | _meetupService = meetupServiceImplementation;
19 | }
20 |
21 | public Task> GetAllMeetupsAsync()
22 | => _cache.GetOrCreateAsync(nameof(GetAllMeetupsAsync),
23 | cacheEntry =>
24 | {
25 | cacheEntry.SlidingExpiration = TimeSpan.FromMinutes(10);
26 | return _meetupService.GetAllMeetupsAsync();
27 | }
28 | );
29 |
30 | public Task GetMeetupAsync(string meetupId)
31 | => _meetupService.GetMeetupAsync(meetupId);
32 |
33 | public async Task AddMeetupAsync(MeetupVm meetup)
34 | {
35 | var result = await _meetupService.AddMeetupAsync(meetup).ConfigureAwait(false);
36 |
37 | _cache.Remove(nameof(GetAllMeetupsAsync));
38 |
39 | return result;
40 | }
41 |
42 | public async Task UpdateMeetupAsync(MeetupVm meetup)
43 | {
44 | var result = await _meetupService.UpdateMeetupAsync(meetup).ConfigureAwait(false);
45 |
46 | _cache.Remove(nameof(GetAllMeetupsAsync));
47 | if (_cache.TryGetValue>(nameof(GetAllMeetupsAsync), out var meetups))
48 | {
49 | meetups.ForEach(x => _cache.Remove($"{nameof(GetMeetupAsync)}_{x.Id}"));
50 | }
51 |
52 | return result;
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/venue-editor/venue-editor-dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject } from "@angular/core";
2 | import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material";
3 | import { ActivatedRoute, Router } from "@angular/router";
4 | import { LayoutService } from "@dotnetru/core";
5 | import { IVenue } from "./interfaces";
6 | import { VenueEditorComponent } from "./venue-editor.component";
7 | import { VenueEditorService } from "./venue-editor.service";
8 |
9 | @Component({
10 | changeDetection: ChangeDetectionStrategy.OnPush,
11 | providers: [VenueEditorService],
12 | selector: "mtp-venue-editor-dialog",
13 | styleUrls: ["./venue-editor.component.css"],
14 | templateUrl: "./venue-editor.component.html",
15 | })
16 | export class VenueEditorDialogComponent extends VenueEditorComponent implements AfterViewInit {
17 | constructor(
18 | private _dialogRef: MatDialogRef,
19 | @Inject(MAT_DIALOG_DATA) private _data: IVenue | undefined,
20 | venueEditorService: VenueEditorService,
21 | layoutService: LayoutService,
22 | activatedRoute: ActivatedRoute,
23 | router: Router,
24 | changeDetectorRef: ChangeDetectorRef,
25 | ) {
26 | super(venueEditorService, layoutService, activatedRoute, router, changeDetectorRef);
27 | this.isDialog = true;
28 | }
29 |
30 | public ngAfterViewInit(): void {
31 | // todo: remove code duplication in VenueEditorComponent
32 | // find: this._venueEditorService.venue$ .subscribe(
33 | if (this._data) {
34 | this.venue = this._data;
35 | this.editMode = true;
36 | this._changeDetectorRef.detectChanges();
37 | }
38 | }
39 |
40 | public save(cb?: (venue: IVenue) => void): void {
41 | super.save((venue: IVenue) => {
42 | this._dialogRef.close(venue);
43 | });
44 | }
45 |
46 | public close(): void {
47 | this._dialogRef.close();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/DevActivator.Meetups.BL/Services/CachedFriendService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using DevActivator.Common.BL.Caching;
5 | using DevActivator.Meetups.BL.Entities;
6 | using DevActivator.Meetups.BL.Interfaces;
7 | using DevActivator.Meetups.BL.Models;
8 |
9 | namespace DevActivator.Meetups.BL.Services
10 | {
11 | public class CachedFriendService : IFriendService
12 | {
13 | private readonly ICache _cache;
14 | private readonly IFriendService _friendService;
15 |
16 | public CachedFriendService(ICache cache, IFriendService friendServiceImplementation)
17 | {
18 | _cache = cache;
19 | _friendService = friendServiceImplementation;
20 | }
21 |
22 | public Task> GetAllFriendsAsync()
23 | => _cache.GetOrCreateAsync(nameof(GetAllFriendsAsync),
24 | cacheEntry =>
25 | {
26 | cacheEntry.SlidingExpiration = TimeSpan.FromMinutes(10);
27 | return _friendService.GetAllFriendsAsync();
28 | }
29 | );
30 |
31 | public Task GetFriendAsync(string friendId)
32 | => _friendService.GetFriendAsync(friendId);
33 |
34 | public async Task AddFriendAsync(Friend friend)
35 | {
36 | var result = await _friendService.AddFriendAsync(friend).ConfigureAwait(false);
37 |
38 | _cache.Remove(nameof(GetAllFriendsAsync));
39 |
40 | return result;
41 | }
42 |
43 | public async Task UpdateFriendAsync(Friend friend)
44 | {
45 | var result = await _friendService.UpdateFriendAsync(friend).ConfigureAwait(false);
46 |
47 | _cache.Remove(nameof(GetAllFriendsAsync));
48 | if (_cache.TryGetValue>(nameof(GetAllFriendsAsync), out var friends))
49 | {
50 | friends.ForEach(x => _cache.Remove($"{nameof(GetFriendAsync)}_{x.Id}"));
51 | }
52 |
53 | return result;
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/DevActivator.Meetups.BL/Services/CachedSpeakerService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading.Tasks;
4 | using DevActivator.Common.BL.Caching;
5 | using DevActivator.Meetups.BL.Interfaces;
6 | using DevActivator.Meetups.BL.Models;
7 |
8 | namespace DevActivator.Meetups.BL.Services
9 | {
10 | public class CachedSpeakerService : ISpeakerService
11 | {
12 | private readonly ICache _cache;
13 | private readonly ISpeakerService _speakerService;
14 |
15 | public CachedSpeakerService(ICache cache, ISpeakerService speakerServiceImplementation)
16 | {
17 | _cache = cache;
18 | _speakerService = speakerServiceImplementation;
19 | }
20 |
21 | public Task> GetAllSpeakersAsync()
22 | => _cache.GetOrCreateAsync(nameof(GetAllSpeakersAsync),
23 | cacheEntry =>
24 | {
25 | cacheEntry.SlidingExpiration = TimeSpan.FromMinutes(10);
26 | return _speakerService.GetAllSpeakersAsync();
27 | }
28 | );
29 |
30 | public Task GetSpeakerAsync(string speakerId)
31 | => _speakerService.GetSpeakerAsync(speakerId);
32 |
33 | public async Task AddSpeakerAsync(SpeakerVm speaker)
34 | {
35 | var result = await _speakerService.AddSpeakerAsync(speaker).ConfigureAwait(false);
36 |
37 | _cache.Remove(nameof(GetAllSpeakersAsync));
38 |
39 | return result;
40 | }
41 |
42 | public async Task UpdateSpeakerAsync(SpeakerVm speaker)
43 | {
44 | var result = await _speakerService.UpdateSpeakerAsync(speaker).ConfigureAwait(false);
45 |
46 | _cache.Remove(nameof(GetAllSpeakersAsync));
47 | if (_cache.TryGetValue>(nameof(GetAllSpeakersAsync), out var speakers))
48 | {
49 | speakers.ForEach(x => _cache.Remove($"{nameof(GetSpeakerAsync)}_{x.Id}"));
50 | }
51 |
52 | return result;
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/app.shared.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from "@angular/common";
2 | import { HttpClientModule } from "@angular/common/http";
3 | import { NgModule } from "@angular/core";
4 | import { MatSidenavModule } from "@angular/material";
5 | import { RouterModule } from "@angular/router";
6 | import { CoreModule } from "@dotnetru/core";
7 | import { FriendEditorModule } from "@dotnetru/pages/friend-editor";
8 | import { MeetupEditorModule } from "@dotnetru/pages/meetup-editor";
9 | import { SearchPageModule } from "@dotnetru/pages/search";
10 | import { SpeakerEditorModule } from "@dotnetru/pages/speaker-editor";
11 | import { TalkEditorModule } from "@dotnetru/pages/talk-editor";
12 | import { TimepadModule } from "@dotnetru/pages/timepad";
13 | import { VenueEditorModule } from "@dotnetru/pages/venue-editor";
14 | import { AutocompleteModule } from "@dotnetru/shared/autocomplete";
15 | import { SpeakerListModule } from "@dotnetru/speaker-list";
16 |
17 | import { AppComponent } from "./app.component";
18 | import { NavMenuModule } from "./components/navmenu/navmenu.module";
19 | import { ToolbarModule } from "./components/toolbar/toolbar.module";
20 | import { SearchPageComponent } from "./pages/search/search.component";
21 |
22 | @NgModule({
23 | declarations: [
24 | AppComponent,
25 | ],
26 | imports: [
27 | CommonModule,
28 | HttpClientModule,
29 |
30 | MatSidenavModule,
31 | ToolbarModule,
32 | NavMenuModule,
33 |
34 | CoreModule,
35 |
36 | AutocompleteModule,
37 |
38 | SpeakerEditorModule,
39 | SpeakerListModule,
40 |
41 | FriendEditorModule,
42 | MeetupEditorModule,
43 | TalkEditorModule,
44 | VenueEditorModule,
45 |
46 | TimepadModule,
47 |
48 | SearchPageModule,
49 |
50 | RouterModule.forRoot([
51 | { path: "", redirectTo: "search", pathMatch: "full" },
52 | { path: "search", component: SearchPageComponent },
53 | { path: "**", redirectTo: "search" },
54 | ]),
55 | ],
56 | })
57 | export class AppModuleShared { }
58 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/meetup-editor/session-editor.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, Output } from "@angular/core";
2 | import { MatDialog } from "@angular/material";
3 | import { ITalk, TalkEditorDialogComponent } from "@dotnetru/pages/talk-editor";
4 | import { IAutocompleteRow } from "@dotnetru/shared/autocomplete";
5 |
6 | import { ISession } from "./interfaces";
7 |
8 | @Component({
9 | changeDetection: ChangeDetectionStrategy.OnPush,
10 | selector: "mtp-session-editor",
11 | styleUrls: ["./session-editor.component.css"],
12 | templateUrl: "./session-editor.component.html",
13 | })
14 | export class SessionEditorComponent {
15 | @Input() public title: string = "";
16 |
17 | @Input()
18 | public get session(): ISession {
19 | return this._session;
20 | }
21 | public set session(value: ISession) {
22 | this._session = value;
23 | }
24 |
25 | @Output() public readonly talkSelected = new EventEmitter();
26 | @Output() public readonly removeSession = new EventEmitter();
27 |
28 | private _session: ISession = { talkId: "" };
29 |
30 | constructor(
31 | private _dialog: MatDialog,
32 | private _changeDetectorRef: ChangeDetectorRef,
33 | ) { }
34 |
35 | public onTalkSelected(row: IAutocompleteRow): void {
36 | this.talkSelected.emit(row.id);
37 | }
38 |
39 | public onRemoveSession(): void {
40 | this.removeSession.emit();
41 | }
42 |
43 | public tryFillEndTime(): void {
44 | if (this.session.startTime && !this.session.endTime) {
45 | this.session.endTime = this.session.startTime.clone().add(1, "hour");
46 | }
47 | }
48 |
49 | public createTalk(): void {
50 | const dialogRef = this._dialog.open(TalkEditorDialogComponent, {
51 | panelClass: "full-height",
52 | width: "640px",
53 | });
54 |
55 | dialogRef.afterClosed().subscribe((talk?: ITalk) => {
56 | if (talk && talk.id) {
57 | this.talkSelected.emit(talk.id);
58 | }
59 | });
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/meetup-editor/session-editor.component.html:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 | Время начала
10 |
12 |
17 |
24 |
25 |
26 |
27 | Время окончания
28 |
30 |
35 |
41 |
42 |
43 |
44 |
48 | fiber_new
49 | Создать доклад
50 |
51 |
--------------------------------------------------------------------------------
/DevActivator.Meetups.BL/Services/TalkService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using DevActivator.Meetups.BL.Entities;
6 | using DevActivator.Meetups.BL.Extensions;
7 | using DevActivator.Meetups.BL.Interfaces;
8 | using DevActivator.Meetups.BL.Models;
9 |
10 | namespace DevActivator.Meetups.BL.Services
11 | {
12 | public class TalkService : ITalkService
13 | {
14 | private readonly ITalkProvider _talkProvider;
15 |
16 | public TalkService(ITalkProvider talkProvider)
17 | {
18 | _talkProvider = talkProvider;
19 | }
20 |
21 | public async Task> GetAllTalksAsync()
22 | {
23 | var talks = await _talkProvider.GetAllTalksAsync().ConfigureAwait(false);
24 | return talks
25 | .Select(x => new AutocompleteRow {Id = x.Id, Name = x.Title})
26 | .ToList();
27 | }
28 |
29 | public async Task GetTalkAsync(string talkId)
30 | {
31 | var talk = await _talkProvider.GetTalkOrDefaultAsync(talkId).ConfigureAwait(false);
32 | return talk.ToVm();
33 | }
34 |
35 | public async Task AddTalkAsync(TalkVm talk)
36 | {
37 | talk.EnsureIsValid();
38 | var original = await _talkProvider.GetTalkOrDefaultAsync(talk.Id).ConfigureAwait(false);
39 | if (original != null)
40 | {
41 | throw new FormatException($"Данный {nameof(talk.Id)} \"{talk.Id}\" уже занят");
42 | }
43 |
44 | var entity = new Talk {Id = talk.Id}.Extend(talk);
45 | var res = await _talkProvider.SaveTalkAsync(entity).ConfigureAwait(false);
46 | return res.ToVm();
47 | }
48 |
49 | public async Task UpdateTalkAsync(TalkVm talk)
50 | {
51 | talk.EnsureIsValid();
52 | var original = await _talkProvider.GetTalkOrDefaultAsync(talk.Id).ConfigureAwait(false);
53 | var res = await _talkProvider.SaveTalkAsync(original.Extend(talk)).ConfigureAwait(false);
54 | return res.ToVm();
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/meetup-editor/meetup-editor.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from "@angular/common";
2 | import { NgModule } from "@angular/core";
3 | import { FormsModule, ReactiveFormsModule } from "@angular/forms";
4 | import {
5 | MAT_DATE_LOCALE,
6 | MatButtonModule,
7 | MatCardModule,
8 | MatDatepickerModule,
9 | MatFormFieldModule,
10 | MatIconModule,
11 | MatInputModule,
12 | MatSelectModule,
13 | } from "@angular/material";
14 | import { RouterModule } from "@angular/router";
15 | import { CoreModule } from "@dotnetru/core";
16 | import { FriendListModule } from "@dotnetru/friend-list";
17 | import { TalkListModule } from "@dotnetru/talk-list";
18 | import { VenueListModule } from "@dotnetru/venue-list";
19 | import { MatDatetimepickerModule } from "@mat-datetimepicker/core";
20 | import { MatMomentDatetimeModule } from "@mat-datetimepicker/moment";
21 |
22 | import { MeetupEditorComponent } from "./meetup-editor.component";
23 | import { SessionEditorComponent } from "./session-editor.component";
24 |
25 | @NgModule({
26 | declarations: [
27 | MeetupEditorComponent,
28 | SessionEditorComponent,
29 | ],
30 | entryComponents: [
31 | MeetupEditorComponent,
32 | ],
33 | exports: [
34 | MeetupEditorComponent,
35 | SessionEditorComponent,
36 | ],
37 | imports: [
38 | RouterModule.forChild([
39 | { path: "meetup-creator", component: MeetupEditorComponent },
40 | { path: "meetup-editor/:meetupId", component: MeetupEditorComponent },
41 | ]),
42 |
43 | CommonModule,
44 | FormsModule,
45 | ReactiveFormsModule,
46 |
47 | MatDatepickerModule,
48 | MatMomentDatetimeModule,
49 | MatDatetimepickerModule,
50 |
51 | MatButtonModule,
52 | MatCardModule,
53 | MatFormFieldModule,
54 | MatIconModule,
55 | MatInputModule,
56 | MatSelectModule,
57 |
58 | CoreModule,
59 | FriendListModule,
60 | VenueListModule,
61 | TalkListModule,
62 | ],
63 | providers: [
64 | { provide: MAT_DATE_LOCALE, useValue: "ru-RU" },
65 | ],
66 | })
67 | export class MeetupEditorModule { }
68 |
--------------------------------------------------------------------------------
/DevActivator.Meetups.BL/Services/VenueService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using DevActivator.Meetups.BL.Entities;
6 | using DevActivator.Meetups.BL.Extensions;
7 | using DevActivator.Meetups.BL.Interfaces;
8 | using DevActivator.Meetups.BL.Models;
9 |
10 | namespace DevActivator.Meetups.BL.Services
11 | {
12 | public class VenueService : IVenueService
13 | {
14 | private readonly IVenueProvider _venueProvider;
15 |
16 | public VenueService(IVenueProvider venueProvider)
17 | {
18 | _venueProvider = venueProvider;
19 | }
20 |
21 | public async Task> GetAllVenuesAsync()
22 | {
23 | var venues = await _venueProvider.GetAllVenuesAsync().ConfigureAwait(false);
24 | return venues
25 | .Select(x => new AutocompleteRow {Id = x.Id, Name = x.Name})
26 | .ToList();
27 | }
28 |
29 | public async Task GetVenueAsync(string venueId)
30 | {
31 | var venue = await _venueProvider.GetVenueOrDefaultAsync(venueId).ConfigureAwait(false);
32 | return venue.ToVm();
33 | }
34 |
35 | public async Task AddVenueAsync(VenueVm venue)
36 | {
37 | venue.EnsureIsValid();
38 |
39 | var original = await _venueProvider.GetVenueOrDefaultAsync(venue.Id).ConfigureAwait(false);
40 | if (original != null)
41 | {
42 | throw new FormatException($"Данный {nameof(venue.Id)} \"{venue.Id}\" уже занят");
43 | }
44 |
45 | var entity = new Venue {Id = venue.Id}.Extend(venue);
46 | var res = await _venueProvider.SaveVenueAsync(entity).ConfigureAwait(false);
47 | return res.ToVm();
48 | }
49 |
50 | public async Task UpdateVenueAsync(VenueVm venue)
51 | {
52 | venue.EnsureIsValid();
53 | var original = await _venueProvider.GetVenueOrDefaultAsync(venue.Id).ConfigureAwait(false);
54 | var res = await _venueProvider.SaveVenueAsync(original.Extend(venue)).ConfigureAwait(false);
55 | return res.ToVm();
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/DevActivator.Meetups.BL/Services/FriendService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using DevActivator.Meetups.BL.Entities;
6 | using DevActivator.Meetups.BL.Extensions;
7 | using DevActivator.Meetups.BL.Interfaces;
8 | using DevActivator.Meetups.BL.Models;
9 |
10 | namespace DevActivator.Meetups.BL.Services
11 | {
12 | public class FriendService : IFriendService
13 | {
14 | private readonly IFriendProvider _friendProvider;
15 |
16 | public FriendService(IFriendProvider friendProvider)
17 | {
18 | _friendProvider = friendProvider;
19 | }
20 |
21 | public async Task> GetAllFriendsAsync()
22 | {
23 | var friends = await _friendProvider.GetAllFriendsAsync().ConfigureAwait(false);
24 | return friends
25 | .Select(x => new AutocompleteRow {Id = x.Id, Name = x.Name})
26 | .ToList();
27 | }
28 |
29 | public async Task GetFriendAsync(string friendId)
30 | {
31 | var friend = await _friendProvider.GetFriendOrDefaultAsync(friendId).ConfigureAwait(false);
32 | return friend;
33 | }
34 |
35 | public async Task AddFriendAsync(Friend friend)
36 | {
37 | friend.EnsureIsValid();
38 |
39 | var original = await _friendProvider.GetFriendOrDefaultAsync(friend.Id).ConfigureAwait(false);
40 | if (original != null)
41 | {
42 | throw new FormatException($"Данный {nameof(friend.Id)} \"{friend.Id}\" уже занят");
43 | }
44 |
45 | var entity = new Friend {Id = friend.Id}.Extend(friend);
46 | var res = await _friendProvider.SaveFriendAsync(entity).ConfigureAwait(false);
47 | return res;
48 | }
49 |
50 | public async Task UpdateFriendAsync(Friend friend)
51 | {
52 | friend.EnsureIsValid();
53 | var original = await _friendProvider.GetFriendOrDefaultAsync(friend.Id).ConfigureAwait(false);
54 | var res = await _friendProvider.SaveFriendAsync(original.Extend(friend)).ConfigureAwait(false);
55 | return res;
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/core/constants.ts:
--------------------------------------------------------------------------------
1 | export const API_ENDPOINTS = {
2 | addFriendUrl: "api/Friend/AddFriend",
3 | addMeetupUrl: "api/Meetup/AddMeetup",
4 | addSpeakerUrl: "api/Speaker/AddSpeaker",
5 | addTalkUrl: "api/Talk/AddTalk",
6 | addVenueUrl: "api/Venue/AddVenue",
7 | getCompositeMeetupUrl: "api/Composite/GetMeetup/{{meetupId}}",
8 | saveCompositeMeetupUrl: "api/Composite/SaveMeetup/{{meetupId}}",
9 | getFriendUrl: "api/Friend/GetFriend/{{friendId}}",
10 | getFriendsUrl: "api/Friend/GetFriends",
11 | getMeetupUrl: "api/Meetup/GetMeetup/{{meetupId}}",
12 | getMeetupsUrl: "api/Meetup/GetMeetups",
13 | getSpeakerUrl: "api/Speaker/GetSpeaker/{{speakerId}}",
14 | getSpeakersUrl: "api/Speaker/GetSpeakers",
15 | getTalkUrl: "api/Talk/GetTalk/{{talkId}}",
16 | getTalksUrl: "api/Talk/GetTalks",
17 | getVenueUrl: "api/Venue/GetVenue/{{venueId}}",
18 | getVenuesUrl: "api/Venue/GetVenues",
19 | storeFriendAvatarUrl: "api/File/StoreFriendAvatar/{{friendId}}",
20 | storeSpeakerAvatarUrl: "api/File/StoreSpeakerAvatar/{{speakerId}}",
21 | updateFriendUrl: "api/Friend/UpdateFriend",
22 | updateMeetupUrl: "api/Meetup/UpdateMeetup",
23 | updateSpeakerUrl: "api/Speaker/UpdateSpeaker",
24 | updateTalkUrl: "api/Talk/UpdateTalk",
25 | updateVenueUrl: "api/Venue/UpdateVenue",
26 | };
27 |
28 | export const LABELS = {
29 | ADDRESS: "Адрес",
30 | BLOG_URL: "Ссылка на блог",
31 | CODE_URL: "Ссылка на код",
32 | COMPANY: "Компания",
33 | COMPANY_URL: "Сайт компании",
34 | CONTACTS_URL: "Ссылка на контакты",
35 | DESCRIPTION: "Описание",
36 | GIT_HUB_URL: "Ссылка на GitHub",
37 | HABR_URL: "Ссылка на хабр",
38 | IDENTITY: "Идентификатор",
39 | MAP_URL: "Ссылка на карту",
40 | NAME: "Имя",
41 | SLIDES_URL: "Ссылка на слайды",
42 | TITLE: "Название",
43 | TWITTER_URL: "Ссылка на твиттер",
44 | URL: "Ссылка",
45 | VIDEO_URL: "Ссылка на видео",
46 | };
47 |
48 | export const PATTERNS = {
49 | IDENTITY: "^[A-Z][\\w-]{2,}$",
50 | REQUIRED_STRING: "^\\S[\\s\\S]*\\S$",
51 | URI: "^https?://.+\\..+$",
52 | };
53 |
54 | export const MIME_TYPES = {
55 | JPEG: "image/jpeg",
56 | PNG: "image/png",
57 | };
58 |
59 | export const FILE_SIZES = {
60 | AVATAR_MAX_SIZE: 40000,
61 | };
62 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/talk-editor/talk-editor.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 | import { API_ENDPOINTS, HttpService, LayoutService } from "@dotnetru/core";
3 | import { BehaviorSubject, Observable } from "rxjs";
4 | import { filter, map } from "rxjs/operators";
5 |
6 | import { ITalk } from "./interfaces";
7 |
8 | @Injectable()
9 | export class TalkEditorService {
10 | private _talk$: BehaviorSubject = new BehaviorSubject(null);
11 | private _dataStore = {
12 | talk: {} as ITalk,
13 | };
14 |
15 | public get talk$(): Observable {
16 | return this._talk$.pipe(
17 | filter((x) => x !== null),
18 | map((x) => x as ITalk),
19 | );
20 | }
21 |
22 | constructor(
23 | private _layoutService: LayoutService,
24 | private _httpService: HttpService,
25 | ) { }
26 |
27 | public hasChanges(talk: ITalk): boolean {
28 | return JSON.stringify(talk) !== JSON.stringify(this._dataStore.talk);
29 | }
30 |
31 | public fetchTalk(talkId: string): void {
32 | this._httpService.get(
33 | API_ENDPOINTS.getTalkUrl.replace("{{talkId}}", talkId),
34 | (talk: ITalk) => {
35 | this._dataStore.talk = talk;
36 | this._talk$.next(Object.assign({}, this._dataStore.talk));
37 | });
38 | }
39 |
40 | public addTalk(talk: ITalk, cb: (res: ITalk) => void): void {
41 | this._httpService.post(
42 | API_ENDPOINTS.addTalkUrl,
43 | talk,
44 | (res: ITalk) => {
45 | this._layoutService.showInfo("Доклад добавлен успешно");
46 | cb(res);
47 | },
48 | );
49 | }
50 |
51 | public updateTalk(talk: ITalk, cb: () => void): void {
52 | this._httpService.post(
53 | API_ENDPOINTS.updateTalkUrl,
54 | talk,
55 | (x: ITalk) => {
56 | this._layoutService.showInfo("Доклад изменён успешно");
57 | this._dataStore.talk = x;
58 | this._talk$.next(Object.assign({}, this._dataStore.talk));
59 | cb();
60 | },
61 | );
62 | }
63 |
64 | public reset(): void {
65 | this._talk$.next(Object.assign({}, this._dataStore.talk));
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/venue-editor/venue-editor.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 | import { API_ENDPOINTS, HttpService, LayoutService } from "@dotnetru/core";
3 | import { BehaviorSubject, Observable } from "rxjs";
4 | import { filter, map } from "rxjs/operators";
5 |
6 | import { IVenue } from "./interfaces";
7 |
8 | @Injectable()
9 | export class VenueEditorService {
10 | private _venue$: BehaviorSubject = new BehaviorSubject(null);
11 | private _dataStore = {
12 | venue: {} as IVenue,
13 | };
14 |
15 | public get venue$(): Observable {
16 | return this._venue$.pipe(
17 | filter((x) => x !== null),
18 | map((x) => x as IVenue),
19 | );
20 | }
21 |
22 | constructor(
23 | private _layoutService: LayoutService,
24 | private _httpService: HttpService,
25 | ) { }
26 |
27 | public hasChanges(venue: IVenue): boolean {
28 | return JSON.stringify(venue) !== JSON.stringify(this._dataStore.venue);
29 | }
30 |
31 | public fetchVenue(venueId: string): void {
32 | this._httpService.get(
33 | API_ENDPOINTS.getVenueUrl.replace("{{venueId}}", venueId),
34 | (venue: IVenue) => {
35 | this._dataStore.venue = venue;
36 | this._venue$.next(Object.assign({}, this._dataStore.venue));
37 | });
38 | }
39 |
40 | public addVenue(venue: IVenue, cb: (res: IVenue) => void): void {
41 | this._httpService.post(
42 | API_ENDPOINTS.addVenueUrl,
43 | venue,
44 | (res: IVenue) => {
45 | this._layoutService.showInfo("Площадка добавлена успешно");
46 | cb(res);
47 | },
48 | );
49 | }
50 |
51 | public updateVenue(venue: IVenue, cb: () => void): void {
52 | this._httpService.post(
53 | API_ENDPOINTS.updateVenueUrl,
54 | venue,
55 | (x: IVenue) => {
56 | this._layoutService.showInfo("Площадка изменена успешно");
57 | this._dataStore.venue = x;
58 | this._venue$.next(Object.assign({}, this._dataStore.venue));
59 | cb();
60 | },
61 | );
62 | }
63 |
64 | public reset(): void {
65 | this._venue$.next(Object.assign({}, this._dataStore.venue));
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/DevActivator.Meetups.BL/Services/MeetupService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using DevActivator.Common.BL.Config;
6 | using DevActivator.Meetups.BL.Entities;
7 | using DevActivator.Meetups.BL.Extensions;
8 | using DevActivator.Meetups.BL.Interfaces;
9 | using DevActivator.Meetups.BL.Models;
10 |
11 | namespace DevActivator.Meetups.BL.Services
12 | {
13 | public class MeetupService : IMeetupService
14 | {
15 | private readonly Settings _settings;
16 | private readonly IMeetupProvider _meetupProvider;
17 |
18 | public MeetupService(Settings settings, IMeetupProvider meetupProvider)
19 | {
20 | _settings = settings;
21 | _meetupProvider = meetupProvider;
22 | }
23 |
24 | public async Task> GetAllMeetupsAsync()
25 | {
26 | var meetups = await _meetupProvider.GetAllMeetupsAsync().ConfigureAwait(false);
27 | return meetups
28 | .Select(x => new AutocompleteRow {Id = x.Id, Name = x.Name})
29 | .ToList();
30 | }
31 |
32 | public async Task GetMeetupAsync(string meetupId)
33 | {
34 | var meetup = await _meetupProvider.GetMeetupOrDefaultAsync(meetupId).ConfigureAwait(false);
35 | return meetup?.ToVm();
36 | }
37 |
38 | public async Task AddMeetupAsync(MeetupVm meetup)
39 | {
40 | meetup.EnsureIsValid();
41 |
42 | var original = await _meetupProvider.GetMeetupOrDefaultAsync(meetup.Id).ConfigureAwait(false);
43 | if (original != null)
44 | {
45 | throw new FormatException($"Данный {nameof(meetup.Id)} \"{meetup.Id}\" уже занят");
46 | }
47 |
48 | var entity = new Meetup {Id = meetup.Id}.Extend(meetup);
49 | var res = await _meetupProvider.SaveMeetupAsync(entity).ConfigureAwait(false);
50 | return res.ToVm();
51 | }
52 |
53 | public async Task UpdateMeetupAsync(MeetupVm meetup)
54 | {
55 | meetup.EnsureIsValid();
56 | var original = await _meetupProvider.GetMeetupOrDefaultAsync(meetup.Id).ConfigureAwait(false);
57 | var res = await _meetupProvider.SaveMeetupAsync(original.Extend(meetup)).ConfigureAwait(false);
58 | return res.ToVm();
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/DevActivator/Controllers/FileController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Threading.Tasks;
4 | using DevActivator.Common.BL.Config;
5 | using DevActivator.Common.BL.Extensions;
6 | using Microsoft.AspNetCore.Http;
7 | using Microsoft.AspNetCore.Mvc;
8 |
9 | namespace DevActivator.Controllers
10 | {
11 | [Route("api/[controller]")]
12 | public class FileController : Controller
13 | {
14 | private readonly Settings _settings;
15 |
16 | public FileController(Settings settings)
17 | {
18 | _settings = settings;
19 | }
20 |
21 | [HttpPut("[action]/{speakerId}")]
22 | public async Task StoreSpeakerAvatar([FromRoute] string speakerId, [FromForm] IFormFile formFile)
23 | {
24 | if (formFile == null || formFile.Length <= 0)
25 | {
26 | throw new ArgumentException("Can't read the file", nameof(formFile));
27 | }
28 |
29 | if (formFile.Length > _settings.AvatarMaxSize)
30 | {
31 | throw new ArgumentOutOfRangeException(nameof(formFile),
32 | $"File size must be lower than {_settings.AvatarMaxSize.ToString()}");
33 | }
34 |
35 | var filePath = _settings.GetSpeakerAvatarFilePath(speakerId);
36 | using (var stream = new FileStream(filePath, FileMode.Create))
37 | {
38 | await formFile.CopyToAsync(stream).ConfigureAwait(true);
39 | }
40 | }
41 |
42 | [HttpPut("[action]/{friendId}")]
43 | public async Task StoreFriendAvatar([FromRoute] string friendId, [FromForm] IFormFile formFile)
44 | {
45 | if (formFile == null || formFile.Length <= 0)
46 | {
47 | throw new ArgumentException("Can't read the file", nameof(formFile));
48 | }
49 |
50 | if (formFile.Length > _settings.AvatarMaxSize)
51 | {
52 | throw new ArgumentOutOfRangeException(nameof(formFile),
53 | $"File size must be lower than {_settings.AvatarMaxSize.ToString()}");
54 | }
55 |
56 | var filePath = _settings.GetFriendAvatarFilePath(friendId);
57 | using (var stream = new FileStream(filePath, FileMode.Create))
58 | {
59 | await formFile.CopyToAsync(stream).ConfigureAwait(true);
60 | }
61 | }
62 | }
63 | }
--------------------------------------------------------------------------------
/DevActivator.Meetups.Tests/ProviderTests/FriendProviderTests.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using DevActivator.Common.BL.Config;
7 | using DevActivator.Common.BL.Extensions;
8 | using Newtonsoft.Json;
9 | using Xunit;
10 | using Xunit.Abstractions;
11 |
12 | namespace DevActivator.Meetups.Tests.ProviderTests
13 | {
14 | public class FriendProviderTests
15 | {
16 | private readonly ITestOutputHelper _testOutputHelper;
17 |
18 | public FriendProviderTests(ITestOutputHelper testOutputHelper)
19 | {
20 | _testOutputHelper = testOutputHelper;
21 | }
22 |
23 | [Fact]
24 | public void TalkSpeakerIdsDeserializationSucceed()
25 | {
26 | // prepare
27 | var settings = new Settings {AuditRepoDirectory = "/Users/alex-mbp/repos/Audit"};
28 | var filePaths = settings.GetAllFilePaths("friends", false);
29 |
30 | // test
31 | var dic = new Dictionary();
32 | foreach (var filePath in filePaths)
33 | {
34 | var encoding = GetEncoding(filePath);
35 | if (!dic.ContainsKey(encoding))
36 | {
37 | _testOutputHelper.WriteLine($"{nameof(encoding)}: {encoding}; {nameof(filePath)}: {filePath};");
38 | dic.Add(encoding, 1);
39 | }
40 | else
41 | {
42 | ++dic[encoding];
43 | }
44 | }
45 |
46 | foreach (var i in dic)
47 | {
48 | _testOutputHelper.WriteLine($"{nameof(i.Key)}: {i.Key}; {nameof(i.Value)}: {i.Value};");
49 | }
50 |
51 | Assert.Single(dic.Keys);
52 | }
53 |
54 | private static Encoding GetEncoding(string filename)
55 | {
56 | // This is a direct quote from MSDN:
57 | // The CurrentEncoding value can be different after the first
58 | // call to any Read method of StreamReader, since encoding
59 | // autodetection is not done until the first call to a Read method.
60 |
61 | using (var reader = new StreamReader(filename, Encoding.Default, true))
62 | {
63 | if (reader.Peek() >= 0) // you need this!
64 | reader.Read();
65 |
66 | return reader.CurrentEncoding;
67 | }
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/DevActivator.Common.BL/Extensions/SettingsExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using System.Linq;
4 | using DevActivator.Common.BL.Config;
5 |
6 | namespace DevActivator.Common.BL.Extensions
7 | {
8 | // todo: move from BL
9 | public static class SettingsExtensions
10 | {
11 | private const string IndexFileName = "index.xml";
12 | private const string AvatarFileName = "avatar.jpg";
13 | private const string LogoFileName = "logo.png";
14 |
15 | private const string Db = "db";
16 |
17 | public static string GetSpeakerAvatarFilePath(this Settings settings, string speakerId)
18 | => Path.Combine(settings.GetDirectory("speakers"), speakerId, AvatarFileName);
19 |
20 | public static string GetFriendAvatarFilePath(this Settings settings, string friendId)
21 | => Path.Combine(settings.GetDirectory("friends"), friendId, LogoFileName);
22 |
23 | public static string GetSpeakerFilePath(this Settings settings, string speakerId)
24 | => Path.Combine(settings.GetDirectory("speakers"), speakerId, IndexFileName);
25 |
26 | public static List GetAllFilePaths(this Settings settings, string directoryName, bool flatEntity)
27 | {
28 | var entityDirectory = settings.GetDirectory(directoryName);
29 | if (!flatEntity)
30 | {
31 | return Directory.GetDirectories(entityDirectory)
32 | .Select(directory => Path.Combine(directory, IndexFileName)).ToList();
33 | }
34 |
35 | return Directory.GetFiles(entityDirectory, "*.xml").ToList();
36 | }
37 |
38 | public static string GetEntityFilePath(this Settings settings, string directoryName, IEntity entity,
39 | bool flatEntity)
40 | => settings.GetEntityFilePath(directoryName, entity.Id, flatEntity);
41 |
42 | public static string GetEntityFilePath(this Settings settings, string directoryName, string entityId,
43 | bool flatEntity)
44 | {
45 | if (!flatEntity)
46 | {
47 | return Path.Combine(settings.GetDirectory(directoryName), entityId, IndexFileName);
48 | }
49 |
50 | return Path.Combine(settings.GetDirectory(directoryName), $"{entityId}.xml");
51 | }
52 |
53 | private static string GetDirectory(this Settings settings, string directoryName)
54 | => Path.Combine(settings.AuditRepoDirectory, Db, directoryName);
55 | }
56 | }
--------------------------------------------------------------------------------
/DevActivator.Meetups.BL/Services/SpeakerService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading.Tasks;
5 | using DevActivator.Common.BL.Config;
6 | using DevActivator.Meetups.BL.Entities;
7 | using DevActivator.Meetups.BL.Extensions;
8 | using DevActivator.Meetups.BL.Interfaces;
9 | using DevActivator.Meetups.BL.Models;
10 |
11 | namespace DevActivator.Meetups.BL.Services
12 | {
13 | public class SpeakerService : ISpeakerService
14 | {
15 | private readonly Settings _settings;
16 | private readonly ISpeakerProvider _speakerProvider;
17 |
18 | public SpeakerService(Settings settings, ISpeakerProvider speakerProvider)
19 | {
20 | _settings = settings;
21 | _speakerProvider = speakerProvider;
22 | }
23 |
24 | public async Task> GetAllSpeakersAsync()
25 | {
26 | var speakers = await _speakerProvider.GetAllSpeakersAsync().ConfigureAwait(false);
27 | return speakers
28 | .Select(x => new AutocompleteRow {Id = x.Id, Name = x.Name})
29 | .ToList();
30 | }
31 |
32 | public async Task GetSpeakerAsync(string speakerId)
33 | {
34 | var speaker = await _speakerProvider.GetSpeakerOrDefaultAsync(speakerId).ConfigureAwait(false);
35 | return speaker.ToVm(speaker.GetLastUpdateDate(_settings));
36 | }
37 |
38 | public async Task AddSpeakerAsync(SpeakerVm speaker)
39 | {
40 | speaker.EnsureIsValid();
41 |
42 | var original = await _speakerProvider.GetSpeakerOrDefaultAsync(speaker.Id).ConfigureAwait(false);
43 | if (original != null)
44 | {
45 | throw new FormatException($"Данный {nameof(speaker.Id)} \"{speaker.Id}\" уже занят");
46 | }
47 |
48 | var entity = new Speaker {Id = speaker.Id}.Extend(speaker);
49 | var res = await _speakerProvider.SaveSpeakerAsync(entity).ConfigureAwait(false);
50 | return res.ToVm(res.GetLastUpdateDate(_settings));
51 | }
52 |
53 | public async Task UpdateSpeakerAsync(SpeakerVm speaker)
54 | {
55 | speaker.EnsureIsValid();
56 | var original = await _speakerProvider.GetSpeakerOrDefaultAsync(speaker.Id).ConfigureAwait(false);
57 | var res = await _speakerProvider.SaveSpeakerAsync(original.Extend(speaker)).ConfigureAwait(false);
58 | return res.ToVm(res.GetLastUpdateDate(_settings));
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/DevActivator/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dotnet-angular-electron",
3 | "private": true,
4 | "version": "0.0.0",
5 | "scripts": {
6 | "start": "webpack --config webpack.config.vendor.js && webpack --watch",
7 | "build:vendor": "webpack --config webpack.config.vendor.js",
8 | "lint": "node node_modules/tslint/bin/tslint -p tsconfig.json -c tslint.json",
9 | "test": "karma start ClientApp/test/karma.conf.js",
10 | "prod": "node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js && node node_modules/webpack/bin/webpack.js"
11 | },
12 | "devDependencies": {
13 | "@angular/animations": "7.2.4",
14 | "@angular/cdk": "^7.3.1",
15 | "@angular/cli": "^7.3.0",
16 | "@angular/common": "7.2.4",
17 | "@angular/compiler": "7.2.4",
18 | "@angular/compiler-cli": "7.2.4",
19 | "@angular/core": "7.2.4",
20 | "@angular/forms": "7.2.4",
21 | "@angular/http": "7.2.4",
22 | "@angular/material": "^7.3.1",
23 | "@angular/material-moment-adapter": "^7.3.1",
24 | "@angular/platform-browser": "7.2.4",
25 | "@angular/platform-browser-dynamic": "7.2.4",
26 | "@angular/platform-server": "7.2.4",
27 | "@angular/router": "7.2.4",
28 | "@mat-datetimepicker/core": "^2.0.1",
29 | "@mat-datetimepicker/moment": "^2.0.1",
30 | "@ngtools/webpack": "^7.1.4",
31 | "@types/chai": "4.0.1",
32 | "@types/jasmine": "^2.5.53",
33 | "@types/webpack-env": "1.13.0",
34 | "angular2-router-loader": "0.3.5",
35 | "angular2-template-loader": "0.6.2",
36 | "aspnet-prerendering": "^3.0.1",
37 | "aspnet-webpack": "^2.0.3",
38 | "awesome-typescript-loader": "^3.2.1",
39 | "chai": "4.0.2",
40 | "codelyzer": "^4.5.0",
41 | "css": "2.2.1",
42 | "css-loader": "0.28.4",
43 | "es6-shim": "0.35.3",
44 | "event-source-polyfill": "0.0.9",
45 | "expose-loader": "0.7.3",
46 | "extract-text-webpack-plugin": "^2.1.2",
47 | "file-loader": "0.11.2",
48 | "html-loader": "0.4.5",
49 | "isomorphic-fetch": "2.2.1",
50 | "jasmine-core": "2.6.4",
51 | "json-loader": "0.5.4",
52 | "karma": "1.7.0",
53 | "karma-chai": "0.1.0",
54 | "karma-chrome-launcher": "2.2.0",
55 | "karma-cli": "1.0.1",
56 | "karma-jasmine": "1.1.0",
57 | "karma-webpack": "2.0.3",
58 | "moment": "^2.24.0",
59 | "node-sass": "^4.11.0",
60 | "preboot": "4.5.2",
61 | "raw-loader": "0.5.1",
62 | "reflect-metadata": "0.1.10",
63 | "rxjs": "6.4.0",
64 | "rxjs-tslint": "^0.1.5",
65 | "sass-loader": "^7.1.0",
66 | "style-loader": "0.18.2",
67 | "to-string-loader": "1.1.5",
68 | "tslint": "^5.11.0",
69 | "typescript": "3.2.4",
70 | "url-loader": "0.5.9",
71 | "webpack": "^2.5.1",
72 | "webpack-hot-middleware": "2.18.2",
73 | "webpack-merge": "4.1.0",
74 | "zone.js": "0.8.29"
75 | },
76 | "dependencies": {}
77 | }
--------------------------------------------------------------------------------
/DevActivator/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "error",
3 | "extends": [
4 | "tslint:recommended",
5 | "codelyzer"
6 | ],
7 | "jsRules": {},
8 | "rules": {
9 | "object-literal-sort-keys": false,
10 | "ordered-imports": false,
11 | "max-line-length": [
12 | true,
13 | {
14 | "limit": 120,
15 | "ignore-pattern": "^import |^export {(.*?)}"
16 | }
17 | ],
18 | "variable-name": [
19 | true,
20 | "ban-keywords",
21 | "check-format",
22 | "allow-leading-underscore"
23 | ],
24 | "no-console": {
25 | "severity": "warning",
26 | "options": [
27 | "log"
28 | ]
29 | },
30 | // The rule have the following arguments:
31 | // [ENABLED, "attribute" | "element", "selectorPrefix" | ["listOfPrefixes"], "camelCase" | "kebab-case"]
32 | "directive-selector": [
33 | true,
34 | "attribute",
35 | [
36 | "dir-prefix1",
37 | "dir-prefix2"
38 | ],
39 | "camelCase"
40 | ],
41 | "component-selector": [
42 | true,
43 | "element",
44 | [
45 | "mtp"
46 | ],
47 | "kebab-case"
48 | ],
49 | "angular-whitespace": [
50 | true,
51 | "check-interpolation",
52 | "check-semicolon"
53 | ],
54 | "use-input-property-decorator": true,
55 | "use-output-property-decorator": true,
56 | "use-host-property-decorator": true,
57 | "no-attribute-parameter-decorator": true,
58 | "no-input-rename": true,
59 | "no-output-on-prefix": true,
60 | "no-output-rename": true,
61 | "no-forward-ref": true,
62 | "use-life-cycle-interface": true,
63 | "use-pipe-transform-interface": true,
64 | "no-output-named-after-standard-event": true,
65 | "max-inline-declarations": true,
66 | "no-life-cycle-call": true,
67 | "prefer-output-readonly": true,
68 | "no-conflicting-life-cycle-hooks": true,
69 | "enforce-component-selector": true,
70 | "no-queries-parameter": true,
71 | "prefer-inline-decorator": true,
72 | "component-class-suffix": [
73 | true,
74 | "Component"
75 | ],
76 | "directive-class-suffix": [
77 | true,
78 | "Directive"
79 | ],
80 | "rxjs-collapse-imports": true,
81 | "rxjs-pipeable-operators-only": true,
82 | "rxjs-no-static-observable-methods": true,
83 | "rxjs-proper-imports": true
84 | },
85 | "rulesDirectory": [
86 | "node_modules/codelyzer",
87 | "node_modules/rxjs-tslint"
88 | ]
89 | }
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/timepad/timepad.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 | import { API_ENDPOINTS, DateConverterService, HttpService, LayoutService } from "@dotnetru/core";
3 | import { BehaviorSubject, Observable } from "rxjs";
4 | import { filter, map } from "rxjs/operators";
5 | import { IApiSession } from "../meetup-editor/interfaces";
6 | import { IApiCompositeMeetup, ICompositeMeetup, IRandomConcatModel } from "./interfaces";
7 |
8 | @Injectable()
9 | export class CompositeService {
10 | public get meetup$(): Observable {
11 | return this._meetup$.pipe(
12 | filter((x) => x !== null),
13 | map((x) => x as ICompositeMeetup),
14 | );
15 | }
16 |
17 | private _meetup$: BehaviorSubject = new BehaviorSubject(null);
18 |
19 | constructor(
20 | private _layoutService: LayoutService,
21 | private _httpService: HttpService,
22 | ) { }
23 |
24 | public fetchMeetup(meetupId: string | undefined, descriptor: IRandomConcatModel, cb?: () => void): void {
25 | this._httpService.post(
26 | API_ENDPOINTS.getCompositeMeetupUrl.replace("{{meetupId}}", meetupId || ""),
27 | descriptor,
28 | (res: IApiCompositeMeetup) => {
29 | const model: ICompositeMeetup = this.toCompositeMeetup(meetupId, res);
30 | this._meetup$.next(model);
31 | if (cb) {
32 | cb();
33 | }
34 | });
35 | }
36 |
37 | public saveMeetup(meetupId: string | undefined, descriptor: IRandomConcatModel, cb?: () => void): void {
38 | this._httpService.post(
39 | API_ENDPOINTS.saveCompositeMeetupUrl.replace("{{meetupId}}", meetupId || ""),
40 | descriptor,
41 | (res: IApiCompositeMeetup) => {
42 | const model: ICompositeMeetup = this.toCompositeMeetup(meetupId, res);
43 | this._layoutService.showInfo("Встреча изменена успешно");
44 | this._meetup$.next(model);
45 | if (cb) {
46 | cb();
47 | }
48 | });
49 | }
50 |
51 | private toCompositeMeetup(meetupId: string | undefined, data: IApiCompositeMeetup): ICompositeMeetup {
52 | return Object.assign({}, {
53 | id: meetupId,
54 | name: data.name,
55 | communityId: data.communityId,
56 | venue: data.venue,
57 | friends: data.friends,
58 | sessions: data.sessions.map((x: IApiSession) => Object.assign({}, x, {
59 | endTime: DateConverterService.toMoment(x.endTime),
60 | startTime: DateConverterService.toMoment(x.startTime),
61 | })),
62 | speakers: data.speakers,
63 | talks: data.talks,
64 | });
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/pages/speaker-editor/speaker-editor.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 | import { API_ENDPOINTS, HttpService, LayoutService } from "@dotnetru/core";
3 | import { BehaviorSubject, Observable } from "rxjs";
4 | import { filter, map } from "rxjs/operators";
5 |
6 | import { ISpeaker } from "./interfaces";
7 |
8 | @Injectable()
9 | export class SpeakerEditorService {
10 | private _speaker$: BehaviorSubject = new BehaviorSubject(null);
11 | private _dataStore = {
12 | speaker: {} as ISpeaker,
13 | };
14 |
15 | public get speaker$(): Observable {
16 | return this._speaker$.pipe(
17 | filter((x) => x !== null),
18 | map((x) => x as ISpeaker),
19 | );
20 | }
21 |
22 | constructor(
23 | private _layoutService: LayoutService,
24 | private _httpService: HttpService,
25 | ) { }
26 |
27 | public hasChanges(speaker: ISpeaker): boolean {
28 | return JSON.stringify(speaker) !== JSON.stringify(this._dataStore.speaker);
29 | }
30 |
31 | public fetchSpeaker(speakerId: string): void {
32 | this._httpService.get(
33 | API_ENDPOINTS.getSpeakerUrl.replace("{{speakerId}}", speakerId),
34 | (speaker: ISpeaker) => {
35 | this._dataStore.speaker = speaker;
36 | this._speaker$.next(Object.assign({}, this._dataStore.speaker));
37 | });
38 | }
39 |
40 | public addSpeaker(speaker: ISpeaker, cb: (speaker: ISpeaker) => void): void {
41 | this._httpService.post(
42 | API_ENDPOINTS.addSpeakerUrl,
43 | speaker,
44 | (res: ISpeaker) => {
45 | this._layoutService.showInfo("Докладчик добавлен успешно");
46 | cb(res);
47 | },
48 | );
49 | }
50 |
51 | public updateSpeaker(speaker: ISpeaker, cb: () => void): void {
52 | this._httpService.post(
53 | API_ENDPOINTS.updateSpeakerUrl,
54 | speaker,
55 | (x: ISpeaker) => {
56 | this._layoutService.showInfo("Докладчик изменён успешно");
57 | this._dataStore.speaker = x;
58 | this._speaker$.next(Object.assign({}, this._dataStore.speaker));
59 | cb();
60 | },
61 | );
62 | }
63 |
64 | public reset(): void {
65 | this._speaker$.next(Object.assign({}, this._dataStore.speaker));
66 | }
67 |
68 | public storeSpeakerAvatar(speakerId: string, blob: Blob): void {
69 | const url: string = API_ENDPOINTS.storeSpeakerAvatarUrl
70 | .replace("{{speakerId}}", speakerId);
71 |
72 | const formData: FormData = new FormData();
73 | formData.append("formFile", blob, "avatar.jpg");
74 |
75 | this._httpService.put(url, formData, () => {
76 | this._layoutService.showInfo("Аватар загружен успешно");
77 | });
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/components/talk-list/talk-list.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | ChangeDetectorRef,
4 | Component,
5 | EventEmitter,
6 | Input,
7 | OnDestroy,
8 | OnInit,
9 | Output,
10 | ViewChild,
11 | } from "@angular/core";
12 | import { AutocompleteComponent, IAutocompleteRow } from "@dotnetru/shared/autocomplete";
13 | import { BehaviorSubject, Subscription } from "rxjs";
14 | import { debounceTime, switchMap } from "rxjs/operators";
15 |
16 | import { TalkListService } from "./talk-list.service";
17 |
18 | @Component({
19 | changeDetection: ChangeDetectionStrategy.OnPush,
20 | selector: "mtp-talk-list",
21 | templateUrl: "./talk-list.component.html",
22 | })
23 | export class TalkListComponent implements OnInit, OnDestroy {
24 | @Input() public title: string = "Поиск доклада";
25 | @Input() public iconName: string = "add";
26 | @Input() public iconText: string = "Добавить";
27 |
28 | @Input() public set talkLink(value: { talkId?: string }) {
29 | if (value && value.talkId) {
30 | this._talkId$.next(value.talkId);
31 | }
32 | }
33 |
34 | @ViewChild("autocomplete") public autocomplete!: AutocompleteComponent;
35 |
36 | @Output() public readonly selected: EventEmitter = new EventEmitter();
37 | @Output() public readonly iconClicked: EventEmitter = new EventEmitter();
38 |
39 | public talks: IAutocompleteRow[] = [];
40 |
41 | private _talkId$: BehaviorSubject = new BehaviorSubject("");
42 | private _subs: Subscription[] = [];
43 |
44 | constructor(
45 | private _talkListService: TalkListService,
46 | private _changeDetectorRef: ChangeDetectorRef,
47 | ) { }
48 |
49 | public ngOnInit(): void {
50 | this._subs = [
51 | this._talkListService.talks$
52 | .subscribe(
53 | (talks: IAutocompleteRow[]) => {
54 | this.talks = talks;
55 | this._changeDetectorRef.detectChanges();
56 | },
57 | ),
58 | this._talkListService.talks$.pipe(switchMap((_) => this._talkId$.pipe()))
59 | .pipe(debounceTime(100))
60 | .subscribe((talkId: string) => {
61 | const talk = this.talks.find((x) => x.id === talkId);
62 | if (talk) {
63 | this.autocomplete.queryControl.patchValue(talk.name);
64 | } else {
65 | console.warn("talk not found", talkId);
66 | }
67 | }),
68 | ];
69 |
70 | this._talkListService.fetchTalks();
71 | }
72 |
73 | public ngOnDestroy(): void {
74 | this._subs.forEach((x) => x.unsubscribe());
75 | }
76 |
77 | public onSelected(row: IAutocompleteRow): void {
78 | this.selected.emit(row);
79 | }
80 |
81 | public onIconClicked(): void {
82 | this.iconClicked.emit();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/shared/autocomplete/autocomplete.component.ts:
--------------------------------------------------------------------------------
1 | import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewChild } from "@angular/core";
2 | import { FormControl } from "@angular/forms";
3 | import { MatInput } from "@angular/material";
4 | import { Observable } from "rxjs";
5 | import { debounceTime, filter, map } from "rxjs/operators";
6 |
7 | import { IAutocompleteRow } from "./interfaces";
8 |
9 | @Component({
10 | changeDetection: ChangeDetectionStrategy.OnPush,
11 | selector: "mtp-autocomplete",
12 | templateUrl: "./autocomplete.component.html",
13 | })
14 | export class AutocompleteComponent {
15 | @Input() public title: string = "";
16 | @Input() public iconName: string = "";
17 | @Input() public iconText: string = "";
18 | @Input() public data: IAutocompleteRow[] = [];
19 | @Input() public clearOnSelect: boolean = false;
20 |
21 | @Output() public readonly selected: EventEmitter = new EventEmitter();
22 | @Output() public readonly iconClicked: EventEmitter = new EventEmitter();
23 |
24 | @ViewChild("queryInput") public queryInput!: MatInput;
25 |
26 | public data$: Observable;
27 | public queryControl: FormControl;
28 |
29 | constructor() {
30 | this.queryControl = new FormControl(undefined);
31 | this.data$ = this.queryControl.valueChanges
32 | .pipe(
33 | filter((val) => typeof val === "string"),
34 | debounceTime(300),
35 | map((query: string) => {
36 | const searchPhrases: string[] = (query || "")
37 | .toLowerCase()
38 | .replace(/\s+/, " ")
39 | .split(" ")
40 | .map((x) => x.replace(/[^а-яёa-z\d]/gi, ""));
41 | return this.data.filter((x) => {
42 | const lowerName: string = x.name.toLowerCase();
43 | let ix: number = 0;
44 | for (const searchPhrase of searchPhrases) {
45 | // important order
46 | ix = lowerName/* .substring(ix) */.indexOf(searchPhrase);
47 | if (ix < 0) {
48 | return false;
49 | }
50 | }
51 |
52 | return true;
53 | });
54 | }),
55 | );
56 | }
57 |
58 | public onSelected(row: IAutocompleteRow): void {
59 | this.queryControl.patchValue(row.name);
60 | this.selected.emit(row);
61 |
62 | if (this.clearOnSelect === true) {
63 | this.queryControl.markAsPristine();
64 | this.queryControl.markAsUntouched();
65 |
66 | if (this.queryInput) {
67 | this.queryInput.value = "";
68 | }
69 |
70 | this.queryControl.setValue(null);
71 | }
72 | }
73 |
74 | public onIconClick(): void {
75 | this.iconClicked.emit();
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/DevActivator/ClientApp/app/components/meetup-list/meetup-list.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ChangeDetectionStrategy,
3 | ChangeDetectorRef,
4 | Component,
5 | EventEmitter,
6 | Input,
7 | OnDestroy,
8 | OnInit,
9 | Output,
10 | ViewChild,
11 | } from "@angular/core";
12 | import { AutocompleteComponent, IAutocompleteRow } from "@dotnetru/shared/autocomplete";
13 | import { BehaviorSubject, Subscription } from "rxjs";
14 | import { debounceTime, switchMap } from "rxjs/operators";
15 |
16 | import { MeetupListService } from "./meetup-list.service";
17 |
18 | @Component({
19 | changeDetection: ChangeDetectionStrategy.OnPush,
20 | selector: "mtp-meetup-list",
21 | templateUrl: "./meetup-list.component.html",
22 | })
23 | export class MeetupListComponent implements OnInit, OnDestroy {
24 | @Input() public title: string = "Поиск встречи";
25 | @Input() public iconName: string = "add";
26 | @Input() public iconText: string = "Добавить";
27 |
28 | @Input() public set meetupLink(value: { meetupId?: string }) {
29 | if (value && value.meetupId) {
30 | this._meetupId$.next(value.meetupId);
31 | }
32 | }
33 |
34 | @ViewChild("autocomplete") public autocomplete!: AutocompleteComponent;
35 |
36 | @Output() public readonly selected: EventEmitter = new EventEmitter();
37 | @Output() public readonly iconClicked: EventEmitter = new EventEmitter();
38 |
39 | public meetups: IAutocompleteRow[] = [];
40 |
41 | private _meetupId$: BehaviorSubject = new BehaviorSubject("");
42 | private _subs: Subscription[] = [];
43 |
44 | constructor(
45 | private _meetupListService: MeetupListService,
46 | private _changeDetectorRef: ChangeDetectorRef,
47 | ) { }
48 |
49 | public ngOnInit(): void {
50 | this._subs = [
51 | this._meetupListService.meetups$
52 | .subscribe(
53 | (meetups: IAutocompleteRow[]) => {
54 | this.meetups = meetups;
55 | this._changeDetectorRef.detectChanges();
56 | },
57 | ),
58 | this._meetupListService.meetups$.pipe(switchMap((_) => this._meetupId$.pipe()))
59 | .pipe(debounceTime(100))
60 | .subscribe((meetupId: string) => {
61 | const meetup = this.meetups.find((x) => x.id === meetupId);
62 | if (meetup) {
63 | this.autocomplete.queryControl.patchValue(meetup.name);
64 | } else {
65 | console.warn("meetup not found", meetupId);
66 | }
67 | }),
68 | ];
69 |
70 | this._meetupListService.fetchMeetups();
71 | }
72 |
73 | public ngOnDestroy(): void {
74 | this._subs.forEach((x) => x.unsubscribe());
75 | }
76 |
77 | public onSelected(row: IAutocompleteRow): void {
78 | this.selected.emit(row);
79 | }
80 |
81 | public onIconClicked(): void {
82 | this.iconClicked.emit();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/DevActivator.Meetups.BL/MeetupModule.cs:
--------------------------------------------------------------------------------
1 | using Autofac;
2 | using DevActivator.Common.BL.Caching;
3 | using DevActivator.Common.BL.Config;
4 | using DevActivator.Meetups.BL.Interfaces;
5 | using DevActivator.Meetups.BL.Services;
6 |
7 | namespace DevActivator.Meetups.BL
8 | {
9 | public class MeetupModule : Module
10 | where TSpeakerProvider : ISpeakerProvider
11 | where TTalkProvider : ITalkProvider
12 | where TVenueProvider : IVenueProvider
13 | where TFriendProvider : IFriendProvider
14 | where TMeetupProvider : IMeetupProvider
15 | {
16 | private const string PureImplementation = nameof(PureImplementation);
17 |
18 | private readonly Settings _settings;
19 |
20 | public MeetupModule(Settings settings)
21 | {
22 | _settings = settings;
23 | }
24 |
25 | protected override void Load(ContainerBuilder builder)
26 | {
27 | builder.Register(x => _settings).AsSelf().SingleInstance();
28 |
29 | builder.RegisterType().As().SingleInstance();
30 | builder.RegisterType().Named(PureImplementation);
31 | builder.RegisterDecorator(
32 | (c, inner) => new CachedSpeakerService(c.Resolve(), inner), PureImplementation)
33 | .SingleInstance();
34 |
35 | builder.RegisterType().As().SingleInstance();
36 | builder.RegisterType().Named(PureImplementation);
37 | builder.RegisterDecorator(
38 | (c, inner) => new CachedTalkService(c.Resolve(), inner), PureImplementation)
39 | .SingleInstance();
40 |
41 | builder.RegisterType().As().SingleInstance();
42 | builder.RegisterType().Named(PureImplementation);
43 | builder.RegisterDecorator(
44 | (c, inner) => new CachedVenueService(c.Resolve(), inner), PureImplementation)
45 | .SingleInstance();
46 |
47 | builder.RegisterType().As().SingleInstance();
48 | builder.RegisterType().Named