├── DevActivator ├── Views │ ├── _ViewStart.cshtml │ ├── _ViewImports.cshtml │ ├── Home │ │ └── Index.cshtml │ └── Shared │ │ ├── _Layout.cshtml │ │ └── Error.cshtml ├── electron.manifest.json ├── ClientApp │ ├── app │ │ ├── pages │ │ │ ├── timepad │ │ │ │ ├── index.ts │ │ │ │ ├── interfaces.ts │ │ │ │ ├── timepad.component.css │ │ │ │ ├── timepad.module.ts │ │ │ │ └── timepad.service.ts │ │ │ ├── search │ │ │ │ ├── index.ts │ │ │ │ ├── search.component.html │ │ │ │ ├── search.module.ts │ │ │ │ └── search.component.ts │ │ │ ├── friend-editor │ │ │ │ ├── index.ts │ │ │ │ ├── interfaces.ts │ │ │ │ ├── friend-editor.component.css │ │ │ │ ├── pipes.ts │ │ │ │ ├── friend-editor.module.ts │ │ │ │ └── friend-editor.service.ts │ │ │ ├── meetup-editor │ │ │ │ ├── index.ts │ │ │ │ ├── session-editor.component.css │ │ │ │ ├── enums.ts │ │ │ │ ├── meetup-editor.component.css │ │ │ │ ├── constants.ts │ │ │ │ ├── interfaces.ts │ │ │ │ ├── session-editor.component.ts │ │ │ │ ├── session-editor.component.html │ │ │ │ └── meetup-editor.module.ts │ │ │ ├── venue-editor │ │ │ │ ├── index.ts │ │ │ │ ├── venue-editor.component.css │ │ │ │ ├── interfaces.ts │ │ │ │ ├── venue-editor.module.ts │ │ │ │ ├── venue-editor-dialog.component.ts │ │ │ │ └── venue-editor.service.ts │ │ │ ├── talk-editor │ │ │ │ ├── index.ts │ │ │ │ ├── talk-editor.component.css │ │ │ │ ├── interfaces.ts │ │ │ │ ├── talk-editor-dialog.component.ts │ │ │ │ ├── talk-editor.module.ts │ │ │ │ └── talk-editor.service.ts │ │ │ └── speaker-editor │ │ │ │ ├── index.ts │ │ │ │ ├── interfaces.ts │ │ │ │ ├── pipes.ts │ │ │ │ ├── speaker-editor.component.css │ │ │ │ ├── speaker-editor-dialog.component.ts │ │ │ │ ├── speaker-editor.module.ts │ │ │ │ └── speaker-editor.service.ts │ │ ├── components │ │ │ ├── navmenu │ │ │ │ ├── index.ts │ │ │ │ ├── navmenu.component.css │ │ │ │ ├── navmenu.component.ts │ │ │ │ ├── navmenu.module.ts │ │ │ │ └── navmenu.component.html │ │ │ ├── toolbar │ │ │ │ ├── toolbar.component.css │ │ │ │ ├── toolbar.component.html │ │ │ │ ├── toolbar.module.ts │ │ │ │ └── toolbar.component.ts │ │ │ ├── talk-list │ │ │ │ ├── index.ts │ │ │ │ ├── talk-list.component.html │ │ │ │ ├── talk-list.service.ts │ │ │ │ ├── talk-list.module.ts │ │ │ │ └── talk-list.component.ts │ │ │ ├── venue-list │ │ │ │ ├── index.ts │ │ │ │ ├── venue-list.component.html │ │ │ │ ├── venue-list.service.ts │ │ │ │ └── venue-list.module.ts │ │ │ ├── friend-list │ │ │ │ ├── index.ts │ │ │ │ ├── friend-list.component.html │ │ │ │ ├── friend-list.service.ts │ │ │ │ └── friend-list.module.ts │ │ │ ├── meetup-list │ │ │ │ ├── index.ts │ │ │ │ ├── meetup-list.component.html │ │ │ │ ├── meetup-list.service.ts │ │ │ │ ├── meetup-list.module.ts │ │ │ │ └── meetup-list.component.ts │ │ │ └── speaker-list │ │ │ │ ├── index.ts │ │ │ │ ├── speaker-list.component.html │ │ │ │ ├── speaker-list.service.ts │ │ │ │ └── speaker-list.module.ts │ │ ├── shared │ │ │ ├── city-select │ │ │ │ ├── city-select.component.css │ │ │ │ ├── constants.ts │ │ │ │ ├── index.ts │ │ │ │ ├── enums.ts │ │ │ │ ├── city-select.component.html │ │ │ │ ├── city-select.module.ts │ │ │ │ ├── city-name.pipe.ts │ │ │ │ └── city-select.component.ts │ │ │ ├── file-dialog │ │ │ │ ├── enums.ts │ │ │ │ ├── index.ts │ │ │ │ ├── interfaces.ts │ │ │ │ ├── file-dialog.component.html │ │ │ │ ├── file-dialog.module.ts │ │ │ │ ├── file-dialog.component.ts │ │ │ │ └── file.service.ts │ │ │ └── autocomplete │ │ │ │ ├── interfaces.ts │ │ │ │ ├── index.ts │ │ │ │ ├── autocomplete.module.ts │ │ │ │ ├── autocomplete.component.html │ │ │ │ └── autocomplete.component.ts │ │ ├── core │ │ │ ├── interfaces.ts │ │ │ ├── pattern-error-message.pipe.ts │ │ │ ├── required-error-message.pipe.ts │ │ │ ├── index.ts │ │ │ ├── datetime.pipe.ts │ │ │ ├── date-converter.service.ts │ │ │ ├── core.module.ts │ │ │ ├── layout.service.ts │ │ │ └── constants.ts │ │ ├── themes │ │ │ ├── snack-bar.theme.scss │ │ │ └── common.scss │ │ ├── app.component.css │ │ ├── app.component.ts │ │ ├── app.component.html │ │ ├── styles.scss │ │ ├── app.browser.module.ts │ │ ├── app.component.spec.ts │ │ └── app.shared.module.ts │ ├── test │ │ ├── karma.conf.js │ │ └── boot-tests.ts │ └── boot.browser.ts ├── wwwroot │ ├── favicon.ico │ └── material-icons │ │ ├── font.woff2 │ │ └── font.css ├── appsettings.Development.json ├── appsettings.json ├── electron.ps1 ├── Controllers │ ├── HomeController.cs │ ├── TalkController.cs │ ├── VenueController.cs │ ├── MeetupController.cs │ ├── FriendController.cs │ ├── SpeakerController.cs │ └── FileController.cs ├── Program.cs ├── Extensions │ └── MiddlewareExtension.cs ├── Models │ ├── RandomConcatModel.cs │ └── CompositeModel.cs ├── tsconfig.json ├── package.json └── tslint.json ├── DevActivator.Common.BL ├── Config │ ├── IFlatEntity.cs │ ├── IEntity.cs │ ├── Settings.cs │ └── CityTimeZone.cs ├── Extensions │ ├── StringExtension.cs │ └── SettingsExtensions.cs ├── DevActivator.Common.BL.csproj ├── Caching │ ├── ICache.cs │ └── MemCache.cs └── Enums │ └── City.cs ├── DevActivator.Meetups.BL ├── Entities │ ├── FriendReference.cs │ ├── SpeakerReference.cs │ ├── Session.cs │ ├── Friend.cs │ ├── Venue.cs │ ├── Meetup.cs │ ├── Talk.cs │ └── Speaker.cs ├── Models │ ├── AutocompleteRow.cs │ ├── SessionVm.cs │ ├── VenueVm.cs │ ├── MeetupVm.cs │ ├── TalkVm.cs │ └── SpeakerVm.cs ├── Enums │ └── Community.cs ├── Interfaces │ ├── ITalkProvider.cs │ ├── IVenueProvider.cs │ ├── IFriendProvider.cs │ ├── IMeetupProvider.cs │ ├── ISpeakerProvider.cs │ ├── ITalkService.cs │ ├── IVenueService.cs │ ├── IMeetupService.cs │ ├── ISpeakerService.cs │ └── IFriendService.cs ├── DevActivator.Meetups.BL.csproj ├── Helpers │ └── ShellHelper.cs ├── Extensions │ ├── FriendExtensions.cs │ ├── VenueExtensions.cs │ └── TalkExtensions.cs ├── Services │ ├── CachedTalkService.cs │ ├── CachedVenueService.cs │ ├── CachedMeetupService.cs │ ├── CachedFriendService.cs │ ├── CachedSpeakerService.cs │ ├── TalkService.cs │ ├── VenueService.cs │ ├── FriendService.cs │ ├── MeetupService.cs │ └── SpeakerService.cs └── MeetupModule.cs ├── DevActivator.Meetups.DAL ├── Config │ ├── TalkConfig.cs │ ├── VenueConfig.cs │ ├── FriendConfig.cs │ ├── MeetupConfig.cs │ └── SpeakerConfig.cs ├── DevActivator.Meetups.DAL.csproj └── Providers │ ├── TalkProvider.cs │ ├── VenueProvider.cs │ ├── FriendProvider.cs │ ├── MeetupProvider.cs │ └── SpeakerProvider.cs ├── .dockerignore ├── docker-compose.yml ├── readme.md ├── DevActivator.Common.DAL └── DevActivator.Common.DAL.csproj ├── nginx ├── index.html └── default.conf ├── DevActivator.Meetups.Tests ├── DevActivator.Meetups.Tests.csproj └── ProviderTests │ ├── TalkProviderTests.cs │ ├── SpeakerProviderTests.cs │ ├── MeetupProviderTests.cs │ └── FriendProviderTests.cs ├── Dockerfile ├── appveyor.yml ├── .teamcity └── patches │ └── buildTypes │ └── Build.kts └── local.Dockerfile /DevActivator/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /DevActivator/electron.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "executable": "DevActivator" 3 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/timepad/index.ts: -------------------------------------------------------------------------------- 1 | export { TimepadModule } from "./timepad.module"; 2 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/components/navmenu/index.ts: -------------------------------------------------------------------------------- 1 | export { NavMenuModule } from "./navmenu.module"; 2 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/search/index.ts: -------------------------------------------------------------------------------- 1 | export { SearchPageModule } from "./search.module"; 2 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/shared/city-select/city-select.component.css: -------------------------------------------------------------------------------- 1 | mat-form-field { 2 | width: 100%; 3 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/friend-editor/index.ts: -------------------------------------------------------------------------------- 1 | export { FriendEditorModule } from "./friend-editor.module"; 2 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/meetup-editor/index.ts: -------------------------------------------------------------------------------- 1 | export { MeetupEditorModule } from "./meetup-editor.module"; 2 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/meetup-editor/session-editor.component.css: -------------------------------------------------------------------------------- 1 | .mat-form-field { 2 | width: 100%; 3 | } -------------------------------------------------------------------------------- /DevActivator/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DotNetRu/DevActivator/master/DevActivator/wwwroot/favicon.ico -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/components/navmenu/navmenu.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: flex; 3 | flex-direction: column; 4 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/shared/file-dialog/enums.ts: -------------------------------------------------------------------------------- 1 | export enum RejectionReason { 2 | FileType = 1, 3 | FileSize = 2, 4 | } 5 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/shared/autocomplete/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface IAutocompleteRow { 2 | id: string; 3 | name: string; 4 | } 5 | -------------------------------------------------------------------------------- /DevActivator/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 2 | @addTagHelper *, Microsoft.AspNetCore.SpaServices 3 | -------------------------------------------------------------------------------- /DevActivator/wwwroot/material-icons/font.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DotNetRu/DevActivator/master/DevActivator/wwwroot/material-icons/font.woff2 -------------------------------------------------------------------------------- /DevActivator.Common.BL/Config/IFlatEntity.cs: -------------------------------------------------------------------------------- 1 | namespace DevActivator.Common.BL.Config 2 | { 3 | public interface IFlatEntity : IEntity 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/components/toolbar/toolbar.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | } 4 | 5 | mtp-menu { 6 | flex-direction: row; 7 | } -------------------------------------------------------------------------------- /DevActivator.Common.BL/Config/IEntity.cs: -------------------------------------------------------------------------------- 1 | namespace DevActivator.Common.BL.Config 2 | { 3 | public interface IEntity 4 | { 5 | string Id { get; } 6 | } 7 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/components/talk-list/index.ts: -------------------------------------------------------------------------------- 1 | export { TalkListComponent } from "./talk-list.component"; 2 | export { TalkListModule } from "./talk-list.module"; 3 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/core/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface IMessage { 2 | duration: number; 3 | severity: "info" | "warn" | "error"; 4 | text: string; 5 | } 6 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/components/venue-list/index.ts: -------------------------------------------------------------------------------- 1 | export { VenueListComponent } from "./venue-list.component"; 2 | export { VenueListModule } from "./venue-list.module"; 3 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/components/friend-list/index.ts: -------------------------------------------------------------------------------- 1 | export { FriendListComponent } from "./friend-list.component"; 2 | export { FriendListModule } from "./friend-list.module"; 3 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/components/meetup-list/index.ts: -------------------------------------------------------------------------------- 1 | export { MeetupListComponent } from "./meetup-list.component"; 2 | export { MeetupListModule } from "./meetup-list.module"; 3 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/components/speaker-list/index.ts: -------------------------------------------------------------------------------- 1 | export { SpeakerListComponent } from "./speaker-list.component"; 2 | export { SpeakerListModule } from "./speaker-list.module"; 3 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/friend-editor/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface IFriend { 2 | id: string; 3 | name: string; 4 | url: string; 5 | description: string; 6 | } 7 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/venue-editor/index.ts: -------------------------------------------------------------------------------- 1 | export { VenueEditorDialogComponent } from "./venue-editor-dialog.component"; 2 | export { VenueEditorModule } from "./venue-editor.module"; 3 | -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Entities/FriendReference.cs: -------------------------------------------------------------------------------- 1 | namespace DevActivator.Meetups.BL.Entities 2 | { 3 | public class FriendReference 4 | { 5 | public string FriendId { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /DevActivator.Meetups.DAL/Config/TalkConfig.cs: -------------------------------------------------------------------------------- 1 | namespace DevActivator.Meetups.DAL.Config 2 | { 3 | public class TalkConfig 4 | { 5 | public static string DirectoryName => "talks"; 6 | } 7 | } -------------------------------------------------------------------------------- /DevActivator.Meetups.DAL/Config/VenueConfig.cs: -------------------------------------------------------------------------------- 1 | namespace DevActivator.Meetups.DAL.Config 2 | { 3 | public class VenueConfig 4 | { 5 | public static string DirectoryName => "venues"; 6 | } 7 | } -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Entities/SpeakerReference.cs: -------------------------------------------------------------------------------- 1 | namespace DevActivator.Meetups.BL.Entities 2 | { 3 | public class SpeakerReference 4 | { 5 | public string SpeakerId { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /DevActivator.Meetups.DAL/Config/FriendConfig.cs: -------------------------------------------------------------------------------- 1 | namespace DevActivator.Meetups.DAL.Config 2 | { 3 | public class FriendConfig 4 | { 5 | public static string DirectoryName => "friends"; 6 | } 7 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/shared/city-select/constants.ts: -------------------------------------------------------------------------------- 1 | import { City } from "./enums"; 2 | 3 | export const CITIES: City[] = [City.Spb, City.Msk, City.Sar, City.Kry, City.Kzn, City.Nsk, City.Nnv, City.Ufa]; 4 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/shared/city-select/index.ts: -------------------------------------------------------------------------------- 1 | export { CitySelectComponent } from "./city-select.component"; 2 | export { CitySelectModule } from "./city-select.module"; 3 | export { City } from "./enums"; 4 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/themes/snack-bar.theme.scss: -------------------------------------------------------------------------------- 1 | .snack-bar-info .mat-simple-snackbar-action { 2 | color: #40ffbe; 3 | } 4 | 5 | .snack-bar-warn .mat-simple-snackbar-action { 6 | color: #ffe440; 7 | } -------------------------------------------------------------------------------- /DevActivator.Meetups.DAL/Config/MeetupConfig.cs: -------------------------------------------------------------------------------- 1 | namespace DevActivator.Meetups.DAL.Config 2 | { 3 | public static class MeetupConfig 4 | { 5 | public static string DirectoryName => "meetups"; 6 | } 7 | } -------------------------------------------------------------------------------- /DevActivator.Meetups.DAL/Config/SpeakerConfig.cs: -------------------------------------------------------------------------------- 1 | namespace DevActivator.Meetups.DAL.Config 2 | { 3 | public static class SpeakerConfig 4 | { 5 | public static string DirectoryName => "speakers"; 6 | } 7 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/shared/file-dialog/index.ts: -------------------------------------------------------------------------------- 1 | export { RejectionReason } from "./enums"; 2 | export { FileDialogModule } from "./file-dialog.module"; 3 | export { IAcceptedFile, IRejectedFile } from "./interfaces"; 4 | -------------------------------------------------------------------------------- /DevActivator/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/shared/city-select/enums.ts: -------------------------------------------------------------------------------- 1 | export enum City { 2 | Spb = 1, 3 | Msk = 2, 4 | Sar = 3, 5 | Kry = 4, 6 | Kzn = 5, 7 | Nsk = 6, 8 | Nnv = 7, 9 | Ufa = 8, 10 | } 11 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/talk-editor/index.ts: -------------------------------------------------------------------------------- 1 | export { ITalk } from "./interfaces"; 2 | export { TalkEditorDialogComponent } from "./talk-editor-dialog.component"; 3 | export { TalkEditorModule } from "./talk-editor.module"; 4 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/app.component.css: -------------------------------------------------------------------------------- 1 | :host, 2 | .mat-drawer-container { 3 | height: 100%; 4 | display: block; 5 | } 6 | 7 | .router-container { 8 | /* 64px - toolbar */ 9 | height: calc(100% - 64px); 10 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/shared/autocomplete/index.ts: -------------------------------------------------------------------------------- 1 | export { AutocompleteComponent } from "./autocomplete.component"; 2 | export { AutocompleteModule } from "./autocomplete.module"; 3 | export { IAutocompleteRow } from "./interfaces"; 4 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .helm/ 3 | .idea/ 4 | .teamcity/ 5 | .vscode/ 6 | docs 7 | etc. 8 | node_modules 9 | 10 | DevActivator/.vscode/ 11 | DevActivator/node_modules/ 12 | 13 | **/bin/ 14 | **/obj/ 15 | scripts 16 | test 17 | tools 18 | -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Models/AutocompleteRow.cs: -------------------------------------------------------------------------------- 1 | namespace DevActivator.Meetups.BL.Models 2 | { 3 | public class AutocompleteRow 4 | { 5 | public string Id { get; set; } 6 | 7 | public string Name { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/talk-editor/talk-editor.component.css: -------------------------------------------------------------------------------- 1 | .mat-form-field { 2 | width: 100%; 3 | margin: 0 auto; 4 | } 5 | 6 | .actions { 7 | width: 100%; 8 | display: flex; 9 | justify-content: flex-end; 10 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/speaker-editor/index.ts: -------------------------------------------------------------------------------- 1 | export { ISpeaker } from "./interfaces"; 2 | export { SpeakerEditorModule } from "./speaker-editor.module"; 3 | export { SpeakerEditorDialogComponent } from "./speaker-editor-dialog.component"; 4 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/venue-editor/venue-editor.component.css: -------------------------------------------------------------------------------- 1 | .mat-form-field { 2 | width: 100%; 3 | margin: 0 auto; 4 | } 5 | 6 | .actions { 7 | width: 100%; 8 | display: flex; 9 | justify-content: flex-end; 10 | } -------------------------------------------------------------------------------- /DevActivator.Common.BL/Config/Settings.cs: -------------------------------------------------------------------------------- 1 | namespace DevActivator.Common.BL.Config 2 | { 3 | public class Settings 4 | { 5 | public string AuditRepoDirectory { get; set; } 6 | 7 | public int AvatarMaxSize { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /DevActivator/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "Settings": { 8 | "AuditRepoDirectory": "/Users/alex-mbp/repos/Audit", 9 | "AvatarMaxSize": 75000 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/venue-editor/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { City } from "@dotnetru/shared/city-select"; 2 | 3 | export interface IVenue { 4 | id: string; 5 | city: City; 6 | name: string; 7 | address: string; 8 | mapUrl: string; 9 | } 10 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/meetup-editor/enums.ts: -------------------------------------------------------------------------------- 1 | export enum Community { 2 | SpbDotNet = 1, 3 | MskDotNet = 2, 4 | SarDotNet = 3, 5 | KryDotNet = 4, 6 | KznDotNet = 5, 7 | NskDotNet = 6, 8 | NnvDotNet = 7, 9 | UfaDotNet = 8, 10 | } 11 | -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Models/SessionVm.cs: -------------------------------------------------------------------------------- 1 | namespace DevActivator.Meetups.BL.Models 2 | { 3 | public class SessionVm 4 | { 5 | public string TalkId { get; set; } 6 | 7 | public string StartTime { get; set; } 8 | 9 | public string EndTime { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /DevActivator/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Home Page"; 3 | } 4 | 5 | Loading... 6 | 7 | 8 | @section scripts { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/components/navmenu/navmenu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | @Component({ 4 | selector: "mtp-menu", 5 | styleUrls: ["./navmenu.component.css"], 6 | templateUrl: "./navmenu.component.html", 7 | }) 8 | export class NavMenuComponent { 9 | } 10 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/core/pattern-error-message.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from "@angular/core"; 2 | 3 | @Pipe({ name: "patternErrorMessage" }) 4 | export class PatternErrorMessagePipe implements PipeTransform { 5 | public transform = (pattern: string): string => `Регулярка не пройдена: ${pattern}`; 6 | } 7 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/core/required-error-message.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from "@angular/core"; 2 | 3 | @Pipe({ name: "requiredErrorMessage" }) 4 | export class RequiredErrorMessagePipe implements PipeTransform { 5 | public transform = (fieldName: string): string => `"${fieldName}" не заполнено`; 6 | } 7 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/meetup-editor/meetup-editor.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | height: 100%; 4 | } 5 | 6 | .mat-form-field { 7 | width: 100%; 8 | margin: 0 auto; 9 | } 10 | 11 | .actions { 12 | width: 100%; 13 | display: flex; 14 | justify-content: flex-end; 15 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | devactivator: 6 | image: devactivator 7 | build: 8 | context: . 9 | dockerfile: local.Dockerfile 10 | container_name: devactivator 11 | ports: 12 | - "5000:80" 13 | volumes: 14 | - ../Audit:/Users/alex-mbp/repos/Audit -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Entities/Session.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace DevActivator.Meetups.BL.Entities 4 | { 5 | public class Session 6 | { 7 | public string TalkId { get; set; } 8 | 9 | public DateTime StartTime { get; set; } 10 | 11 | public DateTime EndTime { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/friend-editor/friend-editor.component.css: -------------------------------------------------------------------------------- 1 | .mat-form-field { 2 | width: 100%; 3 | margin: 0 auto; 4 | } 5 | 6 | .actions { 7 | width: 100%; 8 | display: flex; 9 | justify-content: flex-end; 10 | } 11 | 12 | .avatar { 13 | max-width: 200px; 14 | max-height: 200px; 15 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from "@angular/core"; 2 | 3 | @Component({ 4 | // tslint:disable-next-line:component-selector 5 | selector: "app", 6 | styleUrls: ["./app.component.css"], 7 | templateUrl: "./app.component.html", 8 | }) 9 | export class AppComponent { 10 | public title = "app"; 11 | } 12 | -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Enums/Community.cs: -------------------------------------------------------------------------------- 1 | namespace DevActivator.Meetups.BL.Enums 2 | { 3 | public enum Community 4 | { 5 | SpbDotNet = 1, 6 | MskDotNet = 2, 7 | SarDotNet = 3, 8 | KryDotNet = 4, 9 | KznDotNet = 5, 10 | NskDotNet = 6, 11 | NnvDotNet = 7, 12 | UfaDotNet = 8 13 | } 14 | } -------------------------------------------------------------------------------- /DevActivator.Common.BL/Extensions/StringExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DevActivator.Common.BL.Enums; 3 | 4 | namespace DevActivator.Common.BL.Extensions 5 | { 6 | public static class StringExtension 7 | { 8 | public static City GetCity(this string id) 9 | => (City) Enum.Parse(typeof(City), id.Substring(0, 3), true); 10 | } 11 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 |
12 | 13 |
-------------------------------------------------------------------------------- /DevActivator.Common.BL/DevActivator.Common.BL.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/speaker-editor/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface ISpeaker { 2 | id: string; 3 | name: string; 4 | companyName: string; 5 | companyUrl?: string; 6 | description: string; 7 | blogUrl?: string; 8 | contactsUrl?: string; 9 | twitterUrl?: string; 10 | habrUrl?: string; 11 | gitHubUrl?: string; 12 | } 13 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/talk-editor/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface ISpeakerReference { 2 | speakerId: string; 3 | } 4 | 5 | export interface ITalk { 6 | id: string; 7 | speakerIds: ISpeakerReference[]; 8 | title: string; 9 | description: string; 10 | codeUrl?: string; 11 | slidesUrl?: string; 12 | videoUrl?: string; 13 | } 14 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/components/talk-list/talk-list.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/components/venue-list/venue-list.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/components/meetup-list/meetup-list.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/components/speaker-list/speaker-list.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/meetup-editor/constants.ts: -------------------------------------------------------------------------------- 1 | import { Community } from "./enums"; 2 | 3 | export const COMMUNITIES: Community[] = [ 4 | Community.SpbDotNet, 5 | Community.MskDotNet, 6 | Community.SarDotNet, 7 | Community.KryDotNet, 8 | Community.KznDotNet, 9 | Community.NskDotNet, 10 | Community.NnvDotNet, 11 | Community.UfaDotNet, 12 | ]; 13 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/friend-editor/pipes.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from "@angular/core"; 2 | 3 | @Pipe({ name: "friendImageUrl" }) 4 | export class FriendImageUrlPipe implements PipeTransform { 5 | public transform(friendId: string): string { 6 | return friendId 7 | ? `/static/db/friends/${friendId}/logo.png` 8 | : ""; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/shared/city-select/city-select.component.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | {{ city | cityName }} 8 | 9 | 10 | -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Entities/Friend.cs: -------------------------------------------------------------------------------- 1 | using DevActivator.Common.BL.Config; 2 | 3 | namespace DevActivator.Meetups.BL.Entities 4 | { 5 | public class Friend : IEntity 6 | { 7 | public string Id { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public string Url { get; set; } 12 | 13 | public string Description { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Entities/Venue.cs: -------------------------------------------------------------------------------- 1 | using DevActivator.Common.BL.Config; 2 | 3 | namespace DevActivator.Meetups.BL.Entities 4 | { 5 | public class Venue : IFlatEntity 6 | { 7 | public string Id { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public string Address { get; set; } 12 | 13 | public string MapUrl { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/speaker-editor/pipes.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from "@angular/core"; 2 | 3 | @Pipe({ name: "speakerImageUrl" }) 4 | export class SpeakerImageUrlPipe implements PipeTransform { 5 | public transform(speakerId: string): string { 6 | return speakerId 7 | ? `/static/db/speakers/${speakerId}/avatar.jpg` 8 | : ""; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/speaker-editor/speaker-editor.component.css: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | height: 100%; 4 | } 5 | 6 | .mat-form-field { 7 | width: 100%; 8 | margin: 0 auto; 9 | } 10 | 11 | .actions { 12 | width: 100%; 13 | display: flex; 14 | justify-content: flex-end; 15 | } 16 | 17 | .avatar { 18 | max-width: 200px; 19 | max-height: 200px; 20 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/shared/file-dialog/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { RejectionReason } from "./enums"; 2 | 3 | export interface IAcceptedFile { 4 | file: File; 5 | } 6 | 7 | export interface IRejectedFile extends IAcceptedFile { 8 | reason: RejectionReason; 9 | } 10 | 11 | export interface IVerifiedFiles { 12 | acceptedFiles: IAcceptedFile[]; 13 | rejectedFiles: IRejectedFile[]; 14 | } 15 | -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Models/VenueVm.cs: -------------------------------------------------------------------------------- 1 | using DevActivator.Common.BL.Enums; 2 | 3 | namespace DevActivator.Meetups.BL.Models 4 | { 5 | public class VenueVm 6 | { 7 | public string Id { get; set; } 8 | 9 | public City City { get; set; } 10 | 11 | public string Name { get; set; } 12 | 13 | public string Address { get; set; } 14 | 15 | public string MapUrl { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /DevActivator.Common.BL/Caching/ICache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Caching.Memory; 4 | 5 | namespace DevActivator.Common.BL.Caching 6 | { 7 | public interface ICache 8 | { 9 | Task GetOrCreateAsync(string key, Func> factory); 10 | 11 | void Remove(string key); 12 | 13 | bool TryGetValue(string key, out T value); 14 | } 15 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/components/friend-list/friend-list.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/core/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | API_ENDPOINTS, 3 | PATTERNS, 4 | LABELS, 5 | MIME_TYPES, 6 | FILE_SIZES, 7 | } from "./constants"; 8 | export { CoreModule } from "./core.module"; 9 | export { LayoutService } from "./layout.service"; 10 | export { HttpService } from "./http.service"; 11 | export { IMessage } from "./interfaces"; 12 | export { DateConverterService } from "./date-converter.service"; 13 | -------------------------------------------------------------------------------- /DevActivator/electron.ps1: -------------------------------------------------------------------------------- 1 | invoke-expression '$Env:ASPNETCORE_ENVIRONMENT = "Development"' 2 | $webpack = Start-Process -FilePath "powershell" -ArgumentList "node node_modules/webpack/bin/webpack.js --watch" -PassThru -NoNewWindow 3 | $dotnet = Start-Process -FilePath "powershell" -ArgumentList "dotnet electronize start" -PassThru -NoNewWindow 4 | 5 | # export ASPNETCORE_ENVIRONMENT="Development" && npm run build:vendor && dotnet run 6 | # dotnet electronize start -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Interfaces/ITalkProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using DevActivator.Meetups.BL.Entities; 4 | 5 | namespace DevActivator.Meetups.BL.Interfaces 6 | { 7 | public interface ITalkProvider 8 | { 9 | Task> GetAllTalksAsync(); 10 | 11 | Task GetTalkOrDefaultAsync(string talkId); 12 | 13 | Task SaveTalkAsync(Talk talk); 14 | } 15 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # DevActivator 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/rss66u870o940m9a?svg=true)](https://ci.appveyor.com/project/AnatolyKulakov/devactivator) 4 | 5 | ## Run develop 6 | 7 | ```sh 8 | npm start 9 | ``` 10 | 11 | ## Docker build 12 | 13 | ```sh 14 | docker image build -t dotnetru/devactivator:latest . 15 | ``` 16 | 17 | ## Docker run 18 | ```sh 19 | docker run -p 3000:80 --rm dotnetru/devactivator:latest 20 | ``` 21 | -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Interfaces/IVenueProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using DevActivator.Meetups.BL.Entities; 4 | 5 | namespace DevActivator.Meetups.BL.Interfaces 6 | { 7 | public interface IVenueProvider 8 | { 9 | Task> GetAllVenuesAsync(); 10 | 11 | Task GetVenueOrDefaultAsync(string venueId); 12 | 13 | Task SaveVenueAsync(Venue venue); 14 | } 15 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/core/datetime.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from "@angular/core"; 2 | import { Moment } from "moment"; 3 | import { DateConverterService } from "./date-converter.service"; 4 | 5 | @Pipe({ name: "datetime" }) 6 | export class DatetimePipe implements PipeTransform { 7 | public transform(date: Moment | undefined | null, format?: string): string { 8 | return DateConverterService.toString(date, format); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Interfaces/IFriendProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using DevActivator.Meetups.BL.Entities; 4 | 5 | namespace DevActivator.Meetups.BL.Interfaces 6 | { 7 | public interface IFriendProvider 8 | { 9 | Task> GetAllFriendsAsync(); 10 | 11 | Task GetFriendOrDefaultAsync(string friendId); 12 | 13 | Task SaveFriendAsync(Friend friend); 14 | } 15 | } -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Interfaces/IMeetupProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using DevActivator.Meetups.BL.Entities; 4 | 5 | namespace DevActivator.Meetups.BL.Interfaces 6 | { 7 | public interface IMeetupProvider 8 | { 9 | Task> GetAllMeetupsAsync(); 10 | 11 | Task GetMeetupOrDefaultAsync(string meetupId); 12 | 13 | Task SaveMeetupAsync(Meetup meetup); 14 | } 15 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/shared/file-dialog/file-dialog.component.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Interfaces/ISpeakerProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using DevActivator.Meetups.BL.Entities; 4 | 5 | namespace DevActivator.Meetups.BL.Interfaces 6 | { 7 | public interface ISpeakerProvider 8 | { 9 | Task> GetAllSpeakersAsync(); 10 | 11 | Task GetSpeakerOrDefaultAsync(string speakerId); 12 | 13 | Task SaveSpeakerAsync(Speaker speaker); 14 | } 15 | } -------------------------------------------------------------------------------- /DevActivator.Common.DAL/DevActivator.Common.DAL.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Interfaces/ITalkService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using DevActivator.Meetups.BL.Models; 4 | 5 | namespace DevActivator.Meetups.BL.Interfaces 6 | { 7 | public interface ITalkService 8 | { 9 | Task> GetAllTalksAsync(); 10 | 11 | Task GetTalkAsync(string talkId); 12 | 13 | Task AddTalkAsync(TalkVm talk); 14 | 15 | Task UpdateTalkAsync(TalkVm talk); 16 | } 17 | } -------------------------------------------------------------------------------- /DevActivator/Controllers/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace DevActivator.Controllers 5 | { 6 | public class HomeController : Controller 7 | { 8 | public IActionResult Index() 9 | { 10 | return View(); 11 | } 12 | 13 | public IActionResult Error() 14 | { 15 | ViewData["RequestId"] = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 16 | return View(); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Interfaces/IVenueService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using DevActivator.Meetups.BL.Models; 4 | 5 | namespace DevActivator.Meetups.BL.Interfaces 6 | { 7 | public interface IVenueService 8 | { 9 | Task> GetAllVenuesAsync(); 10 | 11 | Task GetVenueAsync(string venueId); 12 | 13 | Task AddVenueAsync(VenueVm venue); 14 | 15 | Task UpdateVenueAsync(VenueVm venue); 16 | } 17 | } -------------------------------------------------------------------------------- /DevActivator/Program.cs: -------------------------------------------------------------------------------- 1 | using ElectronNET.API; 2 | using Microsoft.AspNetCore; 3 | using Microsoft.AspNetCore.Hosting; 4 | 5 | namespace DevActivator 6 | { 7 | public class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | BuildWebHost(args).Run(); 12 | } 13 | 14 | public static IWebHost BuildWebHost(string[] args) 15 | { 16 | return WebHost.CreateDefaultBuilder(args) 17 | .UseElectron(args) 18 | .UseStartup() 19 | .Build(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Interfaces/IMeetupService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using DevActivator.Meetups.BL.Models; 4 | 5 | namespace DevActivator.Meetups.BL.Interfaces 6 | { 7 | public interface IMeetupService 8 | { 9 | Task> GetAllMeetupsAsync(); 10 | 11 | Task GetMeetupAsync(string meetupId); 12 | 13 | Task AddMeetupAsync(MeetupVm meetup); 14 | 15 | Task UpdateMeetupAsync(MeetupVm meetup); 16 | } 17 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/shared/file-dialog/file-dialog.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { MatButtonModule, MatIconModule } from "@angular/material"; 3 | 4 | import { FileDialogComponent } from "./file-dialog.component"; 5 | 6 | @NgModule({ 7 | declarations: [ 8 | FileDialogComponent, 9 | ], 10 | exports: [ 11 | FileDialogComponent, 12 | ], 13 | imports: [ 14 | MatIconModule, 15 | MatButtonModule, 16 | ], 17 | }) 18 | export class FileDialogModule { 19 | } 20 | -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Interfaces/ISpeakerService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using DevActivator.Meetups.BL.Models; 4 | 5 | namespace DevActivator.Meetups.BL.Interfaces 6 | { 7 | public interface ISpeakerService 8 | { 9 | Task> GetAllSpeakersAsync(); 10 | 11 | Task GetSpeakerAsync(string speakerId); 12 | 13 | Task AddSpeakerAsync(SpeakerVm speaker); 14 | 15 | Task UpdateSpeakerAsync(SpeakerVm speaker); 16 | } 17 | } -------------------------------------------------------------------------------- /DevActivator/Views/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | @ViewData["Title"] - DevActivator 7 | 8 | 9 | 10 | 11 | 12 | 13 | @RenderBody() 14 | 15 | @RenderSection("scripts", required: false) 16 | 17 | 18 | -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Interfaces/IFriendService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using DevActivator.Meetups.BL.Entities; 4 | using DevActivator.Meetups.BL.Models; 5 | 6 | namespace DevActivator.Meetups.BL.Interfaces 7 | { 8 | public interface IFriendService 9 | { 10 | Task> GetAllFriendsAsync(); 11 | 12 | Task GetFriendAsync(string friendId); 13 | 14 | Task AddFriendAsync(Friend friend); 15 | 16 | Task UpdateFriendAsync(Friend friend); 17 | } 18 | } -------------------------------------------------------------------------------- /DevActivator/Extensions/MiddlewareExtension.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DevActivator.Middlewares; 3 | using Microsoft.AspNetCore.Builder; 4 | 5 | namespace DevActivator.Extensions 6 | { 7 | public static class MiddlewareExtension 8 | { 9 | public static string FormatMessageLine(this string str) 10 | => $"{Environment.NewLine}{str ?? string.Empty}{Environment.NewLine}{Environment.NewLine}"; 11 | 12 | public static IApplicationBuilder UseExceptionMiddleware(this IApplicationBuilder builder) 13 | => builder.UseMiddleware(); 14 | } 15 | } -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Models/MeetupVm.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using DevActivator.Meetups.BL.Entities; 3 | using DevActivator.Meetups.BL.Enums; 4 | 5 | namespace DevActivator.Meetups.BL.Models 6 | { 7 | public class MeetupVm 8 | { 9 | public string Id { get; set; } 10 | 11 | public string Name { get; set; } 12 | 13 | public Community CommunityId { get; set; } 14 | 15 | public List FriendIds { get; set; } 16 | 17 | public string VenueId { get; set; } 18 | 19 | public List Sessions { get; set; } 20 | } 21 | } -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Models/TalkVm.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using DevActivator.Meetups.BL.Entities; 3 | 4 | namespace DevActivator.Meetups.BL.Models 5 | { 6 | public class TalkVm 7 | { 8 | public string Id { get; set; } 9 | 10 | public List SpeakerIds { get; set; } 11 | 12 | public string Title { get; set; } 13 | 14 | public string Description { get; set; } 15 | 16 | public string CodeUrl { get; set; } 17 | 18 | public string SlidesUrl { get; set; } 19 | 20 | public string VideoUrl { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /DevActivator.Meetups.DAL/DevActivator.Meetups.DAL.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /DevActivator/Models/RandomConcatModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using DevActivator.Meetups.BL.Models; 3 | 4 | namespace DevActivator.Models 5 | { 6 | public class RandomConcatModel 7 | { 8 | public string Name { get; set; } 9 | 10 | public string CommunityId { get; set; } 11 | 12 | public string VenueId { get; set; } 13 | 14 | public List Sessions { get; set; } 15 | 16 | public List TalkIds { get; set; } 17 | 18 | public List SpeakerIds { get; set; } 19 | 20 | public List FriendIds { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Entities/Meetup.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Xml.Serialization; 3 | using DevActivator.Common.BL.Config; 4 | 5 | namespace DevActivator.Meetups.BL.Entities 6 | { 7 | public class Meetup : IFlatEntity 8 | { 9 | public string Id { get; set; } 10 | 11 | public string Name { get; set; } 12 | 13 | public string CommunityId { get; set; } 14 | 15 | [XmlArrayItem("FriendId")] 16 | public List FriendIds { get; set; } 17 | 18 | public string VenueId { get; set; } 19 | 20 | public List Sessions { get; set; } 21 | } 22 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/components/navmenu/navmenu.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { MatButtonModule, MatIconModule, MatMenuModule } from "@angular/material"; 3 | import { RouterModule } from "@angular/router"; 4 | 5 | import { NavMenuComponent } from "./navmenu.component"; 6 | 7 | @NgModule({ 8 | declarations: [ 9 | NavMenuComponent, 10 | ], 11 | exports: [ 12 | NavMenuComponent, 13 | ], 14 | imports: [ 15 | RouterModule, 16 | 17 | MatButtonModule, 18 | MatIconModule, 19 | MatMenuModule, 20 | ], 21 | }) 22 | export class NavMenuModule { } 23 | -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/DevActivator.Meetups.BL.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | DevActivator.Meetups.BL 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/core/date-converter.service.ts: -------------------------------------------------------------------------------- 1 | import * as moment from "moment"; 2 | import { Moment } from "moment"; 3 | 4 | moment.locale("ru"); 5 | 6 | export class DateConverterService { 7 | public static toMoment(date: string): Moment { 8 | return moment(date).clone(); 9 | } 10 | 11 | public static toString(date?: Moment | null, format?: string): string { 12 | if (!date) { 13 | return ""; 14 | } 15 | 16 | return date.format(format || "YYYY-MM-DDTHH:mm:ss"); 17 | } 18 | 19 | public static toApiString(date: Moment): string { 20 | return DateConverterService.toString(date); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /DevActivator/wwwroot/material-icons/font.css: -------------------------------------------------------------------------------- 1 | /* fallback */ 2 | 3 | @font-face { 4 | font-family: 'Material Icons'; 5 | font-style: normal; 6 | font-weight: 400; 7 | src: url(/material-icons/font.woff2) format('woff2'); 8 | } 9 | 10 | .material-icons { 11 | font-family: 'Material Icons'; 12 | font-weight: normal; 13 | font-style: normal; 14 | font-size: 24px; 15 | line-height: 1; 16 | letter-spacing: normal; 17 | text-transform: none; 18 | display: inline-block; 19 | white-space: nowrap; 20 | word-wrap: normal; 21 | direction: ltr; 22 | -webkit-font-feature-settings: 'liga'; 23 | -webkit-font-smoothing: antialiased; 24 | } -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Entities/Talk.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Xml.Serialization; 3 | using DevActivator.Common.BL.Config; 4 | 5 | namespace DevActivator.Meetups.BL.Entities 6 | { 7 | public class Talk : IFlatEntity 8 | { 9 | public string Id { get; set; } 10 | 11 | [XmlArrayItem("SpeakerId")] 12 | public List SpeakerIds { get; set; } 13 | 14 | public string Title { get; set; } 15 | 16 | public string Description { get; set; } 17 | 18 | public string CodeUrl { get; set; } 19 | 20 | public string SlidesUrl { get; set; } 21 | 22 | public string VideoUrl { get; set; } 23 | } 24 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/components/toolbar/toolbar.component.html: -------------------------------------------------------------------------------- 1 |
3 | 5 |
6 | 7 | 8 | 15 | 16 | 17 | DevActivator v0.0.1-alpha 18 | 19 | -------------------------------------------------------------------------------- /DevActivator.Common.BL/Enums/City.cs: -------------------------------------------------------------------------------- 1 | namespace DevActivator.Common.BL.Enums 2 | { 3 | public enum City 4 | { 5 | /// Санкт-Петербург 6 | Spb = 1, 7 | 8 | /// Москва 9 | Msk = 2, 10 | 11 | /// Саратов 12 | Sar = 3, 13 | 14 | /// Красноярск 15 | Kry = 4, 16 | 17 | /// Казань 18 | Kzn = 5, 19 | 20 | /// Новосибирск 21 | Nsk = 6, 22 | 23 | /// Нижний Новгород 24 | Nnv = 7, 25 | 26 | /// Уфа 27 | Ufa = 8 28 | } 29 | } -------------------------------------------------------------------------------- /nginx/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | Home Page - DevActivator 9 | 10 | 11 | 13 | 15 | 16 | 17 | 18 | Loading!111... 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Entities/Speaker.cs: -------------------------------------------------------------------------------- 1 | using DevActivator.Common.BL.Config; 2 | 3 | namespace DevActivator.Meetups.BL.Entities 4 | { 5 | public class Speaker : IEntity 6 | { 7 | public string Id { get; set; } 8 | 9 | public string Name { get; set; } 10 | 11 | public string CompanyName { get; set; } 12 | 13 | public string CompanyUrl { get; set; } 14 | 15 | public string Description { get; set; } 16 | 17 | public string BlogUrl { get; set; } 18 | 19 | public string ContactsUrl { get; set; } 20 | 21 | public string TwitterUrl { get; set; } 22 | 23 | public string HabrUrl { get; set; } 24 | 25 | public string GitHubUrl { get; set; } 26 | } 27 | } -------------------------------------------------------------------------------- /DevActivator.Meetups.BL/Models/SpeakerVm.cs: -------------------------------------------------------------------------------- 1 | namespace DevActivator.Meetups.BL.Models 2 | { 3 | public class SpeakerVm 4 | { 5 | public string Id { get; set; } 6 | 7 | public string Name { get; set; } 8 | 9 | public string CompanyName { get; set; } 10 | 11 | public string CompanyUrl { get; set; } 12 | 13 | public string Description { get; set; } 14 | 15 | public string BlogUrl { get; set; } 16 | 17 | public string ContactsUrl { get; set; } 18 | 19 | public string TwitterUrl { get; set; } 20 | 21 | public string HabrUrl { get; set; } 22 | 23 | public string GitHubUrl { get; set; } 24 | 25 | public string LastUpdateDate { get; set; } 26 | } 27 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/meetup-editor/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { Moment } from "moment"; 2 | 3 | import { Community } from "./enums"; 4 | 5 | export interface IFriendReference { 6 | friendId: string; 7 | } 8 | 9 | export interface ISession { 10 | talkId: string; 11 | startTime?: Moment; 12 | endTime?: Moment; 13 | } 14 | 15 | export interface IMeetup { 16 | id: string; 17 | name: string; 18 | communityId: Community; 19 | friendIds: IFriendReference[]; 20 | venueId: string; 21 | sessions: ISession[]; 22 | } 23 | 24 | export type IApiSession = ISession & { 25 | startTime: string; 26 | endTime: string; 27 | }; 28 | 29 | export type IApiMeetup = IMeetup & { 30 | sessions: IApiSession[]; 31 | }; 32 | -------------------------------------------------------------------------------- /DevActivator/Models/CompositeModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using DevActivator.Meetups.BL.Entities; 3 | using DevActivator.Meetups.BL.Enums; 4 | using DevActivator.Meetups.BL.Models; 5 | 6 | namespace DevActivator.Models 7 | { 8 | public class CompositeModel 9 | { 10 | public string Id { get; set; } 11 | 12 | public string Name { get; set; } 13 | 14 | public Community? CommunityId { get; set; } 15 | 16 | public VenueVm Venue { get; set; } 17 | 18 | public List Sessions { get; set; } 19 | 20 | public Dictionary Talks { get; set; } 21 | 22 | public Dictionary Speakers { get; set; } 23 | 24 | public List Friends { get; set; } 25 | } 26 | } -------------------------------------------------------------------------------- /DevActivator.Common.BL/Caching/MemCache.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.Extensions.Caching.Memory; 4 | 5 | namespace DevActivator.Common.BL.Caching 6 | { 7 | public class MemCache : ICache 8 | { 9 | private readonly IMemoryCache _cache; 10 | 11 | public MemCache(IMemoryCache cache) 12 | { 13 | _cache = cache; 14 | } 15 | 16 | public Task GetOrCreateAsync(string key, Func> factory) 17 | => _cache.GetOrCreateAsync(key, factory); 18 | 19 | public void Remove(string key) 20 | => _cache.Remove(key); 21 | 22 | public bool TryGetValue(string key, out T value) 23 | => _cache.TryGetValue(key, out value); 24 | } 25 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/styles.scss: -------------------------------------------------------------------------------- 1 | @import '~@angular/material/theming'; 2 | @include mat-core(); 3 | $app-primary: mat-palette($mat-indigo); 4 | $app-accent: mat-palette($mat-pink); 5 | $app-warn: mat-palette($mat-red); 6 | // Custom Sass colors vars (will be available in all the project) 7 | $primary: mat-color($app-primary); 8 | $accent: mat-color($app-accent); 9 | $warn: mat-color($app-warn); 10 | $theme: mat-light-theme($app-primary, $app-accent, $app-warn); 11 | @include angular-material-theme($theme); 12 | @import '~@mat-datetimepicker/core/datetimepicker/datetimepicker-theme.scss'; 13 | // Using the $theme variable from the pre-built theme you can call the theming function 14 | @include mat-datetimepicker-theme($theme); 15 | @import "./themes/snack-bar.theme.scss"; 16 | @import "./themes/common.scss"; -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/app.browser.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { BrowserModule } from "@angular/platform-browser"; 3 | import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; 4 | 5 | import { AppComponent } from "./app.component"; 6 | import { AppModuleShared } from "./app.shared.module"; 7 | 8 | @NgModule({ 9 | bootstrap: [AppComponent], 10 | imports: [ 11 | BrowserModule, 12 | BrowserAnimationsModule, 13 | AppModuleShared, 14 | ], 15 | providers: [ 16 | { provide: "BASE_URL", useFactory: getBaseUrl }, 17 | ], 18 | }) 19 | export class AppModule { 20 | } 21 | 22 | export function getBaseUrl() { 23 | return localStorage.getItem("BASE_URL") || document.getElementsByTagName("base")[0].href; 24 | } 25 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/shared/city-select/city-select.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from "@angular/common"; 2 | import { NgModule } from "@angular/core"; 3 | import { FormsModule } from "@angular/forms"; 4 | import { MatFormFieldModule, MatSelectModule } from "@angular/material"; 5 | 6 | import { CityNamePipe } from "./city-name.pipe"; 7 | import { CitySelectComponent } from "./city-select.component"; 8 | 9 | @NgModule({ 10 | declarations: [ 11 | CityNamePipe, 12 | CitySelectComponent, 13 | ], 14 | exports: [ 15 | CityNamePipe, 16 | CitySelectComponent, 17 | ], 18 | imports: [ 19 | CommonModule, 20 | FormsModule, 21 | 22 | MatFormFieldModule, 23 | MatSelectModule, 24 | ], 25 | }) 26 | export class CitySelectModule { } 27 | -------------------------------------------------------------------------------- /nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | 3 | listen 80; 4 | 5 | sendfile on; 6 | 7 | default_type application/octet-stream; 8 | 9 | 10 | gzip on; 11 | gzip_http_version 1.1; 12 | gzip_disable "MSIE [1-6]\."; 13 | gzip_min_length 1100; 14 | gzip_vary on; 15 | gzip_proxied expired no-cache no-store private auth; 16 | gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript; 17 | gzip_comp_level 9; 18 | 19 | 20 | root /usr/share/nginx/html; 21 | 22 | location ~* \.(eot|otf|ttf|woff|woff2)$ { 23 | add_header Access-Control-Allow-Origin *; 24 | } 25 | 26 | location / { 27 | try_files $uri $uri/ /index.html =404; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/themes/common.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | margin: 0; 5 | padding: 0; 6 | } 7 | 8 | .base-component-container { 9 | display: block; 10 | height: 100%; 11 | overflow: auto; 12 | max-width: 640px; 13 | margin: 0 auto; 14 | } 15 | 16 | .full-width { 17 | width: 100%; 18 | } 19 | 20 | .full-height { 21 | height: 100%; 22 | } 23 | 24 | .actions__space-between { 25 | width: 100%; 26 | display: flex; 27 | justify-content: space-between; 28 | align-items: center; 29 | } 30 | 31 | .actions__right { 32 | width: 100%; 33 | display: flex; 34 | justify-content: flex-end; 35 | } 36 | 37 | .text { 38 | font-family: Roboto, "Helvetica Neue", sans-serif; 39 | } 40 | 41 | .no-wrap { 42 | white-space: nowrap; 43 | } 44 | 45 | .bold { 46 | font-weight: 700; 47 | } -------------------------------------------------------------------------------- /DevActivator.Meetups.Tests/DevActivator.Meetups.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/components/toolbar/toolbar.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | import { 3 | MatButtonModule, 4 | MatIconModule, 5 | MatProgressBarModule, 6 | MatSnackBarModule, 7 | MatToolbarModule, 8 | } from "@angular/material"; 9 | import { CoreModule } from "@dotnetru/core"; 10 | import { NavMenuModule } from "@dotnetru/navmenu"; 11 | 12 | import { ToolbarComponent } from "./toolbar.component"; 13 | 14 | @NgModule({ 15 | declarations: [ 16 | ToolbarComponent, 17 | ], 18 | exports: [ 19 | ToolbarComponent, 20 | ], 21 | imports: [ 22 | CoreModule, 23 | NavMenuModule, 24 | 25 | MatButtonModule, 26 | MatIconModule, 27 | MatProgressBarModule, 28 | MatSnackBarModule, 29 | MatToolbarModule, 30 | ], 31 | }) 32 | export class ToolbarModule { } 33 | -------------------------------------------------------------------------------- /DevActivator.Common.BL/Config/CityTimeZone.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using DevActivator.Common.BL.Enums; 4 | 5 | namespace DevActivator.Common.BL.Config 6 | { 7 | public static class CityTimeZone 8 | { 9 | public static TimeSpan GetTimeZone(this City city) => TimeZoneDic[city]; 10 | 11 | private static readonly Dictionary TimeZoneDic = new Dictionary 12 | { 13 | {City.Spb, TimeSpan.FromHours(3)}, 14 | {City.Msk, TimeSpan.FromHours(3)}, 15 | {City.Sar, TimeSpan.FromHours(4)}, 16 | {City.Kry, TimeSpan.FromHours(7)}, 17 | {City.Kzn, TimeSpan.FromHours(3)}, 18 | {City.Nsk, TimeSpan.FromHours(7)}, 19 | {City.Nnv, TimeSpan.FromHours(3)}, 20 | {City.Ufa, TimeSpan.FromHours(5)}, 21 | }; 22 | } 23 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from "@angular/core"; 2 | 3 | import { DateConverterService } from "./date-converter.service"; 4 | import { DatetimePipe } from "./datetime.pipe"; 5 | import { HttpService } from "./http.service"; 6 | import { LayoutService } from "./layout.service"; 7 | import { PatternErrorMessagePipe } from "./pattern-error-message.pipe"; 8 | import { RequiredErrorMessagePipe } from "./required-error-message.pipe"; 9 | 10 | @NgModule({ 11 | declarations: [ 12 | DatetimePipe, 13 | PatternErrorMessagePipe, 14 | RequiredErrorMessagePipe, 15 | ], 16 | exports: [ 17 | DatetimePipe, 18 | PatternErrorMessagePipe, 19 | RequiredErrorMessagePipe, 20 | ], 21 | providers: [ 22 | DateConverterService, 23 | HttpService, 24 | LayoutService, 25 | ], 26 | }) 27 | export class CoreModule { } 28 | -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/shared/autocomplete/autocomplete.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 | 12 | import { AutocompleteComponent } from "./autocomplete.component"; 13 | 14 | @NgModule({ 15 | declarations: [ 16 | AutocompleteComponent, 17 | ], 18 | exports: [ 19 | AutocompleteComponent, 20 | ], 21 | imports: [ 22 | CommonModule, 23 | ReactiveFormsModule, 24 | 25 | MatAutocompleteModule, 26 | MatButtonModule, 27 | MatFormFieldModule, 28 | MatIconModule, 29 | MatInputModule, 30 | ], 31 | }) 32 | export class AutocompleteModule { } 33 | -------------------------------------------------------------------------------- /DevActivator.Meetups.Tests/ProviderTests/TalkProviderTests.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using DevActivator.Common.BL.Config; 3 | using DevActivator.Meetups.DAL.Providers; 4 | using DevActivator.Meetups.BL.Interfaces; 5 | using Xunit; 6 | 7 | namespace DevActivator.Meetups.Tests.ProviderTests 8 | { 9 | public class TalkProviderTests 10 | { 11 | [Fact] 12 | public async Task TalkSpeakerIdsDeserializationSucceed() 13 | { 14 | // prepare 15 | var settings = new Settings {AuditRepoDirectory = "/Users/alex-mbp/repos/Audit"}; 16 | ITalkProvider talkProvider = new TalkProvider(null, settings); 17 | var testTalkId = "Round-table-Talk-about-Performance"; 18 | 19 | // test 20 | var talk = await talkProvider.GetTalkOrDefaultAsync(testTalkId); 21 | Assert.Equal(6, talk.SpeakerIds.Count); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/shared/autocomplete/autocomplete.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 13 | 15 | {{ data.name }} 16 | 17 | 18 | 19 | 24 | {{ iconName }} 25 | {{ iconText }} 26 | 27 | 28 | -------------------------------------------------------------------------------- /DevActivator.Meetups.DAL/Providers/TalkProvider.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 TalkProvider : BaseProvider, ITalkProvider 13 | { 14 | public TalkProvider(ILogger l, Settings s) : base(l, s, TalkConfig.DirectoryName) 15 | { 16 | } 17 | 18 | public Task> GetAllTalksAsync() 19 | => GetAllAsync(); 20 | 21 | public Task GetTalkOrDefaultAsync(string talkId) 22 | => GetEntityByIdAsync(talkId); 23 | 24 | public Task 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 | 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 | 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(PureImplementation); 49 | builder.RegisterDecorator( 50 | (c, inner) => new CachedFriendService(c.Resolve(), inner), PureImplementation) 51 | .SingleInstance(); 52 | 53 | builder.RegisterType().As().SingleInstance(); 54 | builder.RegisterType().Named(PureImplementation); 55 | builder.RegisterDecorator( 56 | (c, inner) => new CachedMeetupService(c.Resolve(), inner), PureImplementation) 57 | .SingleInstance(); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /DevActivator/ClientApp/app/pages/friend-editor/friend-editor.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | import { Router } from "@angular/router"; 3 | import { API_ENDPOINTS, HttpService, LayoutService } from "@dotnetru/core"; 4 | import { BehaviorSubject, Observable } from "rxjs"; 5 | import { filter, map } from "rxjs/operators"; 6 | 7 | import { IFriend } from "./interfaces"; 8 | 9 | @Injectable() 10 | export class FriendEditorService { 11 | public static getDefaultFriend(): IFriend { 12 | return { id: "", name: "", url: "", description: "" }; 13 | } 14 | 15 | private _friend$: BehaviorSubject = new BehaviorSubject(null); 16 | private _dataStore = { 17 | friend: {} as IFriend, 18 | }; 19 | 20 | public get friend$(): Observable { 21 | return this._friend$.pipe( 22 | filter((x) => x !== null), 23 | map((x) => x as IFriend), 24 | ); 25 | } 26 | 27 | constructor( 28 | private _layoutService: LayoutService, 29 | private _httpService: HttpService, 30 | private _router: Router, 31 | ) { } 32 | 33 | public hasChanges(friend: IFriend): boolean { 34 | return JSON.stringify(friend) !== JSON.stringify(this._dataStore.friend); 35 | } 36 | 37 | public fetchFriend(friendId: string): void { 38 | this._httpService.get( 39 | API_ENDPOINTS.getFriendUrl.replace("{{friendId}}", friendId), 40 | (friend: IFriend) => { 41 | this._dataStore.friend = friend; 42 | this._friend$.next(Object.assign({}, this._dataStore.friend)); 43 | }); 44 | } 45 | 46 | public addFriend(friend: IFriend, cb: (friend: IFriend) => void): void { 47 | this._httpService.post( 48 | API_ENDPOINTS.addFriendUrl, 49 | friend, 50 | (res: IFriend) => { 51 | this._layoutService.showInfo("Друг добавлен успешно"); 52 | cb(res); 53 | }, 54 | ); 55 | } 56 | 57 | public updateFriend(friend: IFriend, cb: () => void): void { 58 | this._httpService.post( 59 | API_ENDPOINTS.updateFriendUrl, 60 | friend, 61 | (x: IFriend) => { 62 | this._layoutService.showInfo("Друг изменён успешно"); 63 | this._dataStore.friend = x; 64 | this._friend$.next(Object.assign({}, this._dataStore.friend)); 65 | cb(); 66 | }, 67 | ); 68 | } 69 | 70 | public reset(): void { 71 | this._friend$.next(Object.assign({}, this._dataStore.friend)); 72 | } 73 | 74 | public storeFriendAvatar(friendId: string, blob: Blob): void { 75 | const url: string = API_ENDPOINTS.storeFriendAvatarUrl 76 | .replace("{{friendId}}", friendId); 77 | 78 | const formData: FormData = new FormData(); 79 | formData.append("formFile", blob, "avatar.jpg"); 80 | 81 | this._httpService.put(url, formData, () => { 82 | this._layoutService.showInfo("Аватар загружен успешно"); 83 | }); 84 | } 85 | } 86 | --------------------------------------------------------------------------------